micosauth 0.1.0__py3-none-any.whl
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.
- micosauth/__init__.py +21 -0
- micosauth/_helpers.py +30 -0
- micosauth/adapters/__init__.py +1 -0
- micosauth/adapters/fastapi/__init__.py +13 -0
- micosauth/adapters/fastapi/context.py +54 -0
- micosauth/adapters/fastapi/deps.py +82 -0
- micosauth/adapters/fastapi/install.py +12 -0
- micosauth/adapters/fastapi/lifespan.py +21 -0
- micosauth/adapters/fastapi/middleware.py +8 -0
- micosauth/decorators/__init__.py +9 -0
- micosauth/decorators/_support.py +85 -0
- micosauth/decorators/check_login.py +19 -0
- micosauth/decorators/check_permission.py +26 -0
- micosauth/decorators/check_role.py +26 -0
- micosauth/exceptions.py +26 -0
- micosauth/guards.py +9 -0
- micosauth/models.py +125 -0
- micosauth/provider.py +31 -0
- micosauth/redis_store.py +442 -0
- micosauth/service.py +81 -0
- micosauth/setting.py +42 -0
- micosauth/token_util.py +13 -0
- micosauth/utils/__init__.py +9 -0
- micosauth/utils/auth.py +311 -0
- micosauth/utils/auth_inner.py +7 -0
- micosauth/utils/session.py +53 -0
- micosauth-0.1.0.dist-info/METADATA +353 -0
- micosauth-0.1.0.dist-info/RECORD +30 -0
- micosauth-0.1.0.dist-info/WHEEL +5 -0
- micosauth-0.1.0.dist-info/top_level.txt +1 -0
micosauth/__init__.py
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from .provider import EmptyMicosAccessProvider, MicosAccessProvider
|
|
2
|
+
from .service import MicosService
|
|
3
|
+
from .setting import MicosDefaultsSetting, MicosRealmSetting, MicosRedisSetting, MicosSetting
|
|
4
|
+
from .token_util import MicosTokenUtil
|
|
5
|
+
from .utils.auth import MicosAuthUtil
|
|
6
|
+
from .utils.auth_inner import MicosAuthInnerUtil
|
|
7
|
+
from .utils.session import MicosSessionUtil
|
|
8
|
+
|
|
9
|
+
__all__ = [
|
|
10
|
+
"MicosAccessProvider",
|
|
11
|
+
"EmptyMicosAccessProvider",
|
|
12
|
+
"MicosRedisSetting",
|
|
13
|
+
"MicosRealmSetting",
|
|
14
|
+
"MicosDefaultsSetting",
|
|
15
|
+
"MicosService",
|
|
16
|
+
"MicosSetting",
|
|
17
|
+
"MicosTokenUtil",
|
|
18
|
+
"MicosAuthUtil",
|
|
19
|
+
"MicosAuthInnerUtil",
|
|
20
|
+
"MicosSessionUtil",
|
|
21
|
+
]
|
micosauth/_helpers.py
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import secrets
|
|
4
|
+
from datetime import datetime, timedelta, timezone
|
|
5
|
+
from fnmatch import fnmatchcase
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def utc_now() -> datetime:
|
|
9
|
+
return datetime.now(timezone.utc)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def expire_at(seconds: int) -> datetime:
|
|
13
|
+
return utc_now() + timedelta(seconds=max(1, int(seconds)))
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def random_token(length: int = 64) -> str:
|
|
17
|
+
if length <= 0:
|
|
18
|
+
length = 64
|
|
19
|
+
raw = secrets.token_hex((length + 1) // 2)
|
|
20
|
+
return raw[:length]
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def permission_matches(granted: str, required: str) -> bool:
|
|
24
|
+
"""判断权限是否匹配,支持通配符。"""
|
|
25
|
+
|
|
26
|
+
granted_value = str(granted or "").strip()
|
|
27
|
+
required_value = str(required or "").strip()
|
|
28
|
+
if not granted_value or not required_value:
|
|
29
|
+
return False
|
|
30
|
+
return fnmatchcase(required_value, granted_value) or fnmatchcase(granted_value, required_value)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__all__ = []
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from .deps import require_login_dep, require_permissions_dep, require_roles_dep
|
|
2
|
+
from .install import install_fastapi_auth
|
|
3
|
+
from .lifespan import build_micosauth_lifespan
|
|
4
|
+
from .middleware import MicosAuthMiddleware
|
|
5
|
+
|
|
6
|
+
__all__ = [
|
|
7
|
+
"install_fastapi_auth",
|
|
8
|
+
"build_micosauth_lifespan",
|
|
9
|
+
"MicosAuthMiddleware",
|
|
10
|
+
"require_login_dep",
|
|
11
|
+
"require_permissions_dep",
|
|
12
|
+
"require_roles_dep",
|
|
13
|
+
]
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
|
|
5
|
+
from fastapi import HTTPException, Request, status
|
|
6
|
+
|
|
7
|
+
from ...exceptions import MicosConfigError
|
|
8
|
+
from ...service import MicosService
|
|
9
|
+
from ...utils.auth import MicosAuthUtil
|
|
10
|
+
from ...utils.session import MicosSessionUtil
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass(slots=True)
|
|
14
|
+
class FastapiMicosContext:
|
|
15
|
+
service: MicosService
|
|
16
|
+
auth: MicosAuthUtil
|
|
17
|
+
session: MicosSessionUtil
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def get_micos_service(request: Request) -> MicosService:
|
|
21
|
+
service = getattr(request.app.state, "micos_service", None)
|
|
22
|
+
if service is None:
|
|
23
|
+
raise MicosConfigError("FastAPI app has no micos_service")
|
|
24
|
+
return service
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def get_micos_context(request: Request) -> FastapiMicosContext:
|
|
28
|
+
service = get_micos_service(request)
|
|
29
|
+
auth = getattr(request.app.state, "micos_auth", None)
|
|
30
|
+
session = getattr(request.app.state, "micos_session", None)
|
|
31
|
+
if auth is None or session is None:
|
|
32
|
+
raise MicosConfigError("FastAPI app has not initialized micosauth utilities")
|
|
33
|
+
return FastapiMicosContext(service=service, auth=auth, session=session)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def get_current_realm_id(request: Request, explicit_realm_id: str | None = None) -> str:
|
|
37
|
+
if explicit_realm_id:
|
|
38
|
+
return explicit_realm_id
|
|
39
|
+
raise MicosConfigError("realm_id is required")
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def get_current_token(request: Request, realm_id: str | None = None) -> str:
|
|
43
|
+
resolved_realm_id = get_current_realm_id(request, realm_id)
|
|
44
|
+
realm = get_micos_service(request).get_realm(resolved_realm_id)
|
|
45
|
+
token = request.headers.get(realm.token_name) or request.cookies.get(realm.token_name)
|
|
46
|
+
return str(token or "")
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def unauthorized(detail: str = "未登录或令牌无效") -> HTTPException:
|
|
50
|
+
return HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail=detail)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def forbidden(detail: str = "无权限访问") -> HTTPException:
|
|
54
|
+
return HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=detail)
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from fastapi import Request
|
|
4
|
+
|
|
5
|
+
from ...exceptions import MicosConfigError
|
|
6
|
+
from ...exceptions import MicosAuthenticationError, MicosAuthorizationError
|
|
7
|
+
from .context import get_current_realm_id, get_current_token, get_micos_context, unauthorized
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def require_login_dep(*, realm: str):
|
|
11
|
+
async def dependency(request: Request):
|
|
12
|
+
context = get_micos_context(request)
|
|
13
|
+
try:
|
|
14
|
+
resolved_realm_id = get_current_realm_id(request, realm)
|
|
15
|
+
except MicosConfigError as exc:
|
|
16
|
+
raise unauthorized(str(exc)) from exc
|
|
17
|
+
token = get_current_token(request, resolved_realm_id)
|
|
18
|
+
if not token:
|
|
19
|
+
raise unauthorized()
|
|
20
|
+
try:
|
|
21
|
+
result = await context.auth.require_login(token, resolved_realm_id)
|
|
22
|
+
except MicosAuthenticationError as exc:
|
|
23
|
+
raise unauthorized(str(exc) or "未登录或令牌无效") from exc
|
|
24
|
+
request.state.micos_token = token
|
|
25
|
+
request.state.micos_realm_id = result.realm_id
|
|
26
|
+
request.state.micos_login_id = result.login_id
|
|
27
|
+
request.state.micos_claims = result.claims
|
|
28
|
+
request.state.micos_session = result.session
|
|
29
|
+
request.state.micos_acl = result.acl
|
|
30
|
+
return result
|
|
31
|
+
|
|
32
|
+
return dependency
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def require_permissions_dep(permissions: list[str] | str, *, realm: str, mode: str = "AND"):
|
|
36
|
+
values = [permissions] if isinstance(permissions, str) else list(permissions)
|
|
37
|
+
|
|
38
|
+
async def dependency(request: Request):
|
|
39
|
+
context = get_micos_context(request)
|
|
40
|
+
try:
|
|
41
|
+
resolved_realm_id = get_current_realm_id(request, realm)
|
|
42
|
+
except MicosConfigError as exc:
|
|
43
|
+
raise unauthorized(str(exc)) from exc
|
|
44
|
+
token = get_current_token(request, resolved_realm_id)
|
|
45
|
+
if not token:
|
|
46
|
+
raise unauthorized()
|
|
47
|
+
try:
|
|
48
|
+
await context.auth.require_permissions(token, values, resolved_realm_id, mode)
|
|
49
|
+
except MicosAuthenticationError as exc:
|
|
50
|
+
raise unauthorized(str(exc) or "未登录或令牌无效") from exc
|
|
51
|
+
except MicosAuthorizationError as exc:
|
|
52
|
+
from .context import forbidden
|
|
53
|
+
|
|
54
|
+
raise forbidden(str(exc) or "无权限访问") from exc
|
|
55
|
+
return True
|
|
56
|
+
|
|
57
|
+
return dependency
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def require_roles_dep(roles: list[str] | str, *, realm: str, mode: str = "AND"):
|
|
61
|
+
values = [roles] if isinstance(roles, str) else list(roles)
|
|
62
|
+
|
|
63
|
+
async def dependency(request: Request):
|
|
64
|
+
context = get_micos_context(request)
|
|
65
|
+
try:
|
|
66
|
+
resolved_realm_id = get_current_realm_id(request, realm)
|
|
67
|
+
except MicosConfigError as exc:
|
|
68
|
+
raise unauthorized(str(exc)) from exc
|
|
69
|
+
token = get_current_token(request, resolved_realm_id)
|
|
70
|
+
if not token:
|
|
71
|
+
raise unauthorized()
|
|
72
|
+
try:
|
|
73
|
+
await context.auth.require_roles(token, values, resolved_realm_id, mode)
|
|
74
|
+
except MicosAuthenticationError as exc:
|
|
75
|
+
raise unauthorized(str(exc) or "未登录或令牌无效") from exc
|
|
76
|
+
except MicosAuthorizationError as exc:
|
|
77
|
+
from .context import forbidden
|
|
78
|
+
|
|
79
|
+
raise forbidden(str(exc) or "无权限访问") from exc
|
|
80
|
+
return True
|
|
81
|
+
|
|
82
|
+
return dependency
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from ...utils.auth import MicosAuthUtil
|
|
4
|
+
from ...utils.session import MicosSessionUtil
|
|
5
|
+
from .middleware import MicosAuthMiddleware
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def install_fastapi_auth(app, service) -> None:
|
|
9
|
+
app.state.micos_service = service
|
|
10
|
+
app.state.micos_auth = MicosAuthUtil(service)
|
|
11
|
+
app.state.micos_session = MicosSessionUtil(service)
|
|
12
|
+
app.add_middleware(MicosAuthMiddleware)
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from contextlib import asynccontextmanager
|
|
4
|
+
|
|
5
|
+
from ...utils.auth import MicosAuthUtil
|
|
6
|
+
from ...utils.session import MicosSessionUtil
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def build_micosauth_lifespan(service):
|
|
10
|
+
@asynccontextmanager
|
|
11
|
+
async def lifespan(app):
|
|
12
|
+
app.state.micos_service = service
|
|
13
|
+
app.state.micos_auth = MicosAuthUtil(service)
|
|
14
|
+
app.state.micos_session = MicosSessionUtil(service)
|
|
15
|
+
await service.init()
|
|
16
|
+
try:
|
|
17
|
+
yield
|
|
18
|
+
finally:
|
|
19
|
+
await service.close()
|
|
20
|
+
|
|
21
|
+
return lifespan
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from fastapi import Request
|
|
4
|
+
from starlette.middleware.base import BaseHTTPMiddleware
|
|
5
|
+
|
|
6
|
+
class MicosAuthMiddleware(BaseHTTPMiddleware):
|
|
7
|
+
async def dispatch(self, request: Request, call_next):
|
|
8
|
+
return await call_next(request)
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import inspect
|
|
4
|
+
from functools import wraps
|
|
5
|
+
|
|
6
|
+
from fastapi import HTTPException, Request, status
|
|
7
|
+
|
|
8
|
+
from ..exceptions import MicosAuthenticationError, MicosAuthorizationError
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def call_maybe_awaitable(fn, *args, **kwargs):
|
|
12
|
+
result = fn(*args, **kwargs)
|
|
13
|
+
if inspect.isawaitable(result):
|
|
14
|
+
return result
|
|
15
|
+
return result
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def get_token_from_call(args, kwargs) -> str:
|
|
19
|
+
token = kwargs.get("token")
|
|
20
|
+
if isinstance(token, str):
|
|
21
|
+
return token
|
|
22
|
+
request = kwargs.get("request")
|
|
23
|
+
if isinstance(request, Request):
|
|
24
|
+
return str(getattr(request.state, "micos_token", "") or "")
|
|
25
|
+
for arg in args:
|
|
26
|
+
if isinstance(arg, Request):
|
|
27
|
+
return str(getattr(arg.state, "micos_token", "") or "")
|
|
28
|
+
if isinstance(arg, str):
|
|
29
|
+
return arg
|
|
30
|
+
return ""
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def get_token_from_request_by_realm(args, kwargs, auth_util, realm: str) -> str:
|
|
34
|
+
request = kwargs.get("request")
|
|
35
|
+
if not isinstance(request, Request):
|
|
36
|
+
for arg in args:
|
|
37
|
+
if isinstance(arg, Request):
|
|
38
|
+
request = arg
|
|
39
|
+
break
|
|
40
|
+
if not isinstance(request, Request):
|
|
41
|
+
return ""
|
|
42
|
+
realm_model = auth_util.service.get_realm(realm)
|
|
43
|
+
token = request.headers.get(realm_model.token_name) or request.cookies.get(realm_model.token_name)
|
|
44
|
+
return str(token or "")
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def get_request_from_call(args, kwargs):
|
|
48
|
+
request = kwargs.get("request")
|
|
49
|
+
if isinstance(request, Request):
|
|
50
|
+
return request
|
|
51
|
+
for arg in args:
|
|
52
|
+
if isinstance(arg, Request):
|
|
53
|
+
return arg
|
|
54
|
+
return None
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def apply_auth_result_to_request(args, kwargs, token: str, result) -> None:
|
|
58
|
+
request = get_request_from_call(args, kwargs)
|
|
59
|
+
if not isinstance(request, Request) or result is None:
|
|
60
|
+
return
|
|
61
|
+
request.state.micos_token = token
|
|
62
|
+
request.state.micos_realm_id = result.realm_id
|
|
63
|
+
request.state.micos_login_id = result.login_id
|
|
64
|
+
request.state.micos_claims = result.claims
|
|
65
|
+
request.state.micos_session = result.session
|
|
66
|
+
request.state.micos_acl = result.acl
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def wraps_guard(func, checker):
|
|
70
|
+
@wraps(func)
|
|
71
|
+
async def wrapper(*args, **kwargs):
|
|
72
|
+
try:
|
|
73
|
+
await checker(*args, **kwargs)
|
|
74
|
+
except HTTPException:
|
|
75
|
+
raise
|
|
76
|
+
except MicosAuthenticationError as exc:
|
|
77
|
+
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail=str(exc) or "未登录或令牌无效") from exc
|
|
78
|
+
except MicosAuthorizationError as exc:
|
|
79
|
+
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=str(exc) or "无权限访问") from exc
|
|
80
|
+
result = call_maybe_awaitable(func, *args, **kwargs)
|
|
81
|
+
if inspect.isawaitable(result):
|
|
82
|
+
return await result
|
|
83
|
+
return result
|
|
84
|
+
|
|
85
|
+
return wrapper
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from ..exceptions import MicosConfigError
|
|
4
|
+
from ._support import apply_auth_result_to_request, get_token_from_call, get_token_from_request_by_realm, wraps_guard
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def require_login(auth_util, *, realm: str):
|
|
8
|
+
if not realm:
|
|
9
|
+
raise MicosConfigError("realm is required")
|
|
10
|
+
|
|
11
|
+
def decorator(func):
|
|
12
|
+
async def checker(*args, **kwargs):
|
|
13
|
+
token = get_token_from_call(args, kwargs) or get_token_from_request_by_realm(args, kwargs, auth_util, realm)
|
|
14
|
+
result = await auth_util.require_login(token, realm)
|
|
15
|
+
apply_auth_result_to_request(args, kwargs, token, result)
|
|
16
|
+
|
|
17
|
+
return wraps_guard(func, checker)
|
|
18
|
+
|
|
19
|
+
return decorator
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from ..exceptions import MicosConfigError
|
|
4
|
+
from ._support import (
|
|
5
|
+
apply_auth_result_to_request,
|
|
6
|
+
get_token_from_call,
|
|
7
|
+
get_token_from_request_by_realm,
|
|
8
|
+
wraps_guard,
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def require_permissions(auth_util, permissions: list[str] | str, *, realm: str, mode: str = "AND"):
|
|
13
|
+
if not realm:
|
|
14
|
+
raise MicosConfigError("realm is required")
|
|
15
|
+
values = [permissions] if isinstance(permissions, str) else list(permissions)
|
|
16
|
+
|
|
17
|
+
def decorator(func):
|
|
18
|
+
async def checker(*args, **kwargs):
|
|
19
|
+
token = get_token_from_call(args, kwargs) or get_token_from_request_by_realm(args, kwargs, auth_util, realm)
|
|
20
|
+
await auth_util.require_permissions(token, values, realm, mode)
|
|
21
|
+
result = await auth_util.require_login(token, realm)
|
|
22
|
+
apply_auth_result_to_request(args, kwargs, token, result)
|
|
23
|
+
|
|
24
|
+
return wraps_guard(func, checker)
|
|
25
|
+
|
|
26
|
+
return decorator
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from ..exceptions import MicosConfigError
|
|
4
|
+
from ._support import (
|
|
5
|
+
apply_auth_result_to_request,
|
|
6
|
+
get_token_from_call,
|
|
7
|
+
get_token_from_request_by_realm,
|
|
8
|
+
wraps_guard,
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def require_roles(auth_util, roles: list[str] | str, *, realm: str, mode: str = "AND"):
|
|
13
|
+
if not realm:
|
|
14
|
+
raise MicosConfigError("realm is required")
|
|
15
|
+
values = [roles] if isinstance(roles, str) else list(roles)
|
|
16
|
+
|
|
17
|
+
def decorator(func):
|
|
18
|
+
async def checker(*args, **kwargs):
|
|
19
|
+
token = get_token_from_call(args, kwargs) or get_token_from_request_by_realm(args, kwargs, auth_util, realm)
|
|
20
|
+
await auth_util.require_roles(token, values, realm, mode)
|
|
21
|
+
result = await auth_util.require_login(token, realm)
|
|
22
|
+
apply_auth_result_to_request(args, kwargs, token, result)
|
|
23
|
+
|
|
24
|
+
return wraps_guard(func, checker)
|
|
25
|
+
|
|
26
|
+
return decorator
|
micosauth/exceptions.py
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
class MicosAuthError(Exception):
|
|
2
|
+
"""micosauth 基础异常。"""
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class MicosConfigError(MicosAuthError):
|
|
6
|
+
"""配置无效时抛出。"""
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class MicosRedisError(MicosAuthError):
|
|
10
|
+
"""Redis 不可用或配置错误时抛出。"""
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class MicosRealmError(MicosAuthError):
|
|
14
|
+
"""Realm 不存在或无效时抛出。"""
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class MicosTokenError(MicosAuthError):
|
|
18
|
+
"""Token 无效时抛出。"""
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class MicosAuthenticationError(MicosAuthError):
|
|
22
|
+
"""认证失败时抛出。"""
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class MicosAuthorizationError(MicosAuthError):
|
|
26
|
+
"""鉴权失败时抛出。"""
|
micosauth/guards.py
ADDED
micosauth/models.py
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass(slots=True)
|
|
9
|
+
class MicosAcl:
|
|
10
|
+
roles: list[str] = field(default_factory=list)
|
|
11
|
+
permissions: list[str] = field(default_factory=list)
|
|
12
|
+
data_scopes: list[str] = field(default_factory=list)
|
|
13
|
+
extra: dict[str, Any] = field(default_factory=dict)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass(slots=True)
|
|
17
|
+
class MicosRealm:
|
|
18
|
+
realm_id: str
|
|
19
|
+
token_name: str = "Authorization"
|
|
20
|
+
token_ttl_seconds: int = 2592000
|
|
21
|
+
temp_token_ttl_seconds: int = 300
|
|
22
|
+
allow_multi_device_login: bool = True
|
|
23
|
+
keep_old_token_on_same_device_login: bool = False
|
|
24
|
+
keep_old_token_on_new_device_login: bool = True
|
|
25
|
+
max_devices_per_login_id: int = 0
|
|
26
|
+
max_tokens_per_login_id: int = 0
|
|
27
|
+
max_tokens_per_device: int = 0
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@dataclass(slots=True)
|
|
31
|
+
class MicosTokenClaims:
|
|
32
|
+
realm_id: str
|
|
33
|
+
login_id: str
|
|
34
|
+
device_id: str
|
|
35
|
+
token: str
|
|
36
|
+
issued_at: datetime
|
|
37
|
+
expires_at: datetime
|
|
38
|
+
last_access_at: datetime
|
|
39
|
+
extra: dict[str, Any] = field(default_factory=dict)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@dataclass(slots=True)
|
|
43
|
+
class MicosSessionRecord:
|
|
44
|
+
realm_id: str
|
|
45
|
+
login_id: str
|
|
46
|
+
token_count: int
|
|
47
|
+
device_count: int
|
|
48
|
+
last_login_at: datetime
|
|
49
|
+
last_access_at: datetime
|
|
50
|
+
extra: dict[str, Any] = field(default_factory=dict)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@dataclass(slots=True)
|
|
54
|
+
class MicosTokenRecord:
|
|
55
|
+
realm_id: str
|
|
56
|
+
login_id: str
|
|
57
|
+
device_id: str
|
|
58
|
+
token: str
|
|
59
|
+
issued_at: datetime
|
|
60
|
+
expires_at: datetime
|
|
61
|
+
last_access_at: datetime
|
|
62
|
+
extra: dict[str, Any] = field(default_factory=dict)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@dataclass(slots=True)
|
|
66
|
+
class MicosTempTokenRecord:
|
|
67
|
+
token: str
|
|
68
|
+
temp_id: str
|
|
69
|
+
token_type: str
|
|
70
|
+
realm_id: str
|
|
71
|
+
expires_at: datetime
|
|
72
|
+
extra: dict[str, Any] = field(default_factory=dict)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@dataclass(slots=True)
|
|
76
|
+
class MicosTokenInspectResult:
|
|
77
|
+
valid: bool
|
|
78
|
+
reason: str = ""
|
|
79
|
+
realm_id: str = ""
|
|
80
|
+
login_id: str = ""
|
|
81
|
+
device_id: str = ""
|
|
82
|
+
token: str = ""
|
|
83
|
+
claims: MicosTokenClaims | None = None
|
|
84
|
+
session: MicosSessionRecord | None = None
|
|
85
|
+
acl: MicosAcl | None = None
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
@dataclass(slots=True)
|
|
89
|
+
class MicosSessionAnalysis:
|
|
90
|
+
total_login_count: int = 0
|
|
91
|
+
total_token_count: int = 0
|
|
92
|
+
total_device_count: int = 0
|
|
93
|
+
one_hour_new_token_count: int = 0
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
@dataclass(slots=True)
|
|
97
|
+
class MicosSessionChartData:
|
|
98
|
+
days: list[str] = field(default_factory=list)
|
|
99
|
+
realm_series: dict[str, list[int]] = field(default_factory=dict)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
@dataclass(slots=True)
|
|
103
|
+
class MicosRealmDistributionItem:
|
|
104
|
+
realm_id: str
|
|
105
|
+
login_count: int = 0
|
|
106
|
+
token_count: int = 0
|
|
107
|
+
device_count: int = 0
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
@dataclass(slots=True)
|
|
111
|
+
class MicosDeviceSessionSummary:
|
|
112
|
+
realm_id: str
|
|
113
|
+
login_id: str
|
|
114
|
+
device_id: str
|
|
115
|
+
token_count: int = 0
|
|
116
|
+
last_login_at: datetime | None = None
|
|
117
|
+
last_access_at: datetime | None = None
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
@dataclass(slots=True)
|
|
121
|
+
class MicosPageResult:
|
|
122
|
+
items: list[object] = field(default_factory=list)
|
|
123
|
+
total: int = 0
|
|
124
|
+
current: int = 1
|
|
125
|
+
size: int = 10
|
micosauth/provider.py
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any, Protocol
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class MicosAccessProvider(Protocol):
|
|
7
|
+
async def get_roles(self, realm_id: str, login_id: str) -> list[str]:
|
|
8
|
+
"""获取角色列表。"""
|
|
9
|
+
|
|
10
|
+
async def get_permissions(self, realm_id: str, login_id: str) -> list[str]:
|
|
11
|
+
"""获取权限列表。"""
|
|
12
|
+
|
|
13
|
+
async def get_data_scopes(self, realm_id: str, login_id: str) -> list[str]:
|
|
14
|
+
"""获取数据范围列表。"""
|
|
15
|
+
|
|
16
|
+
async def get_extra(self, realm_id: str, login_id: str) -> dict[str, Any]:
|
|
17
|
+
"""获取额外信息。"""
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class EmptyMicosAccessProvider:
|
|
21
|
+
async def get_roles(self, realm_id: str, login_id: str) -> list[str]:
|
|
22
|
+
return []
|
|
23
|
+
|
|
24
|
+
async def get_permissions(self, realm_id: str, login_id: str) -> list[str]:
|
|
25
|
+
return []
|
|
26
|
+
|
|
27
|
+
async def get_data_scopes(self, realm_id: str, login_id: str) -> list[str]:
|
|
28
|
+
return []
|
|
29
|
+
|
|
30
|
+
async def get_extra(self, realm_id: str, login_id: str) -> dict[str, Any]:
|
|
31
|
+
return {}
|