simple-module-oidc 0.0.43__tar.gz → 0.0.44__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.
- {simple_module_oidc-0.0.43 → simple_module_oidc-0.0.44}/PKG-INFO +5 -5
- simple_module_oidc-0.0.44/oidc/core/__init__.py +4 -0
- {simple_module_oidc-0.0.43/oidc → simple_module_oidc-0.0.44/oidc/core}/runtime.py +4 -4
- {simple_module_oidc-0.0.43/oidc → simple_module_oidc-0.0.44/oidc/core}/settings.py +2 -2
- {simple_module_oidc-0.0.43/oidc → simple_module_oidc-0.0.44/oidc/core}/state.py +4 -4
- simple_module_oidc-0.0.44/oidc/identity/__init__.py +5 -0
- {simple_module_oidc-0.0.43/oidc → simple_module_oidc-0.0.44/oidc/identity}/provider.py +27 -6
- {simple_module_oidc-0.0.43/oidc → simple_module_oidc-0.0.44/oidc/identity}/renewal.py +3 -3
- {simple_module_oidc-0.0.43/oidc → simple_module_oidc-0.0.44/oidc/identity}/tokens.py +1 -1
- simple_module_oidc-0.0.44/oidc/jwks.py +11 -0
- {simple_module_oidc-0.0.43 → simple_module_oidc-0.0.44}/oidc/module.py +8 -8
- simple_module_oidc-0.0.44/oidc/provider.py +11 -0
- simple_module_oidc-0.0.44/oidc/settings.py +11 -0
- simple_module_oidc-0.0.44/oidc/web/__init__.py +1 -0
- {simple_module_oidc-0.0.43/oidc → simple_module_oidc-0.0.44/oidc/web}/endpoints/api.py +7 -8
- {simple_module_oidc-0.0.43/oidc → simple_module_oidc-0.0.44/oidc/web}/endpoints/views.py +9 -4
- {simple_module_oidc-0.0.43 → simple_module_oidc-0.0.44}/package.json +1 -1
- {simple_module_oidc-0.0.43 → simple_module_oidc-0.0.44}/pyproject.toml +5 -5
- {simple_module_oidc-0.0.43 → simple_module_oidc-0.0.44}/tests/conftest.py +1 -1
- {simple_module_oidc-0.0.43/tests → simple_module_oidc-0.0.44/tests/core/module}/test_oidc_boot.py +4 -4
- {simple_module_oidc-0.0.43/tests → simple_module_oidc-0.0.44/tests/core/module}/test_oidc_module.py +1 -1
- {simple_module_oidc-0.0.43/tests → simple_module_oidc-0.0.44/tests/core/runtime}/test_oidc_settings_runtime.py +4 -4
- {simple_module_oidc-0.0.43/tests → simple_module_oidc-0.0.44/tests/core/settings}/test_oidc_settings_presets.py +2 -2
- {simple_module_oidc-0.0.43/tests → simple_module_oidc-0.0.44/tests/identity/client}/test_oidc_client.py +1 -1
- {simple_module_oidc-0.0.43/tests → simple_module_oidc-0.0.44/tests/identity/discovery}/test_oidc_discovery.py +1 -1
- {simple_module_oidc-0.0.43/tests → simple_module_oidc-0.0.44/tests/identity/jwks}/test_oidc_jwks.py +1 -1
- {simple_module_oidc-0.0.43/tests → simple_module_oidc-0.0.44/tests/identity/jwks}/test_oidc_jwks_resilience.py +1 -1
- {simple_module_oidc-0.0.43/tests → simple_module_oidc-0.0.44/tests/identity/persistence}/test_oidc_cache_persistence.py +3 -3
- {simple_module_oidc-0.0.43/tests → simple_module_oidc-0.0.44/tests/identity/provider}/test_oidc_jwt_policy.py +1 -1
- {simple_module_oidc-0.0.43/tests → simple_module_oidc-0.0.44/tests/identity/provider}/test_oidc_legacy_authority.py +3 -3
- {simple_module_oidc-0.0.43/tests → simple_module_oidc-0.0.44/tests/identity/provider}/test_oidc_provider.py +2 -2
- simple_module_oidc-0.0.44/tests/identity/provider/test_oidc_sign_in_from_claims.py +39 -0
- {simple_module_oidc-0.0.43/tests → simple_module_oidc-0.0.44/tests/identity/provider}/test_oidc_tenant_resolver.py +2 -2
- {simple_module_oidc-0.0.43/tests → simple_module_oidc-0.0.44/tests/identity/renewal}/test_oidc_refresh.py +5 -5
- {simple_module_oidc-0.0.43/tests → simple_module_oidc-0.0.44/tests/identity/renewal}/test_oidc_session_transaction.py +4 -4
- {simple_module_oidc-0.0.43/tests → simple_module_oidc-0.0.44/tests/web/endpoints}/test_oidc_authorization.py +3 -3
- {simple_module_oidc-0.0.43/tests → simple_module_oidc-0.0.44/tests/web/endpoints}/test_oidc_authorization_query.py +2 -2
- {simple_module_oidc-0.0.43/tests → simple_module_oidc-0.0.44/tests/web/endpoints}/test_oidc_endpoint_auth.py +8 -8
- {simple_module_oidc-0.0.43/tests → simple_module_oidc-0.0.44/tests/web/endpoints}/test_oidc_logout.py +1 -1
- {simple_module_oidc-0.0.43 → simple_module_oidc-0.0.44}/.gitignore +0 -0
- {simple_module_oidc-0.0.43 → simple_module_oidc-0.0.44}/README.md +0 -0
- {simple_module_oidc-0.0.43 → simple_module_oidc-0.0.44}/oidc/__init__.py +0 -0
- {simple_module_oidc-0.0.43 → simple_module_oidc-0.0.44}/oidc/contracts/__init__.py +0 -0
- {simple_module_oidc-0.0.43/oidc → simple_module_oidc-0.0.44/oidc/core}/constants.py +0 -0
- {simple_module_oidc-0.0.43/oidc → simple_module_oidc-0.0.44/oidc/core}/presets.py +0 -0
- {simple_module_oidc-0.0.43/oidc → simple_module_oidc-0.0.44/oidc/identity}/client.py +0 -0
- {simple_module_oidc-0.0.43/oidc → simple_module_oidc-0.0.44/oidc/identity}/directory.py +0 -0
- {simple_module_oidc-0.0.43/oidc → simple_module_oidc-0.0.44/oidc/identity}/discovery.py +0 -0
- {simple_module_oidc-0.0.43/oidc → simple_module_oidc-0.0.44/oidc/identity}/jwks.py +0 -0
- {simple_module_oidc-0.0.43/oidc → simple_module_oidc-0.0.44/oidc/identity}/persistence.py +0 -0
- {simple_module_oidc-0.0.43 → simple_module_oidc-0.0.44}/oidc/locales/de.json +0 -0
- {simple_module_oidc-0.0.43 → simple_module_oidc-0.0.44}/oidc/locales/en.json +0 -0
- {simple_module_oidc-0.0.43 → simple_module_oidc-0.0.44}/oidc/models.py +0 -0
- {simple_module_oidc-0.0.43 → simple_module_oidc-0.0.44}/oidc/pages/LoggedOut.tsx +0 -0
- {simple_module_oidc-0.0.43 → simple_module_oidc-0.0.44}/oidc/pages/Login.tsx +0 -0
- {simple_module_oidc-0.0.43 → simple_module_oidc-0.0.44}/oidc/py.typed +0 -0
- {simple_module_oidc-0.0.43/oidc → simple_module_oidc-0.0.44/oidc/web}/endpoints/__init__.py +0 -0
- {simple_module_oidc-0.0.43/tests → simple_module_oidc-0.0.44/tests/core/module}/test_oidc_migration.py +0 -0
- {simple_module_oidc-0.0.43/tests → simple_module_oidc-0.0.44/tests/identity/directory}/test_oidc_directory.py +0 -0
- {simple_module_oidc-0.0.43/tests → simple_module_oidc-0.0.44/tests/identity/provider}/test_oidc_bearer_policy.py +0 -0
- {simple_module_oidc-0.0.43 → simple_module_oidc-0.0.44}/tests/oidc_flow_helpers.py +0 -0
- {simple_module_oidc-0.0.43/tests → simple_module_oidc-0.0.44/tests/web/endpoints}/test_oidc_flow.py +0 -0
- {simple_module_oidc-0.0.43/tests → simple_module_oidc-0.0.44/tests/web/endpoints}/test_oidc_keycloak_flow.py +0 -0
- {simple_module_oidc-0.0.43 → simple_module_oidc-0.0.44}/tests-js/oidc-login.test.tsx +0 -0
- {simple_module_oidc-0.0.43 → simple_module_oidc-0.0.44}/tests-js/oidc-translations.test.tsx +0 -0
- {simple_module_oidc-0.0.43 → simple_module_oidc-0.0.44}/tsconfig.json +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: simple_module_oidc
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.44
|
|
4
4
|
Summary: Generic OIDC authentication provider for simple_module — native Microsoft Entra ID, Auth0, Okta, Keycloak, and any OpenID Connect identity provider
|
|
5
5
|
Project-URL: Homepage, https://github.com/antosubash/simple_module_python
|
|
6
6
|
Project-URL: Repository, https://github.com/antosubash/simple_module_python
|
|
@@ -21,10 +21,10 @@ Requires-Python: >=3.12
|
|
|
21
21
|
Requires-Dist: cryptography>=42
|
|
22
22
|
Requires-Dist: httpx<0.29,>=0.27
|
|
23
23
|
Requires-Dist: pyjwt[crypto]<3,>=2.8
|
|
24
|
-
Requires-Dist: simple-module-auth==0.0.
|
|
25
|
-
Requires-Dist: simple-module-core==0.0.
|
|
26
|
-
Requires-Dist: simple-module-db==0.0.
|
|
27
|
-
Requires-Dist: simple-module-hosting==0.0.
|
|
24
|
+
Requires-Dist: simple-module-auth==0.0.44
|
|
25
|
+
Requires-Dist: simple-module-core==0.0.44
|
|
26
|
+
Requires-Dist: simple-module-db==0.0.44
|
|
27
|
+
Requires-Dist: simple-module-hosting==0.0.44
|
|
28
28
|
Description-Content-Type: text/markdown
|
|
29
29
|
|
|
30
30
|
# simple_module_oidc
|
|
@@ -4,10 +4,10 @@ import logging
|
|
|
4
4
|
|
|
5
5
|
from fastapi import HTTPException, Request
|
|
6
6
|
|
|
7
|
-
from oidc.
|
|
8
|
-
from oidc.
|
|
9
|
-
from oidc.discovery import fetch_metadata
|
|
10
|
-
from oidc.jwks import JWKSCache
|
|
7
|
+
from oidc.core.constants import SESSION_USER_KEY
|
|
8
|
+
from oidc.identity.client import OIDCClient
|
|
9
|
+
from oidc.identity.discovery import fetch_metadata
|
|
10
|
+
from oidc.identity.jwks import JWKSCache
|
|
11
11
|
|
|
12
12
|
logger = logging.getLogger(__name__)
|
|
13
13
|
_DISCOVERY_SUFFIX = "/.well-known/openid-configuration"
|
|
@@ -17,8 +17,8 @@ from simple_module_core.redirect_safety import non_empty_redirect
|
|
|
17
17
|
from simple_module_core.settings_base import DbBackedSettings
|
|
18
18
|
from simple_module_core.stored_settings import field_error
|
|
19
19
|
|
|
20
|
-
from oidc.constants import DEFAULT_LOGIN_REDIRECT_URL
|
|
21
|
-
from oidc.presets import get_preset
|
|
20
|
+
from oidc.core.constants import DEFAULT_LOGIN_REDIRECT_URL
|
|
21
|
+
from oidc.core.presets import get_preset
|
|
22
22
|
|
|
23
23
|
# Sentinel for "unset by the user" so the preset can supply a default while still
|
|
24
24
|
# letting an explicit empty/other value win.
|
|
@@ -7,10 +7,10 @@ from dataclasses import dataclass, field
|
|
|
7
7
|
from typing import TYPE_CHECKING
|
|
8
8
|
|
|
9
9
|
if TYPE_CHECKING:
|
|
10
|
-
from oidc.
|
|
11
|
-
from oidc.
|
|
12
|
-
from oidc.
|
|
13
|
-
from oidc.
|
|
10
|
+
from oidc.core.settings import OidcSettings
|
|
11
|
+
from oidc.identity.client import OIDCClient
|
|
12
|
+
from oidc.identity.discovery import OidcMetadata
|
|
13
|
+
from oidc.identity.jwks import JWKSCache
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
@dataclass
|
|
@@ -12,18 +12,22 @@ from auth.oidc_common.tenant import trusted_tenant, without_tenant_roles
|
|
|
12
12
|
from simple_module_hosting.session import SESSION_EXPIRES_AT_KEY, session_has_expired
|
|
13
13
|
from starlette.requests import Request
|
|
14
14
|
|
|
15
|
-
from oidc.constants import LOGIN_PATH, LOGOUT_PATH, SESSION_USER_KEY
|
|
16
|
-
from oidc.persistence import CachePersistence
|
|
17
|
-
from oidc.renewal import SessionRenewal
|
|
15
|
+
from oidc.core.constants import LOGIN_PATH, LOGOUT_PATH, SESSION_USER_KEY
|
|
16
|
+
from oidc.identity.persistence import CachePersistence
|
|
17
|
+
from oidc.identity.renewal import SessionRenewal
|
|
18
18
|
|
|
19
19
|
if TYPE_CHECKING:
|
|
20
|
-
from oidc.
|
|
21
|
-
from oidc.
|
|
22
|
-
from oidc.
|
|
20
|
+
from oidc.core.settings import OidcSettings
|
|
21
|
+
from oidc.core.state import OidcState
|
|
22
|
+
from oidc.identity.jwks import JWKSCache
|
|
23
23
|
|
|
24
24
|
logger = logging.getLogger(__name__)
|
|
25
25
|
|
|
26
26
|
|
|
27
|
+
class IdentityUnavailableError(Exception):
|
|
28
|
+
"""The user cache could not record the signed-in identity."""
|
|
29
|
+
|
|
30
|
+
|
|
27
31
|
class OidcAuthProvider(CachePersistence, SessionRenewal):
|
|
28
32
|
"""Generic OIDC authentication with issuer-scoped identities."""
|
|
29
33
|
|
|
@@ -113,6 +117,23 @@ class OidcAuthProvider(CachePersistence, SessionRenewal):
|
|
|
113
117
|
logger.info("OIDC bearer token from a client not in api_allowed_client_ids")
|
|
114
118
|
return False
|
|
115
119
|
|
|
120
|
+
async def sign_in_from_claims(
|
|
121
|
+
self, request: Request, claims: dict[str, Any]
|
|
122
|
+
) -> tuple[UserContext, tuple[str, str]]:
|
|
123
|
+
"""Turn verified ID-token claims into a browser sign-in.
|
|
124
|
+
|
|
125
|
+
Returns the session ``UserContext`` and the ``(issuer, subject)``
|
|
126
|
+
identity to pin the session to. The caller has already validated the
|
|
127
|
+
token, nonce and expiry. Raises ``ValueError`` when the claims carry no
|
|
128
|
+
usable identity and ``IdentityUnavailableError`` when the user cache
|
|
129
|
+
refuses it.
|
|
130
|
+
"""
|
|
131
|
+
identity = self._identity(claims)
|
|
132
|
+
cache_id = await self._upsert_user_cache(request, claims)
|
|
133
|
+
if cache_id is None:
|
|
134
|
+
raise IdentityUnavailableError("OIDC identity unavailable")
|
|
135
|
+
return self._claims_to_user_context(claims, cache_id=cache_id), identity
|
|
136
|
+
|
|
116
137
|
def _claims_to_user_context(
|
|
117
138
|
self,
|
|
118
139
|
claims: dict[str, Any],
|
|
@@ -3,14 +3,14 @@
|
|
|
3
3
|
from auth.oidc_common.renewal import refresh_session
|
|
4
4
|
from auth.oidc_common.session import SessionKeys, accept_authority, authority_marker
|
|
5
5
|
|
|
6
|
-
from oidc.constants import (
|
|
6
|
+
from oidc.core.constants import (
|
|
7
7
|
SESSION_IDENTITY_KEY,
|
|
8
8
|
SESSION_REFRESH_RETRY_AT_KEY,
|
|
9
9
|
SESSION_REFRESH_TOKEN_KEY,
|
|
10
10
|
SESSION_USER_KEY,
|
|
11
11
|
)
|
|
12
|
-
from oidc.runtime import require_client
|
|
13
|
-
from oidc.tokens import open_refresh_token, store_refresh_token
|
|
12
|
+
from oidc.core.runtime import require_client
|
|
13
|
+
from oidc.identity.tokens import open_refresh_token, store_refresh_token
|
|
14
14
|
|
|
15
15
|
_KEYS = SessionKeys(
|
|
16
16
|
SESSION_USER_KEY,
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from auth.oidc_common.tokens import provider_tokens
|
|
4
4
|
|
|
5
|
-
from oidc.constants import SESSION_REFRESH_TOKEN_KEY
|
|
5
|
+
from oidc.core.constants import SESSION_REFRESH_TOKEN_KEY
|
|
6
6
|
|
|
7
7
|
_TOKENS = provider_tokens(info=b"oidc-refresh-token", session_key=SESSION_REFRESH_TOKEN_KEY)
|
|
8
8
|
seal_refresh_token = _TOKENS.seal_refresh_token
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""Moved to :mod:`oidc.identity.jwks`; this alias goes in 0.0.46 (module layout)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import importlib
|
|
6
|
+
import sys
|
|
7
|
+
|
|
8
|
+
from simple_module_core.deprecation import deprecated_module
|
|
9
|
+
|
|
10
|
+
deprecated_module("oidc.jwks", remove_in="0.0.46", use="oidc.identity.jwks")
|
|
11
|
+
sys.modules[__name__] = importlib.import_module("oidc.identity.jwks")
|
|
@@ -11,7 +11,7 @@ from simple_module_core.module import ModuleBase, ModuleMeta
|
|
|
11
11
|
from simple_module_core.module_settings import register_module_settings
|
|
12
12
|
from simple_module_core.user_directory import set_user_directory
|
|
13
13
|
|
|
14
|
-
from oidc.constants import (
|
|
14
|
+
from oidc.core.constants import (
|
|
15
15
|
_MODULE_AUTH,
|
|
16
16
|
_MODULE_SETTINGS,
|
|
17
17
|
AUTH_CALLBACK_PATH,
|
|
@@ -36,10 +36,10 @@ class OidcModule(ModuleBase):
|
|
|
36
36
|
_is_auth_provider = True
|
|
37
37
|
|
|
38
38
|
def register_settings(self, app: FastAPI) -> None:
|
|
39
|
-
from oidc.
|
|
40
|
-
from oidc.
|
|
41
|
-
from oidc.
|
|
42
|
-
from oidc.
|
|
39
|
+
from oidc.core.settings import OidcSettings
|
|
40
|
+
from oidc.core.state import OidcState
|
|
41
|
+
from oidc.identity.directory import OidcDirectory
|
|
42
|
+
from oidc.identity.provider import OidcAuthProvider
|
|
43
43
|
|
|
44
44
|
register_module_settings(
|
|
45
45
|
app,
|
|
@@ -67,8 +67,8 @@ class OidcModule(ModuleBase):
|
|
|
67
67
|
)
|
|
68
68
|
|
|
69
69
|
def register_routes(self, api_router: APIRouter, view_router: APIRouter) -> None:
|
|
70
|
-
from oidc.endpoints.api import router as api
|
|
71
|
-
from oidc.endpoints.views import router as views
|
|
70
|
+
from oidc.web.endpoints.api import router as api
|
|
71
|
+
from oidc.web.endpoints.views import router as views
|
|
72
72
|
|
|
73
73
|
api_router.include_router(api)
|
|
74
74
|
view_router.include_router(views)
|
|
@@ -79,7 +79,7 @@ class OidcModule(ModuleBase):
|
|
|
79
79
|
registry.add_exact(LOGOUT_PATH, methods={"POST"})
|
|
80
80
|
|
|
81
81
|
async def on_startup(self, app: FastAPI) -> None:
|
|
82
|
-
from oidc.runtime import initialize
|
|
82
|
+
from oidc.core.runtime import initialize
|
|
83
83
|
|
|
84
84
|
await initialize(app)
|
|
85
85
|
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""Moved to :mod:`oidc.identity.provider`; this alias goes in 0.0.46 (module layout)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import importlib
|
|
6
|
+
import sys
|
|
7
|
+
|
|
8
|
+
from simple_module_core.deprecation import deprecated_module
|
|
9
|
+
|
|
10
|
+
deprecated_module("oidc.provider", remove_in="0.0.46", use="oidc.identity.provider")
|
|
11
|
+
sys.modules[__name__] = importlib.import_module("oidc.identity.provider")
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""Moved to :mod:`oidc.core.settings`; this alias goes in 0.0.46 (module layout)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import importlib
|
|
6
|
+
import sys
|
|
7
|
+
|
|
8
|
+
from simple_module_core.deprecation import deprecated_module
|
|
9
|
+
|
|
10
|
+
deprecated_module("oidc.settings", remove_in="0.0.46", use="oidc.core.settings")
|
|
11
|
+
sys.modules[__name__] = importlib.import_module("oidc.core.settings")
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""The HTTP side: the REST and Inertia endpoints."""
|
|
@@ -10,15 +10,16 @@ from auth.oidc_common.session import sign_in, stale_callback
|
|
|
10
10
|
from fastapi import APIRouter, HTTPException, Request
|
|
11
11
|
from starlette.responses import RedirectResponse
|
|
12
12
|
|
|
13
|
-
from oidc.constants import (
|
|
13
|
+
from oidc.core.constants import (
|
|
14
14
|
SESSION_IDENTITY_KEY,
|
|
15
15
|
SESSION_NONCE_KEY,
|
|
16
16
|
SESSION_STATE_KEY,
|
|
17
17
|
SESSION_USER_KEY,
|
|
18
18
|
SESSION_VERIFIER_KEY,
|
|
19
19
|
)
|
|
20
|
-
from oidc.runtime import require_client
|
|
21
|
-
from oidc.
|
|
20
|
+
from oidc.core.runtime import require_client
|
|
21
|
+
from oidc.identity.provider import IdentityUnavailableError
|
|
22
|
+
from oidc.identity.tokens import store_refresh_token
|
|
22
23
|
|
|
23
24
|
logger = logging.getLogger(__name__)
|
|
24
25
|
router = APIRouter(prefix="/auth", tags=["oidc-auth"])
|
|
@@ -97,13 +98,11 @@ async def oidc_callback(request: Request):
|
|
|
97
98
|
raise HTTPException(status_code=401, detail="Invalid ID token expiry")
|
|
98
99
|
provider = request.app.state.auth.auth_provider
|
|
99
100
|
try:
|
|
100
|
-
identity = provider.
|
|
101
|
+
user_ctx, identity = await provider.sign_in_from_claims(request, claims)
|
|
101
102
|
except ValueError:
|
|
102
103
|
raise HTTPException(status_code=401, detail="Missing OIDC identity") from None
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
raise HTTPException(status_code=503, detail="OIDC identity unavailable")
|
|
106
|
-
user_ctx = provider._claims_to_user_context(claims, cache_id=cache_id)
|
|
104
|
+
except IdentityUnavailableError:
|
|
105
|
+
raise HTTPException(status_code=503, detail="OIDC identity unavailable") from None
|
|
107
106
|
next_url = sign_in(
|
|
108
107
|
request,
|
|
109
108
|
user=user_ctx,
|
|
@@ -8,10 +8,15 @@ from simple_module_hosting.inertia_deps import InertiaDep, is_inertia_request
|
|
|
8
8
|
from simple_module_inertia.response import location
|
|
9
9
|
from starlette.responses import RedirectResponse
|
|
10
10
|
|
|
11
|
-
from oidc.constants import
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
from oidc.core.constants import (
|
|
12
|
+
_PAGE_LOGGED_OUT,
|
|
13
|
+
_PAGE_LOGIN,
|
|
14
|
+
LOGGED_OUT_PATH,
|
|
15
|
+
SESSION_REFRESH_TOKEN_KEY,
|
|
16
|
+
)
|
|
17
|
+
from oidc.core.runtime import is_signed_in
|
|
18
|
+
from oidc.identity.renewal import _KEYS
|
|
19
|
+
from oidc.identity.tokens import open_refresh_token
|
|
15
20
|
|
|
16
21
|
logger = logging.getLogger(__name__)
|
|
17
22
|
router = APIRouter(tags=["oidc-views"])
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "simple_module_oidc"
|
|
3
|
-
version = "0.0.
|
|
3
|
+
version = "0.0.44"
|
|
4
4
|
description = "Generic OIDC authentication provider for simple_module — native Microsoft Entra ID, Auth0, Okta, Keycloak, and any OpenID Connect identity provider"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = "MIT"
|
|
@@ -20,10 +20,10 @@ classifiers = [
|
|
|
20
20
|
"Typing :: Typed",
|
|
21
21
|
]
|
|
22
22
|
dependencies = [
|
|
23
|
-
"simple_module_core==0.0.
|
|
24
|
-
"simple_module_db==0.0.
|
|
25
|
-
"simple_module_hosting==0.0.
|
|
26
|
-
"simple_module_auth==0.0.
|
|
23
|
+
"simple_module_core==0.0.44",
|
|
24
|
+
"simple_module_db==0.0.44",
|
|
25
|
+
"simple_module_hosting==0.0.44",
|
|
26
|
+
"simple_module_auth==0.0.44",
|
|
27
27
|
"PyJWT[crypto]>=2.8,<3",
|
|
28
28
|
"cryptography>=42",
|
|
29
29
|
"httpx>=0.27,<0.29",
|
|
@@ -6,7 +6,7 @@ import httpx
|
|
|
6
6
|
import pytest
|
|
7
7
|
from cryptography.hazmat.primitives.asymmetric import rsa
|
|
8
8
|
from jwt.algorithms import RSAAlgorithm
|
|
9
|
-
from oidc.settings import OidcSettings
|
|
9
|
+
from oidc.core.settings import OidcSettings
|
|
10
10
|
from oidc_flow_helpers import _AUDIENCE, _KID, _PRIVATE_KEY
|
|
11
11
|
from simple_module_core.discovery import discover_modules
|
|
12
12
|
from simple_module_db.base import all_module_bases
|
{simple_module_oidc-0.0.43/tests → simple_module_oidc-0.0.44/tests/core/module}/test_oidc_boot.py
RENAMED
|
@@ -5,11 +5,11 @@ from types import SimpleNamespace
|
|
|
5
5
|
|
|
6
6
|
import httpx
|
|
7
7
|
import pytest
|
|
8
|
+
from oidc.core.runtime import require_client
|
|
9
|
+
from oidc.core.settings import OidcSettings
|
|
10
|
+
from oidc.core.state import OidcState
|
|
11
|
+
from oidc.identity.provider import OidcAuthProvider
|
|
8
12
|
from oidc.module import OidcModule
|
|
9
|
-
from oidc.provider import OidcAuthProvider
|
|
10
|
-
from oidc.runtime import require_client
|
|
11
|
-
from oidc.settings import OidcSettings
|
|
12
|
-
from oidc.state import OidcState
|
|
13
13
|
from starlette.requests import Request
|
|
14
14
|
|
|
15
15
|
|
{simple_module_oidc-0.0.43/tests → simple_module_oidc-0.0.44/tests/core/module}/test_oidc_module.py
RENAMED
|
@@ -16,7 +16,7 @@ def test_oidc_module_meta():
|
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
def test_oidc_provider_satisfies_protocol():
|
|
19
|
-
from oidc.provider import OidcAuthProvider
|
|
19
|
+
from oidc.identity.provider import OidcAuthProvider
|
|
20
20
|
|
|
21
21
|
provider = OidcAuthProvider()
|
|
22
22
|
assert isinstance(provider, AuthProvider)
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
"""OIDC policy uses hydrated settings even without the Settings UI (#192)."""
|
|
2
2
|
|
|
3
|
-
from oidc.settings import OidcSettings
|
|
4
|
-
from settings.invalidation import reload_package
|
|
5
|
-
from settings.service import SettingService
|
|
6
|
-
from settings.store import SettingsStore
|
|
3
|
+
from oidc.core.settings import OidcSettings
|
|
4
|
+
from settings.core.invalidation import reload_package
|
|
5
|
+
from settings.core.service import SettingService
|
|
6
|
+
from settings.module_settings.store import SettingsStore
|
|
7
7
|
from simple_module_core.module_settings import module_settings_registry
|
|
8
8
|
|
|
9
9
|
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import pytest
|
|
6
|
-
from oidc.settings import OidcSettings
|
|
6
|
+
from oidc.core.settings import OidcSettings
|
|
7
7
|
from pydantic import ValidationError
|
|
8
8
|
|
|
9
9
|
|
|
@@ -111,7 +111,7 @@ def test_roles_claim_path_is_seeded_from_env_for_the_first_admin():
|
|
|
111
111
|
import subprocess
|
|
112
112
|
import sys
|
|
113
113
|
|
|
114
|
-
code = "from oidc.settings import OidcSettings; print(OidcSettings().roles_claim_path)"
|
|
114
|
+
code = "from oidc.core.settings import OidcSettings; print(OidcSettings().roles_claim_path)"
|
|
115
115
|
env = {**os.environ, "SM_OIDC_ROLES_CLAIM_PATH": "realm_access.roles"}
|
|
116
116
|
out = subprocess.run(
|
|
117
117
|
[sys.executable, "-c", code], env=env, capture_output=True, text=True, check=True
|
|
@@ -4,9 +4,9 @@ from datetime import UTC, datetime, timedelta
|
|
|
4
4
|
from types import SimpleNamespace
|
|
5
5
|
|
|
6
6
|
import pytest
|
|
7
|
+
from oidc.core.settings import OidcSettings
|
|
8
|
+
from oidc.identity.provider import OidcAuthProvider
|
|
7
9
|
from oidc.models import OidcUserCache
|
|
8
|
-
from oidc.provider import OidcAuthProvider
|
|
9
|
-
from oidc.settings import OidcSettings
|
|
10
10
|
from simple_module_db import independent_session
|
|
11
11
|
from sqlalchemy import event, select
|
|
12
12
|
from starlette.requests import Request
|
|
@@ -145,7 +145,7 @@ async def test_failed_cache_never_falls_back_to_external_subject(cache_request,
|
|
|
145
145
|
def broken(*args):
|
|
146
146
|
raise RuntimeError("database unavailable")
|
|
147
147
|
|
|
148
|
-
monkeypatch.setattr("oidc.persistence.independent_session", broken)
|
|
148
|
+
monkeypatch.setattr("oidc.identity.persistence.independent_session", broken)
|
|
149
149
|
assert await OidcAuthProvider()._upsert_user_cache(cache_request, CLAIMS) is None
|
|
150
150
|
|
|
151
151
|
|
|
@@ -4,9 +4,9 @@ import time
|
|
|
4
4
|
|
|
5
5
|
import pytest
|
|
6
6
|
from auth.contracts.schemas import UserContext
|
|
7
|
-
from oidc.
|
|
8
|
-
from oidc.
|
|
9
|
-
from oidc.
|
|
7
|
+
from oidc.core.settings import OidcSettings
|
|
8
|
+
from oidc.identity.jwks import JWKSCache
|
|
9
|
+
from oidc.identity.provider import OidcAuthProvider
|
|
10
10
|
from starlette.requests import Request
|
|
11
11
|
|
|
12
12
|
|
|
@@ -7,8 +7,8 @@ from unittest.mock import MagicMock
|
|
|
7
7
|
import pytest
|
|
8
8
|
from auth.contracts.provider import AuthProvider
|
|
9
9
|
from auth.contracts.schemas import UserContext
|
|
10
|
-
from oidc.
|
|
11
|
-
from oidc.
|
|
10
|
+
from oidc.core.settings import OidcSettings
|
|
11
|
+
from oidc.identity.provider import OidcAuthProvider
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
@pytest.fixture
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""``sign_in_from_claims`` is the public seam the login callback uses (#848)."""
|
|
2
|
+
|
|
3
|
+
from unittest.mock import AsyncMock
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
from oidc.core.settings import OidcSettings
|
|
7
|
+
from oidc.identity.provider import IdentityUnavailableError, OidcAuthProvider
|
|
8
|
+
from starlette.requests import Request
|
|
9
|
+
|
|
10
|
+
CLAIMS = {"iss": "https://idp.test", "sub": "subject", "email": "a@b.test"}
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@pytest.fixture
|
|
14
|
+
def provider(monkeypatch):
|
|
15
|
+
provider = OidcAuthProvider(OidcSettings(client_id="app"))
|
|
16
|
+
monkeypatch.setattr(provider, "_upsert_user_cache", AsyncMock(return_value="cache-1"))
|
|
17
|
+
return provider
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
async def test_returns_context_and_identity(provider):
|
|
21
|
+
request = Request({"type": "http"})
|
|
22
|
+
ctx, identity = await provider.sign_in_from_claims(request, CLAIMS)
|
|
23
|
+
assert identity == ("https://idp.test", "subject")
|
|
24
|
+
assert (
|
|
25
|
+
ctx.to_session_dict()
|
|
26
|
+
== provider._claims_to_user_context(CLAIMS, cache_id="cache-1").to_session_dict()
|
|
27
|
+
)
|
|
28
|
+
provider._upsert_user_cache.assert_awaited_once_with(request, CLAIMS)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
async def test_missing_identity_raises_value_error(provider):
|
|
32
|
+
with pytest.raises(ValueError):
|
|
33
|
+
await provider.sign_in_from_claims(Request({"type": "http"}), {"email": "a@b.test"})
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
async def test_cache_refusal_raises(provider):
|
|
37
|
+
provider._upsert_user_cache.return_value = None
|
|
38
|
+
with pytest.raises(IdentityUnavailableError):
|
|
39
|
+
await provider.sign_in_from_claims(Request({"type": "http"}), CLAIMS)
|
|
@@ -6,8 +6,8 @@ Lives apart from ``test_oidc_provider.py`` because that file overrides the
|
|
|
6
6
|
|
|
7
7
|
from __future__ import annotations
|
|
8
8
|
|
|
9
|
-
from oidc.
|
|
10
|
-
from oidc.
|
|
9
|
+
from oidc.core.settings import OidcSettings
|
|
10
|
+
from oidc.identity.provider import OidcAuthProvider
|
|
11
11
|
from starlette.requests import Request
|
|
12
12
|
|
|
13
13
|
_USER_ID = "11111111-1111-1111-1111-111111111111"
|
|
@@ -7,10 +7,10 @@ from urllib.parse import parse_qs
|
|
|
7
7
|
import httpx
|
|
8
8
|
import pytest
|
|
9
9
|
from auth.contracts.schemas import UserContext
|
|
10
|
-
from oidc.
|
|
11
|
-
from oidc.
|
|
12
|
-
from oidc.
|
|
13
|
-
from oidc.tokens import open_refresh_token, seal_refresh_token
|
|
10
|
+
from oidc.core.settings import OidcSettings
|
|
11
|
+
from oidc.identity.client import OIDCClient
|
|
12
|
+
from oidc.identity.provider import OidcAuthProvider
|
|
13
|
+
from oidc.identity.tokens import open_refresh_token, seal_refresh_token
|
|
14
14
|
from starlette.requests import Request
|
|
15
15
|
|
|
16
16
|
|
|
@@ -203,7 +203,7 @@ async def test_session_deadline_never_slides(refresh_session, monkeypatch):
|
|
|
203
203
|
|
|
204
204
|
|
|
205
205
|
async def test_refresh_sealing_is_provider_scoped(refresh_session):
|
|
206
|
-
from keycloak.tokens import open_refresh_token as open_keycloak_token
|
|
206
|
+
from keycloak.identity.tokens import open_refresh_token as open_keycloak_token
|
|
207
207
|
|
|
208
208
|
_, request = refresh_session
|
|
209
209
|
sealed = request.session["oidc_refresh_token"]
|
|
@@ -8,10 +8,10 @@ from unittest.mock import AsyncMock
|
|
|
8
8
|
import httpx
|
|
9
9
|
import pytest
|
|
10
10
|
from fastapi import HTTPException
|
|
11
|
-
from oidc.
|
|
12
|
-
from oidc.provider import OidcAuthProvider
|
|
13
|
-
from oidc.
|
|
14
|
-
from oidc.
|
|
11
|
+
from oidc.core.settings import OidcSettings
|
|
12
|
+
from oidc.identity.provider import OidcAuthProvider
|
|
13
|
+
from oidc.identity.tokens import open_refresh_token
|
|
14
|
+
from oidc.web.endpoints import api
|
|
15
15
|
from simple_module_hosting.csrf import get_csrf_token
|
|
16
16
|
from simple_module_hosting.session import SessionMiddleware, preserve_across_sign_in
|
|
17
17
|
from starlette.requests import Request
|
|
@@ -5,9 +5,9 @@ from unittest.mock import AsyncMock
|
|
|
5
5
|
|
|
6
6
|
import pytest
|
|
7
7
|
from auth.contracts.schemas import UserContext
|
|
8
|
-
from oidc.
|
|
9
|
-
from oidc.
|
|
10
|
-
from oidc.
|
|
8
|
+
from oidc.core.settings import OidcSettings
|
|
9
|
+
from oidc.core.state import OidcState
|
|
10
|
+
from oidc.identity.provider import OidcAuthProvider
|
|
11
11
|
from starlette.requests import Request
|
|
12
12
|
|
|
13
13
|
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
from urllib.parse import parse_qs, urlsplit
|
|
4
4
|
|
|
5
5
|
import pytest
|
|
6
|
-
from oidc.client import OIDCClient
|
|
7
|
-
from oidc.discovery import OidcMetadata
|
|
6
|
+
from oidc.identity.client import OIDCClient
|
|
7
|
+
from oidc.identity.discovery import OidcMetadata
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
@pytest.mark.parametrize("query", ["", "?tenant=alpha", "?tenant=alpha&policy=a%2Bb"])
|
|
@@ -6,11 +6,11 @@ from urllib.parse import parse_qs
|
|
|
6
6
|
|
|
7
7
|
import httpx
|
|
8
8
|
import pytest
|
|
9
|
-
from oidc.constants import SESSION_REFRESH_TOKEN_KEY
|
|
10
|
-
from oidc.runtime import initialize
|
|
11
|
-
from oidc.settings import OidcSettings
|
|
12
|
-
from oidc.state import OidcState
|
|
13
|
-
from oidc.tokens import seal_refresh_token
|
|
9
|
+
from oidc.core.constants import SESSION_REFRESH_TOKEN_KEY
|
|
10
|
+
from oidc.core.runtime import initialize
|
|
11
|
+
from oidc.core.settings import OidcSettings
|
|
12
|
+
from oidc.core.state import OidcState
|
|
13
|
+
from oidc.identity.tokens import seal_refresh_token
|
|
14
14
|
from starlette.requests import Request
|
|
15
15
|
|
|
16
16
|
_DOC = {
|
|
@@ -131,7 +131,7 @@ def _logout_request(app, session):
|
|
|
131
131
|
async def test_logout_revokes_before_clearing_even_on_failure(
|
|
132
132
|
endpoint_app, basic, failure, httpx_mock, caplog
|
|
133
133
|
):
|
|
134
|
-
from oidc.endpoints.views import logout
|
|
134
|
+
from oidc.web.endpoints.views import logout
|
|
135
135
|
|
|
136
136
|
session = {SESSION_REFRESH_TOKEN_KEY: seal_refresh_token(endpoint_app, "private-refresh")}
|
|
137
137
|
calls = []
|
|
@@ -167,7 +167,7 @@ async def test_logout_revokes_before_clearing_even_on_failure(
|
|
|
167
167
|
indirect=["endpoint_app"],
|
|
168
168
|
)
|
|
169
169
|
async def test_logout_skips_unavailable_revocation(endpoint_app, token, httpx_mock):
|
|
170
|
-
from oidc.endpoints.views import logout
|
|
170
|
+
from oidc.web.endpoints.views import logout
|
|
171
171
|
|
|
172
172
|
session = {}
|
|
173
173
|
if token:
|
|
@@ -203,7 +203,7 @@ async def test_revocation_uses_its_own_advertised_auth_method(endpoint_app, http
|
|
|
203
203
|
async def test_revocation_total_time_is_capped(endpoint_app, monkeypatch):
|
|
204
204
|
import asyncio
|
|
205
205
|
|
|
206
|
-
from oidc import client as client_module
|
|
206
|
+
from oidc.identity import client as client_module
|
|
207
207
|
|
|
208
208
|
async def hang(*args, **kwargs):
|
|
209
209
|
await asyncio.Event().wait() # an IdP that never answers
|
|
@@ -73,7 +73,7 @@ async def test_logout_drops_the_sessions_shared_renewal_outcome(oidc_app, flow_c
|
|
|
73
73
|
import time
|
|
74
74
|
|
|
75
75
|
from auth.models import RefreshOutcome
|
|
76
|
-
from oidc.renewal import _KEYS
|
|
76
|
+
from oidc.identity.renewal import _KEYS
|
|
77
77
|
from simple_module_db import independent_session
|
|
78
78
|
from sqlmodel import select
|
|
79
79
|
|
|
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
|
{simple_module_oidc-0.0.43/tests → simple_module_oidc-0.0.44/tests/web/endpoints}/test_oidc_flow.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|