diff --git a/.env.benefit-backend.example b/.env.benefit-backend.example index ee55b5793e..2587bfb72c 100644 --- a/.env.benefit-backend.example +++ b/.env.benefit-backend.example @@ -19,11 +19,11 @@ DISABLE_TOS_APPROVAL_CHECK=1 ## Authentication # NEXT_PUBLIC_MOCK_FLAG=0 OIDC_RP_CLIENT_ID= +TOKEN_AUTH_ACCEPTED_AUDIENCE= OIDC_RP_CLIENT_SECRET= -OIDC_OP_BASE_URL=https://tunnistus.test.hel.ninja/auth/realms/helsinki-tunnistus/protocol/openid-connect +OIDC_OP_BASE_URL=https://tunnistus.test.hel.ninja/auth/realms/helsinki-tunnistus OIDC_OP_LOGOUT_CALLBACK_URL=https://localhost:8000/oidc/logout_callback/ -TUNNISTAMO_API_TOKENS_ENDPOINT=https://tunnistamo.test.hel.ninja/api-tokens/ HELSINKI_PROFILE_API_URL=https://profile-api.test.hel.ninja/graphql/ HELSINKI_PROFILE_SCOPE=https://api.hel.fi/auth/helsinkiprofile @@ -111,9 +111,9 @@ AHJO_REST_API_URL= AHJO_REDIRECT_URL= DISABLE_AHJO_SAFE_LIST_CHECK=True -AHJO_TEST_USER_FIRST_NAME = -AHJO_TEST_USER_LAST_NAME = -AHJO_TEST_USER_AD_USERNAME = +AHJO_TEST_USER_FIRST_NAME= +AHJO_TEST_USER_LAST_NAME= +AHJO_TEST_USER_AD_USERNAME= ENABLE_CLAMAV=1 CLAMAV_URL=http://localhost:8080/api/v1 diff --git a/backend/benefit/helsinkibenefit/settings.py b/backend/benefit/helsinkibenefit/settings.py index 10092d8333..925d951f56 100644 --- a/backend/benefit/helsinkibenefit/settings.py +++ b/backend/benefit/helsinkibenefit/settings.py @@ -83,10 +83,9 @@ "d5c8a2743d726a33dbd637fac39d6f0712dcee4af36142fb4fb15afa17b1d9bf", ), SESSION_COOKIE_AGE=(int, 60 * 60 * 2), - TOKEN_AUTH_ACCEPTED_AUDIENCE=(str, "https://api.hel.fi/auth/helsinkibenefit"), - TOKEN_AUTH_ACCEPTED_SCOPE_PREFIX=(str, "helsinkibenefit"), - TOKEN_AUTH_AUTHSERVER_URL=(str, "https://api.hel.fi/sso/openid"), - TOKEN_AUTH_FIELD_FOR_CONSENTS=(str, "https://api.hel.fi/auth"), + TOKEN_AUTH_ACCEPTED_AUDIENCE=(str, "helsinkilisa-api-dev"), + TOKEN_AUTH_ACCEPTED_SCOPE_PREFIX=(list, ["access", "gdprquery", "gdprdelete"]), + TOKEN_AUTH_FIELD_FOR_CONSENTS=(str, "authorization.permissions.scopes"), TOKEN_AUTH_REQUIRE_SCOPE_PREFIX=(bool, True), OIDC_LEEWAY=(int, 60 * 60 * 2), OIDC_RP_CLIENT_ID=(str, ""), @@ -157,8 +156,8 @@ SERVICE_BUS_AUTH_PASSWORD=(str, "sample_password"), SERVICE_BUS_TIMEOUT=(int, 30), SERVICE_BUS_SEARCH_LIMIT=(int, 10), - GDPR_API_QUERY_SCOPE=(str, "helsinkibenefit.gdprquery"), - GDPR_API_DELETE_SCOPE=(str, "helsinkibenefit.gdprdelete"), + GDPR_API_QUERY_SCOPE=(str, "gdprquery"), + GDPR_API_DELETE_SCOPE=(str, "gdprdelete"), # For AHJO Rest API authentication AHJO_CLIENT_ID=(str, "foo"), AHJO_CLIENT_SECRET=(str, "bar"), @@ -411,20 +410,22 @@ DISABLE_TOS_APPROVAL_CHECK = env.bool("DISABLE_TOS_APPROVAL_CHECK") +OIDC_OP_BASE_URL = env.str("OIDC_OP_BASE_URL") OIDC_API_TOKEN_AUTH = { "AUDIENCE": env.str("TOKEN_AUTH_ACCEPTED_AUDIENCE"), - "API_SCOPE_PREFIX": env.str("TOKEN_AUTH_ACCEPTED_SCOPE_PREFIX"), - "ISSUER": env.str("TOKEN_AUTH_AUTHSERVER_URL"), + "API_SCOPE_PREFIX": env.list("TOKEN_AUTH_ACCEPTED_SCOPE_PREFIX"), + "ISSUER": OIDC_OP_BASE_URL, "API_AUTHORIZATION_FIELD": env.str("TOKEN_AUTH_FIELD_FOR_CONSENTS"), "REQUIRE_API_SCOPE_FOR_AUTHENTICATION": env.bool("TOKEN_AUTH_REQUIRE_SCOPE_PREFIX"), "USER_RESOLVER": "users.api.v1.authentications.resolve_user", } TUNNISTAMO_API_TOKENS_ENDPOINT = env.str("TUNNISTAMO_API_TOKENS_ENDPOINT") +TUNNISTUS_API_TOKENS_ENDPOINT = f"{OIDC_OP_BASE_URL}/protocol/openid-connect/token" HELSINKI_PROFILE_SCOPE = env.str("HELSINKI_PROFILE_SCOPE") HELSINKI_PROFILE_API_URL = env.str("HELSINKI_PROFILE_API_URL") -OIDC_RP_SCOPES = f"openid profile {HELSINKI_PROFILE_SCOPE}" +OIDC_RP_SCOPES = "openid profile email" OIDC_AUTH = {"OIDC_LEEWAY": env.int("OIDC_LEEWAY")} OIDC_RP_SIGN_ALGO = "RS256" @@ -432,15 +433,14 @@ OIDC_RP_CLIENT_ID = env.str("OIDC_RP_CLIENT_ID") OIDC_RP_CLIENT_SECRET = env.str("OIDC_RP_CLIENT_SECRET") -OIDC_OP_BASE_URL = env.str("OIDC_OP_BASE_URL") OIDC_SAVE_PERSONALLY_IDENTIFIABLE_INFO = env.bool( "OIDC_SAVE_PERSONALLY_IDENTIFIABLE_INFO" ) -OIDC_OP_AUTHORIZATION_ENDPOINT = f"{OIDC_OP_BASE_URL}/authorize" -OIDC_OP_TOKEN_ENDPOINT = f"{OIDC_OP_BASE_URL}/token" -OIDC_OP_USER_ENDPOINT = f"{OIDC_OP_BASE_URL}/userinfo" -OIDC_OP_JWKS_ENDPOINT = f"{OIDC_OP_BASE_URL}/jwks" -OIDC_OP_LOGOUT_ENDPOINT = f"{OIDC_OP_BASE_URL}/end-session" +OIDC_OP_AUTHORIZATION_ENDPOINT = f"{OIDC_OP_BASE_URL}/protocol/openid-connect/auth" +OIDC_OP_TOKEN_ENDPOINT = f"{OIDC_OP_BASE_URL}/protocol/openid-connect/token" +OIDC_OP_USER_ENDPOINT = f"{OIDC_OP_BASE_URL}/protocol/openid-connect/userinfo" +OIDC_OP_JWKS_ENDPOINT = f"{OIDC_OP_BASE_URL}/protocol/openid-connect/certs" +OIDC_OP_LOGOUT_ENDPOINT = f"{OIDC_OP_BASE_URL}/protocol/openid-connect/logout" OIDC_OP_LOGOUT_CALLBACK_URL = env.str("OIDC_OP_LOGOUT_CALLBACK_URL") # Language selection is done with accept-language header in this project diff --git a/backend/benefit/users/tests/test_gdpr_api.py b/backend/benefit/users/tests/test_gdpr_api.py index 814884514d..e06425e75f 100644 --- a/backend/benefit/users/tests/test_gdpr_api.py +++ b/backend/benefit/users/tests/test_gdpr_api.py @@ -19,9 +19,8 @@ def get_api_token_for_user_with_scopes(user, scopes: list, requests_mock): """Build a proper auth token with desired scopes.""" audience = api_token_auth_settings.AUDIENCE issuer = api_token_auth_settings.ISSUER - auth_field = api_token_auth_settings.API_AUTHORIZATION_FIELD config_url = f"{issuer}/.well-known/openid-configuration" - jwks_url = f"{issuer}/jwks" + jwks_url = f"{issuer}/protocol/openid-connect/certs" configuration = { "issuer": issuer, @@ -35,11 +34,11 @@ def get_api_token_for_user_with_scopes(user, scopes: list, requests_mock): jwt_data = { "iss": issuer, - "sub": str(user.username), "aud": audience, - "exp": int(expire.timestamp()), + "sub": str(user.username), "iat": int(now.timestamp()), - auth_field: scopes, + "exp": int(expire.timestamp()), + "authorization": {"permissions": [{"scopes": scopes}]}, } encoded_jwt = jwt.encode( jwt_data, key=rsa_key.private_key_pem, algorithm=rsa_key.jose_algorithm @@ -99,7 +98,7 @@ def test_gdpr_api_requires_authentication(gdpr_api_client): assert response.status_code == 401 -def test_user_can_only_access_his_own_profile(gdpr_api_client, requests_mock): +def test_user_can_only_access_their_own_profile(gdpr_api_client, requests_mock): user = HelsinkiProfileUserFactory() auth_header = get_api_token_for_user_with_scopes( user, diff --git a/backend/shared/shared/helsinki_profile/hp_client.py b/backend/shared/shared/helsinki_profile/hp_client.py index fcf4f928b0..d81782935d 100644 --- a/backend/shared/shared/helsinki_profile/hp_client.py +++ b/backend/shared/shared/helsinki_profile/hp_client.py @@ -18,7 +18,8 @@ class HelsinkiProfileClient: def __init__(self): if not all( [ - settings.TUNNISTAMO_API_TOKENS_ENDPOINT, + settings.TUNNISTAMO_API_TOKENS_ENDPOINT + or settings.TUNNISTUS_API_TOKENS_ENDPOINT, settings.HELSINKI_PROFILE_API_URL, settings.HELSINKI_PROFILE_SCOPE, ] @@ -38,21 +39,25 @@ def get_profile(self, oidc_access_token): :return dict with queried values (value may be `None`) """ - payload = { - "query": """ - query myProfile { - myProfile { - verifiedPersonalInformation { - nationalIdentificationNumber - } - } - } - """, - } - api_access_token = self.get_api_access_token(oidc_access_token) + api_access_token = "" + if hasattr(settings, "TUNNISTUS_API_TOKENS_ENDPOINT"): + api_access_token = self.get_api_access_token_tunnistus(oidc_access_token) + else: + api_access_token = self.get_api_access_token(oidc_access_token) try: + payload = { + "query": """ + query myProfile { + myProfile { + verifiedPersonalInformation { + nationalIdentificationNumber + } + } + } + """, + } response = requests.post( settings.HELSINKI_PROFILE_API_URL, json=payload, @@ -60,7 +65,9 @@ def get_profile(self, oidc_access_token): verify=True, headers={"Authorization": "Bearer " + api_access_token}, ) + print(response) response.raise_for_status() + print("get profile: ", response.json()) except RequestException as e: raise HelsinkiProfileException(str(e)) @@ -82,7 +89,7 @@ def get_profile(self, oidc_access_token): def get_api_access_token(self, oidc_access_token): """ - Exchanges OIDC access token for API access token + Exchanges OIDC access token for API access token using Tunnistamo """ try: response = requests.get( @@ -100,3 +107,33 @@ def get_api_access_token(self, oidc_access_token): "Could not obtain API access token, check setting HELSINKI_PROFILE_SCOPE" ) return data[settings.HELSINKI_PROFILE_SCOPE] + + def get_api_access_token_tunnistus(self, oidc_access_token): + """ + Exchanges OIDC access token for API access token using Tunnistus Keycloak + """ + try: + response = requests.post( + settings.TUNNISTUS_API_TOKENS_ENDPOINT, + data={ + "audience": "profile-api-test", # TODO: use setting + "grant_type": "urn:ietf:params:oauth:grant-type:uma-ticket", + "permission": "#access", + }, + headers={ + "Authorization": f"Bearer {oidc_access_token}", + "Content-Type": "application/x-www-form-urlencoded", + }, + timeout=10, + ) + response.raise_for_status() + data = response.json() + print(data) + except RequestException as e: + raise HelsinkiProfileException(str(e)) + + if settings.HELSINKI_PROFILE_SCOPE not in data: + raise HelsinkiProfileException( + "Could not obtain API access token, check setting HELSINKI_PROFILE_SCOPE" + ) + return data[settings.HELSINKI_PROFILE_SCOPE] diff --git a/backend/shared/shared/oidc/auth.py b/backend/shared/shared/oidc/auth.py index c1cdf19126..4dac9bc9e8 100644 --- a/backend/shared/shared/oidc/auth.py +++ b/backend/shared/shared/oidc/auth.py @@ -61,7 +61,6 @@ def create_user(self, claims): def authenticate(self, request, **kwargs): """Authenticates a user based on the OIDC code flow.""" - if not request: return None @@ -96,6 +95,8 @@ def authenticate(self, request, **kwargs): # Validate the token payload = self.verify_token(id_token, nonce=nonce) + print("request:", request) + print("payload:", payload) if payload: try: diff --git a/backend/shared/shared/oidc/views/eauth_views.py b/backend/shared/shared/oidc/views/eauth_views.py index f7991f6161..f9b118c6e3 100644 --- a/backend/shared/shared/oidc/views/eauth_views.py +++ b/backend/shared/shared/oidc/views/eauth_views.py @@ -78,9 +78,13 @@ def get(self, request): else: if not request.session.get("oidc_access_token"): return self.login_failure() + print("session:", request.session.__dict__) user_info = get_userinfo(request) + print("userinfo: ", user_info) + user_ssn = user_info.get("national_id_num") + print("user ssn:", user_ssn) # When authenticating via Tunnistamo, we need to call Helsinki Profile GraphQL API if user_ssn is None: @@ -89,10 +93,16 @@ def get(self, request): request.session.get("oidc_access_token") ) except HelsinkiProfileException as e: + print( + "Reading nationalIdentificationNumber from Helsinki Profile API failed: ", + e, + ) logger.warning( f"Reading nationalIdentificationNumber from Helsinki Profile API failed: {str(e)}" ) return self.login_failure() + print("Profile: ", profile) + user_ssn = profile["user_ssn"] if user_ssn is None: