def fxa_oauth_params(request): """Helper to give Firefox Account configuration information.""" return { 'client_id': fxa_conf(request, 'client_id'), 'oauth_uri': fxa_conf(request, 'oauth_uri'), 'scope': fxa_conf(request, 'required_scope'), }
def fxa_oauth_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 OAuth session was not found, please re-authenticate.' return http_error(httpexceptions.HTTPRequestTimeout(), errno=ERRORS.MISSING_AUTH_TOKEN, message=error_msg) # Trade the OAuth code for a longer-lived token auth_client = OAuthClient(server_url=fxa_conf(request, 'oauth_uri'), client_id=fxa_conf(request, 'client_id'), client_secret=fxa_conf(request, 'client_secret')) try: token = auth_client.trade_code(code) except fxa_errors.OutOfProtocolError: raise httpexceptions.HTTPServiceUnavailable() except fxa_errors.InProtocolError as error: logger.error(error) error_details = { 'name': 'code', 'location': 'querystring', 'description': 'Firefox Account code validation failed.' } raise_invalid(request, **error_details) return httpexceptions.HTTPFound(location='%s%s' % (stored_redirect, token))
def _get_credentials(self, request): authorization = request.headers.get('Authorization', '') try: authmeth, auth = authorization.split(' ', 1) except ValueError: return None if authmeth.lower() != 'bearer': return None # Use PyFxa defaults if not specified server_url = fxa_conf(request, 'oauth_uri') scope = aslist(fxa_conf(request, 'required_scope')) auth_cache = self._get_cache(request) auth_client = OAuthClient(server_url=server_url, cache=auth_cache) try: profile = auth_client.verify_token(token=auth, scope=scope) user_id = profile['user'] except fxa_errors.OutOfProtocolError as e: logger.error(e) raise httpexceptions.HTTPServiceUnavailable() except (fxa_errors.InProtocolError, fxa_errors.TrustError) as e: logger.info(e) return None return user_id
def fxa_oauth_token(request): """Return OAuth token from authorization code. """ state = request.validated['state'] code = request.validated['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 OAuth session was not found, please re-authenticate.' return http_error(httpexceptions.HTTPRequestTimeout(), errno=ERRORS.MISSING_AUTH_TOKEN, message=error_msg) # Trade the OAuth code for a longer-lived token auth_client = OAuthClient(server_url=fxa_conf(request, 'oauth_uri'), client_id=fxa_conf(request, 'client_id'), client_secret=fxa_conf(request, 'client_secret')) try: token = auth_client.trade_code(code) except fxa_errors.OutOfProtocolError: raise httpexceptions.HTTPServiceUnavailable() except fxa_errors.InProtocolError as error: logger.error(error) error_details = { 'name': 'code', 'location': 'querystring', 'description': 'Firefox Account code validation failed.' } raise_invalid(request, **error_details) return httpexceptions.HTTPFound(location='%s%s' % (stored_redirect, token))
def fxa_oauth_login(request): """Helper to redirect client towards FxA login form.""" state = persist_state(request) form_url = ('{oauth_uri}/authorization?action=signin' '&client_id={client_id}&state={state}&scope={scope}') scopes = fxa_conf(request, 'requested_scope') form_url = form_url.format(oauth_uri=fxa_conf(request, 'oauth_uri'), client_id=fxa_conf(request, 'client_id'), scope='+'.join(scopes.split()), state=state) request.response.status_code = 302 request.response.headers['Location'] = form_url return {}
def authorized_redirect(req): authorized = aslist(fxa_conf(req, 'webapp.authorized_domains')) if 'redirect' not in req.validated: return True domain = urlparse(req.validated['redirect']).netloc if not any((fnmatch(domain, auth) for auth in authorized)): req.errors.add('querystring', 'redirect', 'redirect URL is not authorized')
def fxa_ping(request): """Verify if the OAuth server is ready.""" server_url = fxa_conf(request, 'oauth_uri') oauth = None if server_url is not None: auth_client = OAuthClient(server_url=server_url) server_url = auth_client.server_url oauth = False try: heartbeat_url = urljoin(server_url, '/__heartbeat__') timeout = float(fxa_conf(request, 'heartbeat_timeout_seconds')) r = requests.get(heartbeat_url, timeout=timeout) r.raise_for_status() oauth = True except requests.exceptions.HTTPError: pass return oauth
def _get_cache(self, request): """Instantiate cache when first request comes in. This way, the policy instantiation is decoupled from registry object. """ if self._cache is None: if hasattr(request.registry, 'cache'): cache_ttl = float(fxa_conf(request, 'cache_ttl_seconds')) oauth_cache = TokenVerificationCache(request.registry.cache, ttl=cache_ttl) self._cache = oauth_cache return self._cache
def _verify_token(self, 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) key = 'fxa_verified_token' if key in request.bound_data: return request.bound_data[key] # Use PyFxa defaults if not specified server_url = fxa_conf(request, 'oauth_uri') scope = aslist(fxa_conf(request, 'required_scope')) auth_cache = self._get_cache(request) auth_client = OAuthClient(server_url=server_url, cache=auth_cache) try: profile = auth_client.verify_token(token=token, scope=scope) user_id = profile['user'] except fxa_errors.OutOfProtocolError as e: logger.exception("Protocol error") raise httpexceptions.HTTPServiceUnavailable() except (fxa_errors.InProtocolError, fxa_errors.TrustError) as e: logger.debug("Invalid FxA token: %s" % e) user_id = None # Save for next call. request.bound_data[key] = user_id return user_id
def _verify_token(self, 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: # Use PyFxa defaults if not specified server_url = fxa_conf(request, 'oauth_uri') auth_cache = self._get_cache(request) auth_client = OAuthClient(server_url=server_url, cache=auth_cache) user_id = None client_name = None for scope, client in request.registry._fxa_oauth_scope_routing.items(): try: profile = auth_client.verify_token(token=token, scope=aslist(scope)) user_id = profile['user'] scope = profile['scope'] client_name = client # Make sure the bearer token scopes don't match multiple configs. routing_scopes = request.registry._fxa_oauth_scope_routing intersecting_scopes = [x for x in routing_scopes.keys() if x and set(x.split()).issubset(set(scope))] if len(intersecting_scopes) > 1: logger.warn("Invalid FxA token: {} matches multiple config" % scope) return None, None break except fxa_errors.OutOfProtocolError as e: logger.exception("Protocol error") raise httpexceptions.HTTPServiceUnavailable() except (fxa_errors.InProtocolError, fxa_errors.TrustError) as e: logger.debug("Invalid FxA token: %s" % e) # Save for next call. request.bound_data[REIFY_KEY] = (user_id, client_name) return request.bound_data[REIFY_KEY]
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['redirect'] expiration = float(fxa_conf(request, 'cache_ttl_seconds')) cache = request.registry.cache cache.set(state, redirect_url, expiration) return state
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(fxa_conf(request, 'cache_ttl_seconds')) cache = request.registry.cache cache.set(state, redirect_url, expiration) return state
def _get_auth_client(self, request): """Instantiate OAuthClient on first request but cache it. Hopefully this lets us retain the requests Session in the PyFxA class and keep the HTTP connection alive for longer. """ if self._auth_client is None: # Use PyFxa defaults if not specified server_url = fxa_conf(request, 'oauth_uri') auth_cache = self._get_cache(request) self._auth_client = OAuthClient(server_url=server_url, cache=auth_cache) return self._auth_client
def authorized_redirect(req, **kwargs): authorized = aslist(fxa_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')