reauth 0.2.0__tar.gz → 0.2.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {reauth-0.2.0 → reauth-0.2.2}/PKG-INFO +2 -2
- {reauth-0.2.0 → reauth-0.2.2}/reauth/__init__.py +1 -1
- {reauth-0.2.0 → reauth-0.2.2}/reauth/authentication_session.py +4 -1
- {reauth-0.2.0 → reauth-0.2.2}/reauth/factors/backup_codes.py +4 -1
- {reauth-0.2.0 → reauth-0.2.2}/reauth/factors/base.py +13 -1
- {reauth-0.2.0 → reauth-0.2.2}/reauth/factors/email_otp.py +2 -1
- {reauth-0.2.0 → reauth-0.2.2}/reauth/factors/hotp.py +2 -1
- {reauth-0.2.0 → reauth-0.2.2}/reauth/factors/oauth2/apple.py +3 -2
- {reauth-0.2.0 → reauth-0.2.2}/reauth/factors/oauth2/base.py +2 -1
- {reauth-0.2.0 → reauth-0.2.2}/reauth/factors/oauth2/github.py +2 -0
- {reauth-0.2.0 → reauth-0.2.2}/reauth/factors/oauth2/google.py +3 -2
- {reauth-0.2.0 → reauth-0.2.2}/reauth/factors/oauth2/oidc.py +13 -3
- {reauth-0.2.0 → reauth-0.2.2}/reauth/factors/totp.py +2 -1
- {reauth-0.2.0 → reauth-0.2.2}/tests/factors/oauth2/test_oidc.py +3 -4
- {reauth-0.2.0 → reauth-0.2.2}/tests/test_authentication_session.py +38 -0
- {reauth-0.2.0 → reauth-0.2.2}/.agents/skills/.gitkeep +0 -0
- {reauth-0.2.0 → reauth-0.2.2}/.agents/skills/authentication-terminology/SKILL.md +0 -0
- {reauth-0.2.0 → reauth-0.2.2}/.agents/skills/openid-connect-core/SKILL.md +0 -0
- {reauth-0.2.0 → reauth-0.2.2}/.agents/skills/openid-connect-core/references/openid-connect-core-1_0.txt +0 -0
- {reauth-0.2.0 → reauth-0.2.2}/.agents/skills/openid-connect-discovery/SKILL.md +0 -0
- {reauth-0.2.0 → reauth-0.2.2}/.agents/skills/openid-connect-discovery/references/openid-connect-discovery-1_0.txt +0 -0
- {reauth-0.2.0 → reauth-0.2.2}/.agents/skills/owasp-cheatsheets/SKILL.md +0 -0
- {reauth-0.2.0 → reauth-0.2.2}/.agents/skills/rfc-spec/SKILL.md +0 -0
- {reauth-0.2.0 → reauth-0.2.2}/.agents/skills/rfc-spec/scripts/create-rfc-skill.sh +0 -0
- {reauth-0.2.0 → reauth-0.2.2}/.agents/skills/rfc4226/SKILL.md +0 -0
- {reauth-0.2.0 → reauth-0.2.2}/.agents/skills/rfc4226/references/RFC4226.txt +0 -0
- {reauth-0.2.0 → reauth-0.2.2}/.agents/skills/rfc6238/SKILL.md +0 -0
- {reauth-0.2.0 → reauth-0.2.2}/.agents/skills/rfc6238/references/RFC6238.txt +0 -0
- {reauth-0.2.0 → reauth-0.2.2}/.agents/skills/rfc6749/SKILL.md +0 -0
- {reauth-0.2.0 → reauth-0.2.2}/.agents/skills/rfc6749/references/RFC6749.txt +0 -0
- {reauth-0.2.0 → reauth-0.2.2}/.agents/skills/rfc7523/SKILL.md +0 -0
- {reauth-0.2.0 → reauth-0.2.2}/.agents/skills/rfc7523/references/RFC7523.txt +0 -0
- {reauth-0.2.0 → reauth-0.2.2}/.agents/skills/rfc7636/SKILL.md +0 -0
- {reauth-0.2.0 → reauth-0.2.2}/.agents/skills/rfc7636/references/RFC7636.txt +0 -0
- {reauth-0.2.0 → reauth-0.2.2}/.agents/skills/rfc8176/SKILL.md +0 -0
- {reauth-0.2.0 → reauth-0.2.2}/.agents/skills/rfc8176/references/RFC8176.txt +0 -0
- {reauth-0.2.0 → reauth-0.2.2}/.agents/skills/webauthn-2/SKILL.md +0 -0
- {reauth-0.2.0 → reauth-0.2.2}/.agents/skills/webauthn-2/references/webauthn-2.txt +0 -0
- {reauth-0.2.0 → reauth-0.2.2}/.editorconfig +0 -0
- {reauth-0.2.0 → reauth-0.2.2}/.github/workflows/build.yml +0 -0
- {reauth-0.2.0 → reauth-0.2.2}/.github/workflows/copilot-setup-steps.yml +0 -0
- {reauth-0.2.0 → reauth-0.2.2}/.github/workflows/documentation.yml +0 -0
- {reauth-0.2.0 → reauth-0.2.2}/.gitignore +0 -0
- {reauth-0.2.0 → reauth-0.2.2}/.zed/settings.json +0 -0
- {reauth-0.2.0 → reauth-0.2.2}/AGENTS.md +0 -0
- {reauth-0.2.0 → reauth-0.2.2}/LICENSE +0 -0
- {reauth-0.2.0 → reauth-0.2.2}/README.md +0 -0
- {reauth-0.2.0 → reauth-0.2.2}/docs/index.md +0 -0
- {reauth-0.2.0 → reauth-0.2.2}/docs/reference/reauth.md +0 -0
- {reauth-0.2.0 → reauth-0.2.2}/justfile +0 -0
- {reauth-0.2.0 → reauth-0.2.2}/mkdocs.yml +0 -0
- {reauth-0.2.0 → reauth-0.2.2}/pyproject.toml +0 -0
- {reauth-0.2.0 → reauth-0.2.2}/reauth/amr.py +0 -0
- {reauth-0.2.0 → reauth-0.2.2}/reauth/crypto.py +0 -0
- {reauth-0.2.0 → reauth-0.2.2}/reauth/exceptions.py +0 -0
- {reauth-0.2.0 → reauth-0.2.2}/reauth/factors/__init__.py +0 -0
- {reauth-0.2.0 → reauth-0.2.2}/reauth/factors/oauth2/__init__.py +0 -0
- {reauth-0.2.0 → reauth-0.2.2}/reauth/factors/oauth2/pkce.py +0 -0
- {reauth-0.2.0 → reauth-0.2.2}/reauth/factors/oauth2/state.py +0 -0
- {reauth-0.2.0 → reauth-0.2.2}/reauth/logging.py +0 -0
- {reauth-0.2.0 → reauth-0.2.2}/reauth/py.typed +0 -0
- {reauth-0.2.0 → reauth-0.2.2}/reauth/timestamp.py +0 -0
- {reauth-0.2.0 → reauth-0.2.2}/solo.yml +0 -0
- {reauth-0.2.0 → reauth-0.2.2}/tests/__init__.py +0 -0
- {reauth-0.2.0 → reauth-0.2.2}/tests/conftest.py +0 -0
- {reauth-0.2.0 → reauth-0.2.2}/tests/factors/__init__.py +0 -0
- {reauth-0.2.0 → reauth-0.2.2}/tests/factors/oauth2/__init__.py +0 -0
- {reauth-0.2.0 → reauth-0.2.2}/tests/factors/oauth2/conftest.py +0 -0
- {reauth-0.2.0 → reauth-0.2.2}/tests/factors/oauth2/test_apple.py +0 -0
- {reauth-0.2.0 → reauth-0.2.2}/tests/factors/oauth2/test_base.py +0 -0
- {reauth-0.2.0 → reauth-0.2.2}/tests/factors/oauth2/test_github.py +0 -0
- {reauth-0.2.0 → reauth-0.2.2}/tests/factors/oauth2/test_pkce.py +0 -0
- {reauth-0.2.0 → reauth-0.2.2}/tests/factors/oauth2/test_state.py +0 -0
- {reauth-0.2.0 → reauth-0.2.2}/tests/factors/test_backup_codes.py +0 -0
- {reauth-0.2.0 → reauth-0.2.2}/tests/factors/test_email_otp.py +0 -0
- {reauth-0.2.0 → reauth-0.2.2}/tests/factors/test_hotp.py +0 -0
- {reauth-0.2.0 → reauth-0.2.2}/tests/factors/test_totp.py +0 -0
- {reauth-0.2.0 → reauth-0.2.2}/tests/test_crypto.py +0 -0
- {reauth-0.2.0 → reauth-0.2.2}/uv.lock +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: reauth
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Summary: The authentication toolkit for Python
|
|
5
5
|
Project-URL: Documentation, https://frankie567.github.io/reauth/
|
|
6
6
|
Project-URL: Source, https://github.com/frankie567/reauth
|
|
@@ -212,7 +212,8 @@ class AuthenticationSessionService(abc.ABC):
|
|
|
212
212
|
authentication_session.identity_id = identity_id
|
|
213
213
|
authentication_session.amr.append(factor.AMR)
|
|
214
214
|
authentication_session.used_factors.append(factor.identifier)
|
|
215
|
-
authentication_session.step
|
|
215
|
+
previous_step = authentication_session.step
|
|
216
|
+
authentication_session.step += factor.advance_by
|
|
216
217
|
await self.update(authentication_session)
|
|
217
218
|
|
|
218
219
|
logger.info(
|
|
@@ -222,6 +223,8 @@ class AuthenticationSessionService(abc.ABC):
|
|
|
222
223
|
"identity_id": identity_id,
|
|
223
224
|
"factor_identifier": factor.identifier,
|
|
224
225
|
"factor_amr": str(factor.AMR),
|
|
226
|
+
"previous_step": previous_step,
|
|
227
|
+
"advance_by": factor.advance_by,
|
|
225
228
|
"step": authentication_session.step,
|
|
226
229
|
},
|
|
227
230
|
)
|
|
@@ -68,6 +68,7 @@ class BackupCodesFactor(FactorBase[BackupCodesEnrollment], abc.ABC):
|
|
|
68
68
|
chars: str = DEFAULT_CHARS,
|
|
69
69
|
identifier: str = "backup_codes",
|
|
70
70
|
step: int = 1,
|
|
71
|
+
advance_by: int = 1,
|
|
71
72
|
) -> None:
|
|
72
73
|
"""
|
|
73
74
|
Initialize the backup codes factor.
|
|
@@ -79,8 +80,10 @@ class BackupCodesFactor(FactorBase[BackupCodesEnrollment], abc.ABC):
|
|
|
79
80
|
chars: Character set for code generation (default: excludes ambiguous chars).
|
|
80
81
|
identifier: Unique identifier for the factor (default: "backup_codes").
|
|
81
82
|
step: Authentication step at which this factor can be used (default: 1).
|
|
83
|
+
advance_by: Number of steps to advance after successful verification
|
|
84
|
+
(default: 1).
|
|
82
85
|
"""
|
|
83
|
-
super().__init__(identifier=identifier, step=step)
|
|
86
|
+
super().__init__(identifier=identifier, step=step, advance_by=advance_by)
|
|
84
87
|
self.hash_secret = hash_secret
|
|
85
88
|
self.code_length = code_length
|
|
86
89
|
self.code_count = code_count
|
|
@@ -6,6 +6,8 @@ from reauth.logging import get_logger
|
|
|
6
6
|
|
|
7
7
|
logger = get_logger(__name__)
|
|
8
8
|
|
|
9
|
+
_ADVANCE_BY_VALUE_ERROR = "advance_by must be at least 1"
|
|
10
|
+
|
|
9
11
|
|
|
10
12
|
class FactorEnrollment(typing.Protocol):
|
|
11
13
|
"""
|
|
@@ -22,7 +24,7 @@ class FactorBase[ENROLLMENT: FactorEnrollment](abc.ABC):
|
|
|
22
24
|
|
|
23
25
|
AMR: typing.ClassVar[AuthenticationMethodReference]
|
|
24
26
|
|
|
25
|
-
def __init__(self, *, identifier: str, step: int = 0) -> None:
|
|
27
|
+
def __init__(self, *, identifier: str, step: int = 0, advance_by: int = 1) -> None:
|
|
26
28
|
"""
|
|
27
29
|
Initialize the factor base.
|
|
28
30
|
|
|
@@ -32,17 +34,27 @@ class FactorBase[ENROLLMENT: FactorEnrollment](abc.ABC):
|
|
|
32
34
|
step: The authentication step at which this factor can be used.
|
|
33
35
|
Factors are only available when the session's current step matches this value.
|
|
34
36
|
Defaults to 0 (can be first factor).
|
|
37
|
+
advance_by: The number of steps to advance the authentication session after
|
|
38
|
+
this factor is successfully verified. Defaults to 1.
|
|
39
|
+
|
|
40
|
+
Raises:
|
|
41
|
+
ValueError: If advance_by is less than 1.
|
|
35
42
|
"""
|
|
43
|
+
if advance_by < 1:
|
|
44
|
+
raise ValueError(_ADVANCE_BY_VALUE_ERROR)
|
|
45
|
+
|
|
36
46
|
logger.debug(
|
|
37
47
|
"Factor initialized",
|
|
38
48
|
extra={
|
|
39
49
|
"identifier": identifier,
|
|
40
50
|
"step": step,
|
|
51
|
+
"advance_by": advance_by,
|
|
41
52
|
"amr": str(self.AMR),
|
|
42
53
|
},
|
|
43
54
|
)
|
|
44
55
|
self.identifier = identifier
|
|
45
56
|
self.step = step
|
|
57
|
+
self.advance_by = advance_by
|
|
46
58
|
|
|
47
59
|
@abc.abstractmethod
|
|
48
60
|
async def get_enrollment(self, identity_id: typing.Any) -> ENROLLMENT | None:
|
|
@@ -77,8 +77,9 @@ class EmailOTPFactor(FactorBase[EmailOTPEnrollment], abc.ABC):
|
|
|
77
77
|
lifetime: datetime.timedelta = datetime.timedelta(minutes=10),
|
|
78
78
|
identifier: str = "email_otp",
|
|
79
79
|
step: int = 0,
|
|
80
|
+
advance_by: int = 1,
|
|
80
81
|
) -> None:
|
|
81
|
-
super().__init__(identifier=identifier, step=step)
|
|
82
|
+
super().__init__(identifier=identifier, step=step, advance_by=advance_by)
|
|
82
83
|
self.hash_secret = hash_secret
|
|
83
84
|
self.code_length = code_length
|
|
84
85
|
self.lifetime = lifetime
|
|
@@ -97,8 +97,9 @@ class HOTPFactor(FactorBase[HOTPEnrollment], abc.ABC):
|
|
|
97
97
|
look_ahead: int = 5,
|
|
98
98
|
identifier: str = "hotp",
|
|
99
99
|
step: int = 1,
|
|
100
|
+
advance_by: int = 1,
|
|
100
101
|
) -> None:
|
|
101
|
-
super().__init__(identifier=identifier, step=step)
|
|
102
|
+
super().__init__(identifier=identifier, step=step, advance_by=advance_by)
|
|
102
103
|
self.code_length = code_length
|
|
103
104
|
self.algorithm: HOTPAlgorithm = algorithm
|
|
104
105
|
self.look_ahead = look_ahead
|
|
@@ -23,8 +23,6 @@ class AppleOAuth2Factor(OIDCFactorBase, abc.ABC):
|
|
|
23
23
|
- Apple: https://developer.apple.com/documentation/accountorganizationaldatasharing/creating-a-client-secret
|
|
24
24
|
"""
|
|
25
25
|
|
|
26
|
-
DISCOVERY_ENDPOINT = "https://appleid.apple.com/.well-known/openid-configuration"
|
|
27
|
-
|
|
28
26
|
def __init__(
|
|
29
27
|
self,
|
|
30
28
|
*,
|
|
@@ -35,11 +33,14 @@ class AppleOAuth2Factor(OIDCFactorBase, abc.ABC):
|
|
|
35
33
|
key_value: str,
|
|
36
34
|
state_service: OAuth2StateService,
|
|
37
35
|
step: int = 0,
|
|
36
|
+
advance_by: int = 1,
|
|
38
37
|
) -> None:
|
|
39
38
|
super().__init__(
|
|
40
39
|
identifier=identifier,
|
|
41
40
|
step=step,
|
|
41
|
+
advance_by=advance_by,
|
|
42
42
|
client_id=client_id,
|
|
43
|
+
discovery_endpoint="https://appleid.apple.com/.well-known/openid-configuration",
|
|
43
44
|
state_service=state_service,
|
|
44
45
|
)
|
|
45
46
|
self.team_id = team_id
|
|
@@ -220,8 +220,9 @@ class OAuth2Factor[EXTRA](FactorBase[OAuth2Enrollment], abc.ABC):
|
|
|
220
220
|
client_id: str,
|
|
221
221
|
state_service: OAuth2StateService,
|
|
222
222
|
step: int = 0,
|
|
223
|
+
advance_by: int = 1,
|
|
223
224
|
) -> None:
|
|
224
|
-
super().__init__(identifier=identifier, step=step)
|
|
225
|
+
super().__init__(identifier=identifier, step=step, advance_by=advance_by)
|
|
225
226
|
self.client_id = client_id
|
|
226
227
|
self.state_service = state_service
|
|
227
228
|
|
|
@@ -88,10 +88,12 @@ class GitHubOAuth2Factor(OAuth2Factor[GitHubOAuth2Extra], abc.ABC):
|
|
|
88
88
|
client_secret: str,
|
|
89
89
|
state_service: OAuth2StateService,
|
|
90
90
|
step: int = 0,
|
|
91
|
+
advance_by: int = 1,
|
|
91
92
|
) -> None:
|
|
92
93
|
super().__init__(
|
|
93
94
|
identifier=identifier,
|
|
94
95
|
step=step,
|
|
96
|
+
advance_by=advance_by,
|
|
95
97
|
client_id=client_id,
|
|
96
98
|
state_service=state_service,
|
|
97
99
|
)
|
|
@@ -12,8 +12,6 @@ class GoogleOAuth2Factor(OIDCFactor, abc.ABC):
|
|
|
12
12
|
- Google: https://developers.google.com/identity/openid-connect/openid-connect
|
|
13
13
|
"""
|
|
14
14
|
|
|
15
|
-
DISCOVERY_ENDPOINT = "https://accounts.google.com/.well-known/openid-configuration"
|
|
16
|
-
|
|
17
15
|
def __init__(
|
|
18
16
|
self,
|
|
19
17
|
*,
|
|
@@ -22,11 +20,14 @@ class GoogleOAuth2Factor(OIDCFactor, abc.ABC):
|
|
|
22
20
|
client_secret: str,
|
|
23
21
|
state_service: OAuth2StateService,
|
|
24
22
|
step: int = 0,
|
|
23
|
+
advance_by: int = 1,
|
|
25
24
|
) -> None:
|
|
26
25
|
super().__init__(
|
|
27
26
|
identifier=identifier,
|
|
28
27
|
step=step,
|
|
28
|
+
advance_by=advance_by,
|
|
29
29
|
client_id=client_id,
|
|
30
30
|
client_secret=client_secret,
|
|
31
|
+
discovery_endpoint="https://accounts.google.com/.well-known/openid-configuration",
|
|
31
32
|
state_service=state_service,
|
|
32
33
|
)
|
|
@@ -308,22 +308,24 @@ class OIDCFactorBase(OAuth2Factor[OIDCExtraParams], abc.ABC):
|
|
|
308
308
|
- OpenID Connect Core 1.0: https://openid.net/specs/openid-connect-core-1_0.html
|
|
309
309
|
"""
|
|
310
310
|
|
|
311
|
-
DISCOVERY_ENDPOINT: typing.ClassVar[str]
|
|
312
|
-
|
|
313
311
|
def __init__(
|
|
314
312
|
self,
|
|
315
313
|
*,
|
|
316
314
|
identifier: str,
|
|
317
315
|
client_id: str,
|
|
316
|
+
discovery_endpoint: str,
|
|
318
317
|
state_service: OAuth2StateService,
|
|
319
318
|
step: int = 0,
|
|
319
|
+
advance_by: int = 1,
|
|
320
320
|
) -> None:
|
|
321
321
|
super().__init__(
|
|
322
322
|
identifier=identifier,
|
|
323
323
|
step=step,
|
|
324
|
+
advance_by=advance_by,
|
|
324
325
|
client_id=client_id,
|
|
325
326
|
state_service=state_service,
|
|
326
327
|
)
|
|
328
|
+
self.discovery_endpoint = discovery_endpoint
|
|
327
329
|
self._discovery_document: dict[str, typing.Any] | None = None
|
|
328
330
|
self._jwks: jwt.PyJWKSet | None = None
|
|
329
331
|
self._client = httpx.AsyncClient()
|
|
@@ -561,7 +563,7 @@ class OIDCFactorBase(OAuth2Factor[OIDCExtraParams], abc.ABC):
|
|
|
561
563
|
|
|
562
564
|
client = self._get_client()
|
|
563
565
|
try:
|
|
564
|
-
response = await client.get(self.
|
|
566
|
+
response = await client.get(self.discovery_endpoint)
|
|
565
567
|
response.raise_for_status()
|
|
566
568
|
except httpx.HTTPError as e:
|
|
567
569
|
raise DiscoveryDocumentException() from e
|
|
@@ -608,14 +610,18 @@ class OIDCFactor(OIDCFactorBase):
|
|
|
608
610
|
identifier: str,
|
|
609
611
|
client_id: str,
|
|
610
612
|
client_secret: str,
|
|
613
|
+
discovery_endpoint: str,
|
|
611
614
|
state_service: OAuth2StateService,
|
|
612
615
|
step: int = 0,
|
|
616
|
+
advance_by: int = 1,
|
|
613
617
|
) -> None:
|
|
614
618
|
super().__init__(
|
|
615
619
|
identifier=identifier,
|
|
616
620
|
client_id=client_id,
|
|
621
|
+
discovery_endpoint=discovery_endpoint,
|
|
617
622
|
state_service=state_service,
|
|
618
623
|
step=step,
|
|
624
|
+
advance_by=advance_by,
|
|
619
625
|
)
|
|
620
626
|
self._client_secret = client_secret
|
|
621
627
|
|
|
@@ -657,15 +663,19 @@ class PrivateKeyJWTOIDCFactor(OIDCFactorBase):
|
|
|
657
663
|
client_id: str,
|
|
658
664
|
jwks: jwt.PyJWKSet,
|
|
659
665
|
kid: str,
|
|
666
|
+
discovery_endpoint: str,
|
|
660
667
|
state_service: OAuth2StateService,
|
|
661
668
|
assertion_lifetime: int = 60,
|
|
662
669
|
step: int = 0,
|
|
670
|
+
advance_by: int = 1,
|
|
663
671
|
) -> None:
|
|
664
672
|
super().__init__(
|
|
665
673
|
identifier=identifier,
|
|
666
674
|
client_id=client_id,
|
|
675
|
+
discovery_endpoint=discovery_endpoint,
|
|
667
676
|
state_service=state_service,
|
|
668
677
|
step=step,
|
|
678
|
+
advance_by=advance_by,
|
|
669
679
|
)
|
|
670
680
|
self._signing_jwks = jwks
|
|
671
681
|
self._kid = kid
|
|
@@ -109,8 +109,9 @@ class TOTPFactor(FactorBase[TOTPEnrollment], abc.ABC):
|
|
|
109
109
|
drift_tolerance: int = 1,
|
|
110
110
|
identifier: str = "totp",
|
|
111
111
|
step: int = 1,
|
|
112
|
+
advance_by: int = 1,
|
|
112
113
|
) -> None:
|
|
113
|
-
super().__init__(identifier=identifier, step=step)
|
|
114
|
+
super().__init__(identifier=identifier, step=step, advance_by=advance_by)
|
|
114
115
|
self.code_length = code_length
|
|
115
116
|
self.algorithm: TOTPAlgorithm = algorithm
|
|
116
117
|
self.time_step = time_step
|
|
@@ -136,8 +136,6 @@ def _create_token_response(
|
|
|
136
136
|
class SQLAlchemyOIDCFactor(OIDCFactor):
|
|
137
137
|
"""Concrete implementation of OIDCFactor using SQLAlchemy for testing."""
|
|
138
138
|
|
|
139
|
-
DISCOVERY_ENDPOINT = DISCOVERY_ENDPOINT
|
|
140
|
-
|
|
141
139
|
def __init__(
|
|
142
140
|
self,
|
|
143
141
|
connection: AsyncConnection,
|
|
@@ -149,6 +147,7 @@ class SQLAlchemyOIDCFactor(OIDCFactor):
|
|
|
149
147
|
identifier="oidc",
|
|
150
148
|
client_id="test-client-id",
|
|
151
149
|
client_secret="test-client-secret",
|
|
150
|
+
discovery_endpoint=DISCOVERY_ENDPOINT,
|
|
152
151
|
state_service=state_service,
|
|
153
152
|
)
|
|
154
153
|
|
|
@@ -654,8 +653,6 @@ class TestOIDCFactorExchangeCode:
|
|
|
654
653
|
|
|
655
654
|
|
|
656
655
|
class _PrivateKeyJWTFactor(PrivateKeyJWTOIDCFactor):
|
|
657
|
-
DISCOVERY_ENDPOINT = DISCOVERY_ENDPOINT
|
|
658
|
-
|
|
659
656
|
async def insert(self, enrollment: OAuth2Enrollment) -> int:
|
|
660
657
|
raise NotImplementedError()
|
|
661
658
|
|
|
@@ -686,6 +683,7 @@ class TestPrivateKeyJWTOIDCFactorGetRequestAuthentication:
|
|
|
686
683
|
client_id="test-client-id",
|
|
687
684
|
jwks=get_private_jwks_from_rsa_key(rsa_key, "sign-key-1"),
|
|
688
685
|
kid="sign-key-1",
|
|
686
|
+
discovery_endpoint=DISCOVERY_ENDPOINT,
|
|
689
687
|
state_service=oauth2_state_service,
|
|
690
688
|
)
|
|
691
689
|
|
|
@@ -719,6 +717,7 @@ class TestPrivateKeyJWTOIDCFactorGetRequestAuthentication:
|
|
|
719
717
|
client_id="test-client-id",
|
|
720
718
|
jwks=get_private_jwks_from_rsa_key(rsa_key, "sign-key-1"),
|
|
721
719
|
kid="unknown-key",
|
|
720
|
+
discovery_endpoint=DISCOVERY_ENDPOINT,
|
|
722
721
|
state_service=oauth2_state_service,
|
|
723
722
|
)
|
|
724
723
|
|
|
@@ -69,6 +69,20 @@ class DummyPasswordFactor(FactorBase[DummyPasswordFactorEnrollment]):
|
|
|
69
69
|
return DummyPasswordFactorEnrollment(id=1, identity_id=identity_id)
|
|
70
70
|
|
|
71
71
|
|
|
72
|
+
class DummySSOFactor(FactorBase[DummyPasswordFactorEnrollment]):
|
|
73
|
+
AMR = AuthenticationMethodReference.OAUTH2
|
|
74
|
+
|
|
75
|
+
def __init__(self) -> None:
|
|
76
|
+
super().__init__(identifier="dummy_sso", step=0, advance_by=2)
|
|
77
|
+
|
|
78
|
+
async def get_enrollment(
|
|
79
|
+
self, identity_id: int
|
|
80
|
+
) -> DummyPasswordFactorEnrollment | None:
|
|
81
|
+
if identity_id != 1:
|
|
82
|
+
return None
|
|
83
|
+
return DummyPasswordFactorEnrollment(id=1, identity_id=identity_id)
|
|
84
|
+
|
|
85
|
+
|
|
72
86
|
@dataclasses.dataclass
|
|
73
87
|
class DummyMFAFactorEnrollment:
|
|
74
88
|
id: int | None
|
|
@@ -363,6 +377,30 @@ class TestAdvance:
|
|
|
363
377
|
assert updated_session.id == session.id
|
|
364
378
|
assert updated_session.amr == [AuthenticationMethodReference.PWD]
|
|
365
379
|
assert updated_session.used_factors == ["dummy_password"]
|
|
380
|
+
assert updated_session.step == 1
|
|
381
|
+
|
|
382
|
+
async def test_factor_can_skip_next_step(
|
|
383
|
+
self,
|
|
384
|
+
sqlalchemy_connection: AsyncConnection,
|
|
385
|
+
mfa_factor: DummyMFAFactor,
|
|
386
|
+
) -> None:
|
|
387
|
+
trusted_factor = DummySSOFactor()
|
|
388
|
+
authentication_session_service = SQLAlchemyAuthenticationSession(
|
|
389
|
+
connection=sqlalchemy_connection,
|
|
390
|
+
hash_secret="test_secret",
|
|
391
|
+
factors={trusted_factor, mfa_factor},
|
|
392
|
+
)
|
|
393
|
+
_token, session = await authentication_session_service.start()
|
|
394
|
+
|
|
395
|
+
updated_session = await authentication_session_service.advance(
|
|
396
|
+
session, 1, trusted_factor
|
|
397
|
+
)
|
|
398
|
+
|
|
399
|
+
assert updated_session.step == 2
|
|
400
|
+
assert (
|
|
401
|
+
await authentication_session_service.get_available_factors(updated_session)
|
|
402
|
+
== set()
|
|
403
|
+
)
|
|
366
404
|
|
|
367
405
|
async def test_identity_one_factor_enrolled(
|
|
368
406
|
self,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|