cmdbox 0.5.3__py3-none-any.whl → 0.5.4__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 (86) hide show
  1. cmdbox/app/auth/__init__.py +0 -0
  2. cmdbox/app/auth/azure_signin.py +38 -0
  3. cmdbox/app/auth/azure_signin_saml.py +12 -0
  4. cmdbox/app/auth/github_signin.py +38 -0
  5. cmdbox/app/auth/google_signin.py +32 -0
  6. cmdbox/app/auth/signin.py +47 -4
  7. cmdbox/app/auth/signin_saml.py +61 -0
  8. cmdbox/app/edge.py +198 -61
  9. cmdbox/app/feature.py +2 -1
  10. cmdbox/app/features/cli/audit_base.py +1 -1
  11. cmdbox/app/features/cli/cmdbox_audit_createdb.py +1 -1
  12. cmdbox/app/features/cli/cmdbox_audit_write.py +4 -0
  13. cmdbox/app/features/cli/cmdbox_client_file_copy.py +1 -1
  14. cmdbox/app/features/cli/cmdbox_client_file_download.py +1 -1
  15. cmdbox/app/features/cli/cmdbox_client_file_list.py +1 -1
  16. cmdbox/app/features/cli/cmdbox_client_file_mkdir.py +1 -1
  17. cmdbox/app/features/cli/cmdbox_client_file_move.py +1 -1
  18. cmdbox/app/features/cli/cmdbox_client_file_remove.py +1 -1
  19. cmdbox/app/features/cli/cmdbox_client_file_rmdir.py +1 -1
  20. cmdbox/app/features/cli/cmdbox_client_file_upload.py +1 -1
  21. cmdbox/app/features/cli/cmdbox_client_server_info.py +1 -1
  22. cmdbox/app/features/cli/cmdbox_edge_config.py +19 -5
  23. cmdbox/app/features/cli/cmdbox_gui_start.py +1 -1
  24. cmdbox/app/features/cli/cmdbox_server_start.py +1 -1
  25. cmdbox/app/features/cli/cmdbox_server_stop.py +1 -1
  26. cmdbox/app/features/cli/cmdbox_web_apikey_add.py +1 -1
  27. cmdbox/app/features/cli/cmdbox_web_apikey_del.py +1 -1
  28. cmdbox/app/features/cli/cmdbox_web_group_add.py +1 -1
  29. cmdbox/app/features/cli/cmdbox_web_group_del.py +1 -1
  30. cmdbox/app/features/cli/cmdbox_web_group_edit.py +1 -1
  31. cmdbox/app/features/cli/cmdbox_web_group_list.py +1 -1
  32. cmdbox/app/features/cli/cmdbox_web_start.py +1 -1
  33. cmdbox/app/features/cli/cmdbox_web_user_add.py +4 -4
  34. cmdbox/app/features/cli/cmdbox_web_user_del.py +1 -1
  35. cmdbox/app/features/cli/cmdbox_web_user_edit.py +4 -4
  36. cmdbox/app/features/cli/cmdbox_web_user_list.py +1 -1
  37. cmdbox/app/features/web/cmdbox_web_audit.py +7 -1
  38. cmdbox/app/features/web/cmdbox_web_do_signin.py +79 -103
  39. cmdbox/app/features/web/cmdbox_web_exec_cmd.py +2 -2
  40. cmdbox/app/features/web/cmdbox_web_signin.py +23 -1
  41. cmdbox/app/options.py +9 -0
  42. cmdbox/app/server.py +15 -3
  43. cmdbox/app/web.py +13 -12
  44. cmdbox/extensions/features.yml +4 -4
  45. cmdbox/extensions/sample_project/sample/app/features/cli/sample_server_time.py +1 -1
  46. cmdbox/extensions/sample_project/sample/extensions/features.yml +23 -0
  47. cmdbox/extensions/sample_project/sample/extensions/user_list.yml +40 -6
  48. cmdbox/extensions/user_list.yml +36 -6
  49. cmdbox/licenses/LICENSE.async-timeout.5.0.1(Apache Software License).txt +13 -0
  50. cmdbox/licenses/files.txt +10 -9
  51. cmdbox/version.py +2 -2
  52. cmdbox/web/assets/cmdbox/audit.js +98 -34
  53. cmdbox/web/assets/cmdbox/signin.js +13 -0
  54. cmdbox/web/assets/cmdbox/users.js +1 -1
  55. cmdbox/web/audit.html +69 -44
  56. cmdbox/web/signin.html +10 -6
  57. {cmdbox-0.5.3.dist-info → cmdbox-0.5.4.dist-info}/METADATA +69 -15
  58. {cmdbox-0.5.3.dist-info → cmdbox-0.5.4.dist-info}/RECORD +71 -79
  59. cmdbox/app/features/web/cmdbox_web_load_pin.py +0 -43
  60. cmdbox/app/features/web/cmdbox_web_save_pin.py +0 -42
  61. cmdbox/licenses/LICENSE.argcomplete.3.6.1(Apache Software License).txt +0 -177
  62. cmdbox/licenses/LICENSE.gevent.25.4.1(MIT).txt +0 -25
  63. cmdbox/licenses/LICENSE.greenlet.3.2.0(MIT AND Python-2.0).txt +0 -30
  64. cmdbox/licenses/LICENSE.pillow.11.1.0(CMU License (MIT-CMU)).txt +0 -1213
  65. cmdbox/licenses/LICENSE.prompt_toolkit.3.0.50(BSD License).txt +0 -27
  66. cmdbox/licenses/LICENSE.psycopg-pool.3.2.6(GNU Lesser General Public License v3 (LGPLv3)).txt +0 -165
  67. cmdbox/licenses/LICENSE.pydantic.2.11.1(MIT License).txt +0 -21
  68. cmdbox/licenses/LICENSE.pydantic_core.2.33.0(MIT License).txt +0 -21
  69. cmdbox/licenses/LICENSE.starlette.0.46.1(BSD License).txt +0 -27
  70. cmdbox/licenses/LICENSE.typing_extensions.4.13.0(UNKNOWN).txt +0 -279
  71. cmdbox/licenses/LICENSE.urllib3.2.3.0(MIT License).txt +0 -21
  72. cmdbox/licenses/LICENSE.uvicorn.0.34.1(BSD License).txt +0 -27
  73. cmdbox/licenses/LICENSE.watchfiles.1.0.4(MIT License).txt +0 -21
  74. /cmdbox/licenses/{LICENSE.certifi.2025.1.31(Mozilla Public License 2.0 (MPL 2.0)).txt → LICENSE.certifi.2025.4.26(Mozilla Public License 2.0 (MPL 2.0)).txt} +0 -0
  75. /cmdbox/licenses/{LICENSE.gevent.24.11.1(MIT License).txt → LICENSE.gevent.25.4.2(MIT).txt} +0 -0
  76. /cmdbox/licenses/{LICENSE.greenlet.3.1.1(MIT License).txt → LICENSE.greenlet.3.2.1(MIT AND Python-2.0).txt} +0 -0
  77. /cmdbox/licenses/{LICENSE.h11.0.14.0(MIT License).txt → LICENSE.h11.0.16.0(MIT License).txt} +0 -0
  78. /cmdbox/licenses/{LICENSE.importlib_metadata.8.6.1(Apache Software License).txt → LICENSE.importlib_metadata.8.7.0(Apache Software License).txt} +0 -0
  79. /cmdbox/licenses/{LICENSE.more-itertools.10.6.0(MIT License).txt → LICENSE.more-itertools.10.7.0(MIT License).txt} +0 -0
  80. /cmdbox/licenses/{LICENSE.numpy.2.2.4(BSD License).txt → LICENSE.numpy.2.2.5(BSD License).txt} +0 -0
  81. /cmdbox/licenses/{LICENSE.packaging.24.2(Apache Software License; BSD License).txt → LICENSE.packaging.25.0(Apache Software License; BSD License).txt} +0 -0
  82. /cmdbox/licenses/{LICENSE.uvicorn.0.34.0(BSD License).txt → LICENSE.uvicorn.0.34.2(BSD License).txt} +0 -0
  83. {cmdbox-0.5.3.dist-info → cmdbox-0.5.4.dist-info}/LICENSE +0 -0
  84. {cmdbox-0.5.3.dist-info → cmdbox-0.5.4.dist-info}/WHEEL +0 -0
  85. {cmdbox-0.5.3.dist-info → cmdbox-0.5.4.dist-info}/entry_points.txt +0 -0
  86. {cmdbox-0.5.3.dist-info → cmdbox-0.5.4.dist-info}/top_level.txt +0 -0
File without changes
@@ -0,0 +1,38 @@
1
+ from cmdbox.app.auth.signin import Signin
2
+ from fastapi import Request, Response
3
+ from typing import Any, Dict
4
+ import requests
5
+ import urllib
6
+
7
+
8
+ class AzureSignin(Signin):
9
+ @classmethod
10
+ def get_email(cls, data:Any) -> str:
11
+ user_info_resp = requests.get(
12
+ url='https://graph.microsoft.com/v1.0/me',
13
+ #url='https://graph.microsoft.com/v1.0/me/transitiveMemberOf?$Top=999',
14
+ headers={'Authorization': f'Bearer {data}'}
15
+ )
16
+ user_info_resp.raise_for_status()
17
+ user_info_json = user_info_resp.json()
18
+ if isinstance(user_info_json, dict):
19
+ email = user_info_json.get('mail', 'notfound')
20
+ return email
21
+ return 'notfound'
22
+
23
+ def request_access_token(self, conf:Dict, req:Request, res:Response) -> str:
24
+ headers = {'Content-Type': 'application/x-www-form-urlencoded',
25
+ 'Accept': 'application/json'}
26
+ data = {'tenant': conf['tenant_id'],
27
+ 'code': req.query_params['code'],
28
+ 'scope': " ".join(conf['scope']),
29
+ 'client_id': conf['client_id'],
30
+ #'client_secret': conf['client_secret'],
31
+ 'redirect_uri': conf['redirect_uri'],
32
+ 'grant_type': 'authorization_code'}
33
+ query = '&'.join([f'{k}={urllib.parse.quote(v)}' for k, v in data.items()])
34
+ # アクセストークン取得
35
+ token_resp = requests.post(url=f'https://login.microsoftonline.com/{conf["tenant_id"]}/oauth2/v2.0/token', headers=headers, data=query)
36
+ token_resp.raise_for_status()
37
+ token_json = token_resp.json()
38
+ return token_json['access_token']
@@ -0,0 +1,12 @@
1
+ from cmdbox.app.auth.signin_saml import SigninSAML
2
+ from typing import Any
3
+
4
+
5
+ class AzyreSigninSAML(SigninSAML):
6
+ @classmethod
7
+ def get_email(cls, data:Any) -> str:
8
+ user_info_json = data.get_attributes()
9
+ if isinstance(user_info_json, dict):
10
+ email = user_info_json.get('http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress', ['notfound'])
11
+ return email[0] if len(email) > 0 else 'notfound'
12
+ return 'notfound'
@@ -0,0 +1,38 @@
1
+ from cmdbox.app.auth.signin import Signin
2
+ from fastapi import Request, Response
3
+ from typing import Any, Dict
4
+ import requests
5
+ import urllib.parse
6
+
7
+
8
+ class GithubSignin(Signin):
9
+ @classmethod
10
+ def get_email(cls, data:Any) -> str:
11
+ user_info_resp = requests.get(
12
+ url='https://api.github.com/user/emails',
13
+ headers={'Authorization': f'Bearer {data}'}
14
+ )
15
+ user_info_resp.raise_for_status()
16
+ user_info_json = user_info_resp.json()
17
+ if type(user_info_json) == list:
18
+ email = 'notfound'
19
+ for u in user_info_json:
20
+ if u['primary']:
21
+ email = u['email']
22
+ break
23
+ return email
24
+ return 'notfound'
25
+
26
+ def request_access_token(self, conf:Dict, req:Request, res:Response) -> str:
27
+ headers = {'Content-Type': 'application/x-www-form-urlencoded',
28
+ 'Accept': 'application/json'}
29
+ data = {'code': req.query_params['code'],
30
+ 'client_id': conf['client_id'],
31
+ 'client_secret': conf['client_secret'],
32
+ 'redirect_uri': conf['redirect_uri']}
33
+ query = '&'.join([f'{k}={urllib.parse.quote(v)}' for k, v in data.items()])
34
+ # アクセストークン取得
35
+ token_resp = requests.post(url='https://github.com/login/oauth/access_token', headers=headers, data=query)
36
+ token_resp.raise_for_status()
37
+ token_json = token_resp.json()
38
+ return token_json['access_token']
@@ -0,0 +1,32 @@
1
+ from cmdbox.app.auth.signin import Signin
2
+ from fastapi import Request, Response
3
+ from typing import Any, Dict
4
+ import requests
5
+ import urllib.parse
6
+
7
+
8
+ class GoogleSignin(Signin):
9
+ @classmethod
10
+ def get_email(cls, data:Any) -> str:
11
+ user_info_resp = requests.get(
12
+ url='https://www.googleapis.com/oauth2/v1/userinfo',
13
+ headers={'Authorization': f'Bearer {data}'}
14
+ )
15
+ user_info_resp.raise_for_status()
16
+ user_info_json = user_info_resp.json()
17
+ return user_info_json['email'] if 'email' in user_info_json else 'notfound'
18
+
19
+ def request_access_token(self, conf:Dict, req:Request, res:Response) -> str:
20
+ headers = {'Content-Type': 'application/x-www-form-urlencoded'}
21
+ next = req.query_params['state']
22
+ data = {'code': req.query_params['code'],
23
+ 'client_id': conf['client_id'],
24
+ 'client_secret': conf['client_secret'],
25
+ 'redirect_uri': conf['redirect_uri'],
26
+ 'grant_type': 'authorization_code'}
27
+ query = '&'.join([f'{k}={urllib.parse.quote(v)}' for k, v in data.items()])
28
+ # アクセストークン取得
29
+ token_resp = requests.post(url='https://oauth2.googleapis.com/token', headers=headers, data=query)
30
+ token_resp.raise_for_status()
31
+ token_json = token_resp.json()
32
+ return token_json['access_token']
cmdbox/app/auth/signin.py CHANGED
@@ -27,13 +27,12 @@ class Signin(object):
27
27
  """
28
28
  return self.signin_file_data
29
29
 
30
- def jadge(self, access_token:str, email:str) -> Tuple[bool, Dict[str, Any]]:
30
+ def jadge(self, email:str) -> Tuple[bool, Dict[str, Any]]:
31
31
  """
32
32
  サインインを成功させるかどうかを判定します。
33
33
  返すユーザーデータには、uid, name, email, groups, hash が必要です。
34
34
 
35
35
  Args:
36
- access_token (str): アクセストークン
37
36
  email (str): メールアドレス
38
37
 
39
38
  Returns:
@@ -203,8 +202,8 @@ class Signin(object):
203
202
  raise HTTPException(status_code=500, detail=f'signin_file format error. "password" not found or empty. ({signin_file})')
204
203
  if 'hash' not in user or user['hash'] is None:
205
204
  raise HTTPException(status_code=500, detail=f'signin_file format error. "hash" not found or empty. ({signin_file})')
206
- if user['hash'] not in ['oauth2', 'plain', 'md5', 'sha1', 'sha256']:
207
- raise HTTPException(status_code=500, detail=f'signin_file format error. Algorithms not supported. ({signin_file}). hash={user["hash"]} "oauth2", "plain", "md5", "sha1", "sha256" only.')
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.')
208
207
  if 'groups' not in user or type(user['groups']) is not list:
209
208
  raise HTTPException(status_code=500, detail=f'signin_file format error. "groups" not found or not list type. ({signin_file})')
210
209
  if len([ug for ug in user['groups'] if ug not in groups]) > 0:
@@ -416,6 +415,24 @@ class Signin(object):
416
415
  raise HTTPException(status_code=500, detail=f'signin_file format error. "scope" not list type in "azure". ({signin_file})')
417
416
  if 'signin_module' not in yml['oauth2']['providers']['azure']:
418
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})')
419
436
  # フォーマットチェックOK
420
437
  return yml
421
438
 
@@ -632,3 +649,29 @@ class Signin(object):
632
649
  return False, f"Password policy error. not_contain_username=True"
633
650
  self.logger.info(f"Password policy OK.")
634
651
  return True, "Password policy OK."
652
+
653
+ def request_access_token(self, conf:Dict, req:Request, res:Response) -> str:
654
+ """
655
+ アクセストークンを取得します
656
+
657
+ Args:
658
+ conf (Dict): サインインモジュールの設定
659
+ req (Request): リクエスト
660
+ res (Response): レスポンス
661
+
662
+ Returns:
663
+ str: アクセストークン
664
+ """
665
+ raise NotImplementedError("request_access_token() is not implemented.")
666
+
667
+ def get_email(self, data:Any) -> str:
668
+ """
669
+ アクセストークンからメールアドレスを取得します
670
+
671
+ Args:
672
+ data (str): アクセストークン又は属性データ
673
+
674
+ Returns:
675
+ str: メールアドレス
676
+ """
677
+ return self.__class__.get_email(data)
@@ -0,0 +1,61 @@
1
+ from cmdbox.app.auth.signin import Signin
2
+ from fastapi import Request, Response
3
+ from typing import Any, Dict, Tuple
4
+ import copy
5
+ import logging
6
+
7
+
8
+ class SigninSAML(Signin):
9
+
10
+ def jadge(self, email:str) -> Tuple[bool, Dict[str, Any]]:
11
+ """
12
+ サインインを成功させるかどうかを判定します。
13
+ 返すユーザーデータには、uid, name, email, groups, hash が必要です。
14
+
15
+ Args:
16
+ email (str): メールアドレス
17
+
18
+ Returns:
19
+ Tuple[bool, Dict[str, Any]]: (成功かどうか, ユーザーデータ)
20
+ """
21
+ copy_signin_data = copy.deepcopy(self.signin_file_data)
22
+ users = [u for u in copy_signin_data['users'] if u['email'] == email and u['hash'] == 'saml']
23
+ return len(users) > 0, users[0] if len(users) > 0 else None
24
+
25
+ async def make_saml(self, prov:str, next:str, form_data:Dict[str, Any], req:Request, res:Response) -> Any:
26
+ """
27
+ SAML認証のリダイレクトURLを取得する
28
+ Args:
29
+ prov (str): プロバイダ名
30
+ next (str): リダイレクト先のURL
31
+ req (Request): リクエスト
32
+ res (Response): レスポンス
33
+ Returns:
34
+ OneLogin_Saml2_Auth: SAML認証オブジェクト
35
+ """
36
+ sd = self.get_data()
37
+ saml_settings = dict(
38
+ strict=False,
39
+ debug=self.logger.level==logging.DEBUG,
40
+ idp=sd['saml']['providers'][prov]['idp'],
41
+ sp=sd['saml']['providers'][prov]['sp'])
42
+ # SAML認証のリダイレクトURLを取得
43
+ request_data = dict(
44
+ https='on' if req.url.scheme=='https' else 'off',
45
+ http_host=req.client.host,
46
+ server_port=req.url.port,
47
+ script_name=f'{req.url.path}?next={next}',
48
+ post_data=dict(),
49
+ get_data=dict(),
50
+ )
51
+ if (req.query_params):
52
+ request_data["get_data"] = req.query_params,
53
+ if "SAMLResponse" in form_data:
54
+ SAMLResponse = form_data["SAMLResponse"]
55
+ request_data["post_data"]["SAMLResponse"] = SAMLResponse
56
+ if "RelayState" in form_data:
57
+ RelayState = form_data["RelayState"]
58
+ request_data["post_data"]["RelayState"] = RelayState
59
+ from onelogin.saml2.auth import OneLogin_Saml2_Auth
60
+ auth = OneLogin_Saml2_Auth(request_data=request_data, old_settings=saml_settings)
61
+ return auth