Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor SSO session creation #389

Merged
merged 4 commits into from
May 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.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(
new_sso_session = await self.SessionService.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
Loading