Skip to content

Commit

Permalink
chore: migrate benefit customer ui to new OIDC authentication (hl-122…
Browse files Browse the repository at this point in the history
…5) (#2970)

* chore: change settings for new auth method

* test: fix GDPR API tests
  • Loading branch information
sirtawast authored May 31, 2024
1 parent 2cc0e51 commit cbcf483
Show file tree
Hide file tree
Showing 6 changed files with 88 additions and 41 deletions.
10 changes: 5 additions & 5 deletions .env.benefit-backend.example
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand Down
30 changes: 15 additions & 15 deletions backend/benefit/helsinkibenefit/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -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, ""),
Expand Down Expand Up @@ -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"),
Expand Down Expand Up @@ -411,36 +410,37 @@

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"

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
Expand Down
11 changes: 5 additions & 6 deletions backend/benefit/users/tests/test_gdpr_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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
Expand Down Expand Up @@ -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,
Expand Down
65 changes: 51 additions & 14 deletions backend/shared/shared/helsinki_profile/hp_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
]
Expand All @@ -38,29 +39,35 @@ 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,
timeout=10,
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))

Expand All @@ -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(
Expand All @@ -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]
3 changes: 2 additions & 1 deletion backend/shared/shared/oidc/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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:
Expand Down
10 changes: 10 additions & 0 deletions backend/shared/shared/oidc/views/eauth_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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:
Expand Down

0 comments on commit cbcf483

Please sign in to comment.