cmdbox 0.5.0.8__py3-none-any.whl → 0.5.1__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/edge.py +96 -12
- cmdbox/app/features/cli/cmdbox_edge_config.py +9 -3
- cmdbox/app/features/cli/cmdbox_gui_start.py +1 -1
- cmdbox/app/features/cli/cmdbox_web_start.py +2 -1
- cmdbox/app/features/web/cmdbox_web_bbforce_cmd.py +1 -1
- cmdbox/app/features/web/cmdbox_web_copyright.py +1 -1
- cmdbox/app/features/web/cmdbox_web_del_cmd.py +1 -1
- cmdbox/app/features/web/cmdbox_web_del_pipe.py +1 -1
- cmdbox/app/features/web/cmdbox_web_do_signin.py +88 -32
- cmdbox/app/features/web/cmdbox_web_exec_cmd.py +2 -2
- cmdbox/app/features/web/cmdbox_web_exec_pipe.py +2 -2
- cmdbox/app/features/web/cmdbox_web_filer download.py +2 -3
- cmdbox/app/features/web/cmdbox_web_filer.py +1 -1
- cmdbox/app/features/web/cmdbox_web_filer_upload.py +1 -1
- cmdbox/app/features/web/cmdbox_web_get_cmd_choices.py +1 -1
- cmdbox/app/features/web/cmdbox_web_get_cmds.py +2 -2
- cmdbox/app/features/web/cmdbox_web_get_modes.py +2 -2
- cmdbox/app/features/web/cmdbox_web_get_server_opt.py +1 -1
- cmdbox/app/features/web/cmdbox_web_gui.py +2 -2
- cmdbox/app/features/web/cmdbox_web_list_cmd.py +2 -2
- cmdbox/app/features/web/cmdbox_web_list_pipe.py +2 -2
- cmdbox/app/features/web/cmdbox_web_load_cmd.py +1 -1
- cmdbox/app/features/web/cmdbox_web_load_pin.py +3 -5
- cmdbox/app/features/web/cmdbox_web_load_pipe.py +1 -1
- cmdbox/app/features/web/cmdbox_web_raw_cmd.py +1 -1
- cmdbox/app/features/web/cmdbox_web_raw_pipe.py +1 -1
- cmdbox/app/features/web/cmdbox_web_result.py +2 -2
- cmdbox/app/features/web/cmdbox_web_save_cmd.py +1 -1
- cmdbox/app/features/web/cmdbox_web_save_pin.py +2 -2
- cmdbox/app/features/web/cmdbox_web_save_pipe.py +1 -1
- cmdbox/app/features/web/cmdbox_web_signin.py +26 -8
- cmdbox/app/features/web/cmdbox_web_users.py +35 -37
- cmdbox/app/features/web/cmdbox_web_versions_cmdbox.py +1 -1
- cmdbox/app/features/web/cmdbox_web_versions_used.py +1 -1
- cmdbox/app/options.py +8 -8
- cmdbox/app/web.py +76 -555
- cmdbox/extensions/sample_project/sample/extensions/features.yml +38 -13
- cmdbox/extensions/sample_project/sample/extensions/user_list.yml +82 -40
- cmdbox/extensions/user_list.yml +10 -0
- cmdbox/version.py +2 -2
- cmdbox/web/assets/cmdbox/list_cmd.js +50 -2
- cmdbox/web/assets/cmdbox/signin.js +7 -0
- cmdbox/web/gui.html +1 -0
- cmdbox/web/signin.html +7 -0
- {cmdbox-0.5.0.8.dist-info → cmdbox-0.5.1.dist-info}/METADATA +1 -1
- {cmdbox-0.5.0.8.dist-info → cmdbox-0.5.1.dist-info}/RECORD +50 -51
- cmdbox/app/signin.py +0 -56
- {cmdbox-0.5.0.8.dist-info → cmdbox-0.5.1.dist-info}/LICENSE +0 -0
- {cmdbox-0.5.0.8.dist-info → cmdbox-0.5.1.dist-info}/WHEEL +0 -0
- {cmdbox-0.5.0.8.dist-info → cmdbox-0.5.1.dist-info}/entry_points.txt +0 -0
- {cmdbox-0.5.0.8.dist-info → cmdbox-0.5.1.dist-info}/top_level.txt +0 -0
cmdbox/app/web.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
from cmdbox.app import common, options
|
|
2
|
+
from cmdbox.app.auth.signin import Signin
|
|
2
3
|
from cmdbox.app.commons import module
|
|
3
4
|
from fastapi import FastAPI, Request, Response, HTTPException
|
|
4
5
|
from fastapi.responses import RedirectResponse
|
|
@@ -95,7 +96,6 @@ class Web:
|
|
|
95
96
|
self.users_html_data = None
|
|
96
97
|
self.assets_data = None
|
|
97
98
|
self.signin_html_data = None
|
|
98
|
-
self.signin_file_data = None
|
|
99
99
|
self.gui_mode = gui_mode
|
|
100
100
|
self.web_features_packages = web_features_packages
|
|
101
101
|
self.web_features_prefix = web_features_prefix
|
|
@@ -111,8 +111,9 @@ class Web:
|
|
|
111
111
|
self.cb_queue = queue.Queue(1000)
|
|
112
112
|
self.options = options.Options.getInstance()
|
|
113
113
|
self.webcap_client = requests.Session()
|
|
114
|
-
|
|
115
|
-
|
|
114
|
+
signin_file_data = Signin.load_signin_file(self.signin_file)
|
|
115
|
+
self.signin = Signin(self.logger, self.signin_file, signin_file_data, self.appcls, self.ver)
|
|
116
|
+
|
|
116
117
|
if self.logger.level == logging.DEBUG:
|
|
117
118
|
self.logger.debug(f"web init parameter: data={self.data} -> {self.data.absolute() if self.data is not None else None}")
|
|
118
119
|
self.logger.debug(f"web init parameter: redis_host={self.redis_host}")
|
|
@@ -134,233 +135,6 @@ class Web:
|
|
|
134
135
|
self.logger.debug(f"web init parameter: pipes_path={self.pipes_path} -> {self.pipes_path.absolute() if self.pipes_path is not None else None}")
|
|
135
136
|
self.logger.debug(f"web init parameter: users_path={self.users_path} -> {self.users_path.absolute() if self.users_path is not None else None}")
|
|
136
137
|
|
|
137
|
-
def enable_cors(self, req:Request, res:Response) -> None:
|
|
138
|
-
"""
|
|
139
|
-
CORSを有効にする
|
|
140
|
-
|
|
141
|
-
Args:
|
|
142
|
-
req (Request): リクエスト
|
|
143
|
-
res (Response): レスポンス
|
|
144
|
-
"""
|
|
145
|
-
if req is None or not 'Origin' in req.headers.keys():
|
|
146
|
-
return
|
|
147
|
-
res.headers['Access-Control-Allow-Origin'] = res.headers['Origin']
|
|
148
|
-
|
|
149
|
-
def check_apikey(self, req:Request, res:Response):
|
|
150
|
-
"""
|
|
151
|
-
ApiKeyをチェックする
|
|
152
|
-
|
|
153
|
-
Args:
|
|
154
|
-
req (Request): リクエスト
|
|
155
|
-
res (Response): レスポンス
|
|
156
|
-
|
|
157
|
-
Returns:
|
|
158
|
-
Response: サインインエラーの場合はリダイレクトレスポンス
|
|
159
|
-
"""
|
|
160
|
-
self.enable_cors(req, res)
|
|
161
|
-
if self.signin_file is None:
|
|
162
|
-
res.headers['signin'] = 'success'
|
|
163
|
-
return None
|
|
164
|
-
if self.signin_file_data is None:
|
|
165
|
-
raise ValueError(f'signin_file_data is None. ({self.signin_file})')
|
|
166
|
-
if 'Authorization' not in req.headers:
|
|
167
|
-
self.logger.warning(f"Authorization not found. headers={req.headers}")
|
|
168
|
-
return RedirectResponse(url=f'/signin{req.url.path}?error=noauth')
|
|
169
|
-
auth = req.headers['Authorization']
|
|
170
|
-
if not auth.startswith('Bearer '):
|
|
171
|
-
self.logger.warning(f"Bearer not found. headers={req.headers}")
|
|
172
|
-
return RedirectResponse(url=f'/signin{req.url.path}?error=apikeyfail')
|
|
173
|
-
bearer, apikey = auth.split(' ')
|
|
174
|
-
apikey = common.hash_password(apikey.strip(), 'sha1')
|
|
175
|
-
if self.logger.level == logging.DEBUG:
|
|
176
|
-
self.logger.debug(f"hashed apikey: {apikey}")
|
|
177
|
-
find_user = None
|
|
178
|
-
self.load_signin_file() # サインインファイルの更新をチェック
|
|
179
|
-
for user in self.signin_file_data['users']:
|
|
180
|
-
if 'apikeys' not in user:
|
|
181
|
-
continue
|
|
182
|
-
for ak, key in user['apikeys'].items():
|
|
183
|
-
if apikey == key:
|
|
184
|
-
find_user = user
|
|
185
|
-
if find_user is None:
|
|
186
|
-
self.logger.warning(f"No matching user found for apikey.")
|
|
187
|
-
return RedirectResponse(url=f'/signin{req.url.path}?error=apikeyfail')
|
|
188
|
-
|
|
189
|
-
group_names = list(set(self.correct_group(find_user['groups'])))
|
|
190
|
-
gids = [g['gid'] for g in self.signin_file_data['groups'] if g['name'] in group_names]
|
|
191
|
-
req.session['signin'] = dict(uid=find_user['uid'], name=find_user['name'], password=find_user['password'],
|
|
192
|
-
gids=gids, groups=group_names)
|
|
193
|
-
if self.logger.level == logging.DEBUG:
|
|
194
|
-
self.logger.debug(f"find user: name={find_user['name']}, group_names={group_names}")
|
|
195
|
-
# パスルールチェック
|
|
196
|
-
user_groups = find_user['groups']
|
|
197
|
-
jadge = self.signin_file_data['pathrule']['policy']
|
|
198
|
-
for rule in self.signin_file_data['pathrule']['rules']:
|
|
199
|
-
if len([g for g in rule['groups'] if g in user_groups]) <= 0:
|
|
200
|
-
continue
|
|
201
|
-
if len([p for p in rule['paths'] if req.url.path.startswith(p)]) <= 0:
|
|
202
|
-
continue
|
|
203
|
-
jadge = rule['rule']
|
|
204
|
-
if self.logger.level == logging.DEBUG:
|
|
205
|
-
self.logger.debug(f"rule: {req.url.path}: {jadge}")
|
|
206
|
-
if jadge == 'allow':
|
|
207
|
-
res.headers['signin'] = 'success'
|
|
208
|
-
return None
|
|
209
|
-
self.logger.warning(f"Unauthorized site. user={find_user['name']}, path={req.url.path}")
|
|
210
|
-
return RedirectResponse(url=f'/signin{req.url.path}?error=unauthorizedsite')
|
|
211
|
-
|
|
212
|
-
def check_signin(self, req:Request, res:Response):
|
|
213
|
-
"""
|
|
214
|
-
サインインをチェックする
|
|
215
|
-
|
|
216
|
-
Args:
|
|
217
|
-
req (Request): リクエスト
|
|
218
|
-
res (Response): レスポンス
|
|
219
|
-
|
|
220
|
-
Returns:
|
|
221
|
-
Response: サインインエラーの場合はリダイレクトレスポンス
|
|
222
|
-
"""
|
|
223
|
-
self.enable_cors(req, res)
|
|
224
|
-
if self.signin_file is None:
|
|
225
|
-
return None
|
|
226
|
-
if self.signin_file_data is None:
|
|
227
|
-
raise ValueError(f'signin_file_data is None. ({self.signin_file})')
|
|
228
|
-
if 'signin' in req.session:
|
|
229
|
-
self.load_signin_file() # サインインファイルの更新をチェック
|
|
230
|
-
path_jadge = self.check_path(req, req.url.path)
|
|
231
|
-
if path_jadge is not None:
|
|
232
|
-
return path_jadge
|
|
233
|
-
return None
|
|
234
|
-
self.logger.info(f"Not found siginin session. Try check_apikey. path={req.url.path}")
|
|
235
|
-
ret = self.check_apikey(req, res)
|
|
236
|
-
if ret is not None and self.logger.level == logging.DEBUG:
|
|
237
|
-
self.logger.debug(f"Not signed in.")
|
|
238
|
-
return ret
|
|
239
|
-
|
|
240
|
-
def check_path(self, req:Request, path:str):
|
|
241
|
-
if 'signin' not in req.session:
|
|
242
|
-
return None
|
|
243
|
-
path = path if path.startswith('/') else f'/{path}'
|
|
244
|
-
# パスルールチェック
|
|
245
|
-
user_groups = req.session['signin']['groups']
|
|
246
|
-
jadge = self.signin_file_data['pathrule']['policy']
|
|
247
|
-
for rule in self.signin_file_data['pathrule']['rules']:
|
|
248
|
-
if len([g for g in rule['groups'] if g in user_groups]) <= 0:
|
|
249
|
-
continue
|
|
250
|
-
if len([p for p in rule['paths'] if path.startswith(p)]) <= 0:
|
|
251
|
-
continue
|
|
252
|
-
jadge = rule['rule']
|
|
253
|
-
if self.logger.level == logging.DEBUG:
|
|
254
|
-
self.logger.debug(f"rule: {path}: {jadge}")
|
|
255
|
-
if jadge == 'allow':
|
|
256
|
-
return None
|
|
257
|
-
else:
|
|
258
|
-
self.logger.warning(f"Unauthorized site. user={req.session['signin']['name']}, path={path}")
|
|
259
|
-
return RedirectResponse(url=f'/signin{path}?error=unauthorizedsite')
|
|
260
|
-
|
|
261
|
-
def check_cmd(self, req:Request, res:Response, mode:str, cmd:str):
|
|
262
|
-
if self.signin_file is None:
|
|
263
|
-
return True
|
|
264
|
-
if self.signin_file_data is None:
|
|
265
|
-
raise ValueError(f'signin_file_data is None. ({self.signin_file})')
|
|
266
|
-
if 'signin' not in req.session or 'groups' not in req.session['signin']:
|
|
267
|
-
return False
|
|
268
|
-
# コマンドチェック
|
|
269
|
-
user_groups = req.session['signin']['groups']
|
|
270
|
-
jadge = self.signin_file_data['cmdrule']['policy']
|
|
271
|
-
for rule in self.signin_file_data['cmdrule']['rules']:
|
|
272
|
-
if len([g for g in rule['groups'] if g in user_groups]) <= 0:
|
|
273
|
-
continue
|
|
274
|
-
if rule['mode'] is not None:
|
|
275
|
-
if rule['mode'] != mode:
|
|
276
|
-
continue
|
|
277
|
-
if len([c for c in rule['cmds'] if cmd == c]) <= 0:
|
|
278
|
-
continue
|
|
279
|
-
jadge = rule['rule']
|
|
280
|
-
if self.logger.level == logging.DEBUG:
|
|
281
|
-
self.logger.debug(f"rule: mode={mode}, cmd={cmd}: {jadge}")
|
|
282
|
-
return jadge == 'allow'
|
|
283
|
-
|
|
284
|
-
def get_enable_modes(self, req:Request, res:Response):
|
|
285
|
-
if self.signin_file is None:
|
|
286
|
-
return self.options.get_modes().copy()
|
|
287
|
-
if self.signin_file_data is None:
|
|
288
|
-
raise ValueError(f'signin_file_data is None. ({self.signin_file})')
|
|
289
|
-
if 'signin' not in req.session or 'groups' not in req.session['signin']:
|
|
290
|
-
return []
|
|
291
|
-
modes = self.options.get_modes().copy()
|
|
292
|
-
user_groups = req.session['signin']['groups']
|
|
293
|
-
jadge = self.signin_file_data['cmdrule']['policy']
|
|
294
|
-
jadge_modes = []
|
|
295
|
-
if jadge == 'allow':
|
|
296
|
-
for m in modes:
|
|
297
|
-
jadge_modes += list(m.keys()) if type(m) is dict else [m]
|
|
298
|
-
for rule in self.signin_file_data['cmdrule']['rules']:
|
|
299
|
-
if len([g for g in rule['groups'] if g in user_groups]) <= 0:
|
|
300
|
-
continue
|
|
301
|
-
if 'mode' not in rule:
|
|
302
|
-
continue
|
|
303
|
-
if rule['mode'] is not None:
|
|
304
|
-
if rule['rule'] == 'allow':
|
|
305
|
-
jadge_modes.append(rule['mode'])
|
|
306
|
-
elif rule['rule'] == 'deny':
|
|
307
|
-
jadge_modes.remove(rule['mode'])
|
|
308
|
-
elif rule['mode'] is None and len(rule['cmds']) <= 0:
|
|
309
|
-
if rule['rule'] == 'allow':
|
|
310
|
-
for m in modes:
|
|
311
|
-
jadge_modes += list(m.keys()) if type(m) is dict else [m]
|
|
312
|
-
elif rule['rule'] == 'deny':
|
|
313
|
-
jadge_modes = []
|
|
314
|
-
return sorted(list(set(['']+jadge_modes)), key=lambda m: m)
|
|
315
|
-
|
|
316
|
-
def get_enable_cmds(self, mode:str, req:Request, res:Response):
|
|
317
|
-
if self.signin_file is None:
|
|
318
|
-
cmds = self.options.get_cmds(mode).copy()
|
|
319
|
-
return cmds
|
|
320
|
-
if self.signin_file_data is None:
|
|
321
|
-
raise ValueError(f'signin_file_data is None. ({self.signin_file})')
|
|
322
|
-
if 'signin' not in req.session or 'groups' not in req.session['signin']:
|
|
323
|
-
return []
|
|
324
|
-
cmds = self.options.get_cmds(mode).copy()
|
|
325
|
-
if mode == '':
|
|
326
|
-
return cmds
|
|
327
|
-
user_groups = req.session['signin']['groups']
|
|
328
|
-
jadge = self.signin_file_data['cmdrule']['policy']
|
|
329
|
-
jadge_cmds = []
|
|
330
|
-
if jadge == 'allow':
|
|
331
|
-
for c in cmds:
|
|
332
|
-
jadge_cmds += list(c.keys()) if type(c) is dict else [c]
|
|
333
|
-
for rule in self.signin_file_data['cmdrule']['rules']:
|
|
334
|
-
if len([g for g in rule['groups'] if g in user_groups]) <= 0:
|
|
335
|
-
continue
|
|
336
|
-
if 'mode' not in rule:
|
|
337
|
-
continue
|
|
338
|
-
if 'cmds' not in rule:
|
|
339
|
-
continue
|
|
340
|
-
if rule['mode'] is not None and rule['mode'] != mode:
|
|
341
|
-
continue
|
|
342
|
-
if len(rule['cmds']) > 0:
|
|
343
|
-
if rule['rule'] == 'allow':
|
|
344
|
-
jadge_cmds += rule['cmds']
|
|
345
|
-
elif rule['rule'] == 'deny':
|
|
346
|
-
for c in rule['cmds']:
|
|
347
|
-
jadge_cmds.remove[c]
|
|
348
|
-
elif rule['mode'] is None and len(rule['cmds']) <= 0:
|
|
349
|
-
if rule['rule'] == 'allow':
|
|
350
|
-
for c in cmds:
|
|
351
|
-
jadge_cmds += list(c.keys()) if type(c) is dict else [c]
|
|
352
|
-
elif rule['rule'] == 'deny':
|
|
353
|
-
jadge_cmds = []
|
|
354
|
-
return sorted(list(set(['']+jadge_cmds)), key=lambda c: c)
|
|
355
|
-
|
|
356
|
-
def correct_group(self, group_names, master_groups=None):
|
|
357
|
-
master_groups = self.signin_file_data['groups'] if master_groups is None else master_groups
|
|
358
|
-
gns = []
|
|
359
|
-
for gn in group_names.copy():
|
|
360
|
-
gns = [gr['name'] for gr in master_groups if 'parent' in gr and gr['parent']==gn]
|
|
361
|
-
gns += self.correct_group(gns, master_groups)
|
|
362
|
-
return group_names + gns
|
|
363
|
-
|
|
364
138
|
def init_webfeatures(self, app:FastAPI):
|
|
365
139
|
self.filemenu = dict()
|
|
366
140
|
self.toolmenu = dict()
|
|
@@ -393,270 +167,6 @@ class Web:
|
|
|
393
167
|
for route in app.routes:
|
|
394
168
|
self.logger.debug(f"loaded webfeature: {route}")
|
|
395
169
|
|
|
396
|
-
def load_signin_file(self):
|
|
397
|
-
"""
|
|
398
|
-
サインインファイルを読み込む
|
|
399
|
-
|
|
400
|
-
Raises:
|
|
401
|
-
HTTPException: サインインファイルのフォーマットエラー
|
|
402
|
-
"""
|
|
403
|
-
if self.signin_file is not None:
|
|
404
|
-
if not self.signin_file.is_file():
|
|
405
|
-
raise HTTPException(status_code=500, detail=f'signin_file is not found. ({self.signin_file})')
|
|
406
|
-
# サインインファイル読込み済みなら返すが、別プロセスがサインインファイルを更新していたら読込みを実施する。
|
|
407
|
-
if not hasattr(self, 'signin_file_last'):
|
|
408
|
-
self.signin_file_last = self.signin_file.stat().st_mtime
|
|
409
|
-
if self.signin_file_last >= self.signin_file.stat().st_mtime and self.signin_file_data is not None:
|
|
410
|
-
return
|
|
411
|
-
self.signin_file_last = self.signin_file.stat().st_mtime
|
|
412
|
-
yml = common.load_yml(self.signin_file)
|
|
413
|
-
# usersのフォーマットチェック
|
|
414
|
-
if 'users' not in yml:
|
|
415
|
-
raise HTTPException(status_code=500, detail=f'signin_file format error. "users" not found. ({self.signin_file})')
|
|
416
|
-
uids = set()
|
|
417
|
-
unames = set()
|
|
418
|
-
groups = [g['name'] for g in yml['groups']]
|
|
419
|
-
for user in yml['users']:
|
|
420
|
-
if 'uid' not in user or user['uid'] is None:
|
|
421
|
-
raise HTTPException(status_code=500, detail=f'signin_file format error. "uid" not found or empty. ({self.signin_file})')
|
|
422
|
-
if user['uid'] in uids:
|
|
423
|
-
raise HTTPException(status_code=500, detail=f'signin_file format error. Duplicate uid found. ({self.signin_file}). uid={user["uid"]}')
|
|
424
|
-
if 'name' not in user or user['name'] is None:
|
|
425
|
-
raise HTTPException(status_code=500, detail=f'signin_file format error. "name" not found or empty. ({self.signin_file})')
|
|
426
|
-
if user['name'] in unames:
|
|
427
|
-
raise HTTPException(status_code=500, detail=f'signin_file format error. Duplicate name found. ({self.signin_file}). name={user["name"]}')
|
|
428
|
-
if 'password' not in user:
|
|
429
|
-
raise HTTPException(status_code=500, detail=f'signin_file format error. "password" not found or empty. ({self.signin_file})')
|
|
430
|
-
if 'hash' not in user or user['hash'] is None:
|
|
431
|
-
raise HTTPException(status_code=500, detail=f'signin_file format error. "hash" not found or empty. ({self.signin_file})')
|
|
432
|
-
if user['hash'] not in ['oauth2', 'plain', 'md5', 'sha1', 'sha256']:
|
|
433
|
-
raise HTTPException(status_code=500, detail=f'signin_file format error. Algorithms not supported. ({self.signin_file}). hash={user["hash"]} "oauth2", "plain", "md5", "sha1", "sha256" only.')
|
|
434
|
-
if 'groups' not in user or type(user['groups']) is not list:
|
|
435
|
-
raise HTTPException(status_code=500, detail=f'signin_file format error. "groups" not found or not list type. ({self.signin_file})')
|
|
436
|
-
if len([ug for ug in user['groups'] if ug not in groups]) > 0:
|
|
437
|
-
raise HTTPException(status_code=500, detail=f'signin_file format error. Group not found. ({self.signin_file}). {user["groups"]}')
|
|
438
|
-
uids.add(user['uid'])
|
|
439
|
-
unames.add(user['name'])
|
|
440
|
-
# groupsのフォーマットチェック
|
|
441
|
-
if 'groups' not in yml:
|
|
442
|
-
raise HTTPException(status_code=500, detail=f'signin_file format error. "groups" not found. ({self.signin_file})')
|
|
443
|
-
gids = set()
|
|
444
|
-
gnames = set()
|
|
445
|
-
for group in yml['groups']:
|
|
446
|
-
if 'gid' not in group or group['gid'] is None:
|
|
447
|
-
raise HTTPException(status_code=500, detail=f'signin_file format error. "gid" not found or empty. ({self.signin_file})')
|
|
448
|
-
if group['gid'] in gids:
|
|
449
|
-
raise HTTPException(status_code=500, detail=f'signin_file format error. Duplicate gid found. ({self.signin_file}). gid={group["gid"]}')
|
|
450
|
-
if 'name' not in group or group['name'] is None:
|
|
451
|
-
raise HTTPException(status_code=500, detail=f'signin_file format error. "name" not found or empty. ({self.signin_file})')
|
|
452
|
-
if group['name'] in gnames:
|
|
453
|
-
raise HTTPException(status_code=500, detail=f'signin_file format error. Duplicate name found. ({self.signin_file}). name={group["name"]}')
|
|
454
|
-
if 'parent' in group:
|
|
455
|
-
if group['parent'] not in groups:
|
|
456
|
-
raise HTTPException(status_code=500, detail=f'signin_file format error. Parent group not found. ({self.signin_file}). parent={group["parent"]}')
|
|
457
|
-
gids.add(group['gid'])
|
|
458
|
-
gnames.add(group['name'])
|
|
459
|
-
# cmdruleのフォーマットチェック
|
|
460
|
-
if 'cmdrule' not in yml:
|
|
461
|
-
raise HTTPException(status_code=500, detail=f'signin_file format error. "cmdrule" not found. ({self.signin_file})')
|
|
462
|
-
if 'policy' not in yml['cmdrule']:
|
|
463
|
-
raise HTTPException(status_code=500, detail=f'signin_file format error. "policy" not found in "cmdrule". ({self.signin_file})')
|
|
464
|
-
if yml['cmdrule']['policy'] not in ['allow', 'deny']:
|
|
465
|
-
raise HTTPException(status_code=500, detail=f'signin_file format error. "policy" not supported in "cmdrule". ({self.signin_file}). "allow" or "deny" only.')
|
|
466
|
-
if 'rules' not in yml['cmdrule']:
|
|
467
|
-
raise HTTPException(status_code=500, detail=f'signin_file format error. "rules" not found in "cmdrule". ({self.signin_file})')
|
|
468
|
-
if type(yml['cmdrule']['rules']) is not list:
|
|
469
|
-
raise HTTPException(status_code=500, detail=f'signin_file format error. "rules" not list type in "cmdrule". ({self.signin_file})')
|
|
470
|
-
for rule in yml['cmdrule']['rules']:
|
|
471
|
-
if 'groups' not in rule:
|
|
472
|
-
raise HTTPException(status_code=500, detail=f'signin_file format error. "groups" not found in "cmdrule.rules" ({self.signin_file})')
|
|
473
|
-
if type(rule['groups']) is not list:
|
|
474
|
-
raise HTTPException(status_code=500, detail=f'signin_file format error. "groups" not list type in "cmdrule.rules". ({self.signin_file})')
|
|
475
|
-
rule['groups'] = list(set(copy.deepcopy(self.correct_group(rule['groups'], yml['groups']))))
|
|
476
|
-
if 'rule' not in rule:
|
|
477
|
-
raise HTTPException(status_code=500, detail=f'signin_file format error. "rule" not found in "cmdrule.rules" ({self.signin_file})')
|
|
478
|
-
if rule['rule'] not in ['allow', 'deny']:
|
|
479
|
-
raise HTTPException(status_code=500, detail=f'signin_file format error. "rule" not supported in "cmdrule.rules". ({self.signin_file}). "allow" or "deny" only.')
|
|
480
|
-
if 'mode' not in rule:
|
|
481
|
-
rule['mode'] = None
|
|
482
|
-
if 'cmds' not in rule:
|
|
483
|
-
rule['cmds'] = []
|
|
484
|
-
if rule['mode'] is not None and len(rule['cmds']) <= 0:
|
|
485
|
-
raise HTTPException(status_code=500, detail=f'signin_file format error. When “cmds” is specified, “mode” must be specified. ({self.signin_file})')
|
|
486
|
-
if type(rule['cmds']) is not list:
|
|
487
|
-
raise HTTPException(status_code=500, detail=f'signin_file format error. "cmds" not list type in "cmdrule.rules". ({self.signin_file})')
|
|
488
|
-
# pathruleのフォーマットチェック
|
|
489
|
-
if 'pathrule' not in yml:
|
|
490
|
-
raise HTTPException(status_code=500, detail=f'signin_file format error. "pathrule" not found. ({self.signin_file})')
|
|
491
|
-
if 'policy' not in yml['pathrule']:
|
|
492
|
-
raise HTTPException(status_code=500, detail=f'signin_file format error. "policy" not found in "pathrule". ({self.signin_file})')
|
|
493
|
-
if yml['pathrule']['policy'] not in ['allow', 'deny']:
|
|
494
|
-
raise HTTPException(status_code=500, detail=f'signin_file format error. "policy" not supported in "pathrule". ({self.signin_file}). "allow" or "deny" only.')
|
|
495
|
-
if 'rules' not in yml['pathrule']:
|
|
496
|
-
raise HTTPException(status_code=500, detail=f'signin_file format error. "rules" not found in "pathrule". ({self.signin_file})')
|
|
497
|
-
if type(yml['pathrule']['rules']) is not list:
|
|
498
|
-
raise HTTPException(status_code=500, detail=f'signin_file format error. "rules" not list type in "pathrule". ({self.signin_file})')
|
|
499
|
-
for rule in yml['pathrule']['rules']:
|
|
500
|
-
if 'groups' not in rule:
|
|
501
|
-
raise HTTPException(status_code=500, detail=f'signin_file format error. "groups" not found in "pathrule.rules" ({self.signin_file})')
|
|
502
|
-
if type(rule['groups']) is not list:
|
|
503
|
-
raise HTTPException(status_code=500, detail=f'signin_file format error. "groups" not list type in "pathrule.rules". ({self.signin_file})')
|
|
504
|
-
rule['groups'] = list(set(copy.deepcopy(self.correct_group(rule['groups'], yml['groups']))))
|
|
505
|
-
if 'rule' not in rule:
|
|
506
|
-
raise HTTPException(status_code=500, detail=f'signin_file format error. "rule" not found in "pathrule.rules" ({self.signin_file})')
|
|
507
|
-
if rule['rule'] not in ['allow', 'deny']:
|
|
508
|
-
raise HTTPException(status_code=500, detail=f'signin_file format error. "rule" not supported in "pathrule.rules". ({self.signin_file}). "allow" or "deny" only.')
|
|
509
|
-
if 'paths' not in rule:
|
|
510
|
-
rule['paths'] = []
|
|
511
|
-
if type(rule['paths']) is not list:
|
|
512
|
-
raise HTTPException(status_code=500, detail=f'signin_file format error. "paths" not list type in "pathrule.rules". ({self.signin_file})')
|
|
513
|
-
# passwordのフォーマットチェック
|
|
514
|
-
if 'password' in yml:
|
|
515
|
-
if 'policy' not in yml['password']:
|
|
516
|
-
raise HTTPException(status_code=500, detail=f'signin_file format error. "policy" not found in "password". ({self.signin_file})')
|
|
517
|
-
if 'enabled' not in yml['password']['policy']:
|
|
518
|
-
raise HTTPException(status_code=500, detail=f'signin_file format error. "enabled" not found in "password.policy". ({self.signin_file})')
|
|
519
|
-
if type(yml['password']['policy']['enabled']) is not bool:
|
|
520
|
-
raise HTTPException(status_code=500, detail=f'signin_file format error. "enabled" not bool type in "password.policy". ({self.signin_file})')
|
|
521
|
-
if type(yml['password']['policy']['not_same_before']) is not bool:
|
|
522
|
-
raise HTTPException(status_code=500, detail=f'signin_file format error. "not_same_before" not bool type in "password.policy". ({self.signin_file})')
|
|
523
|
-
if 'min_length' not in yml['password']['policy']:
|
|
524
|
-
raise HTTPException(status_code=500, detail=f'signin_file format error. "min_length" not found in "password.policy". ({self.signin_file})')
|
|
525
|
-
if type(yml['password']['policy']['min_length']) is not int:
|
|
526
|
-
raise HTTPException(status_code=500, detail=f'signin_file format error. "min_length" not int type in "password.policy". ({self.signin_file})')
|
|
527
|
-
if 'max_length' not in yml['password']['policy']:
|
|
528
|
-
raise HTTPException(status_code=500, detail=f'signin_file format error. "max_length" not found in "password.policy". ({self.signin_file})')
|
|
529
|
-
if type(yml['password']['policy']['max_length']) is not int:
|
|
530
|
-
raise HTTPException(status_code=500, detail=f'signin_file format error. "max_length" not int type in "password.policy". ({self.signin_file})')
|
|
531
|
-
if 'min_lowercase' not in yml['password']['policy']:
|
|
532
|
-
raise HTTPException(status_code=500, detail=f'signin_file format error. "min_lowercase" not found in "password.policy". ({self.signin_file})')
|
|
533
|
-
if type(yml['password']['policy']['min_lowercase']) is not int:
|
|
534
|
-
raise HTTPException(status_code=500, detail=f'signin_file format error. "min_lowercase" not int type in "password.policy". ({self.signin_file})')
|
|
535
|
-
if 'min_uppercase' not in yml['password']['policy']:
|
|
536
|
-
raise HTTPException(status_code=500, detail=f'signin_file format error. "min_uppercase" not found in "password.policy". ({self.signin_file})')
|
|
537
|
-
if type(yml['password']['policy']['min_uppercase']) is not int:
|
|
538
|
-
raise HTTPException(status_code=500, detail=f'signin_file format error. "min_uppercase" not int type in "password.policy". ({self.signin_file})')
|
|
539
|
-
if 'min_digit' not in yml['password']['policy']:
|
|
540
|
-
raise HTTPException(status_code=500, detail=f'signin_file format error. "min_digit" not found in "password.policy". ({self.signin_file})')
|
|
541
|
-
if type(yml['password']['policy']['min_digit']) is not int:
|
|
542
|
-
raise HTTPException(status_code=500, detail=f'signin_file format error. "min_digit" not int type in "password.policy". ({self.signin_file})')
|
|
543
|
-
if 'min_symbol' not in yml['password']['policy']:
|
|
544
|
-
raise HTTPException(status_code=500, detail=f'signin_file format error. "min_symbol" not found in "password.policy". ({self.signin_file})')
|
|
545
|
-
if type(yml['password']['policy']['min_symbol']) is not int:
|
|
546
|
-
raise HTTPException(status_code=500, detail=f'signin_file format error. "min_symbol" not int type in "password.policy". ({self.signin_file})')
|
|
547
|
-
if 'not_contain_username' not in yml['password']['policy']:
|
|
548
|
-
raise HTTPException(status_code=500, detail=f'signin_file format error. "not_contain_username" not found in "password.policy". ({self.signin_file})')
|
|
549
|
-
if type(yml['password']['policy']['not_contain_username']) is not bool:
|
|
550
|
-
raise HTTPException(status_code=500, detail=f'signin_file format error. "not_contain_username" not bool type in "password.policy". ({self.signin_file})')
|
|
551
|
-
if 'expiration' not in yml['password']:
|
|
552
|
-
raise HTTPException(status_code=500, detail=f'signin_file format error. "expiration" not found in "password". ({self.signin_file})')
|
|
553
|
-
if 'enabled' not in yml['password']['expiration']:
|
|
554
|
-
raise HTTPException(status_code=500, detail=f'signin_file format error. "enabled" not found in "password.expiration". ({self.signin_file})')
|
|
555
|
-
if type(yml['password']['expiration']['enabled']) is not bool:
|
|
556
|
-
raise HTTPException(status_code=500, detail=f'signin_file format error. "enabled" not bool type in "password.expiration". ({self.signin_file})')
|
|
557
|
-
if 'period' not in yml['password']['expiration']:
|
|
558
|
-
raise HTTPException(status_code=500, detail=f'signin_file format error. "period" not found in "password.expiration". ({self.signin_file})')
|
|
559
|
-
if type(yml['password']['expiration']['period']) is not int:
|
|
560
|
-
raise HTTPException(status_code=500, detail=f'signin_file format error. "period" not int type in "password.expiration". ({self.signin_file})')
|
|
561
|
-
if 'notify' not in yml['password']['expiration']:
|
|
562
|
-
raise HTTPException(status_code=500, detail=f'signin_file format error. "notify" not found in "password.expiration". ({self.signin_file})')
|
|
563
|
-
if type(yml['password']['expiration']['notify']) is not int:
|
|
564
|
-
raise HTTPException(status_code=500, detail=f'signin_file format error. "notify" not int type in "password.expiration". ({self.signin_file})')
|
|
565
|
-
if 'lockout' not in yml['password']:
|
|
566
|
-
raise HTTPException(status_code=500, detail=f'signin_file format error. "lockout" not found in "password". ({self.signin_file})')
|
|
567
|
-
if 'enabled' not in yml['password']['lockout']:
|
|
568
|
-
raise HTTPException(status_code=500, detail=f'signin_file format error. "enabled" not found in "password.lockout". ({self.signin_file})')
|
|
569
|
-
if type(yml['password']['lockout']['enabled']) is not bool:
|
|
570
|
-
raise HTTPException(status_code=500, detail=f'signin_file format error. "enabled" not bool type in "password.lockout". ({self.signin_file})')
|
|
571
|
-
if 'threshold' not in yml['password']['lockout']:
|
|
572
|
-
raise HTTPException(status_code=500, detail=f'signin_file format error. "threshold" not found in "password.lockout". ({self.signin_file})')
|
|
573
|
-
if type(yml['password']['lockout']['threshold']) is not int:
|
|
574
|
-
raise HTTPException(status_code=500, detail=f'signin_file format error. "threshold" not int type in "password.lockout". ({self.signin_file})')
|
|
575
|
-
if 'reset' not in yml['password']['lockout']:
|
|
576
|
-
raise HTTPException(status_code=500, detail=f'signin_file format error. "reset" not found in "password.lockout". ({self.signin_file})')
|
|
577
|
-
if type(yml['password']['lockout']['reset']) is not int:
|
|
578
|
-
raise HTTPException(status_code=500, detail=f'signin_file format error. "reset" not int type in "password.lockout". ({self.signin_file})')
|
|
579
|
-
# oauth2のフォーマットチェック
|
|
580
|
-
if 'oauth2' not in yml:
|
|
581
|
-
raise HTTPException(status_code=500, detail=f'signin_file format error. "oauth2" not found. ({self.signin_file})')
|
|
582
|
-
if 'providers' not in yml['oauth2']:
|
|
583
|
-
raise HTTPException(status_code=500, detail=f'signin_file format error. "providers" not found in "oauth2". ({self.signin_file})')
|
|
584
|
-
if 'google' not in yml['oauth2']['providers']:
|
|
585
|
-
raise HTTPException(status_code=500, detail=f'signin_file format error. "google" not found in "providers". ({self.signin_file})')
|
|
586
|
-
if 'enabled' not in yml['oauth2']['providers']['google']:
|
|
587
|
-
raise HTTPException(status_code=500, detail=f'signin_file format error. "enabled" not found in "google". ({self.signin_file})')
|
|
588
|
-
if type(yml['oauth2']['providers']['google']['enabled']) is not bool:
|
|
589
|
-
raise HTTPException(status_code=500, detail=f'signin_file format error. "enabled" not bool type in "google". ({self.signin_file})')
|
|
590
|
-
if 'client_id' not in yml['oauth2']['providers']['google']:
|
|
591
|
-
raise HTTPException(status_code=500, detail=f'signin_file format error. "client_id" not found in "google". ({self.signin_file})')
|
|
592
|
-
if 'client_secret' not in yml['oauth2']['providers']['google']:
|
|
593
|
-
raise HTTPException(status_code=500, detail=f'signin_file format error. "client_secret" not found in "google". ({self.signin_file})')
|
|
594
|
-
if 'redirect_uri' not in yml['oauth2']['providers']['google']:
|
|
595
|
-
raise HTTPException(status_code=500, detail=f'signin_file format error. "redirect_uri" not found in "google". ({self.signin_file})')
|
|
596
|
-
if 'scope' not in yml['oauth2']['providers']['google']:
|
|
597
|
-
raise HTTPException(status_code=500, detail=f'signin_file format error. "scope" not found in "google". ({self.signin_file})')
|
|
598
|
-
if type(yml['oauth2']['providers']['google']['scope']) is not list:
|
|
599
|
-
raise HTTPException(status_code=500, detail=f'signin_file format error. "scope" not list type in "google". ({self.signin_file})')
|
|
600
|
-
if 'github' not in yml['oauth2']['providers']:
|
|
601
|
-
raise HTTPException(status_code=500, detail=f'signin_file format error. "github" not found in "providers". ({self.signin_file})')
|
|
602
|
-
if 'enabled' not in yml['oauth2']['providers']['github']:
|
|
603
|
-
raise HTTPException(status_code=500, detail=f'signin_file format error. "enabled" not found in "github". ({self.signin_file})')
|
|
604
|
-
if type(yml['oauth2']['providers']['github']['enabled']) is not bool:
|
|
605
|
-
raise HTTPException(status_code=500, detail=f'signin_file format error. "enabled" not bool type in "github". ({self.signin_file})')
|
|
606
|
-
if 'client_id' not in yml['oauth2']['providers']['github']:
|
|
607
|
-
raise HTTPException(status_code=500, detail=f'signin_file format error. "client_id" not found in "github". ({self.signin_file})')
|
|
608
|
-
if 'client_secret' not in yml['oauth2']['providers']['github']:
|
|
609
|
-
raise HTTPException(status_code=500, detail=f'signin_file format error. "client_secret" not found in "github". ({self.signin_file})')
|
|
610
|
-
if 'redirect_uri' not in yml['oauth2']['providers']['github']:
|
|
611
|
-
raise HTTPException(status_code=500, detail=f'signin_file format error. "redirect_uri" not found in "github". ({self.signin_file})')
|
|
612
|
-
if 'scope' not in yml['oauth2']['providers']['github']:
|
|
613
|
-
raise HTTPException(status_code=500, detail=f'signin_file format error. "scope" not found in "github". ({self.signin_file})')
|
|
614
|
-
if type(yml['oauth2']['providers']['github']['scope']) is not list:
|
|
615
|
-
raise HTTPException(status_code=500, detail=f'signin_file format error. "scope" not list type in "github". ({self.signin_file})')
|
|
616
|
-
# フォーマットチェックOK
|
|
617
|
-
self.signin_file_data = yml
|
|
618
|
-
|
|
619
|
-
def check_password_policy(self, user_name:str, password:str, new_password:str) -> Tuple[bool, str]:
|
|
620
|
-
"""
|
|
621
|
-
パスワードポリシーをチェックする
|
|
622
|
-
|
|
623
|
-
Args:
|
|
624
|
-
user_name (str): ユーザー名
|
|
625
|
-
password (str): 元パスワード
|
|
626
|
-
new_password (str): 新しいパスワード
|
|
627
|
-
Returns:
|
|
628
|
-
bool: True:ポリシーOK, False:ポリシーNG
|
|
629
|
-
str: メッセージ
|
|
630
|
-
"""
|
|
631
|
-
if self.signin_file_data is None or 'password' not in self.signin_file_data:
|
|
632
|
-
return True, "There is no password policy set."
|
|
633
|
-
policy = self.signin_file_data['password']['policy']
|
|
634
|
-
if not policy['enabled']:
|
|
635
|
-
return True, "Password policy is disabled."
|
|
636
|
-
if policy['not_same_before'] and password == new_password:
|
|
637
|
-
self.logger.warning(f"Password policy error. The same password cannot be changed.")
|
|
638
|
-
return False, f"Password policy error. The same password cannot be changed."
|
|
639
|
-
if len(new_password) < policy['min_length'] or len(new_password) > policy['max_length']:
|
|
640
|
-
self.logger.warning(f"Password policy error. min_length={policy['min_length']}, max_length={policy['max_length']}")
|
|
641
|
-
return False, f"Password policy error. min_length={policy['min_length']}, max_length={policy['max_length']}"
|
|
642
|
-
if len([c for c in new_password if c.islower()]) < policy['min_lowercase']:
|
|
643
|
-
self.logger.warning(f"Password policy error. min_lowercase={policy['min_lowercase']}")
|
|
644
|
-
return False, f"Password policy error. min_lowercase={policy['min_lowercase']}"
|
|
645
|
-
if len([c for c in new_password if c.isupper()]) < policy['min_uppercase']:
|
|
646
|
-
self.logger.warning(f"Password policy error. min_uppercase={policy['min_uppercase']}")
|
|
647
|
-
return False, f"Password policy error. min_uppercase={policy['min_uppercase']}"
|
|
648
|
-
if len([c for c in new_password if c.isdigit()]) < policy['min_digit']:
|
|
649
|
-
self.logger.warning(f"Password policy error. min_digit={policy['min_digit']}")
|
|
650
|
-
return False, f"Password policy error. min_digit={policy['min_digit']}"
|
|
651
|
-
if len([c for c in new_password if c in string.punctuation]) < policy['min_symbol']:
|
|
652
|
-
self.logger.warning(f"Password policy error. min_symbol={policy['min_symbol']}")
|
|
653
|
-
return False, f"Password policy error. min_symbol={policy['min_symbol']}"
|
|
654
|
-
if policy['not_contain_username'] and (user_name is None or user_name in new_password):
|
|
655
|
-
self.logger.warning(f"Password policy error. not_contain_username=True")
|
|
656
|
-
return False, f"Password policy error. not_contain_username=True"
|
|
657
|
-
self.logger.info(f"Password policy OK.")
|
|
658
|
-
return True, "Password policy OK."
|
|
659
|
-
|
|
660
170
|
def change_password(self, user_name:str, password:str, new_password:str, confirm_password:str):
|
|
661
171
|
"""
|
|
662
172
|
パスワードを変更する
|
|
@@ -670,7 +180,8 @@ class Web:
|
|
|
670
180
|
HTTPException: パスワードが一致しない場合
|
|
671
181
|
HTTPException: ユーザーが存在しない場合
|
|
672
182
|
"""
|
|
673
|
-
|
|
183
|
+
signin_data = self.signin.get_data()
|
|
184
|
+
if signin_data is None:
|
|
674
185
|
raise ValueError(f'signin_file_data is None. ({self.signin_file})')
|
|
675
186
|
if self.signin_file is None:
|
|
676
187
|
raise ValueError(f"signin_file is None.")
|
|
@@ -684,19 +195,19 @@ class Web:
|
|
|
684
195
|
return dict(warn="Confirm password is empty.")
|
|
685
196
|
if new_password != confirm_password:
|
|
686
197
|
return dict(warn="Password does not match.")
|
|
687
|
-
for u in
|
|
198
|
+
for u in signin_data['users']:
|
|
688
199
|
if u['name'] == user_name:
|
|
689
200
|
p = password if u['hash'] == 'plain' else common.hash_password(password, u['hash'])
|
|
690
201
|
if u['password'] != p:
|
|
691
202
|
return dict(warn="Password does not match.")
|
|
692
|
-
jadge, msg = self.check_password_policy(user_name, password, new_password)
|
|
203
|
+
jadge, msg = self.signin.check_password_policy(user_name, password, new_password)
|
|
693
204
|
if not jadge:
|
|
694
205
|
return dict(warn=msg)
|
|
695
206
|
u['password'] = new_password if u['hash'] == 'plain' else common.hash_password(new_password, u['hash'])
|
|
696
207
|
# パスワード更新日時の保存
|
|
697
208
|
self.user_data(None, u['uid'], user_name, 'password', 'last_update', datetime.datetime.now())
|
|
698
209
|
# サインインファイルの保存
|
|
699
|
-
common.save_yml(self.signin_file,
|
|
210
|
+
common.save_yml(self.signin_file, signin_data)
|
|
700
211
|
return dict(success="Password changed.")
|
|
701
212
|
return dict(warn="User not found.")
|
|
702
213
|
|
|
@@ -710,12 +221,13 @@ class Web:
|
|
|
710
221
|
Returns:
|
|
711
222
|
List[Dict[str, Any]]: ユーザー一覧
|
|
712
223
|
"""
|
|
713
|
-
|
|
224
|
+
signin_data = self.signin.get_data()
|
|
225
|
+
if signin_data is None:
|
|
714
226
|
raise ValueError(f'signin_file_data is None. ({self.signin_file})')
|
|
715
227
|
if self.signin_file is None:
|
|
716
228
|
raise ValueError(f"signin_file is None.")
|
|
717
229
|
ret = []
|
|
718
|
-
for u in copy.deepcopy(
|
|
230
|
+
for u in copy.deepcopy(signin_data['users']):
|
|
719
231
|
u['password'] = '********'
|
|
720
232
|
if 'apikeys' in u:
|
|
721
233
|
u['apikeys'] = dict([(ak, '********') for ak in u['apikeys']])
|
|
@@ -741,7 +253,8 @@ class Web:
|
|
|
741
253
|
Returns:
|
|
742
254
|
str: ApiKey
|
|
743
255
|
"""
|
|
744
|
-
|
|
256
|
+
signin_data = self.signin.get_data()
|
|
257
|
+
if signin_data is None:
|
|
745
258
|
raise ValueError(f'signin_file_data is None. ({self.signin_file})')
|
|
746
259
|
if self.signin_file is None:
|
|
747
260
|
raise ValueError(f"signin_file is None.")
|
|
@@ -749,10 +262,10 @@ class Web:
|
|
|
749
262
|
raise ValueError(f"User name is not found. ({user})")
|
|
750
263
|
if 'apikey_name' not in user:
|
|
751
264
|
raise ValueError(f"ApiKey name is not found. ({user})")
|
|
752
|
-
if len([u for u in
|
|
265
|
+
if len([u for u in signin_data['users'] if u['name'] == user['name']]) <= 0:
|
|
753
266
|
raise ValueError(f"User name is not exists. ({user})")
|
|
754
267
|
apikey:str = None
|
|
755
|
-
for u in
|
|
268
|
+
for u in signin_data['users']:
|
|
756
269
|
if u['name'] == user['name']:
|
|
757
270
|
if 'apikeys' not in u:
|
|
758
271
|
u['apikeys'] = dict()
|
|
@@ -765,7 +278,7 @@ class Web:
|
|
|
765
278
|
raise ValueError(f"signin_file is None.")
|
|
766
279
|
if self.logger.level == logging.DEBUG:
|
|
767
280
|
self.logger.debug(f"apikey_add: {user} -> {self.signin_file}")
|
|
768
|
-
common.save_yml(self.signin_file,
|
|
281
|
+
common.save_yml(self.signin_file, signin_data)
|
|
769
282
|
return apikey
|
|
770
283
|
|
|
771
284
|
def apikey_del(self, user:Dict[str, Any]):
|
|
@@ -775,7 +288,8 @@ class Web:
|
|
|
775
288
|
Args:
|
|
776
289
|
user (Dict[str, Any]): ユーザー情報
|
|
777
290
|
"""
|
|
778
|
-
|
|
291
|
+
signin_data = self.signin.get_data()
|
|
292
|
+
if signin_data is None:
|
|
779
293
|
raise ValueError(f'signin_file_data is None. ({self.signin_file})')
|
|
780
294
|
if self.signin_file is None:
|
|
781
295
|
raise ValueError(f"signin_file is None.")
|
|
@@ -783,10 +297,10 @@ class Web:
|
|
|
783
297
|
raise ValueError(f"User name is not found. ({user})")
|
|
784
298
|
if 'apikey_name' not in user:
|
|
785
299
|
raise ValueError(f"ApiKey name is not found. ({user})")
|
|
786
|
-
if len([u for u in
|
|
300
|
+
if len([u for u in signin_data['users'] if u['name'] == user['name']]) <= 0:
|
|
787
301
|
raise ValueError(f"User name is not exists. ({user})")
|
|
788
302
|
apikey:str = None
|
|
789
|
-
for u in
|
|
303
|
+
for u in signin_data['users']:
|
|
790
304
|
if u['name'] == user['name']:
|
|
791
305
|
if 'apikeys' not in u:
|
|
792
306
|
continue
|
|
@@ -803,7 +317,7 @@ class Web:
|
|
|
803
317
|
raise ValueError(f"signin_file is None.")
|
|
804
318
|
if self.logger.level == logging.DEBUG:
|
|
805
319
|
self.logger.debug(f"apikey_del: {user} -> {self.signin_file}")
|
|
806
|
-
common.save_yml(self.signin_file,
|
|
320
|
+
common.save_yml(self.signin_file, signin_data)
|
|
807
321
|
|
|
808
322
|
def user_add(self, user:Dict[str, Any]):
|
|
809
323
|
"""
|
|
@@ -812,7 +326,8 @@ class Web:
|
|
|
812
326
|
Args:
|
|
813
327
|
user (Dict[str, Any]): ユーザー情報
|
|
814
328
|
"""
|
|
815
|
-
|
|
329
|
+
signin_data = self.signin.get_data()
|
|
330
|
+
if signin_data is None:
|
|
816
331
|
raise ValueError(f'signin_file_data is None. ({self.signin_file})')
|
|
817
332
|
if self.signin_file is None:
|
|
818
333
|
raise ValueError(f"signin_file is None.")
|
|
@@ -838,26 +353,26 @@ class Web:
|
|
|
838
353
|
for gn in user['groups']:
|
|
839
354
|
if len(self.group_list(gn)) <= 0:
|
|
840
355
|
raise ValueError(f"Group is not found. ({gn})")
|
|
841
|
-
if len([u for u in
|
|
356
|
+
if len([u for u in signin_data['users'] if u['uid'] == user['uid']]) > 0:
|
|
842
357
|
raise ValueError(f"User uid is already exists. ({user})")
|
|
843
|
-
if len([u for u in
|
|
358
|
+
if len([u for u in signin_data['users'] if u['name'] == user['name']]) > 0:
|
|
844
359
|
raise ValueError(f"User name is already exists. ({user})")
|
|
845
360
|
if hash not in ['oauth2', 'plain', 'md5', 'sha1', 'sha256']:
|
|
846
361
|
raise ValueError(f"User hash is not supported. ({user})")
|
|
847
|
-
jadge, msg = self.check_password_policy(user['name'], '', user['password'])
|
|
362
|
+
jadge, msg = self.signin.check_password_policy(user['name'], '', user['password'])
|
|
848
363
|
if not jadge:
|
|
849
364
|
raise ValueError(msg)
|
|
850
365
|
if hash != 'plain':
|
|
851
366
|
user['password'] = common.hash_password(user['password'], hash if hash != 'oauth2' else 'sha1')
|
|
852
367
|
else:
|
|
853
368
|
user['password'] = user['password']
|
|
854
|
-
|
|
369
|
+
signin_data['users'].append(user)
|
|
855
370
|
if self.logger.level == logging.DEBUG:
|
|
856
371
|
self.logger.debug(f"user_add: {user} -> {self.signin_file}")
|
|
857
372
|
# パスワード更新日時の保存
|
|
858
373
|
self.user_data(None, user['uid'], user['name'], 'password', 'last_update', datetime.datetime.now())
|
|
859
374
|
# サインインファイルの保存
|
|
860
|
-
common.save_yml(self.signin_file,
|
|
375
|
+
common.save_yml(self.signin_file, signin_data)
|
|
861
376
|
|
|
862
377
|
def user_edit(self, user:Dict[str, Any]):
|
|
863
378
|
"""
|
|
@@ -866,7 +381,8 @@ class Web:
|
|
|
866
381
|
Args:
|
|
867
382
|
user (Dict[str, Any]): ユーザー情報
|
|
868
383
|
"""
|
|
869
|
-
|
|
384
|
+
signin_data = self.signin.get_data()
|
|
385
|
+
if signin_data is None:
|
|
870
386
|
raise ValueError(f'signin_file_data is None. ({self.signin_file})')
|
|
871
387
|
if self.signin_file is None:
|
|
872
388
|
raise ValueError(f"signin_file is None.")
|
|
@@ -890,17 +406,17 @@ class Web:
|
|
|
890
406
|
for gn in user['groups']:
|
|
891
407
|
if len(self.group_list(gn)) <= 0:
|
|
892
408
|
raise ValueError(f"Group is not found. ({gn})")
|
|
893
|
-
if len([u for u in
|
|
409
|
+
if len([u for u in signin_data['users'] if u['uid'] == user['uid']]) <= 0:
|
|
894
410
|
raise ValueError(f"User uid is not found. ({user})")
|
|
895
|
-
if len([u for u in
|
|
411
|
+
if len([u for u in signin_data['users'] if u['name'] == user['name']]) <= 0:
|
|
896
412
|
raise ValueError(f"User name is not found. ({user})")
|
|
897
413
|
if hash not in ['oauth2', 'plain', 'md5', 'sha1', 'sha256']:
|
|
898
414
|
raise ValueError(f"User hash is not supported. ({user})")
|
|
899
|
-
for u in
|
|
415
|
+
for u in signin_data['users']:
|
|
900
416
|
if u['uid'] == user['uid']:
|
|
901
417
|
u['name'] = user['name']
|
|
902
418
|
if 'password' in user and user['password'] != '':
|
|
903
|
-
jadge, msg = self.check_password_policy(user['name'], u['password'], user['password'])
|
|
419
|
+
jadge, msg = self.signin.check_password_policy(user['name'], u['password'], user['password'])
|
|
904
420
|
if not jadge:
|
|
905
421
|
raise ValueError(msg)
|
|
906
422
|
if hash != 'plain':
|
|
@@ -915,7 +431,7 @@ class Web:
|
|
|
915
431
|
if self.logger.level == logging.DEBUG:
|
|
916
432
|
self.logger.debug(f"user_edit: {user} -> {self.signin_file}")
|
|
917
433
|
# サインインファイルの保存
|
|
918
|
-
common.save_yml(self.signin_file,
|
|
434
|
+
common.save_yml(self.signin_file, signin_data)
|
|
919
435
|
|
|
920
436
|
def user_del(self, uid:int):
|
|
921
437
|
"""
|
|
@@ -924,7 +440,8 @@ class Web:
|
|
|
924
440
|
Args:
|
|
925
441
|
uid (int): ユーザーID
|
|
926
442
|
"""
|
|
927
|
-
|
|
443
|
+
signin_data = self.signin.get_data()
|
|
444
|
+
if signin_data is None:
|
|
928
445
|
raise ValueError(f'signin_file_data is None. ({self.signin_file})')
|
|
929
446
|
if self.signin_file is None:
|
|
930
447
|
raise ValueError(f"signin_file is None.")
|
|
@@ -932,13 +449,13 @@ class Web:
|
|
|
932
449
|
uid = int(uid)
|
|
933
450
|
except:
|
|
934
451
|
raise ValueError(f"User uid is not number. ({uid})")
|
|
935
|
-
users = [u for u in
|
|
936
|
-
if len(users) == len(
|
|
452
|
+
users = [u for u in signin_data['users'] if u['uid'] != uid]
|
|
453
|
+
if len(users) == len(signin_data['users']):
|
|
937
454
|
raise ValueError(f"User uid is not found. ({uid})")
|
|
938
|
-
|
|
455
|
+
signin_data['users'] = users
|
|
939
456
|
if self.logger.level == logging.DEBUG:
|
|
940
457
|
self.logger.debug(f"user_del: {uid} -> {self.signin_file}")
|
|
941
|
-
common.save_yml(self.signin_file,
|
|
458
|
+
common.save_yml(self.signin_file, signin_data)
|
|
942
459
|
|
|
943
460
|
def group_list(self, name:str=None) -> List[Dict[str, Any]]:
|
|
944
461
|
"""
|
|
@@ -950,11 +467,12 @@ class Web:
|
|
|
950
467
|
Returns:
|
|
951
468
|
List[Dict[str, Any]]: グループ一覧
|
|
952
469
|
"""
|
|
953
|
-
|
|
470
|
+
signin_data = self.signin.get_data()
|
|
471
|
+
if signin_data is None:
|
|
954
472
|
raise ValueError(f'signin_file_data is None. ({self.signin_file})')
|
|
955
473
|
if name is None:
|
|
956
|
-
return copy.deepcopy(
|
|
957
|
-
for g in copy.deepcopy(
|
|
474
|
+
return copy.deepcopy(signin_data['groups'])
|
|
475
|
+
for g in copy.deepcopy(signin_data['groups']):
|
|
958
476
|
if g['name'] == name:
|
|
959
477
|
return [g]
|
|
960
478
|
return []
|
|
@@ -966,7 +484,8 @@ class Web:
|
|
|
966
484
|
Args:
|
|
967
485
|
group (Dict[str, Any]): グループ情報
|
|
968
486
|
"""
|
|
969
|
-
|
|
487
|
+
signin_data = self.signin.get_data()
|
|
488
|
+
if signin_data is None:
|
|
970
489
|
raise ValueError(f'signin_file_data is None. ({self.signin_file})')
|
|
971
490
|
if self.signin_file is None:
|
|
972
491
|
raise ValueError(f"signin_file is None.")
|
|
@@ -980,20 +499,20 @@ class Web:
|
|
|
980
499
|
raise ValueError(f"Group name is not found. ({group})")
|
|
981
500
|
if 'parent' in group and (group['parent'] is None or group['parent'] == ''):
|
|
982
501
|
del group['parent']
|
|
983
|
-
elif 'parent' in group and group['parent'] not in [g['name'] for g in
|
|
502
|
+
elif 'parent' in group and group['parent'] not in [g['name'] for g in signin_data['groups']]:
|
|
984
503
|
raise ValueError(f"Group parent is not found. ({group})")
|
|
985
504
|
if 'parent' in group and group['parent'] == group['name']:
|
|
986
505
|
raise ValueError(f"Group parent is same as group name. ({group})")
|
|
987
|
-
if len([g for g in
|
|
506
|
+
if len([g for g in signin_data['groups'] if g['gid'] == group['gid']]) > 0:
|
|
988
507
|
raise ValueError(f"Group gid is already exists. ({group})")
|
|
989
|
-
if len([g for g in
|
|
508
|
+
if len([g for g in signin_data['groups'] if g['name'] == group['name']]) > 0:
|
|
990
509
|
raise ValueError(f"Group name is already exists. ({group})")
|
|
991
|
-
|
|
510
|
+
signin_data['groups'].append(group)
|
|
992
511
|
if self.signin_file is None:
|
|
993
512
|
raise ValueError(f"signin_file is None.")
|
|
994
513
|
if self.logger.level == logging.DEBUG:
|
|
995
514
|
self.logger.debug(f"group_add: {group} -> {self.signin_file}")
|
|
996
|
-
common.save_yml(self.signin_file,
|
|
515
|
+
common.save_yml(self.signin_file, signin_data)
|
|
997
516
|
|
|
998
517
|
def group_edit(self, group:Dict[str, Any]):
|
|
999
518
|
"""
|
|
@@ -1002,7 +521,8 @@ class Web:
|
|
|
1002
521
|
Args:
|
|
1003
522
|
group (Dict[str, Any]): グループ情報
|
|
1004
523
|
"""
|
|
1005
|
-
|
|
524
|
+
signin_data = self.signin.get_data()
|
|
525
|
+
if signin_data is None:
|
|
1006
526
|
raise ValueError(f'signin_file_data is None. ({self.signin_file})')
|
|
1007
527
|
if self.signin_file is None:
|
|
1008
528
|
raise ValueError(f"signin_file is None.")
|
|
@@ -1016,15 +536,15 @@ class Web:
|
|
|
1016
536
|
raise ValueError(f"Group name is not found. ({group})")
|
|
1017
537
|
if 'parent' in group and (group['parent'] is None or group['parent'] == ''):
|
|
1018
538
|
del group['parent']
|
|
1019
|
-
elif 'parent' in group and group['parent'] not in [g['name'] for g in
|
|
539
|
+
elif 'parent' in group and group['parent'] not in [g['name'] for g in signin_data['groups']]:
|
|
1020
540
|
raise ValueError(f"Group parent is not found. ({group})")
|
|
1021
541
|
if 'parent' in group and group['parent'] == group['name']:
|
|
1022
542
|
raise ValueError(f"Group parent is same as group name. ({group})")
|
|
1023
|
-
if len([g for g in
|
|
543
|
+
if len([g for g in signin_data['groups'] if g['gid'] == group['gid']]) <= 0:
|
|
1024
544
|
raise ValueError(f"Group gid is not found. ({group})")
|
|
1025
|
-
if len([g for g in
|
|
545
|
+
if len([g for g in signin_data['groups'] if g['name'] == group['name']]) <= 0:
|
|
1026
546
|
raise ValueError(f"Group name is not found. ({group})")
|
|
1027
|
-
for g in
|
|
547
|
+
for g in signin_data['groups']:
|
|
1028
548
|
if g['gid'] == group['gid']:
|
|
1029
549
|
g['name'] = group['name']
|
|
1030
550
|
g['parent'] = group['parent']
|
|
@@ -1032,7 +552,7 @@ class Web:
|
|
|
1032
552
|
raise ValueError(f"signin_file is None.")
|
|
1033
553
|
if self.logger.level == logging.DEBUG:
|
|
1034
554
|
self.logger.debug(f"group_edit: {group} -> {self.signin_file}")
|
|
1035
|
-
common.save_yml(self.signin_file,
|
|
555
|
+
common.save_yml(self.signin_file, signin_data)
|
|
1036
556
|
|
|
1037
557
|
def group_del(self, gid:int):
|
|
1038
558
|
"""
|
|
@@ -1041,7 +561,8 @@ class Web:
|
|
|
1041
561
|
Args:
|
|
1042
562
|
gid (int): グループID
|
|
1043
563
|
"""
|
|
1044
|
-
|
|
564
|
+
signin_data = self.signin.get_data()
|
|
565
|
+
if signin_data is None:
|
|
1045
566
|
raise ValueError(f'signin_file_data is None. ({self.signin_file})')
|
|
1046
567
|
if self.signin_file is None:
|
|
1047
568
|
raise ValueError(f"signin_file is None.")
|
|
@@ -1051,41 +572,41 @@ class Web:
|
|
|
1051
572
|
raise ValueError(f"Group gid is not number. ({gid})")
|
|
1052
573
|
# グループがユーザーに使用されているかチェック
|
|
1053
574
|
user_group_ids = []
|
|
1054
|
-
for user in
|
|
575
|
+
for user in signin_data['users']:
|
|
1055
576
|
for group in user['groups']:
|
|
1056
|
-
user_group_ids += [g['gid'] for g in
|
|
577
|
+
user_group_ids += [g['gid'] for g in signin_data['groups'] if g['name'] == group]
|
|
1057
578
|
if gid in user_group_ids:
|
|
1058
579
|
raise ValueError(f"Group gid is used by user. ({gid})")
|
|
1059
580
|
# グループが親グループに使用されているかチェック
|
|
1060
581
|
parent_group_ids = []
|
|
1061
|
-
for group in
|
|
582
|
+
for group in signin_data['groups']:
|
|
1062
583
|
if 'parent' in group:
|
|
1063
|
-
parent_group_ids += [g['gid'] for g in
|
|
584
|
+
parent_group_ids += [g['gid'] for g in signin_data['groups'] if g['name'] == group['parent']]
|
|
1064
585
|
if gid in parent_group_ids:
|
|
1065
586
|
raise ValueError(f"Group gid is used by parent group. ({gid})")
|
|
1066
587
|
# グループがcmdruleグループに使用されているかチェック
|
|
1067
588
|
cmdrule_group_ids = []
|
|
1068
|
-
for rule in
|
|
589
|
+
for rule in signin_data['cmdrule']['rules']:
|
|
1069
590
|
for group in rule['groups']:
|
|
1070
|
-
cmdrule_group_ids += [g['gid'] for g in
|
|
591
|
+
cmdrule_group_ids += [g['gid'] for g in signin_data['groups'] if g['name'] == group]
|
|
1071
592
|
if gid in cmdrule_group_ids:
|
|
1072
593
|
raise ValueError(f"Group gid is used by cmdrule group. ({gid})")
|
|
1073
594
|
# グループがpathruleグループに使用されているかチェック
|
|
1074
595
|
pathrule_group_ids = []
|
|
1075
|
-
for rule in
|
|
596
|
+
for rule in signin_data['pathrule']['rules']:
|
|
1076
597
|
for group in rule['groups']:
|
|
1077
|
-
pathrule_group_ids += [g['gid'] for g in
|
|
598
|
+
pathrule_group_ids += [g['gid'] for g in signin_data['groups'] if g['name'] == group]
|
|
1078
599
|
if gid in pathrule_group_ids:
|
|
1079
600
|
raise ValueError(f"Group gid is used by pathrule group. ({gid})")
|
|
1080
601
|
|
|
1081
602
|
# グループ削除
|
|
1082
|
-
groups = [g for g in
|
|
1083
|
-
if len(groups) == len(
|
|
603
|
+
groups = [g for g in signin_data['groups'] if g['gid'] != gid]
|
|
604
|
+
if len(groups) == len(signin_data['groups']):
|
|
1084
605
|
raise ValueError(f"Group gid is not found. ({gid})")
|
|
1085
|
-
|
|
606
|
+
signin_data['groups'] = groups
|
|
1086
607
|
if self.logger.level == logging.DEBUG:
|
|
1087
608
|
self.logger.debug(f"group_del: {gid} -> {self.signin_file}")
|
|
1088
|
-
common.save_yml(self.signin_file,
|
|
609
|
+
common.save_yml(self.signin_file, signin_data)
|
|
1089
610
|
|
|
1090
611
|
def user_data(self, req:Request, uid:str, user_name:str, categoly:str, key:str=None, val:Any=None, delkey:bool=False) -> Any:
|
|
1091
612
|
"""
|
|
@@ -1137,7 +658,7 @@ class Web:
|
|
|
1137
658
|
|
|
1138
659
|
def start(self, allow_host:str="0.0.0.0", listen_port:int=8081, ssl_listen_port:int=8443,
|
|
1139
660
|
ssl_cert:Path=None, ssl_key:Path=None, ssl_keypass:str=None, ssl_ca_certs:Path=None,
|
|
1140
|
-
session_domain:str=None, session_path:str='/', session_secure:bool=False, session_timeout:int=
|
|
661
|
+
session_domain:str=None, session_path:str='/', session_secure:bool=False, session_timeout:int=900, outputs_key:List[str]=[],
|
|
1141
662
|
guvicorn_workers:int=-1, guvicorn_timeout:int=30):
|
|
1142
663
|
"""
|
|
1143
664
|
Webサーバを起動する
|
|
@@ -1153,7 +674,7 @@ class Web:
|
|
|
1153
674
|
session_domain (str, optional): セッションドメイン. Defaults to None.
|
|
1154
675
|
session_path (str, optional): セッションパス. Defaults to '/'.
|
|
1155
676
|
session_secure (bool, optional): セッションセキュア. Defaults to False.
|
|
1156
|
-
session_timeout (int, optional): セッションタイムアウト. Defaults to
|
|
677
|
+
session_timeout (int, optional): セッションタイムアウト. Defaults to 900.
|
|
1157
678
|
outputs_key (list, optional): 出力キー. Defaults to [].
|
|
1158
679
|
guvicorn_workers (int, optional): Gunicornワーカー数. Defaults to -1.
|
|
1159
680
|
guvicorn_timeout (int, optional): Gunicornタイムアウト. Defaults to 30.
|