keepup-auth 0.4.0__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.
- keepup_auth-0.4.0/PKG-INFO +21 -0
- keepup_auth-0.4.0/README.md +10 -0
- keepup_auth-0.4.0/keepup_auth/__init__.py +22 -0
- keepup_auth-0.4.0/keepup_auth/config.py +128 -0
- keepup_auth-0.4.0/keepup_auth/dependencies.py +556 -0
- keepup_auth-0.4.0/keepup_auth/dto/__init__.py +0 -0
- keepup_auth-0.4.0/keepup_auth/dto/token.py +31 -0
- keepup_auth-0.4.0/keepup_auth/external_accounts.py +144 -0
- keepup_auth-0.4.0/keepup_auth/factory.py +28 -0
- keepup_auth-0.4.0/keepup_auth/identity/__init__.py +33 -0
- keepup_auth-0.4.0/keepup_auth/identity/access.py +119 -0
- keepup_auth-0.4.0/keepup_auth/identity/accounts.py +86 -0
- keepup_auth-0.4.0/keepup_auth/identity/config.py +220 -0
- keepup_auth-0.4.0/keepup_auth/identity/contract.py +166 -0
- keepup_auth-0.4.0/keepup_auth/identity/loader.py +119 -0
- keepup_auth-0.4.0/keepup_auth/identity/runtime.py +265 -0
- keepup_auth-0.4.0/keepup_auth/login_throttle.py +179 -0
- keepup_auth-0.4.0/keepup_auth/oidc.py +255 -0
- keepup_auth-0.4.0/keepup_auth/oidc_policy.py +122 -0
- keepup_auth-0.4.0/keepup_auth/oidc_routes.py +265 -0
- keepup_auth-0.4.0/keepup_auth/panel_session.py +379 -0
- keepup_auth-0.4.0/keepup_auth/permissions.py +59 -0
- keepup_auth-0.4.0/keepup_auth/providers/__init__.py +0 -0
- keepup_auth-0.4.0/keepup_auth/providers/base.py +216 -0
- keepup_auth-0.4.0/keepup_auth/providers/local.py +212 -0
- keepup_auth-0.4.0/keepup_auth/routes.py +514 -0
- keepup_auth-0.4.0/keepup_auth/seed_accounts.py +352 -0
- keepup_auth-0.4.0/keepup_auth/session_lifetime.py +104 -0
- keepup_auth-0.4.0/keepup_auth/signing_key.py +138 -0
- keepup_auth-0.4.0/keepup_auth/socket_sessions.py +176 -0
- keepup_auth-0.4.0/keepup_auth/tables.py +35 -0
- keepup_auth-0.4.0/keepup_auth/user_roles.py +376 -0
- keepup_auth-0.4.0/keepup_auth/user_routes.py +313 -0
- keepup_auth-0.4.0/keepup_auth/usernames.py +36 -0
- keepup_auth-0.4.0/keepup_auth/websocket.py +88 -0
- keepup_auth-0.4.0/keepup_auth.egg-info/PKG-INFO +21 -0
- keepup_auth-0.4.0/keepup_auth.egg-info/SOURCES.txt +41 -0
- keepup_auth-0.4.0/keepup_auth.egg-info/dependency_links.txt +1 -0
- keepup_auth-0.4.0/keepup_auth.egg-info/entry_points.txt +2 -0
- keepup_auth-0.4.0/keepup_auth.egg-info/requires.txt +4 -0
- keepup_auth-0.4.0/keepup_auth.egg-info/top_level.txt +1 -0
- keepup_auth-0.4.0/pyproject.toml +31 -0
- keepup_auth-0.4.0/setup.cfg +4 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: keepup-auth
|
|
3
|
+
Version: 0.4.0
|
|
4
|
+
Summary: Sign-in, sessions, login throttling, roles, OIDC and somebody else's identity system.
|
|
5
|
+
Requires-Python: >=3.11
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
Requires-Dist: keepup-db>=0.4.0
|
|
8
|
+
Requires-Dist: keepup-users>=0.4.0
|
|
9
|
+
Requires-Dist: bcrypt<5,>=4.0
|
|
10
|
+
Requires-Dist: pyjwt[crypto]<3,>=2.15
|
|
11
|
+
|
|
12
|
+
# keepup-auth
|
|
13
|
+
|
|
14
|
+
Sign-in, sessions, login throttling, roles, OIDC and somebody else's identity system.
|
|
15
|
+
|
|
16
|
+
One capability of [keepup](https://github.com/FedorSergeev/keepup-admin), the framework over FastAPI for admin
|
|
17
|
+
panels: the base is installed as `keepup-admin` and imported as `keepup`, and
|
|
18
|
+
this distribution carries one part of it. It is installed on its own
|
|
19
|
+
(`pip install keepup-auth`) or together with the base (`pip install "keepup-admin[panel]"`),
|
|
20
|
+
and it declares itself to the kernel through its
|
|
21
|
+
`[project.entry-points."keepup.plugins"]` entry point.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# keepup-auth
|
|
2
|
+
|
|
3
|
+
Sign-in, sessions, login throttling, roles, OIDC and somebody else's identity system.
|
|
4
|
+
|
|
5
|
+
One capability of [keepup](https://github.com/FedorSergeev/keepup-admin), the framework over FastAPI for admin
|
|
6
|
+
panels: the base is installed as `keepup-admin` and imported as `keepup`, and
|
|
7
|
+
this distribution carries one part of it. It is installed on its own
|
|
8
|
+
(`pip install keepup-auth`) or together with the base (`pip install "keepup-admin[panel]"`),
|
|
9
|
+
and it declares itself to the kernel through its
|
|
10
|
+
`[project.entry-points."keepup.plugins"]` entry point.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""Sign-in, sessions, login throttling, roles, OIDC and somebody else's identity system.
|
|
2
|
+
|
|
3
|
+
This distribution provides `auth, permissions` and declares one plugin,
|
|
4
|
+
`auth`, in the `keepup.plugins` group. It depends on `keepup-admin`
|
|
5
|
+
for the constructor -- the descriptor, the catalogue, the service registry and
|
|
6
|
+
the lifecycle -- and on nothing of another capability.
|
|
7
|
+
|
|
8
|
+
The code moves here in keepup-124: until then the plugin lives in the framework's
|
|
9
|
+
own `builtin/auth.py` and this package is the home it is moving to.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
__version__ = "0.4.0"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
# What the sign-in package exported, now that it is this distribution.
|
|
16
|
+
"""Authentication: providers, dependencies, the panel session and its routes.
|
|
17
|
+
|
|
18
|
+
The provider is chosen by ``config/auth.yaml`` through
|
|
19
|
+
:mod:`keepup.auth.factory`. What the application adds -- documents a person
|
|
20
|
+
must accept, the rule a password is held to, where a sign-in is written down --
|
|
21
|
+
arrives through settings, not from here.
|
|
22
|
+
"""
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"""Where the accounts of this deployment live.
|
|
2
|
+
|
|
3
|
+
One source: the application's own ``users`` table. Signing in through an
|
|
4
|
+
external identity provider is not a second source and not a second provider --
|
|
5
|
+
the account is still here, the provider only proves who is knocking. That path
|
|
6
|
+
is configured by the application through ``KeepupSettings.oidc`` and lives in
|
|
7
|
+
:mod:`keepup.auth.oidc`.
|
|
8
|
+
|
|
9
|
+
Until task 170 there was a second provider, an Apache Directory one, which
|
|
10
|
+
wrote into the directory as much as it read from it. It was never enabled on
|
|
11
|
+
any deployment and has been removed; ``provider: "ldap"`` now stops the start
|
|
12
|
+
rather than falling back to local, because a deployment expecting a directory
|
|
13
|
+
and silently getting local sign-in is an open door where nobody looked for one.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
import os
|
|
17
|
+
from enum import Enum
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
from typing import Any, Dict
|
|
20
|
+
|
|
21
|
+
import yaml
|
|
22
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
23
|
+
|
|
24
|
+
from keepup.roles import ROLE_CLIENT
|
|
25
|
+
|
|
26
|
+
#: What an application may import from this module. Everything else is
|
|
27
|
+
#: internal and may change without notice -- see doc/keepup.md.
|
|
28
|
+
__all__ = [
|
|
29
|
+
"AuthConfig",
|
|
30
|
+
"AuthProviderType",
|
|
31
|
+
# Assigned inside a try at module level: the configuration is read on
|
|
32
|
+
# import, and an application takes the object rather than building one.
|
|
33
|
+
"auth_config",
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
#: Kept as an enum although it has one member: the configuration file says a
|
|
37
|
+
#: name, and a name has to be validated against something.
|
|
38
|
+
class AuthProviderType(str, Enum):
|
|
39
|
+
"""Where a deployment keeps its accounts."""
|
|
40
|
+
|
|
41
|
+
LOCAL = "local"
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
#: Removed providers, named so that a deployment still asking for one is told
|
|
45
|
+
#: what happened instead of quietly getting something else.
|
|
46
|
+
RETIRED_PROVIDERS = {
|
|
47
|
+
"ldap": "the Apache Directory provider was removed in task 170; put an "
|
|
48
|
+
"OpenID Connect provider in front of your directory (Keycloak, for "
|
|
49
|
+
"example) and configure the application with it",
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class RetiredProvider(RuntimeError):
|
|
54
|
+
"""Raised at start-up for a provider that no longer exists."""
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class AuthConfig(BaseModel):
|
|
58
|
+
"""The authentication settings of this deployment."""
|
|
59
|
+
|
|
60
|
+
model_config = ConfigDict(validate_default=True)
|
|
61
|
+
|
|
62
|
+
provider: AuthProviderType = Field(AuthProviderType.LOCAL,
|
|
63
|
+
description="Where the accounts live")
|
|
64
|
+
enabled: bool = Field(True, description="Whether authentication is enabled")
|
|
65
|
+
default_role: str = Field(ROLE_CLIENT, description="Default role")
|
|
66
|
+
|
|
67
|
+
#: No default. The placeholder that used to stand here is one of the
|
|
68
|
+
#: values keepup.auth.signing_key refuses outright, so it never worked as
|
|
69
|
+
#: a default -- it only put a key-shaped string into the package.
|
|
70
|
+
jwt_secret: str = Field("", description="JWT signing key; supplied by the deployment")
|
|
71
|
+
jwt_algorithm: str = Field("HS256", description="JWT algorithm")
|
|
72
|
+
jwt_expire_minutes: int = Field(1440, description="Token lifetime, in minutes")
|
|
73
|
+
|
|
74
|
+
@classmethod
|
|
75
|
+
def from_yaml(cls, config_path: Path = Path("config/auth.yaml")) -> "AuthConfig":
|
|
76
|
+
"""Load the configuration from a YAML file."""
|
|
77
|
+
if not config_path.exists():
|
|
78
|
+
raise FileNotFoundError(f"Configuration file not found: {config_path}")
|
|
79
|
+
|
|
80
|
+
with open(config_path, 'r', encoding='utf-8') as f:
|
|
81
|
+
config_data = yaml.safe_load(f) or {}
|
|
82
|
+
|
|
83
|
+
return cls.from_mapping(config_data)
|
|
84
|
+
|
|
85
|
+
@classmethod
|
|
86
|
+
def from_mapping(cls, config_data: Dict[str, Any]) -> "AuthConfig":
|
|
87
|
+
"""Build the configuration from already parsed data."""
|
|
88
|
+
section = config_data.get("auth", config_data)
|
|
89
|
+
provider_str = str(section.get("provider", "local")).lower()
|
|
90
|
+
|
|
91
|
+
if provider_str in RETIRED_PROVIDERS:
|
|
92
|
+
raise RetiredProvider(
|
|
93
|
+
f"config/auth.yaml asks for the '{provider_str}' provider: "
|
|
94
|
+
f"{RETIRED_PROVIDERS[provider_str]}")
|
|
95
|
+
|
|
96
|
+
try:
|
|
97
|
+
provider = AuthProviderType(provider_str)
|
|
98
|
+
except ValueError:
|
|
99
|
+
print(f"Unknown provider '{provider_str}', falling back to 'local'")
|
|
100
|
+
provider = AuthProviderType.LOCAL
|
|
101
|
+
|
|
102
|
+
jwt_config = config_data.get("jwt", {}) or {}
|
|
103
|
+
|
|
104
|
+
return cls(
|
|
105
|
+
provider=provider,
|
|
106
|
+
enabled=section.get("enabled", True),
|
|
107
|
+
default_role=section.get("default_role", ROLE_CLIENT),
|
|
108
|
+
jwt_secret=jwt_config.get("secret", ""),
|
|
109
|
+
jwt_algorithm=jwt_config.get("algorithm", "HS256"),
|
|
110
|
+
jwt_expire_minutes=jwt_config.get("expire_minutes", 1440),
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
try:
|
|
115
|
+
config_path = Path(os.environ.get("AUTH_CONFIG_PATH", "config/auth.yaml"))
|
|
116
|
+
auth_config = AuthConfig.from_yaml(config_path)
|
|
117
|
+
print(f"Authentication configuration loaded from {config_path}")
|
|
118
|
+
print(f"Provider in use: {auth_config.provider}")
|
|
119
|
+
except RetiredProvider:
|
|
120
|
+
# Never swallowed: the whole point is that this deployment must not come up
|
|
121
|
+
# signing people in some other way than it asked for.
|
|
122
|
+
raise
|
|
123
|
+
except FileNotFoundError as e:
|
|
124
|
+
print(f"Warning: {e}. Falling back to the default configuration.")
|
|
125
|
+
auth_config = AuthConfig()
|
|
126
|
+
except Exception as e:
|
|
127
|
+
print(f"Could not load the configuration: {e}. Falling back to the default one.")
|
|
128
|
+
auth_config = AuthConfig()
|