cmdbox 0.5.3.1__py3-none-any.whl → 0.6.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.
Potentially problematic release.
This version of cmdbox might be problematic. Click here for more details.
- cmdbox/app/auth/__init__.py +0 -0
- cmdbox/app/auth/azure_signin.py +38 -0
- cmdbox/app/auth/azure_signin_saml.py +12 -0
- cmdbox/app/auth/github_signin.py +38 -0
- cmdbox/app/auth/google_signin.py +32 -0
- cmdbox/app/auth/signin.py +490 -287
- cmdbox/app/auth/signin_saml.py +61 -0
- cmdbox/app/common.py +48 -3
- cmdbox/app/edge.py +182 -213
- cmdbox/app/edge_tool.py +177 -0
- cmdbox/app/feature.py +10 -10
- cmdbox/app/features/cli/agent_base.py +477 -0
- cmdbox/app/features/cli/audit_base.py +1 -1
- cmdbox/app/features/cli/cmdbox_audit_search.py +24 -1
- cmdbox/app/features/cli/cmdbox_client_file_download.py +1 -1
- cmdbox/app/features/cli/cmdbox_cmd_list.py +105 -0
- cmdbox/app/features/cli/cmdbox_cmd_load.py +104 -0
- cmdbox/app/features/cli/cmdbox_edge_config.py +21 -7
- cmdbox/app/features/cli/cmdbox_edge_start.py +1 -1
- cmdbox/app/features/cli/cmdbox_gui_start.py +9 -132
- cmdbox/app/features/cli/cmdbox_gui_stop.py +4 -21
- cmdbox/app/features/cli/cmdbox_server_start.py +1 -1
- cmdbox/app/features/cli/cmdbox_web_apikey_add.py +1 -1
- cmdbox/app/features/cli/cmdbox_web_apikey_del.py +1 -1
- cmdbox/app/features/cli/cmdbox_web_genpass.py +0 -3
- cmdbox/app/features/cli/cmdbox_web_group_add.py +1 -1
- cmdbox/app/features/cli/cmdbox_web_group_del.py +1 -1
- cmdbox/app/features/cli/cmdbox_web_group_edit.py +1 -1
- cmdbox/app/features/cli/cmdbox_web_group_list.py +1 -1
- cmdbox/app/features/cli/cmdbox_web_start.py +119 -104
- cmdbox/app/features/cli/cmdbox_web_stop.py +1 -1
- cmdbox/app/features/cli/cmdbox_web_user_add.py +4 -4
- cmdbox/app/features/cli/cmdbox_web_user_del.py +1 -1
- cmdbox/app/features/cli/cmdbox_web_user_edit.py +4 -4
- cmdbox/app/features/cli/cmdbox_web_user_list.py +1 -1
- cmdbox/app/features/web/cmdbox_web_agent.py +250 -0
- cmdbox/app/features/web/cmdbox_web_do_signin.py +79 -103
- cmdbox/app/features/web/cmdbox_web_exec_cmd.py +8 -3
- cmdbox/app/features/web/cmdbox_web_signin.py +26 -4
- cmdbox/app/features/web/cmdbox_web_users.py +2 -0
- cmdbox/app/options.py +55 -2
- cmdbox/app/web.py +155 -27
- cmdbox/extensions/features.yml +18 -0
- cmdbox/extensions/sample_project/sample/app/features/cli/__init__.py +0 -0
- cmdbox/extensions/sample_project/sample/app/features/web/__init__.py +0 -0
- cmdbox/extensions/sample_project/sample/extensions/features.yml +23 -0
- cmdbox/extensions/sample_project/sample/extensions/user_list.yml +40 -6
- cmdbox/extensions/user_list.yml +37 -6
- cmdbox/licenses/{LICENSE.starlette.0.41.3(BSD License).txt → LICENSE.Authlib.1.5.2(BSD License).txt } +3 -1
- cmdbox/licenses/{LICENSE.pydantic_core.2.33.0(MIT License).txt → LICENSE.Deprecated.1.2.18(MIT License).txt } +2 -2
- cmdbox/licenses/{LICENSE.more-itertools.10.6.0(MIT License).txt → LICENSE.SQLAlchemy.2.0.40(MIT License).txt } +1 -1
- cmdbox/licenses/LICENSE.aiohttp.3.11.18(Apache Software License).txt +13 -0
- cmdbox/licenses/LICENSE.aiosignal.1.3.2(Apache Software License).txt +201 -0
- cmdbox/licenses/LICENSE.async-timeout.5.0.1(Apache Software License).txt +13 -0
- cmdbox/licenses/{LICENSE.watchfiles.1.0.0(MIT License).txt → LICENSE.attrs.25.3.0(UNKNOWN).txt} +1 -1
- cmdbox/licenses/{LICENSE.anyio.4.6.2.post1(MIT License).txt → LICENSE.cachetools.5.5.2(MIT License).txt } +1 -1
- cmdbox/licenses/LICENSE.distro.1.9.0(Apache Software License).txt +202 -0
- cmdbox/licenses/{LICENSE.pydantic_core.2.33.1(MIT License).txt → LICENSE.docstring_parser.0.16(MIT License).txt } +1 -1
- cmdbox/licenses/LICENSE.filelock.3.18.0(The Unlicense (Unlicense)).txt +24 -0
- cmdbox/licenses/LICENSE.frozenlist.1.6.0(Apache-2.0).txt +201 -0
- cmdbox/licenses/{LICENSE.starlette.0.46.1(BSD License).txt → LICENSE.fsspec.2025.3.2(BSD License).txt } +3 -1
- cmdbox/licenses/{LICENSE.argcomplete.3.6.1(Apache Software License).txt → LICENSE.google-adk.0.5.0(Apache Software License).txt } +25 -0
- cmdbox/licenses/LICENSE.google-api-python-client.2.169.0(Apache Software License).txt +201 -0
- cmdbox/licenses/LICENSE.google-auth-httplib2.0.2.0(Apache Software License).txt +201 -0
- cmdbox/licenses/LICENSE.google-auth.2.40.1(Apache Software License).txt +201 -0
- cmdbox/licenses/LICENSE.google-cloud-aiplatform.1.92.0(Apache 2.0).txt +202 -0
- cmdbox/licenses/LICENSE.google-cloud-bigquery.3.31.0(Apache Software License).txt +202 -0
- cmdbox/licenses/LICENSE.google-cloud-core.2.4.3(Apache Software License).txt +202 -0
- cmdbox/licenses/LICENSE.google-cloud-resource-manager.1.14.2(Apache Software License).txt +202 -0
- cmdbox/licenses/LICENSE.google-cloud-secret-manager.2.23.3(Apache Software License).txt +202 -0
- cmdbox/licenses/LICENSE.google-cloud-speech.2.32.0(Apache Software License).txt +202 -0
- cmdbox/licenses/LICENSE.google-cloud-storage.2.19.0(Apache Software License).txt +202 -0
- cmdbox/licenses/LICENSE.google-cloud-trace.1.16.1(Apache Software License).txt +202 -0
- cmdbox/licenses/LICENSE.google-crc32c.1.7.1(Apache 2.0).txt +202 -0
- cmdbox/licenses/LICENSE.google-genai.1.14.0(Apache Software License).txt +202 -0
- cmdbox/licenses/LICENSE.google-resumable-media.2.7.2(Apache Software License).txt +202 -0
- cmdbox/licenses/LICENSE.googleapis-common-protos.1.70.0(Apache Software License).txt +202 -0
- cmdbox/licenses/{LICENSE.fastapi.0.115.5(MIT License).txt → LICENSE.graphviz.0.20.3(MIT License).txt } +1 -1
- cmdbox/licenses/LICENSE.grpc-google-iam-v1.0.14.2(Apache Software License).txt +202 -0
- cmdbox/licenses/LICENSE.grpcio-status.1.71.0(Apache Software License).txt +610 -0
- cmdbox/licenses/LICENSE.grpcio.1.71.0(Apache Software License).txt +610 -0
- cmdbox/licenses/{LICENSE.uvicorn.0.34.0(BSD License).txt → LICENSE.httpcore.1.0.9(BSD License).txt } +1 -1
- cmdbox/licenses/LICENSE.httplib2.0.22.0(MIT License).txt +23 -0
- cmdbox/licenses/{LICENSE.tomli.2.1.0(MIT License).txt → LICENSE.httpx-sse.0.4.0(MIT).txt} +1 -1
- cmdbox/licenses/LICENSE.httpx.0.28.1(BSD License).txt +12 -0
- cmdbox/licenses/LICENSE.huggingface-hub.0.31.1(Apache Software License).txt +201 -0
- cmdbox/licenses/{LICENSE.charset-normalizer.3.4.0(MIT License).txt → LICENSE.jsonschema-specifications.2025.4.1(UNKNOWN).txt} +5 -7
- cmdbox/licenses/LICENSE.jsonschema.4.23.0(MIT License).txt +19 -0
- cmdbox/licenses/{LICENSE.pkginfo.1.10.0(MIT License).txt → LICENSE.litellm.1.69.0(MIT License).txt } +6 -1
- cmdbox/licenses/{LICENSE.redis.5.2.1(MIT License).txt → LICENSE.mcp.1.8.0(MIT License).txt } +1 -1
- cmdbox/licenses/LICENSE.multidict.6.4.3(Apache Software License).txt +13 -0
- cmdbox/licenses/{LICENSE.argcomplete.3.5.1(Apache Software License).txt → LICENSE.openai.1.75.0(Apache Software License).txt } +25 -1
- cmdbox/licenses/LICENSE.opentelemetry-api.1.33.0(Apache Software License).txt +201 -0
- cmdbox/licenses/LICENSE.opentelemetry-exporter-gcp-trace.1.9.0(Apache Software License).txt +201 -0
- cmdbox/licenses/LICENSE.opentelemetry-resourcedetector-gcp.1.9.0a0(Apache Software License).txt +201 -0
- cmdbox/licenses/LICENSE.opentelemetry-sdk.1.33.0(Apache Software License).txt +201 -0
- cmdbox/licenses/LICENSE.opentelemetry-semantic-conventions.0.54b0(Apache Software License).txt +201 -0
- cmdbox/licenses/LICENSE.propcache.0.3.1(Apache Software License).txt +202 -0
- cmdbox/licenses/LICENSE.proto-plus.1.26.1(Apache Software License).txt +202 -0
- cmdbox/licenses/{LICENSE.Pygments.2.18.0(BSD License).txt → LICENSE.protobuf.5.29.4(3-Clause BSD License).txt } +15 -8
- cmdbox/licenses/LICENSE.pyasn1.0.6.1(BSD License).txt +24 -0
- cmdbox/licenses/LICENSE.pyasn1_modules.0.4.2(BSD License).txt +24 -0
- cmdbox/licenses/LICENSE.pydantic-settings.2.9.1(MIT License).txt +21 -0
- cmdbox/licenses/{LICENSE.gevent.25.4.1(MIT).txt → LICENSE.pyparsing.3.2.3(MIT License).txt } +5 -12
- cmdbox/licenses/LICENSE.python-dateutil.2.9.0.post0(Apache Software License; BSD License).txt +54 -0
- cmdbox/licenses/LICENSE.referencing.0.36.2(UNKNOWN).txt +19 -0
- cmdbox/licenses/LICENSE.regex.2024.11.6(Apache Software License).txt +208 -0
- cmdbox/licenses/LICENSE.rpds-py.0.24.0(MIT).txt +19 -0
- cmdbox/licenses/{LICENSE.python-multipart.0.0.17(Apache Software License).txt → LICENSE.rsa.4.9.1(Apache Software License).txt } +1 -2
- cmdbox/licenses/{LICENSE.sphinx-intl.2.3.0(BSD License).txt → LICENSE.shapely.2.1.0(BSD License).txt } +6 -2
- cmdbox/licenses/LICENSE.sse-starlette.2.3.4(BSD License).txt +27 -0
- cmdbox/licenses/LICENSE.tiktoken.0.9.0(MIT License).txt +21 -0
- cmdbox/licenses/LICENSE.tokenizers.0.21.1(Apache Software License).txt +1 -0
- cmdbox/licenses/{LICENSE.six.1.16.0(MIT License).txt → LICENSE.tqdm.4.67.1(MIT License; Mozilla Public License 2.0 (MPL 2.0)).txt } +32 -1
- cmdbox/licenses/{LICENSE.rich.13.9.4(MIT License).txt → LICENSE.tzlocal.5.3.1(MIT License).txt } +3 -3
- cmdbox/licenses/LICENSE.uritemplate.4.1.1(Apache Software License; BSD License).txt +3 -0
- cmdbox/licenses/LICENSE.wrapt.1.17.2(BSD License).txt +24 -0
- cmdbox/licenses/LICENSE.yarl.1.20.0(Apache Software License).txt +202 -0
- cmdbox/licenses/files.txt +111 -17
- cmdbox/logconf_agent.yml +38 -0
- cmdbox/logconf_audit.yml +13 -5
- cmdbox/logconf_client.yml +13 -5
- cmdbox/logconf_cmdbox.yml +13 -5
- cmdbox/logconf_edge.yml +13 -5
- cmdbox/logconf_gui.yml +13 -5
- cmdbox/logconf_server.yml +13 -5
- cmdbox/logconf_web.yml +13 -5
- cmdbox/version.py +3 -2
- cmdbox/web/agent.html +263 -0
- cmdbox/web/assets/cmdbox/agent.js +335 -0
- cmdbox/web/assets/cmdbox/common.js +1111 -1020
- cmdbox/web/assets/cmdbox/signin.js +16 -3
- cmdbox/web/assets/cmdbox/users.js +1 -1
- cmdbox/web/assets/filer/filer.js +4 -2
- cmdbox/web/signin.html +10 -6
- {cmdbox-0.5.3.1.dist-info → cmdbox-0.6.0.dist-info}/METADATA +132 -35
- {cmdbox-0.5.3.1.dist-info → cmdbox-0.6.0.dist-info}/RECORD +161 -123
- cmdbox/app/features/web/cmdbox_web_load_pin.py +0 -43
- cmdbox/app/features/web/cmdbox_web_save_pin.py +0 -42
- cmdbox/licenses/LICENSE.Jinja2.3.1.4(BSD License).txt +0 -28
- cmdbox/licenses/LICENSE.Sphinx.8.1.3(BSD License).txt +0 -31
- cmdbox/licenses/LICENSE.babel.2.16.0(BSD License).txt +0 -27
- cmdbox/licenses/LICENSE.certifi.2025.1.31(Mozilla Public License 2.0 (MPL 2.0)).txt +0 -20
- cmdbox/licenses/LICENSE.click.8.1.8(BSD License).txt +0 -28
- cmdbox/licenses/LICENSE.cryptography.44.0.2(Apache Software License; BSD License).txt +0 -3
- cmdbox/licenses/LICENSE.greenlet.3.2.0(MIT AND Python-2.0).txt +0 -30
- cmdbox/licenses/LICENSE.keyring.25.5.0(MIT License).txt +0 -17
- cmdbox/licenses/LICENSE.numpy.2.2.4(BSD License).txt +0 -950
- cmdbox/licenses/LICENSE.pillow.11.0.0(CMU License (MIT-CMU)).txt +0 -1226
- cmdbox/licenses/LICENSE.pillow.11.1.0(CMU License (MIT-CMU)).txt +0 -1213
- cmdbox/licenses/LICENSE.prettytable.3.12.0(BSD License).txt +0 -30
- cmdbox/licenses/LICENSE.prompt_toolkit.3.0.50(BSD License).txt +0 -27
- cmdbox/licenses/LICENSE.psycopg.3.2.6(GNU Lesser General Public License v3 (LGPLv3)).txt +0 -165
- cmdbox/licenses/LICENSE.pydantic.2.11.1(MIT License).txt +0 -21
- cmdbox/licenses/LICENSE.pydantic.2.11.3(MIT License).txt +0 -21
- cmdbox/licenses/LICENSE.python-dotenv.1.0.1(BSD License).txt +0 -27
- cmdbox/licenses/LICENSE.twine.5.1.1(Apache Software License).txt +0 -174
- cmdbox/licenses/LICENSE.typing_extensions.4.13.0(UNKNOWN).txt +0 -279
- cmdbox/licenses/LICENSE.urllib3.2.2.3(MIT License).txt +0 -21
- cmdbox/licenses/LICENSE.urllib3.2.3.0(MIT License).txt +0 -21
- cmdbox/licenses/LICENSE.uvicorn.0.34.1(BSD License).txt +0 -27
- cmdbox/licenses/LICENSE.watchfiles.1.0.4(MIT License).txt +0 -21
- cmdbox/licenses/LICENSE.websockets.14.1(BSD License).txt +0 -24
- cmdbox/licenses/LICENSE.zope.interface.7.1.1(Zope Public License).txt +0 -44
- /cmdbox/licenses/{LICENSE.typing_extensions.4.12.2(Python Software Foundation License).txt → LICENSE.aiohappyeyeballs.2.6.1(Python Software Foundation License).txt} +0 -0
- /cmdbox/licenses/{LICENSE.certifi.2024.8.30(Mozilla Public License 2.0 (MPL 2.0)).txt → LICENSE.certifi.2025.4.26(Mozilla Public License 2.0 (MPL 2.0)).txt} +0 -0
- /cmdbox/licenses/{LICENSE.charset-normalizer.3.4.1(MIT License).txt → LICENSE.charset-normalizer.3.4.2(MIT License).txt} +0 -0
- /cmdbox/licenses/{LICENSE.click.8.1.7(BSD License).txt → LICENSE.click.8.2.0(UNKNOWN).txt} +0 -0
- /cmdbox/licenses/{LICENSE.cryptography.43.0.3(Apache Software License; BSD License).txt → LICENSE.cryptography.44.0.3(Apache Software License; BSD License).txt} +0 -0
- /cmdbox/licenses/{LICENSE.gevent.24.11.1(MIT License).txt → LICENSE.gevent.25.4.2(MIT).txt} +0 -0
- /cmdbox/licenses/{LICENSE.importlib_metadata.8.5.0(Apache Software License).txt → LICENSE.google-api-core.2.24.2(Apache Software License).txt} +0 -0
- /cmdbox/licenses/{LICENSE.greenlet.3.1.1(MIT License).txt → LICENSE.greenlet.3.2.2(MIT AND Python-2.0).txt} +0 -0
- /cmdbox/licenses/{LICENSE.h11.0.14.0(MIT License).txt → LICENSE.h11.0.16.0(MIT License).txt} +0 -0
- /cmdbox/licenses/{LICENSE.nh3.0.2.18(MIT).txt → LICENSE.jiter.0.9.0(MIT License).txt} +0 -0
- /cmdbox/licenses/{LICENSE.more-itertools.10.5.0(MIT License).txt → LICENSE.more-itertools.10.7.0(MIT License).txt} +0 -0
- /cmdbox/licenses/{LICENSE.numpy.2.1.3(BSD License).txt → LICENSE.numpy.2.2.5(BSD License).txt} +0 -0
- /cmdbox/licenses/{LICENSE.packaging.24.2(Apache Software License; BSD License).txt → LICENSE.packaging.25.0(Apache Software License; BSD License).txt} +0 -0
- /cmdbox/licenses/{LICENSE.psycopg-binary.3.2.6(GNU Lesser General Public License v3 (LGPLv3)).txt → LICENSE.psycopg-binary.3.2.7(GNU Lesser General Public License v3 (LGPLv3)).txt} +0 -0
- /cmdbox/licenses/{LICENSE.psycopg-pool.3.2.6(GNU Lesser General Public License v3 (LGPLv3)).txt → LICENSE.psycopg.3.2.7(GNU Lesser General Public License v3 (LGPLv3)).txt} +0 -0
- /cmdbox/licenses/{LICENSE.pydantic.2.10.2(MIT License).txt → LICENSE.pydantic.2.11.4(MIT License).txt} +0 -0
- /cmdbox/licenses/{LICENSE.pydantic_core.2.27.1(MIT License).txt → LICENSE.pydantic_core.2.33.2(MIT License).txt} +0 -0
- /cmdbox/licenses/{LICENSE.redis.5.2.0(MIT License).txt → LICENSE.redis.6.0.0(MIT License).txt} +0 -0
- /cmdbox/licenses/{LICENSE.snowballstemmer.2.2.0(BSD License).txt → LICENSE.snowballstemmer.3.0.1(BSD License).txt} +0 -0
- /cmdbox/licenses/{LICENSE.uvicorn.0.32.1(BSD License).txt → LICENSE.uvicorn.0.34.2(BSD License).txt} +0 -0
- {cmdbox-0.5.3.1.dist-info → cmdbox-0.6.0.dist-info}/LICENSE +0 -0
- {cmdbox-0.5.3.1.dist-info → cmdbox-0.6.0.dist-info}/WHEEL +0 -0
- {cmdbox-0.5.3.1.dist-info → cmdbox-0.6.0.dist-info}/entry_points.txt +0 -0
- {cmdbox-0.5.3.1.dist-info → cmdbox-0.6.0.dist-info}/top_level.txt +0 -0
cmdbox/app/auth/signin.py
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
from cmdbox.app import common, options
|
|
2
|
-
from fastapi import Request, Response, HTTPException
|
|
2
|
+
from fastapi import Request, Response, HTTPException, WebSocket
|
|
3
3
|
from fastapi.responses import RedirectResponse
|
|
4
4
|
from pathlib import Path
|
|
5
5
|
from typing import Dict, Any, Tuple, List, Union
|
|
6
6
|
import copy
|
|
7
|
+
import contextvars
|
|
7
8
|
import logging
|
|
8
9
|
import string
|
|
9
10
|
|
|
@@ -27,13 +28,12 @@ class Signin(object):
|
|
|
27
28
|
"""
|
|
28
29
|
return self.signin_file_data
|
|
29
30
|
|
|
30
|
-
def jadge(self,
|
|
31
|
+
def jadge(self, email:str) -> Tuple[bool, Dict[str, Any]]:
|
|
31
32
|
"""
|
|
32
33
|
サインインを成功させるかどうかを判定します。
|
|
33
34
|
返すユーザーデータには、uid, name, email, groups, hash が必要です。
|
|
34
35
|
|
|
35
36
|
Args:
|
|
36
|
-
access_token (str): アクセストークン
|
|
37
37
|
email (str): メールアドレス
|
|
38
38
|
|
|
39
39
|
Returns:
|
|
@@ -60,7 +60,8 @@ class Signin(object):
|
|
|
60
60
|
gids = [g['gid'] for g in copy_signin_data['groups'] if g['name'] in group_names]
|
|
61
61
|
return group_names, gids
|
|
62
62
|
|
|
63
|
-
|
|
63
|
+
@classmethod
|
|
64
|
+
def _enable_cors(cls, req:Request, res:Response) -> None:
|
|
64
65
|
"""
|
|
65
66
|
CORSを有効にする
|
|
66
67
|
|
|
@@ -72,7 +73,7 @@ class Signin(object):
|
|
|
72
73
|
return
|
|
73
74
|
res.headers['Access-Control-Allow-Origin'] = res.headers['Origin']
|
|
74
75
|
|
|
75
|
-
def check_signin(self, req:Request, res:Response):
|
|
76
|
+
def check_signin(self, req:Request, res:Response) -> Union[None, RedirectResponse]:
|
|
76
77
|
"""
|
|
77
78
|
サインインをチェックする
|
|
78
79
|
|
|
@@ -81,24 +82,44 @@ class Signin(object):
|
|
|
81
82
|
res (Response): レスポンス
|
|
82
83
|
|
|
83
84
|
Returns:
|
|
84
|
-
|
|
85
|
+
Union[None, RedirectResponse]: サインインエラーの場合はリダイレクトレスポンス
|
|
85
86
|
"""
|
|
86
|
-
self.enable_cors(req, res)
|
|
87
87
|
if self.signin_file_data is None:
|
|
88
88
|
return None
|
|
89
89
|
if 'signin' in req.session:
|
|
90
|
-
self.signin_file_data =
|
|
91
|
-
|
|
90
|
+
self.signin_file_data = Signin.load_signin_file(self.signin_file, self.signin_file_data) # サインインファイルの更新をチェック
|
|
91
|
+
return Signin._check_signin(req, res, self.signin_file_data, self.logger)
|
|
92
|
+
|
|
93
|
+
@classmethod
|
|
94
|
+
def _check_signin(cls, req:Request, res:Response, signin_file_data:Dict[str, Any], logger:logging.Logger) -> Union[None, RedirectResponse]:
|
|
95
|
+
"""
|
|
96
|
+
サインインをチェックする
|
|
97
|
+
|
|
98
|
+
Args:
|
|
99
|
+
req (Request): リクエスト
|
|
100
|
+
res (Response): レスポンス
|
|
101
|
+
signin_file_data (Dict[str, Any]): サインインファイルデータ(変更不可)
|
|
102
|
+
logger (logging.Logger): ロガー
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
Union[None, RedirectResponse]: サインインエラーの場合はリダイレクトレスポンス
|
|
106
|
+
"""
|
|
107
|
+
Signin._enable_cors(req, res)
|
|
108
|
+
if signin_file_data is None:
|
|
109
|
+
return None
|
|
110
|
+
if 'signin' in req.session:
|
|
111
|
+
path_jadge = Signin._check_path(req, req.url.path, signin_file_data, logger)
|
|
92
112
|
if path_jadge is not None:
|
|
93
113
|
return path_jadge
|
|
94
114
|
return None
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
115
|
+
if logger.level == logging.DEBUG:
|
|
116
|
+
logger.debug(f"Not found siginin session. Try check_apikey. path={req.url.path}")
|
|
117
|
+
ret = Signin._check_apikey(req, res, signin_file_data, logger)
|
|
118
|
+
if ret is not None and logger.level == logging.DEBUG:
|
|
119
|
+
logger.debug(f"Not signed in.")
|
|
99
120
|
return ret
|
|
100
121
|
|
|
101
|
-
def check_apikey(self, req:Request, res:Response):
|
|
122
|
+
def check_apikey(self, req:Request, res:Response) -> Union[None, RedirectResponse]:
|
|
102
123
|
"""
|
|
103
124
|
ApiKeyをチェックする
|
|
104
125
|
|
|
@@ -107,56 +128,71 @@ class Signin(object):
|
|
|
107
128
|
res (Response): レスポンス
|
|
108
129
|
|
|
109
130
|
Returns:
|
|
110
|
-
|
|
131
|
+
Union[None, RedirectResponse]: サインインエラーの場合はリダイレクトレスポンス
|
|
111
132
|
"""
|
|
112
|
-
|
|
113
|
-
|
|
133
|
+
return Signin._check_apikey(req, res, self.signin_file_data)
|
|
134
|
+
|
|
135
|
+
@classmethod
|
|
136
|
+
def _check_apikey(cls, req:Request, res:Response, signin_file_data:Dict[str, Any], logger:logging.Logger) -> Union[None, RedirectResponse]:
|
|
137
|
+
"""
|
|
138
|
+
ApiKeyをチェックする
|
|
139
|
+
|
|
140
|
+
Args:
|
|
141
|
+
req (Request): リクエスト
|
|
142
|
+
res (Response): レスポンス
|
|
143
|
+
signin_file_data (Dict[str, Any]): サインインファイルデータ(変更不可)
|
|
144
|
+
logger (logging.Logger): ロガー
|
|
145
|
+
|
|
146
|
+
Returns:
|
|
147
|
+
Union[None, RedirectResponse]: サインインエラーの場合はリダイレクトレスポンス
|
|
148
|
+
"""
|
|
149
|
+
Signin._enable_cors(req, res)
|
|
150
|
+
if signin_file_data is None:
|
|
114
151
|
res.headers['signin'] = 'success'
|
|
115
152
|
return None
|
|
116
153
|
if 'Authorization' not in req.headers:
|
|
117
|
-
self.logger.warning(f"Authorization not found. headers={req.headers}")
|
|
154
|
+
#self.logger.warning(f"Authorization not found. headers={req.headers}")
|
|
118
155
|
return RedirectResponse(url=f'/signin{req.url.path}?error=noauth')
|
|
119
156
|
auth = req.headers['Authorization']
|
|
120
157
|
if not auth.startswith('Bearer '):
|
|
121
|
-
self.logger.warning(f"Bearer not found. headers={req.headers}")
|
|
158
|
+
#self.logger.warning(f"Bearer not found. headers={req.headers}")
|
|
122
159
|
return RedirectResponse(url=f'/signin{req.url.path}?error=apikeyfail')
|
|
123
160
|
bearer, apikey = auth.split(' ')
|
|
124
161
|
apikey = common.hash_password(apikey.strip(), 'sha1')
|
|
125
|
-
if
|
|
126
|
-
|
|
162
|
+
if logger.level == logging.DEBUG:
|
|
163
|
+
logger.debug(f"hashed apikey: {apikey}")
|
|
127
164
|
find_user = None
|
|
128
|
-
|
|
129
|
-
for user in self.signin_file_data['users']:
|
|
165
|
+
for user in signin_file_data['users']:
|
|
130
166
|
if 'apikeys' not in user:
|
|
131
167
|
continue
|
|
132
168
|
for ak, key in user['apikeys'].items():
|
|
133
169
|
if apikey == key:
|
|
134
170
|
find_user = user
|
|
135
171
|
if find_user is None:
|
|
136
|
-
|
|
172
|
+
logger.warning(f"No matching user found for apikey.")
|
|
137
173
|
return RedirectResponse(url=f'/signin{req.url.path}?error=apikeyfail')
|
|
138
174
|
|
|
139
|
-
group_names = list(set(
|
|
140
|
-
gids = [g['gid'] for g in
|
|
175
|
+
group_names = list(set(Signin.correct_group(signin_file_data, find_user['groups'], None)))
|
|
176
|
+
gids = [g['gid'] for g in signin_file_data['groups'] if g['name'] in group_names]
|
|
141
177
|
req.session['signin'] = dict(uid=find_user['uid'], name=find_user['name'], password=find_user['password'],
|
|
142
178
|
gids=gids, groups=group_names)
|
|
143
|
-
if
|
|
144
|
-
|
|
179
|
+
if logger.level == logging.DEBUG:
|
|
180
|
+
logger.debug(f"find user: name={find_user['name']}, group_names={group_names}")
|
|
145
181
|
# パスルールチェック
|
|
146
182
|
user_groups = find_user['groups']
|
|
147
|
-
jadge =
|
|
148
|
-
for rule in
|
|
183
|
+
jadge = signin_file_data['pathrule']['policy']
|
|
184
|
+
for rule in signin_file_data['pathrule']['rules']:
|
|
149
185
|
if len([g for g in rule['groups'] if g in user_groups]) <= 0:
|
|
150
186
|
continue
|
|
151
187
|
if len([p for p in rule['paths'] if req.url.path.startswith(p)]) <= 0:
|
|
152
188
|
continue
|
|
153
189
|
jadge = rule['rule']
|
|
154
|
-
if
|
|
155
|
-
|
|
190
|
+
if logger.level == logging.DEBUG:
|
|
191
|
+
logger.debug(f"rule: {req.url.path}: {jadge}")
|
|
156
192
|
if jadge == 'allow':
|
|
157
193
|
res.headers['signin'] = 'success'
|
|
158
194
|
return None
|
|
159
|
-
|
|
195
|
+
logger.warning(f"Unauthorized site. user={find_user['name']}, path={req.url.path}")
|
|
160
196
|
return RedirectResponse(url=f'/signin{req.url.path}?error=unauthorizedsite')
|
|
161
197
|
|
|
162
198
|
@classmethod
|
|
@@ -174,250 +210,270 @@ class Signin(object):
|
|
|
174
210
|
Returns:
|
|
175
211
|
Dict[str, Any]: サインインファイルデータ
|
|
176
212
|
"""
|
|
177
|
-
if signin_file is
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
return signin_file_data
|
|
213
|
+
if signin_file is None:
|
|
214
|
+
return None
|
|
215
|
+
signin_file = Path(signin_file) if isinstance(signin_file, str) else signin_file
|
|
216
|
+
if not signin_file.is_file():
|
|
217
|
+
raise HTTPException(status_code=500, detail=f'signin_file is not found. ({signin_file})')
|
|
218
|
+
# サインインファイル読込み済みなら返すが、別プロセスがサインインファイルを更新していたら読込みを実施する。
|
|
219
|
+
if not hasattr(cls, 'signin_file_last'):
|
|
185
220
|
cls.signin_file_last = signin_file.stat().st_mtime
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
if '
|
|
264
|
-
raise HTTPException(status_code=500, detail=f'signin_file format error. "
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
if '
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
if '
|
|
357
|
-
raise HTTPException(status_code=500, detail=f'signin_file format error. "
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
221
|
+
if cls.signin_file_last >= signin_file.stat().st_mtime and signin_file_data is not None:
|
|
222
|
+
return signin_file_data
|
|
223
|
+
cls.signin_file_last = signin_file.stat().st_mtime
|
|
224
|
+
yml = common.load_yml(signin_file)
|
|
225
|
+
# usersのフォーマットチェック
|
|
226
|
+
if 'users' not in yml:
|
|
227
|
+
raise HTTPException(status_code=500, detail=f'signin_file format error. "users" not found. ({signin_file})')
|
|
228
|
+
uids = set()
|
|
229
|
+
unames = set()
|
|
230
|
+
groups = [g['name'] for g in yml['groups']]
|
|
231
|
+
for user in yml['users']:
|
|
232
|
+
if 'uid' not in user or user['uid'] is None:
|
|
233
|
+
raise HTTPException(status_code=500, detail=f'signin_file format error. "uid" not found or empty. ({signin_file})')
|
|
234
|
+
if user['uid'] in uids:
|
|
235
|
+
raise HTTPException(status_code=500, detail=f'signin_file format error. Duplicate uid found. ({signin_file}). uid={user["uid"]}')
|
|
236
|
+
if 'name' not in user or user['name'] is None:
|
|
237
|
+
raise HTTPException(status_code=500, detail=f'signin_file format error. "name" not found or empty. ({signin_file})')
|
|
238
|
+
if user['name'] in unames:
|
|
239
|
+
raise HTTPException(status_code=500, detail=f'signin_file format error. Duplicate name found. ({signin_file}). name={user["name"]}')
|
|
240
|
+
if 'password' not in user:
|
|
241
|
+
raise HTTPException(status_code=500, detail=f'signin_file format error. "password" not found or empty. ({signin_file})')
|
|
242
|
+
if 'hash' not in user or user['hash'] is None:
|
|
243
|
+
raise HTTPException(status_code=500, detail=f'signin_file format error. "hash" not found or empty. ({signin_file})')
|
|
244
|
+
if user['hash'] not in ['oauth2', 'saml', 'plain', 'md5', 'sha1', 'sha256']:
|
|
245
|
+
raise HTTPException(status_code=500, detail=f'signin_file format error. Algorithms not supported. ({signin_file}). hash={user["hash"]} "oauth2", "saml", "plain", "md5", "sha1", "sha256" only.')
|
|
246
|
+
if 'groups' not in user or type(user['groups']) is not list:
|
|
247
|
+
raise HTTPException(status_code=500, detail=f'signin_file format error. "groups" not found or not list type. ({signin_file})')
|
|
248
|
+
if len([ug for ug in user['groups'] if ug not in groups]) > 0:
|
|
249
|
+
raise HTTPException(status_code=500, detail=f'signin_file format error. Group not found. ({signin_file}). {user["groups"]}')
|
|
250
|
+
uids.add(user['uid'])
|
|
251
|
+
unames.add(user['name'])
|
|
252
|
+
# groupsのフォーマットチェック
|
|
253
|
+
if 'groups' not in yml:
|
|
254
|
+
raise HTTPException(status_code=500, detail=f'signin_file format error. "groups" not found. ({signin_file})')
|
|
255
|
+
gids = set()
|
|
256
|
+
gnames = set()
|
|
257
|
+
for group in yml['groups']:
|
|
258
|
+
if 'gid' not in group or group['gid'] is None:
|
|
259
|
+
raise HTTPException(status_code=500, detail=f'signin_file format error. "gid" not found or empty. ({signin_file})')
|
|
260
|
+
if group['gid'] in gids:
|
|
261
|
+
raise HTTPException(status_code=500, detail=f'signin_file format error. Duplicate gid found. ({signin_file}). gid={group["gid"]}')
|
|
262
|
+
if 'name' not in group or group['name'] is None:
|
|
263
|
+
raise HTTPException(status_code=500, detail=f'signin_file format error. "name" not found or empty. ({signin_file})')
|
|
264
|
+
if group['name'] in gnames:
|
|
265
|
+
raise HTTPException(status_code=500, detail=f'signin_file format error. Duplicate name found. ({signin_file}). name={group["name"]}')
|
|
266
|
+
if 'parent' in group:
|
|
267
|
+
if group['parent'] not in groups:
|
|
268
|
+
raise HTTPException(status_code=500, detail=f'signin_file format error. Parent group not found. ({signin_file}). parent={group["parent"]}')
|
|
269
|
+
gids.add(group['gid'])
|
|
270
|
+
gnames.add(group['name'])
|
|
271
|
+
# cmdruleのフォーマットチェック
|
|
272
|
+
if 'cmdrule' not in yml:
|
|
273
|
+
raise HTTPException(status_code=500, detail=f'signin_file format error. "cmdrule" not found. ({signin_file})')
|
|
274
|
+
if 'policy' not in yml['cmdrule']:
|
|
275
|
+
raise HTTPException(status_code=500, detail=f'signin_file format error. "policy" not found in "cmdrule". ({signin_file})')
|
|
276
|
+
if yml['cmdrule']['policy'] not in ['allow', 'deny']:
|
|
277
|
+
raise HTTPException(status_code=500, detail=f'signin_file format error. "policy" not supported in "cmdrule". ({signin_file}). "allow" or "deny" only.')
|
|
278
|
+
if 'rules' not in yml['cmdrule']:
|
|
279
|
+
raise HTTPException(status_code=500, detail=f'signin_file format error. "rules" not found in "cmdrule". ({signin_file})')
|
|
280
|
+
if type(yml['cmdrule']['rules']) is not list:
|
|
281
|
+
raise HTTPException(status_code=500, detail=f'signin_file format error. "rules" not list type in "cmdrule". ({signin_file})')
|
|
282
|
+
for rule in yml['cmdrule']['rules']:
|
|
283
|
+
if 'groups' not in rule:
|
|
284
|
+
raise HTTPException(status_code=500, detail=f'signin_file format error. "groups" not found in "cmdrule.rules" ({signin_file})')
|
|
285
|
+
if type(rule['groups']) is not list:
|
|
286
|
+
raise HTTPException(status_code=500, detail=f'signin_file format error. "groups" not list type in "cmdrule.rules". ({signin_file})')
|
|
287
|
+
rule['groups'] = list(set(copy.deepcopy(cls.correct_group(yml, rule['groups'], yml['groups']))))
|
|
288
|
+
if 'rule' not in rule:
|
|
289
|
+
raise HTTPException(status_code=500, detail=f'signin_file format error. "rule" not found in "cmdrule.rules" ({signin_file})')
|
|
290
|
+
if rule['rule'] not in ['allow', 'deny']:
|
|
291
|
+
raise HTTPException(status_code=500, detail=f'signin_file format error. "rule" not supported in "cmdrule.rules". ({signin_file}). "allow" or "deny" only.')
|
|
292
|
+
if 'mode' not in rule:
|
|
293
|
+
rule['mode'] = None
|
|
294
|
+
if 'cmds' not in rule:
|
|
295
|
+
rule['cmds'] = []
|
|
296
|
+
if rule['mode'] is None and len(rule['cmds']) > 0:
|
|
297
|
+
raise HTTPException(status_code=500, detail=f'signin_file format error. When “cmds” is specified, “mode” must be specified. ({signin_file})')
|
|
298
|
+
if type(rule['cmds']) is not list:
|
|
299
|
+
raise HTTPException(status_code=500, detail=f'signin_file format error. "cmds" not list type in "cmdrule.rules". ({signin_file})')
|
|
300
|
+
# pathruleのフォーマットチェック
|
|
301
|
+
if 'pathrule' not in yml:
|
|
302
|
+
raise HTTPException(status_code=500, detail=f'signin_file format error. "pathrule" not found. ({signin_file})')
|
|
303
|
+
if 'policy' not in yml['pathrule']:
|
|
304
|
+
raise HTTPException(status_code=500, detail=f'signin_file format error. "policy" not found in "pathrule". ({signin_file})')
|
|
305
|
+
if yml['pathrule']['policy'] not in ['allow', 'deny']:
|
|
306
|
+
raise HTTPException(status_code=500, detail=f'signin_file format error. "policy" not supported in "pathrule". ({signin_file}). "allow" or "deny" only.')
|
|
307
|
+
if 'rules' not in yml['pathrule']:
|
|
308
|
+
raise HTTPException(status_code=500, detail=f'signin_file format error. "rules" not found in "pathrule". ({signin_file})')
|
|
309
|
+
if type(yml['pathrule']['rules']) is not list:
|
|
310
|
+
raise HTTPException(status_code=500, detail=f'signin_file format error. "rules" not list type in "pathrule". ({signin_file})')
|
|
311
|
+
for rule in yml['pathrule']['rules']:
|
|
312
|
+
if 'groups' not in rule:
|
|
313
|
+
raise HTTPException(status_code=500, detail=f'signin_file format error. "groups" not found in "pathrule.rules" ({signin_file})')
|
|
314
|
+
if type(rule['groups']) is not list:
|
|
315
|
+
raise HTTPException(status_code=500, detail=f'signin_file format error. "groups" not list type in "pathrule.rules". ({signin_file})')
|
|
316
|
+
rule['groups'] = list(set(copy.deepcopy(cls.correct_group(yml, rule['groups'], yml['groups']))))
|
|
317
|
+
if 'rule' not in rule:
|
|
318
|
+
raise HTTPException(status_code=500, detail=f'signin_file format error. "rule" not found in "pathrule.rules" ({signin_file})')
|
|
319
|
+
if rule['rule'] not in ['allow', 'deny']:
|
|
320
|
+
raise HTTPException(status_code=500, detail=f'signin_file format error. "rule" not supported in "pathrule.rules". ({signin_file}). "allow" or "deny" only.')
|
|
321
|
+
if 'paths' not in rule:
|
|
322
|
+
rule['paths'] = []
|
|
323
|
+
if type(rule['paths']) is not list:
|
|
324
|
+
raise HTTPException(status_code=500, detail=f'signin_file format error. "paths" not list type in "pathrule.rules". ({signin_file})')
|
|
325
|
+
# passwordのフォーマットチェック
|
|
326
|
+
if 'password' in yml:
|
|
327
|
+
if 'policy' not in yml['password']:
|
|
328
|
+
raise HTTPException(status_code=500, detail=f'signin_file format error. "policy" not found in "password". ({signin_file})')
|
|
329
|
+
if 'enabled' not in yml['password']['policy']:
|
|
330
|
+
raise HTTPException(status_code=500, detail=f'signin_file format error. "enabled" not found in "password.policy". ({signin_file})')
|
|
331
|
+
if type(yml['password']['policy']['enabled']) is not bool:
|
|
332
|
+
raise HTTPException(status_code=500, detail=f'signin_file format error. "enabled" not bool type in "password.policy". ({signin_file})')
|
|
333
|
+
if 'not_same_before' not in yml['password']['policy']:
|
|
334
|
+
raise HTTPException(status_code=500, detail=f'signin_file format error. "not_same_before" not found in "password.policy". ({signin_file})')
|
|
335
|
+
if type(yml['password']['policy']['not_same_before']) is not bool:
|
|
336
|
+
raise HTTPException(status_code=500, detail=f'signin_file format error. "not_same_before" not bool type in "password.policy". ({signin_file})')
|
|
337
|
+
if 'min_length' not in yml['password']['policy']:
|
|
338
|
+
raise HTTPException(status_code=500, detail=f'signin_file format error. "min_length" not found in "password.policy". ({signin_file})')
|
|
339
|
+
if type(yml['password']['policy']['min_length']) is not int:
|
|
340
|
+
raise HTTPException(status_code=500, detail=f'signin_file format error. "min_length" not int type in "password.policy". ({signin_file})')
|
|
341
|
+
if 'max_length' not in yml['password']['policy']:
|
|
342
|
+
raise HTTPException(status_code=500, detail=f'signin_file format error. "max_length" not found in "password.policy". ({signin_file})')
|
|
343
|
+
if type(yml['password']['policy']['max_length']) is not int:
|
|
344
|
+
raise HTTPException(status_code=500, detail=f'signin_file format error. "max_length" not int type in "password.policy". ({signin_file})')
|
|
345
|
+
if 'min_lowercase' not in yml['password']['policy']:
|
|
346
|
+
raise HTTPException(status_code=500, detail=f'signin_file format error. "min_lowercase" not found in "password.policy". ({signin_file})')
|
|
347
|
+
if type(yml['password']['policy']['min_lowercase']) is not int:
|
|
348
|
+
raise HTTPException(status_code=500, detail=f'signin_file format error. "min_lowercase" not int type in "password.policy". ({signin_file})')
|
|
349
|
+
if 'min_uppercase' not in yml['password']['policy']:
|
|
350
|
+
raise HTTPException(status_code=500, detail=f'signin_file format error. "min_uppercase" not found in "password.policy". ({signin_file})')
|
|
351
|
+
if type(yml['password']['policy']['min_uppercase']) is not int:
|
|
352
|
+
raise HTTPException(status_code=500, detail=f'signin_file format error. "min_uppercase" not int type in "password.policy". ({signin_file})')
|
|
353
|
+
if 'min_digit' not in yml['password']['policy']:
|
|
354
|
+
raise HTTPException(status_code=500, detail=f'signin_file format error. "min_digit" not found in "password.policy". ({signin_file})')
|
|
355
|
+
if type(yml['password']['policy']['min_digit']) is not int:
|
|
356
|
+
raise HTTPException(status_code=500, detail=f'signin_file format error. "min_digit" not int type in "password.policy". ({signin_file})')
|
|
357
|
+
if 'min_symbol' not in yml['password']['policy']:
|
|
358
|
+
raise HTTPException(status_code=500, detail=f'signin_file format error. "min_symbol" not found in "password.policy". ({signin_file})')
|
|
359
|
+
if type(yml['password']['policy']['min_symbol']) is not int:
|
|
360
|
+
raise HTTPException(status_code=500, detail=f'signin_file format error. "min_symbol" not int type in "password.policy". ({signin_file})')
|
|
361
|
+
if 'not_contain_username' not in yml['password']['policy']:
|
|
362
|
+
raise HTTPException(status_code=500, detail=f'signin_file format error. "not_contain_username" not found in "password.policy". ({signin_file})')
|
|
363
|
+
if type(yml['password']['policy']['not_contain_username']) is not bool:
|
|
364
|
+
raise HTTPException(status_code=500, detail=f'signin_file format error. "not_contain_username" not bool type in "password.policy". ({signin_file})')
|
|
365
|
+
if 'expiration' not in yml['password']:
|
|
366
|
+
raise HTTPException(status_code=500, detail=f'signin_file format error. "expiration" not found in "password". ({signin_file})')
|
|
367
|
+
if 'enabled' not in yml['password']['expiration']:
|
|
368
|
+
raise HTTPException(status_code=500, detail=f'signin_file format error. "enabled" not found in "password.expiration". ({signin_file})')
|
|
369
|
+
if type(yml['password']['expiration']['enabled']) is not bool:
|
|
370
|
+
raise HTTPException(status_code=500, detail=f'signin_file format error. "enabled" not bool type in "password.expiration". ({signin_file})')
|
|
371
|
+
if 'period' not in yml['password']['expiration']:
|
|
372
|
+
raise HTTPException(status_code=500, detail=f'signin_file format error. "period" not found in "password.expiration". ({signin_file})')
|
|
373
|
+
if type(yml['password']['expiration']['period']) is not int:
|
|
374
|
+
raise HTTPException(status_code=500, detail=f'signin_file format error. "period" not int type in "password.expiration". ({signin_file})')
|
|
375
|
+
if 'notify' not in yml['password']['expiration']:
|
|
376
|
+
raise HTTPException(status_code=500, detail=f'signin_file format error. "notify" not found in "password.expiration". ({signin_file})')
|
|
377
|
+
if type(yml['password']['expiration']['notify']) is not int:
|
|
378
|
+
raise HTTPException(status_code=500, detail=f'signin_file format error. "notify" not int type in "password.expiration". ({signin_file})')
|
|
379
|
+
if 'lockout' not in yml['password']:
|
|
380
|
+
raise HTTPException(status_code=500, detail=f'signin_file format error. "lockout" not found in "password". ({signin_file})')
|
|
381
|
+
if 'enabled' not in yml['password']['lockout']:
|
|
382
|
+
raise HTTPException(status_code=500, detail=f'signin_file format error. "enabled" not found in "password.lockout". ({signin_file})')
|
|
383
|
+
if type(yml['password']['lockout']['enabled']) is not bool:
|
|
384
|
+
raise HTTPException(status_code=500, detail=f'signin_file format error. "enabled" not bool type in "password.lockout". ({signin_file})')
|
|
385
|
+
if 'threshold' not in yml['password']['lockout']:
|
|
386
|
+
raise HTTPException(status_code=500, detail=f'signin_file format error. "threshold" not found in "password.lockout". ({signin_file})')
|
|
387
|
+
if type(yml['password']['lockout']['threshold']) is not int:
|
|
388
|
+
raise HTTPException(status_code=500, detail=f'signin_file format error. "threshold" not int type in "password.lockout". ({signin_file})')
|
|
389
|
+
if 'reset' not in yml['password']['lockout']:
|
|
390
|
+
raise HTTPException(status_code=500, detail=f'signin_file format error. "reset" not found in "password.lockout". ({signin_file})')
|
|
391
|
+
if type(yml['password']['lockout']['reset']) is not int:
|
|
392
|
+
raise HTTPException(status_code=500, detail=f'signin_file format error. "reset" not int type in "password.lockout". ({signin_file})')
|
|
393
|
+
# oauth2のフォーマットチェック
|
|
394
|
+
if 'oauth2' not in yml:
|
|
395
|
+
raise HTTPException(status_code=500, detail=f'signin_file format error. "oauth2" not found. ({signin_file})')
|
|
396
|
+
if 'providers' not in yml['oauth2']:
|
|
397
|
+
raise HTTPException(status_code=500, detail=f'signin_file format error. "providers" not found in "oauth2". ({signin_file})')
|
|
398
|
+
# google
|
|
399
|
+
if 'google' not in yml['oauth2']['providers']:
|
|
400
|
+
raise HTTPException(status_code=500, detail=f'signin_file format error. "google" not found in "providers". ({signin_file})')
|
|
401
|
+
if 'enabled' not in yml['oauth2']['providers']['google']:
|
|
402
|
+
raise HTTPException(status_code=500, detail=f'signin_file format error. "enabled" not found in "google". ({signin_file})')
|
|
403
|
+
if type(yml['oauth2']['providers']['google']['enabled']) is not bool:
|
|
404
|
+
raise HTTPException(status_code=500, detail=f'signin_file format error. "enabled" not bool type in "google". ({signin_file})')
|
|
405
|
+
if 'client_id' not in yml['oauth2']['providers']['google']:
|
|
406
|
+
raise HTTPException(status_code=500, detail=f'signin_file format error. "client_id" not found in "google". ({signin_file})')
|
|
407
|
+
if 'client_secret' not in yml['oauth2']['providers']['google']:
|
|
408
|
+
raise HTTPException(status_code=500, detail=f'signin_file format error. "client_secret" not found in "google". ({signin_file})')
|
|
409
|
+
if 'redirect_uri' not in yml['oauth2']['providers']['google']:
|
|
410
|
+
raise HTTPException(status_code=500, detail=f'signin_file format error. "redirect_uri" not found in "google". ({signin_file})')
|
|
411
|
+
if 'scope' not in yml['oauth2']['providers']['google']:
|
|
412
|
+
raise HTTPException(status_code=500, detail=f'signin_file format error. "scope" not found in "google". ({signin_file})')
|
|
413
|
+
if type(yml['oauth2']['providers']['google']['scope']) is not list:
|
|
414
|
+
raise HTTPException(status_code=500, detail=f'signin_file format error. "scope" not list type in "google". ({signin_file})')
|
|
415
|
+
if 'signin_module' not in yml['oauth2']['providers']['google']:
|
|
416
|
+
raise HTTPException(status_code=500, detail=f'signin_file format error. "signin_module" not found in "google". ({signin_file})')
|
|
417
|
+
# github
|
|
418
|
+
if 'github' not in yml['oauth2']['providers']:
|
|
419
|
+
raise HTTPException(status_code=500, detail=f'signin_file format error. "github" not found in "providers". ({signin_file})')
|
|
420
|
+
if 'enabled' not in yml['oauth2']['providers']['github']:
|
|
421
|
+
raise HTTPException(status_code=500, detail=f'signin_file format error. "enabled" not found in "github". ({signin_file})')
|
|
422
|
+
if type(yml['oauth2']['providers']['github']['enabled']) is not bool:
|
|
423
|
+
raise HTTPException(status_code=500, detail=f'signin_file format error. "enabled" not bool type in "github". ({signin_file})')
|
|
424
|
+
if 'client_id' not in yml['oauth2']['providers']['github']:
|
|
425
|
+
raise HTTPException(status_code=500, detail=f'signin_file format error. "client_id" not found in "github". ({signin_file})')
|
|
426
|
+
if 'client_secret' not in yml['oauth2']['providers']['github']:
|
|
427
|
+
raise HTTPException(status_code=500, detail=f'signin_file format error. "client_secret" not found in "github". ({signin_file})')
|
|
428
|
+
if 'redirect_uri' not in yml['oauth2']['providers']['github']:
|
|
429
|
+
raise HTTPException(status_code=500, detail=f'signin_file format error. "redirect_uri" not found in "github". ({signin_file})')
|
|
430
|
+
if 'scope' not in yml['oauth2']['providers']['github']:
|
|
431
|
+
raise HTTPException(status_code=500, detail=f'signin_file format error. "scope" not found in "github". ({signin_file})')
|
|
432
|
+
if type(yml['oauth2']['providers']['github']['scope']) is not list:
|
|
433
|
+
raise HTTPException(status_code=500, detail=f'signin_file format error. "scope" not list type in "github". ({signin_file})')
|
|
434
|
+
if 'signin_module' not in yml['oauth2']['providers']['github']:
|
|
435
|
+
raise HTTPException(status_code=500, detail=f'signin_file format error. "signin_module" not found in "github". ({signin_file})')
|
|
436
|
+
# azure
|
|
437
|
+
if 'azure' not in yml['oauth2']['providers']:
|
|
438
|
+
raise HTTPException(status_code=500, detail=f'signin_file format error. "azure" not found in "providers". ({signin_file})')
|
|
439
|
+
if 'enabled' not in yml['oauth2']['providers']['azure']:
|
|
440
|
+
raise HTTPException(status_code=500, detail=f'signin_file format error. "enabled" not found in "azure". ({signin_file})')
|
|
441
|
+
if type(yml['oauth2']['providers']['azure']['enabled']) is not bool:
|
|
442
|
+
raise HTTPException(status_code=500, detail=f'signin_file format error. "enabled" not bool type in "azure". ({signin_file})')
|
|
443
|
+
if 'tenant_id' not in yml['oauth2']['providers']['azure']:
|
|
444
|
+
raise HTTPException(status_code=500, detail=f'signin_file format error. "tenant_id" not found in "azure". ({signin_file})')
|
|
445
|
+
if 'client_id' not in yml['oauth2']['providers']['azure']:
|
|
446
|
+
raise HTTPException(status_code=500, detail=f'signin_file format error. "client_id" not found in "azure". ({signin_file})')
|
|
447
|
+
if 'client_secret' not in yml['oauth2']['providers']['azure']:
|
|
448
|
+
raise HTTPException(status_code=500, detail=f'signin_file format error. "client_secret" not found in "azure". ({signin_file})')
|
|
449
|
+
if 'redirect_uri' not in yml['oauth2']['providers']['azure']:
|
|
450
|
+
raise HTTPException(status_code=500, detail=f'signin_file format error. "redirect_uri" not found in "azure". ({signin_file})')
|
|
451
|
+
if 'scope' not in yml['oauth2']['providers']['azure']:
|
|
452
|
+
raise HTTPException(status_code=500, detail=f'signin_file format error. "scope" not found in "azure". ({signin_file})')
|
|
453
|
+
if type(yml['oauth2']['providers']['azure']['scope']) is not list:
|
|
454
|
+
raise HTTPException(status_code=500, detail=f'signin_file format error. "scope" not list type in "azure". ({signin_file})')
|
|
455
|
+
if 'signin_module' not in yml['oauth2']['providers']['azure']:
|
|
456
|
+
raise HTTPException(status_code=500, detail=f'signin_file format error. "signin_module" not found in "azure". ({signin_file})')
|
|
457
|
+
# samlのフォーマットチェック
|
|
458
|
+
if 'saml' not in yml:
|
|
459
|
+
raise HTTPException(status_code=500, detail=f'signin_file format error. "saml" not found. ({signin_file})')
|
|
460
|
+
if 'providers' not in yml['saml']:
|
|
461
|
+
raise HTTPException(status_code=500, detail=f'signin_file format error. "providers" not found in "saml". ({signin_file})')
|
|
462
|
+
# azure
|
|
463
|
+
if 'azure' not in yml['saml']['providers']:
|
|
464
|
+
raise HTTPException(status_code=500, detail=f'signin_file format error. "azure" not found in "providers". ({signin_file})')
|
|
465
|
+
if 'enabled' not in yml['saml']['providers']['azure']:
|
|
466
|
+
raise HTTPException(status_code=500, detail=f'signin_file format error. "enabled" not found in "azure". ({signin_file})')
|
|
467
|
+
if type(yml['saml']['providers']['azure']['enabled']) is not bool:
|
|
468
|
+
raise HTTPException(status_code=500, detail=f'signin_file format error. "enabled" not bool type in "azure". ({signin_file})')
|
|
469
|
+
if 'signin_module' not in yml['saml']['providers']['azure']:
|
|
470
|
+
raise HTTPException(status_code=500, detail=f'signin_file format error. "signin_module" not found in "azure". ({signin_file})')
|
|
471
|
+
if 'sp' not in yml['saml']['providers']['azure']:
|
|
472
|
+
raise HTTPException(status_code=500, detail=f'signin_file format error. "sp" not found in "azure". ({signin_file})')
|
|
473
|
+
if 'idp' not in yml['saml']['providers']['azure']:
|
|
474
|
+
raise HTTPException(status_code=500, detail=f'signin_file format error. "idp" not found in "azure". ({signin_file})')
|
|
475
|
+
# フォーマットチェックOK
|
|
476
|
+
return yml
|
|
421
477
|
|
|
422
478
|
@classmethod
|
|
423
479
|
def correct_group(cls, signin_file_data:Dict[str, Any], group_names:List[str], master_groups:List[Dict[str, Any]]) -> List[str]:
|
|
@@ -448,26 +504,42 @@ class Signin(object):
|
|
|
448
504
|
Returns:
|
|
449
505
|
Union[None, RedirectResponse]: 認可された場合はNone、認可されなかった場合はリダイレクトレスポンス
|
|
450
506
|
"""
|
|
451
|
-
|
|
507
|
+
return Signin._check_path(req, path, self.signin_file_data, self.logger)
|
|
508
|
+
|
|
509
|
+
@classmethod
|
|
510
|
+
def _check_path(cls, req:Request, path:str, signin_file_data:Dict[str, Any], logger:logging.Logger) -> Union[None, RedirectResponse]:
|
|
511
|
+
"""
|
|
512
|
+
パスの認可をチェックします
|
|
513
|
+
|
|
514
|
+
Args:
|
|
515
|
+
req (Request): リクエスト
|
|
516
|
+
path (str): パス
|
|
517
|
+
signin_file_data (Dict[str, Any]): サインインファイルデータ
|
|
518
|
+
logger (logging.Logger): ロガー
|
|
519
|
+
|
|
520
|
+
Returns:
|
|
521
|
+
Union[None, RedirectResponse]: 認可された場合はNone、認可されなかった場合はリダイレクトレスポンス
|
|
522
|
+
"""
|
|
523
|
+
if signin_file_data is None:
|
|
452
524
|
return None
|
|
453
525
|
if 'signin' not in req.session:
|
|
454
526
|
return None
|
|
455
527
|
path = path if path.startswith('/') else f'/{path}'
|
|
456
528
|
# パスルールチェック
|
|
457
529
|
user_groups = req.session['signin']['groups']
|
|
458
|
-
jadge =
|
|
459
|
-
for rule in
|
|
530
|
+
jadge = signin_file_data['pathrule']['policy']
|
|
531
|
+
for rule in signin_file_data['pathrule']['rules']:
|
|
460
532
|
if len([g for g in rule['groups'] if g in user_groups]) <= 0:
|
|
461
533
|
continue
|
|
462
534
|
if len([p for p in rule['paths'] if path.startswith(p)]) <= 0:
|
|
463
535
|
continue
|
|
464
536
|
jadge = rule['rule']
|
|
465
|
-
if
|
|
466
|
-
|
|
537
|
+
if logger.level == logging.DEBUG:
|
|
538
|
+
logger.debug(f"rule: {path}: {jadge}")
|
|
467
539
|
if jadge == 'allow':
|
|
468
540
|
return None
|
|
469
541
|
else:
|
|
470
|
-
|
|
542
|
+
logger.warning(f"Unauthorized site. user={req.session['signin']['name']}, path={path}")
|
|
471
543
|
return RedirectResponse(url=f'/signin{path}?error=unauthorizedsite')
|
|
472
544
|
|
|
473
545
|
def check_cmd(self, req:Request, res:Response, mode:str, cmd:str):
|
|
@@ -487,10 +559,57 @@ class Signin(object):
|
|
|
487
559
|
return True
|
|
488
560
|
if 'signin' not in req.session or 'groups' not in req.session['signin']:
|
|
489
561
|
return False
|
|
562
|
+
return Signin._check_cmd(self.signin_file_data, req.session['signin']['groups'], mode, cmd, self.logger)
|
|
563
|
+
|
|
564
|
+
@classmethod
|
|
565
|
+
def load_groups(cls, signin_file_data:Dict[str, Any], apikey:str, logger:logging.Logger):
|
|
566
|
+
"""
|
|
567
|
+
APIキーからユーザグループを取得します
|
|
568
|
+
Args:
|
|
569
|
+
signin_file_data (Dict[str, Any]): サインインファイルデータ
|
|
570
|
+
apikey (str): APIキー
|
|
571
|
+
logger (logging.Logger): ロガー
|
|
572
|
+
Returns:
|
|
573
|
+
Dict[str, Any]: ユーザグループの情報
|
|
574
|
+
"""
|
|
575
|
+
apikey = common.hash_password(apikey.strip(), 'sha1')
|
|
576
|
+
if logger.level == logging.DEBUG:
|
|
577
|
+
logger.debug(f"hashed apikey: {apikey}")
|
|
578
|
+
find_user = None
|
|
579
|
+
for user in signin_file_data['users']:
|
|
580
|
+
if 'apikeys' not in user:
|
|
581
|
+
continue
|
|
582
|
+
for ak, key in user['apikeys'].items():
|
|
583
|
+
if apikey == key:
|
|
584
|
+
find_user = user
|
|
585
|
+
if find_user is None:
|
|
586
|
+
logger.warning(f"No matching user found for apikey.")
|
|
587
|
+
return dict(warn='No matching user found for apikey.')
|
|
588
|
+
|
|
589
|
+
group_names = list(set(Signin.correct_group(signin_file_data, find_user['groups'], None)))
|
|
590
|
+
return dict(success=group_names)
|
|
591
|
+
|
|
592
|
+
@classmethod
|
|
593
|
+
def _check_cmd(cls, signin_file_data:Dict[str, Any], user_groups:List[str], mode:str, cmd:str, logger:logging.Logger) -> bool:
|
|
594
|
+
"""
|
|
595
|
+
コマンドの認可をチェックします
|
|
596
|
+
|
|
597
|
+
Args:
|
|
598
|
+
signin_file_data (Dict[str, Any]): サインインファイルデータ
|
|
599
|
+
user_groups (List[str]): ユーザグループ
|
|
600
|
+
mode (str): モード
|
|
601
|
+
cmd (str): コマンド
|
|
602
|
+
|
|
603
|
+
Returns:
|
|
604
|
+
bool: 認可されたかどうか
|
|
605
|
+
"""
|
|
606
|
+
if signin_file_data is None:
|
|
607
|
+
return True
|
|
608
|
+
if user_groups is None or len(user_groups) <= 0:
|
|
609
|
+
return False
|
|
490
610
|
# コマンドチェック
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
for rule in self.signin_file_data['cmdrule']['rules']:
|
|
611
|
+
jadge = signin_file_data['cmdrule']['policy']
|
|
612
|
+
for rule in signin_file_data['cmdrule']['rules']:
|
|
494
613
|
if len([g for g in rule['groups'] if g in user_groups]) <= 0:
|
|
495
614
|
continue
|
|
496
615
|
if rule['mode'] is not None:
|
|
@@ -499,8 +618,8 @@ class Signin(object):
|
|
|
499
618
|
if len([c for c in rule['cmds'] if cmd == c]) <= 0:
|
|
500
619
|
continue
|
|
501
620
|
jadge = rule['rule']
|
|
502
|
-
if
|
|
503
|
-
|
|
621
|
+
if logger.level == logging.DEBUG:
|
|
622
|
+
logger.debug(f"rule: mode={mode}, cmd={cmd}: {jadge}")
|
|
504
623
|
return jadge == 'allow'
|
|
505
624
|
|
|
506
625
|
def get_enable_modes(self, req:Request, res:Response) -> List[str]:
|
|
@@ -632,3 +751,87 @@ class Signin(object):
|
|
|
632
751
|
return False, f"Password policy error. not_contain_username=True"
|
|
633
752
|
self.logger.info(f"Password policy OK.")
|
|
634
753
|
return True, "Password policy OK."
|
|
754
|
+
|
|
755
|
+
def request_access_token(self, conf:Dict, req:Request, res:Response) -> str:
|
|
756
|
+
"""
|
|
757
|
+
アクセストークンを取得します
|
|
758
|
+
|
|
759
|
+
Args:
|
|
760
|
+
conf (Dict): サインインモジュールの設定
|
|
761
|
+
req (Request): リクエスト
|
|
762
|
+
res (Response): レスポンス
|
|
763
|
+
|
|
764
|
+
Returns:
|
|
765
|
+
str: アクセストークン
|
|
766
|
+
"""
|
|
767
|
+
raise NotImplementedError("request_access_token() is not implemented.")
|
|
768
|
+
|
|
769
|
+
def get_email(self, data:Any) -> str:
|
|
770
|
+
"""
|
|
771
|
+
アクセストークンからメールアドレスを取得します
|
|
772
|
+
|
|
773
|
+
Args:
|
|
774
|
+
data (str): アクセストークン又は属性データ
|
|
775
|
+
|
|
776
|
+
Returns:
|
|
777
|
+
str: メールアドレス
|
|
778
|
+
"""
|
|
779
|
+
return self.__class__.get_email(data)
|
|
780
|
+
|
|
781
|
+
|
|
782
|
+
request_scope = contextvars.ContextVar('request_scope', default=None)
|
|
783
|
+
|
|
784
|
+
async def create_request_scope(req:Request=None, res:Response=None, websocket:WebSocket=None):
|
|
785
|
+
"""
|
|
786
|
+
FastAPIのDepends用に、ContextVarを使用してリクエストスコープを提供します。
|
|
787
|
+
これにより、リクエストごとに異なるRequestオブジェクトを取得できます。
|
|
788
|
+
これは、FastAPIのDependsで使用されることを意図しています。
|
|
789
|
+
次のように使用します。
|
|
790
|
+
|
|
791
|
+
Example:
|
|
792
|
+
|
|
793
|
+
::
|
|
794
|
+
|
|
795
|
+
from cmdbox.app.auth import signin
|
|
796
|
+
from fastapi import Depends, Request, Response
|
|
797
|
+
|
|
798
|
+
@app.get("/some-endpoint")
|
|
799
|
+
async def some_endpoint(req: Request, res: Response, scope=Depends(signin.create_request_scope)):
|
|
800
|
+
# 何らかの処理
|
|
801
|
+
|
|
802
|
+
Args:
|
|
803
|
+
req (Request): リクエスト
|
|
804
|
+
res (Response): レスポンス
|
|
805
|
+
websocket (WebSocket): WebSocket接続
|
|
806
|
+
"""
|
|
807
|
+
sess = None
|
|
808
|
+
if req is not None:
|
|
809
|
+
sess = req.session if hasattr(req, 'session') else None
|
|
810
|
+
request_scope.set(dict(req=req, res=res, websocket=websocket))
|
|
811
|
+
try:
|
|
812
|
+
yield # リクエストの処理
|
|
813
|
+
finally:
|
|
814
|
+
# リクエストの処理が終わったら、ContextVarをクリアします
|
|
815
|
+
request_scope.set(None)
|
|
816
|
+
|
|
817
|
+
def get_request_scope() -> Dict[str, Any]:
|
|
818
|
+
"""
|
|
819
|
+
FastAPIのDepends用に、ContextVarからリクエストスコープを取得します。
|
|
820
|
+
|
|
821
|
+
Example:
|
|
822
|
+
|
|
823
|
+
::
|
|
824
|
+
|
|
825
|
+
from cmdbox.app.auth import signin
|
|
826
|
+
from fastapi import Request, Response
|
|
827
|
+
scope = signin.get_request_scope()
|
|
828
|
+
scope['req'] # Requestオブジェクト
|
|
829
|
+
scope['res'] # Responseオブジェクト
|
|
830
|
+
scope['session'] # sessionを表す辞書
|
|
831
|
+
scope['websocket'] # WebSocket接続
|
|
832
|
+
scope['logger'] # loggerオブジェクト
|
|
833
|
+
|
|
834
|
+
Returns:
|
|
835
|
+
Dict[str, Any]: リクエストとレスポンスとWebSocket接続
|
|
836
|
+
"""
|
|
837
|
+
return request_scope.get() if request_scope.get() is not None else dict(req=None, res=None, session=None, websocket=None)
|