Skip to content

Instantly share code, notes, and snippets.

@s1037989
Created November 14, 2024 01:00
Show Gist options
  • Save s1037989/0119f95d593b700bdef8a0e56e6e9e98 to your computer and use it in GitHub Desktop.
Save s1037989/0119f95d593b700bdef8a0e56e6e9e98 to your computer and use it in GitHub Desktop.
Flickr API OAuth and Upload
package Mojolicious::Plugin::Flickr;
use Mojo::Base 'Mojolicious::Plugin', -signatures;
use Digest::SHA qw(hmac_sha1);
use Mojo::Collection qw(c);
use Mojo::JSON qw(j);
use Mojo::Parameters;
use Mojo::Util qw(b64_encode hmac_sha1_sum md5_sum url_escape);
has app => undef, weak => 1;
has oauth_params => sub ($self) {
return {
oauth_consumer_key => $self->app->flickr->consumer_key,
oauth_nonce => Mojo::Util::md5_sum(time . rand),
oauth_signature_method => 'HMAC-SHA1',
oauth_timestamp => time,
oauth_version => '1.0',
};
};
sub register {
my ($self, $app, $conf) = @_;
$self->app($app);
$conf ||= {};
my $default = {
request_url => 'https://api.flickr.com/services/oauth/request_token',
auth_url => 'https://www.flickr.com/services/oauth/authorize',
callback_url => 'ceduler', # or oob
access_url => 'https://api.flickr.com/services/oauth/access_token',
rest_url => 'https://api.flickr.com/services/rest',
upload_url => 'https://up.flickr.com/services/upload/',
};
die "flickr config is required" unless $app->config->{flickr};
die "flickr.consumer_key config is required" unless $app->config->{flickr}{consumer_key};
die "flickr.consumer_secret config is required" unless $app->config->{flickr}{consumer_secret};
for my $key (c(keys $app->config->{flickr}->%*, keys(%$conf), keys(%$default))->uniq->grep(sub{$_})->@*) {
$app->helper("flickr.$key" => sub ($c) {
my $value = $app->config->{flickr}{$key} || $conf->{$key} || $default->{$key};
return $value && $key =~ /_url$/ ? $c->url_for($value)->to_abs : $value;
});
}
$app->ua->on(prepare => sub ($ua, $tx) {
my $url = $tx->req->url->clone->query(Mojo::Parameters->new)->to_abs->to_string;
$app->log->trace(sprintf 'prepare url: %s', $tx->req->url->to_abs);
$app->log->trace(sprintf 'base url: %s', $url);
$app->log->trace(sprintf 'query: %s', $tx->req->url->query->to_string);
if ($url eq $app->flickr->request_url->to_abs) {
$app->log->trace('------ START REQUEST TOKEN');
$self->_sign_request($tx);
$app->log->trace('------ END REQUEST TOKEN');
}
elsif ($url eq $app->flickr->access_url->to_abs) {
$app->log->trace('------ START ACCESS TOKEN');
$self->_sign_access($tx);
$app->log->trace('------ END ACCESS TOKEN');
}
elsif ($url eq $app->flickr->rest_url->to_abs) {
$app->log->trace('------ START REST');
$self->_sign_rest($tx);
$app->log->trace('------ END REST');
}
elsif ($url eq $app->flickr->upload_url->to_abs) {
$app->log->trace('------ START UPLOAD');
$self->_sign_upload($tx);
$app->log->trace('------ END UPLOAD');
}
});
}
sub _extract_keys ($self, $tx) {
$self->app->log->trace('%s', $tx->req->headers->header('X-Keys'));
my $keys = j($tx->req->headers->header('X-Keys'));
$tx->req->headers->remove('X-Keys');
return $keys;
}
sub _join { join '&', map { url_escape $_ } @_ }
sub _sign ($self, $tx, $keys, $param_str) {
my $base_str = _join($tx->req->method, $tx->req->url->clone->query(Mojo::Parameters->new)->to_string, $param_str);
my $signing_key = _join($self->app->flickr->consumer_secret, $keys->{oauth_token_secret});
my $signature = b64_encode(hmac_sha1($base_str, $signing_key), '');
$self->app->log->trace(sprintf 'param_str: %s', $param_str);
$self->app->log->trace(sprintf 'base_str: %s', $base_str);
$self->app->log->trace(sprintf 'signing_key: %s', $signing_key);
$self->app->log->trace(sprintf 'signature: %s', $signature);
$self->app->log->trace(sprintf 'headers: %s', $tx->req->headers->to_string);
$self->app->log->trace(sprintf 'oauth url: %s', $tx->req->url->to_abs);
return $signature;
}
sub _sign_request ($self, $tx) {
my $keys = {oauth_token_secret => ''};
my $url = $tx->req->url;
$url->query($self->oauth_params);
my $params = $url->query->to_hash;
my $param_str = join '&', map { sprintf '%s=%s', $_, url_escape $params->{$_} } sort keys %$params;
$url->query({oauth_signature => $self->_sign($tx, $keys, $param_str)});
}
sub _sign_access ($self, $tx) {
my $keys = {oauth_token_secret => $self->app->home->child('keys')->child($tx->req->param('oauth_token'))->slurp};
my $url = $tx->req->url;
$url->query($self->oauth_params);
my $params = $url->query->to_hash;
my $param_str = join '&', map { sprintf '%s=%s', $_, url_escape $params->{$_} } sort keys %$params;
$url->query({oauth_signature => $self->_sign($tx, $keys, $param_str)});
}
sub _sign_rest ($self, $tx) {
my $keys = $self->_extract_keys($tx);
my $url = $tx->req->url;
$url->query($self->oauth_params);
$url->query({oauth_token => $keys->{oauth_token}});
my $params = $url->query->to_hash;
my $param_str = join '&', map { sprintf '%s=%s', $_, url_escape $params->{$_} } sort keys %$params;
$url->query({oauth_signature => $self->_sign($tx, $keys, $param_str)});
}
sub _sign_upload ($self, $tx) {
my $keys = $self->_extract_keys($tx);
my $url = $tx->req->url;
my $params = $self->oauth_params;
$params->{api_key} = $params->{oauth_consumer_key};
$params->{oauth_token} = $keys->{oauth_token};
my $param_str = join '&', map { sprintf '%s=%s', $_, url_escape $params->{$_} } grep { !/^(photo)$/ } sort keys %$params;
$params->{oauth_signature} = $self->_sign($tx, $keys, $param_str);
$tx->req->headers->authorization('OAuth ' . join ', ', map { sprintf '%s="%s"', $_, $params->{$_} } sort keys %$params);
}
1;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment