cmdbox 0.5.3.1__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 (89) 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 +0 -1
  10. cmdbox/app/features/cli/cmdbox_edge_config.py +19 -5
  11. cmdbox/app/features/cli/cmdbox_web_user_add.py +3 -3
  12. cmdbox/app/features/cli/cmdbox_web_user_edit.py +3 -3
  13. cmdbox/app/features/web/cmdbox_web_do_signin.py +79 -103
  14. cmdbox/app/features/web/cmdbox_web_signin.py +23 -1
  15. cmdbox/app/web.py +13 -12
  16. cmdbox/extensions/sample_project/sample/extensions/features.yml +23 -0
  17. cmdbox/extensions/sample_project/sample/extensions/user_list.yml +40 -6
  18. cmdbox/extensions/user_list.yml +36 -6
  19. cmdbox/licenses/{LICENSE.python-multipart.0.0.17(Apache Software License).txt → LICENSE.async-timeout.5.0.1(Apache Software License).txt } +2 -3
  20. cmdbox/licenses/files.txt +10 -9
  21. cmdbox/version.py +2 -2
  22. cmdbox/web/assets/cmdbox/signin.js +13 -0
  23. cmdbox/web/assets/cmdbox/users.js +1 -1
  24. cmdbox/web/signin.html +10 -6
  25. {cmdbox-0.5.3.1.dist-info → cmdbox-0.5.4.dist-info}/METADATA +64 -10
  26. {cmdbox-0.5.3.1.dist-info → cmdbox-0.5.4.dist-info}/RECORD +39 -83
  27. cmdbox/app/features/web/cmdbox_web_load_pin.py +0 -43
  28. cmdbox/app/features/web/cmdbox_web_save_pin.py +0 -42
  29. cmdbox/licenses/LICENSE.Jinja2.3.1.4(BSD License).txt +0 -28
  30. cmdbox/licenses/LICENSE.Pygments.2.18.0(BSD License).txt +0 -25
  31. cmdbox/licenses/LICENSE.Sphinx.8.1.3(BSD License).txt +0 -31
  32. cmdbox/licenses/LICENSE.anyio.4.6.2.post1(MIT License).txt +0 -20
  33. cmdbox/licenses/LICENSE.argcomplete.3.5.1(Apache Software License).txt +0 -177
  34. cmdbox/licenses/LICENSE.argcomplete.3.6.1(Apache Software License).txt +0 -177
  35. cmdbox/licenses/LICENSE.babel.2.16.0(BSD License).txt +0 -27
  36. cmdbox/licenses/LICENSE.certifi.2025.1.31(Mozilla Public License 2.0 (MPL 2.0)).txt +0 -20
  37. cmdbox/licenses/LICENSE.charset-normalizer.3.4.0(MIT License).txt +0 -21
  38. cmdbox/licenses/LICENSE.click.8.1.7(BSD License).txt +0 -28
  39. cmdbox/licenses/LICENSE.cryptography.43.0.3(Apache Software License; BSD License).txt +0 -3
  40. cmdbox/licenses/LICENSE.fastapi.0.115.5(MIT License).txt +0 -21
  41. cmdbox/licenses/LICENSE.gevent.25.4.1(MIT).txt +0 -25
  42. cmdbox/licenses/LICENSE.greenlet.3.2.0(MIT AND Python-2.0).txt +0 -30
  43. cmdbox/licenses/LICENSE.importlib_metadata.8.6.1(Apache Software License).txt +0 -202
  44. cmdbox/licenses/LICENSE.keyring.25.5.0(MIT License).txt +0 -17
  45. cmdbox/licenses/LICENSE.more-itertools.10.6.0(MIT License).txt +0 -19
  46. cmdbox/licenses/LICENSE.nh3.0.2.18(MIT).txt +0 -1
  47. cmdbox/licenses/LICENSE.numpy.2.2.4(BSD License).txt +0 -950
  48. cmdbox/licenses/LICENSE.pillow.11.0.0(CMU License (MIT-CMU)).txt +0 -1226
  49. cmdbox/licenses/LICENSE.pillow.11.1.0(CMU License (MIT-CMU)).txt +0 -1213
  50. cmdbox/licenses/LICENSE.pkginfo.1.10.0(MIT License).txt +0 -21
  51. cmdbox/licenses/LICENSE.prettytable.3.12.0(BSD License).txt +0 -30
  52. cmdbox/licenses/LICENSE.prompt_toolkit.3.0.50(BSD License).txt +0 -27
  53. cmdbox/licenses/LICENSE.psycopg-pool.3.2.6(GNU Lesser General Public License v3 (LGPLv3)).txt +0 -165
  54. cmdbox/licenses/LICENSE.pydantic.2.10.2(MIT License).txt +0 -21
  55. cmdbox/licenses/LICENSE.pydantic.2.11.1(MIT License).txt +0 -21
  56. cmdbox/licenses/LICENSE.pydantic_core.2.27.1(MIT License).txt +0 -21
  57. cmdbox/licenses/LICENSE.pydantic_core.2.33.0(MIT License).txt +0 -21
  58. cmdbox/licenses/LICENSE.python-dotenv.1.0.1(BSD License).txt +0 -27
  59. cmdbox/licenses/LICENSE.redis.5.2.0(MIT License).txt +0 -21
  60. cmdbox/licenses/LICENSE.rich.13.9.4(MIT License).txt +0 -19
  61. cmdbox/licenses/LICENSE.six.1.16.0(MIT License).txt +0 -18
  62. cmdbox/licenses/LICENSE.sphinx-intl.2.3.0(BSD License).txt +0 -25
  63. cmdbox/licenses/LICENSE.starlette.0.41.3(BSD License).txt +0 -27
  64. cmdbox/licenses/LICENSE.starlette.0.46.1(BSD License).txt +0 -27
  65. cmdbox/licenses/LICENSE.tomli.2.1.0(MIT License).txt +0 -21
  66. cmdbox/licenses/LICENSE.twine.5.1.1(Apache Software License).txt +0 -174
  67. cmdbox/licenses/LICENSE.typing_extensions.4.12.2(Python Software Foundation License).txt +0 -279
  68. cmdbox/licenses/LICENSE.typing_extensions.4.13.0(UNKNOWN).txt +0 -279
  69. cmdbox/licenses/LICENSE.urllib3.2.2.3(MIT License).txt +0 -21
  70. cmdbox/licenses/LICENSE.urllib3.2.3.0(MIT License).txt +0 -21
  71. cmdbox/licenses/LICENSE.uvicorn.0.34.0(BSD License).txt +0 -27
  72. cmdbox/licenses/LICENSE.uvicorn.0.34.1(BSD License).txt +0 -27
  73. cmdbox/licenses/LICENSE.watchfiles.1.0.0(MIT License).txt +0 -21
  74. cmdbox/licenses/LICENSE.watchfiles.1.0.4(MIT License).txt +0 -21
  75. cmdbox/licenses/LICENSE.websockets.14.1(BSD License).txt +0 -24
  76. cmdbox/licenses/LICENSE.zope.interface.7.1.1(Zope Public License).txt +0 -44
  77. /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
  78. /cmdbox/licenses/{LICENSE.gevent.24.11.1(MIT License).txt → LICENSE.gevent.25.4.2(MIT).txt} +0 -0
  79. /cmdbox/licenses/{LICENSE.greenlet.3.1.1(MIT License).txt → LICENSE.greenlet.3.2.1(MIT AND Python-2.0).txt} +0 -0
  80. /cmdbox/licenses/{LICENSE.h11.0.14.0(MIT License).txt → LICENSE.h11.0.16.0(MIT License).txt} +0 -0
  81. /cmdbox/licenses/{LICENSE.importlib_metadata.8.5.0(Apache Software License).txt → LICENSE.importlib_metadata.8.7.0(Apache Software License).txt} +0 -0
  82. /cmdbox/licenses/{LICENSE.more-itertools.10.5.0(MIT License).txt → LICENSE.more-itertools.10.7.0(MIT License).txt} +0 -0
  83. /cmdbox/licenses/{LICENSE.numpy.2.1.3(BSD License).txt → LICENSE.numpy.2.2.5(BSD License).txt} +0 -0
  84. /cmdbox/licenses/{LICENSE.packaging.24.2(Apache Software License; BSD License).txt → LICENSE.packaging.25.0(Apache Software License; BSD License).txt} +0 -0
  85. /cmdbox/licenses/{LICENSE.uvicorn.0.32.1(BSD License).txt → LICENSE.uvicorn.0.34.2(BSD License).txt} +0 -0
  86. {cmdbox-0.5.3.1.dist-info → cmdbox-0.5.4.dist-info}/LICENSE +0 -0
  87. {cmdbox-0.5.3.1.dist-info → cmdbox-0.5.4.dist-info}/WHEEL +0 -0
  88. {cmdbox-0.5.3.1.dist-info → cmdbox-0.5.4.dist-info}/entry_points.txt +0 -0
  89. {cmdbox-0.5.3.1.dist-info → cmdbox-0.5.4.dist-info}/top_level.txt +0 -0
cmdbox/app/edge.py CHANGED
@@ -1,4 +1,5 @@
1
1
  from cmdbox.app import common, feature, options, web
2
+ from cmdbox.app.auth import signin, signin_saml, azure_signin, azure_signin_saml, github_signin, google_signin
2
3
  from cmdbox.app.commons import convert
3
4
  from cmdbox.app.options import Options
4
5
  from fastapi import FastAPI, Request, HTTPException
@@ -63,12 +64,30 @@ class Edge(object):
63
64
  conf = common.loadopt(conf_file)
64
65
  else:
65
66
  conf = dict()
67
+ skip_opts = []
68
+ input_opts = []
69
+ def _show_opts(choice_show:Dict[str, List[str]], value:str, ref_opts:List[Dict[str, Any]]) -> None:
70
+ for k, v in choice_show.items():
71
+ if k == value:
72
+ input_opts.extend(v)
73
+ else:
74
+ skip_opts.extend(v)
75
+ for r in ref_opts:
76
+ for opt in v:
77
+ if 'opt' not in r or r['opt'] is None:
78
+ continue
79
+ if 'choice_show' not in r or r['choice_show'] is None:
80
+ continue
81
+ _show_opts(r['choice_show'], '', [o for o in ref_opts if o['opt'] == opt])
66
82
  for r in ref_opts:
67
83
  if 'opt' not in r or r['opt'] is None:
68
84
  continue
69
85
  opt = r['opt']
70
- if opt in ['output_json', 'output_json_append', 'stdout_log', 'capture_stdout', 'capture_maxsize']:
86
+ if opt in ['tag', 'clmsg_id', 'output_json', 'output_json_append', 'stdout_log', 'capture_stdout', 'capture_maxsize']:
87
+ continue
88
+ if opt in skip_opts and opt not in input_opts:
71
89
  continue
90
+ choice_show = r['choice_show'] if 'choice_show' in r else dict()
72
91
  default = conf[opt] if opt in conf else None
73
92
  default = r['default'] if default is None and 'default' in r else default
74
93
  default = default if default is not None else ''
@@ -86,6 +105,7 @@ class Edge(object):
86
105
  value = questionary.select(f"{opt}:({help}):", choice, default=default).ask()
87
106
  else:
88
107
  value = questionary.text(f"{opt}:({help}):", default=default, validate=lambda v:not required or len(v)>0).ask()
108
+ _show_opts(choice_show, value, ref_opts)
89
109
  if r['type'] == Options.T_BOOL: value = value=='True'
90
110
  if r['type'] == Options.T_INT: value = int(value)
91
111
  if r['type'] == Options.T_FLOAT: value = float(value)
@@ -129,43 +149,71 @@ class Edge(object):
129
149
  if 'auth_type' not in opt or opt['auth_type'] is None:
130
150
  msg = dict(warn=f"Please run the `edge config` command. And please set the auth_type.")
131
151
  return msg
132
- if opt['auth_type'] == 'idpw' and ('user' not in opt or opt['user'] is None):
133
- msg = dict(warn=f"Please run the `edge config` command. And please set the user.")
134
- return msg
135
- if opt['auth_type'] == 'idpw' and ('password' not in opt or opt['password'] is None):
136
- msg = dict(warn=f"Please run the `edge config` command. And please set the password.")
137
- return msg
138
- if opt['auth_type'] == 'apikey' and ('apikey' not in opt or opt['apikey'] is None):
139
- msg = dict(warn=f"Please run the `edge config` command. And please set the apikey.")
140
- return msg
141
- if opt['auth_type'] == 'oauth2' and ('oauth2' not in opt or opt['oauth2'] is None):
142
- msg = dict(warn=f"Please run the `edge config` command. And please set the oauth2.")
143
- return msg
144
- if opt['auth_type'] == 'oauth2' and ('oauth2_port' not in opt or opt['oauth2_port'] is None):
145
- msg = dict(warn=f"Please run the `edge config` command. And please set the oauth2_port.")
146
- return msg
147
- if isinstance(opt['oauth2_port'], str):
148
- if not opt['oauth2_port'].isdigit():
149
- msg = dict(warn=f"Please set the numeric value in the oauth2_port. oauth2_port={opt['oauth2_port']}")
152
+ if opt['auth_type'] == 'idpw':
153
+ if 'user' not in opt or opt['user'] is None:
154
+ msg = dict(warn=f"Please run the `edge config` command. And please set the user.")
150
155
  return msg
151
- opt['oauth2_port'] = int(opt['oauth2_port'])
152
- if opt['oauth2'] == 'azure' and ('oauth2_tenant_id' not in opt or opt['oauth2_tenant_id'] is None):
153
- msg = dict(warn=f"Please run the `edge config` command. And please set the oauth2_tenant_id.")
154
- return msg
155
- if opt['auth_type'] == 'oauth2' and ('oauth2_client_id' not in opt or opt['oauth2_client_id'] is None):
156
- msg = dict(warn=f"Please run the `edge config` command. And please set the oauth2_client_id.")
157
- return msg
158
- if opt['auth_type'] == 'oauth2' and ('oauth2_client_secret' not in opt or opt['oauth2_client_secret'] is None):
159
- msg = dict(warn=f"Please run the `edge config` command. And please set the oauth2_client_secret.")
160
- return msg
161
- if opt['auth_type'] == 'oauth2' and ('oauth2_timeout' not in opt or opt['oauth2_timeout'] is None):
162
- msg = dict(warn=f"Please run the `edge config` command. And please set the oauth2_timeout.")
163
- return msg
164
- if isinstance(opt['oauth2_timeout'], str):
165
- if not opt['oauth2_timeout'].isdigit():
166
- msg = dict(warn=f"Please set the numeric value in the oauth2_timeout. oauth2_timeout={opt['oauth2_timeout']}")
156
+ if 'password' not in opt or opt['password'] is None:
157
+ msg = dict(warn=f"Please run the `edge config` command. And please set the password.")
158
+ return msg
159
+ if opt['auth_type'] == 'apikey':
160
+ if 'apikey' not in opt or opt['apikey'] is None:
161
+ msg = dict(warn=f"Please run the `edge config` command. And please set the apikey.")
167
162
  return msg
168
- opt['oauth2_timeout'] = int(opt['oauth2_timeout'])
163
+ if opt['auth_type'] == 'oauth2':
164
+ if 'oauth2' not in opt or opt['oauth2'] is None:
165
+ msg = dict(warn=f"Please run the `edge config` command. And please set the oauth2.")
166
+ return msg
167
+ if 'oauth2_port' not in opt or opt['oauth2_port'] is None:
168
+ msg = dict(warn=f"Please run the `edge config` command. And please set the oauth2_port.")
169
+ return msg
170
+ if isinstance(opt['oauth2_port'], str):
171
+ if not opt['oauth2_port'].isdigit():
172
+ msg = dict(warn=f"Please set the numeric value in the oauth2_port. oauth2_port={opt['oauth2_port']}")
173
+ return msg
174
+ opt['oauth2_port'] = int(opt['oauth2_port'])
175
+ if opt['oauth2'] == 'azure':
176
+ if 'oauth2_tenant_id' not in opt or opt['oauth2_tenant_id'] is None:
177
+ msg = dict(warn=f"Please run the `edge config` command. And please set the oauth2_tenant_id.")
178
+ return msg
179
+ if 'oauth2_client_id' not in opt or opt['oauth2_client_id'] is None:
180
+ msg = dict(warn=f"Please run the `edge config` command. And please set the oauth2_client_id.")
181
+ return msg
182
+ if 'oauth2_client_secret' not in opt or opt['oauth2_client_secret'] is None:
183
+ msg = dict(warn=f"Please run the `edge config` command. And please set the oauth2_client_secret.")
184
+ return msg
185
+ if 'oauth2_timeout' not in opt or opt['oauth2_timeout'] is None:
186
+ msg = dict(warn=f"Please run the `edge config` command. And please set the oauth2_timeout.")
187
+ return msg
188
+ if isinstance(opt['oauth2_timeout'], str):
189
+ if not opt['oauth2_timeout'].isdigit():
190
+ msg = dict(warn=f"Please set the numeric value in the oauth2_timeout. oauth2_timeout={opt['oauth2_timeout']}")
191
+ return msg
192
+ opt['oauth2_timeout'] = int(opt['oauth2_timeout'])
193
+ if opt['auth_type'] == 'saml':
194
+ if 'saml' not in opt or opt['saml'] is None:
195
+ msg = dict(warn=f"Please run the `edge config` command. And please set the saml.")
196
+ return msg
197
+ if 'saml_port' not in opt or opt['saml_port'] is None:
198
+ msg = dict(warn=f"Please run the `edge config` command. And please set the saml.")
199
+ return msg
200
+ if isinstance(opt['saml_port'], str):
201
+ if not opt['saml_port'].isdigit():
202
+ msg = dict(warn=f"Please set the numeric value in the saml_port. saml_port={opt['saml_port']}")
203
+ return msg
204
+ opt['saml_port'] = int(opt['saml_port'])
205
+ if opt['saml'] == 'azure':
206
+ if 'saml_tenant_id' not in opt or opt['saml_tenant_id'] is None:
207
+ msg = dict(warn=f"Please run the `edge config` command. And please set the saml_tenant_id.")
208
+ return msg
209
+ if 'saml_timeout' not in opt or opt['saml_timeout'] is None:
210
+ msg = dict(warn=f"Please run the `edge config` command. And please set the saml_timeout.")
211
+ return msg
212
+ if isinstance(opt['saml_timeout'], str):
213
+ if not opt['saml_timeout'].isdigit():
214
+ msg = dict(warn=f"Please set the numeric value in the saml_timeout. saml_timeout={opt['saml_timeout']}")
215
+ return msg
216
+ opt['saml_timeout'] = int(opt['saml_timeout'])
169
217
  if 'svcert_no_verify' not in opt or opt['svcert_no_verify'] is not True:
170
218
  opt['svcert_no_verify'] = False
171
219
  if 'timeout' not in opt or opt['timeout'] is None:
@@ -184,9 +232,12 @@ class Edge(object):
184
232
  if self.svcert_no_verify:
185
233
  urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
186
234
  status, msg = self.signin(opt.get('auth_type'), opt.get('user'), opt.get('password'), opt.get('apikey'),
187
- opt.get('oauth2'), int(opt.get('oauth2_port')),
235
+ opt.get('oauth2'), int(opt.get('oauth2_port', 8091)),
188
236
  opt.get('oauth2_tenant_id'), opt.get('oauth2_client_id'), opt.get('oauth2_client_secret'),
189
- int(opt.get('oauth2_timeout')))
237
+ int(opt.get('oauth2_timeout', 60)),
238
+ opt.get('saml'), int(opt.get('saml_port', 8091)),
239
+ opt.get('saml_tenant_id'), int(opt.get('saml_timeout', 60)))
240
+
190
241
  if status != 0:
191
242
  return msg
192
243
 
@@ -248,7 +299,7 @@ class Edge(object):
248
299
  resq:queue.Queue = pipe_cmd['resq']
249
300
  del pipe_cmd['resq']
250
301
  tool = Tool(self.logger, self.appcls, self.ver)
251
- tool.set_session(self.session, self.svcert_no_verify, self.endpoint, self.icon_path, self.user_info, self.oauth2)
302
+ tool.set_session(self.session, self.svcert_no_verify, self.endpoint, self.icon_path, self.user_info, self.oauth2, self.saml)
252
303
  feat:feature.Feature = self.options.get_cmd_attr(pipe_cmd['mode'], pipe_cmd['cmd'], 'feature')
253
304
  while not thevent.is_set():
254
305
  prevres = None if prevq is None else prevq.get(pipe_cmd['timeout'])
@@ -305,7 +356,7 @@ class Edge(object):
305
356
  def mkcmd(opt):
306
357
  def _ex():
307
358
  tool = Tool(self.logger, self.appcls, self.ver)
308
- tool.set_session(self.session, self.svcert_no_verify, self.endpoint, self.icon_path, self.user_info, self.oauth2)
359
+ tool.set_session(self.session, self.svcert_no_verify, self.endpoint, self.icon_path, self.user_info, self.oauth2, self.saml)
309
360
  feat:feature.Feature = self.options.get_cmd_attr(opt['mode'], opt['cmd'], 'feature')
310
361
  for status, ret in feat.edgerun(opt, tool, self.logger, self.timeout):
311
362
  pass
@@ -353,7 +404,9 @@ class Edge(object):
353
404
 
354
405
  def signin(self, auth_type:str, user:str, password:str, apikey:str,
355
406
  oauth2:str, oauth2_port:int, oauth2_tenant_id:str, oauth2_client_id:str, oauth2_client_secret:str,
356
- oauth2_timeout:int) -> Tuple[int, Dict[str, Any]]:
407
+ oauth2_timeout:int,
408
+ saml:str, saml_port:int, saml_tenant_id:str,
409
+ saml_timeout:int) -> Tuple[int, Dict[str, Any]]:
357
410
  """
358
411
  サインインを行います
359
412
 
@@ -368,6 +421,10 @@ class Edge(object):
368
421
  oauth2_client_id (str): OAuth2クライアントID
369
422
  oauth2_client_secret (str): OAuth2クライアントシークレット
370
423
  oauth2_timeout (int): OAuth2タイムアウト
424
+ saml (str): SAML
425
+ saml_port (int): SAMLポート
426
+ saml_tenant_id (str): SAMLテナントID
427
+ saml_timeout (int): SAMLタイムアウト
371
428
 
372
429
  Returns:
373
430
  Tuple[int, Dict[str, Any]]: 終了コード, メッセージ
@@ -375,13 +432,14 @@ class Edge(object):
375
432
  self.session = requests.Session()
376
433
  self.signed_in = False
377
434
  self.oauth2 = oauth2
435
+ self.saml = saml
378
436
  if auth_type == "noauth":
379
437
  status, res, _ = self.site_request(self.session.get, "/gui")
380
438
  if status != 0: return status, res
381
439
  status, self.user_info = self.load_user_info()
382
440
  self.user_info['auth_type'] = auth_type
383
441
  if status != 0: return status, res
384
- self.tool.set_session(self.session, self.svcert_no_verify, self.endpoint, self.icon_path, self.user_info, self.oauth2)
442
+ self.tool.set_session(self.session, self.svcert_no_verify, self.endpoint, self.icon_path, self.user_info, self.oauth2, self.saml)
385
443
  return 0, dict(success="No auth.")
386
444
 
387
445
  # ID/PW認証を使用する場合
@@ -398,7 +456,7 @@ class Edge(object):
398
456
  self.user_info['auth_type'] = auth_type
399
457
  self.user_info['password'] = password
400
458
  if status != 0: return status, res
401
- self.tool.set_session(self.session, self.svcert_no_verify, self.endpoint, self.icon_path, self.user_info, self.oauth2)
459
+ self.tool.set_session(self.session, self.svcert_no_verify, self.endpoint, self.icon_path, self.user_info, self.oauth2, self.saml)
402
460
  return 0, dict(success="Signin Success.")
403
461
 
404
462
  # APIKEY認証を使用する場合
@@ -413,7 +471,7 @@ class Edge(object):
413
471
  self.user_info['auth_type'] = auth_type
414
472
  self.user_info['apikey'] = apikey
415
473
  if status != 0: return status, res
416
- self.tool.set_session(self.session, self.svcert_no_verify, self.endpoint, self.icon_path, self.user_info, self.oauth2)
474
+ self.tool.set_session(self.session, self.svcert_no_verify, self.endpoint, self.icon_path, self.user_info, self.oauth2, self.saml)
417
475
  return 0, dict(success="Signin Success.")
418
476
 
419
477
  # OAuth2認証を使用する場合
@@ -453,9 +511,9 @@ class Edge(object):
453
511
  status, self.user_info = self.load_user_info()
454
512
  self.user_info['auth_type'] = auth_type
455
513
  self.user_info['access_token'] = access_token
456
- if status != 0: return status, res
514
+ if status != 0: return res
457
515
  self.signed_in = True
458
- self.tool.set_session(self.session, self.svcert_no_verify, self.endpoint, self.icon_path, self.user_info, self.oauth2)
516
+ self.tool.set_session(self.session, self.svcert_no_verify, self.endpoint, self.icon_path, self.user_info, self.oauth2, self.saml)
459
517
  return dict(success="Signin success. Please close your browser.")
460
518
  except Exception as e:
461
519
  raise HTTPException(status_code=500, detail=f'Failed to get token. {e}')
@@ -519,9 +577,9 @@ class Edge(object):
519
577
  status, self.user_info = self.load_user_info()
520
578
  self.user_info['auth_type'] = auth_type
521
579
  self.user_info['access_token'] = access_token
522
- if status != 0: return status, res
580
+ if status != 0: return res
523
581
  self.signed_in = True
524
- self.tool.set_session(self.session, self.svcert_no_verify, self.endpoint, self.icon_path, self.user_info, self.oauth2)
582
+ self.tool.set_session(self.session, self.svcert_no_verify, self.endpoint, self.icon_path, self.user_info, self.oauth2, self.saml)
525
583
  return dict(success="Signin success. Please close your browser.")
526
584
  except Exception as e:
527
585
  raise HTTPException(status_code=500, detail=f'Failed to get token. {e}')
@@ -549,7 +607,6 @@ class Edge(object):
549
607
  time.sleep(1)
550
608
  return 0, dict(success="Signin success.")
551
609
 
552
-
553
610
  # Azure OAuth2を使用する場合
554
611
  elif oauth2 == "azure":
555
612
  if oauth2_tenant_id is None:
@@ -591,9 +648,9 @@ class Edge(object):
591
648
  status, self.user_info = self.load_user_info()
592
649
  self.user_info['auth_type'] = auth_type
593
650
  self.user_info['access_token'] = access_token
594
- if status != 0: return status, res
651
+ if status != 0: return res
595
652
  self.signed_in = True
596
- self.tool.set_session(self.session, self.svcert_no_verify, self.endpoint, self.icon_path, self.user_info, self.oauth2)
653
+ self.tool.set_session(self.session, self.svcert_no_verify, self.endpoint, self.icon_path, self.user_info, self.oauth2, self.saml)
597
654
  return dict(success="Signin success. Please close your browser.")
598
655
  except Exception as e:
599
656
  raise HTTPException(status_code=500, detail=f'Failed to get token. {e}')
@@ -622,6 +679,80 @@ class Edge(object):
622
679
  time.sleep(1)
623
680
  return 0, dict(success="Signin success.")
624
681
 
682
+ # saml認証を使用する場合
683
+ elif auth_type == "saml":
684
+ # Azure samlを使用する場合
685
+ if saml == "azure":
686
+ if saml_tenant_id is None:
687
+ return 1, dict(warn="Please specify the --saml_tenant_id option.")
688
+ saml_settings = dict(
689
+ strict=False,
690
+ debug=self.logger.level==logging.DEBUG,
691
+ idp=dict(
692
+ entityId=f'https://sts.windows.net/{saml_tenant_id}/',
693
+ singleSignOnService=dict(
694
+ url=f'https://login.microsoftonline.com/{saml_tenant_id}/saml2',
695
+ binding=f'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect'),
696
+ certFingerprint='',
697
+ certFingerprintAlgorithm='sha1',
698
+ singleLogoutService=dict()),
699
+ sp=dict(
700
+ entityId=self.endpoint,
701
+ assertionConsumerService=dict(
702
+ url=f'http://localhost:{saml_port}/saml/azure/callback',
703
+ binding=f'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST'),
704
+ attributeConsumingService=dict(),
705
+ singleLogoutService=dict(
706
+ binding=f'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect'),
707
+ NameIDFormat=f'urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified',
708
+ x509cert='',
709
+ privateKey=''))
710
+ request_data = dict(
711
+ https='off',
712
+ http_host='localhost',
713
+ server_port=saml_port,
714
+ script_name=f'/saml/azure/gui?next=gui',
715
+ post_data=dict(),
716
+ get_data=dict(n=common.random_string(8)),
717
+ )
718
+ from onelogin.saml2.auth import OneLogin_Saml2_Auth
719
+ auth = OneLogin_Saml2_Auth(request_data=request_data, old_settings=saml_settings)
720
+
721
+ # SAML認証のコールバックを受けるFastAPIサーバーを起動
722
+ fastapi = FastAPI()
723
+ @fastapi.post('/saml/azure/callback')
724
+ async def saml_azure_callback(req:Request):
725
+ form_data = await req.form()
726
+ try:
727
+ status, res, headers = self.site_request(self.session.post, f"/saml/azure/callback", data=form_data, ok_status=[200, 307])
728
+ if status != 0 or headers.get('signin') is None:
729
+ return dict(warn=f"Signin failed.")
730
+ status, self.user_info = self.load_user_info()
731
+ self.user_info['auth_type'] = auth_type
732
+ if status != 0: return res
733
+ self.signed_in = True
734
+ self.user_info['saml_token'] = convert.str2b64str(common.to_str(form_data._dict))
735
+ self.tool.set_session(self.session, self.svcert_no_verify, self.endpoint, self.icon_path, self.user_info, self.oauth2, self.saml)
736
+ return dict(success="Signin success. Please close your browser.")
737
+ except Exception as e:
738
+ raise HTTPException(status_code=500, detail=f'Failed to get token. {e}')
739
+
740
+ if not hasattr(self, 'thUvicorn') or not self.thUvicorn.is_alive():
741
+ self.thUvicorn = web.ThreadedUvicorn(self.logger, Config(app=fastapi, host='localhost', port=saml_port), {})
742
+ self.thUvicorn.start()
743
+ time.sleep(1)
744
+
745
+ # SAML認証のリクエストを送信
746
+ webbrowser.open(auth.login())
747
+
748
+ # 認証完了まで指定秒数待つ
749
+ tm = time.time()
750
+ while not self.signed_in:
751
+ if time.time() - tm > saml_timeout:
752
+ return 1, dict(warn="Signin Timeout.")
753
+ time.sleep(1)
754
+ return 0, dict(success="Signin success.")
755
+
625
756
  return 1, dict(warn="unsupported auth_type.")
626
757
 
627
758
  class Tool(object):
@@ -657,7 +788,7 @@ class Tool(object):
657
788
  except Exception as e:
658
789
  self.logger.error(f"notify error. {e}", exc_info=True)
659
790
 
660
- def set_session(self, session:requests.Session, svcert_no_verify:bool, endpoint:str, icon_path:Path, user_info:Dict[str, Any], oauth2:str):
791
+ def set_session(self, session:requests.Session, svcert_no_verify:bool, endpoint:str, icon_path:Path, user_info:Dict[str, Any], oauth2:str, saml:str):
661
792
  """
662
793
  セッションを設定します
663
794
 
@@ -675,6 +806,7 @@ class Tool(object):
675
806
  self.icon_path = icon_path
676
807
  self.user = user_info
677
808
  self.oauth2 = oauth2
809
+ self.saml = saml
678
810
 
679
811
  def exec_cmd(self, opt:Dict[str, Any], logger:logging.Logger, timeout:int, prevres:Any=None) -> Tuple[int, Dict[str, Any]]:
680
812
  """
@@ -774,13 +906,18 @@ class Tool(object):
774
906
  token = convert.str2b64str(common.to_str(token))
775
907
  webbrowser.open(f"{self.endpoint}/dosignin_token/{token}{path}")
776
908
  return 0, dict(success="Open browser.")
777
- elif self.user['auth_type'] == "oauth2" and self.oauth2 == 'google':
778
- webbrowser.open(f"{self.endpoint}/oauth2/google/session/{self.user['access_token']}{path}")
779
- return 0, dict(success="Open browser.")
780
- elif self.user['auth_type'] == "oauth2" and self.oauth2 == 'github':
781
- webbrowser.open(f"{self.endpoint}/oauth2/github/session/{self.user['access_token']}{path}")
782
- return 0, dict(success="Open browser.")
783
- elif self.user['auth_type'] == "oauth2" and self.oauth2 == 'azure':
784
- webbrowser.open(f"{self.endpoint}/oauth2/azure/session/{self.user['access_token']}{path}")
785
- return 0, dict(success="Open browser.")
909
+ elif self.user['auth_type'] == "oauth2":
910
+ if self.oauth2 == 'google':
911
+ webbrowser.open(f"{self.endpoint}/oauth2/google/session/{self.user['access_token']}{path}")
912
+ return 0, dict(success="Open browser.")
913
+ if self.oauth2 == 'github':
914
+ webbrowser.open(f"{self.endpoint}/oauth2/github/session/{self.user['access_token']}{path}")
915
+ return 0, dict(success="Open browser.")
916
+ if self.oauth2 == 'azure':
917
+ webbrowser.open(f"{self.endpoint}/oauth2/azure/session/{self.user['access_token']}{path}")
918
+ return 0, dict(success="Open browser.")
919
+ elif self.user['auth_type'] == "saml":
920
+ if self.saml == 'azure':
921
+ webbrowser.open(f"{self.endpoint}/saml/azure/session/{self.user['saml_token']}{path}")
922
+ return 0, dict(success="Open browser.")
786
923
  return 1, dict(warn="unsupported auth_type.")
cmdbox/app/feature.py CHANGED
@@ -8,7 +8,6 @@ from typing import Dict, Any, Tuple, List, Union
8
8
  import argparse
9
9
  import logging
10
10
  import os
11
- import time
12
11
 
13
12
 
14
13
  class Feature(object):
@@ -44,12 +44,13 @@ class EdgeConfig(feature.UnsupportEdgeFeature):
44
44
  required=False, multi=False, hide=False, choice=None,
45
45
  discription_ja="アイコン画像のパスを指定します。",
46
46
  discription_en="Specify the path to the icon image."),
47
- dict(opt="auth_type", type=Options.T_STR, default="idpw", required=False, multi=False, hide=False, choice=["noauth", "idpw", "apikey", "oauth2"],
47
+ dict(opt="auth_type", type=Options.T_STR, default="idpw", required=False, multi=False, hide=False, choice=["noauth", "idpw", "apikey", "oauth2", "saml"],
48
48
  discription_ja="エンドポイント接続じの認証方式を指定します。",
49
49
  discription_en="Specifies the authentication method for endpoint connections.",
50
50
  choice_show=dict(idpw=["user","password"],
51
51
  apikey=["apikey"],
52
- oauth2=["oauth2","oauth2_port"]),),
52
+ oauth2=["oauth2", "oauth2_port", "oauth2_timeout"],
53
+ saml=["saml", "saml_port", "saml_timeout"]),),
53
54
  dict(opt="user", type=Options.T_STR, default="user", required=False, multi=False, hide=False, choice=None,
54
55
  discription_ja="エンドポイントへの接続ユーザーを指定します。",
55
56
  discription_en="Specifies the user connecting to the endpoint."),
@@ -62,9 +63,9 @@ class EdgeConfig(feature.UnsupportEdgeFeature):
62
63
  dict(opt="oauth2", type=Options.T_STR, default=None, required=False, multi=False, hide=False, choice=["", "google", "github", "azure"],
63
64
  discription_ja="OAuth2認証を使用してエンドポイントに接続します。",
64
65
  discription_en="Connect to the endpoint using OAuth2 authentication.",
65
- choice_show=dict(google=["oauth2_client_id","oauth2_client_secret"],
66
- github=["oauth2_client_id","oauth2_client_secret"],
67
- azure=["oauth2_tenant_id","oauth2_client_id","oauth2_client_secret"])),
66
+ choice_show=dict(google=["oauth2_client_id", "oauth2_client_secret"],
67
+ github=["oauth2_client_id", "oauth2_client_secret"],
68
+ azure=["oauth2_tenant_id", "oauth2_client_id", "oauth2_client_secret"])),
68
69
  dict(opt="oauth2_port", type=Options.T_INT, default="8091", required=False, multi=False, hide=False, choice=None,
69
70
  discription_ja="OAuth2認証を使用する場合のコールバックポートを指定します。省略した時は `8091` を使用します。",
70
71
  discription_en="Specifies the callback port when OAuth2 authentication is used. If omitted, `8091` is used."),
@@ -80,6 +81,19 @@ class EdgeConfig(feature.UnsupportEdgeFeature):
80
81
  dict(opt="oauth2_timeout", type=Options.T_INT, default="60", required=False, multi=False, hide=False, choice=None,
81
82
  discription_ja="OAuth2認証が完了するまでのタイムアウト時間を指定します。",
82
83
  discription_en="Specify the timeout period before OAuth2 authentication completes."),
84
+ dict(opt="saml", type=Options.T_STR, default=None, required=False, multi=False, hide=False, choice=["", "azure"],
85
+ discription_ja="SAML認証を使用してエンドポイントに接続します。",
86
+ discription_en="Connect to the endpoint using SAML authentication.",
87
+ choice_show=dict(azure=["saml_tenant_id"])),
88
+ dict(opt="saml_port", type=Options.T_INT, default="8091", required=False, multi=False, hide=False, choice=None,
89
+ discription_ja="SAML認証を使用する場合のコールバックポートを指定します。省略した時は `8091` を使用します。",
90
+ discription_en="Specifies the callback port when SAML authentication is used. If omitted, `8091` is used."),
91
+ dict(opt="saml_tenant_id", type=Options.T_STR, default=None, required=False, multi=False, hide=False, choice=None,
92
+ discription_ja="SAML認証を使用するときのテナントIDを指定します。",
93
+ discription_en="Specifies the tenant ID when SAML authentication is used."),
94
+ dict(opt="saml_timeout", type=Options.T_INT, default="60", required=False, multi=False, hide=False, choice=None,
95
+ discription_ja="SAML認証が完了するまでのタイムアウト時間を指定します。",
96
+ discription_en="Specify the timeout period before SAML authentication completes."),
83
97
  dict(opt="data", type=Options.T_FILE, default=common.HOME_DIR / f".{self.ver.__appid__}", required=False, multi=False, hide=True, choice=None,
84
98
  discription_ja=f"省略した時は f`$HONE/.{self.ver.__appid__}` を使用します。",
85
99
  discription_en=f"When omitted, f`$HONE/.{self.ver.__appid__}` is used."),
@@ -61,12 +61,12 @@ class WebUserAdd(feature.UnsupportEdgeFeature):
61
61
  dict(opt="user_pass", type=Options.T_STR, default=None, required=False, multi=False, hide=False, choice=None,
62
62
  discription_ja="ユーザーパスワードを指定します。",
63
63
  discription_en="Specify the user password."),
64
- dict(opt="user_pass_hash", type=Options.T_STR, default='sha1', required=False, multi=False, hide=False, choice=['oauth2', 'plain', 'md5', 'sha1', 'sha256'],
64
+ dict(opt="user_pass_hash", type=Options.T_STR, default='sha1', required=False, multi=False, hide=False, choice=['oauth2', 'saml', 'plain', 'md5', 'sha1', 'sha256'],
65
65
  discription_ja="ユーザーパスワードのハッシュアルゴリズムを指定します。",
66
66
  discription_en="Specifies the hash algorithm for user passwords."),
67
67
  dict(opt="user_email", type=Options.T_STR, default=None, required=False, multi=False, hide=False, choice=None,
68
- discription_ja="ユーザーメールアドレスを指定します。 `user_pass_hash` が `oauth2` の時は必須です。",
69
- discription_en="Specify the user email. Required when `user_pass_hash` is `oauth2`."),
68
+ discription_ja="ユーザーメールアドレスを指定します。 `user_pass_hash` が `oauth2` 又は `saml` の時は必須です。",
69
+ discription_en="Specify the user email. Required when `user_pass_hash` is `oauth2` or `saml`."),
70
70
  dict(opt="user_group", type=Options.T_STR, default=None, required=True, multi=True, hide=False, choice=None,
71
71
  discription_ja="ユーザーが所属するグループを指定します。",
72
72
  discription_en="Specifies the groups to which the user belongs."),
@@ -61,12 +61,12 @@ class WebUserEdit(feature.UnsupportEdgeFeature):
61
61
  dict(opt="user_pass", type=Options.T_STR, default=None, required=False, multi=False, hide=False, choice=None,
62
62
  discription_ja="ユーザーパスワードを指定します。",
63
63
  discription_en="Specify the user password."),
64
- dict(opt="user_pass_hash", type=Options.T_STR, default='sha1', required=False, multi=False, hide=False, choice=['oauth2', 'plain', 'md5', 'sha1', 'sha256'],
64
+ dict(opt="user_pass_hash", type=Options.T_STR, default='sha1', required=False, multi=False, hide=False, choice=['oauth2', 'saml', 'plain', 'md5', 'sha1', 'sha256'],
65
65
  discription_ja="ユーザーパスワードのハッシュアルゴリズムを指定します。",
66
66
  discription_en="Specifies the hash algorithm for user passwords."),
67
67
  dict(opt="user_email", type=Options.T_STR, default=None, required=False, multi=False, hide=False, choice=None,
68
- discription_ja="ユーザーメールアドレスを指定します。 `user_pass_hash` が `oauth2` の時は必須です。",
69
- discription_en="Specify the user email. Required when `user_pass_hash` is `oauth2`."),
68
+ discription_ja="ユーザーメールアドレスを指定します。 `user_pass_hash` が `oauth2` 又は `saml` の時は必須です。",
69
+ discription_en="Specify the user email. Required when `user_pass_hash` is `oauth2` or `saml`."),
70
70
  dict(opt="user_group", type=Options.T_STR, default=None, required=True, multi=True, hide=False, choice=None,
71
71
  discription_ja="ユーザーが所属するグループを指定します。",
72
72
  discription_en="Specifies the groups to which the user belongs."),