cmdbox 0.5.3__py3-none-any.whl → 0.5.4__py3-none-any.whl

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

Potentially problematic release.


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

Files changed (86) hide show
  1. cmdbox/app/auth/__init__.py +0 -0
  2. cmdbox/app/auth/azure_signin.py +38 -0
  3. cmdbox/app/auth/azure_signin_saml.py +12 -0
  4. cmdbox/app/auth/github_signin.py +38 -0
  5. cmdbox/app/auth/google_signin.py +32 -0
  6. cmdbox/app/auth/signin.py +47 -4
  7. cmdbox/app/auth/signin_saml.py +61 -0
  8. cmdbox/app/edge.py +198 -61
  9. cmdbox/app/feature.py +2 -1
  10. cmdbox/app/features/cli/audit_base.py +1 -1
  11. cmdbox/app/features/cli/cmdbox_audit_createdb.py +1 -1
  12. cmdbox/app/features/cli/cmdbox_audit_write.py +4 -0
  13. cmdbox/app/features/cli/cmdbox_client_file_copy.py +1 -1
  14. cmdbox/app/features/cli/cmdbox_client_file_download.py +1 -1
  15. cmdbox/app/features/cli/cmdbox_client_file_list.py +1 -1
  16. cmdbox/app/features/cli/cmdbox_client_file_mkdir.py +1 -1
  17. cmdbox/app/features/cli/cmdbox_client_file_move.py +1 -1
  18. cmdbox/app/features/cli/cmdbox_client_file_remove.py +1 -1
  19. cmdbox/app/features/cli/cmdbox_client_file_rmdir.py +1 -1
  20. cmdbox/app/features/cli/cmdbox_client_file_upload.py +1 -1
  21. cmdbox/app/features/cli/cmdbox_client_server_info.py +1 -1
  22. cmdbox/app/features/cli/cmdbox_edge_config.py +19 -5
  23. cmdbox/app/features/cli/cmdbox_gui_start.py +1 -1
  24. cmdbox/app/features/cli/cmdbox_server_start.py +1 -1
  25. cmdbox/app/features/cli/cmdbox_server_stop.py +1 -1
  26. cmdbox/app/features/cli/cmdbox_web_apikey_add.py +1 -1
  27. cmdbox/app/features/cli/cmdbox_web_apikey_del.py +1 -1
  28. cmdbox/app/features/cli/cmdbox_web_group_add.py +1 -1
  29. cmdbox/app/features/cli/cmdbox_web_group_del.py +1 -1
  30. cmdbox/app/features/cli/cmdbox_web_group_edit.py +1 -1
  31. cmdbox/app/features/cli/cmdbox_web_group_list.py +1 -1
  32. cmdbox/app/features/cli/cmdbox_web_start.py +1 -1
  33. cmdbox/app/features/cli/cmdbox_web_user_add.py +4 -4
  34. cmdbox/app/features/cli/cmdbox_web_user_del.py +1 -1
  35. cmdbox/app/features/cli/cmdbox_web_user_edit.py +4 -4
  36. cmdbox/app/features/cli/cmdbox_web_user_list.py +1 -1
  37. cmdbox/app/features/web/cmdbox_web_audit.py +7 -1
  38. cmdbox/app/features/web/cmdbox_web_do_signin.py +79 -103
  39. cmdbox/app/features/web/cmdbox_web_exec_cmd.py +2 -2
  40. cmdbox/app/features/web/cmdbox_web_signin.py +23 -1
  41. cmdbox/app/options.py +9 -0
  42. cmdbox/app/server.py +15 -3
  43. cmdbox/app/web.py +13 -12
  44. cmdbox/extensions/features.yml +4 -4
  45. cmdbox/extensions/sample_project/sample/app/features/cli/sample_server_time.py +1 -1
  46. cmdbox/extensions/sample_project/sample/extensions/features.yml +23 -0
  47. cmdbox/extensions/sample_project/sample/extensions/user_list.yml +40 -6
  48. cmdbox/extensions/user_list.yml +36 -6
  49. cmdbox/licenses/LICENSE.async-timeout.5.0.1(Apache Software License).txt +13 -0
  50. cmdbox/licenses/files.txt +10 -9
  51. cmdbox/version.py +2 -2
  52. cmdbox/web/assets/cmdbox/audit.js +98 -34
  53. cmdbox/web/assets/cmdbox/signin.js +13 -0
  54. cmdbox/web/assets/cmdbox/users.js +1 -1
  55. cmdbox/web/audit.html +69 -44
  56. cmdbox/web/signin.html +10 -6
  57. {cmdbox-0.5.3.dist-info → cmdbox-0.5.4.dist-info}/METADATA +69 -15
  58. {cmdbox-0.5.3.dist-info → cmdbox-0.5.4.dist-info}/RECORD +71 -79
  59. cmdbox/app/features/web/cmdbox_web_load_pin.py +0 -43
  60. cmdbox/app/features/web/cmdbox_web_save_pin.py +0 -42
  61. cmdbox/licenses/LICENSE.argcomplete.3.6.1(Apache Software License).txt +0 -177
  62. cmdbox/licenses/LICENSE.gevent.25.4.1(MIT).txt +0 -25
  63. cmdbox/licenses/LICENSE.greenlet.3.2.0(MIT AND Python-2.0).txt +0 -30
  64. cmdbox/licenses/LICENSE.pillow.11.1.0(CMU License (MIT-CMU)).txt +0 -1213
  65. cmdbox/licenses/LICENSE.prompt_toolkit.3.0.50(BSD License).txt +0 -27
  66. cmdbox/licenses/LICENSE.psycopg-pool.3.2.6(GNU Lesser General Public License v3 (LGPLv3)).txt +0 -165
  67. cmdbox/licenses/LICENSE.pydantic.2.11.1(MIT License).txt +0 -21
  68. cmdbox/licenses/LICENSE.pydantic_core.2.33.0(MIT License).txt +0 -21
  69. cmdbox/licenses/LICENSE.starlette.0.46.1(BSD License).txt +0 -27
  70. cmdbox/licenses/LICENSE.typing_extensions.4.13.0(UNKNOWN).txt +0 -279
  71. cmdbox/licenses/LICENSE.urllib3.2.3.0(MIT License).txt +0 -21
  72. cmdbox/licenses/LICENSE.uvicorn.0.34.1(BSD License).txt +0 -27
  73. cmdbox/licenses/LICENSE.watchfiles.1.0.4(MIT License).txt +0 -21
  74. /cmdbox/licenses/{LICENSE.certifi.2025.1.31(Mozilla Public License 2.0 (MPL 2.0)).txt → LICENSE.certifi.2025.4.26(Mozilla Public License 2.0 (MPL 2.0)).txt} +0 -0
  75. /cmdbox/licenses/{LICENSE.gevent.24.11.1(MIT License).txt → LICENSE.gevent.25.4.2(MIT).txt} +0 -0
  76. /cmdbox/licenses/{LICENSE.greenlet.3.1.1(MIT License).txt → LICENSE.greenlet.3.2.1(MIT AND Python-2.0).txt} +0 -0
  77. /cmdbox/licenses/{LICENSE.h11.0.14.0(MIT License).txt → LICENSE.h11.0.16.0(MIT License).txt} +0 -0
  78. /cmdbox/licenses/{LICENSE.importlib_metadata.8.6.1(Apache Software License).txt → LICENSE.importlib_metadata.8.7.0(Apache Software License).txt} +0 -0
  79. /cmdbox/licenses/{LICENSE.more-itertools.10.6.0(MIT License).txt → LICENSE.more-itertools.10.7.0(MIT License).txt} +0 -0
  80. /cmdbox/licenses/{LICENSE.numpy.2.2.4(BSD License).txt → LICENSE.numpy.2.2.5(BSD License).txt} +0 -0
  81. /cmdbox/licenses/{LICENSE.packaging.24.2(Apache Software License; BSD License).txt → LICENSE.packaging.25.0(Apache Software License; BSD License).txt} +0 -0
  82. /cmdbox/licenses/{LICENSE.uvicorn.0.34.0(BSD License).txt → LICENSE.uvicorn.0.34.2(BSD License).txt} +0 -0
  83. {cmdbox-0.5.3.dist-info → cmdbox-0.5.4.dist-info}/LICENSE +0 -0
  84. {cmdbox-0.5.3.dist-info → cmdbox-0.5.4.dist-info}/WHEEL +0 -0
  85. {cmdbox-0.5.3.dist-info → cmdbox-0.5.4.dist-info}/entry_points.txt +0 -0
  86. {cmdbox-0.5.3.dist-info → cmdbox-0.5.4.dist-info}/top_level.txt +0 -0
@@ -47,7 +47,7 @@ class GuiStart(feature.UnsupportEdgeFeature):
47
47
  dict(opt="password", type=Options.T_STR, default=self.default_pass, required=True, multi=False, hide=True, choice=None, web="mask",
48
48
  discription_ja="Redisサーバーのアクセスパスワード(任意)を指定します。省略時は `password` を使用します。",
49
49
  discription_en="Specify the access password of the Redis server (optional). If omitted, `password` is used."),
50
- dict(opt="svname", type=Options.T_STR, default="server", required=True, multi=False, hide=True, choice=None, web="readonly",
50
+ dict(opt="svname", type=Options.T_STR, default=self.default_svname, required=True, multi=False, hide=True, choice=None, web="readonly",
51
51
  discription_ja="サーバーのサービス名を指定します。省略時は `server` を使用します。",
52
52
  discription_en="Specify the service name of the inference server. If omitted, `server` is used."),
53
53
  dict(opt="data", type=Options.T_FILE, default=common.HOME_DIR / f".{self.ver.__appid__}", required=False, multi=False, hide=False, choice=None,
@@ -46,7 +46,7 @@ class ServerStart(feature.OneshotNotifyEdgeFeature):
46
46
  dict(opt="password", type=Options.T_STR, default=self.default_pass, required=True, multi=False, hide=True, choice=None, web="mask",
47
47
  discription_ja="Redisサーバーのアクセスパスワード(任意)を指定します。省略時は `password` を使用します。",
48
48
  discription_en="Specify the access password of the Redis server (optional). If omitted, `password` is used."),
49
- dict(opt="svname", type=Options.T_STR, default="server", required=True, multi=False, hide=True, choice=None, web="readonly",
49
+ dict(opt="svname", type=Options.T_STR, default=self.default_svname, required=True, multi=False, hide=True, choice=None, web="readonly",
50
50
  discription_ja="サーバーのサービス名を指定します。省略時は `server` を使用します。",
51
51
  discription_en="Specify the service name of the inference server. If omitted, `server` is used."),
52
52
  dict(opt="data", type=Options.T_FILE, default=common.HOME_DIR / f".{self.ver.__appid__}", required=False, multi=False, hide=False, choice=None,
@@ -47,7 +47,7 @@ class ServerStop(feature.OneshotNotifyEdgeFeature):
47
47
  dict(opt="password", type=Options.T_STR, default=self.default_pass, required=True, multi=False, hide=True, choice=None, web="mask",
48
48
  discription_ja="Redisサーバーのアクセスパスワード(任意)を指定します。省略時は `password` を使用します。",
49
49
  discription_en="Specify the access password of the Redis server (optional). If omitted, `password` is used."),
50
- dict(opt="svname", type=Options.T_STR, default="server", required=True, multi=False, hide=True, choice=None, web="readonly",
50
+ dict(opt="svname", type=Options.T_STR, default=self.default_svname, required=True, multi=False, hide=True, choice=None, web="readonly",
51
51
  discription_ja="サーバーのサービス名を指定します。省略時は `server` を使用します。",
52
52
  discription_en="Specify the service name of the inference server. If omitted, `server` is used."),
53
53
  dict(opt="retry_count", type=Options.T_INT, default=3, required=False, multi=False, hide=True, choice=None,
@@ -46,7 +46,7 @@ class WebApikeyAdd(feature.UnsupportEdgeFeature):
46
46
  dict(opt="password", type=Options.T_STR, default=self.default_pass, required=True, multi=False, hide=True, choice=None, web="mask",
47
47
  discription_ja="Redisサーバーのアクセスパスワード(任意)を指定します。省略時は `password` を使用します。",
48
48
  discription_en="Specify the access password of the Redis server (optional). If omitted, `password` is used."),
49
- dict(opt="svname", type=Options.T_STR, default="server", required=True, multi=False, hide=True, choice=None, web="readonly",
49
+ dict(opt="svname", type=Options.T_STR, default=self.default_svname, required=True, multi=False, hide=True, choice=None, web="readonly",
50
50
  discription_ja="サーバーのサービス名を指定します。省略時は `server` を使用します。",
51
51
  discription_en="Specify the service name of the inference server. If omitted, `server` is used."),
52
52
  dict(opt="data", type=Options.T_FILE, default=common.HOME_DIR / f".{self.ver.__appid__}", required=False, multi=False, hide=False, choice=None,
@@ -46,7 +46,7 @@ class WebApikeyDel(feature.UnsupportEdgeFeature):
46
46
  dict(opt="password", type=Options.T_STR, default=self.default_pass, required=True, multi=False, hide=True, choice=None, web="mask",
47
47
  discription_ja="Redisサーバーのアクセスパスワード(任意)を指定します。省略時は `password` を使用します。",
48
48
  discription_en="Specify the access password of the Redis server (optional). If omitted, `password` is used."),
49
- dict(opt="svname", type=Options.T_STR, default="server", required=True, multi=False, hide=True, choice=None, web="readonly",
49
+ dict(opt="svname", type=Options.T_STR, default=self.default_svname, required=True, multi=False, hide=True, choice=None, web="readonly",
50
50
  discription_ja="サーバーのサービス名を指定します。省略時は `server` を使用します。",
51
51
  discription_en="Specify the service name of the inference server. If omitted, `server` is used."),
52
52
  dict(opt="data", type=Options.T_FILE, default=common.HOME_DIR / f".{self.ver.__appid__}", required=False, multi=False, hide=False, choice=None,
@@ -46,7 +46,7 @@ class WebGroupAdd(feature.UnsupportEdgeFeature):
46
46
  dict(opt="password", type=Options.T_STR, default=self.default_pass, required=True, multi=False, hide=True, choice=None, web="mask",
47
47
  discription_ja="Redisサーバーのアクセスパスワード(任意)を指定します。省略時は `password` を使用します。",
48
48
  discription_en="Specify the access password of the Redis server (optional). If omitted, `password` is used."),
49
- dict(opt="svname", type=Options.T_STR, default="server", required=True, multi=False, hide=True, choice=None, web="readonly",
49
+ dict(opt="svname", type=Options.T_STR, default=self.default_svname, required=True, multi=False, hide=True, choice=None, web="readonly",
50
50
  discription_ja="サーバーのサービス名を指定します。省略時は `server` を使用します。",
51
51
  discription_en="Specify the service name of the inference server. If omitted, `server` is used."),
52
52
  dict(opt="data", type=Options.T_FILE, default=common.HOME_DIR / f".{self.ver.__appid__}", required=False, multi=False, hide=False, choice=None,
@@ -46,7 +46,7 @@ class WebGroupDel(feature.UnsupportEdgeFeature):
46
46
  dict(opt="password", type=Options.T_STR, default=self.default_pass, required=True, multi=False, hide=True, choice=None, web="mask",
47
47
  discription_ja="Redisサーバーのアクセスパスワード(任意)を指定します。省略時は `password` を使用します。",
48
48
  discription_en="Specify the access password of the Redis server (optional). If omitted, `password` is used."),
49
- dict(opt="svname", type=Options.T_STR, default="server", required=True, multi=False, hide=True, choice=None, web="readonly",
49
+ dict(opt="svname", type=Options.T_STR, default=self.default_svname, required=True, multi=False, hide=True, choice=None, web="readonly",
50
50
  discription_ja="サーバーのサービス名を指定します。省略時は `server` を使用します。",
51
51
  discription_en="Specify the service name of the inference server. If omitted, `server` is used."),
52
52
  dict(opt="data", type=Options.T_FILE, default=common.HOME_DIR / f".{self.ver.__appid__}", required=False, multi=False, hide=False, choice=None,
@@ -46,7 +46,7 @@ class WebGroupEdit(feature.UnsupportEdgeFeature):
46
46
  dict(opt="password", type=Options.T_STR, default=self.default_pass, required=True, multi=False, hide=True, choice=None, web="mask",
47
47
  discription_ja="Redisサーバーのアクセスパスワード(任意)を指定します。省略時は `password` を使用します。",
48
48
  discription_en="Specify the access password of the Redis server (optional). If omitted, `password` is used."),
49
- dict(opt="svname", type=Options.T_STR, default="server", required=True, multi=False, hide=True, choice=None, web="readonly",
49
+ dict(opt="svname", type=Options.T_STR, default=self.default_svname, required=True, multi=False, hide=True, choice=None, web="readonly",
50
50
  discription_ja="サーバーのサービス名を指定します。省略時は `server` を使用します。",
51
51
  discription_en="Specify the service name of the inference server. If omitted, `server` is used."),
52
52
  dict(opt="data", type=Options.T_FILE, default=common.HOME_DIR / f".{self.ver.__appid__}", required=False, multi=False, hide=False, choice=None,
@@ -46,7 +46,7 @@ class WebGroupList(feature.OneshotResultEdgeFeature):
46
46
  dict(opt="password", type=Options.T_STR, default=self.default_pass, required=True, multi=False, hide=True, choice=None, web="mask",
47
47
  discription_ja="Redisサーバーのアクセスパスワード(任意)を指定します。省略時は `password` を使用します。",
48
48
  discription_en="Specify the access password of the Redis server (optional). If omitted, `password` is used."),
49
- dict(opt="svname", type=Options.T_STR, default="server", required=True, multi=False, hide=True, choice=None, web="readonly",
49
+ dict(opt="svname", type=Options.T_STR, default=self.default_svname, required=True, multi=False, hide=True, choice=None, web="readonly",
50
50
  discription_ja="サーバーのサービス名を指定します。省略時は `server` を使用します。",
51
51
  discription_en="Specify the service name of the inference server. If omitted, `server` is used."),
52
52
  dict(opt="data", type=Options.T_FILE, default=common.HOME_DIR / f".{self.ver.__appid__}", required=False, multi=False, hide=False, choice=None,
@@ -47,7 +47,7 @@ class WebStart(feature.UnsupportEdgeFeature):
47
47
  dict(opt="password", type=Options.T_STR, default=self.default_pass, required=True, multi=False, hide=True, choice=None, web="mask",
48
48
  discription_ja="Redisサーバーのアクセスパスワード(任意)を指定します。省略時は `password` を使用します。",
49
49
  discription_en="Specify the access password of the Redis server (optional). If omitted, `password` is used."),
50
- dict(opt="svname", type=Options.T_STR, default="server", required=True, multi=False, hide=True, choice=None, web="readonly",
50
+ dict(opt="svname", type=Options.T_STR, default=self.default_svname, required=True, multi=False, hide=True, choice=None, web="readonly",
51
51
  discription_ja="サーバーのサービス名を指定します。省略時は `server` を使用します。",
52
52
  discription_en="Specify the service name of the inference server. If omitted, `server` is used."),
53
53
  dict(opt="data", type=Options.T_FILE, default=common.HOME_DIR / f".{self.ver.__appid__}", required=False, multi=False, hide=False, choice=None,
@@ -46,7 +46,7 @@ class WebUserAdd(feature.UnsupportEdgeFeature):
46
46
  dict(opt="password", type=Options.T_STR, default=self.default_pass, required=True, multi=False, hide=True, choice=None, web="mask",
47
47
  discription_ja="Redisサーバーのアクセスパスワード(任意)を指定します。省略時は `password` を使用します。",
48
48
  discription_en="Specify the access password of the Redis server (optional). If omitted, `password` is used."),
49
- dict(opt="svname", type=Options.T_STR, default="server", required=True, multi=False, hide=True, choice=None, web="readonly",
49
+ dict(opt="svname", type=Options.T_STR, default=self.default_svname, required=True, multi=False, hide=True, choice=None, web="readonly",
50
50
  discription_ja="サーバーのサービス名を指定します。省略時は `server` を使用します。",
51
51
  discription_en="Specify the service name of the inference server. If omitted, `server` is used."),
52
52
  dict(opt="data", type=Options.T_FILE, default=common.HOME_DIR / f".{self.ver.__appid__}", required=False, multi=False, hide=False, choice=None,
@@ -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."),
@@ -46,7 +46,7 @@ class WebUserDel(feature.UnsupportEdgeFeature):
46
46
  dict(opt="password", type=Options.T_STR, default=self.default_pass, required=True, multi=False, hide=True, choice=None, web="mask",
47
47
  discription_ja="Redisサーバーのアクセスパスワード(任意)を指定します。省略時は `password` を使用します。",
48
48
  discription_en="Specify the access password of the Redis server (optional). If omitted, `password` is used."),
49
- dict(opt="svname", type=Options.T_STR, default="server", required=True, multi=False, hide=True, choice=None, web="readonly",
49
+ dict(opt="svname", type=Options.T_STR, default=self.default_svname, required=True, multi=False, hide=True, choice=None, web="readonly",
50
50
  discription_ja="サーバーのサービス名を指定します。省略時は `server` を使用します。",
51
51
  discription_en="Specify the service name of the inference server. If omitted, `server` is used."),
52
52
  dict(opt="data", type=Options.T_FILE, default=common.HOME_DIR / f".{self.ver.__appid__}", required=False, multi=False, hide=False, choice=None,
@@ -46,7 +46,7 @@ class WebUserEdit(feature.UnsupportEdgeFeature):
46
46
  dict(opt="password", type=Options.T_STR, default=self.default_pass, required=True, multi=False, hide=True, choice=None, web="mask",
47
47
  discription_ja="Redisサーバーのアクセスパスワード(任意)を指定します。省略時は `password` を使用します。",
48
48
  discription_en="Specify the access password of the Redis server (optional). If omitted, `password` is used."),
49
- dict(opt="svname", type=Options.T_STR, default="server", required=True, multi=False, hide=True, choice=None, web="readonly",
49
+ dict(opt="svname", type=Options.T_STR, default=self.default_svname, required=True, multi=False, hide=True, choice=None, web="readonly",
50
50
  discription_ja="サーバーのサービス名を指定します。省略時は `server` を使用します。",
51
51
  discription_en="Specify the service name of the inference server. If omitted, `server` is used."),
52
52
  dict(opt="data", type=Options.T_FILE, default=common.HOME_DIR / f".{self.ver.__appid__}", required=False, multi=False, hide=False, choice=None,
@@ -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."),
@@ -46,7 +46,7 @@ class WebUserList(feature.OneshotResultEdgeFeature):
46
46
  dict(opt="password", type=Options.T_STR, default=self.default_pass, required=True, multi=False, hide=True, choice=None, web="mask",
47
47
  discription_ja="Redisサーバーのアクセスパスワード(任意)を指定します。省略時は `password` を使用します。",
48
48
  discription_en="Specify the access password of the Redis server (optional). If omitted, `password` is used."),
49
- dict(opt="svname", type=Options.T_STR, default="server", required=True, multi=False, hide=True, choice=None, web="readonly",
49
+ dict(opt="svname", type=Options.T_STR, default=self.default_svname, required=True, multi=False, hide=True, choice=None, web="readonly",
50
50
  discription_ja="サーバーのサービス名を指定します。省略時は `server` を使用します。",
51
51
  discription_en="Specify the service name of the inference server. If omitted, `server` is used."),
52
52
  dict(opt="data", type=Options.T_FILE, default=common.HOME_DIR / f".{self.ver.__appid__}", required=False, multi=False, hide=False, choice=None,
@@ -41,9 +41,13 @@ class Audit(feature.WebFeature):
41
41
  if web.signin.get_data() is None:
42
42
  return dict(error='signin_file_data is None.')
43
43
  if not hasattr(web.options, 'audit_search') or web.options.audit_search is None:
44
- raise dict(error='audit search feature is not found.')
44
+ return dict(warn='audit feature is disabled.')
45
45
  opt = await req.json()
46
46
  opt = {**opt, **web.options.audit_search_args.copy()}
47
+ opt['host'] = web.redis_host
48
+ opt['port'] = web.redis_port
49
+ opt['password'] = web.redis_password
50
+ opt['svname'] = web.svname
47
51
  args = argparse.Namespace(**{k:common.chopdq(v) for k,v in opt.items()})
48
52
  status, ret_main, _ = web.options.audit_search.apprun(web.logger, args, time.perf_counter(), [])
49
53
  if status != 0:
@@ -55,6 +59,8 @@ class Audit(feature.WebFeature):
55
59
  signin = web.signin.check_signin(req, res)
56
60
  if signin is not None:
57
61
  return signin
62
+ if not hasattr(web.options, 'audit_search_args'):
63
+ return dict(warn='audit feature is disabled.')
58
64
  return dict(success=web.options.audit_search_args)
59
65
 
60
66
  def toolmenu(self, web:Web) -> Dict[str, Any]:
@@ -1,18 +1,19 @@
1
+ import urllib.parse
1
2
  from cmdbox.app import common
2
- from cmdbox.app.auth.signin import Signin
3
+ from cmdbox.app.auth import signin, signin_saml, azure_signin, azure_signin_saml, github_signin, google_signin
3
4
  from cmdbox.app.commons import convert
4
5
  from cmdbox.app.features.web import cmdbox_web_signin
5
6
  from cmdbox.app.web import Web
6
7
  from fastapi import FastAPI, Request, Response, HTTPException
7
8
  from fastapi.responses import HTMLResponse, RedirectResponse
9
+ from typing import Any, Dict
8
10
  import copy
9
11
  import datetime
10
12
  import importlib
11
13
  import inspect
12
14
  import json
13
15
  import logging
14
- import requests
15
- import urllib.parse
16
+ import urllib
16
17
 
17
18
 
18
19
  class DoSignin(cmdbox_web_signin.Signin):
@@ -60,7 +61,7 @@ class DoSignin(cmdbox_web_signin.Signin):
60
61
  if name == '' or passwd == '':
61
62
  web.options.audit_exec(req, res, web, body=dict(msg='signin failed.'), audit_type='auth')
62
63
  return RedirectResponse(url=f'/signin/{next}?error=1')
63
- user = [u for u in signin_data['users'] if u['name'] == name and u['hash'] != 'oauth2']
64
+ user = [u for u in signin_data['users'] if u['name'] == name and u['hash'] != 'oauth2' and u['hash'] != 'saml']
64
65
  if len(user) <= 0:
65
66
  web.options.audit_exec(req, res, web, body=dict(msg='signin failed.'), audit_type='auth')
66
67
  return RedirectResponse(url=f'/signin/{next}?error=1')
@@ -108,7 +109,7 @@ class DoSignin(cmdbox_web_signin.Signin):
108
109
  last_update = web.user_data(None, uid, name, 'password', 'last_update')
109
110
  notify_passchange = True if last_update is None else False
110
111
  # パスワード認証の場合はパスワード有効期限チェック
111
- if user['hash']!='oauth2' and 'password' in signin_data and not notify_passchange:
112
+ if user['hash']!='oauth2' and user['hash']!='saml' and 'password' in signin_data and not notify_passchange:
112
113
  last_update = datetime.datetime.strptime(last_update, '%Y-%m-%dT%H:%M:%S')
113
114
  # パスワード有効期限
114
115
  expiration = signin_data['password']['expiration']
@@ -152,7 +153,7 @@ class DoSignin(cmdbox_web_signin.Signin):
152
153
  members = inspect.getmembers(mod, inspect.isclass)
153
154
  signin_data = web.signin.get_data()
154
155
  for name, cls in members:
155
- if cls is Signin or issubclass(cls, Signin):
156
+ if cls is signin.Signin or issubclass(cls, signin.Signin):
156
157
  sobj = cls(web.logger, web.signin_file, signin_data, appcls, ver)
157
158
  return sobj
158
159
  return None
@@ -161,9 +162,10 @@ class DoSignin(cmdbox_web_signin.Signin):
161
162
  raise e
162
163
 
163
164
  signin_data = web.signin.get_data()
164
- self.google_signin = Signin(web.logger, web.signin_file, signin_data, self.appcls, self.ver)
165
- self.github_signin = Signin(web.logger, web.signin_file, signin_data, self.appcls, self.ver)
166
- self.azure_signin = Signin(web.logger, web.signin_file, signin_data, self.appcls, self.ver)
165
+ self.google_signin = google_signin.GoogleSignin(web.logger, web.signin_file, signin_data, self.appcls, self.ver)
166
+ self.github_signin = github_signin.GithubSignin(web.logger, web.signin_file, signin_data, self.appcls, self.ver)
167
+ self.azure_signin = azure_signin.AzureSignin(web.logger, web.signin_file, signin_data, self.appcls, self.ver)
168
+ self.azure_saml_signin = azure_signin_saml.AzyreSigninSAML(web.logger, web.signin_file, signin_data, self.appcls, self.ver)
167
169
  if signin_data is not None:
168
170
  # signinオブジェクトの指定があった場合読込む
169
171
  if 'signin_module' in signin_data['oauth2']['providers']['google']:
@@ -175,6 +177,9 @@ class DoSignin(cmdbox_web_signin.Signin):
175
177
  if 'signin_module' in signin_data['oauth2']['providers']['azure']:
176
178
  sobj = _load_signin(web, signin_data['oauth2']['providers']['azure']['signin_module'], self.appcls, self.ver)
177
179
  self.azure_signin = sobj if sobj is not None else self.azure_signin
180
+ if 'signin_module' in signin_data['saml']['providers']['azure']:
181
+ sobj = _load_signin(web, signin_data['saml']['providers']['azure']['signin_module'], self.appcls, self.ver)
182
+ self.azure_saml_signin = sobj if sobj is not None else self.azure_saml_signin
178
183
 
179
184
  def _set_session(req:Request, user:dict, email:str, hashed_password:str, access_token:str, group_names:list, gids:list):
180
185
  """
@@ -209,20 +214,10 @@ class DoSignin(cmdbox_web_signin.Signin):
209
214
  @app.get('/oauth2/google/callback')
210
215
  async def oauth2_google_callback(req:Request, res:Response):
211
216
  conf = web.signin.get_data()['oauth2']['providers']['google']
212
- headers = {'Content-Type': 'application/x-www-form-urlencoded'}
213
217
  next = req.query_params['state']
214
- data = {'code': req.query_params['code'],
215
- 'client_id': conf['client_id'],
216
- 'client_secret': conf['client_secret'],
217
- 'redirect_uri': conf['redirect_uri'],
218
- 'grant_type': 'authorization_code'}
219
- query = '&'.join([f'{k}={urllib.parse.quote(v)}' for k, v in data.items()])
220
218
  try:
221
219
  # アクセストークン取得
222
- token_resp = requests.post(url='https://oauth2.googleapis.com/token', headers=headers, data=query)
223
- token_resp.raise_for_status()
224
- token_json = token_resp.json()
225
- access_token = token_json['access_token']
220
+ access_token = self.google_signin.request_access_token(conf, req, res)
226
221
  return await oauth2_google_session(access_token, next, req, res)
227
222
  except Exception as e:
228
223
  web.logger.warning(f'Failed to get token. {e}', exc_info=True)
@@ -230,45 +225,15 @@ class DoSignin(cmdbox_web_signin.Signin):
230
225
 
231
226
  @app.get('/oauth2/google/session/{access_token}/{next}')
232
227
  async def oauth2_google_session(access_token:str, next:str, req:Request, res:Response):
233
- try:
234
- # ユーザー情報取得(email)
235
- user_info_resp = requests.get(
236
- url='https://www.googleapis.com/oauth2/v1/userinfo',
237
- headers={'Authorization': f'Bearer {access_token}'}
238
- )
239
- user_info_resp.raise_for_status()
240
- user_info_json = user_info_resp.json()
241
- email = user_info_json['email']
242
- # サインイン判定
243
- jadge, user = self.google_signin.jadge(access_token, email)
244
- if not jadge:
245
- return RedirectResponse(url=f'/signin/{next}?error=appdeny')
246
- # グループ取得
247
- group_names, gids = self.google_signin.get_groups(access_token, user)
248
- # セッションに保存
249
- _set_session(req, user, email, None, access_token, group_names, gids)
250
- return RedirectResponse(url=f'../../{next}', headers=dict(signin="success")) # nginxのリバプロ対応のための相対パス
251
- except Exception as e:
252
- web.logger.warning(f'Failed to get token. {e}', exc_info=True)
253
- raise HTTPException(status_code=500, detail=f'Failed to get token. {e}')
228
+ return await oauth2_login_session(self.google_signin, access_token, next, req, res)
254
229
 
255
230
  @app.get('/oauth2/github/callback')
256
231
  async def oauth2_github_callback(req:Request, res:Response):
257
232
  conf = web.signin.get_data()['oauth2']['providers']['github']
258
- headers = {'Content-Type': 'application/x-www-form-urlencoded',
259
- 'Accept': 'application/json'}
260
233
  next = req.query_params['state']
261
- data = {'code': req.query_params['code'],
262
- 'client_id': conf['client_id'],
263
- 'client_secret': conf['client_secret'],
264
- 'redirect_uri': conf['redirect_uri']}
265
- query = '&'.join([f'{k}={urllib.parse.quote(v)}' for k, v in data.items()])
266
234
  try:
267
235
  # アクセストークン取得
268
- token_resp = requests.post(url='https://github.com/login/oauth/access_token', headers=headers, data=query)
269
- token_resp.raise_for_status()
270
- token_json = token_resp.json()
271
- access_token = token_json['access_token']
236
+ access_token = self.github_signin.request_access_token(conf, req, res)
272
237
  return await oauth2_github_session(access_token, next, req, res)
273
238
  except Exception as e:
274
239
  web.logger.warning(f'Failed to get token. {e}', exc_info=True)
@@ -276,53 +241,15 @@ class DoSignin(cmdbox_web_signin.Signin):
276
241
 
277
242
  @app.get('/oauth2/github/session/{access_token}/{next}')
278
243
  async def oauth2_github_session(access_token:str, next:str, req:Request, res:Response):
279
- try:
280
- # ユーザー情報取得(email)
281
- user_info_resp = requests.get(
282
- url='https://api.github.com/user/emails',
283
- headers={'Authorization': f'Bearer {access_token}'}
284
- )
285
- user_info_resp.raise_for_status()
286
- user_info_json = user_info_resp.json()
287
- if type(user_info_json) == list:
288
- email = 'notfound'
289
- for u in user_info_json:
290
- if u['primary']:
291
- email = u['email']
292
- break
293
- # サインイン判定
294
- jadge, user = self.github_signin.jadge(access_token, email)
295
- if not jadge:
296
- return RedirectResponse(url=f'/signin/{next}?error=appdeny')
297
- # グループ取得
298
- group_names, gids = self.github_signin.get_groups(access_token, user)
299
- # セッションに保存
300
- _set_session(req, user, email, None, access_token, group_names, gids)
301
- return RedirectResponse(url=f'../../{next}', headers=dict(signin="success")) # nginxのリバプロ対応のための相対パス
302
- except Exception as e:
303
- web.logger.warning(f'Failed to get token. {e}', exc_info=True)
304
- raise HTTPException(status_code=500, detail=f'Failed to get token. {e}')
244
+ return await oauth2_login_session(self.github_signin, access_token, next, req, res)
305
245
 
306
246
  @app.get('/oauth2/azure/callback')
307
247
  async def oauth2_azure_callback(req:Request, res:Response):
308
248
  conf = web.signin.get_data()['oauth2']['providers']['azure']
309
- headers = {'Content-Type': 'application/x-www-form-urlencoded',
310
- 'Accept': 'application/json'}
311
249
  next = req.query_params['state']
312
- data = {'tenant': conf['tenant_id'],
313
- 'code': req.query_params['code'],
314
- 'scope': " ".join(conf['scope']),
315
- 'client_id': conf['client_id'],
316
- #'client_secret': conf['client_secret'],
317
- 'redirect_uri': conf['redirect_uri'],
318
- 'grant_type': 'authorization_code'}
319
- query = '&'.join([f'{k}={urllib.parse.quote(v)}' for k, v in data.items()])
320
250
  try:
321
251
  # アクセストークン取得
322
- token_resp = requests.post(url=f'https://login.microsoftonline.com/{conf["tenant_id"]}/oauth2/v2.0/token', headers=headers, data=query)
323
- token_resp.raise_for_status()
324
- token_json = token_resp.json()
325
- access_token = token_json['access_token']
252
+ access_token = self.azure_signin.request_access_token(conf, req, res)
326
253
  return await oauth2_azure_session(access_token, next, req, res)
327
254
  except Exception as e:
328
255
  web.logger.warning(f'Failed to get token. {e}', exc_info=True)
@@ -330,26 +257,75 @@ class DoSignin(cmdbox_web_signin.Signin):
330
257
 
331
258
  @app.get('/oauth2/azure/session/{access_token}/{next}')
332
259
  async def oauth2_azure_session(access_token:str, next:str, req:Request, res:Response):
260
+ return await oauth2_login_session(self.azure_signin, access_token, next, req, res)
261
+
262
+ async def oauth2_login_session(signin:signin.Signin, access_token:str, next:str, req:Request, res:Response):
333
263
  try:
334
264
  # ユーザー情報取得(email)
335
- user_info_resp = requests.get(
336
- url='https://graph.microsoft.com/v1.0/me',
337
- #url='https://graph.microsoft.com/v1.0/me/transitiveMemberOf?$Top=999',
338
- headers={'Authorization': f'Bearer {access_token}'}
339
- )
340
- user_info_resp.raise_for_status()
341
- user_info_json = user_info_resp.json()
342
- if isinstance(user_info_json, dict):
343
- email = user_info_json.get('mail', 'notfound')
265
+ email = signin.get_email(access_token)
344
266
  # サインイン判定
345
- jadge, user = self.azure_signin.jadge(access_token, email)
267
+ jadge, user = signin.jadge(email)
346
268
  if not jadge:
347
269
  return RedirectResponse(url=f'/signin/{next}?error=appdeny')
348
270
  # グループ取得
349
- group_names, gids = self.azure_signin.get_groups(access_token, user)
271
+ group_names, gids = signin.get_groups(access_token, user)
350
272
  # セッションに保存
351
273
  _set_session(req, user, email, None, access_token, group_names, gids)
352
274
  return RedirectResponse(url=f'../../{next}', headers=dict(signin="success")) # nginxのリバプロ対応のための相対パス
353
275
  except Exception as e:
354
276
  web.logger.warning(f'Failed to get token. {e}', exc_info=True)
355
277
  raise HTTPException(status_code=500, detail=f'Failed to get token. {e}')
278
+
279
+ @app.post('/saml/azure/callback')
280
+ async def saml_azure_callback(req:Request, res:Response):
281
+ form = await req.form()
282
+ return await saml_login_callback('azure', self.azure_saml_signin, form, None, req, res)
283
+
284
+ @app.get('/saml/azure/session/{saml_token}/{next}')
285
+ async def saml_azure_session(saml_token:str, next:str, req:Request, res:Response):
286
+ form = json.loads(convert.b64str2str(saml_token))
287
+ return await saml_login_callback('azure', self.azure_saml_signin, form, next, req, res)
288
+
289
+ async def saml_login_callback(prov, saml_signin:signin_saml.SigninSAML, form:Dict[str, Any], next:str, req:Request, res:Response):
290
+ """
291
+ SAML認証のコールバック処理を行います
292
+ Args:
293
+ prov (str): SAMLプロバイダ名
294
+ saml_signin (signin_saml.SigninSAML): SAMLサインインオブジェクト
295
+ form (Dict[str, Any]): フォームデータ
296
+ req (Request): Requestオブジェクト
297
+ res (Response): Responseオブジェクト
298
+ """
299
+ relay = form.get('RelayState')
300
+ query = urllib.parse.urlparse(relay).query if relay is not None else None
301
+ if next is None:
302
+ next = urllib.parse.parse_qs(query).get('next', None) if query is not None else None
303
+ next = next[0] if next is not None and len(next) > 0 else None
304
+ auth = await saml_signin.make_saml(prov, next, form, req, res)
305
+ auth.process_response() # Process IdP response
306
+ errors = auth.get_errors() # This method receives an array with the errors
307
+ if len(errors) == 0:
308
+ if not auth.is_authenticated(): # This check if the response was ok and the user data retrieved or not (user authenticated)
309
+ return RedirectResponse(url=f'/signin/{next}?error=saml_not_auth')
310
+ else:
311
+ # ユーザー情報取得
312
+ email = saml_signin.get_email(auth)
313
+ # サインイン判定
314
+ jadge, user = saml_signin.jadge(email)
315
+ if not jadge:
316
+ return RedirectResponse(url=f'/signin/{next}?error=appdeny')
317
+ # グループ取得
318
+ group_names, gids = saml_signin.get_groups(None, user)
319
+ # セッションに保存
320
+ _set_session(req, user, email, None, None, group_names, gids)
321
+ # SAML場合、ブラウザ制限によりリダイレクトでセッションクッキーが消えるので、HTMLで移動する
322
+ html = """
323
+ <html><head><meta http-equiv="refresh" content="0;url=../../{next}"></head>
324
+ <body style="background-color:#212529;color:#fff;">loading..</body>
325
+ <script type="text/javascript">window.location.href="../../{next}";</script></html>
326
+ """.format(next=next)
327
+ return HTMLResponse(content=html, headers=dict(signin="success"))
328
+ else:
329
+ msg = f"Error when processing SAML Response: {', '.join(errors)} {auth.get_last_error_reason()}"
330
+ web.logger.warning(msg)
331
+ raise HTTPException(status_code=500, detail=msg)
@@ -136,9 +136,9 @@ class ExecCmd(cmdbox_web_load_cmd.LoadCmd):
136
136
  found = True
137
137
  if not found or o not in loaded: continue
138
138
  opt[o] = loaded[o]
139
- if isinstance(feat, cmdbox_audit_write.AuditWrite) and o in _options.audit_write_args:
139
+ if isinstance(feat, cmdbox_audit_write.AuditWrite) and hasattr(_options, 'audit_write_args') and o in _options.audit_write_args:
140
140
  opt[o] = _options.audit_write_args[o]
141
- elif isinstance(feat, cmdbox_audit_search.AuditSearch) and o in _options.audit_search_args:
141
+ elif isinstance(feat, cmdbox_audit_search.AuditSearch) and hasattr(_options, 'audit_search_args') and o in _options.audit_search_args:
142
142
  opt[o] = _options.audit_search_args[o]
143
143
  except:
144
144
  pass
@@ -1,9 +1,9 @@
1
- import urllib.parse
2
1
  from cmdbox.app import feature
3
2
  from cmdbox.app.web import Web
4
3
  from fastapi import FastAPI, Request, Response, HTTPException
5
4
  from fastapi.responses import HTMLResponse, RedirectResponse
6
5
  import urllib
6
+ import urllib.parse
7
7
 
8
8
 
9
9
  class Signin(feature.WebFeature):
@@ -83,3 +83,25 @@ class Signin(feature.WebFeature):
83
83
  return dict(google=signin_data['oauth2']['providers']['google']['enabled'],
84
84
  github=signin_data['oauth2']['providers']['github']['enabled'],
85
85
  azure=signin_data['oauth2']['providers']['azure']['enabled'],)
86
+
87
+ @app.get('/saml/{prov}/{next}')
88
+ async def saml_login(prov:str, next:str, req:Request, res:Response):
89
+ """
90
+ SAML認証のログイン処理を行います
91
+
92
+ Args:
93
+ prov (str): SAMLプロバイダ名
94
+ next (str): リダイレクト先のURL
95
+ req (Request): Requestオブジェクト
96
+ res (Response): Responseオブジェクト
97
+ """
98
+ form = await req.form()
99
+ auth = await web.signin_saml.make_saml(prov, next, form, req, res)
100
+ return RedirectResponse(url=auth.login())
101
+
102
+ @app.get('/saml/enabled')
103
+ async def saml_enabled(req:Request, res:Response):
104
+ if web.signin_html_data is None:
105
+ return dict(azure=False)
106
+ signin_data = web.signin_saml.get_data()
107
+ return dict(azure=signin_data['saml']['providers']['azure']['enabled'],)
cmdbox/app/options.py CHANGED
@@ -713,6 +713,11 @@ class Options:
713
713
  user (str): メッセージを発生させたユーザー名
714
714
  kwargs (Any): 呼び出し元で使用しているキーワード引数
715
715
  """
716
+ if not hasattr(self, 'audit_write_args') or self.audit_write_args is None:
717
+ return
718
+ yml = self.features_yml_data
719
+ if yml is None or 'audit' not in yml or 'enabled' not in yml['audit'] or not yml['audit']['enabled']:
720
+ return
716
721
  if not hasattr(self, 'audit_write') or self.audit_write is None:
717
722
  raise Exception('audit write feature is not found.')
718
723
  clmsg_date = datetime.now().strftime('%Y-%m-%d %H:%M:%S') + common.get_tzoffset_str()
@@ -761,6 +766,10 @@ class Options:
761
766
  else:
762
767
  clmsg_body[key] = common.to_str(val, 100)
763
768
  opt[key] = val
769
+ if hasattr(arg, 'redis_host'): opt['host'] = arg.redis_host
770
+ if hasattr(arg, 'redis_port'): opt['port'] = arg.redis_port
771
+ if hasattr(arg, 'redis_password'): opt['password'] = arg.redis_password
772
+ if hasattr(arg, 'svname'): opt['svname'] = arg.svname
764
773
  if hasattr(arg, 'clmsg_id'): opt['clmsg_id'] = arg.clmsg_id
765
774
  elif isinstance(arg, web.Web):
766
775
  opt['host'] = arg.redis_host
cmdbox/app/server.py CHANGED
@@ -133,6 +133,7 @@ class Server(filer.Filer):
133
133
 
134
134
  while self.is_running:
135
135
  try:
136
+ msg = None
136
137
  # ブロッキングリストから要素を取り出す
137
138
  ctime = time.time()
138
139
  self.redis_cli.hset(self.redis_cli.hbname, 'ctime', ctime)
@@ -186,18 +187,29 @@ class Server(filer.Filer):
186
187
  except exceptions.TimeoutError:
187
188
  pass
188
189
  except exceptions.ConnectionError as e:
189
- self.logger.warning(f"Connection to the server was lost. {e}", exec_info=True)
190
+ self.logger.warning(f"Connection to the server was lost. {e}", exc_info=True)
190
191
  if not self.redis_cli.check_server(find_svname=False, retry_count=self.retry_count, retry_interval=self.retry_interval, outstatus=True):
191
192
  self.is_running = False
192
193
  break
193
194
  except OSError as e:
194
- self.logger.warning(f"OSError. {e}", exec_info=True)
195
+ self.logger.warning(f"OSError. {e}. This message is not executable in the server environment. ({msg})", exc_info=True)
196
+ if msg is not None and len(msg) > 1:
197
+ self.redis_cli.rpush(msg[1], dict(warn=f"OSError. {e}. This message is not executable in the server environment. ({msg[0]})"))
198
+ error_cnt += 1
199
+ self.redis_cli.hset(self.redis_cli.hbname, 'error_cnt', error_cnt)
200
+ pass
201
+ except IndexError as e:
202
+ self.logger.warning(f"IndexError. {e}. The message received by the server is invalid. ({msg})", exc_info=True)
203
+ if msg is not None and len(msg) > 1:
204
+ self.redis_cli.rpush(msg[1], dict(warn=f"IndexError. {e}. The message received by the server is invalid. ({msg[0]})"))
205
+ error_cnt += 1
206
+ self.redis_cli.hset(self.redis_cli.hbname, 'error_cnt', error_cnt)
195
207
  pass
196
208
  except KeyboardInterrupt as e:
197
209
  self.is_running = False
198
210
  break
199
211
  except Exception as e:
200
- self.logger.warning(f"Unknown error occurred. {e}", exc_info=True)
212
+ self.logger.warning(f"Unknown error occurred. {e}. Service will be stopped due to unknown cause.({msg})", exc_info=True)
201
213
  self.is_running = False
202
214
  break
203
215
  self.redis_cli.delete(self.redis_cli.svname)