"""Flickr OAuth drop-in.
Uses oauthlib directly to authenticate and sign requests with OAuth
1.0 credentials. https://www.flickr.com/services/api/auth.oauth.html
Note that when users decline Flickr's OAuth prompt by clicking the Cancel
button, Flickr redirects them to its home page, *not* to us.
"""
import logging
import oauthlib.oauth1
import urllib.parse, urllib.request
from flask import request
from google.cloud import ndb
from . import flickr_auth, views, models
from .webutil import flask_util, util
from .webutil.util import json_dumps, json_loads
logger = logging.getLogger(__name__)
REQUEST_TOKEN_URL = 'https://www.flickr.com/services/oauth/request_token'
AUTHORIZE_URL = 'https://www.flickr.com/services/oauth/authorize'
AUTHENTICATE_URL = 'https://www.flickr.com/services/oauth/authenticate'
ACCESS_TOKEN_URL = 'https://www.flickr.com/services/oauth/access_token'
API_URL = 'https://api.flickr.com/services/rest'
[docs]class FlickrAuth(models.BaseAuth):
"""An authenticated Flickr user.
Provides methods that return information about this user and make
OAuth-signed requests to the Flickr API. Stores OAuth credentials in
the datastore. Key is the Flickr user ID. See models.BaseAuth for
usage details.
"""
# access token
token_key = ndb.StringProperty(required=True)
token_secret = ndb.StringProperty(required=True)
user_json = ndb.TextProperty(required=True)
[docs] def site_name(self):
return 'Flickr'
[docs] def user_display_name(self):
"""Returns the user id.
"""
return self.key_id()
[docs] def access_token(self):
"""Returns the OAuth access token as a (string key, string secret) tuple.
"""
return (self.token_key, self.token_secret)
def _api(self):
return oauthlib.oauth1.Client(
flickr_auth.FLICKR_APP_KEY,
client_secret=flickr_auth.FLICKR_APP_SECRET,
resource_owner_key=self.token_key,
resource_owner_secret=self.token_secret,
signature_type=oauthlib.oauth1.SIGNATURE_TYPE_QUERY)
[docs] def urlopen(self, url, **kwargs):
return flickr_auth.signed_urlopen(
url, self.token_key, self.token_secret, **kwargs)
def call_api_method(self, method, params):
return flickr_auth.call_api_method(
method, params, self.token_key, self.token_secret)
[docs]class Start(views.Start):
"""Starts three-legged OAuth with Flickr.
Fetches an OAuth request token, then redirects to Flickr's auth page to
request an access token.
"""
NAME = 'flickr'
LABEL = 'Flickr'
[docs] def redirect_url(self, state=None):
assert flickr_auth.FLICKR_APP_KEY and flickr_auth.FLICKR_APP_SECRET, \
"Please fill in the flickr_app_key and flickr_app_secret files in your app's root directory."
# double-URL-encode state because Flickr URL-decodes the redirect URL before
# redirecting to it, and JSON values may have ?s and &s. e.g. the Bridgy
# WordPress plugin's redirect URL when using Bridgy's registration API
# (https://brid.gy/about#registration-api) looks like:
# /wp-admin/admin.php?page=bridgy_options&service=flickr
if state:
state = urllib.parse.quote(state)
client = oauthlib.oauth1.Client(
flickr_auth.FLICKR_APP_KEY,
client_secret=flickr_auth.FLICKR_APP_SECRET,
callback_uri=self.to_url(state))
url, headers, data = client.sign(REQUEST_TOKEN_URL)
resp = util.requests_get(url, headers=headers, data=data)
parsed = urllib.parse.parse_qs(resp.text)
if parsed.get('error') or parsed.get('oauth_problem'):
flask_util.error(resp.text)
resource_owner_key = parsed.get('oauth_token')[0]
resource_owner_secret = parsed.get('oauth_token_secret')[0]
models.OAuthRequestToken(
id=resource_owner_key,
token_secret=resource_owner_secret,
state=state).put()
if self.scope:
auth_url = AUTHORIZE_URL + '?' + urllib.parse.urlencode({
'perms': self.scope or 'read',
'oauth_token': resource_owner_key
})
else:
auth_url = AUTHENTICATE_URL + '?' + urllib.parse.urlencode({
'oauth_token': resource_owner_key
})
logger.info(
'Generated request token, redirect to Flickr authorization url: %s',
auth_url)
return auth_url
[docs]class Callback(views.Callback):
"""The OAuth callback. Fetches an access token and redirects to the
front page.
"""
[docs] def dispatch_request(self):
oauth_token = request.values.get('oauth_token')
oauth_verifier = request.values.get('oauth_verifier')
request_token = models.OAuthRequestToken.get_by_id(oauth_token)
client = oauthlib.oauth1.Client(
flickr_auth.FLICKR_APP_KEY,
client_secret=flickr_auth.FLICKR_APP_SECRET,
resource_owner_key=oauth_token,
resource_owner_secret=request_token.token_secret,
verifier=oauth_verifier)
uri, headers, body = client.sign(ACCESS_TOKEN_URL)
try:
resp = util.urlopen(urllib.request.Request(uri, body, headers))
except BaseException as e:
util.interpret_http_exception(e)
raise
parsed = dict(urllib.parse.parse_qs(resp.read().decode()))
access_token = parsed.get('oauth_token')[0]
access_secret = parsed.get('oauth_token_secret')[0]
user_nsid = parsed.get('user_nsid')[0]
if access_token is None:
flask_util.error('Missing required query parameter oauth_token.')
auth = FlickrAuth(id=user_nsid, token_key=access_token,
token_secret=access_secret)
user_json = auth.call_api_method('flickr.people.getInfo', {'user_id': user_nsid})
auth.user_json = json_dumps(user_json)
auth.put()
return self.finish(auth, state=request.values.get('state'))