cmdbox 0.5.3.1__py3-none-any.whl → 0.6.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of cmdbox might be problematic. Click here for more details.

Files changed (188) 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 +490 -287
  7. cmdbox/app/auth/signin_saml.py +61 -0
  8. cmdbox/app/common.py +48 -3
  9. cmdbox/app/edge.py +182 -213
  10. cmdbox/app/edge_tool.py +177 -0
  11. cmdbox/app/feature.py +10 -10
  12. cmdbox/app/features/cli/agent_base.py +477 -0
  13. cmdbox/app/features/cli/audit_base.py +1 -1
  14. cmdbox/app/features/cli/cmdbox_audit_search.py +24 -1
  15. cmdbox/app/features/cli/cmdbox_client_file_download.py +1 -1
  16. cmdbox/app/features/cli/cmdbox_cmd_list.py +105 -0
  17. cmdbox/app/features/cli/cmdbox_cmd_load.py +104 -0
  18. cmdbox/app/features/cli/cmdbox_edge_config.py +21 -7
  19. cmdbox/app/features/cli/cmdbox_edge_start.py +1 -1
  20. cmdbox/app/features/cli/cmdbox_gui_start.py +9 -132
  21. cmdbox/app/features/cli/cmdbox_gui_stop.py +4 -21
  22. cmdbox/app/features/cli/cmdbox_server_start.py +1 -1
  23. cmdbox/app/features/cli/cmdbox_web_apikey_add.py +1 -1
  24. cmdbox/app/features/cli/cmdbox_web_apikey_del.py +1 -1
  25. cmdbox/app/features/cli/cmdbox_web_genpass.py +0 -3
  26. cmdbox/app/features/cli/cmdbox_web_group_add.py +1 -1
  27. cmdbox/app/features/cli/cmdbox_web_group_del.py +1 -1
  28. cmdbox/app/features/cli/cmdbox_web_group_edit.py +1 -1
  29. cmdbox/app/features/cli/cmdbox_web_group_list.py +1 -1
  30. cmdbox/app/features/cli/cmdbox_web_start.py +119 -104
  31. cmdbox/app/features/cli/cmdbox_web_stop.py +1 -1
  32. cmdbox/app/features/cli/cmdbox_web_user_add.py +4 -4
  33. cmdbox/app/features/cli/cmdbox_web_user_del.py +1 -1
  34. cmdbox/app/features/cli/cmdbox_web_user_edit.py +4 -4
  35. cmdbox/app/features/cli/cmdbox_web_user_list.py +1 -1
  36. cmdbox/app/features/web/cmdbox_web_agent.py +250 -0
  37. cmdbox/app/features/web/cmdbox_web_do_signin.py +79 -103
  38. cmdbox/app/features/web/cmdbox_web_exec_cmd.py +8 -3
  39. cmdbox/app/features/web/cmdbox_web_signin.py +26 -4
  40. cmdbox/app/features/web/cmdbox_web_users.py +2 -0
  41. cmdbox/app/options.py +55 -2
  42. cmdbox/app/web.py +155 -27
  43. cmdbox/extensions/features.yml +18 -0
  44. cmdbox/extensions/sample_project/sample/app/features/cli/__init__.py +0 -0
  45. cmdbox/extensions/sample_project/sample/app/features/web/__init__.py +0 -0
  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 +37 -6
  49. cmdbox/licenses/{LICENSE.starlette.0.41.3(BSD License).txt → LICENSE.Authlib.1.5.2(BSD License).txt } +3 -1
  50. cmdbox/licenses/{LICENSE.pydantic_core.2.33.0(MIT License).txt → LICENSE.Deprecated.1.2.18(MIT License).txt } +2 -2
  51. cmdbox/licenses/{LICENSE.more-itertools.10.6.0(MIT License).txt → LICENSE.SQLAlchemy.2.0.40(MIT License).txt } +1 -1
  52. cmdbox/licenses/LICENSE.aiohttp.3.11.18(Apache Software License).txt +13 -0
  53. cmdbox/licenses/LICENSE.aiosignal.1.3.2(Apache Software License).txt +201 -0
  54. cmdbox/licenses/LICENSE.async-timeout.5.0.1(Apache Software License).txt +13 -0
  55. cmdbox/licenses/{LICENSE.watchfiles.1.0.0(MIT License).txt → LICENSE.attrs.25.3.0(UNKNOWN).txt} +1 -1
  56. cmdbox/licenses/{LICENSE.anyio.4.6.2.post1(MIT License).txt → LICENSE.cachetools.5.5.2(MIT License).txt } +1 -1
  57. cmdbox/licenses/LICENSE.distro.1.9.0(Apache Software License).txt +202 -0
  58. cmdbox/licenses/{LICENSE.pydantic_core.2.33.1(MIT License).txt → LICENSE.docstring_parser.0.16(MIT License).txt } +1 -1
  59. cmdbox/licenses/LICENSE.filelock.3.18.0(The Unlicense (Unlicense)).txt +24 -0
  60. cmdbox/licenses/LICENSE.frozenlist.1.6.0(Apache-2.0).txt +201 -0
  61. cmdbox/licenses/{LICENSE.starlette.0.46.1(BSD License).txt → LICENSE.fsspec.2025.3.2(BSD License).txt } +3 -1
  62. cmdbox/licenses/{LICENSE.argcomplete.3.6.1(Apache Software License).txt → LICENSE.google-adk.0.5.0(Apache Software License).txt } +25 -0
  63. cmdbox/licenses/LICENSE.google-api-python-client.2.169.0(Apache Software License).txt +201 -0
  64. cmdbox/licenses/LICENSE.google-auth-httplib2.0.2.0(Apache Software License).txt +201 -0
  65. cmdbox/licenses/LICENSE.google-auth.2.40.1(Apache Software License).txt +201 -0
  66. cmdbox/licenses/LICENSE.google-cloud-aiplatform.1.92.0(Apache 2.0).txt +202 -0
  67. cmdbox/licenses/LICENSE.google-cloud-bigquery.3.31.0(Apache Software License).txt +202 -0
  68. cmdbox/licenses/LICENSE.google-cloud-core.2.4.3(Apache Software License).txt +202 -0
  69. cmdbox/licenses/LICENSE.google-cloud-resource-manager.1.14.2(Apache Software License).txt +202 -0
  70. cmdbox/licenses/LICENSE.google-cloud-secret-manager.2.23.3(Apache Software License).txt +202 -0
  71. cmdbox/licenses/LICENSE.google-cloud-speech.2.32.0(Apache Software License).txt +202 -0
  72. cmdbox/licenses/LICENSE.google-cloud-storage.2.19.0(Apache Software License).txt +202 -0
  73. cmdbox/licenses/LICENSE.google-cloud-trace.1.16.1(Apache Software License).txt +202 -0
  74. cmdbox/licenses/LICENSE.google-crc32c.1.7.1(Apache 2.0).txt +202 -0
  75. cmdbox/licenses/LICENSE.google-genai.1.14.0(Apache Software License).txt +202 -0
  76. cmdbox/licenses/LICENSE.google-resumable-media.2.7.2(Apache Software License).txt +202 -0
  77. cmdbox/licenses/LICENSE.googleapis-common-protos.1.70.0(Apache Software License).txt +202 -0
  78. cmdbox/licenses/{LICENSE.fastapi.0.115.5(MIT License).txt → LICENSE.graphviz.0.20.3(MIT License).txt } +1 -1
  79. cmdbox/licenses/LICENSE.grpc-google-iam-v1.0.14.2(Apache Software License).txt +202 -0
  80. cmdbox/licenses/LICENSE.grpcio-status.1.71.0(Apache Software License).txt +610 -0
  81. cmdbox/licenses/LICENSE.grpcio.1.71.0(Apache Software License).txt +610 -0
  82. cmdbox/licenses/{LICENSE.uvicorn.0.34.0(BSD License).txt → LICENSE.httpcore.1.0.9(BSD License).txt } +1 -1
  83. cmdbox/licenses/LICENSE.httplib2.0.22.0(MIT License).txt +23 -0
  84. cmdbox/licenses/{LICENSE.tomli.2.1.0(MIT License).txt → LICENSE.httpx-sse.0.4.0(MIT).txt} +1 -1
  85. cmdbox/licenses/LICENSE.httpx.0.28.1(BSD License).txt +12 -0
  86. cmdbox/licenses/LICENSE.huggingface-hub.0.31.1(Apache Software License).txt +201 -0
  87. cmdbox/licenses/{LICENSE.charset-normalizer.3.4.0(MIT License).txt → LICENSE.jsonschema-specifications.2025.4.1(UNKNOWN).txt} +5 -7
  88. cmdbox/licenses/LICENSE.jsonschema.4.23.0(MIT License).txt +19 -0
  89. cmdbox/licenses/{LICENSE.pkginfo.1.10.0(MIT License).txt → LICENSE.litellm.1.69.0(MIT License).txt } +6 -1
  90. cmdbox/licenses/{LICENSE.redis.5.2.1(MIT License).txt → LICENSE.mcp.1.8.0(MIT License).txt } +1 -1
  91. cmdbox/licenses/LICENSE.multidict.6.4.3(Apache Software License).txt +13 -0
  92. cmdbox/licenses/{LICENSE.argcomplete.3.5.1(Apache Software License).txt → LICENSE.openai.1.75.0(Apache Software License).txt } +25 -1
  93. cmdbox/licenses/LICENSE.opentelemetry-api.1.33.0(Apache Software License).txt +201 -0
  94. cmdbox/licenses/LICENSE.opentelemetry-exporter-gcp-trace.1.9.0(Apache Software License).txt +201 -0
  95. cmdbox/licenses/LICENSE.opentelemetry-resourcedetector-gcp.1.9.0a0(Apache Software License).txt +201 -0
  96. cmdbox/licenses/LICENSE.opentelemetry-sdk.1.33.0(Apache Software License).txt +201 -0
  97. cmdbox/licenses/LICENSE.opentelemetry-semantic-conventions.0.54b0(Apache Software License).txt +201 -0
  98. cmdbox/licenses/LICENSE.propcache.0.3.1(Apache Software License).txt +202 -0
  99. cmdbox/licenses/LICENSE.proto-plus.1.26.1(Apache Software License).txt +202 -0
  100. cmdbox/licenses/{LICENSE.Pygments.2.18.0(BSD License).txt → LICENSE.protobuf.5.29.4(3-Clause BSD License).txt } +15 -8
  101. cmdbox/licenses/LICENSE.pyasn1.0.6.1(BSD License).txt +24 -0
  102. cmdbox/licenses/LICENSE.pyasn1_modules.0.4.2(BSD License).txt +24 -0
  103. cmdbox/licenses/LICENSE.pydantic-settings.2.9.1(MIT License).txt +21 -0
  104. cmdbox/licenses/{LICENSE.gevent.25.4.1(MIT).txt → LICENSE.pyparsing.3.2.3(MIT License).txt } +5 -12
  105. cmdbox/licenses/LICENSE.python-dateutil.2.9.0.post0(Apache Software License; BSD License).txt +54 -0
  106. cmdbox/licenses/LICENSE.referencing.0.36.2(UNKNOWN).txt +19 -0
  107. cmdbox/licenses/LICENSE.regex.2024.11.6(Apache Software License).txt +208 -0
  108. cmdbox/licenses/LICENSE.rpds-py.0.24.0(MIT).txt +19 -0
  109. cmdbox/licenses/{LICENSE.python-multipart.0.0.17(Apache Software License).txt → LICENSE.rsa.4.9.1(Apache Software License).txt } +1 -2
  110. cmdbox/licenses/{LICENSE.sphinx-intl.2.3.0(BSD License).txt → LICENSE.shapely.2.1.0(BSD License).txt } +6 -2
  111. cmdbox/licenses/LICENSE.sse-starlette.2.3.4(BSD License).txt +27 -0
  112. cmdbox/licenses/LICENSE.tiktoken.0.9.0(MIT License).txt +21 -0
  113. cmdbox/licenses/LICENSE.tokenizers.0.21.1(Apache Software License).txt +1 -0
  114. cmdbox/licenses/{LICENSE.six.1.16.0(MIT License).txt → LICENSE.tqdm.4.67.1(MIT License; Mozilla Public License 2.0 (MPL 2.0)).txt } +32 -1
  115. cmdbox/licenses/{LICENSE.rich.13.9.4(MIT License).txt → LICENSE.tzlocal.5.3.1(MIT License).txt } +3 -3
  116. cmdbox/licenses/LICENSE.uritemplate.4.1.1(Apache Software License; BSD License).txt +3 -0
  117. cmdbox/licenses/LICENSE.wrapt.1.17.2(BSD License).txt +24 -0
  118. cmdbox/licenses/LICENSE.yarl.1.20.0(Apache Software License).txt +202 -0
  119. cmdbox/licenses/files.txt +111 -17
  120. cmdbox/logconf_agent.yml +38 -0
  121. cmdbox/logconf_audit.yml +13 -5
  122. cmdbox/logconf_client.yml +13 -5
  123. cmdbox/logconf_cmdbox.yml +13 -5
  124. cmdbox/logconf_edge.yml +13 -5
  125. cmdbox/logconf_gui.yml +13 -5
  126. cmdbox/logconf_server.yml +13 -5
  127. cmdbox/logconf_web.yml +13 -5
  128. cmdbox/version.py +3 -2
  129. cmdbox/web/agent.html +263 -0
  130. cmdbox/web/assets/cmdbox/agent.js +335 -0
  131. cmdbox/web/assets/cmdbox/common.js +1111 -1020
  132. cmdbox/web/assets/cmdbox/signin.js +16 -3
  133. cmdbox/web/assets/cmdbox/users.js +1 -1
  134. cmdbox/web/assets/filer/filer.js +4 -2
  135. cmdbox/web/signin.html +10 -6
  136. {cmdbox-0.5.3.1.dist-info → cmdbox-0.6.0.dist-info}/METADATA +132 -35
  137. {cmdbox-0.5.3.1.dist-info → cmdbox-0.6.0.dist-info}/RECORD +161 -123
  138. cmdbox/app/features/web/cmdbox_web_load_pin.py +0 -43
  139. cmdbox/app/features/web/cmdbox_web_save_pin.py +0 -42
  140. cmdbox/licenses/LICENSE.Jinja2.3.1.4(BSD License).txt +0 -28
  141. cmdbox/licenses/LICENSE.Sphinx.8.1.3(BSD License).txt +0 -31
  142. cmdbox/licenses/LICENSE.babel.2.16.0(BSD License).txt +0 -27
  143. cmdbox/licenses/LICENSE.certifi.2025.1.31(Mozilla Public License 2.0 (MPL 2.0)).txt +0 -20
  144. cmdbox/licenses/LICENSE.click.8.1.8(BSD License).txt +0 -28
  145. cmdbox/licenses/LICENSE.cryptography.44.0.2(Apache Software License; BSD License).txt +0 -3
  146. cmdbox/licenses/LICENSE.greenlet.3.2.0(MIT AND Python-2.0).txt +0 -30
  147. cmdbox/licenses/LICENSE.keyring.25.5.0(MIT License).txt +0 -17
  148. cmdbox/licenses/LICENSE.numpy.2.2.4(BSD License).txt +0 -950
  149. cmdbox/licenses/LICENSE.pillow.11.0.0(CMU License (MIT-CMU)).txt +0 -1226
  150. cmdbox/licenses/LICENSE.pillow.11.1.0(CMU License (MIT-CMU)).txt +0 -1213
  151. cmdbox/licenses/LICENSE.prettytable.3.12.0(BSD License).txt +0 -30
  152. cmdbox/licenses/LICENSE.prompt_toolkit.3.0.50(BSD License).txt +0 -27
  153. cmdbox/licenses/LICENSE.psycopg.3.2.6(GNU Lesser General Public License v3 (LGPLv3)).txt +0 -165
  154. cmdbox/licenses/LICENSE.pydantic.2.11.1(MIT License).txt +0 -21
  155. cmdbox/licenses/LICENSE.pydantic.2.11.3(MIT License).txt +0 -21
  156. cmdbox/licenses/LICENSE.python-dotenv.1.0.1(BSD License).txt +0 -27
  157. cmdbox/licenses/LICENSE.twine.5.1.1(Apache Software License).txt +0 -174
  158. cmdbox/licenses/LICENSE.typing_extensions.4.13.0(UNKNOWN).txt +0 -279
  159. cmdbox/licenses/LICENSE.urllib3.2.2.3(MIT License).txt +0 -21
  160. cmdbox/licenses/LICENSE.urllib3.2.3.0(MIT License).txt +0 -21
  161. cmdbox/licenses/LICENSE.uvicorn.0.34.1(BSD License).txt +0 -27
  162. cmdbox/licenses/LICENSE.watchfiles.1.0.4(MIT License).txt +0 -21
  163. cmdbox/licenses/LICENSE.websockets.14.1(BSD License).txt +0 -24
  164. cmdbox/licenses/LICENSE.zope.interface.7.1.1(Zope Public License).txt +0 -44
  165. /cmdbox/licenses/{LICENSE.typing_extensions.4.12.2(Python Software Foundation License).txt → LICENSE.aiohappyeyeballs.2.6.1(Python Software Foundation License).txt} +0 -0
  166. /cmdbox/licenses/{LICENSE.certifi.2024.8.30(Mozilla Public License 2.0 (MPL 2.0)).txt → LICENSE.certifi.2025.4.26(Mozilla Public License 2.0 (MPL 2.0)).txt} +0 -0
  167. /cmdbox/licenses/{LICENSE.charset-normalizer.3.4.1(MIT License).txt → LICENSE.charset-normalizer.3.4.2(MIT License).txt} +0 -0
  168. /cmdbox/licenses/{LICENSE.click.8.1.7(BSD License).txt → LICENSE.click.8.2.0(UNKNOWN).txt} +0 -0
  169. /cmdbox/licenses/{LICENSE.cryptography.43.0.3(Apache Software License; BSD License).txt → LICENSE.cryptography.44.0.3(Apache Software License; BSD License).txt} +0 -0
  170. /cmdbox/licenses/{LICENSE.gevent.24.11.1(MIT License).txt → LICENSE.gevent.25.4.2(MIT).txt} +0 -0
  171. /cmdbox/licenses/{LICENSE.importlib_metadata.8.5.0(Apache Software License).txt → LICENSE.google-api-core.2.24.2(Apache Software License).txt} +0 -0
  172. /cmdbox/licenses/{LICENSE.greenlet.3.1.1(MIT License).txt → LICENSE.greenlet.3.2.2(MIT AND Python-2.0).txt} +0 -0
  173. /cmdbox/licenses/{LICENSE.h11.0.14.0(MIT License).txt → LICENSE.h11.0.16.0(MIT License).txt} +0 -0
  174. /cmdbox/licenses/{LICENSE.nh3.0.2.18(MIT).txt → LICENSE.jiter.0.9.0(MIT License).txt} +0 -0
  175. /cmdbox/licenses/{LICENSE.more-itertools.10.5.0(MIT License).txt → LICENSE.more-itertools.10.7.0(MIT License).txt} +0 -0
  176. /cmdbox/licenses/{LICENSE.numpy.2.1.3(BSD License).txt → LICENSE.numpy.2.2.5(BSD License).txt} +0 -0
  177. /cmdbox/licenses/{LICENSE.packaging.24.2(Apache Software License; BSD License).txt → LICENSE.packaging.25.0(Apache Software License; BSD License).txt} +0 -0
  178. /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
  179. /cmdbox/licenses/{LICENSE.psycopg-pool.3.2.6(GNU Lesser General Public License v3 (LGPLv3)).txt → LICENSE.psycopg.3.2.7(GNU Lesser General Public License v3 (LGPLv3)).txt} +0 -0
  180. /cmdbox/licenses/{LICENSE.pydantic.2.10.2(MIT License).txt → LICENSE.pydantic.2.11.4(MIT License).txt} +0 -0
  181. /cmdbox/licenses/{LICENSE.pydantic_core.2.27.1(MIT License).txt → LICENSE.pydantic_core.2.33.2(MIT License).txt} +0 -0
  182. /cmdbox/licenses/{LICENSE.redis.5.2.0(MIT License).txt → LICENSE.redis.6.0.0(MIT License).txt} +0 -0
  183. /cmdbox/licenses/{LICENSE.snowballstemmer.2.2.0(BSD License).txt → LICENSE.snowballstemmer.3.0.1(BSD License).txt} +0 -0
  184. /cmdbox/licenses/{LICENSE.uvicorn.0.32.1(BSD License).txt → LICENSE.uvicorn.0.34.2(BSD License).txt} +0 -0
  185. {cmdbox-0.5.3.1.dist-info → cmdbox-0.6.0.dist-info}/LICENSE +0 -0
  186. {cmdbox-0.5.3.1.dist-info → cmdbox-0.6.0.dist-info}/WHEEL +0 -0
  187. {cmdbox-0.5.3.1.dist-info → cmdbox-0.6.0.dist-info}/entry_points.txt +0 -0
  188. {cmdbox-0.5.3.1.dist-info → cmdbox-0.6.0.dist-info}/top_level.txt +0 -0
cmdbox/app/options.py CHANGED
@@ -1,6 +1,6 @@
1
1
  from cmdbox.app import common, feature, web
2
2
  from cmdbox.app.commons import module
3
- from fastapi import Request
3
+ from fastapi import Request, WebSocket
4
4
  from fastapi.routing import APIRoute
5
5
  from datetime import datetime
6
6
  from pathlib import Path
@@ -49,6 +49,7 @@ class Options:
49
49
  self.aliases_loaded_cli = False
50
50
  self.aliases_loaded_web = False
51
51
  self.audit_loaded = False
52
+ self.agentrule_loaded = False
52
53
  self.init_options()
53
54
 
54
55
  def get_mode_keys(self) -> List[str]:
@@ -345,6 +346,7 @@ class Options:
345
346
  svcmd = fobj.get_svcmd()
346
347
  if svcmd is not None:
347
348
  self._options["svcmd"][svcmd] = fobj
349
+ opt['use_agent'] = self.check_agentrule(mode, cmd, logger)
348
350
  self.init_debugoption()
349
351
 
350
352
  def is_features_loaded(self, ftype:str) -> bool:
@@ -396,6 +398,8 @@ class Options:
396
398
  return
397
399
  if type(yml['features'][ftype]) is not list:
398
400
  raise Exception(f'features.yml is invalid. (The “features.{ftype} element must be a list. {ftype}={yml["features"][ftype]})')
401
+ # featureモジュール読込みの前にagentruleの読み込み
402
+ self.load_features_agentrule(logger)
399
403
  for data in yml['features'][ftype]:
400
404
  if type(data) is not dict:
401
405
  raise Exception(f'features.yml is invalid. (The “features.{ftype}” element must be a list element must be a dictionary. data={data})')
@@ -415,6 +419,7 @@ class Options:
415
419
  func(data['package'], data['prefix'], exclude_modules, appcls, ver, logger, self.is_features_loaded(ftype))
416
420
  self.features_loaded[ftype] = True
417
421
 
422
+
418
423
  def load_features_args(self, args_dict:Dict[str, Any]):
419
424
  yml = self.features_yml_data
420
425
  if yml is None:
@@ -658,6 +663,54 @@ class Options:
658
663
  self.audit_search_args['cmd'] = cmd
659
664
  self.audit_loaded = True
660
665
 
666
+ def load_features_agentrule(self, logger:logging.Logger):
667
+ yml = self.features_yml_data
668
+ if yml is None: return
669
+ if self.agentrule_loaded: return
670
+ if 'agentrule' not in yml: return
671
+ if 'policy' not in yml['agentrule']:
672
+ raise Exception('features.yml is invalid. (The agentrule element must have "policy" specified.)')
673
+ if yml['agentrule']['policy'] not in ['allow', 'deny']:
674
+ raise Exception('features.yml is invalid. (The policy element must specify allow or deny.)')
675
+ if 'rules' not in yml['agentrule']:
676
+ raise Exception('features.yml is invalid. (The agentrule element must have "rules" specified.)')
677
+ for rule in yml['agentrule']['rules']:
678
+ if 'mode' not in rule:
679
+ rule['mode'] = None
680
+ if 'cmds' not in rule:
681
+ rule['cmds'] = []
682
+ if rule['mode'] is None and len(rule['cmds']) > 0:
683
+ raise Exception('features.yml is invalid. (When “cmds” is specified, “mode” must be specified.)')
684
+ if 'rule' not in rule:
685
+ raise Exception('features.yml is invalid. (The agentrule.rules element must have "rule" specified.)')
686
+ self.agentrule_loaded = True
687
+
688
+ def check_agentrule(self, mode:str, cmd:str, logger:logging.Logger) -> bool:
689
+ """
690
+ エージェントが使用してよいコマンドかどうかをチェックします
691
+
692
+ Args:
693
+ mode (str): モード
694
+ cmd (str): コマンド
695
+
696
+ Returns:
697
+ bool: 認可されたかどうか
698
+ """
699
+ if not self.agentrule_loaded:
700
+ return False
701
+ # コマンドチェック
702
+ jadge = self.features_yml_data['agentrule']['policy']
703
+ for rule in self.features_yml_data['agentrule']['rules']:
704
+ if rule['mode'] is not None:
705
+ if rule['mode'] != mode:
706
+ continue
707
+ if len([c for c in rule['cmds'] if cmd == c]) <= 0:
708
+ continue
709
+ jadge = rule['rule']
710
+ if logger.level == logging.DEBUG:
711
+ logger.debug(f"agent rule: mode={mode}, cmd={cmd}: {jadge}")
712
+ return jadge == 'allow'
713
+
661
714
  AT_USER = 'user'
662
715
  AT_ADMIN = 'admin'
663
716
  AT_SYSTEM = 'system'
@@ -779,7 +832,7 @@ class Options:
779
832
  elif isinstance(arg, feature.Feature):
780
833
  func_feature = arg
781
834
  opt['clmsg_src'] = func_feature.__class__.__name__
782
- elif isinstance(arg, Request):
835
+ elif isinstance(arg, Request) or isinstance(arg, WebSocket):
783
836
  if 'signin' in arg.session and arg.session['signin'] is not None and 'name' in arg.session['signin']:
784
837
  opt['clmsg_user'] = arg.session['signin']['name']
785
838
  if opt['audit_type'] is None:
cmdbox/app/web.py CHANGED
@@ -1,11 +1,12 @@
1
1
  from cmdbox.app import common, options
2
- from cmdbox.app.auth.signin import Signin
2
+ from cmdbox.app.auth import signin, signin_saml
3
3
  from cmdbox.app.commons import module
4
- from fastapi import FastAPI, Request, Response, HTTPException
5
- from fastapi.responses import RedirectResponse
4
+ from fastapi import FastAPI, Request, Response
6
5
  from pathlib import Path
6
+ from starlette.applications import Starlette
7
7
  from starlette.middleware.sessions import SessionMiddleware
8
- from typing import Any, Dict, List, Tuple
8
+ from starlette.routing import Mount
9
+ from typing import Any, Dict, List
9
10
  from uvicorn.config import Config
10
11
  import asyncio
11
12
  import copy
@@ -31,7 +32,7 @@ class Web:
31
32
  def __init__(self, logger:logging.Logger, data:Path, appcls=None, ver=None,
32
33
  redis_host:str = "localhost", redis_port:int = 6379, redis_password:str = None, svname:str = 'server',
33
34
  client_only:bool=False, doc_root:Path=None, gui_html:str=None, filer_html:str=None, result_html:str=None, users_html:str=None,
34
- audit_html:str=None, assets:List[str]=None, signin_html:str=None, signin_file:str=None, gui_mode:bool=False,
35
+ audit_html:str=None, agent_html:str=None, assets:List[str]=None, signin_html:str=None, signin_file:str=None, gui_mode:bool=False,
35
36
  web_features_packages:List[str]=None, web_features_prefix:List[str]=None):
36
37
  """
37
38
  cmdboxクライアント側のwebapiサービス
@@ -52,6 +53,7 @@ class Web:
52
53
  result_html (str, optional): 結果のHTMLファイル. Defaults to None.
53
54
  users_html (str, optional): ユーザーのHTMLファイル. Defaults to None.
54
55
  audit_html (str, optional): 監査のHTMLファイル. Defaults to None.
56
+ agent_html (str, optional): エージェントのHTMLファイル. Defaults to None.
55
57
  assets (List[str], optional): 静的ファイルのリスト. Defaults to None.
56
58
  signin_html (str, optional): ログイン画面のHTMLファイル. Defaults to None.
57
59
  signin_file (str, optional): ログイン情報のファイル. Defaults to args.signin_file.
@@ -78,6 +80,7 @@ class Web:
78
80
  self.result_html = Path(result_html) if result_html is not None else Path(__file__).parent.parent / 'web' / 'result.html'
79
81
  self.users_html = Path(users_html) if users_html is not None else Path(__file__).parent.parent / 'web' / 'users.html'
80
82
  self.audit_html = Path(audit_html) if audit_html is not None else Path(__file__).parent.parent / 'web' / 'audit.html'
83
+ self.agent_html = Path(agent_html) if agent_html is not None else Path(__file__).parent.parent / 'web' / 'agent.html'
81
84
  self.assets = []
82
85
  if assets is not None:
83
86
  if not isinstance(assets, list):
@@ -97,6 +100,7 @@ class Web:
97
100
  self.result_html_data = None
98
101
  self.users_html_data = None
99
102
  self.audit_html_data = None
103
+ self.agent_html_data = None
100
104
  self.assets_data = None
101
105
  self.signin_html_data = None
102
106
  self.gui_mode = gui_mode
@@ -106,18 +110,21 @@ class Web:
106
110
  self.pipes_path = self.data / ".pipes"
107
111
  self.users_path = self.data / ".users"
108
112
  self.audit_path = self.data / '.audit'
113
+ self.agent_path = self.data / '.agent'
109
114
  self.static_root = Path(__file__).parent.parent / 'web'
110
115
  common.mkdirs(self.cmds_path)
111
116
  common.mkdirs(self.pipes_path)
112
117
  common.mkdirs(self.users_path)
113
118
  common.mkdirs(self.audit_path)
119
+ common.mkdirs(self.agent_path)
114
120
  self.pipe_th = None
115
121
  self.img_queue = queue.Queue(1000)
116
122
  self.cb_queue = queue.Queue(1000)
117
123
  self.options = options.Options.getInstance()
118
124
  self.webcap_client = requests.Session()
119
- signin_file_data = Signin.load_signin_file(self.signin_file)
120
- self.signin = Signin(self.logger, self.signin_file, signin_file_data, self.appcls, self.ver)
125
+ signin_file_data = signin.Signin.load_signin_file(self.signin_file)
126
+ self.signin = signin.Signin(self.logger, self.signin_file, signin_file_data, self.appcls, self.ver)
127
+ self.signin_saml = signin_saml.SigninSAML(self.logger, self.signin_file, signin_file_data, self.appcls, self.ver)
121
128
 
122
129
  if self.logger.level == logging.DEBUG:
123
130
  self.logger.debug(f"web init parameter: data={self.data} -> {self.data.absolute() if self.data is not None else None}")
@@ -131,6 +138,7 @@ class Web:
131
138
  self.logger.debug(f"web init parameter: result_html={self.result_html} -> {self.result_html.absolute() if self.result_html is not None else None}")
132
139
  self.logger.debug(f"web init parameter: users_html={self.users_html} -> {self.users_html.absolute() if self.users_html is not None else None}")
133
140
  self.logger.debug(f"web init parameter: audit_html={self.audit_html} -> {self.audit_html.absolute() if self.audit_html is not None else None}")
141
+ self.logger.debug(f"web init parameter: agent_html={self.agent_html} -> {self.agent_html.absolute() if self.agent_html is not None else None}")
134
142
  self.logger.debug(f"web init parameter: assets={self.assets} -> {[a.absolute() for a in self.assets] if self.assets is not None else None}")
135
143
  self.logger.debug(f"web init parameter: signin_html={self.signin_html} -> {self.signin_html.absolute() if self.signin_html is not None else None}")
136
144
  self.logger.debug(f"web init parameter: signin_file={self.signin_file} -> {self.signin_file.absolute() if self.signin_file is not None else None}")
@@ -140,6 +148,8 @@ class Web:
140
148
  self.logger.debug(f"web init parameter: cmds_path={self.cmds_path} -> {self.cmds_path.absolute() if self.cmds_path is not None else None}")
141
149
  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}")
142
150
  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}")
151
+ self.logger.debug(f"web init parameter: audit_path={self.audit_path} -> {self.audit_path.absolute() if self.audit_path is not None else None}")
152
+ self.logger.debug(f"web init parameter: agent_path={self.agent_path} -> {self.agent_path.absolute() if self.agent_path is not None else None}")
143
153
 
144
154
  def init_webfeatures(self, app:FastAPI):
145
155
  self.filemenu = dict()
@@ -149,7 +159,10 @@ class Web:
149
159
  if self.options.is_features_loaded('web'):
150
160
  return
151
161
  # webfeatureの読込み
162
+ self.wf_dep = []
152
163
  def wf_route(pk, prefix, excludes, w, app, appcls, ver, logger):
164
+ if pk in w.wf_dep: return
165
+ w.wf_dep.append(pk)
153
166
  for wf in module.load_webfeatures(pk, prefix, excludes, appcls=appcls, ver=ver, logger=logger):
154
167
  wf.route(self, app)
155
168
  self.filemenu = {**self.filemenu, **wf.filemenu(w)}
@@ -348,12 +361,12 @@ class Web:
348
361
  if 'hash' not in user or user['hash'] == '':
349
362
  raise ValueError(f"User hash is not found or empty. ({user})")
350
363
  hash = user['hash']
351
- if hash!='oauth2' and ('password' not in user or user['password'] == ''):
364
+ if hash!='oauth2' and hash!='saml' and ('password' not in user or user['password'] == ''):
352
365
  raise ValueError(f"User password is not found or empty. ({user})")
353
366
  if 'email' not in user:
354
367
  raise ValueError(f"User email is not found. ({user})")
355
- if hash=='oauth2' and (user['email'] is None or user['email']==''):
356
- raise ValueError(f"Required when `email` is `oauth2`. ({user})")
368
+ if (hash=='oauth2' or hash=='saml') and (user['email'] is None or user['email']==''):
369
+ raise ValueError(f"Required when `email` is `oauth2` or `saml`. ({user})")
357
370
  if 'groups' not in user or type(user['groups']) is not list:
358
371
  raise ValueError(f"User groups is not found or empty. ({user})")
359
372
  for gn in user['groups']:
@@ -363,13 +376,13 @@ class Web:
363
376
  raise ValueError(f"User uid is already exists. ({user})")
364
377
  if len([u for u in signin_data['users'] if u['name'] == user['name']]) > 0:
365
378
  raise ValueError(f"User name is already exists. ({user})")
366
- if hash not in ['oauth2', 'plain', 'md5', 'sha1', 'sha256']:
379
+ if hash not in ['oauth2', 'saml', 'plain', 'md5', 'sha1', 'sha256']:
367
380
  raise ValueError(f"User hash is not supported. ({user})")
368
381
  jadge, msg = self.signin.check_password_policy(user['name'], '', user['password'])
369
382
  if not jadge:
370
383
  raise ValueError(msg)
371
384
  if hash != 'plain':
372
- user['password'] = common.hash_password(user['password'], hash if hash != 'oauth2' else 'sha1')
385
+ user['password'] = common.hash_password(user['password'], hash if hash != 'oauth2' and hash != 'saml' else 'sha1')
373
386
  else:
374
387
  user['password'] = user['password']
375
388
  signin_data['users'].append(user)
@@ -405,8 +418,8 @@ class Web:
405
418
  if 'email' not in user:
406
419
  raise ValueError(f"User email is not found. ({user})")
407
420
  hash = user['hash']
408
- if hash=='oauth2' and (user['email'] is None or user['email']==''):
409
- raise ValueError(f"Required when `email` is `oauth2`. ({user})")
421
+ if (hash=='oauth2' or hash=='saml') and (user['email'] is None or user['email']==''):
422
+ raise ValueError(f"Required when `email` is `oauth2` or `saml`. ({user})")
410
423
  if 'groups' not in user or type(user['groups']) is not list:
411
424
  raise ValueError(f"User groups is not found or empty. ({user})")
412
425
  for gn in user['groups']:
@@ -416,7 +429,7 @@ class Web:
416
429
  raise ValueError(f"User uid is not found. ({user})")
417
430
  if len([u for u in signin_data['users'] if u['name'] == user['name']]) <= 0:
418
431
  raise ValueError(f"User name is not found. ({user})")
419
- if hash not in ['oauth2', 'plain', 'md5', 'sha1', 'sha256']:
432
+ if hash not in ['oauth2', 'saml', 'plain', 'md5', 'sha1', 'sha256']:
420
433
  raise ValueError(f"User hash is not supported. ({user})")
421
434
  for u in signin_data['users']:
422
435
  if u['uid'] == user['uid']:
@@ -426,7 +439,7 @@ class Web:
426
439
  if not jadge:
427
440
  raise ValueError(msg)
428
441
  if hash != 'plain':
429
- u['password'] = common.hash_password(user['password'], hash if hash != 'oauth2' else 'sha1')
442
+ u['password'] = common.hash_password(user['password'], hash if hash != 'oauth2' and hash != 'saml' else 'sha1')
430
443
  else:
431
444
  u['password'] = user['password']
432
445
  # パスワード更新日時の保存
@@ -665,7 +678,8 @@ class Web:
665
678
  def start(self, allow_host:str="0.0.0.0", listen_port:int=8081, ssl_listen_port:int=8443,
666
679
  ssl_cert:Path=None, ssl_key:Path=None, ssl_keypass:str=None, ssl_ca_certs:Path=None,
667
680
  session_domain:str=None, session_path:str='/', session_secure:bool=False, session_timeout:int=900, outputs_key:List[str]=[],
668
- guvicorn_workers:int=-1, guvicorn_timeout:int=30):
681
+ guvicorn_workers:int=-1, guvicorn_timeout:int=30,
682
+ agent_runner=None, mcp=None, mcp_listen_port=9081, mcp_ssl_listen_port=9443):
669
683
  """
670
684
  Webサーバを起動する
671
685
 
@@ -684,6 +698,10 @@ class Web:
684
698
  outputs_key (list, optional): 出力キー. Defaults to [].
685
699
  guvicorn_workers (int, optional): Gunicornワーカー数. Defaults to -1.
686
700
  guvicorn_timeout (int, optional): Gunicornタイムアウト. Defaults to 30.
701
+ agent_runner (Runner, optional): エージェントランナー. Defaults to None.
702
+ mcp (MCP, optional): MCP. Defaults to None.
703
+ mcp_listen_port (int, optional): MCPリスンポート. Defaults to 9081.
704
+ mcp_ssl_listen_port (int, optional): MCP SSLリスンポート. Defaults to 9443.
687
705
  """
688
706
  self.allow_host = allow_host
689
707
  self.listen_port = listen_port
@@ -699,6 +717,10 @@ class Web:
699
717
  self.session_timeout = session_timeout
700
718
  self.guvicorn_workers = guvicorn_workers
701
719
  self.guvicorn_timeout = guvicorn_timeout
720
+ self.agent_runner = agent_runner
721
+ self.mcp = mcp
722
+ self.mcp_listen_port = mcp_listen_port
723
+ self.mcp_ssl_listen_port = mcp_ssl_listen_port
702
724
  if self.logger.level == logging.DEBUG:
703
725
  self.logger.debug(f"web start parameter: allow_host={self.allow_host}")
704
726
  self.logger.debug(f"web start parameter: listen_port={self.listen_port}")
@@ -714,7 +736,81 @@ class Web:
714
736
  self.logger.debug(f"web start parameter: session_timeout={self.session_timeout}")
715
737
  self.logger.debug(f"web start parameter: guvicorn_worker={self.guvicorn_workers}")
716
738
  self.logger.debug(f"web start parameter: guvicorn_timeout={self.guvicorn_timeout}")
739
+ self.logger.debug(f"web start parameter: agent_runner={self.agent_runner}")
740
+ self.logger.debug(f"web start parameter: mcp={self.mcp}")
741
+ self.logger.debug(f"web start parameter: mcp_listen_port={self.mcp_listen_port}")
742
+ self.logger.debug(f"web start parameter: mcp_ssl_listen_port={self.mcp_ssl_listen_port}")
743
+
744
+ if self.agent_runner is not None:
745
+ # google.adkが大きいので必要な時にだけ読込む
746
+ from google.adk.sessions import BaseSessionService, Session
747
+ async def create_agent_session(session_service:BaseSessionService, user_id:str, session_id:str=None) -> Session:
748
+ """
749
+ セッションを作成します
750
+
751
+ Args:
752
+ session_service (BaseSessionService): セッションサービス
753
+ user_id (str): ユーザーID
754
+ session_id (str): セッションID
755
+
756
+ Returns:
757
+ Any: セッション
758
+ """
759
+ if session_id is None:
760
+ session_id = common.random_string(32)
761
+ session = await session_service.get_session(app_name=self.ver.__appid__, user_id=user_id, session_id=session_id)
762
+ if session is None:
763
+ session = await session_service.create_session(app_name=self.ver.__appid__, user_id=user_id, session_id=session_id)
764
+ return session
765
+ self.create_agent_session = create_agent_session
766
+ async def list_agent_sessions(session_service:BaseSessionService, user_id:str, session_id:str=None) -> List[Session]:
767
+ """
768
+ セッションをリストします
769
+
770
+ Args:
771
+ session_service (BaseSessionService): セッションサービス
772
+ user_id (str): ユーザーID
773
+
774
+ Returns:
775
+ List[Session]: セッションリスト
776
+ """
777
+ if session_id is None:
778
+ sessions = await session_service.list_sessions(app_name=self.ver.__appid__, user_id=user_id)
779
+ ret = []
780
+ for s in sessions.sessions:
781
+ ret.append(await session_service.get_session(app_name=self.ver.__appid__, user_id=user_id, session_id=s.id))
782
+ return ret
783
+ else:
784
+ session = await session_service.get_session(app_name=self.ver.__appid__, user_id=user_id, session_id=session_id)
785
+ if session is None:
786
+ return []
787
+ return [session]
788
+ self.list_agent_sessions = list_agent_sessions
789
+ async def delete_agent_session(session_service:BaseSessionService, user_id:str, session_id:str) -> bool:
790
+ """
791
+ セッションを削除します
792
+
793
+ Args:
794
+ session_service (BaseSessionService): セッションサービス
795
+ user_id (str): ユーザーID
796
+ session_id (str): セッションID
797
+
798
+ Returns:
799
+ bool: 成功した場合はTrue, 失敗した場合はFalse
800
+ """
801
+ return await session_service.delete_session(app_name=self.ver.__appid__, user_id=user_id, session_id=session_id)
802
+ self.delete_agent_session = delete_agent_session
717
803
 
804
+ """
805
+ if self.mcp is not None:
806
+ # MCPをFastAPIにマウント
807
+ mcp_app:Starlette = self.mcp.streamable_http_app()
808
+ app = FastAPI(lifespan=self.mcp.settings.lifespan)
809
+ app.mount("/mcp", mcp_app)
810
+ #app.include_router(mcp_app.router)
811
+ else:
812
+ app = FastAPI()
813
+ """
718
814
  app = FastAPI()
719
815
  @app.middleware("http")
720
816
  async def set_context_cookie(req:Request, call_next):
@@ -732,19 +828,32 @@ class Web:
732
828
 
733
829
  self.is_running = True
734
830
  #uvicorn.run(app, host=self.allow_host, port=self.listen_port, workers=2)
735
- th = ThreadedUvicorn(self.logger, config=Config(app=app, host=self.allow_host, port=self.listen_port),
831
+ http_config = Config(app=app, host=self.allow_host, port=self.listen_port)
832
+ th = ThreadedUvicorn(self.logger, config=http_config,
736
833
  guvicorn_config=dict(workers=self.guvicorn_workers, timeout=self.guvicorn_timeout))
737
834
  th.start()
835
+ if self.mcp is not None and self.ssl_cert is None and self.ssl_key is None:
836
+ mcp_app:Starlette = self.mcp.streamable_http_app()
837
+ http_config = Config(app=mcp_app, host=self.allow_host, port=self.mcp_listen_port)
838
+ mcp_th = ThreadedUvicorn(self.logger, config=http_config, force_uvicorn=True)
839
+ mcp_th.start()
738
840
  browser_port = self.listen_port
739
841
  th_ssl = None
740
842
  if self.ssl_cert is not None and self.ssl_key is not None:
741
- th_ssl = ThreadedUvicorn(self.logger,
742
- config=Config(app=app, host=self.allow_host, port=self.ssl_listen_port,
743
- ssl_certfile=self.ssl_cert, ssl_keyfile=self.ssl_key,
744
- ssl_keyfile_password=self.ssl_keypass, ssl_ca_certs=self.ssl_ca_certs),
843
+ https_config = Config(app=app, host=self.allow_host, port=self.ssl_listen_port,
844
+ ssl_certfile=self.ssl_cert, ssl_keyfile=self.ssl_key,
845
+ ssl_keyfile_password=self.ssl_keypass, ssl_ca_certs=self.ssl_ca_certs)
846
+ th_ssl = ThreadedUvicorn(self.logger, config=https_config,
745
847
  guvicorn_config=dict(workers=self.guvicorn_workers, timeout=self.guvicorn_timeout))
746
848
  th_ssl.start()
747
849
  browser_port = self.ssl_listen_port
850
+ if self.mcp is not None:
851
+ mcp_app:Starlette = self.mcp.streamable_http_app()
852
+ https_config = Config(app=mcp_app, host=self.allow_host, port=self.mcp_ssl_listen_port,
853
+ ssl_certfile=self.ssl_cert, ssl_keyfile=self.ssl_key,
854
+ ssl_keyfile_password=self.ssl_keypass, ssl_ca_certs=self.ssl_ca_certs)
855
+ mcp_th_ssl = ThreadedUvicorn(self.logger, config=https_config, force_uvicorn=True)
856
+ mcp_th_ssl.start()
748
857
  try:
749
858
  if self.gui_mode:
750
859
  webbrowser.open(f'http://localhost:{browser_port}/gui')
@@ -753,12 +862,20 @@ class Web:
753
862
  while self.is_running:
754
863
  gevent.sleep(1)
755
864
  th.stop()
865
+ if self.mcp is not None:
866
+ mcp_th.stop()
756
867
  if th_ssl is not None:
757
868
  th_ssl.stop()
869
+ if self.mcp is not None:
870
+ mcp_th_ssl.stop()
758
871
  except KeyboardInterrupt:
759
872
  th.stop()
873
+ if self.mcp is not None:
874
+ mcp_th.stop()
760
875
  if th_ssl is not None:
761
876
  th_ssl.stop()
877
+ if self.mcp is not None:
878
+ mcp_th_ssl.stop()
762
879
 
763
880
  def stop(self):
764
881
  """
@@ -779,13 +896,24 @@ class Web:
779
896
  self.logger.info(f"Exit web.")
780
897
 
781
898
  class ThreadedUvicorn:
782
- def __init__(self, logger:logging.Logger, config:Config, guvicorn_config:Dict[str, Any]):
899
+ def __init__(self, logger:logging.Logger, config:Config, guvicorn_config:Dict[str, Any]=None, force_uvicorn:bool=False):
783
900
  self.logger = logger
784
901
  self.guvicorn_config = guvicorn_config
785
- if platform.system() == "Windows":
902
+ self.force_uvicorn = True if platform.system() == "Windows" else force_uvicorn
903
+ stderr_handler = common.create_log_handler(stderr=True)
904
+ stdout_handler = common.create_log_handler(stderr=False)
905
+ if self.force_uvicorn:
906
+ # loggerの設定
907
+ common.reset_logger("uvicorn")
908
+ common.reset_logger("uvicorn.error")
909
+ common.reset_logger("uvicorn.access")
786
910
  self.server = uvicorn.Server(config)
787
911
  self.thread = RaiseThread(daemon=True, target=self.server.run)
788
912
  else:
913
+ # loggerの設定
914
+ common.reset_logger("gunicorn.error")
915
+ common.reset_logger("gunicorn.access")
916
+
789
917
  from gunicorn.app.wsgiapp import WSGIApplication
790
918
  class App(WSGIApplication):
791
919
  def __init__(self, app, options):
@@ -822,7 +950,7 @@ class ThreadedUvicorn:
822
950
  #self.thread = RaiseThread(daemon=True, target=self.server.run)
823
951
 
824
952
  def start(self):
825
- if platform.system() == "Windows":
953
+ if self.force_uvicorn:
826
954
  asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
827
955
  self.thread.start()
828
956
  asyncio.run(self.wait_for_started())
@@ -836,7 +964,7 @@ class ThreadedUvicorn:
836
964
  await asyncio.sleep(0.1)
837
965
 
838
966
  def stop(self):
839
- if platform.system() == "Windows":
967
+ if self.force_uvicorn:
840
968
  if self.thread.is_alive():
841
969
  self.server.should_exit = True
842
970
  self.thread.raise_exception()
@@ -846,7 +974,7 @@ class ThreadedUvicorn:
846
974
  self.server.started = False
847
975
 
848
976
  def is_alive(self):
849
- if platform.system() == "Windows":
977
+ if self.force_uvicorn:
850
978
  return self.thread.is_alive()
851
979
  else:
852
980
  return self.server.started
@@ -37,6 +37,24 @@ aliases: # Specify the alias for the specified co
37
37
  # e.g. /{1}_exec
38
38
  move: # Specify whether to move the regular expression group of the source to the target.
39
39
  # e.g. true
40
+ agentrule: # Specifies a list of rules that determine which commands the agent can execute.
41
+ policy: deny # Specify the default policy for the rule. The value can be allow or deny.
42
+ rules: # Specify the rules for the commands that the agent can execute according to the group to which the user belongs.
43
+ - mode: audit # Specify the "mode" as the condition for applying the rule.
44
+ cmds: [search, write] # Specify the "cmd" to which the rule applies. Multiple items can be specified in a list.
45
+ rule: allow # Specifies whether the specified command is allowed or not. Values are allow or deny.
46
+ - mode: client
47
+ cmds: [file_copy, file_download, file_list, file_mkdir, file_move, file_remove, file_rmdir, file_upload, server_info]
48
+ rule: allow
49
+ - mode: cmd
50
+ cmds: [list, load]
51
+ rule: allow
52
+ - mode: server
53
+ cmds: [list]
54
+ rule: allow
55
+ - mode: web
56
+ cmds: [gencert, genpass, group_list, user_list]
57
+ rule: allow
40
58
  audit:
41
59
  enabled: true # Specify whether to enable the audit function.
42
60
  write:
@@ -46,3 +46,26 @@ aliases: # Specify the alias for the specified co
46
46
  # e.g. /{1}_exec
47
47
  move: # Specify whether to move the regular expression group of the source to the target.
48
48
  # e.g. true
49
+ audit:
50
+ enabled: true # Specify whether to enable the audit function.
51
+ write:
52
+ mode: audit # Specify the mode of the feature to be writed.
53
+ cmd: write # Specify the command to be writed.
54
+ search:
55
+ mode: audit # Specify the mode of the feature to be searched.
56
+ cmd: search # Specify the command to be searched.
57
+ options: # Specify the options for the audit function.
58
+ host: localhost # Specify the service host of the audit Redis server.However, if it is specified as a command line argument, it is ignored.
59
+ port: 6379 # Specify the service port of the audit Redis server.However, if it is specified as a command line argument, it is ignored.
60
+ password: password # Specify the access password of the audit Redis server.However, if it is specified as a command line argument, it is ignored.
61
+ svname: cmdbox # Specify the audit service name of the inference server.However, if it is specified as a command line argument, it is ignored.
62
+ retry_count: 3 # Specifies the number of reconnections to the audit Redis server.If less than 0 is specified, reconnection is forever.
63
+ retry_interval: 1 # Specifies the number of seconds before reconnecting to the audit Redis server.
64
+ timeout: 15 # Specify the maximum waiting time until the server responds.
65
+ pg_enabled: False # Specify True if using the postgresql database server.
66
+ pg_host: localhost # Specify the postgresql host.
67
+ pg_port: 5432 # Specify the postgresql port.
68
+ pg_user: postgres # Specify the postgresql user name.
69
+ pg_password: password # Specify the postgresql password.
70
+ pg_dbname: audit # Specify the postgresql database name.
71
+ retention_period_days: 365 # Specify the number of days to retain audit logs.
@@ -2,9 +2,9 @@ users: # A list of users, each of which is a map that co
2
2
  - uid: 1 # An ID that identifies a user. No two users can have the same ID.
3
3
  name: admin # A name that identifies the user. No two users can have the same name.
4
4
  password: admin # The user's password. The value is hashed with the hash function specified in the next hash field.
5
- hash: plain # The hash function used to hash the password, which can be plain, md5, sha1, or sha256, or oauth2.
5
+ hash: plain # The hash function used to hash the password, which can be plain, md5, sha1, or sha256, or oauth2, or saml.
6
6
  groups: [admin] # A list of groups to which the user belongs, as specified in the groups field.
7
- email: admin@aaa.bbb.jp # The email address of the user, used when authenticating using the provider specified in the oauth2 field.
7
+ email: admin@aaa.bbb.jp # The email address of the user, used when authenticating using the provider specified in the oauth2 or saml field.
8
8
  - uid: 101
9
9
  name: user01
10
10
  password: b75705d7e35e7014521a46b532236ec3
@@ -36,7 +36,6 @@ groups: # A list of groups, each of which is a map that c
36
36
  - gid: 103
37
37
  name: editor
38
38
  parent: user
39
-
40
39
  cmdrule: # A list of command rules, Specify a rule that determines whether or not a command is executable when executed by a user in web mode.
41
40
  policy: deny # Specify the default policy for the rule. The value can be allow or deny.
42
41
  rules: # Specify rules to allow or deny execution of the command, depending on the group the user belongs to.
@@ -50,6 +49,10 @@ cmdrule: # A list of command rules, Specify a rule that de
50
49
  mode: server
51
50
  cmds: [list]
52
51
  rule: allow
52
+ - groups: [user, guest]
53
+ mode: audit
54
+ cmds: [write]
55
+ rule: allow
53
56
  - groups: [user, guest]
54
57
  mode: web
55
58
  cmds: [genpass]
@@ -70,6 +73,7 @@ pathrule: # List of RESTAPI rules, rules that determine whe
70
73
  rule: allow
71
74
  - groups: [user]
72
75
  paths: [/signin, /assets, /bbforce_cmd, /copyright, /dosignin, /dosignout, /password/change,
76
+ /gui/user_data/load, /gui/user_data/save, /gui/user_data/delete,
73
77
  /exec_cmd, /exec_pipe, /filer, /gui, /get_server_opt, /usesignout, /versions_cmdbox, /versions_used]
74
78
  rule: allow
75
79
  - groups: [readonly]
@@ -105,7 +109,8 @@ oauth2: # OAuth2 settings.
105
109
  client_secret: XXXXXXXXXXX # Specify Google's OAuth2 client secret.
106
110
  redirect_uri: https://localhost:8443/oauth2/google/callback # Specify Google's OAuth2 redirect URI.
107
111
  scope: ['email'] # Specify the scope you want to retrieve with Google's OAuth2. Usually, just reading the email is sufficient.
108
- signin_module: # Specify the module name that implements the sign-in. see, cmdbox.app.signin.SignIn
112
+ signin_module: # Specify the module name that implements the sign-in.
113
+ cmdbox.app.auth.google_signin
109
114
  note: # Specify a description such as Google's OAuth2 reference site.
110
115
  - https://developers.google.com/identity/protocols/oauth2/web-server?hl=ja#httprest
111
116
  github: # OAuth2 settings for GitHub.
@@ -114,7 +119,8 @@ oauth2: # OAuth2 settings.
114
119
  client_secret: XXXXXXXXXXX # Specify the GitHub OAuth2 client secret.
115
120
  redirect_uri: https://localhost:8443/oauth2/github/callback # Specify the OAuth2 redirect URI for GitHub.
116
121
  scope: ['user:email'] # Specify the scope you want to get from GitHub's OAuth2. Usually, just reading the email is sufficient.
117
- signin_module: # Specify the module name that implements the sign-in. see, cmdbox.app.signin.SignIn
122
+ signin_module: # Specify the module name that implements the sign-in.
123
+ cmdbox.app.auth.github_signin
118
124
  note: # Specify a description, such as a reference site for OAuth2 on GitHub.
119
125
  - https://docs.github.com/ja/apps/oauth-apps/building-oauth-apps/authorizing-oauth-apps#scopes
120
126
  azure: # OAuth2 settings for Azure AD.
@@ -124,6 +130,34 @@ oauth2: # OAuth2 settings.
124
130
  client_secret: XXXXXXXXXXX # Specify the Azure AD OAuth2 client secret.
125
131
  redirect_uri: https://localhost:8443/oauth2/azure/callback # Specify the OAuth2 redirect URI for Azure AD.
126
132
  scope: ['openid', 'profile', 'email', 'https://graph.microsoft.com/mail.read']
127
- signin_module: # Specify the module name that implements the sign-in. see, cmdbox.app.signin.SignIn
133
+ signin_module: # Specify the module name that implements the sign-in.
134
+ cmdbox.app.auth.azure_signin
128
135
  note: # Specify a description, such as a reference site for Azure AD's OAuth2.
129
136
  - https://learn.microsoft.com/ja-jp/entra/identity-platform/v2-oauth2-auth-code-flow
137
+ saml: # SAML settings.
138
+ providers: # This is a per-provider setting for OAuth2.
139
+ azure: # SAML settings for Azure AD.
140
+ enabled: false # Specify whether to enable SAML authentication for Azure AD.
141
+ signin_module: # Specify the module name that implements the sign-in.
142
+ cmdbox.app.auth.azure_signin_saml # Specify the python3-saml configuration.
143
+ # see) https://github.com/SAML-Toolkits/python3-saml
144
+ sp:
145
+ entityId: https://localhost:8443/
146
+ assertionConsumerService:
147
+ url: https://localhost:8443/saml/azure/callback
148
+ binding: urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST
149
+ attributeConsumingService: {}
150
+ singleLogoutService:
151
+ binding: urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect
152
+ NameIDFormat: urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified
153
+ x509cert: ''
154
+ privateKey: ''
155
+ idp:
156
+ entityId: https://sts.windows.net/{tenant-id}/
157
+ singleSignOnService:
158
+ url: https://login.microsoftonline.com/{tenant-id}/saml2
159
+ binding: urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect
160
+ x509cert: XXXXXXXXXXX
161
+ singleLogoutService: {}
162
+ certFingerprint: ''
163
+ certFingerprintAlgorithm: sha1