From f10b96f04dde3de55efd37ad818228e7c03fe5dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robin=20Hru=C5=A1ka?= Date: Tue, 25 Jun 2024 14:30:12 +0200 Subject: [PATCH 1/7] catch b64 decoding errors and log token value --- seacatauth/openidconnect/service.py | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/seacatauth/openidconnect/service.py b/seacatauth/openidconnect/service.py index 49de676c..2c284762 100644 --- a/seacatauth/openidconnect/service.py +++ b/seacatauth/openidconnect/service.py @@ -1,3 +1,4 @@ +import binascii import datetime import json import base64 @@ -524,7 +525,11 @@ async def get_session_by_authorization_code(self, code, code_verifier: str | Non """ Retrieve session by its temporary authorization code. """ - token_bytes = base64.urlsafe_b64decode(code.encode("ascii")) + try: + token_bytes = base64.urlsafe_b64decode(code.encode("ascii")) + except binascii.Error as e: + L.error("Corrupt authorization code format: Base64 decoding failed.", struct_data={"code": code}) + raise exceptions.SessionNotFoundError("Corrupt authorization code format") from e token_data = await self.TokenService.get(token_bytes, token_type=AuthorizationCode.TokenType) if "cc" in token_data: self.PKCE.evaluate_code_challenge( @@ -544,7 +549,11 @@ async def get_session_by_access_token(self, token_value: str): """ Retrieve session by its access token. """ - token_bytes = base64.urlsafe_b64decode(token_value.encode("ascii")) + try: + token_bytes = base64.urlsafe_b64decode(token_value.encode("ascii")) + except binascii.Error as e: + L.error("Corrupt access token format: Base64 decoding failed.", struct_data={"token_value": token_value}) + raise exceptions.SessionNotFoundError("Corrupt access token format") from e try: token_data = await self.TokenService.get(token_bytes, token_type=AccessToken.TokenType) except KeyError: @@ -564,11 +573,15 @@ async def get_session_by_refresh_token(self, token_value: str): """ Retrieve session by its refresh token. """ - token_bytes = base64.urlsafe_b64decode(token_value.encode("ascii")) + try: + token_bytes = base64.urlsafe_b64decode(token_value.encode("ascii")) + except binascii.Error as e: + L.error("Corrupt refresh token format: Base64 decoding failed.", struct_data={"token_value": token_value}) + raise exceptions.SessionNotFoundError("Corrupt refresh token format") from e try: token_data = await self.TokenService.get(token_bytes, token_type=RefreshToken.TokenType) except KeyError: - raise exceptions.SessionNotFoundError("Invalid or expired access token") + raise exceptions.SessionNotFoundError("Invalid or expired refresh token") try: session = await self.SessionService.get(token_data["sid"]) except KeyError: From 4662d39d12f6584c461384e0707eaabb182c2b22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robin=20Hru=C5=A1ka?= Date: Tue, 25 Jun 2024 14:43:27 +0200 Subject: [PATCH 2/7] update CHANGELOG.md --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index fbb9b84b..cf60b253 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## v24.20 ### Pre-releases +- `v24.20-alpha10` - `v24.20-alpha9` - `v24.20-alpha8` - `v24.20-alpha7` @@ -18,6 +19,7 @@ - Default password criteria are more restrictive (#372, `v24.20-alpha1`, Compatible with Seacat Auth Webui v24.19-alpha and later, Seacat Account Webui v24.08-beta and later) ### Fix +- Catch token decoding errors when finding sessions (#397, `v24.20-alpha10`) - Properly encrypt cookie value in session update (#394, `v24.20-alpha8`) - Properly parse URL query before adding new parameters (#393, `v24.20-alpha8`) - Delete client cookie on introspection failure (#385, `v24.20-alpha6`) From 1f3ace079355c3a9c73a9f7f10658987860d8b36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robin=20Hru=C5=A1ka?= Date: Tue, 25 Jun 2024 14:57:33 +0200 Subject: [PATCH 3/7] also catch ascii decode errors --- seacatauth/openidconnect/service.py | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/seacatauth/openidconnect/service.py b/seacatauth/openidconnect/service.py index 2c284762..d2f695c2 100644 --- a/seacatauth/openidconnect/service.py +++ b/seacatauth/openidconnect/service.py @@ -530,6 +530,10 @@ async def get_session_by_authorization_code(self, code, code_verifier: str | Non except binascii.Error as e: L.error("Corrupt authorization code format: Base64 decoding failed.", struct_data={"code": code}) raise exceptions.SessionNotFoundError("Corrupt authorization code format") from e + except UnicodeEncodeError as e: + L.error("Corrupt authorization code format: ASCII decoding failed.", struct_data={"code": code}) + raise exceptions.SessionNotFoundError("Corrupt authorization code format") from e + token_data = await self.TokenService.get(token_bytes, token_type=AuthorizationCode.TokenType) if "cc" in token_data: self.PKCE.evaluate_code_challenge( @@ -552,8 +556,14 @@ async def get_session_by_access_token(self, token_value: str): try: token_bytes = base64.urlsafe_b64decode(token_value.encode("ascii")) except binascii.Error as e: - L.error("Corrupt access token format: Base64 decoding failed.", struct_data={"token_value": token_value}) + L.error("Corrupt access token format: Base64 decoding failed.", struct_data={ + "token_value": token_value}) + raise exceptions.SessionNotFoundError("Corrupt access token format") from e + except UnicodeEncodeError as e: + L.error("Corrupt access token format: ASCII decoding failed.", struct_data={ + "token_value": token_value}) raise exceptions.SessionNotFoundError("Corrupt access token format") from e + try: token_data = await self.TokenService.get(token_bytes, token_type=AccessToken.TokenType) except KeyError: @@ -576,8 +586,14 @@ async def get_session_by_refresh_token(self, token_value: str): try: token_bytes = base64.urlsafe_b64decode(token_value.encode("ascii")) except binascii.Error as e: - L.error("Corrupt refresh token format: Base64 decoding failed.", struct_data={"token_value": token_value}) + L.error("Corrupt refresh token format: Base64 decoding failed.", struct_data={ + "token_value": token_value}) + raise exceptions.SessionNotFoundError("Corrupt refresh token format") from e + except UnicodeEncodeError as e: + L.error("Corrupt refresh token format: ASCII decoding failed.", struct_data={ + "token_value": token_value}) raise exceptions.SessionNotFoundError("Corrupt refresh token format") from e + try: token_data = await self.TokenService.get(token_bytes, token_type=RefreshToken.TokenType) except KeyError: From e4c8440da8152f2497a4c86479c6b31e8f43a4f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robin=20Hru=C5=A1ka?= Date: Wed, 26 Jun 2024 14:34:27 +0200 Subject: [PATCH 4/7] fix attribute error in update_credentials --- seacatauth/credentials/service.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/seacatauth/credentials/service.py b/seacatauth/credentials/service.py index cad5ce0e..ab08e7f6 100644 --- a/seacatauth/credentials/service.py +++ b/seacatauth/credentials/service.py @@ -733,6 +733,7 @@ async def can_access_credentials(self, session, credentials_id: str) -> bool: """ Check if the target user is a member of currently authorized tenant """ + tenant_service = self.App.get_service("seacatauth.TenantService") if not session: return False if session.is_superuser(): @@ -740,7 +741,7 @@ async def can_access_credentials(self, session, credentials_id: str) -> bool: for tenant_id in session.Authorization.Authz.keys(): if tenant_id == "*": continue - if await self.TenantService.has_tenant_assigned(credentials_id, tenant_id): + if await tenant_service.has_tenant_assigned(credentials_id, tenant_id): # User is member of currently authorized tenant return True # The request and the target credentials have no tenant in common From 18e47330436d9c5d333215db74ade29fc86b4c14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robin=20Hru=C5=A1ka?= Date: Wed, 26 Jun 2024 14:46:12 +0200 Subject: [PATCH 5/7] update CHANGELOG.md --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index cf60b253..8e1a5c53 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## v24.20 ### Pre-releases +- `v24.20-alpha11` - `v24.20-alpha10` - `v24.20-alpha9` - `v24.20-alpha8` @@ -19,6 +20,7 @@ - Default password criteria are more restrictive (#372, `v24.20-alpha1`, Compatible with Seacat Auth Webui v24.19-alpha and later, Seacat Account Webui v24.08-beta and later) ### Fix +- Fix AttributeError in credentials update (#399, `v24.20-alpha11`) - Catch token decoding errors when finding sessions (#397, `v24.20-alpha10`) - Properly encrypt cookie value in session update (#394, `v24.20-alpha8`) - Properly parse URL query before adding new parameters (#393, `v24.20-alpha8`) From 448e4eca2714a476eb6714bbea31bef384a366bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robin=20Hru=C5=A1ka?= Date: Wed, 26 Jun 2024 16:45:22 +0200 Subject: [PATCH 6/7] log login factor verification failure --- seacatauth/authn/login_descriptor.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/seacatauth/authn/login_descriptor.py b/seacatauth/authn/login_descriptor.py index b0578b9a..10d1e4ce 100644 --- a/seacatauth/authn/login_descriptor.py +++ b/seacatauth/authn/login_descriptor.py @@ -1,5 +1,6 @@ import logging import typing +import asab from .login_factors import LoginFactorABC @@ -101,6 +102,11 @@ async def authenticate(self, login_session, request_data): assert len(self.FactorGroups) == 1 for factor in self.FactorGroups[0]: if (await factor.authenticate(login_session, request_data)) is False: + L.log(asab.LOG_NOTICE, "Login factor verification failed.", struct_data={ + "descriptor_id": self.ID, + "factor_type": factor.Type, + "cid": login_session.CredentialsId, + }) return False return True From 69d66ed80eb9d2aa8401366e3af3e7d922631a3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robin=20Hru=C5=A1ka?= Date: Wed, 26 Jun 2024 16:55:43 +0200 Subject: [PATCH 7/7] update CHANGELOG.md --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8e1a5c53..31d7ebe4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## v24.20 ### Pre-releases +- `v24.20-alpha12` - `v24.20-alpha11` - `v24.20-alpha10` - `v24.20-alpha9` @@ -30,6 +31,7 @@ - Properly handle Argon2 verification error in login call (#378, `v24.20-alpha3`) ### Features +- Log login factor verification failure (#402, `v24.20-alpha12`) - External login with dynamic redirection (#384, `v24.20-alpha9`) - Custom response codes for tenant-related authorization errors (#392, `v24.20-alpha8`) - Implement OAuth refresh tokens (#358, `v24.20-alpha2`)