From fe7c24abb4b72cb11079152e708c5f35357cf2fd Mon Sep 17 00:00:00 2001 From: ncubed development host Date: Wed, 22 Nov 2023 16:12:41 +0000 Subject: [PATCH 1/7] Added authentik as new identity provider --- docs/security.rst | 23 +- flask_appbuilder/exceptions.py | 8 + flask_appbuilder/security/manager.py | 52 +++- tests/security/test_auth_oauth.py | 343 ++++++++++++++++++++++++++- 4 files changed, 423 insertions(+), 3 deletions(-) diff --git a/docs/security.rst b/docs/security.rst index 9deab25af..7bdc349af 100644 --- a/docs/security.rst +++ b/docs/security.rst @@ -329,13 +329,34 @@ Specify a list of OAUTH_PROVIDERS in **config.py** that you want to allow for yo "authorize_url": "https://login.microsoftonline.com/AZURE_TENANT_ID/oauth2/authorize", }, }, + { + "name": "authentik", + "token_key": "access_token", + "icon": "fa-fingerprint", + "remote_app": { + "api_base_url": "https://authentik.mydomain.com", + "client_kwargs": { + "scope": "email profile", + "verify_signature": True, + }, + "access_token_url": ( + "https://authentik.mydomain.com" "/application/o/token/" + ), + "authorize_url": ( + "https://authentik.mydomain.com/" "application/o/authorize/" + ), + "request_token_url": None, + "client_id": "CLIENT_ID", + "client_secret": "CLIENT_SECRET", + }, + }, ] This needs a small explanation, you basically have five special keys: :name: the name of the provider: you can choose whatever you want, but FAB has builtin logic in `BaseSecurityManager.get_oauth_user_info()` for: - 'azure', 'github', 'google', 'keycloak', 'keycloak_before_17', 'linkedin', 'okta', 'openshift', 'twitter' + 'authentik', 'azure', 'github', 'google', 'keycloak', 'keycloak_before_17', 'linkedin', 'okta', 'openshift', 'twitter' :icon: the font-awesome icon for this provider diff --git a/flask_appbuilder/exceptions.py b/flask_appbuilder/exceptions.py index 92189f6b2..9a34d174e 100644 --- a/flask_appbuilder/exceptions.py +++ b/flask_appbuilder/exceptions.py @@ -58,3 +58,11 @@ class OAuthProviderUnknown(FABException): """ ... + + +class InvalidLoginAttempt(FABException): + """ + When the credentials entered could not be verified + """ + + ... diff --git a/flask_appbuilder/security/manager.py b/flask_appbuilder/security/manager.py index 20b35d264..6a7b8687a 100644 --- a/flask_appbuilder/security/manager.py +++ b/flask_appbuilder/security/manager.py @@ -4,7 +4,7 @@ from typing import Any, Callable, Dict, List, Optional, Set, Tuple, Union from flask import Flask, g, session, url_for -from flask_appbuilder.exceptions import OAuthProviderUnknown +from flask_appbuilder.exceptions import OAuthProviderUnknown, InvalidLoginAttempt from flask_babel import lazy_gettext as _ from flask_jwt_extended import current_user as current_user_jwt from flask_jwt_extended import JWTManager @@ -669,6 +669,20 @@ def get_oauth_user_info( "last_name": data.get("family_name", ""), "email": data.get("email", ""), } + # for Authentik + if provider == "authentik": + # log.warn(f'RESPONSE: {resp}') + id_token = resp["id_token"] + # log.debug(f"JWT token : {id_token}") + me = self._get_authentik_token_info(id_token) + log.debug("User info from authentik: %s", me) + return { + "email": me["preferred_username"], + "first_name": me.get("given_name", ""), + "username": me["nickname"], + "role_keys": me.get("groups", []), + } + raise OAuthProviderUnknown() def _get_microsoft_jwks(self) -> List[Dict[str, Any]]: @@ -690,6 +704,42 @@ def _decode_and_validate_azure_jwt(self, id_token: str) -> Dict[str, str]: return jwt.decode(id_token, options={"verify_signature": False}) + def _get_authentik_jwks(self, jwks_url) -> dict: + import requests + + resp = requests.get(jwks_url) + if resp.status_code == 200: + return resp.json() + return False + + def _validate_jwt(self, id_token, jwks): + from authlib.jose import JsonWebKey, jwt as authlib_jwt + + keyset = JsonWebKey.import_key_set(jwks) + claims = authlib_jwt.decode(id_token, keyset) + claims.validate() + log.info("JWT token is validated") + return claims + + def _get_authentik_token_info(self, id_token): + me = jwt.decode(id_token, options={"verify_signature": False}) + + verify_signature = self.oauth_remotes["authentik"].client_kwargs.get( + "verify_signature", True + ) + if verify_signature: + # Validate the token using authentik certificate + if me.get("iss", ""): + jwks = self._get_authentik_jwks(me["iss"] + "jwks/") + if jwks: + return self._validate_jwt(id_token, jwks) + else: + # Return the token info without validating + log.warning("JWT token is not validated!") + return me + + raise InvalidLoginAttempt("OAuth signature verify failed") + def register_views(self): if not self.appbuilder.app.config.get("FAB_ADD_SECURITY_VIEWS", True): return diff --git a/tests/security/test_auth_oauth.py b/tests/security/test_auth_oauth.py index acf4b9e83..019b7cf0b 100644 --- a/tests/security/test_auth_oauth.py +++ b/tests/security/test_auth_oauth.py @@ -5,11 +5,12 @@ from flask import Flask from flask_appbuilder import AppBuilder, SQLA from flask_appbuilder.const import AUTH_OAUTH -from flask_appbuilder.exceptions import OAuthProviderUnknown +from flask_appbuilder.exceptions import OAuthProviderUnknown, InvalidLoginAttempt import jinja2 import jwt from tests.const import USERNAME_ADMIN, USERNAME_READONLY from tests.fixtures.users import create_default_users +from authlib.jose.errors import BadSignatureError logging.basicConfig(format="%(asctime)s:%(levelname)s:%(name)s:%(message)s") logging.getLogger().setLevel(logging.DEBUG) @@ -652,3 +653,343 @@ def test_oauth_user_info_azure_with_jwt_validation(self): "username": "b1a54a40-8dfa-4a6d-a2b8-f90b84d4b1df", }, ) + + +class OAuthAuthentikTestCase(unittest.TestCase): + def setUp(self): + # start Flask + self.app = Flask(__name__) + self.app.jinja_env.undefined = jinja2.StrictUndefined + self.app.config["SQLALCHEMY_DATABASE_URI"] = os.environ.get( + "SQLALCHEMY_DATABASE_URI", "sqlite:///" + ) + self.app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False + self.app.config["AUTH_TYPE"] = AUTH_OAUTH + self.app.config["OAUTH_PROVIDERS"] = [ + { + "name": "authentik", + "token_key": "access_token", + "icon": "fa-fingerprint", + "remote_app": { + "api_base_url": "https://authentik.mydomain.com", + "client_kwargs": { + "scope": "email profile", + "verify_signature": False, + }, + "access_token_url": ( + "https://authentik.mydomain.com" "/application/o/token/" + ), + "authorize_url": ( + "https://authentik.mydomain.com/" "application/o/authorize/" + ), + "request_token_url": None, + "client_id": "CLIENT_ID", + "client_secret": "CLIENT_SECRET", + }, + }, + ] + + # start Database + self.db = SQLA(self.app) + + def tearDown(self): + # Remove test user + user_alice = self.appbuilder.sm.find_user("alice") + if user_alice: + self.db.session.delete(user_alice) + self.db.session.commit() + + # stop Flask + self.app = None + + # stop Flask-AppBuilder + self.appbuilder = None + + # stop Database + self.db.session.remove() + self.db = None + + # ---------------- + # Unit Tests + # ---------------- + def test_oauth_user_info_authentik(self): + self.appbuilder = AppBuilder(self.app, self.db.session) + claims = { + "iss": "https://authentik.mydomain.com/application/o/flask-appbuilder-test/", + "sub": "2ac1102e7cf5a4b1cb2dd5adbe4761c551691ecd88991f78d0195d4d3d0cfcfa", + "aud": "CLIENT_ID", + "exp": 1703257941, + "iat": 1700665941, + "auth_time": 7282182129, # 100 years from now ;) + "acr": "goauthentik.io/providers/oauth2/default", + "at_hash": "cAydO2DJMi_ZL6opx3eUdw", + "email": "alice@example.com", + "email_verified": True, + "name": "Alice", + "given_name": "Alice Doe", + "preferred_username": "alice@example.com", + "nickname": "alice", + "groups": ["GROUP_1", "GROUP_2"], + } + + # Create an unsigned JWT + unsigned_jwt = jwt.encode(claims, key=None, algorithm="none") + user_info = self.appbuilder.sm.get_oauth_user_info( + "authentik", {"access_token": "", "id_token": unsigned_jwt} + ) + self.assertEqual( + user_info, + { + "email": "alice@example.com", + "first_name": "Alice Doe", + "role_keys": ["GROUP_1", "GROUP_2"], + "username": "alice", + }, + ) + + def test_oauth_user_info_authentik_with_jwt_validation(self): + self.app.config["OAUTH_PROVIDERS"] = [ + { + "name": "authentik", + "token_key": "access_token", + "icon": "fa-fingerprint", + "remote_app": { + "api_base_url": "https://authentik.mydomain.com", + "client_kwargs": { + "scope": "email profile", + "verify_signature": True, + }, + "access_token_url": ( + "https://authentik.mydomain.com" "/application/o/token/" + ), + "authorize_url": ( + "https://authentik.mydomain.com/" "application/o/authorize/" + ), + "request_token_url": None, + "client_id": "CLIENT_ID", + "client_secret": "CLIENT_SECRET", + }, + }, + ] + + self.appbuilder = AppBuilder(self.app, self.db.session) + claims = { + "iss": "https://authentik.mydomain.com/application/o/flask-appbuilder-test/", + "sub": "2ac1102e7cf5a4b1cb2dd5adbe4761c551691ecd88991f78d0195d4d3d0cfcfa", + "aud": "CLIENT_ID", + "exp": 1703257941, + "iat": 1700665941, + "auth_time": 7282182129, # 100 years from now ;) + "acr": "goauthentik.io/providers/oauth2/default", + "at_hash": "cAydO2DJMi_ZL6opx3eUdw", + "email": "alice@example.com", + "email_verified": True, + "name": "Alice", + "given_name": "Alice Doe", + "preferred_username": "alice@example.com", + "nickname": "alice", + "groups": ["GROUP_1", "GROUP_2"], + } + from unittest.mock import MagicMock + + private_key = """-----BEGIN PRIVATE KEY----- +MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBALeDojEka93XZ/J8 +bDGgn2MIHykafgCx2D6wTZgmmhzpRH7/k7J/WSsqG6eSFg38mGJukPCa4dcG8dCL +meajEf2g4IoaYiE55yXs0ou/tixBJI8wRY+NfCluxgIcHdKhZISVO6CkR5r7diN/ +SLHPsFnDd0UiMJ5c48UsJwk8T5T7AgMBAAECgYEAqalrVB+mEi1KDud1Z9RmRzqF +BI1XnPDPSfXZZyeZJ82J5BgJxubx23RMqPnopfm4MJikK64lyZTED9hg6tgskk1X +J9pc7iyU4PQf+tx4tvElyOL4OSqGss/tQHtHz76hNOR1kxeCcJsJG+WS8P0/Kmj1 +0IoYKLFlb5AHr6KqDGECQQDZ0qKIzxdmZj3gSsNldc4oOQOKJgd1QSDGCOqR9p7f +oj7nuOPRVgnztqXALhNhpZXYJq8dWmpGYFi+EC1piRUDAkEA162gPgGzUJAIyhUM +sA6Uy9v64nqBnlygVpofhdvyznSf/KUsmWQZv7gpMMXnIGAQP+rqM1gJvuRtodml +hUeSqQJAHJH4J6GiHBhE/WpQ/rnY9IWl5TTfvY1xUwhQXBzQ8dxCC/rARvDWFVVb +oD1q5V/mq5dHWL5HOjvg5+0PR8xnKQJAMOdBik3AZugB1jBnrBPiUUcT3/5/HXVL +NdfEhgmVSJLRI+wf7LfxzrLnRBPbkE+334ZYjEPOEeahpS1AhrPv4QJAHpap1I+v +8m+N5G/MppasppHLJmXhnFeQsnBX7XcdYiCqHikuBlIzoQ0Cj5xbkfgMMCVORO64 +r9+EFRsxA5GNYA== +-----END PRIVATE KEY-----""" + # Create a signed JWT + signed_jwt = jwt.encode( + claims, key=private_key, algorithm="RS256", headers={"kid": "1"} + ) + self.appbuilder.sm._get_authentik_jwks = MagicMock( + return_value={ + "keys": [ + { + "alg": "RS256", + "e": "AQAB", + "kid": "1", + "kty": "RSA", + "n": "t4OiMSRr3ddn8nxsMaCfYwgfKRp-ALHYPrBNmCaaHOlEfv-" + "Tsn9ZKyobp5IWDfyYYm6Q8Jrh1wbx0IuZ5qMR_aDgihpiITnnJezSi7-" + "2LEEkjzBFj418KW7GAhwd0qFkhJU7oKRHmvt2I39Isc-wWcN3RSIwnlz" + "jxSwnCTxPlPs", + "use": "sig", + "x5c": [ + "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC3g6IxJGvd12fyfGwxoJ9" + "jCB8pGn4Asdg+sE2YJpoc6UR+/5Oyf1krKhunkhYN/JhibpDwmuHXBvHQi5n" + "moxH9oOCKGmIhOecl7NKLv7YsQSSPMEWPjXwpbsYCHB3SoWSElTugpEea+3Y" + "jf0ixz7BZw3dFIjCeXOPFLCcJPE+U+wIDAQAB" + ], + } + ] + } + ) + user_info = self.appbuilder.sm.get_oauth_user_info( + "authentik", {"access_token": "", "id_token": signed_jwt} + ) + self.assertEqual( + user_info, + { + "email": "alice@example.com", + "first_name": "Alice Doe", + "role_keys": ["GROUP_1", "GROUP_2"], + "username": "alice", + }, + ) + + def test_oauth_user_info_authentik_with_jwt_validation_wrong_signature(self): + """ + Tests if the get_user_info raises an exception + if the token is signed by a different JKWS + """ + self.app.config["OAUTH_PROVIDERS"] = [ + { + "name": "authentik", + "token_key": "access_token", + "icon": "fa-fingerprint", + "remote_app": { + "api_base_url": "https://authentik.mydomain.com", + "client_kwargs": { + "scope": "email profile", + "verify_signature": True, + }, + "access_token_url": ( + "https://authentik.mydomain.com" "/application/o/token/" + ), + "authorize_url": ( + "https://authentik.mydomain.com/" "application/o/authorize/" + ), + "request_token_url": None, + "client_id": "CLIENT_ID", + "client_secret": "CLIENT_SECRET", + }, + }, + ] + + self.appbuilder = AppBuilder(self.app, self.db.session) + claims = { + "iss": "https://authentik.mydomain.com/application/o/flask-appbuilder-test/", + "sub": "2ac1102e7cf5a4b1cb2dd5adbe4761c551691ecd88991f78d0195d4d3d0cfcfa", + "aud": "CLIENT_ID", + "exp": 1703257941, + "iat": 1700665941, + "auth_time": 7282182129, # 100 years from now ;) + "acr": "goauthentik.io/providers/oauth2/default", + "at_hash": "cAydO2DJMi_ZL6opx3eUdw", + "email": "alice@example.com", + "email_verified": True, + "name": "Alice", + "given_name": "Alice Doe", + "preferred_username": "alice@example.com", + "nickname": "alice", + "groups": ["GROUP_1", "GROUP_2"], + } + from unittest.mock import MagicMock + + private_key = """-----BEGIN PRIVATE KEY----- +MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAqPfgaTEWEP3S9w0t +gsicURfo+nLW09/0KfOPinhYZ4ouzU+3xC4pSlEp8Ut9FgL0AgqNslNaK34Kq+NZ +jO9DAQIDAQABAkAgkuLEHLaqkWhLgNKagSajeobLS3rPT0Agm0f7k55FXVt743hw +Ngkp98bMNrzy9AQ1mJGbQZGrpr4c8ZAx3aRNAiEAoxK/MgGeeLui385KJ7ZOYktj +hLBNAB69fKwTZFsUNh0CIQEJQRpFCcydunv2bENcN/oBTRw39E8GNv2pIcNxZkcb +NQIgbYSzn3Py6AasNj6nEtCfB+i1p3F35TK/87DlPSrmAgkCIQDJLhFoj1gbwRbH +/bDRPrtlRUDDx44wHoEhSDRdy77eiQIgE6z/k6I+ChN1LLttwX0galITxmAYrOBh +BVl433tgTTQ= +-----END PRIVATE KEY-----""" + # Create a signed JWT + wrong_signed_jwt = jwt.encode( + claims, key=private_key, algorithm="RS256", headers={"kid": "1"} + ) + self.appbuilder.sm._get_authentik_jwks = MagicMock( + return_value={ + "keys": [ + { + "alg": "RS256", + "e": "AQAB", + "kid": "1", + "kty": "RSA", + "n": "t4OiMSRr3ddn8nxsMaCfYwgfKRp-ALHYPrBNmCaaHOlEfv-" + "Tsn9ZKyobp5IWDfyYYm6Q8Jrh1wbx0IuZ5qMR_aDgihpiITnnJezSi7-" + "2LEEkjzBFj418KW7GAhwd0qFkhJU7oKRHmvt2I39Isc-wWcN3RSIwnlz" + "jxSwnCTxPlPs", + "use": "sig", + "x5c": [ + "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC3g6IxJGvd12fyfGwxoJ9" + "jCB8pGn4Asdg+sE2YJpoc6UR+/5Oyf1krKhunkhYN/JhibpDwmuHXBvHQi5n" + "moxH9oOCKGmIhOecl7NKLv7YsQSSPMEWPjXwpbsYCHB3SoWSElTugpEea+3Y" + "jf0ixz7BZw3dFIjCeXOPFLCcJPE+U+wIDAQAB" + ], + } + ] + } + ) + with self.assertRaises(BadSignatureError): + self.appbuilder.sm.get_oauth_user_info( + "authentik", {"access_token": "", "id_token": wrong_signed_jwt} + ) + + def test_oauth_user_info_authentik_with_jwt_validation_without_signature(self): + """ + Tests if an unsigned token raises an error if verify_signature is set to True + """ + self.app.config["OAUTH_PROVIDERS"] = [ + { + "name": "authentik", + "token_key": "access_token", + "icon": "fa-fingerprint", + "remote_app": { + "api_base_url": "https://authentik.mydomain.com", + "client_kwargs": { + "scope": "email profile", + "verify_signature": True, + }, + "access_token_url": ( + "https://authentik.mydomain.com" "/application/o/token/" + ), + "authorize_url": ( + "https://authentik.mydomain.com/" "application/o/authorize/" + ), + "request_token_url": None, + "client_id": "CLIENT_ID", + "client_secret": "CLIENT_SECRET", + }, + }, + ] + + self.appbuilder = AppBuilder(self.app, self.db.session) + claims = { + "iss": "https://authentik.mydomain.com/application/o/flask-appbuilder-test/", + "sub": "2ac1102e7cf5a4b1cb2dd5adbe4761c551691ecd88991f78d0195d4d3d0cfcfa", + "aud": "CLIENT_ID", + "exp": 1703257941, + "iat": 1700665941, + "auth_time": 7282182129, # 100 years from now ;) + "acr": "goauthentik.io/providers/oauth2/default", + "at_hash": "cAydO2DJMi_ZL6opx3eUdw", + "email": "alice@example.com", + "email_verified": True, + "name": "Alice", + "given_name": "Alice Doe", + "preferred_username": "alice@example.com", + "nickname": "alice", + "groups": ["GROUP_1", "GROUP_2"], + } + from unittest.mock import MagicMock + + unsigned_jwt = jwt.encode(claims, key=None, algorithm="none") + self.appbuilder.sm._get_authentik_jwks = MagicMock(return_value={}) + with self.assertRaises(InvalidLoginAttempt): + self.appbuilder.sm.get_oauth_user_info( + "authentik", {"access_token": "", "id_token": unsigned_jwt} + ) From d385518da77ba5e5603af57fe93d78c04e9dbde4 Mon Sep 17 00:00:00 2001 From: ncubed development host Date: Thu, 23 Nov 2023 09:49:37 +0000 Subject: [PATCH 2/7] Changed the way we check jwks. - Made the URI configurable - Prevents malicous tokens from providing a fake jwks - Fixed minor flake8 error --- docs/security.rst | 5 +++-- flask_appbuilder/security/manager.py | 14 +++++++++----- tests/security/test_auth_oauth.py | 14 +++++++++++++- 3 files changed, 25 insertions(+), 8 deletions(-) diff --git a/docs/security.rst b/docs/security.rst index 7bdc349af..84f57ad1b 100644 --- a/docs/security.rst +++ b/docs/security.rst @@ -340,14 +340,15 @@ Specify a list of OAUTH_PROVIDERS in **config.py** that you want to allow for yo "verify_signature": True, }, "access_token_url": ( - "https://authentik.mydomain.com" "/application/o/token/" + "https://authentik.mydomain.com/application/o/token/" ), "authorize_url": ( - "https://authentik.mydomain.com/" "application/o/authorize/" + "https://authentik.mydomain.com/application/o/authorize/" ), "request_token_url": None, "client_id": "CLIENT_ID", "client_secret": "CLIENT_SECRET", + 'jwks_uri': 'https://authentik.mydomain.com/application/o/APPLICATION_NAME/jwks/', }, }, ] diff --git a/flask_appbuilder/security/manager.py b/flask_appbuilder/security/manager.py index 6a7b8687a..7f5c9fac6 100644 --- a/flask_appbuilder/security/manager.py +++ b/flask_appbuilder/security/manager.py @@ -4,7 +4,7 @@ from typing import Any, Callable, Dict, List, Optional, Set, Tuple, Union from flask import Flask, g, session, url_for -from flask_appbuilder.exceptions import OAuthProviderUnknown, InvalidLoginAttempt +from flask_appbuilder.exceptions import InvalidLoginAttempt, OAuthProviderUnknown from flask_babel import lazy_gettext as _ from flask_jwt_extended import current_user as current_user_jwt from flask_jwt_extended import JWTManager @@ -671,9 +671,7 @@ def get_oauth_user_info( } # for Authentik if provider == "authentik": - # log.warn(f'RESPONSE: {resp}') id_token = resp["id_token"] - # log.debug(f"JWT token : {id_token}") me = self._get_authentik_token_info(id_token) log.debug("User info from authentik: %s", me) return { @@ -729,10 +727,16 @@ def _get_authentik_token_info(self, id_token): ) if verify_signature: # Validate the token using authentik certificate - if me.get("iss", ""): - jwks = self._get_authentik_jwks(me["iss"] + "jwks/") + jwks_uri = self.oauth_remotes["authentik"].server_metadata.get("jwks_uri") + if jwks_uri: + jwks = self._get_authentik_jwks(jwks_uri) if jwks: return self._validate_jwt(id_token, jwks) + else: + log.error( + "jwks_uri not specified in OAuth Providers, " + "could not verify token signature" + ) else: # Return the token info without validating log.warning("JWT token is not validated!") diff --git a/tests/security/test_auth_oauth.py b/tests/security/test_auth_oauth.py index 019b7cf0b..2cbd48aba 100644 --- a/tests/security/test_auth_oauth.py +++ b/tests/security/test_auth_oauth.py @@ -5,7 +5,7 @@ from flask import Flask from flask_appbuilder import AppBuilder, SQLA from flask_appbuilder.const import AUTH_OAUTH -from flask_appbuilder.exceptions import OAuthProviderUnknown, InvalidLoginAttempt +from flask_appbuilder.exceptions import InvalidLoginAttempt, OAuthProviderUnknown import jinja2 import jwt from tests.const import USERNAME_ADMIN, USERNAME_READONLY @@ -768,6 +768,10 @@ def test_oauth_user_info_authentik_with_jwt_validation(self): "request_token_url": None, "client_id": "CLIENT_ID", "client_secret": "CLIENT_SECRET", + "jwks_uri": ( + "https://authentik.mydomain.com/" + "application/o/APPLICATION_NAME/jwks/" + ), }, }, ] @@ -873,6 +877,10 @@ def test_oauth_user_info_authentik_with_jwt_validation_wrong_signature(self): "request_token_url": None, "client_id": "CLIENT_ID", "client_secret": "CLIENT_SECRET", + "jwks_uri": ( + "https://authentik.mydomain.com/" + "application/o/APPLICATION_NAME/jwks/" + ), }, }, ] @@ -963,6 +971,10 @@ def test_oauth_user_info_authentik_with_jwt_validation_without_signature(self): "request_token_url": None, "client_id": "CLIENT_ID", "client_secret": "CLIENT_SECRET", + "jwks_uri": ( + "https://authentik.mydomain.com/" + "application/o/APPLICATION_NAME/jwks/" + ), }, }, ] From 2b03af892dbcd2051d12462e2314950c101e0544 Mon Sep 17 00:00:00 2001 From: ncubed development host Date: Thu, 23 Nov 2023 10:23:57 +0000 Subject: [PATCH 3/7] Changed import order --- tests/security/test_auth_oauth.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/tests/security/test_auth_oauth.py b/tests/security/test_auth_oauth.py index 2cbd48aba..7da75972e 100644 --- a/tests/security/test_auth_oauth.py +++ b/tests/security/test_auth_oauth.py @@ -3,14 +3,18 @@ import unittest from flask import Flask -from flask_appbuilder import AppBuilder, SQLA -from flask_appbuilder.const import AUTH_OAUTH -from flask_appbuilder.exceptions import InvalidLoginAttempt, OAuthProviderUnknown + import jinja2 import jwt +from authlib.jose.errors import BadSignatureError + + +from flask_appbuilder import SQLA, AppBuilder +from flask_appbuilder.const import AUTH_OAUTH +from flask_appbuilder.exceptions import (InvalidLoginAttempt, + OAuthProviderUnknown) from tests.const import USERNAME_ADMIN, USERNAME_READONLY from tests.fixtures.users import create_default_users -from authlib.jose.errors import BadSignatureError logging.basicConfig(format="%(asctime)s:%(levelname)s:%(name)s:%(message)s") logging.getLogger().setLevel(logging.DEBUG) From 52c575376a5bb8dea8453a20d368ebb7b8024fff Mon Sep 17 00:00:00 2001 From: ncubed development host Date: Thu, 23 Nov 2023 10:27:17 +0000 Subject: [PATCH 4/7] Changed import format again --- tests/security/test_auth_oauth.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/security/test_auth_oauth.py b/tests/security/test_auth_oauth.py index 7da75972e..fde7c451b 100644 --- a/tests/security/test_auth_oauth.py +++ b/tests/security/test_auth_oauth.py @@ -11,8 +11,7 @@ from flask_appbuilder import SQLA, AppBuilder from flask_appbuilder.const import AUTH_OAUTH -from flask_appbuilder.exceptions import (InvalidLoginAttempt, - OAuthProviderUnknown) +from flask_appbuilder.exceptions import InvalidLoginAttempt, OAuthProviderUnknown from tests.const import USERNAME_ADMIN, USERNAME_READONLY from tests.fixtures.users import create_default_users From a4c13cdaa403c0d1f8f493432c6b9b274cc4f2e8 Mon Sep 17 00:00:00 2001 From: ncubed development host Date: Thu, 23 Nov 2023 10:32:43 +0000 Subject: [PATCH 5/7] Final import fix (i hope) --- tests/security/test_auth_oauth.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/tests/security/test_auth_oauth.py b/tests/security/test_auth_oauth.py index fde7c451b..3e06660ad 100644 --- a/tests/security/test_auth_oauth.py +++ b/tests/security/test_auth_oauth.py @@ -2,17 +2,18 @@ import os import unittest +from authlib.jose.errors import BadSignatureError from flask import Flask - import jinja2 import jwt -from authlib.jose.errors import BadSignatureError - -from flask_appbuilder import SQLA, AppBuilder +from flask_appbuilder import AppBuilder +from flask_appbuilder import SQLA from flask_appbuilder.const import AUTH_OAUTH -from flask_appbuilder.exceptions import InvalidLoginAttempt, OAuthProviderUnknown -from tests.const import USERNAME_ADMIN, USERNAME_READONLY +from flask_appbuilder.exceptions import InvalidLoginAttempt +from flask_appbuilder.exceptions import OAuthProviderUnknown +from tests.const import USERNAME_ADMIN +from tests.const import USERNAME_READONLY from tests.fixtures.users import create_default_users logging.basicConfig(format="%(asctime)s:%(levelname)s:%(name)s:%(message)s") From cf03c4613de3302e157602a074b6cce47f71f35b Mon Sep 17 00:00:00 2001 From: Tony Fortes Ramos Date: Thu, 23 Nov 2023 12:00:51 +0100 Subject: [PATCH 6/7] Fix imports in test for authentik --- tests/security/test_auth_oauth.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/security/test_auth_oauth.py b/tests/security/test_auth_oauth.py index 3e06660ad..9f2f85534 100644 --- a/tests/security/test_auth_oauth.py +++ b/tests/security/test_auth_oauth.py @@ -4,14 +4,13 @@ from authlib.jose.errors import BadSignatureError from flask import Flask -import jinja2 -import jwt - from flask_appbuilder import AppBuilder from flask_appbuilder import SQLA from flask_appbuilder.const import AUTH_OAUTH from flask_appbuilder.exceptions import InvalidLoginAttempt from flask_appbuilder.exceptions import OAuthProviderUnknown +import jinja2 +import jwt from tests.const import USERNAME_ADMIN from tests.const import USERNAME_READONLY from tests.fixtures.users import create_default_users From 7d63e4a637039acc55a1c99893210767e47980bf Mon Sep 17 00:00:00 2001 From: ncubed development host Date: Mon, 27 Nov 2023 08:52:52 +0000 Subject: [PATCH 7/7] black refactor --- tests/security/test_auth_oauth.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/security/test_auth_oauth.py b/tests/security/test_auth_oauth.py index 0ecb124ae..a10e17d3c 100644 --- a/tests/security/test_auth_oauth.py +++ b/tests/security/test_auth_oauth.py @@ -672,6 +672,7 @@ def test_oauth_user_info_azure_with_jwt_validation(self): "username": "b1a54a40-8dfa-4a6d-a2b8-f90b84d4b1df", }, ) + def test_oauth_user_info_auth0(self): self.appbuilder = AppBuilder(self.app, self.db.session)