cmdbox 0.5.1.2__py3-none-any.whl → 0.5.2__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 (117) hide show
  1. cmdbox/app/app.py +4 -2
  2. cmdbox/app/auth/signin.py +633 -631
  3. cmdbox/app/client.py +10 -10
  4. cmdbox/app/common.py +50 -6
  5. cmdbox/app/commons/convert.py +9 -0
  6. cmdbox/app/commons/module.py +113 -113
  7. cmdbox/app/commons/redis_client.py +40 -29
  8. cmdbox/app/edge.py +4 -4
  9. cmdbox/app/features/cli/audit_base.py +135 -0
  10. cmdbox/app/features/cli/cmdbox_audit_createdb.py +224 -0
  11. cmdbox/app/features/cli/cmdbox_audit_delete.py +299 -0
  12. cmdbox/app/features/cli/cmdbox_audit_search.py +350 -0
  13. cmdbox/app/features/cli/cmdbox_audit_write.py +240 -0
  14. cmdbox/app/features/cli/cmdbox_client_file_copy.py +207 -207
  15. cmdbox/app/features/cli/cmdbox_client_file_download.py +207 -207
  16. cmdbox/app/features/cli/cmdbox_client_file_list.py +193 -193
  17. cmdbox/app/features/cli/cmdbox_client_file_mkdir.py +191 -191
  18. cmdbox/app/features/cli/cmdbox_client_file_move.py +199 -199
  19. cmdbox/app/features/cli/cmdbox_client_file_remove.py +190 -190
  20. cmdbox/app/features/cli/cmdbox_client_file_rmdir.py +190 -190
  21. cmdbox/app/features/cli/cmdbox_client_file_upload.py +212 -212
  22. cmdbox/app/features/cli/cmdbox_client_server_info.py +166 -166
  23. cmdbox/app/features/cli/cmdbox_server_list.py +88 -88
  24. cmdbox/app/features/cli/cmdbox_server_stop.py +138 -138
  25. cmdbox/app/features/web/cmdbox_web_del_cmd.py +2 -0
  26. cmdbox/app/features/web/cmdbox_web_del_pipe.py +1 -0
  27. cmdbox/app/features/web/cmdbox_web_do_signin.py +12 -2
  28. cmdbox/app/features/web/cmdbox_web_do_signout.py +1 -0
  29. cmdbox/app/features/web/cmdbox_web_exec_cmd.py +25 -1
  30. cmdbox/app/features/web/cmdbox_web_exec_pipe.py +1 -0
  31. cmdbox/app/features/web/cmdbox_web_filer download.py +43 -42
  32. cmdbox/app/features/web/cmdbox_web_filer.py +1 -0
  33. cmdbox/app/features/web/cmdbox_web_filer_upload.py +65 -64
  34. cmdbox/app/features/web/cmdbox_web_gui.py +166 -165
  35. cmdbox/app/features/web/cmdbox_web_load_pin.py +43 -43
  36. cmdbox/app/features/web/cmdbox_web_raw_pipe.py +87 -87
  37. cmdbox/app/features/web/cmdbox_web_save_cmd.py +1 -0
  38. cmdbox/app/features/web/cmdbox_web_save_pin.py +42 -42
  39. cmdbox/app/features/web/cmdbox_web_save_pipe.py +1 -0
  40. cmdbox/app/features/web/cmdbox_web_users.py +12 -0
  41. cmdbox/app/options.py +767 -601
  42. cmdbox/extensions/features.yml +20 -0
  43. cmdbox/extensions/sample_project/sample/app/features/cli/sample_client_time.py +82 -82
  44. cmdbox/extensions/sample_project/sample/app/features/cli/sample_server_time.py +145 -145
  45. cmdbox/licenses/{LICENSE.Sphinx.8.1.3(BSD License).txt → LICENSE.Sphinx.8.2.3(UNKNOWN).txt} +1 -1
  46. cmdbox/licenses/{LICENSE.babel.2.16.0(BSD License).txt → LICENSE.babel.2.17.0(BSD License).txt } +1 -1
  47. cmdbox/licenses/{LICENSE.pkginfo.1.10.0(MIT License).txt → LICENSE.charset-normalizer.3.4.1(MIT License).txt } +1 -1
  48. cmdbox/licenses/LICENSE.gunicorn.23.0.0(MIT License).txt +23 -0
  49. cmdbox/licenses/LICENSE.importlib_metadata.8.6.1(Apache Software License).txt +202 -0
  50. cmdbox/licenses/LICENSE.nh3.0.2.21(MIT).txt +21 -0
  51. cmdbox/licenses/{LICENSE.pillow.11.0.0(CMU License (MIT-CMU)).txt → LICENSE.pillow.11.1.0(CMU License (MIT-CMU)).txt } +27 -40
  52. cmdbox/licenses/LICENSE.plyer.2.1.0(MIT License).txt +19 -0
  53. cmdbox/licenses/LICENSE.prompt_toolkit.3.0.50(BSD License).txt +27 -0
  54. cmdbox/licenses/LICENSE.psycopg-binary.3.2.6(GNU Lesser General Public License v3 (LGPLv3)).txt +165 -0
  55. cmdbox/licenses/LICENSE.psycopg-pool.3.2.6(GNU Lesser General Public License v3 (LGPLv3)).txt +165 -0
  56. cmdbox/licenses/LICENSE.psycopg.3.2.6(GNU Lesser General Public License v3 (LGPLv3)).txt +165 -0
  57. cmdbox/licenses/LICENSE.pycryptodome.3.22.0(BSD License; Public Domain).txt +61 -0
  58. cmdbox/licenses/LICENSE.pystray.0.19.5(GNU Lesser General Public License v3 (LGPLv3)).txt +674 -0
  59. cmdbox/licenses/LICENSE.questionary.2.1.0(MIT License).txt +19 -0
  60. cmdbox/licenses/LICENSE.roman-numerals-py.3.1.0(CC0 1.0 Universal (CC0 1.0) Public Domain Dedication; Zero-Clause BSD (0BSD)).txt +146 -0
  61. cmdbox/licenses/{LICENSE.six.1.16.0(MIT License).txt → LICENSE.six.1.17.0(MIT License).txt } +1 -1
  62. cmdbox/licenses/{LICENSE.charset-normalizer.3.4.0(MIT License).txt → LICENSE.typing-inspection.0.4.0(MIT License).txt } +2 -2
  63. cmdbox/licenses/LICENSE.tzdata.2025.2(Apache Software License).txt +15 -0
  64. cmdbox/licenses/files.txt +48 -36
  65. cmdbox/logconf_audit.yml +30 -0
  66. cmdbox/logconf_cmdbox.yml +30 -0
  67. cmdbox/version.py +2 -2
  68. cmdbox/web/assets/cmdbox/color_mode.css +516 -0
  69. cmdbox/web/assets/cmdbox/common.js +19 -0
  70. cmdbox/web/assets/cmdbox/list_cmd.js +9 -10
  71. cmdbox/web/assets/cmdbox/main.js +2 -2
  72. cmdbox/web/assets/cmdbox/result.js +2 -2
  73. cmdbox/web/assets/cmdbox/signin.js +2 -2
  74. cmdbox/web/assets/cmdbox/users.js +2 -3
  75. cmdbox/web/assets/cmdbox/view_result.js +1 -1
  76. cmdbox/web/assets/filer/main.js +2 -2
  77. cmdbox/web/filer.html +16 -2
  78. cmdbox/web/gui.html +15 -1
  79. cmdbox/web/result.html +15 -1
  80. cmdbox/web/signin.html +35 -14
  81. cmdbox/web/users.html +15 -1
  82. {cmdbox-0.5.1.2.dist-info → cmdbox-0.5.2.dist-info}/METADATA +25 -5
  83. {cmdbox-0.5.1.2.dist-info → cmdbox-0.5.2.dist-info}/RECORD +116 -96
  84. {cmdbox-0.5.1.2.dist-info → cmdbox-0.5.2.dist-info}/entry_points.txt +0 -1
  85. cmdbox/licenses/LICENSE.nh3.0.2.18(MIT).txt +0 -1
  86. /cmdbox/licenses/{LICENSE.Jinja2.3.1.4(BSD License).txt → LICENSE.Jinja2.3.1.6(BSD License).txt} +0 -0
  87. /cmdbox/licenses/{LICENSE.Pygments.2.18.0(BSD License).txt → LICENSE.Pygments.2.19.1(BSD License).txt} +0 -0
  88. /cmdbox/licenses/{LICENSE.anyio.4.6.2.post1(MIT License).txt → LICENSE.anyio.4.9.0(MIT License).txt} +0 -0
  89. /cmdbox/licenses/{LICENSE.argcomplete.3.5.1(Apache Software License).txt → LICENSE.argcomplete.3.6.1(Apache Software License).txt} +0 -0
  90. /cmdbox/licenses/{LICENSE.certifi.2024.8.30(Mozilla Public License 2.0 (MPL 2.0)).txt → LICENSE.certifi.2025.1.31(Mozilla Public License 2.0 (MPL 2.0)).txt} +0 -0
  91. /cmdbox/licenses/{LICENSE.click.8.1.7(BSD License).txt → LICENSE.click.8.1.8(BSD License).txt} +0 -0
  92. /cmdbox/licenses/{LICENSE.cryptography.43.0.3(Apache Software License; BSD License).txt → LICENSE.cryptography.44.0.2(Apache Software License; BSD License).txt} +0 -0
  93. /cmdbox/licenses/{LICENSE.fastapi.0.115.5(MIT License).txt → LICENSE.fastapi.0.115.12(MIT License).txt} +0 -0
  94. /cmdbox/licenses/{LICENSE.importlib_metadata.8.5.0(Apache Software License).txt → LICENSE.id.1.5.0(Apache Software License).txt} +0 -0
  95. /cmdbox/licenses/{LICENSE.keyring.25.5.0(MIT License).txt → LICENSE.keyring.25.6.0(MIT License).txt} +0 -0
  96. /cmdbox/licenses/{LICENSE.more-itertools.10.5.0(MIT License).txt → LICENSE.more-itertools.10.6.0(MIT License).txt} +0 -0
  97. /cmdbox/licenses/{LICENSE.numpy.2.1.3(BSD License).txt → LICENSE.numpy.2.2.4(BSD License).txt} +0 -0
  98. /cmdbox/licenses/{LICENSE.prettytable.3.12.0(BSD License).txt → LICENSE.prettytable.3.16.0(UNKNOWN).txt} +0 -0
  99. /cmdbox/licenses/{LICENSE.pydantic.2.10.2(MIT License).txt → LICENSE.pydantic.2.11.1(MIT License).txt} +0 -0
  100. /cmdbox/licenses/{LICENSE.pydantic_core.2.27.1(MIT License).txt → LICENSE.pydantic_core.2.33.0(MIT License).txt} +0 -0
  101. /cmdbox/licenses/{LICENSE.python-dotenv.1.0.1(BSD License).txt → LICENSE.python-dotenv.1.1.0(BSD License).txt} +0 -0
  102. /cmdbox/licenses/{LICENSE.python-multipart.0.0.17(Apache Software License).txt → LICENSE.python-multipart.0.0.20(Apache Software License).txt} +0 -0
  103. /cmdbox/licenses/{LICENSE.redis.5.2.0(MIT License).txt → LICENSE.redis.5.2.1(MIT License).txt} +0 -0
  104. /cmdbox/licenses/{LICENSE.rich.13.9.4(MIT License).txt → LICENSE.rich.14.0.0(MIT License).txt} +0 -0
  105. /cmdbox/licenses/{LICENSE.sphinx-intl.2.3.0(BSD License).txt → LICENSE.sphinx-intl.2.3.1(BSD License).txt} +0 -0
  106. /cmdbox/licenses/{LICENSE.starlette.0.41.3(BSD License).txt → LICENSE.starlette.0.46.1(BSD License).txt} +0 -0
  107. /cmdbox/licenses/{LICENSE.tomli.2.1.0(MIT License).txt → LICENSE.tomli.2.2.1(MIT License).txt} +0 -0
  108. /cmdbox/licenses/{LICENSE.twine.5.1.1(Apache Software License).txt → LICENSE.twine.6.1.0(Apache Software License).txt} +0 -0
  109. /cmdbox/licenses/{LICENSE.typing_extensions.4.12.2(Python Software Foundation License).txt → LICENSE.typing_extensions.4.13.0(UNKNOWN).txt} +0 -0
  110. /cmdbox/licenses/{LICENSE.urllib3.2.2.3(MIT License).txt → LICENSE.urllib3.2.3.0(MIT License).txt} +0 -0
  111. /cmdbox/licenses/{LICENSE.uvicorn.0.32.1(BSD License).txt → LICENSE.uvicorn.0.34.0(BSD License).txt} +0 -0
  112. /cmdbox/licenses/{LICENSE.watchfiles.1.0.0(MIT License).txt → LICENSE.watchfiles.1.0.4(MIT License).txt} +0 -0
  113. /cmdbox/licenses/{LICENSE.websockets.14.1(BSD License).txt → LICENSE.websockets.15.0.1(BSD License).txt} +0 -0
  114. /cmdbox/licenses/{LICENSE.zope.interface.7.1.1(Zope Public License).txt → LICENSE.zope.interface.7.2(Zope Public License).txt} +0 -0
  115. {cmdbox-0.5.1.2.dist-info → cmdbox-0.5.2.dist-info}/LICENSE +0 -0
  116. {cmdbox-0.5.1.2.dist-info → cmdbox-0.5.2.dist-info}/WHEEL +0 -0
  117. {cmdbox-0.5.1.2.dist-info → cmdbox-0.5.2.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,350 @@
1
+ from cmdbox.app import common, client
2
+ from cmdbox.app.commons import convert, redis_client
3
+ from cmdbox.app.features.cli import audit_base
4
+ from cmdbox.app.options import Options
5
+ from pathlib import Path
6
+ from psycopg.rows import dict_row
7
+ from typing import Dict, Any, Tuple, List, Union
8
+ import argparse
9
+ import logging
10
+ import json
11
+ import sys
12
+
13
+
14
+ class AuditSearch(audit_base.AuditBase):
15
+ def get_mode(self) -> Union[str, List[str]]:
16
+ """
17
+ この機能のモードを返します
18
+
19
+ Returns:
20
+ Union[str, List[str]]: モード
21
+ """
22
+ return 'audit'
23
+
24
+ def get_cmd(self):
25
+ """
26
+ この機能のコマンドを返します
27
+
28
+ Returns:
29
+ str: コマンド
30
+ """
31
+ return 'search'
32
+
33
+ def get_option(self):
34
+ """
35
+ この機能のオプションを返します
36
+
37
+ Returns:
38
+ Dict[str, Any]: オプション
39
+ """
40
+ opt = super().get_option()
41
+ opt['discription_ja'] = "監査ログを検索します。"
42
+ opt['discription_en'] = "Search the audit log."
43
+ opt['choice'] += [
44
+ dict(opt="select", type=Options.T_STR, default=None, required=False, multi=True, hide=False,
45
+ choice=['', 'audit_type', 'clmsg_id', 'clmsg_date', 'clmsg_tag', 'clmsg_src', 'clmsg_body', 'svmsg_id', 'svmsg_date'],
46
+ discription_ja="取得項目を指定します。指定しない場合は全ての項目を取得します。",
47
+ discription_en="Specify the items to be retrieved. If not specified, all items are acquired."),
48
+ dict(opt="filter_audit_type", type=Options.T_STR, default=None, required=False, multi=False, hide=False, choice=Options.AUDITS,
49
+ discription_ja="フィルタ条件の監査の種類を指定します。",
50
+ discription_en="Specifies the type of audit for the filter condition."),
51
+ dict(opt="filter_clmsg_id", type=Options.T_STR, default=None, required=False, multi=False, hide=False, choice=None,
52
+ discription_ja="フィルタ条件のクライアントのメッセージIDを指定します。",
53
+ discription_en="Specify the message ID of the client for the filter condition."),
54
+ dict(opt="filter_clmsg_sdate", type=Options.T_DATETIME, default=None, required=False, multi=False, hide=False, choice=None,
55
+ discription_ja="フィルタ条件のクライアントのメッセージ発生日時(開始)を指定します。",
56
+ discription_en="Specify the date and time (start) when the message occurred for the client in the filter condition."),
57
+ dict(opt="filter_clmsg_edate", type=Options.T_DATETIME, default=None, required=False, multi=False, hide=False, choice=None,
58
+ discription_ja="フィルタ条件のクライアントのメッセージ発生日時(終了)を指定します。",
59
+ discription_en="Specify the date and time (end) when the message occurred for the client in the filter condition."),
60
+ dict(opt="filter_clmsg_src", type=Options.T_STR, default=None, required=False, multi=False, hide=False, choice=None,
61
+ discription_ja="フィルタ条件のクライアントのメッセージの発生源を指定します。LIKE検索を行います。",
62
+ discription_en="Specifies the source of the message for the client in the filter condition; performs a LIKE search."),
63
+ dict(opt="filter_clmsg_user", type=Options.T_STR, default=None, required=False, multi=False, hide=False, choice=None,
64
+ discription_ja="フィルタ条件のクライアントのメッセージの発生させたユーザーを指定します。LIKE検索を行います。",
65
+ discription_en="Specifies the user who generated the message for the client in the filter condition; performs a LIKE search."),
66
+ dict(opt="filter_clmsg_body", type=Options.T_DICT, default=None, required=False, multi=True, hide=False, choice=None,
67
+ discription_ja="フィルタ条件のクライアントのメッセージの本文を辞書形式で指定します。LIKE検索を行います。",
68
+ discription_en="Specifies the body of the client's message in the filter condition in dictionary format; performs a LIKE search."),
69
+ dict(opt="filter_clmsg_tag", type=Options.T_STR, default=None, required=False, multi=True, hide=False, choice=None,
70
+ discription_ja="フィルタ条件のクライアントのメッセージのタグを指定します。",
71
+ discription_en="Specifies the tag of the client's message in the filter condition."),
72
+ dict(opt="filter_svmsg_id", type=Options.T_STR, default=None, required=False, multi=False, hide=False, choice=None,
73
+ discription_ja="フィルタ条件のサーバーのメッセージIDを指定します。",
74
+ discription_en="Specify the message ID of the server for the filter condition."),
75
+ dict(opt="filter_svmsg_sdate", type=Options.T_DATETIME, default=None, required=False, multi=False, hide=False, choice=None,
76
+ discription_ja="フィルタ条件のサーバーのメッセージ発生日時(開始)を指定します。",
77
+ discription_en="Specify the date and time (start) when the message occurred for the server in the filter condition."),
78
+ dict(opt="filter_svmsg_edate", type=Options.T_DATETIME, default=None, required=False, multi=False, hide=False, choice=None,
79
+ discription_ja="フィルタ条件のサーバーのメッセージ発生日時(終了)を指定します。",
80
+ discription_en="Specify the date and time (end) when the message occurred for the server in the filter condition."),
81
+ dict(opt="sort", type=Options.T_DICT, default=None, required=False, multi=True, hide=False, choice=['', 'ASC', 'DICT'],
82
+ discription_ja="ソート項目を指定します。",
83
+ discription_en="Specify the sort item."),
84
+ dict(opt="offset", type=Options.T_INT, default=0, required=False, multi=False, hide=False, choice=None,
85
+ discription_ja="取得する行の開始位置を指定します。",
86
+ discription_en="Specifies the starting position of the row to be retrieved."),
87
+ dict(opt="limit", type=Options.T_INT, default=100, required=False, multi=False, hide=False, choice=None,
88
+ discription_ja="取得する行数を指定します。",
89
+ discription_en="Specifies the number of rows to retrieve."),
90
+ ]
91
+ return opt
92
+
93
+ def get_svcmd(self):
94
+ """
95
+ この機能のサーバー側のコマンドを返します
96
+
97
+ Returns:
98
+ str: サーバー側のコマンド
99
+ """
100
+ return 'audit_search'
101
+
102
+ def apprun(self, logger:logging.Logger, args:argparse.Namespace, tm:float, pf:List[Dict[str, float]]=[]) -> Tuple[int, Dict[str, Any], Any]:
103
+ """
104
+ この機能の実行を行います
105
+
106
+ Args:
107
+ logger (logging.Logger): ロガー
108
+ args (argparse.Namespace): 引数
109
+ tm (float): 実行開始時間
110
+ pf (List[Dict[str, float]]): 呼出元のパフォーマンス情報
111
+
112
+ Returns:
113
+ Tuple[int, Dict[str, Any], Any]: 終了コード, 結果, オブジェクト
114
+ """
115
+ if args.svname is None:
116
+ msg = dict(warn=f"Please specify the --svname option.")
117
+ common.print_format(msg, args.format, tm, None, False, pf=pf)
118
+ return 1, msg, None
119
+
120
+ select_str = json.dumps(args.select, default=common.default_json_enc, ensure_ascii=False) if args.select else '[]'
121
+ select_b64 = convert.str2b64str(select_str)
122
+ sort_str = json.dumps(args.sort, default=common.default_json_enc, ensure_ascii=False) if args.sort else '{}'
123
+ sort_b64 = convert.str2b64str(sort_str)
124
+ offset = args.offset
125
+ limit = args.limit
126
+ filter_audit_type_b64 = convert.str2b64str(args.filter_audit_type)
127
+ filter_clmsg_id_b64 = convert.str2b64str(args.filter_clmsg_id)
128
+ filter_clmsg_sdate = args.filter_clmsg_sdate+common.get_tzoffset_str() if args.filter_clmsg_sdate else None
129
+ filter_clmsg_sdate_b64 = convert.str2b64str(filter_clmsg_sdate)
130
+ filter_clmsg_edate = args.filter_clmsg_edate+common.get_tzoffset_str() if args.filter_clmsg_edate else None
131
+ filter_clmsg_edate_b64 = convert.str2b64str(filter_clmsg_edate)
132
+ filter_clmsg_src_b64 = convert.str2b64str(args.filter_clmsg_src)
133
+ filter_clmsg_user_b64 = convert.str2b64str(args.filter_clmsg_user)
134
+ filter_clmsg_body_str = json.dumps(args.filter_clmsg_body, default=common.default_json_enc, ensure_ascii=False) if args.filter_clmsg_body else '{}'
135
+ filter_clmsg_body_b64 = convert.str2b64str(filter_clmsg_body_str)
136
+ filter_clmsg_tag_str = json.dumps(args.filter_clmsg_tag, default=common.default_json_enc, ensure_ascii=False) if args.filter_clmsg_tag else '[]'
137
+ filter_clmsg_tag_b64 = convert.str2b64str(filter_clmsg_tag_str)
138
+ filter_svmsg_id_b64 = convert.str2b64str(args.filter_svmsg_id)
139
+ filter_svmsg_sdate_b64 = convert.str2b64str(args.filter_svmsg_sdate)
140
+ filter_svmsg_edate_b64 = convert.str2b64str(args.filter_svmsg_edate)
141
+ pg_enabled = args.pg_enabled
142
+ pg_host_b64 = convert.str2b64str(args.pg_host)
143
+ pg_port = args.pg_port if isinstance(args.pg_port, int) else None
144
+ pg_user_b64 = convert.str2b64str(args.pg_user)
145
+ pg_password_b64 = convert.str2b64str(args.pg_password)
146
+ pg_dbname_b64 = convert.str2b64str(args.pg_dbname)
147
+
148
+ cl = client.Client(logger, redis_host=args.host, redis_port=args.port, redis_password=args.password, svname=args.svname)
149
+ ret = cl.redis_cli.send_cmd(self.get_svcmd(),
150
+ [select_b64, sort_b64, str(offset), str(limit),
151
+ filter_audit_type_b64, filter_clmsg_id_b64, filter_clmsg_sdate_b64, filter_clmsg_edate_b64,
152
+ filter_clmsg_src_b64, filter_clmsg_user_b64, filter_clmsg_body_b64, filter_clmsg_tag_b64,
153
+ filter_svmsg_id_b64, filter_svmsg_sdate_b64, filter_svmsg_edate_b64,
154
+ pg_enabled, pg_host_b64, pg_port, pg_user_b64, pg_password_b64, pg_dbname_b64],
155
+ retry_count=args.retry_count, retry_interval=args.retry_interval, timeout=args.timeout)
156
+ common.print_format(ret, args.format, tm, None, False, pf=pf)
157
+
158
+ if 'success' not in ret:
159
+ return 1, ret, cl
160
+
161
+ if 'data' in ret['success']:
162
+ for row in ret['success']['data']:
163
+ try:
164
+ row['clmsg_tag'] = json.loads(row['clmsg_tag'])
165
+ except:
166
+ pass
167
+ try:
168
+ row['clmsg_body'] = json.loads(row['clmsg_body'])
169
+ except:
170
+ pass
171
+
172
+ return 0, ret, cl
173
+
174
+ def is_cluster_redirect(self):
175
+ """
176
+ クラスター宛のメッセージの場合、メッセージを転送するかどうかを返します
177
+
178
+ Returns:
179
+ bool: メッセージを転送する場合はTrue
180
+ """
181
+ return False
182
+
183
+ def svrun(self, data_dir:Path, logger:logging.Logger, redis_cli:redis_client.RedisClient, msg:List[str],
184
+ sessions:Dict[str, Dict[str, Any]]) -> int:
185
+ """
186
+ この機能のサーバー側の実行を行います
187
+
188
+ Args:
189
+ data_dir (Path): データディレクトリ
190
+ logger (logging.Logger): ロガー
191
+ redis_cli (redis_client.RedisClient): Redisクライアント
192
+ msg (List[str]): 受信メッセージ
193
+ sessions (Dict[str, Dict[str, Any]]): セッション情報
194
+
195
+ Returns:
196
+ int: 終了コード
197
+ """
198
+ select = json.loads(convert.b64str2str(msg[2]))
199
+ sort = json.loads(convert.b64str2str(msg[3]))
200
+ offset = int(msg[4]) if msg[4] else 0
201
+ limit = int(msg[5]) if msg[5] else 100
202
+
203
+ filter_audit_type = convert.b64str2str(msg[6])
204
+ filter_clmsg_id = convert.b64str2str(msg[7])
205
+ filter_clmsg_sdate = convert.b64str2str(msg[8])
206
+ filter_clmsg_edate = convert.b64str2str(msg[9])
207
+ filter_clmsg_src = convert.b64str2str(msg[10])
208
+ filter_clmsg_user = convert.b64str2str(msg[11])
209
+ body = json.loads(convert.b64str2str(msg[12]))
210
+ tags = json.loads(convert.b64str2str(msg[13]))
211
+ filter_svmsg_id = convert.b64str2str(msg[14])
212
+ filter_svmsg_sdate = convert.b64str2str(msg[15])
213
+ filter_svmsg_edate = convert.b64str2str(msg[16])
214
+ pg_enabled = True if msg[17]=='True' else False
215
+ pg_host = convert.b64str2str(msg[18])
216
+ pg_port = int(msg[19]) if msg[19]!='None' else None
217
+ pg_user = convert.b64str2str(msg[20])
218
+ pg_password = convert.b64str2str(msg[21])
219
+ pg_dbname = convert.b64str2str(msg[22])
220
+ st = self.search(msg[1], select, sort, offset, limit,
221
+ filter_audit_type, filter_clmsg_id, filter_clmsg_sdate, filter_clmsg_edate,
222
+ filter_clmsg_src, filter_clmsg_user, body, tags,
223
+ filter_svmsg_id, filter_svmsg_sdate, filter_svmsg_edate,
224
+ pg_enabled, pg_host, pg_port, pg_user, pg_password, pg_dbname,
225
+ data_dir, logger, redis_cli)
226
+ return st
227
+
228
+ def search(self, reskey:str, select:List[str], sort:Dict[str, str], offset:int, limit:int,
229
+ filter_audit_type:str, filter_clmsg_id:str, filter_clmsg_sdate:str, filter_clmsg_edate:str,
230
+ filter_clmsg_src:str, filter_clmsg_user:str, filter_clmsg_body:Dict[str, Any], filter_clmsg_tags:List[str],
231
+ filter_svmsg_id:str, filter_svmsg_sdate:str, filter_svmsg_edate:str,
232
+ pg_enabled:bool, pg_host:str, pg_port:int, pg_user:str, pg_password:str, pg_dbname:str,
233
+ data_dir:Path, logger:logging.Logger, redis_cli:redis_client.RedisClient) -> int:
234
+ """
235
+ 監査ログを検索する
236
+
237
+ Args:
238
+ reskey (str): レスポンスキー
239
+ select (List[str]): 取得項目
240
+ sort (Dict[str, str]): ソート条件
241
+ offset (int): 取得する行の開始位置
242
+ limit (int): 取得する行数
243
+ filter_audit_type (str): 監査の種類
244
+ filter_clmsg_id (str): クライアントメッセージID
245
+ filter_clmsg_sdate (str): クライアントメッセージ発生日時(開始)
246
+ filter_clmsg_edate (str): クライアントメッセージ発生日時(終了)
247
+ filter_clmsg_src (str): クライアントメッセージの発生源
248
+ filter_clmsg_user (str): クライアントメッセージの発生させたユーザー
249
+ filter_clmsg_body (Dict[str, Any]): クライアントメッセージの本文
250
+ filter_clmsg_tags (List[str]): クライアントメッセージのタグ
251
+ filter_svmsg_id (str): サーバーメッセージID
252
+ filter_svmsg_sdate (str): サーバーメッセージ発生日時(開始)
253
+ filter_svmsg_edate (str): サーバーメッセージ発生日時(終了)
254
+ pg_enabled (bool): PostgreSQLを使用する場合はTrue
255
+ pg_host (str): PostgreSQLホスト
256
+ pg_port (int): PostgreSQLポート
257
+ pg_user (str): PostgreSQLユーザー
258
+ pg_password (str): PostgreSQLパスワード
259
+ pg_dbname (str): PostgreSQLデータベース名
260
+ data_dir (Path): データディレクトリ
261
+ logger (logging.Logger): ロガー
262
+ redis_cli (redis_client.RedisClient): Redisクライアント
263
+
264
+ Returns:
265
+ int: レスポンスコード
266
+ """
267
+ try:
268
+ with self.initdb(data_dir, logger, pg_enabled, pg_host, pg_port, pg_user, pg_password, pg_dbname) as conn:
269
+ def dict_factory(cursor, row):
270
+ return {col[0]: row[idx] for idx, col in enumerate(cursor.description)}
271
+ conn.row_factory = dict_row if pg_enabled else dict_factory
272
+ cursor = conn.cursor()
273
+ try:
274
+ select = select if select else ['audit_type', 'clmsg_id', 'clmsg_date', 'clmsg_src', 'clmsg_user', 'clmsg_body', 'clmsg_tag', 'svmsg_id', 'svmsg_date']
275
+ if pg_enabled:
276
+ toz = common.get_tzoffset_str()
277
+ sel = []
278
+ for s in select:
279
+ if s in ['clmsg_date', 'svmsg_date']:
280
+ sel.append(f"{s} AT TIME ZONE INTERVAL '{toz}' as {s}")
281
+ else:
282
+ sel.append(s)
283
+ select = sel
284
+ sql = f'SELECT {",".join(select)} FROM audit'
285
+ params = []
286
+ where = []
287
+ if filter_audit_type and filter_audit_type != 'None':
288
+ where.append(f'audit_type={"%s" if pg_enabled else "?"}')
289
+ params.append(filter_audit_type)
290
+ if filter_clmsg_id and filter_clmsg_id != 'None':
291
+ where.append(f'clmsg_id={"%s" if pg_enabled else "?"}')
292
+ params.append(filter_clmsg_id)
293
+ if filter_clmsg_sdate and filter_clmsg_sdate != 'None':
294
+ where.append(f'clmsg_date>={"%s" if pg_enabled else "?"}')
295
+ params.append(filter_clmsg_sdate)
296
+ if filter_clmsg_edate and filter_clmsg_edate != 'None':
297
+ where.append(f'clmsg_date<={"%s" if pg_enabled else "?"}')
298
+ params.append(filter_clmsg_edate)
299
+ if filter_clmsg_src and filter_clmsg_src != 'None':
300
+ where.append(f'clmsg_src LIKE {"%s" if pg_enabled else "?"}')
301
+ params.append(filter_clmsg_src)
302
+ if filter_clmsg_user and filter_clmsg_user != 'None':
303
+ where.append(f'clmsg_user LIKE {"%s" if pg_enabled else "?"}')
304
+ params.append(filter_clmsg_user)
305
+ if filter_clmsg_body:
306
+ if sys.version_info[0] < 3 or sys.version_info[0] >= 3 and sys.version_info[1] < 10:
307
+ raise RuntimeError("Python 3.10 or later is required for JSON support.")
308
+ for key, value in filter_clmsg_body.items():
309
+ where.append(f"clmsg_body->>'{key}' LIKE {'%s' if pg_enabled else '?'}")
310
+ params.append(value)
311
+ if filter_clmsg_tags:
312
+ for tag in filter_clmsg_tags:
313
+ where.append(f"clmsg_tag like {'%s' if pg_enabled else '?'}")
314
+ params.append(f'%{tag}%')
315
+ if filter_svmsg_id and filter_svmsg_id != 'None':
316
+ where.append(f'svmsg_id={"%s" if pg_enabled else "?"}')
317
+ params.append(filter_svmsg_id)
318
+ if filter_svmsg_sdate and filter_svmsg_sdate != 'None':
319
+ where.append(f'svmsg_date>={"%s" if pg_enabled else "?"}')
320
+ params.append(filter_svmsg_sdate)
321
+ if filter_svmsg_edate and filter_svmsg_edate != 'None':
322
+ where.append(f'svmsg_date<={"%s" if pg_enabled else "?"}')
323
+ params.append(filter_svmsg_edate)
324
+ sql += ' WHERE ' + ' AND '.join(where) if len(where)>0 else ''
325
+ if sort and len(sort) > 0:
326
+ sql += ' ORDER BY ' + ', '.join([f"{k} {v}" for k, v in sort.items()])
327
+ else:
328
+ sql += ' ORDER BY svmsg_date DESC'
329
+ if offset > 0:
330
+ sql += f' OFFSET {"%s" if pg_enabled else "?"}'
331
+ params.append(offset)
332
+ if limit > 0:
333
+ sql += f' LIMIT {"%s" if pg_enabled else "?"}'
334
+ params.append(limit)
335
+ cursor.execute(sql, tuple(params))
336
+ rows = cursor.fetchall()
337
+ if not rows:
338
+ rescode, msg = (self.RESP_WARN, dict(warn="No data found"))
339
+ redis_cli.rpush(reskey, msg)
340
+ return rescode
341
+ else:
342
+ rescode, msg = (self.RESP_SCCESS, dict(success=rows))
343
+ redis_cli.rpush(reskey, msg)
344
+ return rescode
345
+ finally:
346
+ cursor.close()
347
+ except Exception as e:
348
+ logger.warning(f"Failed to write: {e}", exc_info=True)
349
+ redis_cli.rpush(reskey, dict(warn=f"Failed to write: {e}"))
350
+ return self.RESP_WARN
@@ -0,0 +1,240 @@
1
+ from cmdbox.app import common, client
2
+ from cmdbox.app.commons import convert, redis_client
3
+ from cmdbox.app.features.cli import audit_base
4
+ from cmdbox.app.options import Options
5
+ from datetime import datetime
6
+ from pathlib import Path
7
+ from typing import Dict, Any, Tuple, List, Union
8
+ import argparse
9
+ import logging
10
+ import json
11
+ import uuid
12
+
13
+
14
+ class AuditWrite(audit_base.AuditBase):
15
+ def get_mode(self) -> Union[str, List[str]]:
16
+ """
17
+ この機能のモードを返します
18
+
19
+ Returns:
20
+ Union[str, List[str]]: モード
21
+ """
22
+ return 'audit'
23
+
24
+ def get_cmd(self):
25
+ """
26
+ この機能のコマンドを返します
27
+
28
+ Returns:
29
+ str: コマンド
30
+ """
31
+ return 'write'
32
+
33
+ def get_option(self):
34
+ """
35
+ この機能のオプションを返します
36
+
37
+ Returns:
38
+ Dict[str, Any]: オプション
39
+ """
40
+ opt = super().get_option()
41
+ opt['discription_ja'] = "監査を記録します。"
42
+ opt['discription_en'] = "Record the audit."
43
+ opt['choice'] += [
44
+ dict(opt="audit_type", type=Options.T_STR, default=None, required=True, multi=False, hide=False, choice=Options.AUDITS,
45
+ discription_ja="監査の種類を指定します。",
46
+ discription_en="Specifies the audit type."),
47
+ dict(opt="clmsg_id", type=Options.T_STR, default=None, required=False, multi=False, hide=False, choice=None,
48
+ discription_ja="クライアントのメッセージIDを指定します。省略した場合はuuid4で生成されます。",
49
+ discription_en="Specifies the message ID of the client. If omitted, uuid4 will be generated."),
50
+ dict(opt="clmsg_date", type=Options.T_DATETIME, default=None, required=False, multi=False, hide=False, choice=None,
51
+ discription_ja="クライアントのメッセージ発生日時を指定します。省略した場合はサーバーの現在日時が使用されます。",
52
+ discription_en="Specifies the date and time the client message occurred. If omitted, the server's current date/time is used."),
53
+ dict(opt="clmsg_src", type=Options.T_STR, default=None, required=False, multi=False, hide=False, choice=None,
54
+ discription_ja="クライアントのメッセージの発生源を指定します。通常 `cmdbox.app.feature.Feature` を継承したクラス名を指定します。",
55
+ discription_en="Specifies the source of client messages. Usually specifies the name of a class that extends `cmdbox.app.feature.Feature` ."),
56
+ dict(opt="clmsg_user", type=Options.T_STR, default=None, required=False, multi=False, hide=False, choice=None,
57
+ discription_ja="クライアントのメッセージを発生させたユーザーを指定します。",
58
+ discription_en="SpecSpecifies the user who generated the client message."),
59
+ dict(opt="clmsg_body", type=Options.T_DICT, default=None, required=False, multi=True, hide=False, choice=None,
60
+ discription_ja="クライアントのメッセージの本文を辞書形式で指定します。",
61
+ discription_en="Specifies the body of the client's message in dictionary format."),
62
+ dict(opt="clmsg_tag", type=Options.T_STR, default=None, required=False, multi=True, hide=False, choice=None,
63
+ discription_ja="クライアントのメッセージのタグを指定します。後で検索しやすくするために指定します。",
64
+ discription_en="Specifies the tag for the client's message. Specify to make it easier to search later."),
65
+ dict(opt="retention_period_days", type=Options.T_INT, default=365, required=False, multi=False, hide=True, choice=None, web="mask",
66
+ discription_ja="監査を保存する日数を指定します。この日数より古い監査は削除します。0以下を指定すると無期限で保存されます。",
67
+ discription_en="Specify the number of days to keep the audit. If the number is less than or equal to 0, the audit will be kept indefinitely."),
68
+ ]
69
+ return opt
70
+
71
+ def get_svcmd(self):
72
+ """
73
+ この機能のサーバー側のコマンドを返します
74
+
75
+ Returns:
76
+ str: サーバー側のコマンド
77
+ """
78
+ return 'audit_write'
79
+
80
+ def apprun(self, logger:logging.Logger, args:argparse.Namespace, tm:float, pf:List[Dict[str, float]]=[]) -> Tuple[int, Dict[str, Any], Any]:
81
+ """
82
+ この機能の実行を行います
83
+
84
+ Args:
85
+ logger (logging.Logger): ロガー
86
+ args (argparse.Namespace): 引数
87
+ tm (float): 実行開始時間
88
+ pf (List[Dict[str, float]]): 呼出元のパフォーマンス情報
89
+
90
+ Returns:
91
+ Tuple[int, Dict[str, Any], Any]: 終了コード, 結果, オブジェクト
92
+ """
93
+ if args.svname is None:
94
+ msg = dict(warn=f"Please specify the --svname option.")
95
+ common.print_format(msg, False, tm, args.output_json, args.output_json_append, pf=pf)
96
+ return 1, msg, None
97
+ if args.audit_type is None:
98
+ msg = dict(warn=f"Please specify the --audit_type option.")
99
+ common.print_format(msg, False, tm, args.output_json, args.output_json_append, pf=pf)
100
+ return 1, msg, None
101
+ if args.clmsg_id is None:
102
+ args.clmsg_id = str(uuid.uuid4())
103
+ if args.clmsg_date is None:
104
+ args.clmsg_date = datetime.now().strftime('%Y-%m-%d %H:%M:%S') + common.get_tzoffset_str()
105
+
106
+ audit_type_b64 = convert.str2b64str(args.audit_type)
107
+ clmsg_id_b64 = convert.str2b64str(args.clmsg_id)
108
+ clmsg_date_b64 = convert.str2b64str(args.clmsg_date)
109
+ clmsg_src_b64 = convert.str2b64str(args.clmsg_src) if args.clmsg_src is not None else ''
110
+ clmsg_user_b64 = convert.str2b64str(args.clmsg_user) if args.clmsg_user is not None else ''
111
+ clmsg_body_str = json.dumps(args.clmsg_body, default=common.default_json_enc, ensure_ascii=False) if args.clmsg_body is not None else '{}'
112
+ clmsg_body_b64 = convert.str2b64str(clmsg_body_str)
113
+ clmsg_tag_str = json.dumps(args.clmsg_tag, default=common.default_json_enc, ensure_ascii=False) if args.clmsg_tag is not None else '[]'
114
+ clmsg_tag_b64 = convert.str2b64str(clmsg_tag_str)
115
+ pg_enabled = args.pg_enabled
116
+ pg_host_b64 = convert.str2b64str(args.pg_host)
117
+ pg_port = args.pg_port if isinstance(args.pg_port, int) else None
118
+ pg_user_b64 = convert.str2b64str(args.pg_user)
119
+ pg_password_b64 = convert.str2b64str(args.pg_password)
120
+ pg_dbname_b64 = convert.str2b64str(args.pg_dbname)
121
+
122
+ cl = client.Client(logger, redis_host=args.host, redis_port=args.port, redis_password=args.password, svname=args.svname)
123
+ cl.redis_cli.send_cmd(self.get_svcmd(),
124
+ [audit_type_b64, clmsg_id_b64, clmsg_date_b64, clmsg_src_b64, clmsg_user_b64, clmsg_body_b64, clmsg_tag_b64,
125
+ pg_enabled, pg_host_b64, pg_port, pg_user_b64, pg_password_b64, pg_dbname_b64,
126
+ args.retention_period_days],
127
+ retry_count=args.retry_count, retry_interval=args.retry_interval, timeout=args.timeout, nowait=True)
128
+ ret = dict(success=True)
129
+ #common.print_format(ret, False, tm, None, False, pf=pf)
130
+ return 0, ret, cl
131
+
132
+ def is_cluster_redirect(self):
133
+ """
134
+ クラスター宛のメッセージの場合、メッセージを転送するかどうかを返します
135
+
136
+ Returns:
137
+ bool: メッセージを転送する場合はTrue
138
+ """
139
+ return False
140
+
141
+ def svrun(self, data_dir:Path, logger:logging.Logger, redis_cli:redis_client.RedisClient, msg:List[str],
142
+ sessions:Dict[str, Dict[str, Any]]) -> int:
143
+ """
144
+ この機能のサーバー側の実行を行います
145
+
146
+ Args:
147
+ data_dir (Path): データディレクトリ
148
+ logger (logging.Logger): ロガー
149
+ redis_cli (redis_client.RedisClient): Redisクライアント
150
+ msg (List[str]): 受信メッセージ
151
+ sessions (Dict[str, Dict[str, Any]]): セッション情報
152
+
153
+ Returns:
154
+ int: 終了コード
155
+ """
156
+ audit_type = convert.b64str2str(msg[2])
157
+ clmsg_id = convert.b64str2str(msg[3])
158
+ clmsg_date = convert.b64str2str(msg[4])
159
+ clmsg_src = convert.b64str2str(msg[5])
160
+ clmsg_user = convert.b64str2str(msg[6])
161
+ clmsg_body = convert.b64str2str(msg[7])
162
+ clmsg_tags = convert.b64str2str(msg[8])
163
+ pg_enabled = True if msg[9]=='True' else False
164
+ pg_host = convert.b64str2str(msg[10])
165
+ pg_port = int(msg[11]) if msg[11]!='None' else None
166
+ pg_user = convert.b64str2str(msg[12])
167
+ pg_password = convert.b64str2str(msg[13])
168
+ pg_dbname = convert.b64str2str(msg[14])
169
+ retention_period_days = int(msg[15]) if msg[15] != 'None' else None
170
+ svmsg_id = str(uuid.uuid4())
171
+ st = self.write(msg[1], audit_type, clmsg_id, clmsg_date, clmsg_src, clmsg_user, clmsg_body, clmsg_tags, svmsg_id,
172
+ pg_enabled, pg_host, pg_port, pg_user, pg_password, pg_dbname,
173
+ retention_period_days,
174
+ data_dir, logger, redis_cli)
175
+ return st
176
+
177
+ def write(self, reskey:str, audit_type:str, clmsg_id:str, clmsg_date:str, clmsg_src:str, clmsg_user:str, clmsg_body:str, clmsg_tags:str, svmsg_id:str,
178
+ pg_enabled:bool, pg_host:str, pg_port:int, pg_user:str, pg_password:str, pg_dbname:str,
179
+ retention_period_days:int,
180
+ data_dir:Path, logger:logging.Logger, redis_cli:redis_client.RedisClient) -> int:
181
+ """
182
+ 監査ログを書き込む
183
+
184
+ Args:
185
+ reskey (str): レスポンスキー
186
+ audit_type (str): 監査の種類
187
+ clmsg_id (str): クライアントメッセージID
188
+ clmsg_date (str): クライアントメッセージ発生日時
189
+ clmsg_src (str): クライアントメッセージの発生源
190
+ clmsg_user (str): クライアントメッセージの発生させたユーザー
191
+ clmsg_body (str): クライアントメッセージの本文
192
+ clmsg_tags (str): クライアントメッセージのタグ
193
+ svmsg_id (str): サーバーメッセージID
194
+ pg_enabled (bool): PostgreSQLを使用する場合はTrue
195
+ pg_host (str): PostgreSQLホスト
196
+ pg_port (int): PostgreSQLポート
197
+ pg_user (str): PostgreSQLユーザー
198
+ pg_password (str): PostgreSQLパスワード
199
+ pg_dbname (str): PostgreSQLデータベース名
200
+ retention_period_days (int): 監査を保存する日数
201
+ data_dir (Path): データディレクトリ
202
+ logger (logging.Logger): ロガー
203
+ redis_cli (redis_client.RedisClient): Redisクライアント
204
+
205
+ Returns:
206
+ int: レスポンスコード
207
+ """
208
+ try:
209
+ with self.initdb(data_dir, logger, pg_enabled, pg_host, pg_port, pg_user, pg_password, pg_dbname) as conn:
210
+ cursor = conn.cursor()
211
+ try:
212
+ clmsg_tags = json.dumps(clmsg_tags)
213
+ if not pg_enabled:
214
+ cursor.execute('''
215
+ INSERT INTO audit (audit_type, clmsg_id, clmsg_date, clmsg_src, clmsg_user, clmsg_body, clmsg_tag,
216
+ svmsg_id, svmsg_date)
217
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
218
+ ''', (audit_type, clmsg_id, clmsg_date, clmsg_src, clmsg_user, clmsg_body, clmsg_tags, svmsg_id))
219
+ if retention_period_days is not None and retention_period_days > 0:
220
+ cursor.execute('DELETE FROM audit WHERE svmsg_date < datetime(CURRENT_TIMESTAMP, ?)',
221
+ (f'-{retention_period_days} days',))
222
+ else:
223
+ cursor.execute('''
224
+ INSERT INTO audit (audit_type, clmsg_id, clmsg_date, clmsg_src, clmsg_user, clmsg_body, clmsg_tag,
225
+ svmsg_id, svmsg_date)
226
+ VALUES (%s, %s, %s, %s, %s, %s, %s, %s, CURRENT_TIMESTAMP)
227
+ ''', (audit_type, clmsg_id, clmsg_date, clmsg_src, clmsg_user, clmsg_body, clmsg_tags, svmsg_id))
228
+ if retention_period_days is not None and retention_period_days > 0:
229
+ cursor.execute("DELETE FROM audit WHERE svmsg_date < CURRENT_TIMESTAMP + %s ",
230
+ (f'-{retention_period_days} day',))
231
+ conn.commit()
232
+ rescode, msg = (self.RESP_SCCESS, dict(success=True))
233
+ redis_cli.rpush(reskey, msg)
234
+ return rescode
235
+ finally:
236
+ cursor.close()
237
+ except Exception as e:
238
+ logger.warning(f"Failed to write: {e}", exc_info=True)
239
+ redis_cli.rpush(reskey, dict(warn=f"Failed to write: {e}"))
240
+ return self.RESP_WARN