Skip to content

Commit

Permalink
feat: pass keycloak action parameter through
Browse files Browse the repository at this point in the history
Running actions with keycloak is done using `kc_action` parameter.
Here, we're just passing the action through so that a user
can e.g. update the password on keycloak while the authentication
still flows through tunnistamo.

Also add some walrus-operators to if-statements.

Refs: HP-2461
  • Loading branch information
charn committed Jul 4, 2024
1 parent 0c5d168 commit 1edf3ae
Show file tree
Hide file tree
Showing 3 changed files with 79 additions and 12 deletions.
17 changes: 10 additions & 7 deletions auth_backends/helsinki_tunnistus_suomifi.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@

class HelsinkiTunnistus(OidcBackchannelLogoutMixin, OpenIdConnectAuth):
"""Authenticates the user against Keycloak proxying to suomi.fi
This is plain OIDC backend, except that it uses the Keycloak provided
user id ("sub" field) as the local user identifier.
This is plain OIDC backend, except that it uses the Keycloak provided
user id ("sub" field) as the local user identifier.
"""
name = 'heltunnistussuomifi'

Expand All @@ -27,14 +28,16 @@ def auth_extra_arguments(self):
#
# This is done to relay the client_id to the Helsinki tunnistus Keycloak.
# The session variable is set in the TunnistamoOidcAuthorizeView.get method.
original_client_id = self.strategy.request.session.get(
if original_client_id := self.strategy.request.session.get(
"oidc_authorize_original_client_id"
)
if original_client_id:
):
extra_arguments["original_client_id"] = original_client_id

ui_locales = self.strategy.request.session.get("ui_locales")
if ui_locales:
# Keycloak action passthrough
if kc_action := self.strategy.request.session.get("oidc_authorize_kc_action"):
extra_arguments["kc_action"] = kc_action

if ui_locales := self.strategy.request.session.get("ui_locales"):
extra_arguments["ui_locales"] = ui_locales

return extra_arguments
Expand Down
66 changes: 63 additions & 3 deletions users/tests/test_tunnistamo_authorize_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,14 +116,14 @@ def test_api_scopes_are_added_to_user_consent_after_authorization(client, api_sc
@pytest.mark.django_db
def test_original_client_id_is_saved_to_the_session(
client,
loginmethod_factory,
oidcclient_factory,
create_client,
):
"""Test that the original client id is saved to the session
"""Test that the original client id is saved to the session.
This is an implementation detail test, but we don't have a better way to test
this right now. Proper testing would need end-to-end tests with e.g. Selenium."""
this right now. Proper testing would need end-to-end tests with e.g. Selenium.
"""
oidc_client = None

if create_client:
Expand Down Expand Up @@ -197,6 +197,66 @@ def test_ui_locales_parameter_of_authorize_request_is_passed_to_helsinki_tunnist
assert intercepted_request["data"].get("ui_locales") == ui_locales


@pytest.mark.parametrize("kc_action", (None, "UPDATE_PASSWORD"))
@pytest.mark.django_db
def test_kc_action_is_saved_to_the_session(
client,
oidcclient_factory,
kc_action,
):
"""Test that the keycloak action (kc_action) is saved to the session."""
oidcclient_factory(
client_id="test_client",
redirect_uris=['https://tunnistamo.test/redirect_uri'],
response_types=["id_token"]
)

url = reverse('authorize')

data = {
'client_id': 'test_client',
'response_type': 'id_token',
'redirect_uri': 'https://tunnistamo.test/redirect_uri',
'scope': 'openid',
'response_mode': 'form_post',
'nonce': 'abcdefg'
}

if kc_action:
data["kc_action"] = kc_action

client.get(url, data)

if kc_action:
session_kc_action = client.session.get("oidc_authorize_kc_action")
assert session_kc_action == kc_action
else:
assert "oidc_authorize_kc_action" not in client.session


@pytest.mark.django_db
def test_kc_action_is_passed_to_helsinki_tunnistus_authentication_service(
settings,
oidcclient_factory,
):
settings.SOCIAL_AUTH_HELTUNNISTUSSUOMIFI_OIDC_ENDPOINT = 'https://heltunnistussuomifi.example.com'
django_client = CancelExampleComRedirectClient()

state = get_random_string()
kc_action = "UPDATE_PASSWORD"
start_oidc_authorize(
django_client,
oidcclient_factory,
backend_name=HelsinkiTunnistus.name,
extra_authorize_params={'state': state, 'kc_action': kc_action},
)

assert len(django_client.intercepted_requests) == 1
intercepted_request = django_client.intercepted_requests[0]
assert intercepted_request["path"] == '/authorize'
assert intercepted_request["data"].get("kc_action") == kc_action


@pytest.mark.django_db
@pytest.mark.parametrize('with_pkce', (True, False))
@pytest.mark.parametrize('response_type', [key for key, val in RESPONSE_TYPE_CHOICES])
Expand Down
8 changes: 6 additions & 2 deletions users/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -166,9 +166,9 @@ class TunnistamoOidcAuthorizeView(AuthorizeView):
def get(self, request, *args, **kwargs):
request.GET = _extend_scope_in_query_params(request.GET)

if request.GET.get('client_id'):
if client_id := request.GET.get("client_id"):
try:
client = Client.objects.get(client_id=request.GET.get('client_id'))
client = Client.objects.get(client_id=client_id)

# Save the client_id to the session to be used in the HelsinkiTunnistus
# social auth backend.
Expand All @@ -178,6 +178,10 @@ def get(self, request, *args, **kwargs):
# validated again in the parent get method.
pass

# Keycloak action passthrough
if kc_action := request.GET.get("kc_action"):
request.session["oidc_authorize_kc_action"] = kc_action

return super().get(request, *args, **kwargs)

def post(self, request, *args, **kwargs):
Expand Down

0 comments on commit 1edf3ae

Please sign in to comment.