From 19dc89051a1984233a054d3579573278867e4357 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robin=20Hru=C5=A1ka?= Date: Tue, 28 May 2024 12:14:04 +0200 Subject: [PATCH 1/6] move sso session creation into session service --- seacatauth/authn/service.py | 43 +++++++-------------------- seacatauth/session/service.py | 56 ++++++++++++++++++++++++++++++++++- 2 files changed, 65 insertions(+), 34 deletions(-) diff --git a/seacatauth/authn/service.py b/seacatauth/authn/service.py index f77499b2..928c343d 100644 --- a/seacatauth/authn/service.py +++ b/seacatauth/authn/service.py @@ -2,6 +2,7 @@ import json import logging import re +import typing import urllib.parse import asab @@ -328,44 +329,20 @@ async def authenticate(self, login_session, request_data): break return authenticated + async def login(self, login_session, root_session: SessionAdapter | None = None, from_info: list = None): """ - Build and create a root session + Build and create an SSO root session """ - scope = frozenset(["profile", "email", "phone"]) - - ext_login_svc = self.App.get_service("seacatauth.ExternalLoginService") - session_builders = [ - await credentials_session_builder(self.CredentialsService, login_session.SeacatLogin.CredentialsId, scope), - await authz_session_builder( - tenant_service=self.TenantService, - role_service=self.RoleService, - credentials_id=login_session.SeacatLogin.CredentialsId, - tenants=None # Root session is tenant-agnostic - ), - authentication_session_builder(login_session.SeacatLogin.AuthenticatedVia), - await available_factors_session_builder(self, login_session.SeacatLogin.CredentialsId), - await external_login_session_builder(ext_login_svc, login_session.SeacatLogin.CredentialsId), - ] - - if root_session and not root_session.is_anonymous(): - # Update existing root session - session = await self.SessionService.update_session( - root_session.SessionId, - session_builders=session_builders - ) - else: - # Create a new root session - session_builders.append(cookie_session_builder()) - session = await self.SessionService.create_session( - session_type="root", - session_builders=session_builders, - ) - + sso_session = await self.SessionService.build_and_upsert_sso_root_session( + credentials_id=login_session.SeacatLogin.CredentialsId, + login_descriptor=login_session.SeacatLogin.AuthenticatedVia, + current_sso_session=root_session, + ) AuditLogger.log(asab.LOG_NOTICE, "Authentication successful", struct_data={ "cid": login_session.SeacatLogin.CredentialsId, "lsid": login_session.Id, - "sid": str(session.Session.Id), + "sid": str(sso_session.Session.Id), "from_ip": from_info, }) await self.LastActivityService.update_last_activity( @@ -374,7 +351,7 @@ async def login(self, login_session, root_session: SessionAdapter | None = None, # Delete login session await self.delete_login_session(login_session.Id) - return session + return sso_session async def create_m2m_session( diff --git a/seacatauth/session/service.py b/seacatauth/session/service.py index 24c6f86a..b1199efd 100644 --- a/seacatauth/session/service.py +++ b/seacatauth/session/service.py @@ -20,7 +20,15 @@ from .adapter import SessionAdapter, rest_get from .algorithmic import AlgorithmicSessionProvider from .token import SessionTokenService -from .builders import oauth2_session_builder, credentials_session_builder, authz_session_builder +from .builders import ( + oauth2_session_builder, + credentials_session_builder, + authz_session_builder, + authentication_session_builder, + available_factors_session_builder, + external_login_session_builder, + cookie_session_builder +) # @@ -647,6 +655,52 @@ async def inherit_or_generate_new_track_id( return await self.get(dst_session.SessionId) + async def build_and_upsert_sso_root_session( + self, + credentials_id: str, + login_descriptor: dict, + current_sso_session: typing.Optional[SessionAdapter] = None, + ): + authentication_service = self.App.get_service("seacatauth.AuthenticationService") + credentials_service = self.App.get_service("seacatauth.CredentialsService") + tenant_service = self.App.get_service("seacatauth.TenantService") + role_service = self.App.get_service("seacatauth.RoleService") + + scope = frozenset(["profile", "email", "phone"]) + ext_login_svc = self.App.get_service("seacatauth.ExternalLoginService") + session_builders = [ + await credentials_session_builder(credentials_service, credentials_id, scope), + authentication_session_builder(login_descriptor), + await available_factors_session_builder(authentication_service, credentials_id), + await external_login_session_builder(ext_login_svc, credentials_id), + # TODO: SSO session should not need to have Authz data + await authz_session_builder( + tenant_service=tenant_service, + role_service=role_service, + credentials_id=credentials_id, + tenants=None # Root session is tenant-agnostic + ), + ] + + if current_sso_session and not current_sso_session.is_anonymous(): + # Update existing SSO root session (re-login) + assert current_sso_session.Session.Type == "root" + assert current_sso_session.Credentials.Id == credentials_id + new_sso_session = await self.update_session( + current_sso_session.SessionId, + session_builders=session_builders + ) + else: + # Create a new root session + session_builders.append(cookie_session_builder()) + new_sso_session = await self.create_session( + session_type="root", + session_builders=session_builders, + ) + return new_sso_session + + + async def build_client_session( self, root_session: SessionAdapter, From a40026d09ad1b691f3d78bc95b3682bd4c8a2133 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robin=20Hru=C5=A1ka?= Date: Tue, 28 May 2024 12:16:22 +0200 Subject: [PATCH 2/6] update CHANGELOG.md --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 96d05ad1..ec90e45a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## v24.20 ### Pre-releases +- `v24.20-alpha8` - `v24.20-alpha7` - `v24.20-alpha6` - `v24.20-alpha5` @@ -25,6 +26,7 @@ - Configurable password criteria (#372, `v24.20-alpha1`) ### Refactoring +- Move SSO session creation into session service (#387, `v24.20-alpha8`) - Refactor duplicate code in session build (#386, `v24.20-alpha7`) --- From a00c4cd7714f0f6ef06224c518828f96e706c37a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robin=20Hru=C5=A1ka?= Date: Tue, 28 May 2024 12:16:29 +0200 Subject: [PATCH 3/6] flake8 --- seacatauth/authn/service.py | 1 - 1 file changed, 1 deletion(-) diff --git a/seacatauth/authn/service.py b/seacatauth/authn/service.py index 928c343d..35c0a526 100644 --- a/seacatauth/authn/service.py +++ b/seacatauth/authn/service.py @@ -2,7 +2,6 @@ import json import logging import re -import typing import urllib.parse import asab From 9df543fe057103c5d558cf13d357be102f245cc7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robin=20Hru=C5=A1ka?= Date: Tue, 28 May 2024 13:14:06 +0200 Subject: [PATCH 4/6] use build session method in impersonation --- seacatauth/authn/service.py | 52 ++++++++++++++++++++--------------- seacatauth/session/service.py | 39 ++++++-------------------- 2 files changed, 38 insertions(+), 53 deletions(-) diff --git a/seacatauth/authn/service.py b/seacatauth/authn/service.py index 35c0a526..8fe13df1 100644 --- a/seacatauth/authn/service.py +++ b/seacatauth/authn/service.py @@ -333,15 +333,29 @@ async def login(self, login_session, root_session: SessionAdapter | None = None, """ Build and create an SSO root session """ - sso_session = await self.SessionService.build_and_upsert_sso_root_session( + session_builders = await self.SessionService.build_sso_root_session( credentials_id=login_session.SeacatLogin.CredentialsId, login_descriptor=login_session.SeacatLogin.AuthenticatedVia, - current_sso_session=root_session, ) + if root_session and not root_session.is_anonymous(): + # Update existing SSO root session (re-login) + assert root_session.Session.Type == "root" + assert root_session.Credentials.Id == login_session.SeacatLogin.CredentialsId + new_sso_session = await self.update_session( + root_session.SessionId, + session_builders=session_builders + ) + else: + # Create a new root session + new_sso_session = await self.create_session( + session_type="root", + session_builders=session_builders, + ) + AuditLogger.log(asab.LOG_NOTICE, "Authentication successful", struct_data={ "cid": login_session.SeacatLogin.CredentialsId, "lsid": login_session.Id, - "sid": str(sso_session.Session.Id), + "sid": str(new_sso_session.Session.Id), "from_ip": from_info, }) await self.LastActivityService.update_last_activity( @@ -350,7 +364,7 @@ async def login(self, login_session, root_session: SessionAdapter | None = None, # Delete login session await self.delete_login_session(login_session.Id) - return sso_session + return new_sso_session async def create_m2m_session( @@ -417,24 +431,18 @@ async def create_impersonated_session(self, impersonator_session, target_cid: st "from the impersonated session's authorization scope.", struct_data={"impersonator_cid": impersonator_cid, "target_cid": target_cid}) - scope = frozenset(["profile", "email", "phone"]) - session_builders = [ - await credentials_session_builder(self.CredentialsService, target_cid, scope), - await authz_session_builder( - tenant_service=self.TenantService, - role_service=self.RoleService, - credentials_id=target_cid, - tenants=None, # Root session is tenant-agnostic - exclude_resources={"authz:superuser", "authz:impersonate"}, - ), - cookie_session_builder(), - await available_factors_session_builder(self, target_cid), - await external_login_session_builder(ext_login_svc, target_cid), - ( - (SessionAdapter.FN.Authentication.ImpersonatorCredentialsId, impersonator_cid), - (SessionAdapter.FN.Authentication.ImpersonatorSessionId, impersonator_session.SessionId) - ) - ] + session_builders = await self.SessionService.build_sso_root_session( + credentials_id=target_cid, + # Use default login descriptor + login_descriptor={ + "id": "default", + "factors": [{"id": "password", "type": "password"}] + }, + ) + session_builders.append(( + (SessionAdapter.FN.Authentication.ImpersonatorCredentialsId, impersonator_cid), + (SessionAdapter.FN.Authentication.ImpersonatorSessionId, impersonator_session.SessionId), + )) session = await self.SessionService.create_session( session_type="root", diff --git a/seacatauth/session/service.py b/seacatauth/session/service.py index b1199efd..5c038a9c 100644 --- a/seacatauth/session/service.py +++ b/seacatauth/session/service.py @@ -655,11 +655,10 @@ async def inherit_or_generate_new_track_id( return await self.get(dst_session.SessionId) - async def build_and_upsert_sso_root_session( + async def build_sso_root_session( self, credentials_id: str, login_descriptor: dict, - current_sso_session: typing.Optional[SessionAdapter] = None, ): authentication_service = self.App.get_service("seacatauth.AuthenticationService") credentials_service = self.App.get_service("seacatauth.CredentialsService") @@ -678,27 +677,11 @@ async def build_and_upsert_sso_root_session( tenant_service=tenant_service, role_service=role_service, credentials_id=credentials_id, - tenants=None # Root session is tenant-agnostic + tenants=None, # Root session is tenant-agnostic ), + cookie_session_builder(), ] - - if current_sso_session and not current_sso_session.is_anonymous(): - # Update existing SSO root session (re-login) - assert current_sso_session.Session.Type == "root" - assert current_sso_session.Credentials.Id == credentials_id - new_sso_session = await self.update_session( - current_sso_session.SessionId, - session_builders=session_builders - ) - else: - # Create a new root session - session_builders.append(cookie_session_builder()) - new_sso_session = await self.create_session( - session_type="root", - session_builders=session_builders, - ) - return new_sso_session - + return session_builders async def build_client_session( @@ -736,19 +719,13 @@ async def build_client_session( ] if "profile" in scope or "userinfo:authn" in scope or "userinfo:*" in scope: - available_factors = await authentication_service.get_eligible_factors(root_session.Credentials.Id) - available_external_logins = {} - for result in await external_login_service.list(root_session.Credentials.Id): - try: - available_external_logins[result["type"]] = result["sub"] - except KeyError: - # BACK COMPAT - available_external_logins[result["t"]] = result["s"] + session_builders.append( + await external_login_session_builder(external_login_service, root_session.Credentials.Id)) + session_builders.append( + await available_factors_session_builder(authentication_service, root_session.Credentials.Id)) session_builders.append([ (SessionAdapter.FN.Authentication.LoginDescriptor, root_session.Authentication.LoginDescriptor), (SessionAdapter.FN.Authentication.LoginFactors, root_session.Authentication.LoginFactors), - (SessionAdapter.FN.Authentication.AvailableFactors, available_factors), - (SessionAdapter.FN.Authentication.ExternalLoginOptions, available_external_logins), ]) if "batman" in scope: From de8ba221b7abf28327b446ab7a1e794e782e8198 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robin=20Hru=C5=A1ka?= Date: Tue, 28 May 2024 13:15:09 +0200 Subject: [PATCH 5/6] flake8 --- seacatauth/authn/service.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/seacatauth/authn/service.py b/seacatauth/authn/service.py index 8fe13df1..3a9cbecb 100644 --- a/seacatauth/authn/service.py +++ b/seacatauth/authn/service.py @@ -16,10 +16,9 @@ from ..session import ( credentials_session_builder, authz_session_builder, - cookie_session_builder, authentication_session_builder, available_factors_session_builder, - external_login_session_builder, SessionAdapter, + SessionAdapter, ) from ..events import EventTypes From c158054219523f156c630f59a2bd5ebf11252d17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robin=20Hru=C5=A1ka?= Date: Tue, 28 May 2024 13:50:37 +0200 Subject: [PATCH 6/6] flake8 --- seacatauth/authn/service.py | 1 - 1 file changed, 1 deletion(-) diff --git a/seacatauth/authn/service.py b/seacatauth/authn/service.py index 3a9cbecb..d503fe33 100644 --- a/seacatauth/authn/service.py +++ b/seacatauth/authn/service.py @@ -409,7 +409,6 @@ async def create_impersonated_session(self, impersonator_session, target_cid: st """ Create a new root session as a different user. Equivalent to logging in as the target user. """ - ext_login_svc = self.App.get_service("seacatauth.ExternalLoginService") impersonator_cid = impersonator_session.Credentials.Id # Check if target exists