def _verify_token(self, access_token, request): """Verify the token extracted from the Authorization header. This method stores the result in two locations to avoid hitting the auth remote server as much as possible: - on the request object, in case the Pyramid authentication methods like `effective_principals()` or `authenticated_userid()` are called several times during the request cycle; - in the cache backend, to reuse validated token from one request to another (during ``cache_ttl_seconds`` seconds.) """ # First check if this request was already verified. # `request.bound_data` is an attribute provided by Kinto to store # some data that is shared among sub-requests (e.g. default bucket # or batch requests) if REIFY_KEY not in request.bound_data: settings = request.registry.settings hmac_secret = settings['userid_hmac_secret'] cache_ttl = float(facebook_conf(request, 'cache_ttl_seconds')) hmac_token = core_utils.hmac_digest(hmac_secret, access_token) cache_key = 'facebook:verify:{}'.format(hmac_token) payload = request.registry.cache.get(cache_key) if payload is None: # Verify token from Facebook url = facebook_conf(request, 'userinfo_endpoint') params = { 'input_token': access_token, 'access_token': facebook_conf(request, 'app_access_token'), } # XXX: Implement token validation for Facebook resp = requests.get(url, params=params) try: resp.raise_for_status() except requests.exceptions.HTTPError: logger.exception("Facebook Token Protocol Error") raise httpexceptions.HTTPServiceUnavailable() else: body = resp.json() if not body['data']['is_valid']: payload = {} else: payload = body['data'] request.registry.cache.set(cache_key, payload, ttl=cache_ttl) # Save for next call. request.bound_data[REIFY_KEY] = payload.get('user_id') return request.bound_data[REIFY_KEY]
def facebook_token(request): """Return OAuth token from authorization code. """ state = request.validated['querystring']['state'] code = request.validated['querystring']['code'] # Require on-going session stored_redirect = request.registry.cache.get(state) # Make sure we cannot try twice with the same code request.registry.cache.delete(state) if not stored_redirect: error_msg = 'The Facebook Auth session was not found, please re-authenticate.' return http_error(httpexceptions.HTTPRequestTimeout(), errno=ERRORS.MISSING_AUTH_TOKEN, message=error_msg) url = facebook_conf(request, 'token_endpoint') params = { 'client_id': facebook_conf(request, 'client_id'), 'client_secret': facebook_conf(request, 'client_secret'), 'redirect_uri': request.route_url(token.name), 'code': code, } resp = requests.get(url, params=params) if resp.status_code == 400: response_body = resp.json() logger.error( "Facebook Token Validation Failed: {}".format(response_body)) error_details = { 'name': 'code', 'location': 'querystring', 'description': 'Facebook OAuth code validation failed.' } raise_invalid(request, **error_details) try: resp.raise_for_status() except requests.exceptions.HTTPError: logger.exception("Facebook Token Protocol Error") raise httpexceptions.HTTPServiceUnavailable() else: response_body = resp.json() access_token = response_body['access_token'] return httpexceptions.HTTPFound(location='%s%s' % (stored_redirect, access_token))
def forget(self, request): """A no-op. Credentials are sent on every request. Return WWW-Authenticate Realm header for Bearer token. """ return [('WWW-Authenticate', '{} realm="{}"'.format(facebook_conf(request, 'header_type'), self.realm))]
def facebook_ping(request): """Verify if the OAuth server is ready.""" heartbeat_url = facebook_conf(request, 'authorization_endpoint') facebook = None if heartbeat_url is not None: try: timeout = float(facebook_conf(request, 'heartbeat_timeout_seconds')) r = requests.get(heartbeat_url, timeout=timeout) r.raise_for_status() facebook = True except requests.exceptions.HTTPError: pass return facebook
def facebook_login(request): """Helper to redirect client towards Facebook login form.""" state = persist_state(request) params = { 'client_id': facebook_conf(request, 'client_id'), 'redirect_uri': request.route_url(token.name), 'state': state } login_form_url = '{}?{}'.format( facebook_conf(request, 'authorization_endpoint'), urlencode(params)) request.response.status_code = 302 request.response.headers['Location'] = login_form_url return {}
def persist_state(request): """Persist arbitrary string in cache. It will be matched when the user returns from the OAuth server login page. """ state = uuid.uuid4().hex redirect_url = request.validated['querystring']['redirect'] expiration = float(facebook_conf(request, 'cache_ttl_seconds')) request.registry.cache.set(state, redirect_url, expiration) return state
def authorized_redirect(req, **kwargs): authorized = aslist(facebook_conf(req, 'webapp.authorized_domains')) if not req.validated: # Schema was not validated. Give up. return False redirect = req.validated['querystring']['redirect'] domain = urlparse(redirect).netloc if not any((fnmatch(domain, auth) for auth in authorized)): req.errors.add('querystring', 'redirect', 'redirect URL is not authorized')
def unauthenticated_userid(self, request): """Return the Facebook user_id or ``None`` if token could not be verified. """ authorization = request.headers.get('Authorization', '') try: authmeth, token = authorization.split(' ', 1) except ValueError: return None if authmeth.lower() != facebook_conf(request, 'header_type').lower(): return None user_id = self._verify_token(token, request) return user_id