def _discovery(request): """Returns a JSON file listing the services supported by the server.""" services = request.registry.settings['tokenserver.applications'] discovery = {} discovery["services"] = services discovery["auth"] = request.url.rstrip("/") try: verifier = get_browserid_verifier(request.registry) except ComponentLookupError: pass else: discovery["browserid"] = { "allowed_issuers": verifier.allowed_issuers, "trusted_issuers": verifier.trusted_issuers, } try: verifier = get_oauth_verifier(request.registry) except ComponentLookupError: pass else: discovery["oauth"] = { "default_issuer": verifier.default_issuer, "scope": verifier.scope, "server_url": verifier.server_url, } return discovery
def _validate_oauth_token(request, token): try: verifier = get_oauth_verifier(request.registry) except ComponentLookupError: raise _unauthorized(description='Unsupported') try: with metrics_timer('tokenserver.oauth.verify', request): token = verifier.verify(token) except (fxa.errors.Error, ConnectionError) as e: request.metrics['token.oauth.verify_failure'] = 1 if isinstance(e, fxa.errors.InProtocolError): request.metrics['token.oauth.errno.%s' % e.errno] = 1 # Log a full traceback for errors that are not a simple # "your token was bad and we dont trust it". if not isinstance(e, fxa.errors.TrustError): if not isinstance(e, fxa.errors.InProtocolError): logger.exception("Unexpected verification error") elif e.errno not in OAUTH_EXPECTED_ERRNOS: logger.exception("Unexpected verification error") # Report an appropriate error code. if isinstance(e, ConnectionError): request.metrics['token.oauth.connection_error'] = 1 raise json_error(503, description="Resource is not available") raise _unauthorized("invalid-credentials") request.metrics['token.oauth.verify_success'] = 1 request.validated['authorization'] = token # OAuth clients should send the scoped-key kid in lieu of X-Client-State. # A future enhancement might allow us to learn this from the OAuth # verification response rather than requiring a separate header. kid = request.headers.get('X-KeyID') if kid: try: # The kid combines a timestamp and a hash of the key material, # so we can decode it into equivalent information to what we # get out of a BrowserID assertion. generation, client_state = kid.split("-", 1) generation = int(generation) idpClaims = request.validated['authorization']['idpClaims'] idpClaims['fxa-generation'] = generation client_state = browserid.utils.decode_bytes(client_state) client_state = client_state.encode('hex') if not 1 <= len(client_state) <= 32: raise json_error(400, location='header', name='X-Client-State', description='Invalid client state value') # Sanity-check in case the client sent *both* headers. # If they don't match, the client is definitely confused. if 'X-Client-State' in request.headers: if request.headers['X-Client-State'] != client_state: raise _unauthorized("invalid-client-state") request.validated['client-state'] = client_state except (IndexError, ValueError): raise _unauthorized("invalid-credentials")
def _validate_oauth_token(request, token): try: verifier = get_oauth_verifier(request.registry) except ComponentLookupError: raise _unauthorized(description='Unsupported') try: with metrics_timer('tokenserver.oauth.verify', request): token = verifier.verify(token) except (fxa.errors.Error, ConnectionError) as e: request.metrics['token.oauth.verify_failure'] = 1 if isinstance(e, fxa.errors.InProtocolError): request.metrics['token.oauth.errno.%s' % e.errno] = 1 # Log a full traceback for errors that are not a simple # "your token was bad and we dont trust it". if not isinstance(e, fxa.errors.TrustError): logger.exception("Unexpected verification error") # Report an appropriate error code. if isinstance(e, ConnectionError): request.metrics['token.oauth.connection_error'] = 1 raise json_error(503, description="Resource is not available") raise _unauthorized("invalid-credentials") request.metrics['token.oauth.verify_success'] = 1 request.validated['authorization'] = token # OAuth clients should send the scoped-key kid in lieu of X-Client-State. # A future enhancement might allow us to learn this from the OAuth # verification response rather than requiring a separate header. kid = request.headers.get('X-KeyID') if kid: try: # The kid combines a timestamp and a hash of the key material, # so we can decode it into equivalent information to what we # get out of a BrowserID assertion. generation, client_state = kid.split("-", 1) generation = int(generation) idpClaims = request.validated['authorization']['idpClaims'] idpClaims['fxa-generation'] = generation client_state = browserid.utils.decode_bytes(client_state) client_state = client_state.encode('hex') if not 1 <= len(client_state) <= 32: raise json_error(400, location='header', name='X-Client-State', description='Invalid client state value') # Sanity-check in case the client sent *both* headers. # If they don't match, the client is definitely confused. if 'X-Client-State' in request.headers: if request.headers['X-Client-State'] != client_state: raise _unauthorized("invalid-client-state") request.validated['client-state'] = client_state except (IndexError, ValueError): raise _unauthorized("invalid-credentials")
def mock_oauth_verifier(self, response=None, exc=None): def mock_verify_method(token): if exc is not None: raise exc if response is not None: return response return { "email": token.decode("hex"), "idpClaims": {}, } verifier = get_oauth_verifier(self.config.registry) orig_verify_method = verifier.__dict__.get("verify", None) verifier.__dict__["verify"] = mock_verify_method try: yield None finally: if orig_verify_method is None: del verifier.__dict__["verify"] else: verifier.__dict__["verify"] = orig_verify_method
def _valid_oauth_token(request, token): try: verifier = get_oauth_verifier(request.registry) with metrics_timer('tokenserver.oauth.verify', request): token = verifier.verify(token) except (fxa.errors.Error, ConnectionError) as e: request.metrics['token.oauth.verify_failure'] = 1 if isinstance(e, fxa.errors.InProtocolError): request.metrics['token.oauth.errno.%s' % e.errno] = 1 # Log a full traceback for errors that are not a simple # "your token was bad and we dont trust it". if not isinstance(e, fxa.errors.TrustError): logger.exception("Unexpected verification error") # Report an appropriate error code. if isinstance(e, ConnectionError): request.metrics['token.oauth.connection_error'] = 1 raise json_error(503, description="Resource is not available") raise _unauthorized("invalid-credentials") request.metrics['token.oauth.verify_success'] = 1 request.validated['authorization'] = token # OAuth clients should send the scoped-key kid in lieu of X-Client-State. # A future enhancement might allow us to learn this from the OAuth # verification response rather than requiring a separate header. kid = request.headers.get('X-KeyID') if kid: try: # The kid combines a timestamp and a hash of the key material, # so we can decode it into equivalent information to what we # get out of a BrowserID assertion. generation, client_state = kid.split("-", 1) generation = int(generation) idpClaims = request.validated['authorization']['idpClaims'] idpClaims['fxa-generation'] = generation client_state = browserid.utils.decode_bytes(client_state) client_state = client_state.encode('hex') if not 1 <= len(client_state) <= 32: raise json_error(400, location='header', name='X-Client-State', description='Invalid client state value') # Sanity-check in case the client sent *both* headers. # If they don't match, the client is definitely confused. if 'X-Client-State' in request.headers: if request.headers['X-Client-State'] != client_state: raise _unauthorized("invalid-client-state") request.validated['client-state'] = client_state except (IndexError, ValueError): raise _unauthorized("invalid-credentials") id_key = request.registry.settings.get("fxa.metrics_uid_secret_key") if id_key is None: id_key = 'insecure' email = token['email'] fxa_uid_full = fxa_metrics_hash(email, id_key) # "legacy" key used by heka active_counts.lua request.metrics['uid'] = fxa_uid_full request.metrics['email'] = email # "new" keys use shorter values fxa_uid = fxa_uid_full[:32] request.validated['fxa_uid'] = fxa_uid request.metrics['fxa_uid'] = fxa_uid # There's currently no notion of a "device id" in OAuth. # In future we might be able to use e.g. the refresh token # or some derivative of it here. device = 'none' device_id = fxa_metrics_hash(fxa_uid + device, id_key)[:32] request.validated['device_id'] = device_id request.metrics['device_id'] = device_id