def test_valid_id_token_without_nonce(app): """ Create a token and then validate it and make sure there are no exceptions when a nonce is not provided. """ issuer = config.get("BASE_URL") keypair = app.keypairs[0] client_id = "client_12345" user = User(username="******", is_admin=False) expires_in = 2592000 nonce = None max_age = None token_result = generate_signed_id_token( keypair.kid, keypair.private_key, user, expires_in, client_id, audiences=[client_id], auth_time=None, max_age=None, nonce=None, ) unsigned_token = UnsignedIDToken.from_signed_and_encoded_token( token_result.token, client_id=client_id, issuer=issuer, max_age=max_age, nonce=nonce, ) unsigned_token.validate() assert not unsigned_token.get("nonce")
def test_valid_id_token(app): """ Create a token and then validate it and make sure there are no exceptions """ issuer = config.get("BASE_URL") keypair = app.keypairs[0] client_id = "client_12345" user = User(username="******", is_admin=False) expires_in = 2592000 nonce = "a1b2c3d4e5f6g7h8i9j0k!l@#n$%^q&*stuvwxyz" max_age = None token_result = generate_signed_id_token( keypair.kid, keypair.private_key, user, expires_in, client_id, audiences=[client_id], auth_time=None, max_age=None, nonce=None, ) unsigned_token = UnsignedIDToken.from_signed_and_encoded_token( token_result.token, client_id=client_id, issuer=issuer, max_age=max_age, nonce=nonce, ) unsigned_token.validate()
def test_id_token_max_age(app): """ Create a token and then validate it and make sure there are no exceptions when a nonce is not provided. FIXME: We should test that this tries to re-auth user, not throw exception """ keypair = app.keypairs[0] client_id = "client_12345" user = User(username='******', is_admin=False) expires_in = 2592000 nonce = None max_age = 1 now = int(time.time()) - 10 with pytest.raises(IDTokenError): generate_signed_id_token( keypair.kid, keypair.private_key, user, expires_in, client_id, audiences=[client_id], auth_time=now, max_age=max_age, nonce=nonce)
def test_recode_id_token(app, kid, rsa_private_key): """ Test that after signing, unsigning, re-signing, and unsigning again, the contents of the ID Token that should be the same, are. """ issuer = config.get("BASE_URL") keypair = app.keypairs[0] client_id = "client_12345" user = User(username="******", is_admin=False) expires_in = 2592000 nonce = "a1b2c3d4e5f6g7h8i9j0k!l@#n$%^q&*stuvwxyz" max_age = None original_signed_token = generate_signed_id_token( keypair.kid, keypair.private_key, user, expires_in, client_id, audiences=[client_id], auth_time=None, max_age=max_age, nonce=nonce, ) original_unsigned_token = UnsignedCodeIDToken.from_signed_and_encoded_token( original_signed_token.token, client_id=client_id, issuer=issuer, max_age=max_age, nonce=nonce, ) new_signed_token = original_unsigned_token.get_signed_and_encoded_token( kid, rsa_private_key) new_unsigned_token = UnsignedCodeIDToken.from_signed_and_encoded_token( new_signed_token, client_id=client_id, issuer=issuer, max_age=max_age, nonce=nonce, ) assert original_unsigned_token.iss == new_unsigned_token.iss assert original_unsigned_token.sub == new_unsigned_token.sub assert original_unsigned_token.aud == new_unsigned_token.aud assert original_unsigned_token.azp == new_unsigned_token.azp assert original_unsigned_token.nonce == new_unsigned_token.nonce
def test_expired_id_token(app): """ Create a token that is already expired make sure an exception is thrown. """ keypair = app.keypairs[0] client_id = "client_12345" user = User(username='******', is_admin=False) expires_in = 0 nonce = None max_age = None with pytest.raises(IDTokenError): token = generate_signed_id_token( keypair.kid, keypair.private_key, user, expires_in, client_id, audiences=[client_id], auth_time=None, max_age=max_age, nonce=nonce ) assert not token
def create_id_token(user, keypair, expires_in, client_id, audiences=None, auth_time=None, max_age=None, nonce=None): try: return token.generate_signed_id_token(keypair.kid, keypair.private_key, user, expires_in, client_id, audiences=audiences, auth_time=auth_time, max_age=max_age, nonce=nonce) except Exception as e: return flask.jsonify({'errors': e.message})
def __call__(self, client, grant_type, expires_in=None, scope=None, include_refresh_token=True, nonce=None, refresh_token=None, refresh_token_claims=None): """ Generate the token response, which looks like the following: { 'token_type': 'Bearer', 'id_token': 'eyJhb[...long encoded JWT...]OnoVQ', 'access_token': 'eyJhb[...long encoded JWT...]evfxA', 'refresh_token': 'eyJhb[ ... long encoded JWT ... ]KnLJA', 'expires_in': 1200, } This function will be called in authlib internals. Args: client: not used (would be used to determine expiration) grant_type: not used expires_in: not used (see expiration times configured above) scope (List[str]): list of requested scopes include_refresh_token: not used nonce (str): "nonsense" to include in ID token (see OIDC spec) refresh_token: for a refresh token grant, pass in the previous refresh token to return that same token again instead of generating a new one (otherwise this will let the refresh token refresh itself) refresh_token_claims (dict): also for a refresh token grant, pass the previous refresh token claims (to avoid having to encode or decode the refresh token here) """ # Find the ``User`` model. # The way to do this depends on the grant type. if grant_type == 'authorization_code': # For authorization code grant, get the code from either the query # string or the form data, and use that to look up the user. if flask.request.method == 'GET': code = flask.request.args.get('code') else: code = flask.request.form.get('code') user = (current_session.query(AuthorizationCode).filter_by( code=code).first().user) if grant_type == 'refresh_token': # For refresh token, the user ID is the ``sub`` field in the token. user = (current_session.query(User).filter_by( id=int(refresh_token_claims['sub'])).first()) keypair = flask.current_app.keypairs[0] id_token = generate_signed_id_token( kid=keypair.kid, private_key=keypair.private_key, user=user, expires_in=self.ACCESS_TOKEN_EXPIRES_IN, client_id=client.client_id, audiences=scope, nonce=nonce, ) access_token = generate_signed_access_token( kid=keypair.kid, private_key=keypair.private_key, user=user, expires_in=self.ACCESS_TOKEN_EXPIRES_IN, scopes=scope, ) # If ``refresh_token`` was passed (for instance from the refresh # grant), use that instead of generating a new one. if refresh_token is None: refresh_token, _ = generate_signed_refresh_token( kid=keypair.kid, private_key=keypair.private_key, user=user, expires_in=self.REFRESH_TOKEN_EXPIRES_IN, scopes=scope, ) # ``expires_in`` is just the access token expiration time. expires_in = self.ACCESS_TOKEN_EXPIRES_IN return { 'token_type': 'Bearer', 'id_token': id_token, 'access_token': access_token, 'refresh_token': refresh_token, 'expires_in': expires_in, }
def generate_implicit_response(client, grant_type, include_access_token=True, expires_in=None, user=None, scope=None, nonce=None, **kwargs): # prevent those bothersome "not bound to session" errors if user not in current_session: user = current_session.query(User).filter_by(id=user.id).first() if not user: raise OIDCError("user not authenticated") keypair = flask.current_app.keypairs[0] linked_google_email = get_linked_google_account_email(user.id) linked_google_account_exp = get_linked_google_account_exp(user.id) if not isinstance(scope, list): scope = scope.split(" ") if not "user" in scope: scope.append("user") # ``expires_in`` is just the token expiration time. expires_in = config["ACCESS_TOKEN_EXPIRES_IN"] response = { "token_type": "Bearer", "expires_in": expires_in, # "state" handled in authlib } # don't provide user projects access in access_tokens for implicit flow # due to issues with "Location" header size during redirect (and b/c # of general deprecation of user access information in tokens) if include_access_token: access_token = generate_signed_access_token( kid=keypair.kid, private_key=keypair.private_key, user=user, expires_in=config["ACCESS_TOKEN_EXPIRES_IN"], scopes=scope, client_id=client.client_id, linked_google_email=linked_google_email, include_project_access=False, ).token response["access_token"] = access_token # don't provide user projects access in id_tokens for implicit flow # due to issues with "Location" header size during redirect (and b/c # of general deprecation of user access information in tokens) id_token = generate_signed_id_token( kid=keypair.kid, private_key=keypair.private_key, user=user, expires_in=config["ACCESS_TOKEN_EXPIRES_IN"], client_id=client.client_id, audiences=scope, nonce=nonce, linked_google_email=linked_google_email, linked_google_account_exp=linked_google_account_exp, include_project_access=False, auth_flow_type=AuthFlowTypes.IMPLICIT, access_token=access_token if include_access_token else None, ).token response["id_token"] = id_token return response
def generate_token_response(client, grant_type, expires_in=None, user=None, scope=None, include_refresh_token=True, nonce=None, refresh_token=None, refresh_token_claims=None, **kwargs): # prevent those bothersome "not bound to session" errors if user not in current_session: user = current_session.query(User).filter_by(id=user.id).first() if not user: # Find the ``User`` model. # The way to do this depends on the grant type. if grant_type == "authorization_code": # For authorization code grant, get the code from either the query # string or the form data, and use that to look up the user. if flask.request.method == "GET": code = flask.request.args.get("code") else: code = flask.request.form.get("code") user = (current_session.query(AuthorizationCode).filter_by( code=code).first().user) if grant_type == "refresh_token": # For refresh token, the user ID is the ``sub`` field in the token. user = (current_session.query(User).filter_by( id=int(refresh_token_claims["sub"])).first()) keypair = flask.current_app.keypairs[0] linked_google_email = get_linked_google_account_email(user.id) linked_google_account_exp = get_linked_google_account_exp(user.id) if not isinstance(scope, list): scope = scope.split(" ") access_token = generate_signed_access_token( kid=keypair.kid, private_key=keypair.private_key, user=user, expires_in=config["ACCESS_TOKEN_EXPIRES_IN"], scopes=scope, client_id=client.client_id, linked_google_email=linked_google_email, ).token id_token = generate_signed_id_token( kid=keypair.kid, private_key=keypair.private_key, user=user, expires_in=config["ACCESS_TOKEN_EXPIRES_IN"], client_id=client.client_id, audiences=scope, nonce=nonce, linked_google_email=linked_google_email, linked_google_account_exp=linked_google_account_exp, auth_flow_type=AuthFlowTypes.CODE, access_token=access_token, ).token # If ``refresh_token`` was passed (for instance from the refresh # grant), use that instead of generating a new one. if refresh_token is None: refresh_token = generate_signed_refresh_token( kid=keypair.kid, private_key=keypair.private_key, user=user, expires_in=config["REFRESH_TOKEN_EXPIRES_IN"], scopes=scope, client_id=client.client_id, ).token # ``expires_in`` is just the access token expiration time. expires_in = config["ACCESS_TOKEN_EXPIRES_IN"] return { "token_type": "Bearer", "id_token": id_token, "access_token": access_token, "refresh_token": refresh_token, "expires_in": expires_in, }
def generate_implicit_response(client, grant_type, include_access_token=True, expires_in=None, user=None, scope=None, nonce=None, **kwargs): # prevent those bothersome "not bound to session" errors if user not in current_session: user = current_session.query(User).filter_by(id=user.id).first() if not user: raise OIDCError("user not authenticated") keypair = flask.current_app.keypairs[0] linked_google_email = get_linked_google_account_email(user.id) linked_google_account_exp = get_linked_google_account_exp(user.id) if not isinstance(scope, list): scope = scope.split(" ") if not "user" in scope: scope.append("user") id_token = generate_signed_id_token( kid=keypair.kid, private_key=keypair.private_key, user=user, expires_in=ACCESS_TOKEN_EXPIRES_IN, client_id=client.client_id, audiences=scope, nonce=nonce, linked_google_email=linked_google_email, linked_google_account_exp=linked_google_account_exp, ).token # ``expires_in`` is just the token expiration time. expires_in = ACCESS_TOKEN_EXPIRES_IN response = { "token_type": "Bearer", "id_token": id_token, "expires_in": expires_in, # "state" handled in authlib } if include_access_token: access_token = generate_signed_access_token( kid=keypair.kid, private_key=keypair.private_key, user=user, expires_in=ACCESS_TOKEN_EXPIRES_IN, scopes=scope, client_id=client.client_id, linked_google_email=linked_google_email, ).token response["access_token"] = access_token return response