Example #1
0
 def test_convenience_functions(self):
     token = tokenlib.make_token({"hello": "world"})
     self.assertEquals(tokenlib.parse_token(token)["hello"], "world")
     self.assertRaises(ValueError, tokenlib.parse_token, token, secret="X")
     self.assertEquals(tokenlib.get_derived_secret(token),
                       tokenlib.get_derived_secret(token))
     self.assertNotEquals(tokenlib.get_derived_secret(token),
                          tokenlib.get_derived_secret(token, secret="X"))
Example #2
0
 def test_convenience_functions(self):
     token = tokenlib.make_token({"hello": "world"})
     self.assertEquals(tokenlib.parse_token(token)["hello"], "world")
     self.assertRaises(ValueError, tokenlib.parse_token, token, secret="X")
     self.assertEquals(tokenlib.get_derived_secret(token),
                       tokenlib.get_derived_secret(token))
     self.assertNotEquals(tokenlib.get_derived_secret(token),
                          tokenlib.get_derived_secret(token, secret="X"))
Example #3
0
    def decode_hawk_id(self, request, tokenid):
        """Decode a Hawk token id into its userid and secret key.

        This method determines the appropriate secrets to use for the given
        request, then passes them on to tokenlib to handle the given Hawk
        token.

        If the id is invalid then ValueError will be raised.
        """
        # There might be multiple secrets in use, if we're in the
        # process of transitioning from one to another.  Try each
        # until we find one that works.
        node_name = self._get_node_name(request)
        secrets = self._get_token_secrets(node_name)
        for secret in secrets:
            try:
                data = tokenlib.parse_token(tokenid, secret=secret)
                userid = data["uid"]
                token_node_name = data["node"]
                if token_node_name != node_name:
                    raise ValueError("incorrect node for this token")
                key = tokenlib.get_derived_secret(tokenid, secret=secret)
                break
            except (ValueError, KeyError):
                pass
        else:
            logger.warn("Authentication Failed: invalid hawk id")
            raise ValueError("invalid Hawk id")
        return userid, key
Example #4
0
    def decode_hawk_id(self, request, tokenid):
        """Decode a Hawk token id into its userid and secret key.

        This method determines the appropriate secrets to use for the given
        request, then passes them on to tokenlib to handle the given Hawk
        token.

        If the id is invalid then ValueError will be raised.
        """
        # There might be multiple secrets in use, if we're in the
        # process of transitioning from one to another.  Try each
        # until we find one that works.
        node_name = self._get_node_name(request)
        secrets = self._get_token_secrets(node_name)
        for secret in secrets:
            try:
                data = tokenlib.parse_token(tokenid, secret=secret)
                userid = data["uid"]
                token_node_name = data["node"]
                if token_node_name != node_name:
                    raise ValueError("incorrect node for this token")
                key = tokenlib.get_derived_secret(tokenid, secret=secret)
                break
            except (ValueError, KeyError):
                pass
        else:
            logger.warn("Authentication Failed: invalid hawk id")
            raise ValueError("invalid Hawk id")
        return userid, key
Example #5
0
def delete_service_data(config, service, user, timeout=60, settings=None):
    """Send a data-deletion request to the user's service node.

    This is a little bit of hackery to cause the user's service node to
    remove any data it still has stored for the user.  We simulate a DELETE
    request from the user's own account.
    """
    secrets = config.registry.settings['tokenserver.secrets']
    pattern = config.registry['endpoints_patterns'][service]
    node_secrets = secrets.get(user.node)
    if not node_secrets:
        msg = "The node %r does not have any shared secret" % (user.node, )
        raise ValueError(msg)
    token = tokenlib.make_token(
        {
            "uid":
            user.uid,
            "node":
            user.node,
            "fxa_uid":
            user.email.split("@", 1)[0],
            "fxa_kid":
            format_key_id(user.keys_changed_at or user.generation,
                          user.client_state.decode('hex')),
        },
        secret=node_secrets[-1])
    secret = tokenlib.get_derived_secret(token, secret=node_secrets[-1])
    endpoint = pattern.format(uid=user.uid, service=service, node=user.node)
    auth = HawkAuth(token, secret)
    if settings and settings.dryrun:
        return
    resp = requests.delete(endpoint, auth=auth, timeout=timeout)
    if resp.status_code >= 400 and resp.status_code != 404:
        resp.raise_for_status()
def delete_service_data(user, secret, timeout=60):
    """Send a data-deletion request to the user's service node.

    This is a little bit of hackery to cause the user's service node to
    remove any data it still has stored for the user.  We simulate a DELETE
    request from the user's own account.
    """
    token = tokenlib.make_token(
        {
            "uid":
            user.uid,
            "node":
            user.node,
            "fxa_uid":
            user.email.split("@", 1)[0],
            "fxa_kid":
            format_key_id(user.keys_changed_at or user.generation,
                          binascii.unhexlify(user.client_state)),
        },
        secret=secret)
    secret = tokenlib.get_derived_secret(token, secret=secret)
    endpoint = PATTERN.format(uid=user.uid, node=user.node)
    auth = HawkAuth(token, secret)
    resp = requests.delete(endpoint, auth=auth, timeout=timeout)
    if resp.status_code >= 400 and resp.status_code != 404:
        resp.raise_for_status()
def delete_service_data(config, service, user, timeout=60):
    """Send a data-deletion request to the user's service node.

    This is a little bit of hackery to cause the user's service node to
    remove any data it still has stored for the user.  We simulate a DELETE
    request from the user's own account.
    """
    secrets = config.registry.settings['tokenserver.secrets']
    pattern = config.registry['endpoints_patterns'][service]
    node_secrets = secrets.get(user.node)
    if not node_secrets:
        msg = "The node %r does not have any shared secret" % (user.node,)
        raise ValueError(msg)
    token = tokenlib.make_token({
        "uid": user.uid,
        "node": user.node,
        "fxa_uid": user.email.split("@", 1)[0],
        "fxa_kid": user.client_state
    }, secret=node_secrets[-1])
    secret = tokenlib.get_derived_secret(token, secret=node_secrets[-1])
    endpoint = pattern.format(uid=user.uid, service=service, node=user.node)
    auth = HawkAuth(token, secret)
    resp = requests.delete(endpoint, auth=auth, timeout=timeout)
    if resp.status_code >= 400 and resp.status_code != 404:
        resp.raise_for_status()
    def test_purging_of_old_user_records(self):
        # Make some old user records.
        service = "sync-1.1"
        email = "*****@*****.**"
        user = self.backend.allocate_user(service,
                                          email,
                                          client_state="aa",
                                          generation=123)
        self.backend.update_user(service,
                                 user,
                                 client_state="bb",
                                 generation=456,
                                 keys_changed_at=450)
        self.backend.update_user(service,
                                 user,
                                 client_state="cc",
                                 generation=789)
        user_records = list(self.backend.get_user_records(service, email))
        self.assertEqual(len(user_records), 3)
        user = self.backend.get_user(service, email)
        self.assertEquals(user["client_state"], "cc")
        self.assertEquals(len(user["old_client_states"]), 2)

        # The default grace-period should prevent any cleanup.
        self.assertTrue(purge_old_records(self.ini_file))
        user_records = list(self.backend.get_user_records(service, email))
        self.assertEqual(len(user_records), 3)
        self.assertEqual(len(self.service_requests), 0)

        # With no grace period, we should cleanup two old records.
        self.assertTrue(purge_old_records(self.ini_file, grace_period=0))
        user_records = list(self.backend.get_user_records(service, email))
        self.assertEqual(len(user_records), 1)
        self.assertEqual(len(self.service_requests), 2)

        # Check that the proper delete requests were made to the service.
        secrets = self.config.registry.settings["tokenserver.secrets"]
        node_secret = secrets.get(self.service_node)[-1]
        expected_kids = ["0000000000450-uw", "0000000000123-qg"]
        for i, environ in enumerate(self.service_requests):
            # They must be to the correct path.
            self.assertEquals(environ["REQUEST_METHOD"], "DELETE")
            self.assertTrue(re.match("/1.1/[0-9]+", environ["PATH_INFO"]))
            # They must have a correct request signature.
            token = hawkauthlib.get_id(environ)
            secret = tokenlib.get_derived_secret(token, secret=node_secret)
            self.assertTrue(hawkauthlib.check_signature(environ, secret))
            userdata = tokenlib.parse_token(token, secret=node_secret)
            self.assertTrue("uid" in userdata)
            self.assertTrue("node" in userdata)
            self.assertEqual(userdata["fxa_uid"], "test")
            self.assertEqual(userdata["fxa_kid"], expected_kids[i])

        # Check that the user's current state is unaffected
        user = self.backend.get_user(service, email)
        self.assertEquals(user["client_state"], "cc")
        self.assertEquals(len(user["old_client_states"]), 0)
Example #9
0
 def _generate_token_credentials(self):
     """Pick an identity, log in and generate the auth token."""
     # If the server_url has a hash fragment, it's a storage node and
     # that's the secret.  Otherwise it's a token server url.
     uid = random.randint(1, 1000000)
     url = urlparse(self.server_url)
     if url.fragment:
         endpoint = url._replace(fragment="", path="/1.5/" + str(uid))
         self.endpoint_url = urlunparse(endpoint)
         data = {
             "uid": uid,
             "node": urlunparse(url._replace(fragment="")),
             "expires": time.time() + ASSERTION_LIFETIME,
         }
         self.auth_token = tokenlib.make_token(data, secret=url.fragment)
         self.auth_secret = tokenlib.get_derived_secret(self.auth_token,
                                                        secret=url.fragment)
     else:
         email = "user%s@%s" % (uid, MOCKMYID_DOMAIN)
         exp = time.time() + ASSERTION_LIFETIME + HawkAuth.timeskew
         assertion = browserid.tests.support.make_assertion(
             email=email,
             audience=self.server_url,
             issuer=MOCKMYID_DOMAIN,
             issuer_keypair=(None, MOCKMYID_PRIVATE_KEY),
             exp=int(exp * 1000),
         )
         token_url = self.server_url + "/1.0/sync/1.5"
         response = self.session.get(token_url,
                                     headers={
                                         "Authorization":
                                         "BrowserID " + assertion,
                                     })
         # Maybe timeskew between client and server?
         if response.status_code == 401:
             server_time = int(response.headers["X-Timestamp"])
             HawkAuth.timeskew = server_time - int(time.time())
             exp = time.time() + ASSERTION_LIFETIME + HawkAuth.timeskew
             assertion = browserid.tests.support.make_assertion(
                 email=email,
                 audience=self.server_url,
                 issuer=MOCKMYID_DOMAIN,
                 issuer_keypair=(None, MOCKMYID_PRIVATE_KEY),
                 exp=int(exp * 1000),
             )
             response = self.session.get(token_url,
                                         headers={
                                             "Authorization":
                                             "BrowserID " + assertion,
                                         })
         response.raise_for_status()
         credentials = response.json()
         self.auth_token = credentials["id"].encode('ascii')
         self.auth_secret = credentials["key"].encode('ascii')
         self.endpoint_url = credentials["api_endpoint"]
     return self.auth_token, self.auth_secret, self.endpoint_url
 def test_valid_oauth_request(self):
     oauth_token = self.oauth_token
     headers = {
         'Authorization': 'Bearer %s' % oauth_token,
         'X-KeyID': '1234-qqo'
     }
     # Send a valid request, allocating a new user
     res = self.app.get('/1.0/sync/1.5', headers=headers)
     fxa_uid = self.session.uid
     # Retrieve the user from the database
     user = self._get_user(res.json['uid'])
     # First, let's verify that the token we received is valid. To do this,
     # we can unpack the hawk header ID into the payload and its signature
     # and then construct a tokenlib token to compute the signature
     # ourselves. To obtain a matching signature, we use the same secret as
     # is used by Tokenserver.
     raw = urlsafe_b64decode(res.json['id'])
     payload = raw[:-32]
     signature = raw[-32:]
     payload_dict = json.loads(payload.decode('utf-8'))
     # The `id` payload should include a field indicating the origin of the
     # token
     self.assertEqual(payload_dict['tokenserver_origin'], 'rust')
     signing_secret = self.TOKEN_SIGNING_SECRET
     expected_token = tokenlib.make_token(payload_dict,
                                          secret=signing_secret)
     expected_signature = urlsafe_b64decode(expected_token)[-32:]
     # Using the #compare_digest method here is not strictly necessary, as
     # this is not a security-sensitive situation, but it's good practice
     self.assertTrue(hmac.compare_digest(expected_signature, signature))
     # Check that the given key is a secret derived from the hawk ID
     expected_secret = tokenlib.get_derived_secret(res.json['id'],
                                                   secret=signing_secret)
     self.assertEqual(res.json['key'], expected_secret)
     # Check to make sure the remainder of the fields are valid
     self.assertEqual(res.json['uid'], user['uid'])
     self.assertEqual(res.json['api_endpoint'],
                      '%s/1.5/%s' % (self.NODE_URL, user['uid']))
     self.assertEqual(res.json['duration'], DEFAULT_TOKEN_DURATION)
     self.assertEqual(res.json['hashalg'], 'sha256')
     self.assertEqual(res.json['hashed_fxa_uid'],
                      self._fxa_metrics_hash(fxa_uid)[:32])
     self.assertEqual(res.json['node_type'], 'spanner')
     # The response should have an X-Timestamp header that contains the
     # number of seconds since the UNIX epoch
     self.assertIn('X-Timestamp', res.headers)
     self.assertIsNotNone(int(res.headers['X-Timestamp']))
     token = self.unsafelyParseToken(res.json['id'])
     self.assertIn('hashed_device_id', token)
     self.assertEqual(token["uid"], res.json["uid"])
     self.assertEqual(token["fxa_uid"], fxa_uid)
     self.assertEqual(token["fxa_kid"], "0000000001234-qqo")
     self.assertNotEqual(token["hashed_fxa_uid"], token["fxa_uid"])
     self.assertEqual(token["hashed_fxa_uid"], res.json["hashed_fxa_uid"])
     self.assertIn("hashed_device_id", token)
    def test_valid_browserid_request(self):
        assertion = self.browserid_assertion
        headers = {
            'Authorization': 'BrowserID %s' % assertion,
            'X-Client-State': 'aaaa'
        }
        # Send a valid request, allocating a new user
        res = self.app.get('/1.0/sync/1.5', headers=headers)
        fxa_uid = self.session.uid
        # Retrieve the user from the database
        user = self._get_user(res.json['uid'])
        # First, let's verify that the token we received is valid. To do this,
        # we can unpack the hawk header ID into the payload and its signature
        # and then construct a tokenlib token to compute the signature
        # ourselves. To obtain a matching signature, we use the same secret as
        # is used by Tokenserver.
        raw = urlsafe_b64decode(res.json['id'])
        payload = raw[:-32]
        signature = raw[-32:]
        payload_dict = json.loads(payload.decode('utf-8'))

        signing_secret = self.TOKEN_SIGNING_SECRET
        expected_token = tokenlib.make_token(payload_dict,
                                             secret=signing_secret)
        expected_signature = urlsafe_b64decode(expected_token)[-32:]
        # Using the #compare_digest method here is not strictly necessary, as
        # this is not a security-sensitive situation, but it's good practice
        self.assertTrue(hmac.compare_digest(expected_signature, signature))
        # Check that the given key is a secret derived from the hawk ID
        expected_secret = tokenlib.get_derived_secret(res.json['id'],
                                                      secret=signing_secret)
        self.assertEqual(res.json['key'], expected_secret)
        # Check to make sure the remainder of the fields are valid
        self.assertEqual(res.json['uid'], user['uid'])
        self.assertEqual(res.json['api_endpoint'],
                         '%s/1.5/%s' % (self.NODE_URL, user['uid']))
        self.assertEqual(res.json['duration'], DEFAULT_TOKEN_DURATION)
        self.assertEqual(res.json['hashalg'], 'sha256')
        self.assertEqual(res.json['hashed_fxa_uid'],
                         self._fxa_metrics_hash(fxa_uid)[:32])
        self.assertEqual(res.json['node_type'], 'spanner')

        token = self.unsafelyParseToken(res.json['id'])
        self.assertIn('hashed_device_id', token)
        self.assertEqual(token["uid"], res.json["uid"])
        self.assertEqual(token["fxa_uid"], fxa_uid)
        assertion = self.browserid_assertion
        keys_changed_at = \
            self._extract_keys_changed_at_from_assertion(assertion)
        self.assertEqual(token["fxa_kid"], "%s-qqo" % str(keys_changed_at))
        self.assertNotEqual(token["hashed_fxa_uid"], token["fxa_uid"])
        self.assertEqual(token["hashed_fxa_uid"], res.json["hashed_fxa_uid"])
        self.assertIn("hashed_device_id", token)
Example #12
0
 def _generate_token_credentials(self):
     """Pick an identity, log in and generate the auth token."""
     # If the server_url has a hash fragment, it's a storage node and
     # that's the secret.  Otherwise it's a token server url.
     uid = random.randint(1, 1000000)
     url = urlparse(self.server_url)
     if url.fragment:
         endpoint = url._replace(fragment="", path="/1.5/" + str(uid))
         self.endpoint_url = urlunparse(endpoint)
         data = {
             "uid": uid,
             "node": urlunparse(url._replace(fragment="")),
             "expires": time.time() + ASSERTION_LIFETIME,
         }
         self.auth_token = tokenlib.make_token(data, secret=url.fragment)
         self.auth_secret = tokenlib.get_derived_secret(self.auth_token,
                                                        secret=url.fragment)
     else:
         email = "user%s@%s" % (uid, MOCKMYID_DOMAIN)
         exp = time.time() + ASSERTION_LIFETIME + HawkAuth.timeskew
         assertion = browserid.tests.support.make_assertion(
             email=email,
             audience=self.server_url,
             issuer=MOCKMYID_DOMAIN,
             issuer_keypair=(None, MOCKMYID_PRIVATE_KEY),
             exp=int(exp * 1000),
         )
         token_url = self.server_url + "/1.0/sync/1.5"
         response = self.session.get(token_url, headers={
             "Authorization": "BrowserID " + assertion,
         })
         # Maybe timeskew between client and server?
         if response.status_code == 401:
             server_time = int(response.headers["X-Timestamp"])
             HawkAuth.timeskew = server_time - int(time.time())
             exp = time.time() + ASSERTION_LIFETIME + HawkAuth.timeskew
             assertion = browserid.tests.support.make_assertion(
                 email=email,
                 audience=self.server_url,
                 issuer=MOCKMYID_DOMAIN,
                 issuer_keypair=(None, MOCKMYID_PRIVATE_KEY),
                 exp=int(exp * 1000),
             )
             response = self.session.get(token_url, headers={
                 "Authorization": "BrowserID " + assertion,
             })
         response.raise_for_status()
         credentials = response.json()
         self.auth_token = credentials["id"].encode('ascii')
         self.auth_secret = credentials["key"].encode('ascii')
         self.endpoint_url = credentials["api_endpoint"]
     return self.auth_token, self.auth_secret, self.endpoint_url
Example #13
0
def create_token(args):
    expires = int(time.time()) + args.duration
    token_data = {
        'uid': args.uid,
        'node': args.node,
        'expires': expires,
        'fxa_uid': args.fxa_uid,
        'fxa_kid': args.fxa_kid,
        'hashed_fxa_uid': metrics_hash(args, args.fxa_uid),
        'hashed_device_id': metrics_hash(args, args.device_id),
        'salt': SALT,
    }
    token = tokenlib.make_token(token_data, secret=args.secret)
    key = tokenlib.get_derived_secret(token, secret=args.secret)
    return token, key, expires, SALT
    def encode_hawk_id(  # pylint: disable=E0202, W0613
            self, request, userid=None, **data):
        """Encode the given userid into a Hawk token id and secret key.

        This method is essentially the reverse of decode_hawk_id.  Given
        a userid, it returns a Hawk id and corresponding secret key.
        It is not needed for consuming authentication tokens, but is very
        useful when building them for testing purposes.
        """
        if userid is not None:
            data["userid"] = userid
        master_secret = self.master_secret
        tokenid = tokenlib.make_token(data, secret=master_secret)
        secret = tokenlib.get_derived_secret(tokenid, secret=master_secret)
        return tokenid, secret
Example #15
0
def create_token():
    expires = int(time.time()) + DURATION
    token_data = {
        'uid': LEGACY_UID,
        'node': NODE,
        'expires': expires,
        'fxa_uid': FXA_UID,
        'fxa_kid': FXA_KID,
        'hashed_fxa_uid': metrics_hash(FXA_UID),
        'hashed_device_id': metrics_hash(DEVICE_ID),
        'salt': SALT,
    }
    token = tokenlib.make_token(token_data, secret=SECRET)
    key = tokenlib.get_derived_secret(token, secret=SECRET)
    return token, key, expires, SALT
Example #16
0
    def encode_hawk_id(self, request, userid):
        """Encode the given userid into a Hawk id and secret key.

        This method is essentially the reverse of decode_hawk_id.  It is
        not needed for consuming authentication tokens, but is very useful
        when building them for testing purposes.
        """
        node_name = self._get_node_name(request)
        # There might be multiple secrets in use, if we're in the
        # process of transitioning from one to another.  Always use
        # the last one aka the "most recent" secret.
        secret = self._get_token_secrets(node_name)[-1]
        data = {"uid": userid, "node": node_name}
        tokenid = tokenlib.make_token(data, secret=secret)
        key = tokenlib.get_derived_secret(tokenid, secret=secret)
        return tokenid, key
Example #17
0
    def encode_hawk_id(self, request, userid):
        """Encode the given userid into a Hawk id and secret key.

        This method is essentially the reverse of decode_hawk_id.  It is
        not needed for consuming authentication tokens, but is very useful
        when building them for testing purposes.
        """
        node_name = self._get_node_name(request)
        # There might be multiple secrets in use, if we're in the
        # process of transitioning from one to another.  Always use
        # the last one aka the "most recent" secret.
        secret = self._get_token_secrets(node_name)[-1]
        data = {"uid": userid, "node": node_name}
        tokenid = tokenlib.make_token(data, secret=secret)
        key = tokenlib.get_derived_secret(tokenid, secret=secret)
        return tokenid, key
    def test_purging_of_old_user_records(self):
        # Make some old user records.
        service = "test-1.0"
        email = "*****@*****.**"
        user = self.backend.allocate_user(service, email, client_state="a")
        self.backend.update_user(service, user, client_state="b")
        self.backend.update_user(service, user, client_state="c")
        user_records = list(self.backend.get_user_records(service, email))
        self.assertEqual(len(user_records), 3)
        user = self.backend.get_user(service, email)
        self.assertEquals(user["client_state"], "c")
        self.assertEquals(len(user["old_client_states"]), 2)

        # The default grace-period should prevent any cleanup.
        self.assertTrue(purge_old_records(self.ini_file))
        user_records = list(self.backend.get_user_records(service, email))
        self.assertEqual(len(user_records), 3)
        self.assertEqual(len(self.service_requests), 0)

        # With no grace period, we should cleanup two old records.
        self.assertTrue(purge_old_records(self.ini_file, grace_period=0))
        user_records = list(self.backend.get_user_records(service, email))
        self.assertEqual(len(user_records), 1)
        self.assertEqual(len(self.service_requests), 2)

        # Check that the proper delete requests were made to the service.
        secrets = self.config.registry.settings["tokenserver.secrets"]
        node_secret = secrets.get(self.service_node)[-1]
        expected_kids = ["b", "a"]
        for i, environ in enumerate(self.service_requests):
            # They must be to the correct path.
            self.assertEquals(environ["REQUEST_METHOD"], "DELETE")
            self.assertTrue(re.match("/1.0/[0-9]+", environ["PATH_INFO"]))
            # They must have a correct request signature.
            token = hawkauthlib.get_id(environ)
            secret = tokenlib.get_derived_secret(token, secret=node_secret)
            self.assertTrue(hawkauthlib.check_signature(environ, secret))
            userdata = tokenlib.parse_token(token, secret=node_secret)
            self.assertTrue("uid" in userdata)
            self.assertTrue("node" in userdata)
            self.assertEqual(userdata["fxa_uid"], "test")
            self.assertEqual(userdata["fxa_kid"], expected_kids[i])

        # Check that the user's current state is unaffected
        user = self.backend.get_user(service, email)
        self.assertEquals(user["client_state"], "c")
        self.assertEquals(len(user["old_client_states"]), 0)
    def encode_hawk_id(self, request, userid, extra=None):
        """Encode the given userid into a Hawk id and secret key.

        This method is essentially the reverse of decode_hawk_id.  It is
        not needed for consuming authentication tokens, but is very useful
        when building them for testing purposes.

        Unlike its superclass method, this one allows the caller to specify
        a dict of additional user data to include in the auth token.
        """
        node_name = self._get_node_name(request)
        secret = self._get_token_secrets(node_name)[-1]
        data = {"uid": userid, "node": node_name}
        if extra is not None:
            data.update(extra)
        tokenid = tokenlib.make_token(data, secret=secret)
        key = tokenlib.get_derived_secret(tokenid, secret=secret)
        return tokenid, key
    def test_purging_of_old_user_records(self):
        # Make some old user records.
        service = "test-1.0"
        email = "*****@*****.**"
        user = self.backend.allocate_user(service, email, client_state="a")
        self.backend.update_user(service, user, client_state="b")
        self.backend.update_user(service, user, client_state="c")
        user_records = list(self.backend.get_user_records(service, email))
        self.assertEqual(len(user_records), 3)
        user = self.backend.get_user(service, email)
        self.assertEquals(user["client_state"], "c")
        self.assertEquals(len(user["old_client_states"]), 2)

        # The default grace-period should prevent any cleanup.
        self.assertTrue(purge_old_records(self.ini_file))
        user_records = list(self.backend.get_user_records(service, email))
        self.assertEqual(len(user_records), 3)
        self.assertEqual(len(self.service_requests), 0)

        # With no grace period, we should cleanup two old records.
        self.assertTrue(purge_old_records(self.ini_file, grace_period=0))
        user_records = list(self.backend.get_user_records(service, email))
        self.assertEqual(len(user_records), 1)
        self.assertEqual(len(self.service_requests), 2)

        # Check that the proper delete requests were made to the service.
        secrets = self.config.registry.settings["tokenserver.secrets"]
        node_secret = secrets.get(self.service_node)[-1]
        for environ in self.service_requests:
            # They must be to the correct path.
            self.assertEquals(environ["REQUEST_METHOD"], "DELETE")
            self.assertTrue(re.match("/1.0/[0-9]+", environ["PATH_INFO"]))
            # They must have a correct request signature.
            token = hawkauthlib.get_id(environ)
            secret = tokenlib.get_derived_secret(token, secret=node_secret)
            self.assertTrue(hawkauthlib.check_signature(environ, secret))

        # Check that the user's current state is unaffected
        user = self.backend.get_user(service, email)
        self.assertEquals(user["client_state"], "c")
        self.assertEquals(len(user["old_client_states"]), 0)
Example #21
0
def delete_service_data(config, service, uid, node, timeout=60):
    """Send a data-deletion request to the user's service node.

    This is a little bit of hackery to cause the user's service node to
    remove any data it still has stored for the user.  We simulate a DELETE
    request from the user's own account.
    """
    secrets = config.registry.settings['tokenserver.secrets']
    pattern = config.registry['endpoints_patterns'][service]
    node_secrets = secrets.get(node)
    if not node_secrets:
        msg = "The node %r does not have any shared secret" % (node, )
        raise ValueError(msg)
    user = {"uid": uid, "node": node}
    token = tokenlib.make_token(user, secret=node_secrets[-1])
    secret = tokenlib.get_derived_secret(token, secret=node_secrets[-1])
    endpoint = pattern.format(uid=uid, service=service, node=node)
    auth = HawkAuth(token, secret)
    resp = requests.delete(endpoint, auth=auth, timeout=timeout)
    if resp.status_code >= 400 and resp.status_code != 404:
        resp.raise_for_status()
    def decode_hawk_id(self, request, tokenid):  # pylint: disable=E0202
        """Decode a Hawk token id into its userid and Hawk secret key.

        This method decodes the given Hawk token id to give the corresponding
        userid and Hawk secret key.  It is a simple default implementation
        using the tokenlib library, and can be overridden by passing a callable
        info the HawkAuthenticationPolicy constructor.

        If the Hawk token id is invalid then ValueError will be raised.
        """
        master_secret = self.master_secret
        secret = tokenlib.get_derived_secret(tokenid, secret=master_secret)
        data = tokenlib.parse_token(tokenid, secret=master_secret)
        userid = None
        for key in ("username", "userid", "uid", "email"):
            userid = data.get(key)
            if userid is not None:
                break
        else:
            msg = "Hawk id contains no userid"
            raise self.challenge(request, msg)
        return userid, secret
Example #23
0
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
    }
Example #24
0
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,
    }
            # The token failed to validate using any secret.
            logger.warn("Authentication Failed: invalid hawk id")
            raise ValueError("invalid Hawk id")

        # Let the app access all user data from the token.
        request.user.update(data)
        request.metrics["metrics_uid"] = data.get("hashed_fxa_uid")
        request.metrics["metrics_device_id"] = data.get("hashed_device_id")

        # Sanity-check that we're on the right node.
        if data["node"] != node_name:
            msg = "incorrect node for this token: %s"
            raise ValueError(msg % (data["node"],))

        # Calculate the matching request-signing secret.
        key = tokenlib.get_derived_secret(tokenid, secret=secret)

        return userid, key

    def encode_hawk_id(self, request, userid, extra=None):
        """Encode the given userid into a Hawk id and secret key.

        This method is essentially the reverse of decode_hawk_id.  It is
        not needed for consuming authentication tokens, but is very useful
        when building them for testing purposes.

        Unlike its superclass method, this one allows the caller to specify
        a dict of additional user data to include in the auth token.
        """
        node_name = self._get_node_name(request)
        secret = self._get_token_secrets(node_name)[-1]
class SyncStorageAuthenticationPolicy(TokenServerAuthenticationPolicy):
    """Pyramid authentication policy with special handling of expired tokens.

    This class extends the standard mozsvc TokenServerAuthenticationPolicy
    to (carefully) allow some access by holders of expired tokens.  Presenting
    an expired token will result in a principal of "expired:<uid>" rather than
    just "<uid>", allowing this case to be specially detected and handled for
    some resources without interfering with the usual authentication rules.
    """

    implements(IAuthenticationPolicy)

    def __init__(self, secrets=None, **kwds):
        self.expired_token_timeout = kwds.pop("expired_token_timeout", None)
        if self.expired_token_timeout is None:
            self.expired_token_timeout = DEFAULT_EXPIRED_TOKEN_TIMEOUT
        super(SyncStorageAuthenticationPolicy, self).__init__(secrets, **kwds)

    @classmethod
    def _parse_settings(cls, settings):
        """Parse settings for an instance of this class."""
        supercls = super(SyncStorageAuthenticationPolicy, cls)
        kwds = supercls._parse_settings(settings)
        expired_token_timeout = settings.pop("expired_token_timeout", None)
        if expired_token_timeout is not None:
            kwds["expired_token_timeout"] = int(expired_token_timeout)
        return kwds

    def decode_hawk_id(self, request, tokenid):
        """Decode a Hawk token id into its userid and secret key.

        This method determines the appropriate secrets to use for the given
        request, then passes them on to tokenlib to handle the given Hawk
        token.  If the id is invalid then ValueError will be raised.

        Unlike the superclass method, this implementation allows expired
        tokens to be used up to a configurable timeout.  The effective userid
        for expired tokens is changed to be "expired:<uid>".
        """
        now = time.time()
        node_name = self._get_node_name(request)
        # There might be multiple secrets in use,
        # so try each until we find one that works.
        secrets = self._get_token_secrets(node_name)
        for secret in secrets:
            try:
                tm = tokenlib.TokenManager(secret=secret)
                # Check for a proper valid signature first.
                # If that failed because of an expired token, check if
                # it falls within the allowable expired-token window.
                try:
                    data = tm.parse_token(tokenid, now=now)
                except tokenlib.errors.ExpiredTokenError:
                    recently = now - self.expired_token_timeout
                    data = tm.parse_token(tokenid, now=recently)
                    data["uid"] = "expired:%d" % (data["uid"], )
            except ValueError:
                # Token validation failed, move on to the next secret.
                continue
            else:
                # Token validation succeeded, quit the loop.
                break
        else:
            # The token failed to validate using any secret.
            log_cef("Authentication Failed: invalid hawk id",
                    5,
                    request.environ,
                    request.registry.settings,
                    "",
                    signature=AUTH_FAILURE)
            raise ValueError("invalid Hawk id")
        # Sanity-check the contained data.
        # Any errors raise ValueError, triggering auth failure.
        try:
            userid = data["uid"]
            token_node_name = data["node"]
        except KeyError, e:
            msg = "missing value in token data: %s"
            raise ValueError(msg % (e, ))
        if token_node_name != node_name:
            msg = "incorrect node for this token: %s"
            raise ValueError(msg % (token_node_name, ))
        # Calculate the matching request-signing secret.
        key = tokenlib.get_derived_secret(tokenid, secret=secret)
        return userid, key
Example #27
0
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}
Example #28
0
    def decode_hawk_id(self, request, tokenid):
        """Decode a Hawk token id into its userid and secret key.

        This method determines the appropriate secrets to use for the given
        request, then passes them on to tokenlib to handle the given Hawk
        token.  If the id is invalid then ValueError will be raised.

        Unlike the superclass method, this implementation allows expired
        tokens to be used up to a configurable timeout.  The effective userid
        for expired tokens is changed to be "expired:<uid>".
        """
        now = time.time()
        node_name = self._get_node_name(request)
        # There might be multiple secrets in use,
        # so try each until we find one that works.
        secrets = self._get_token_secrets(node_name)
        for secret in secrets:
            try:
                tm = tokenlib.TokenManager(secret=secret)
                # Check for a proper valid signature first.
                # If that failed because of an expired token, check if
                # it falls within the allowable expired-token window.
                try:
                    data = self._parse_token(tm, tokenid, now)
                    userid = data["uid"]
                except tokenlib.errors.ExpiredTokenError:
                    recently = now - self.expired_token_timeout
                    data = self._parse_token(tm, tokenid, recently)
                    # We replace the uid with a special string to ensure that
                    # calling code doesn't accidentally treat the token as
                    # valid. If it wants to use the expired uid, it will have
                    # to explicitly dig it back out from `request.user`.
                    data["expired_uid"] = data["uid"]
                    userid = data["uid"] = "expired:%d" % (data["uid"],)
            except tokenlib.errors.InvalidSignatureError:
                # Token signature check failed, try the next secret.
                continue
            except TypeError as e:
                # Something went wrong when validating the contained data.
                raise ValueError(str(e))
            else:
                # Token signature check succeeded, quit the loop.
                break
        else:
            # The token failed to validate using any secret.
            print("warn Authentication Failed: invalid hawk id")
            raise ValueError("invalid Hawk id")

        # Let the app access all user data from the token.
        request.user.update(data)
        request.metrics["metrics_uid"] = data.get("hashed_fxa_uid")
        request.metrics["metrics_device_id"] = data.get("hashed_device_id")

        # Sanity-check that we're on the right node.
        if data["node"] != node_name:
            msg = "incorrect node for this token: %s"
            raise ValueError(msg % (data["node"],))

        # Calculate the matching request-signing secret.
        key = tokenlib.get_derived_secret(tokenid, secret=secret)

        return userid, key