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.
- cmdbox/app/auth/__init__.py +0 -0
- cmdbox/app/auth/azure_signin.py +38 -0
- cmdbox/app/auth/azure_signin_saml.py +12 -0
- cmdbox/app/auth/github_signin.py +38 -0
- cmdbox/app/auth/google_signin.py +32 -0
- cmdbox/app/auth/signin.py +47 -4
- cmdbox/app/auth/signin_saml.py +61 -0
- cmdbox/app/edge.py +198 -61
- cmdbox/app/feature.py +0 -1
- cmdbox/app/features/cli/cmdbox_edge_config.py +19 -5
- cmdbox/app/features/cli/cmdbox_web_user_add.py +3 -3
- cmdbox/app/features/cli/cmdbox_web_user_edit.py +3 -3
- cmdbox/app/features/web/cmdbox_web_do_signin.py +79 -103
- cmdbox/app/features/web/cmdbox_web_signin.py +23 -1
- cmdbox/app/web.py +13 -12
- cmdbox/extensions/sample_project/sample/extensions/features.yml +23 -0
- cmdbox/extensions/sample_project/sample/extensions/user_list.yml +40 -6
- cmdbox/extensions/user_list.yml +36 -6
- cmdbox/licenses/{LICENSE.python-multipart.0.0.17(Apache Software License).txt → LICENSE.async-timeout.5.0.1(Apache Software License).txt } +2 -3
- cmdbox/licenses/files.txt +10 -9
- cmdbox/version.py +2 -2
- cmdbox/web/assets/cmdbox/signin.js +13 -0
- cmdbox/web/assets/cmdbox/users.js +1 -1
- cmdbox/web/signin.html +10 -6
- {cmdbox-0.5.3.1.dist-info → cmdbox-0.5.4.dist-info}/METADATA +64 -10
- {cmdbox-0.5.3.1.dist-info → cmdbox-0.5.4.dist-info}/RECORD +39 -83
- cmdbox/app/features/web/cmdbox_web_load_pin.py +0 -43
- cmdbox/app/features/web/cmdbox_web_save_pin.py +0 -42
- cmdbox/licenses/LICENSE.Jinja2.3.1.4(BSD License).txt +0 -28
- cmdbox/licenses/LICENSE.Pygments.2.18.0(BSD License).txt +0 -25
- cmdbox/licenses/LICENSE.Sphinx.8.1.3(BSD License).txt +0 -31
- cmdbox/licenses/LICENSE.anyio.4.6.2.post1(MIT License).txt +0 -20
- cmdbox/licenses/LICENSE.argcomplete.3.5.1(Apache Software License).txt +0 -177
- cmdbox/licenses/LICENSE.argcomplete.3.6.1(Apache Software License).txt +0 -177
- cmdbox/licenses/LICENSE.babel.2.16.0(BSD License).txt +0 -27
- cmdbox/licenses/LICENSE.certifi.2025.1.31(Mozilla Public License 2.0 (MPL 2.0)).txt +0 -20
- cmdbox/licenses/LICENSE.charset-normalizer.3.4.0(MIT License).txt +0 -21
- cmdbox/licenses/LICENSE.click.8.1.7(BSD License).txt +0 -28
- cmdbox/licenses/LICENSE.cryptography.43.0.3(Apache Software License; BSD License).txt +0 -3
- cmdbox/licenses/LICENSE.fastapi.0.115.5(MIT License).txt +0 -21
- cmdbox/licenses/LICENSE.gevent.25.4.1(MIT).txt +0 -25
- cmdbox/licenses/LICENSE.greenlet.3.2.0(MIT AND Python-2.0).txt +0 -30
- cmdbox/licenses/LICENSE.importlib_metadata.8.6.1(Apache Software License).txt +0 -202
- cmdbox/licenses/LICENSE.keyring.25.5.0(MIT License).txt +0 -17
- cmdbox/licenses/LICENSE.more-itertools.10.6.0(MIT License).txt +0 -19
- cmdbox/licenses/LICENSE.nh3.0.2.18(MIT).txt +0 -1
- cmdbox/licenses/LICENSE.numpy.2.2.4(BSD License).txt +0 -950
- cmdbox/licenses/LICENSE.pillow.11.0.0(CMU License (MIT-CMU)).txt +0 -1226
- cmdbox/licenses/LICENSE.pillow.11.1.0(CMU License (MIT-CMU)).txt +0 -1213
- cmdbox/licenses/LICENSE.pkginfo.1.10.0(MIT License).txt +0 -21
- cmdbox/licenses/LICENSE.prettytable.3.12.0(BSD License).txt +0 -30
- cmdbox/licenses/LICENSE.prompt_toolkit.3.0.50(BSD License).txt +0 -27
- cmdbox/licenses/LICENSE.psycopg-pool.3.2.6(GNU Lesser General Public License v3 (LGPLv3)).txt +0 -165
- cmdbox/licenses/LICENSE.pydantic.2.10.2(MIT License).txt +0 -21
- cmdbox/licenses/LICENSE.pydantic.2.11.1(MIT License).txt +0 -21
- cmdbox/licenses/LICENSE.pydantic_core.2.27.1(MIT License).txt +0 -21
- cmdbox/licenses/LICENSE.pydantic_core.2.33.0(MIT License).txt +0 -21
- cmdbox/licenses/LICENSE.python-dotenv.1.0.1(BSD License).txt +0 -27
- cmdbox/licenses/LICENSE.redis.5.2.0(MIT License).txt +0 -21
- cmdbox/licenses/LICENSE.rich.13.9.4(MIT License).txt +0 -19
- cmdbox/licenses/LICENSE.six.1.16.0(MIT License).txt +0 -18
- cmdbox/licenses/LICENSE.sphinx-intl.2.3.0(BSD License).txt +0 -25
- cmdbox/licenses/LICENSE.starlette.0.41.3(BSD License).txt +0 -27
- cmdbox/licenses/LICENSE.starlette.0.46.1(BSD License).txt +0 -27
- cmdbox/licenses/LICENSE.tomli.2.1.0(MIT License).txt +0 -21
- cmdbox/licenses/LICENSE.twine.5.1.1(Apache Software License).txt +0 -174
- cmdbox/licenses/LICENSE.typing_extensions.4.12.2(Python Software Foundation License).txt +0 -279
- cmdbox/licenses/LICENSE.typing_extensions.4.13.0(UNKNOWN).txt +0 -279
- cmdbox/licenses/LICENSE.urllib3.2.2.3(MIT License).txt +0 -21
- cmdbox/licenses/LICENSE.urllib3.2.3.0(MIT License).txt +0 -21
- cmdbox/licenses/LICENSE.uvicorn.0.34.0(BSD License).txt +0 -27
- cmdbox/licenses/LICENSE.uvicorn.0.34.1(BSD License).txt +0 -27
- cmdbox/licenses/LICENSE.watchfiles.1.0.0(MIT License).txt +0 -21
- cmdbox/licenses/LICENSE.watchfiles.1.0.4(MIT License).txt +0 -21
- cmdbox/licenses/LICENSE.websockets.14.1(BSD License).txt +0 -24
- cmdbox/licenses/LICENSE.zope.interface.7.1.1(Zope Public License).txt +0 -44
- /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
- /cmdbox/licenses/{LICENSE.gevent.24.11.1(MIT License).txt → LICENSE.gevent.25.4.2(MIT).txt} +0 -0
- /cmdbox/licenses/{LICENSE.greenlet.3.1.1(MIT License).txt → LICENSE.greenlet.3.2.1(MIT AND Python-2.0).txt} +0 -0
- /cmdbox/licenses/{LICENSE.h11.0.14.0(MIT License).txt → LICENSE.h11.0.16.0(MIT License).txt} +0 -0
- /cmdbox/licenses/{LICENSE.importlib_metadata.8.5.0(Apache Software License).txt → LICENSE.importlib_metadata.8.7.0(Apache Software License).txt} +0 -0
- /cmdbox/licenses/{LICENSE.more-itertools.10.5.0(MIT License).txt → LICENSE.more-itertools.10.7.0(MIT License).txt} +0 -0
- /cmdbox/licenses/{LICENSE.numpy.2.1.3(BSD License).txt → LICENSE.numpy.2.2.5(BSD License).txt} +0 -0
- /cmdbox/licenses/{LICENSE.packaging.24.2(Apache Software License; BSD License).txt → LICENSE.packaging.25.0(Apache Software License; BSD License).txt} +0 -0
- /cmdbox/licenses/{LICENSE.uvicorn.0.32.1(BSD License).txt → LICENSE.uvicorn.0.34.2(BSD License).txt} +0 -0
- {cmdbox-0.5.3.1.dist-info → cmdbox-0.5.4.dist-info}/LICENSE +0 -0
- {cmdbox-0.5.3.1.dist-info → cmdbox-0.5.4.dist-info}/WHEEL +0 -0
- {cmdbox-0.5.3.1.dist-info → cmdbox-0.5.4.dist-info}/entry_points.txt +0 -0
- {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'
|
|
133
|
-
|
|
134
|
-
|
|
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
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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"
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
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
|
@@ -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."),
|