cmdbox 0.5.4__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.

Files changed (143) hide show
  1. cmdbox/app/auth/signin.py +463 -303
  2. cmdbox/app/common.py +48 -3
  3. cmdbox/app/edge.py +5 -173
  4. cmdbox/app/edge_tool.py +177 -0
  5. cmdbox/app/feature.py +10 -9
  6. cmdbox/app/features/cli/agent_base.py +477 -0
  7. cmdbox/app/features/cli/audit_base.py +1 -1
  8. cmdbox/app/features/cli/cmdbox_audit_search.py +24 -1
  9. cmdbox/app/features/cli/cmdbox_client_file_download.py +1 -1
  10. cmdbox/app/features/cli/cmdbox_cmd_list.py +105 -0
  11. cmdbox/app/features/cli/cmdbox_cmd_load.py +104 -0
  12. cmdbox/app/features/cli/cmdbox_edge_config.py +2 -2
  13. cmdbox/app/features/cli/cmdbox_edge_start.py +1 -1
  14. cmdbox/app/features/cli/cmdbox_gui_start.py +9 -132
  15. cmdbox/app/features/cli/cmdbox_gui_stop.py +4 -21
  16. cmdbox/app/features/cli/cmdbox_server_start.py +1 -1
  17. cmdbox/app/features/cli/cmdbox_web_apikey_add.py +1 -1
  18. cmdbox/app/features/cli/cmdbox_web_apikey_del.py +1 -1
  19. cmdbox/app/features/cli/cmdbox_web_genpass.py +0 -3
  20. cmdbox/app/features/cli/cmdbox_web_group_add.py +1 -1
  21. cmdbox/app/features/cli/cmdbox_web_group_del.py +1 -1
  22. cmdbox/app/features/cli/cmdbox_web_group_edit.py +1 -1
  23. cmdbox/app/features/cli/cmdbox_web_group_list.py +1 -1
  24. cmdbox/app/features/cli/cmdbox_web_start.py +119 -104
  25. cmdbox/app/features/cli/cmdbox_web_stop.py +1 -1
  26. cmdbox/app/features/cli/cmdbox_web_user_add.py +1 -1
  27. cmdbox/app/features/cli/cmdbox_web_user_del.py +1 -1
  28. cmdbox/app/features/cli/cmdbox_web_user_edit.py +1 -1
  29. cmdbox/app/features/cli/cmdbox_web_user_list.py +1 -1
  30. cmdbox/app/features/web/cmdbox_web_agent.py +250 -0
  31. cmdbox/app/features/web/cmdbox_web_exec_cmd.py +8 -3
  32. cmdbox/app/features/web/cmdbox_web_signin.py +3 -3
  33. cmdbox/app/features/web/cmdbox_web_users.py +2 -0
  34. cmdbox/app/options.py +55 -2
  35. cmdbox/app/web.py +142 -15
  36. cmdbox/extensions/features.yml +18 -0
  37. cmdbox/extensions/sample_project/sample/app/features/cli/__init__.py +0 -0
  38. cmdbox/extensions/sample_project/sample/app/features/web/__init__.py +0 -0
  39. cmdbox/extensions/user_list.yml +1 -0
  40. cmdbox/licenses/LICENSE.Authlib.1.5.2(BSD License).txt +29 -0
  41. cmdbox/licenses/LICENSE.Deprecated.1.2.18(MIT License).txt +21 -0
  42. cmdbox/licenses/LICENSE.SQLAlchemy.2.0.40(MIT License).txt +19 -0
  43. cmdbox/licenses/LICENSE.aiohappyeyeballs.2.6.1(Python Software Foundation License).txt +279 -0
  44. cmdbox/licenses/LICENSE.aiohttp.3.11.18(Apache Software License).txt +13 -0
  45. cmdbox/licenses/LICENSE.aiosignal.1.3.2(Apache Software License).txt +201 -0
  46. cmdbox/licenses/LICENSE.attrs.25.3.0(UNKNOWN).txt +21 -0
  47. cmdbox/licenses/LICENSE.cachetools.5.5.2(MIT License).txt +20 -0
  48. cmdbox/licenses/LICENSE.distro.1.9.0(Apache Software License).txt +202 -0
  49. cmdbox/licenses/LICENSE.docstring_parser.0.16(MIT License).txt +21 -0
  50. cmdbox/licenses/LICENSE.filelock.3.18.0(The Unlicense (Unlicense)).txt +24 -0
  51. cmdbox/licenses/LICENSE.frozenlist.1.6.0(Apache-2.0).txt +201 -0
  52. cmdbox/licenses/LICENSE.fsspec.2025.3.2(BSD License).txt +29 -0
  53. cmdbox/licenses/LICENSE.google-adk.0.5.0(Apache Software License).txt +202 -0
  54. cmdbox/licenses/LICENSE.google-api-python-client.2.169.0(Apache Software License).txt +201 -0
  55. cmdbox/licenses/LICENSE.google-auth-httplib2.0.2.0(Apache Software License).txt +201 -0
  56. cmdbox/licenses/LICENSE.google-auth.2.40.1(Apache Software License).txt +201 -0
  57. cmdbox/licenses/LICENSE.google-cloud-aiplatform.1.92.0(Apache 2.0).txt +202 -0
  58. cmdbox/licenses/LICENSE.google-cloud-bigquery.3.31.0(Apache Software License).txt +202 -0
  59. cmdbox/licenses/LICENSE.google-cloud-core.2.4.3(Apache Software License).txt +202 -0
  60. cmdbox/licenses/LICENSE.google-cloud-resource-manager.1.14.2(Apache Software License).txt +202 -0
  61. cmdbox/licenses/LICENSE.google-cloud-secret-manager.2.23.3(Apache Software License).txt +202 -0
  62. cmdbox/licenses/LICENSE.google-cloud-speech.2.32.0(Apache Software License).txt +202 -0
  63. cmdbox/licenses/LICENSE.google-cloud-storage.2.19.0(Apache Software License).txt +202 -0
  64. cmdbox/licenses/LICENSE.google-cloud-trace.1.16.1(Apache Software License).txt +202 -0
  65. cmdbox/licenses/LICENSE.google-crc32c.1.7.1(Apache 2.0).txt +202 -0
  66. cmdbox/licenses/LICENSE.google-genai.1.14.0(Apache Software License).txt +202 -0
  67. cmdbox/licenses/LICENSE.google-resumable-media.2.7.2(Apache Software License).txt +202 -0
  68. cmdbox/licenses/LICENSE.googleapis-common-protos.1.70.0(Apache Software License).txt +202 -0
  69. cmdbox/licenses/LICENSE.graphviz.0.20.3(MIT License).txt +21 -0
  70. cmdbox/licenses/LICENSE.grpc-google-iam-v1.0.14.2(Apache Software License).txt +202 -0
  71. cmdbox/licenses/LICENSE.grpcio-status.1.71.0(Apache Software License).txt +610 -0
  72. cmdbox/licenses/LICENSE.grpcio.1.71.0(Apache Software License).txt +610 -0
  73. cmdbox/licenses/LICENSE.httpcore.1.0.9(BSD License).txt +27 -0
  74. cmdbox/licenses/LICENSE.httplib2.0.22.0(MIT License).txt +23 -0
  75. cmdbox/licenses/LICENSE.httpx-sse.0.4.0(MIT).txt +21 -0
  76. cmdbox/licenses/LICENSE.httpx.0.28.1(BSD License).txt +12 -0
  77. cmdbox/licenses/LICENSE.huggingface-hub.0.31.1(Apache Software License).txt +201 -0
  78. cmdbox/licenses/LICENSE.importlib_metadata.8.6.1(Apache Software License).txt +202 -0
  79. cmdbox/licenses/LICENSE.jiter.0.9.0(MIT License).txt +1 -0
  80. cmdbox/licenses/LICENSE.jsonschema-specifications.2025.4.1(UNKNOWN).txt +19 -0
  81. cmdbox/licenses/LICENSE.jsonschema.4.23.0(MIT License).txt +19 -0
  82. cmdbox/licenses/LICENSE.litellm.1.69.0(MIT License).txt +26 -0
  83. cmdbox/licenses/LICENSE.mcp.1.8.0(MIT License).txt +21 -0
  84. cmdbox/licenses/LICENSE.multidict.6.4.3(Apache Software License).txt +13 -0
  85. cmdbox/licenses/LICENSE.openai.1.75.0(Apache Software License).txt +201 -0
  86. cmdbox/licenses/LICENSE.opentelemetry-api.1.33.0(Apache Software License).txt +201 -0
  87. cmdbox/licenses/LICENSE.opentelemetry-exporter-gcp-trace.1.9.0(Apache Software License).txt +201 -0
  88. cmdbox/licenses/LICENSE.opentelemetry-resourcedetector-gcp.1.9.0a0(Apache Software License).txt +201 -0
  89. cmdbox/licenses/LICENSE.opentelemetry-sdk.1.33.0(Apache Software License).txt +201 -0
  90. cmdbox/licenses/LICENSE.opentelemetry-semantic-conventions.0.54b0(Apache Software License).txt +201 -0
  91. cmdbox/licenses/LICENSE.propcache.0.3.1(Apache Software License).txt +202 -0
  92. cmdbox/licenses/LICENSE.proto-plus.1.26.1(Apache Software License).txt +202 -0
  93. cmdbox/licenses/LICENSE.protobuf.5.29.4(3-Clause BSD License).txt +32 -0
  94. cmdbox/licenses/LICENSE.pyasn1.0.6.1(BSD License).txt +24 -0
  95. cmdbox/licenses/LICENSE.pyasn1_modules.0.4.2(BSD License).txt +24 -0
  96. cmdbox/licenses/LICENSE.pydantic-settings.2.9.1(MIT License).txt +21 -0
  97. cmdbox/licenses/LICENSE.pyparsing.3.2.3(MIT License).txt +18 -0
  98. cmdbox/licenses/LICENSE.python-dateutil.2.9.0.post0(Apache Software License; BSD License).txt +54 -0
  99. cmdbox/licenses/LICENSE.referencing.0.36.2(UNKNOWN).txt +19 -0
  100. cmdbox/licenses/LICENSE.regex.2024.11.6(Apache Software License).txt +208 -0
  101. cmdbox/licenses/LICENSE.rpds-py.0.24.0(MIT).txt +19 -0
  102. cmdbox/licenses/LICENSE.rsa.4.9.1(Apache Software License).txt +13 -0
  103. cmdbox/licenses/LICENSE.shapely.2.1.0(BSD License).txt +29 -0
  104. cmdbox/licenses/LICENSE.sse-starlette.2.3.4(BSD License).txt +27 -0
  105. cmdbox/licenses/LICENSE.tiktoken.0.9.0(MIT License).txt +21 -0
  106. cmdbox/licenses/LICENSE.tokenizers.0.21.1(Apache Software License).txt +1 -0
  107. cmdbox/licenses/LICENSE.tqdm.4.67.1(MIT License; Mozilla Public License 2.0 (MPL 2.0)).txt +49 -0
  108. cmdbox/licenses/LICENSE.tzlocal.5.3.1(MIT License).txt +19 -0
  109. cmdbox/licenses/LICENSE.uritemplate.4.1.1(Apache Software License; BSD License).txt +3 -0
  110. cmdbox/licenses/LICENSE.wrapt.1.17.2(BSD License).txt +24 -0
  111. cmdbox/licenses/LICENSE.yarl.1.20.0(Apache Software License).txt +202 -0
  112. cmdbox/licenses/files.txt +104 -11
  113. cmdbox/logconf_agent.yml +38 -0
  114. cmdbox/logconf_audit.yml +13 -5
  115. cmdbox/logconf_client.yml +13 -5
  116. cmdbox/logconf_cmdbox.yml +13 -5
  117. cmdbox/logconf_edge.yml +13 -5
  118. cmdbox/logconf_gui.yml +13 -5
  119. cmdbox/logconf_server.yml +13 -5
  120. cmdbox/logconf_web.yml +13 -5
  121. cmdbox/version.py +3 -2
  122. cmdbox/web/agent.html +263 -0
  123. cmdbox/web/assets/cmdbox/agent.js +335 -0
  124. cmdbox/web/assets/cmdbox/common.js +1111 -1020
  125. cmdbox/web/assets/cmdbox/signin.js +4 -4
  126. cmdbox/web/assets/filer/filer.js +4 -2
  127. {cmdbox-0.5.4.dist-info → cmdbox-0.6.0.dist-info}/METADATA +69 -26
  128. {cmdbox-0.5.4.dist-info → cmdbox-0.6.0.dist-info}/RECORD +143 -61
  129. /cmdbox/licenses/{LICENSE.charset-normalizer.3.4.1(MIT License).txt → LICENSE.charset-normalizer.3.4.2(MIT License).txt} +0 -0
  130. /cmdbox/licenses/{LICENSE.click.8.1.8(BSD License).txt → LICENSE.click.8.2.0(UNKNOWN).txt} +0 -0
  131. /cmdbox/licenses/{LICENSE.cryptography.44.0.2(Apache Software License; BSD License).txt → LICENSE.cryptography.44.0.3(Apache Software License; BSD License).txt} +0 -0
  132. /cmdbox/licenses/{LICENSE.importlib_metadata.8.7.0(Apache Software License).txt → LICENSE.google-api-core.2.24.2(Apache Software License).txt} +0 -0
  133. /cmdbox/licenses/{LICENSE.greenlet.3.2.1(MIT AND Python-2.0).txt → LICENSE.greenlet.3.2.2(MIT AND Python-2.0).txt} +0 -0
  134. /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
  135. /cmdbox/licenses/{LICENSE.psycopg.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
  136. /cmdbox/licenses/{LICENSE.pydantic.2.11.3(MIT License).txt → LICENSE.pydantic.2.11.4(MIT License).txt} +0 -0
  137. /cmdbox/licenses/{LICENSE.pydantic_core.2.33.1(MIT License).txt → LICENSE.pydantic_core.2.33.2(MIT License).txt} +0 -0
  138. /cmdbox/licenses/{LICENSE.redis.5.2.1(MIT License).txt → LICENSE.redis.6.0.0(MIT License).txt} +0 -0
  139. /cmdbox/licenses/{LICENSE.snowballstemmer.2.2.0(BSD License).txt → LICENSE.snowballstemmer.3.0.1(BSD License).txt} +0 -0
  140. {cmdbox-0.5.4.dist-info → cmdbox-0.6.0.dist-info}/LICENSE +0 -0
  141. {cmdbox-0.5.4.dist-info → cmdbox-0.6.0.dist-info}/WHEEL +0 -0
  142. {cmdbox-0.5.4.dist-info → cmdbox-0.6.0.dist-info}/entry_points.txt +0 -0
  143. {cmdbox-0.5.4.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
 
@@ -59,7 +60,8 @@ class Signin(object):
59
60
  gids = [g['gid'] for g in copy_signin_data['groups'] if g['name'] in group_names]
60
61
  return group_names, gids
61
62
 
62
- def enable_cors(self, req:Request, res:Response) -> None:
63
+ @classmethod
64
+ def _enable_cors(cls, req:Request, res:Response) -> None:
63
65
  """
64
66
  CORSを有効にする
65
67
 
@@ -71,7 +73,7 @@ class Signin(object):
71
73
  return
72
74
  res.headers['Access-Control-Allow-Origin'] = res.headers['Origin']
73
75
 
74
- def check_signin(self, req:Request, res:Response):
76
+ def check_signin(self, req:Request, res:Response) -> Union[None, RedirectResponse]:
75
77
  """
76
78
  サインインをチェックする
77
79
 
@@ -80,24 +82,44 @@ class Signin(object):
80
82
  res (Response): レスポンス
81
83
 
82
84
  Returns:
83
- Response: サインインエラーの場合はリダイレクトレスポンス
85
+ Union[None, RedirectResponse]: サインインエラーの場合はリダイレクトレスポンス
84
86
  """
85
- self.enable_cors(req, res)
86
87
  if self.signin_file_data is None:
87
88
  return None
88
89
  if 'signin' in req.session:
89
- self.signin_file_data = self.load_signin_file(self.signin_file, self.signin_file_data) # サインインファイルの更新をチェック
90
- path_jadge = self.check_path(req, req.url.path)
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)
91
112
  if path_jadge is not None:
92
113
  return path_jadge
93
114
  return None
94
- self.logger.info(f"Not found siginin session. Try check_apikey. path={req.url.path}")
95
- ret = self.check_apikey(req, res)
96
- if ret is not None and self.logger.level == logging.DEBUG:
97
- self.logger.debug(f"Not signed in.")
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.")
98
120
  return ret
99
121
 
100
- def check_apikey(self, req:Request, res:Response):
122
+ def check_apikey(self, req:Request, res:Response) -> Union[None, RedirectResponse]:
101
123
  """
102
124
  ApiKeyをチェックする
103
125
 
@@ -106,56 +128,71 @@ class Signin(object):
106
128
  res (Response): レスポンス
107
129
 
108
130
  Returns:
109
- Response: サインインエラーの場合はリダイレクトレスポンス
131
+ Union[None, RedirectResponse]: サインインエラーの場合はリダイレクトレスポンス
110
132
  """
111
- self.enable_cors(req, res)
112
- if self.signin_file_data is None:
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:
113
151
  res.headers['signin'] = 'success'
114
152
  return None
115
153
  if 'Authorization' not in req.headers:
116
- self.logger.warning(f"Authorization not found. headers={req.headers}")
154
+ #self.logger.warning(f"Authorization not found. headers={req.headers}")
117
155
  return RedirectResponse(url=f'/signin{req.url.path}?error=noauth')
118
156
  auth = req.headers['Authorization']
119
157
  if not auth.startswith('Bearer '):
120
- self.logger.warning(f"Bearer not found. headers={req.headers}")
158
+ #self.logger.warning(f"Bearer not found. headers={req.headers}")
121
159
  return RedirectResponse(url=f'/signin{req.url.path}?error=apikeyfail')
122
160
  bearer, apikey = auth.split(' ')
123
161
  apikey = common.hash_password(apikey.strip(), 'sha1')
124
- if self.logger.level == logging.DEBUG:
125
- self.logger.debug(f"hashed apikey: {apikey}")
162
+ if logger.level == logging.DEBUG:
163
+ logger.debug(f"hashed apikey: {apikey}")
126
164
  find_user = None
127
- self.signin_file_data = self.load_signin_file(self.signin_file, self.signin_file_data) # サインインファイルの更新をチェック
128
- for user in self.signin_file_data['users']:
165
+ for user in signin_file_data['users']:
129
166
  if 'apikeys' not in user:
130
167
  continue
131
168
  for ak, key in user['apikeys'].items():
132
169
  if apikey == key:
133
170
  find_user = user
134
171
  if find_user is None:
135
- self.logger.warning(f"No matching user found for apikey.")
172
+ logger.warning(f"No matching user found for apikey.")
136
173
  return RedirectResponse(url=f'/signin{req.url.path}?error=apikeyfail')
137
174
 
138
- group_names = list(set(self.__class__.correct_group(self.get_data(), find_user['groups'], None)))
139
- gids = [g['gid'] for g in self.signin_file_data['groups'] if g['name'] in group_names]
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]
140
177
  req.session['signin'] = dict(uid=find_user['uid'], name=find_user['name'], password=find_user['password'],
141
178
  gids=gids, groups=group_names)
142
- if self.logger.level == logging.DEBUG:
143
- self.logger.debug(f"find user: name={find_user['name']}, group_names={group_names}")
179
+ if logger.level == logging.DEBUG:
180
+ logger.debug(f"find user: name={find_user['name']}, group_names={group_names}")
144
181
  # パスルールチェック
145
182
  user_groups = find_user['groups']
146
- jadge = self.signin_file_data['pathrule']['policy']
147
- for rule in self.signin_file_data['pathrule']['rules']:
183
+ jadge = signin_file_data['pathrule']['policy']
184
+ for rule in signin_file_data['pathrule']['rules']:
148
185
  if len([g for g in rule['groups'] if g in user_groups]) <= 0:
149
186
  continue
150
187
  if len([p for p in rule['paths'] if req.url.path.startswith(p)]) <= 0:
151
188
  continue
152
189
  jadge = rule['rule']
153
- if self.logger.level == logging.DEBUG:
154
- self.logger.debug(f"rule: {req.url.path}: {jadge}")
190
+ if logger.level == logging.DEBUG:
191
+ logger.debug(f"rule: {req.url.path}: {jadge}")
155
192
  if jadge == 'allow':
156
193
  res.headers['signin'] = 'success'
157
194
  return None
158
- self.logger.warning(f"Unauthorized site. user={find_user['name']}, path={req.url.path}")
195
+ logger.warning(f"Unauthorized site. user={find_user['name']}, path={req.url.path}")
159
196
  return RedirectResponse(url=f'/signin{req.url.path}?error=unauthorizedsite')
160
197
 
161
198
  @classmethod
@@ -173,268 +210,270 @@ class Signin(object):
173
210
  Returns:
174
211
  Dict[str, Any]: サインインファイルデータ
175
212
  """
176
- if signin_file is not None:
177
- if not signin_file.is_file():
178
- raise HTTPException(status_code=500, detail=f'signin_file is not found. ({signin_file})')
179
- # サインインファイル読込み済みなら返すが、別プロセスがサインインファイルを更新していたら読込みを実施する。
180
- if not hasattr(cls, 'signin_file_last'):
181
- cls.signin_file_last = signin_file.stat().st_mtime
182
- if cls.signin_file_last >= signin_file.stat().st_mtime and signin_file_data is not None:
183
- 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'):
184
220
  cls.signin_file_last = signin_file.stat().st_mtime
185
- yml = common.load_yml(signin_file)
186
- # usersのフォーマットチェック
187
- if 'users' not in yml:
188
- raise HTTPException(status_code=500, detail=f'signin_file format error. "users" not found. ({signin_file})')
189
- uids = set()
190
- unames = set()
191
- groups = [g['name'] for g in yml['groups']]
192
- for user in yml['users']:
193
- if 'uid' not in user or user['uid'] is None:
194
- raise HTTPException(status_code=500, detail=f'signin_file format error. "uid" not found or empty. ({signin_file})')
195
- if user['uid'] in uids:
196
- raise HTTPException(status_code=500, detail=f'signin_file format error. Duplicate uid found. ({signin_file}). uid={user["uid"]}')
197
- if 'name' not in user or user['name'] is None:
198
- raise HTTPException(status_code=500, detail=f'signin_file format error. "name" not found or empty. ({signin_file})')
199
- if user['name'] in unames:
200
- raise HTTPException(status_code=500, detail=f'signin_file format error. Duplicate name found. ({signin_file}). name={user["name"]}')
201
- if 'password' not in user:
202
- raise HTTPException(status_code=500, detail=f'signin_file format error. "password" not found or empty. ({signin_file})')
203
- if 'hash' not in user or user['hash'] is None:
204
- raise HTTPException(status_code=500, detail=f'signin_file format error. "hash" not found or empty. ({signin_file})')
205
- if user['hash'] not in ['oauth2', 'saml', 'plain', 'md5', 'sha1', 'sha256']:
206
- 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.')
207
- if 'groups' not in user or type(user['groups']) is not list:
208
- raise HTTPException(status_code=500, detail=f'signin_file format error. "groups" not found or not list type. ({signin_file})')
209
- if len([ug for ug in user['groups'] if ug not in groups]) > 0:
210
- raise HTTPException(status_code=500, detail=f'signin_file format error. Group not found. ({signin_file}). {user["groups"]}')
211
- uids.add(user['uid'])
212
- unames.add(user['name'])
213
- # groupsのフォーマットチェック
214
- if 'groups' not in yml:
215
- raise HTTPException(status_code=500, detail=f'signin_file format error. "groups" not found. ({signin_file})')
216
- gids = set()
217
- gnames = set()
218
- for group in yml['groups']:
219
- if 'gid' not in group or group['gid'] is None:
220
- raise HTTPException(status_code=500, detail=f'signin_file format error. "gid" not found or empty. ({signin_file})')
221
- if group['gid'] in gids:
222
- raise HTTPException(status_code=500, detail=f'signin_file format error. Duplicate gid found. ({signin_file}). gid={group["gid"]}')
223
- if 'name' not in group or group['name'] is None:
224
- raise HTTPException(status_code=500, detail=f'signin_file format error. "name" not found or empty. ({signin_file})')
225
- if group['name'] in gnames:
226
- raise HTTPException(status_code=500, detail=f'signin_file format error. Duplicate name found. ({signin_file}). name={group["name"]}')
227
- if 'parent' in group:
228
- if group['parent'] not in groups:
229
- raise HTTPException(status_code=500, detail=f'signin_file format error. Parent group not found. ({signin_file}). parent={group["parent"]}')
230
- gids.add(group['gid'])
231
- gnames.add(group['name'])
232
- # cmdruleのフォーマットチェック
233
- if 'cmdrule' not in yml:
234
- raise HTTPException(status_code=500, detail=f'signin_file format error. "cmdrule" not found. ({signin_file})')
235
- if 'policy' not in yml['cmdrule']:
236
- raise HTTPException(status_code=500, detail=f'signin_file format error. "policy" not found in "cmdrule". ({signin_file})')
237
- if yml['cmdrule']['policy'] not in ['allow', 'deny']:
238
- raise HTTPException(status_code=500, detail=f'signin_file format error. "policy" not supported in "cmdrule". ({signin_file}). "allow" or "deny" only.')
239
- if 'rules' not in yml['cmdrule']:
240
- raise HTTPException(status_code=500, detail=f'signin_file format error. "rules" not found in "cmdrule". ({signin_file})')
241
- if type(yml['cmdrule']['rules']) is not list:
242
- raise HTTPException(status_code=500, detail=f'signin_file format error. "rules" not list type in "cmdrule". ({signin_file})')
243
- for rule in yml['cmdrule']['rules']:
244
- if 'groups' not in rule:
245
- raise HTTPException(status_code=500, detail=f'signin_file format error. "groups" not found in "cmdrule.rules" ({signin_file})')
246
- if type(rule['groups']) is not list:
247
- raise HTTPException(status_code=500, detail=f'signin_file format error. "groups" not list type in "cmdrule.rules". ({signin_file})')
248
- rule['groups'] = list(set(copy.deepcopy(cls.correct_group(yml, rule['groups'], yml['groups']))))
249
- if 'rule' not in rule:
250
- raise HTTPException(status_code=500, detail=f'signin_file format error. "rule" not found in "cmdrule.rules" ({signin_file})')
251
- if rule['rule'] not in ['allow', 'deny']:
252
- raise HTTPException(status_code=500, detail=f'signin_file format error. "rule" not supported in "cmdrule.rules". ({signin_file}). "allow" or "deny" only.')
253
- if 'mode' not in rule:
254
- rule['mode'] = None
255
- if 'cmds' not in rule:
256
- rule['cmds'] = []
257
- if rule['mode'] is not None and len(rule['cmds']) <= 0:
258
- raise HTTPException(status_code=500, detail=f'signin_file format error. When “cmds is specified, “mode” must be specified. ({signin_file})')
259
- if type(rule['cmds']) is not list:
260
- raise HTTPException(status_code=500, detail=f'signin_file format error. "cmds" not list type in "cmdrule.rules". ({signin_file})')
261
- # pathruleのフォーマットチェック
262
- if 'pathrule' not in yml:
263
- raise HTTPException(status_code=500, detail=f'signin_file format error. "pathrule" not found. ({signin_file})')
264
- if 'policy' not in yml['pathrule']:
265
- raise HTTPException(status_code=500, detail=f'signin_file format error. "policy" not found in "pathrule". ({signin_file})')
266
- if yml['pathrule']['policy'] not in ['allow', 'deny']:
267
- raise HTTPException(status_code=500, detail=f'signin_file format error. "policy" not supported in "pathrule". ({signin_file}). "allow" or "deny" only.')
268
- if 'rules' not in yml['pathrule']:
269
- raise HTTPException(status_code=500, detail=f'signin_file format error. "rules" not found in "pathrule". ({signin_file})')
270
- if type(yml['pathrule']['rules']) is not list:
271
- raise HTTPException(status_code=500, detail=f'signin_file format error. "rules" not list type in "pathrule". ({signin_file})')
272
- for rule in yml['pathrule']['rules']:
273
- if 'groups' not in rule:
274
- raise HTTPException(status_code=500, detail=f'signin_file format error. "groups" not found in "pathrule.rules" ({signin_file})')
275
- if type(rule['groups']) is not list:
276
- raise HTTPException(status_code=500, detail=f'signin_file format error. "groups" not list type in "pathrule.rules". ({signin_file})')
277
- rule['groups'] = list(set(copy.deepcopy(cls.correct_group(yml, rule['groups'], yml['groups']))))
278
- if 'rule' not in rule:
279
- raise HTTPException(status_code=500, detail=f'signin_file format error. "rule" not found in "pathrule.rules" ({signin_file})')
280
- if rule['rule'] not in ['allow', 'deny']:
281
- raise HTTPException(status_code=500, detail=f'signin_file format error. "rule" not supported in "pathrule.rules". ({signin_file}). "allow" or "deny" only.')
282
- if 'paths' not in rule:
283
- rule['paths'] = []
284
- if type(rule['paths']) is not list:
285
- raise HTTPException(status_code=500, detail=f'signin_file format error. "paths" not list type in "pathrule.rules". ({signin_file})')
286
- # passwordのフォーマットチェック
287
- if 'password' in yml:
288
- if 'policy' not in yml['password']:
289
- raise HTTPException(status_code=500, detail=f'signin_file format error. "policy" not found in "password". ({signin_file})')
290
- if 'enabled' not in yml['password']['policy']:
291
- raise HTTPException(status_code=500, detail=f'signin_file format error. "enabled" not found in "password.policy". ({signin_file})')
292
- if type(yml['password']['policy']['enabled']) is not bool:
293
- raise HTTPException(status_code=500, detail=f'signin_file format error. "enabled" not bool type in "password.policy". ({signin_file})')
294
- if 'not_same_before' not in yml['password']['policy']:
295
- raise HTTPException(status_code=500, detail=f'signin_file format error. "not_same_before" not found in "password.policy". ({signin_file})')
296
- if type(yml['password']['policy']['not_same_before']) is not bool:
297
- raise HTTPException(status_code=500, detail=f'signin_file format error. "not_same_before" not bool type in "password.policy". ({signin_file})')
298
- if 'min_length' not in yml['password']['policy']:
299
- raise HTTPException(status_code=500, detail=f'signin_file format error. "min_length" not found in "password.policy". ({signin_file})')
300
- if type(yml['password']['policy']['min_length']) is not int:
301
- raise HTTPException(status_code=500, detail=f'signin_file format error. "min_length" not int type in "password.policy". ({signin_file})')
302
- if 'max_length' not in yml['password']['policy']:
303
- raise HTTPException(status_code=500, detail=f'signin_file format error. "max_length" not found in "password.policy". ({signin_file})')
304
- if type(yml['password']['policy']['max_length']) is not int:
305
- raise HTTPException(status_code=500, detail=f'signin_file format error. "max_length" not int type in "password.policy". ({signin_file})')
306
- if 'min_lowercase' not in yml['password']['policy']:
307
- raise HTTPException(status_code=500, detail=f'signin_file format error. "min_lowercase" not found in "password.policy". ({signin_file})')
308
- if type(yml['password']['policy']['min_lowercase']) is not int:
309
- raise HTTPException(status_code=500, detail=f'signin_file format error. "min_lowercase" not int type in "password.policy". ({signin_file})')
310
- if 'min_uppercase' not in yml['password']['policy']:
311
- raise HTTPException(status_code=500, detail=f'signin_file format error. "min_uppercase" not found in "password.policy". ({signin_file})')
312
- if type(yml['password']['policy']['min_uppercase']) is not int:
313
- raise HTTPException(status_code=500, detail=f'signin_file format error. "min_uppercase" not int type in "password.policy". ({signin_file})')
314
- if 'min_digit' not in yml['password']['policy']:
315
- raise HTTPException(status_code=500, detail=f'signin_file format error. "min_digit" not found in "password.policy". ({signin_file})')
316
- if type(yml['password']['policy']['min_digit']) is not int:
317
- raise HTTPException(status_code=500, detail=f'signin_file format error. "min_digit" not int type in "password.policy". ({signin_file})')
318
- if 'min_symbol' not in yml['password']['policy']:
319
- raise HTTPException(status_code=500, detail=f'signin_file format error. "min_symbol" not found in "password.policy". ({signin_file})')
320
- if type(yml['password']['policy']['min_symbol']) is not int:
321
- raise HTTPException(status_code=500, detail=f'signin_file format error. "min_symbol" not int type in "password.policy". ({signin_file})')
322
- if 'not_contain_username' not in yml['password']['policy']:
323
- raise HTTPException(status_code=500, detail=f'signin_file format error. "not_contain_username" not found in "password.policy". ({signin_file})')
324
- if type(yml['password']['policy']['not_contain_username']) is not bool:
325
- raise HTTPException(status_code=500, detail=f'signin_file format error. "not_contain_username" not bool type in "password.policy". ({signin_file})')
326
- if 'expiration' not in yml['password']:
327
- raise HTTPException(status_code=500, detail=f'signin_file format error. "expiration" not found in "password". ({signin_file})')
328
- if 'enabled' not in yml['password']['expiration']:
329
- raise HTTPException(status_code=500, detail=f'signin_file format error. "enabled" not found in "password.expiration". ({signin_file})')
330
- if type(yml['password']['expiration']['enabled']) is not bool:
331
- raise HTTPException(status_code=500, detail=f'signin_file format error. "enabled" not bool type in "password.expiration". ({signin_file})')
332
- if 'period' not in yml['password']['expiration']:
333
- raise HTTPException(status_code=500, detail=f'signin_file format error. "period" not found in "password.expiration". ({signin_file})')
334
- if type(yml['password']['expiration']['period']) is not int:
335
- raise HTTPException(status_code=500, detail=f'signin_file format error. "period" not int type in "password.expiration". ({signin_file})')
336
- if 'notify' not in yml['password']['expiration']:
337
- raise HTTPException(status_code=500, detail=f'signin_file format error. "notify" not found in "password.expiration". ({signin_file})')
338
- if type(yml['password']['expiration']['notify']) is not int:
339
- raise HTTPException(status_code=500, detail=f'signin_file format error. "notify" not int type in "password.expiration". ({signin_file})')
340
- if 'lockout' not in yml['password']:
341
- raise HTTPException(status_code=500, detail=f'signin_file format error. "lockout" not found in "password". ({signin_file})')
342
- if 'enabled' not in yml['password']['lockout']:
343
- raise HTTPException(status_code=500, detail=f'signin_file format error. "enabled" not found in "password.lockout". ({signin_file})')
344
- if type(yml['password']['lockout']['enabled']) is not bool:
345
- raise HTTPException(status_code=500, detail=f'signin_file format error. "enabled" not bool type in "password.lockout". ({signin_file})')
346
- if 'threshold' not in yml['password']['lockout']:
347
- raise HTTPException(status_code=500, detail=f'signin_file format error. "threshold" not found in "password.lockout". ({signin_file})')
348
- if type(yml['password']['lockout']['threshold']) is not int:
349
- raise HTTPException(status_code=500, detail=f'signin_file format error. "threshold" not int type in "password.lockout". ({signin_file})')
350
- if 'reset' not in yml['password']['lockout']:
351
- raise HTTPException(status_code=500, detail=f'signin_file format error. "reset" not found in "password.lockout". ({signin_file})')
352
- if type(yml['password']['lockout']['reset']) is not int:
353
- raise HTTPException(status_code=500, detail=f'signin_file format error. "reset" not int type in "password.lockout". ({signin_file})')
354
- # oauth2のフォーマットチェック
355
- if 'oauth2' not in yml:
356
- raise HTTPException(status_code=500, detail=f'signin_file format error. "oauth2" not found. ({signin_file})')
357
- if 'providers' not in yml['oauth2']:
358
- raise HTTPException(status_code=500, detail=f'signin_file format error. "providers" not found in "oauth2". ({signin_file})')
359
- # google
360
- if 'google' not in yml['oauth2']['providers']:
361
- raise HTTPException(status_code=500, detail=f'signin_file format error. "google" not found in "providers". ({signin_file})')
362
- if 'enabled' not in yml['oauth2']['providers']['google']:
363
- raise HTTPException(status_code=500, detail=f'signin_file format error. "enabled" not found in "google". ({signin_file})')
364
- if type(yml['oauth2']['providers']['google']['enabled']) is not bool:
365
- raise HTTPException(status_code=500, detail=f'signin_file format error. "enabled" not bool type in "google". ({signin_file})')
366
- if 'client_id' not in yml['oauth2']['providers']['google']:
367
- raise HTTPException(status_code=500, detail=f'signin_file format error. "client_id" not found in "google". ({signin_file})')
368
- if 'client_secret' not in yml['oauth2']['providers']['google']:
369
- raise HTTPException(status_code=500, detail=f'signin_file format error. "client_secret" not found in "google". ({signin_file})')
370
- if 'redirect_uri' not in yml['oauth2']['providers']['google']:
371
- raise HTTPException(status_code=500, detail=f'signin_file format error. "redirect_uri" not found in "google". ({signin_file})')
372
- if 'scope' not in yml['oauth2']['providers']['google']:
373
- raise HTTPException(status_code=500, detail=f'signin_file format error. "scope" not found in "google". ({signin_file})')
374
- if type(yml['oauth2']['providers']['google']['scope']) is not list:
375
- raise HTTPException(status_code=500, detail=f'signin_file format error. "scope" not list type in "google". ({signin_file})')
376
- if 'signin_module' not in yml['oauth2']['providers']['google']:
377
- raise HTTPException(status_code=500, detail=f'signin_file format error. "signin_module" not found in "google". ({signin_file})')
378
- # github
379
- if 'github' not in yml['oauth2']['providers']:
380
- raise HTTPException(status_code=500, detail=f'signin_file format error. "github" not found in "providers". ({signin_file})')
381
- if 'enabled' not in yml['oauth2']['providers']['github']:
382
- raise HTTPException(status_code=500, detail=f'signin_file format error. "enabled" not found in "github". ({signin_file})')
383
- if type(yml['oauth2']['providers']['github']['enabled']) is not bool:
384
- raise HTTPException(status_code=500, detail=f'signin_file format error. "enabled" not bool type in "github". ({signin_file})')
385
- if 'client_id' not in yml['oauth2']['providers']['github']:
386
- raise HTTPException(status_code=500, detail=f'signin_file format error. "client_id" not found in "github". ({signin_file})')
387
- if 'client_secret' not in yml['oauth2']['providers']['github']:
388
- raise HTTPException(status_code=500, detail=f'signin_file format error. "client_secret" not found in "github". ({signin_file})')
389
- if 'redirect_uri' not in yml['oauth2']['providers']['github']:
390
- raise HTTPException(status_code=500, detail=f'signin_file format error. "redirect_uri" not found in "github". ({signin_file})')
391
- if 'scope' not in yml['oauth2']['providers']['github']:
392
- raise HTTPException(status_code=500, detail=f'signin_file format error. "scope" not found in "github". ({signin_file})')
393
- if type(yml['oauth2']['providers']['github']['scope']) is not list:
394
- raise HTTPException(status_code=500, detail=f'signin_file format error. "scope" not list type in "github". ({signin_file})')
395
- if 'signin_module' not in yml['oauth2']['providers']['github']:
396
- raise HTTPException(status_code=500, detail=f'signin_file format error. "signin_module" not found in "github". ({signin_file})')
397
- # azure
398
- if 'azure' not in yml['oauth2']['providers']:
399
- raise HTTPException(status_code=500, detail=f'signin_file format error. "azure" not found in "providers". ({signin_file})')
400
- if 'enabled' not in yml['oauth2']['providers']['azure']:
401
- raise HTTPException(status_code=500, detail=f'signin_file format error. "enabled" not found in "azure". ({signin_file})')
402
- if type(yml['oauth2']['providers']['azure']['enabled']) is not bool:
403
- raise HTTPException(status_code=500, detail=f'signin_file format error. "enabled" not bool type in "azure". ({signin_file})')
404
- if 'tenant_id' not in yml['oauth2']['providers']['azure']:
405
- raise HTTPException(status_code=500, detail=f'signin_file format error. "tenant_id" not found in "azure". ({signin_file})')
406
- if 'client_id' not in yml['oauth2']['providers']['azure']:
407
- raise HTTPException(status_code=500, detail=f'signin_file format error. "client_id" not found in "azure". ({signin_file})')
408
- if 'client_secret' not in yml['oauth2']['providers']['azure']:
409
- raise HTTPException(status_code=500, detail=f'signin_file format error. "client_secret" not found in "azure". ({signin_file})')
410
- if 'redirect_uri' not in yml['oauth2']['providers']['azure']:
411
- raise HTTPException(status_code=500, detail=f'signin_file format error. "redirect_uri" not found in "azure". ({signin_file})')
412
- if 'scope' not in yml['oauth2']['providers']['azure']:
413
- raise HTTPException(status_code=500, detail=f'signin_file format error. "scope" not found in "azure". ({signin_file})')
414
- if type(yml['oauth2']['providers']['azure']['scope']) is not list:
415
- raise HTTPException(status_code=500, detail=f'signin_file format error. "scope" not list type in "azure". ({signin_file})')
416
- if 'signin_module' not in yml['oauth2']['providers']['azure']:
417
- raise HTTPException(status_code=500, detail=f'signin_file format error. "signin_module" not found in "azure". ({signin_file})')
418
- # samlのフォーマットチェック
419
- if 'saml' not in yml:
420
- raise HTTPException(status_code=500, detail=f'signin_file format error. "saml" not found. ({signin_file})')
421
- if 'providers' not in yml['saml']:
422
- raise HTTPException(status_code=500, detail=f'signin_file format error. "providers" not found in "saml". ({signin_file})')
423
- # azure
424
- if 'azure' not in yml['saml']['providers']:
425
- raise HTTPException(status_code=500, detail=f'signin_file format error. "azure" not found in "providers". ({signin_file})')
426
- if 'enabled' not in yml['saml']['providers']['azure']:
427
- raise HTTPException(status_code=500, detail=f'signin_file format error. "enabled" not found in "azure". ({signin_file})')
428
- if type(yml['saml']['providers']['azure']['enabled']) is not bool:
429
- raise HTTPException(status_code=500, detail=f'signin_file format error. "enabled" not bool type in "azure". ({signin_file})')
430
- if 'signin_module' not in yml['saml']['providers']['azure']:
431
- raise HTTPException(status_code=500, detail=f'signin_file format error. "signin_module" not found in "azure". ({signin_file})')
432
- if 'sp' not in yml['saml']['providers']['azure']:
433
- raise HTTPException(status_code=500, detail=f'signin_file format error. "sp" not found in "azure". ({signin_file})')
434
- if 'idp' not in yml['saml']['providers']['azure']:
435
- raise HTTPException(status_code=500, detail=f'signin_file format error. "idp" not found in "azure". ({signin_file})')
436
- # フォーマットチェックOK
437
- return yml
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
438
477
 
439
478
  @classmethod
440
479
  def correct_group(cls, signin_file_data:Dict[str, Any], group_names:List[str], master_groups:List[Dict[str, Any]]) -> List[str]:
@@ -465,26 +504,42 @@ class Signin(object):
465
504
  Returns:
466
505
  Union[None, RedirectResponse]: 認可された場合はNone、認可されなかった場合はリダイレクトレスポンス
467
506
  """
468
- if self.signin_file_data is None:
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:
469
524
  return None
470
525
  if 'signin' not in req.session:
471
526
  return None
472
527
  path = path if path.startswith('/') else f'/{path}'
473
528
  # パスルールチェック
474
529
  user_groups = req.session['signin']['groups']
475
- jadge = self.signin_file_data['pathrule']['policy']
476
- for rule in self.signin_file_data['pathrule']['rules']:
530
+ jadge = signin_file_data['pathrule']['policy']
531
+ for rule in signin_file_data['pathrule']['rules']:
477
532
  if len([g for g in rule['groups'] if g in user_groups]) <= 0:
478
533
  continue
479
534
  if len([p for p in rule['paths'] if path.startswith(p)]) <= 0:
480
535
  continue
481
536
  jadge = rule['rule']
482
- if self.logger.level == logging.DEBUG:
483
- self.logger.debug(f"rule: {path}: {jadge}")
537
+ if logger.level == logging.DEBUG:
538
+ logger.debug(f"rule: {path}: {jadge}")
484
539
  if jadge == 'allow':
485
540
  return None
486
541
  else:
487
- self.logger.warning(f"Unauthorized site. user={req.session['signin']['name']}, path={path}")
542
+ logger.warning(f"Unauthorized site. user={req.session['signin']['name']}, path={path}")
488
543
  return RedirectResponse(url=f'/signin{path}?error=unauthorizedsite')
489
544
 
490
545
  def check_cmd(self, req:Request, res:Response, mode:str, cmd:str):
@@ -504,10 +559,57 @@ class Signin(object):
504
559
  return True
505
560
  if 'signin' not in req.session or 'groups' not in req.session['signin']:
506
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
507
610
  # コマンドチェック
508
- user_groups = req.session['signin']['groups']
509
- jadge = self.signin_file_data['cmdrule']['policy']
510
- 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']:
511
613
  if len([g for g in rule['groups'] if g in user_groups]) <= 0:
512
614
  continue
513
615
  if rule['mode'] is not None:
@@ -516,8 +618,8 @@ class Signin(object):
516
618
  if len([c for c in rule['cmds'] if cmd == c]) <= 0:
517
619
  continue
518
620
  jadge = rule['rule']
519
- if self.logger.level == logging.DEBUG:
520
- self.logger.debug(f"rule: mode={mode}, cmd={cmd}: {jadge}")
621
+ if logger.level == logging.DEBUG:
622
+ logger.debug(f"rule: mode={mode}, cmd={cmd}: {jadge}")
521
623
  return jadge == 'allow'
522
624
 
523
625
  def get_enable_modes(self, req:Request, res:Response) -> List[str]:
@@ -675,3 +777,61 @@ class Signin(object):
675
777
  str: メールアドレス
676
778
  """
677
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)