Skip to content

Commit

Permalink
Merge pull request #387 from TeskaLabs/refactoring/sso-session-creation
Browse files Browse the repository at this point in the history
Move SSO session creation into session service
  • Loading branch information
byewokko authored May 28, 2024
2 parents 991017d + c158054 commit 0feac6f
Show file tree
Hide file tree
Showing 3 changed files with 70 additions and 55 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
## v24.20

### Pre-releases
- `v24.20-alpha8`
- `v24.20-alpha7`
- `v24.20-alpha6`
- `v24.20-alpha5`
Expand All @@ -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`)

---
Expand Down
70 changes: 26 additions & 44 deletions seacatauth/authn/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -328,44 +327,34 @@ 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),
]

session_builders = await self.SessionService.build_sso_root_session(
credentials_id=login_session.SeacatLogin.CredentialsId,
login_descriptor=login_session.SeacatLogin.AuthenticatedVia,
)
if root_session and not root_session.is_anonymous():
# Update existing root session
session = await self.SessionService.update_session(
# 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
session_builders.append(cookie_session_builder())
session = await self.SessionService.create_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(session.Session.Id),
"sid": str(new_sso_session.Session.Id),
"from_ip": from_info,
})
await self.LastActivityService.update_last_activity(
Expand All @@ -374,7 +363,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 new_sso_session


async def create_m2m_session(
Expand Down Expand Up @@ -420,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
Expand All @@ -441,24 +429,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",
Expand Down
53 changes: 42 additions & 11 deletions seacatauth/session/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
)

#

Expand Down Expand Up @@ -647,6 +655,35 @@ async def inherit_or_generate_new_track_id(
return await self.get(dst_session.SessionId)


async def build_sso_root_session(
self,
credentials_id: str,
login_descriptor: dict,
):
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
),
cookie_session_builder(),
]
return session_builders


async def build_client_session(
self,
root_session: SessionAdapter,
Expand Down Expand Up @@ -682,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:
Expand Down

0 comments on commit 0feac6f

Please sign in to comment.