def _valid_browserid_assertion(request, assertion): try: verifier = get_browserid_verifier(request.registry) with metrics_timer('tokenserver.assertion.verify', request): assertion = verifier.verify(assertion) except browserid.errors.Error as e: # Convert CamelCase to under_scores for reporting. error_type = e.__class__.__name__ error_type = re.sub('(?<=.)([A-Z])', r'_\1', error_type).lower() request.metrics['token.assertion.verify_failure'] = 1 request.metrics['token.assertion.%s' % error_type] = 1 # Log a full traceback for errors that are not a simple # "your assertion was bad and we dont trust it". if not isinstance(e, browserid.errors.TrustError): logger.exception("Unexpected verification error") # Report an appropriate error code. if isinstance(e, browserid.errors.ConnectionError): raise json_error(503, description="Resource is not available") if isinstance(e, browserid.errors.ExpiredSignatureError): raise _unauthorized("invalid-timestamp") raise _unauthorized("invalid-credentials") # FxA sign-in confirmation introduced the notion of unverified tokens. # The default value is True to preserve backwards compatibility. try: tokenVerified = assertion['idpClaims']['fxa-tokenVerified'] except KeyError: tokenVerified = True if not tokenVerified: raise _unauthorized("invalid-credentials") # everything sounds good, add the assertion to the list of validated fields # and continue request.metrics['token.assertion.verify_success'] = 1 request.validated['authorization'] = assertion id_key = request.registry.settings.get("fxa.metrics_uid_secret_key") if id_key is None: id_key = 'insecure' email = assertion['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 try: device = assertion['idpClaims']['fxa-deviceId'] if device is None: device = 'none' except KeyError: 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
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 valid_assertion(request): """Validate that the assertion given in the request is correct. If not, add errors in the response so that the client can know what happened. """ token = request.headers.get('Authorization') if token is None: raise _unauthorized() token = token.split() if len(token) != 2: raise _unauthorized() name, assertion = token if name.lower() != 'browserid': resp = _unauthorized(description='Unsupported') resp.www_authenticate = ('BrowserID', {}) raise resp try: verifier = get_verifier() with metrics_timer('tokenserver.assertion.verify', request): assertion = verifier.verify(assertion) except browserid.errors.Error as e: # Convert CamelCase to under_scores for reporting. error_type = e.__class__.__name__ error_type = re.sub('(?<=.)([A-Z])', r'_\1', error_type).lower() request.metrics['token.assertion.verify_failure'] = 1 request.metrics['token.assertion.%s' % error_type] = 1 # Log a full traceback for errors that are not a simple # "your assertion was bad and we dont trust it". if not isinstance(e, browserid.errors.TrustError): logger.exception("Unexpected verification error") # Report an appropriate error code. if isinstance(e, browserid.errors.ConnectionError): raise json_error(503, description="Resource is not available") if isinstance(e, browserid.errors.ExpiredSignatureError): raise _unauthorized("invalid-timestamp") raise _unauthorized("invalid-credentials") # everything sounds good, add the assertion to the list of validated fields # and continue request.metrics['token.assertion.verify_success'] = 1 request.validated['assertion'] = assertion # Include a unique FxA identifier in the logs, but obfuscate # it for privacy purposes. id_key = request.registry.settings.get("fxa.metrics_uid_secret_key") if id_key: email = request.validated['assertion']['email'] request.metrics['uid'] = fxa_metrics_uid(email, id_key)
def valid_assertion(request): """Validate that the assertion given in the request is correct. If not, add errors in the response so that the client can know what happened. """ token = request.headers.get('Authorization') if token is None: raise _unauthorized() token = token.split() if len(token) != 2: raise _unauthorized() name, assertion = token if name.lower() != 'browserid': resp = _unauthorized(description='Unsupported') resp.www_authenticate = ('BrowserID', {}) raise resp def _handle_exception(error_type): # convert CamelCase to camel_case error_type = re.sub('(?<=.)([A-Z])', r'_\1', error_type).lower() request.metrics['token.assertion.verify_failure'] = 1 request.metrics['token.assertion.%s' % error_type] = 1 if error_type == "connection_error": raise json_error(503, description="Resource is not available") if error_type == "expired_signature_error": raise _unauthorized("invalid-timestamp") else: raise _unauthorized("invalid-credentials") try: verifier = get_verifier() with metrics_timer('tokenserver.assertion.verify', request): assertion = verifier.verify(assertion) except BrowserIDError as e: _handle_exception(e.__class__.__name__) # everything sounds good, add the assertion to the list of validated fields # and continue request.metrics['token.assertion.verify_success'] = 1 request.validated['assertion'] = assertion
def _validate_browserid_assertion(request, assertion): try: verifier = get_browserid_verifier(request.registry) except ComponentLookupError: raise _unauthorized(description='Unsupported') try: with metrics_timer('tokenserver.assertion.verify', request): assertion = verifier.verify(assertion) except browserid.errors.Error as e: # Convert CamelCase to under_scores for reporting. error_type = e.__class__.__name__ error_type = re.sub('(?<=.)([A-Z])', r'_\1', error_type).lower() request.metrics['token.assertion.verify_failure'] = 1 request.metrics['token.assertion.%s' % error_type] = 1 # Log a full traceback for errors that are not a simple # "your assertion was bad and we dont trust it". if not isinstance(e, browserid.errors.TrustError): logger.exception("Unexpected verification error") # Report an appropriate error code. if isinstance(e, browserid.errors.ConnectionError): raise json_error(503, description="Resource is not available") if isinstance(e, browserid.errors.ExpiredSignatureError): raise _unauthorized("invalid-timestamp") raise _unauthorized("invalid-credentials") # FxA sign-in confirmation introduced the notion of unverified tokens. # The default value is True to preserve backwards compatibility. try: tokenVerified = assertion['idpClaims']['fxa-tokenVerified'] except KeyError: tokenVerified = True if not tokenVerified: raise _unauthorized("invalid-credentials") # everything sounds good, add the assertion to the list of validated fields # and continue request.metrics['token.assertion.verify_success'] = 1 request.validated['authorization'] = assertion
def return_token(request): """This service does the following process: - validates the BrowserID assertion provided on the Authorization header - allocates when necessary a node to the user for the required service - checks generation numbers and x-client-state header - returns a JSON mapping containing the following values: - **id** -- a signed authorization token, containing the user's id for hthe application and the node. - **secret** -- a secret derived from the shared secret - **uid** -- the user id for this servic - **api_endpoint** -- the root URL for the user for the service. """ # at this stage, we are sure that the assertion, application and version # number were valid, so let's build the authentication token and return it. backend = request.registry.getUtility(INodeAssignment) settings = request.registry.settings email = request.validated['assertion']['email'] try: idp_claims = request.validated['assertion']['idpClaims'] generation = idp_claims['fxa-generation'] if not isinstance(generation, (int, long)): raise _unauthorized("invalid-generation") except KeyError: generation = 0 application = request.validated['application'] version = request.validated['version'] pattern = request.validated['pattern'] service = get_service_name(application, version) client_state = request.validated['client-state'] with metrics_timer('tokenserver.backend.get_user', request): user = backend.get_user(service, email) if not user: allowed = settings.get('tokenserver.allow_new_users', True) if not allowed: raise _unauthorized('invalid-credentials') with metrics_timer('tokenserver.backend.allocate_user', request): user = backend.allocate_user(service, email, generation, client_state) # Update if this client is ahead of previously-seen clients. updates = {} if generation > user['generation']: updates['generation'] = generation if client_state != user['client_state']: # Don't revert from some-client-state to no-client-state. if not client_state: raise _unauthorized('invalid-client-state') # Don't revert to a previous client-state. if client_state in user['old_client_states']: raise _unauthorized('invalid-client-state') # If the IdP has been sending generation numbers, then # don't update client-state without a change in generation number. if user['generation'] > 0 and 'generation' not in updates: raise _unauthorized('invalid-client-state') updates['client_state'] = client_state if updates: with metrics_timer('tokenserver.backend.update_user', request): backend.update_user(service, user, **updates) # Error out if this client is behind some previously-seen client. # This is done after the updates because some other, even more up-to-date # client may have raced with a concurrent update. if user['generation'] > generation: raise _unauthorized("invalid-generation") secrets = settings['tokenserver.secrets'] node_secrets = secrets.get(user['node']) if not node_secrets: raise Exception("The specified node does not have any shared secret") secret = node_secrets[-1] # the last one is the most recent one token_duration = settings.get( 'tokenserver.token_duration', DEFAULT_TOKEN_DURATION ) try: requested_duration = int(request.params["duration"]) except (KeyError, ValueError): pass else: if 0 < requested_duration < token_duration: token_duration = requested_duration token_data = { 'uid': user['uid'], 'node': user['node'], 'expires': int(time.time()) + token_duration, } token = tokenlib.make_token(token_data, secret=secret) secret = tokenlib.get_derived_secret(token, secret=secret) endpoint = pattern.format( uid=user['uid'], service=service, node=user['node'] ) return {'id': token, 'key': secret, 'uid': user['uid'], 'api_endpoint': endpoint, 'duration': token_duration, 'hashalg': tokenlib.DEFAULT_HASHMOD}
def return_token(request): """This service does the following process: - validates the BrowserID or OAuth credentials provided in the Authorization header - allocates when necessary a node to the user for the required service - checks generation number, key-rotation timestamp and x-client-state header for consistency - returns a JSON mapping containing the following values: - **id** -- a signed authorization token, containing the user's id for hthe application and the node. - **secret** -- a secret derived from the shared secret - **uid** -- the user id for this service - **api_endpoint** -- the root URL for the user for the service. """ # at this stage, we are sure that the credentials, application and version # number were valid, so let's build the authentication token and return it. backend = request.registry.getUtility(INodeAssignment) settings = request.registry.settings email = request.validated['authorization']['email'] # The `generation` and `keys_changed_at` fields are both optional. try: idp_claims = request.validated['authorization']['idpClaims'] except KeyError: generation = 0 keys_changed_at = 0 else: generation = idp_claims.get('fxa-generation', 0) if not isinstance(generation, (int, long)): raise _unauthorized("invalid-generation") keys_changed_at = idp_claims.get('fxa-keysChangedAt', 0) if not isinstance(keys_changed_at, (int, long)): raise _unauthorized("invalid-credentials", description="invalid keysChangedAt") application = request.validated['application'] version = request.validated['version'] pattern = request.validated['pattern'] service = get_service_name(application, version) client_state = request.validated['client-state'] with metrics_timer('tokenserver.backend.get_user', request): user = backend.get_user(service, email) if not user: allowed = settings.get('tokenserver.allow_new_users', True) if not allowed: raise _unauthorized('new-users-disabled') with metrics_timer('tokenserver.backend.allocate_user', request): user = backend.allocate_user(service, email, generation, client_state, keys_changed_at=keys_changed_at) # We now perform an elaborate set of consistency checks on the # provided claims, which we expect to behave as follows: # # * `generation` is a monotonic timestamp, and increases every time # there is an authentication-related change on the user's account. # # * `keys_changed_at` is a monotonic timestamp, and increases every time # the user's keys change. This is a type of auth-related change, so # `keys_changed_at` <= `generation` at all times. # # * `client_state` is a key fingerprint and should never change back # to a previously-seen value. # # Callers who provide identity claims that violate any of these rules # either have stale credetials (in which case they should re-authenticate) # or are buggy (in which case we deny them access to the user's data). # # The logic here is slightly complicated by the fact that older versions # of the FxA server may not have been sending all the expected fields, and # that some clients do not report the `generation` timestamp. # Update if this client is ahead of previously-seen clients. updates = {} if generation > user['generation']: updates['generation'] = generation if keys_changed_at > user['keys_changed_at']: # If the caller reports a generation number, then a change # in keys should correspond to a change in generation number. # Unfortunately a previous version of the server that didn't # have `keys_changed_at` support may have already seen and # written the new value of `generation`. The best we can do # here is enforce that `keys_changed_at` <= `generation`. if generation > 0 and generation < keys_changed_at: raise _unauthorized('invalid-keysChangedAt') if generation == 0 and keys_changed_at > user['generation']: updates['generation'] = keys_changed_at updates['keys_changed_at'] = keys_changed_at if client_state != user['client_state']: # Don't revert from some-client-state to no-client-state. if not client_state: raise _invalid_client_state('empty string') # Don't revert to a previous client-state. if client_state in user['old_client_states']: raise _invalid_client_state('stale value') # If we have a generation number, then # don't update client-state without a change in generation number. if generation > 0 and 'generation' not in updates: raise _invalid_client_state('new value with no generation change') # If the IdP has been sending keys_changed_at timestamps, then # don't update client-state without a change in keys_changed_at. if user['keys_changed_at'] > 0 and 'keys_changed_at' not in updates: raise _invalid_client_state( 'new value with no keys_changed_at change') updates['client_state'] = client_state if updates: with metrics_timer('tokenserver.backend.update_user', request): backend.update_user(service, user, **updates) # Error out if this client provided a generation number, but it is behind # the generation number of some previously-seen client. if generation > 0 and user['generation'] > generation: raise _unauthorized("invalid-generation") # Error out if we previously saw a keys_changed_at for this user, but they # haven't provided one or it's earlier than previously seen. This means # that once the IdP starts sending keys_changed_at, we'll error out if it # stops (because we can't generate a proper `fxa_kid` in this case). if user['keys_changed_at'] > 0: if user['keys_changed_at'] > keys_changed_at: raise _unauthorized("invalid-keysChangedAt") secrets = settings['tokenserver.secrets'] node_secrets = secrets.get(user['node']) if not node_secrets: raise Exception("The specified node does not have any shared secret") secret = node_secrets[-1] # the last one is the most recent one # Clients can request a smaller token duration via an undocumented # query parameter, for testing purposes. token_duration = settings.get('tokenserver.token_duration', DEFAULT_TOKEN_DURATION) try: requested_duration = int(request.params["duration"]) except (KeyError, ValueError): pass else: if 0 < requested_duration < token_duration: token_duration = requested_duration token_data = { 'uid': user['uid'], 'node': user['node'], 'expires': int(time.time()) + token_duration, 'fxa_uid': request.validated['fxa_uid'], 'fxa_kid': format_key_id( # Follow FxA behaviour of using generation as a fallback. user['keys_changed_at'] or user['generation'], client_state.decode('hex')), 'hashed_fxa_uid': request.validated['hashed_fxa_uid'], 'hashed_device_id': request.validated['hashed_device_id'] } token = tokenlib.make_token(token_data, secret=secret) secret = tokenlib.get_derived_secret(token, secret=secret) endpoint = pattern.format(uid=user['uid'], service=service, node=user['node']) # To help measure user retention, include the timestamp at which we # first saw this user as part of the logs. request.metrics['uid.first_seen_at'] = user['first_seen_at'] # To help segmented analysis of client-side metrics, we can tell # clients to tag their metrics with a "node type" string that is # at much coarser granularity than the individual node name. try: node_type = settings['tokenserver.node_type_classifier'](user['node']) except KeyError: node_type = None request.metrics['node_type'] = node_type return { 'id': token, 'key': secret, 'uid': user['uid'], 'api_endpoint': endpoint, 'duration': token_duration, 'hashalg': tokenlib.DEFAULT_HASHMOD, # Extra stuff for clients to include in telemetry. 'hashed_fxa_uid': request.validated['hashed_fxa_uid'], 'node_type': node_type, }
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
def return_token(request): """This service does the following process: - validates the BrowserID assertion provided on the Authorization header - allocates when necessary a node to the user for the required service - checks generation numbers and x-client-state header - returns a JSON mapping containing the following values: - **id** -- a signed authorization token, containing the user's id for hthe application and the node. - **secret** -- a secret derived from the shared secret - **uid** -- the user id for this servic - **api_endpoint** -- the root URL for the user for the service. """ # at this stage, we are sure that the assertion, application and version # number were valid, so let's build the authentication token and return it. backend = request.registry.getUtility(INodeAssignment) settings = request.registry.settings email = request.validated['authorization']['email'] try: idp_claims = request.validated['authorization']['idpClaims'] generation = idp_claims['fxa-generation'] if not isinstance(generation, (int, long)): raise _unauthorized("invalid-generation") except KeyError: generation = 0 application = request.validated['application'] version = request.validated['version'] pattern = request.validated['pattern'] service = get_service_name(application, version) client_state = request.validated['client-state'] with metrics_timer('tokenserver.backend.get_user', request): user = backend.get_user(service, email) if not user: allowed = settings.get('tokenserver.allow_new_users', True) if not allowed: raise _unauthorized('new-users-disabled') with metrics_timer('tokenserver.backend.allocate_user', request): user = backend.allocate_user(service, email, generation, client_state) # Update if this client is ahead of previously-seen clients. updates = {} if generation > user['generation']: updates['generation'] = generation if client_state != user['client_state']: # Don't revert from some-client-state to no-client-state. if not client_state: raise _invalid_client_state('empty string') # Don't revert to a previous client-state. if client_state in user['old_client_states']: raise _invalid_client_state('stale value') # If the IdP has been sending generation numbers, then # don't update client-state without a change in generation number. if user['generation'] > 0 and 'generation' not in updates: raise _invalid_client_state('new value with no generation change') updates['client_state'] = client_state if updates: with metrics_timer('tokenserver.backend.update_user', request): backend.update_user(service, user, **updates) # Error out if this client is behind some previously-seen client. # This is done after the updates because some other, even more up-to-date # client may have raced with a concurrent update. if user['generation'] > generation: raise _unauthorized("invalid-generation") secrets = settings['tokenserver.secrets'] node_secrets = secrets.get(user['node']) if not node_secrets: raise Exception("The specified node does not have any shared secret") secret = node_secrets[-1] # the last one is the most recent one # Clients can request a smaller token duration via an undocumented # query parameter, for testing purposes. token_duration = settings.get( 'tokenserver.token_duration', DEFAULT_TOKEN_DURATION ) try: requested_duration = int(request.params["duration"]) except (KeyError, ValueError): pass else: if 0 < requested_duration < token_duration: token_duration = requested_duration token_data = { 'uid': user['uid'], 'node': user['node'], 'expires': int(time.time()) + token_duration, 'fxa_uid': request.validated['fxa_uid'], 'device_id': request.validated['device_id'] } token = tokenlib.make_token(token_data, secret=secret) secret = tokenlib.get_derived_secret(token, secret=secret) endpoint = pattern.format( uid=user['uid'], service=service, node=user['node'] ) # To help measure user retention, include the timestamp at which we # first saw this user as part of the logs. request.metrics['uid.first_seen_at'] = user['first_seen_at'] return { 'id': token, 'key': secret, 'uid': user['uid'], 'hashed_fxa_uid': request.validated['fxa_uid'], 'api_endpoint': endpoint, 'duration': token_duration, 'hashalg': tokenlib.DEFAULT_HASHMOD }
def test_timing_contextmanager_doesnt_fail_if_no_reqest_object(self): with metrics_timer("timer1"): time.sleep(0.01)
def valid_assertion(request): """Validate that the assertion given in the request is correct. If not, add errors in the response so that the client can know what happened. """ token = request.headers.get('Authorization') if token is None: raise _unauthorized() token = token.split() if len(token) != 2: raise _unauthorized() name, assertion = token if name.lower() != 'browserid': resp = _unauthorized(description='Unsupported') resp.www_authenticate = ('BrowserID', {}) raise resp try: verifier = get_verifier() with metrics_timer('tokenserver.assertion.verify', request): assertion = verifier.verify(assertion) except browserid.errors.Error as e: # Convert CamelCase to under_scores for reporting. error_type = e.__class__.__name__ error_type = re.sub('(?<=.)([A-Z])', r'_\1', error_type).lower() request.metrics['token.assertion.verify_failure'] = 1 request.metrics['token.assertion.%s' % error_type] = 1 # Log a full traceback for errors that are not a simple # "your assertion was bad and we dont trust it". if not isinstance(e, browserid.errors.TrustError): logger.exception("Unexpected verification error") # Report an appropriate error code. if isinstance(e, browserid.errors.ConnectionError): raise json_error(503, description="Resource is not available") if isinstance(e, browserid.errors.ExpiredSignatureError): raise _unauthorized("invalid-timestamp") raise _unauthorized("invalid-credentials") # everything sounds good, add the assertion to the list of validated fields # and continue request.metrics['token.assertion.verify_success'] = 1 request.validated['assertion'] = assertion id_key = request.registry.settings.get("fxa.metrics_uid_secret_key") if id_key is None: id_key = 'insecure' email = assertion['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 try: device = assertion['idpClaims']['fxa-deviceId'] if device is None: device = 'none' except KeyError: 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
def viewit(request): with metrics_timer("timer1"): time.sleep(0.01)