clue-api 1.0.0.dev7__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.
Files changed (91) hide show
  1. clue/.gitignore +21 -0
  2. clue/__init__.py +0 -0
  3. clue/api/__init__.py +211 -0
  4. clue/api/base.py +99 -0
  5. clue/api/v1/__init__.py +82 -0
  6. clue/api/v1/actions.py +92 -0
  7. clue/api/v1/auth.py +243 -0
  8. clue/api/v1/configs.py +83 -0
  9. clue/api/v1/fetchers.py +94 -0
  10. clue/api/v1/lookup.py +221 -0
  11. clue/api/v1/registration.py +109 -0
  12. clue/api/v1/static.py +94 -0
  13. clue/app.py +166 -0
  14. clue/cache/__init__.py +129 -0
  15. clue/common/__init__.py +0 -0
  16. clue/common/classification.py +1006 -0
  17. clue/common/classification.yml +130 -0
  18. clue/common/dict_utils.py +130 -0
  19. clue/common/exceptions.py +199 -0
  20. clue/common/forge.py +152 -0
  21. clue/common/json_utils.py +10 -0
  22. clue/common/list_utils.py +11 -0
  23. clue/common/logging/__init__.py +291 -0
  24. clue/common/logging/audit.py +157 -0
  25. clue/common/logging/format.py +42 -0
  26. clue/common/regex.py +31 -0
  27. clue/common/str_utils.py +213 -0
  28. clue/common/swagger.py +139 -0
  29. clue/common/uid.py +47 -0
  30. clue/config.py +60 -0
  31. clue/constants/__init__.py +0 -0
  32. clue/constants/supported_types.py +38 -0
  33. clue/cronjobs/__init__.py +30 -0
  34. clue/cronjobs/plugins.py +32 -0
  35. clue/error.py +129 -0
  36. clue/gunicorn_config.py +29 -0
  37. clue/healthz.py +74 -0
  38. clue/helper/discover.py +53 -0
  39. clue/helper/headers.py +30 -0
  40. clue/helper/oauth.py +128 -0
  41. clue/models/__init__.py +0 -0
  42. clue/models/actions.py +243 -0
  43. clue/models/config.py +456 -0
  44. clue/models/fetchers.py +136 -0
  45. clue/models/graph.py +162 -0
  46. clue/models/model_list.py +52 -0
  47. clue/models/network.py +430 -0
  48. clue/models/results/__init__.py +34 -0
  49. clue/models/results/base.py +10 -0
  50. clue/models/results/graph.py +26 -0
  51. clue/models/results/image.py +22 -0
  52. clue/models/results/status.py +55 -0
  53. clue/models/results/validation.py +57 -0
  54. clue/models/selector.py +67 -0
  55. clue/models/utils.py +52 -0
  56. clue/models/validators.py +19 -0
  57. clue/patched.py +8 -0
  58. clue/plugin/__init__.py +1008 -0
  59. clue/plugin/helpers/__init__.py +0 -0
  60. clue/plugin/helpers/central_server.py +27 -0
  61. clue/plugin/helpers/email_render.py +228 -0
  62. clue/plugin/helpers/token.py +34 -0
  63. clue/plugin/helpers/trino.py +103 -0
  64. clue/plugin/interactive.py +270 -0
  65. clue/plugin/models.py +19 -0
  66. clue/plugin/utils.py +78 -0
  67. clue/remote/__init__.py +0 -0
  68. clue/remote/datatypes/__init__.py +130 -0
  69. clue/remote/datatypes/cache.py +62 -0
  70. clue/remote/datatypes/events.py +118 -0
  71. clue/remote/datatypes/hash.py +193 -0
  72. clue/remote/datatypes/queues/__init__.py +0 -0
  73. clue/remote/datatypes/queues/comms.py +62 -0
  74. clue/remote/datatypes/set.py +96 -0
  75. clue/remote/datatypes/user_quota_tracker.py +54 -0
  76. clue/security/__init__.py +211 -0
  77. clue/security/obo.py +95 -0
  78. clue/security/utils.py +34 -0
  79. clue/services/action_service.py +186 -0
  80. clue/services/auth_service.py +348 -0
  81. clue/services/config_service.py +38 -0
  82. clue/services/fetcher_service.py +203 -0
  83. clue/services/jwt_service.py +233 -0
  84. clue/services/lookup_service.py +786 -0
  85. clue/services/type_service.py +165 -0
  86. clue/services/user_service.py +152 -0
  87. clue_api-1.0.0.dev7.dist-info/METADATA +111 -0
  88. clue_api-1.0.0.dev7.dist-info/RECORD +91 -0
  89. clue_api-1.0.0.dev7.dist-info/WHEEL +4 -0
  90. clue_api-1.0.0.dev7.dist-info/entry_points.txt +8 -0
  91. clue_api-1.0.0.dev7.dist-info/licenses/LICENSE +11 -0
@@ -0,0 +1,291 @@
1
+ import json
2
+ import logging
3
+ import logging.handlers
4
+ import os
5
+ import re
6
+ import uuid
7
+ from traceback import format_exception
8
+ from typing import Optional, Self, Union
9
+
10
+ from flask import request
11
+
12
+ from clue.common.logging.format import (
13
+ BRL_DATE_FORMAT,
14
+ BRL_JSON_FORMAT,
15
+ BRL_LOG_FORMAT,
16
+ BRL_SYSLOG_FORMAT,
17
+ )
18
+ from clue.common.str_utils import default_string_value
19
+
20
+ LOG_LEVEL_MAP = {
21
+ "DEBUG": logging.DEBUG,
22
+ "INFO": logging.INFO,
23
+ "WARNING": logging.WARNING,
24
+ "ERROR": logging.ERROR,
25
+ "CRITICAL": logging.CRITICAL,
26
+ "DISABLED": 60,
27
+ }
28
+
29
+ DEBUG = False
30
+
31
+
32
+ class JsonFormatter(logging.Formatter):
33
+ """A custom implementation of logging.Formatter that supports json logs as well as traceback for exceptions.
34
+
35
+ Args:
36
+ logging (str): The template to use for the logs
37
+ """
38
+
39
+ def formatMessage(self: Self, record: logging.LogRecord): # noqa: N802
40
+ """Formats record.message as json, and formats exceptions using traceback.
41
+
42
+ Args:
43
+ record (logging.LogRecord): The log record to format
44
+
45
+ Returns:
46
+ str: The formatted log
47
+ """
48
+ if record.exc_info:
49
+ record.exc_text = self.formatException(record.exc_info)
50
+ record.exc_info = None
51
+
52
+ if record.exc_text:
53
+ record.message += "\n" + record.exc_text
54
+ record.exc_text = None
55
+
56
+ record.message = json.dumps(record.message)
57
+ return self._style.format(record)
58
+
59
+ def formatException(self, exc_info): # noqa: N802
60
+ """Formats the exception using traceback
61
+
62
+ Args:
63
+ exc_info (logging._SysExcInfoType): The exc_info object from the LogRecord
64
+
65
+ Returns:
66
+ str: The formatted traceback error
67
+ """
68
+ return "".join(format_exception(*exc_info))
69
+
70
+
71
+ def init_logging(name: str, log_level: Optional[int] = None): # noqa: C901
72
+ """Initializes the logging stack for an app
73
+
74
+ Args:
75
+ name (str): The name of the app
76
+ log_level (Optional[int], optional): The log level to use. Defaults to None.
77
+
78
+ Returns:
79
+ Logger: The initialized logger
80
+ """
81
+ from clue.config import config
82
+
83
+ app_name: str = default_string_value(env_name="APP_NAME", default="clue") # type: ignore[assignment]
84
+
85
+ logger = logging.getLogger(app_name)
86
+
87
+ # Test if we've initialized the log handler already.
88
+ if len(logger.handlers) != 0:
89
+ return logger.getChild(name)
90
+
91
+ if name.startswith(f"{app_name}."):
92
+ name = name[len(app_name) + 1 :]
93
+
94
+ debug = config.api.debug
95
+ config.logging.log_to_console = config.logging.log_to_console or debug
96
+
97
+ if log_level is None:
98
+ log_level = LOG_LEVEL_MAP[config.logging.log_level]
99
+
100
+ logging.root.setLevel(logging.CRITICAL)
101
+ logger.setLevel(log_level)
102
+
103
+ if config.logging.log_level == "DISABLED":
104
+ # While log_level is set to disable, we will not create any handlers
105
+ return logger.getChild(name)
106
+
107
+ if config.logging.log_to_file:
108
+ if not os.path.isdir(config.logging.log_directory):
109
+ logger.warning(
110
+ "Log directory does not exist. Will try to create %s",
111
+ config.logging.log_directory,
112
+ )
113
+ os.makedirs(config.logging.log_directory)
114
+
115
+ if log_level <= logging.DEBUG:
116
+ dbg_file_handler = logging.handlers.RotatingFileHandler(
117
+ os.path.join(config.logging.log_directory, f"{name}.dbg"),
118
+ maxBytes=10485760,
119
+ backupCount=5,
120
+ )
121
+ dbg_file_handler.setLevel(logging.DEBUG)
122
+ if config.logging.log_as_json:
123
+ dbg_file_handler.setFormatter(JsonFormatter(BRL_JSON_FORMAT))
124
+ else:
125
+ dbg_file_handler.setFormatter(logging.Formatter(BRL_LOG_FORMAT, BRL_DATE_FORMAT))
126
+ logger.addHandler(dbg_file_handler)
127
+
128
+ if log_level <= logging.INFO:
129
+ op_file_handler = logging.handlers.RotatingFileHandler(
130
+ os.path.join(config.logging.log_directory, f"{name}.log"),
131
+ maxBytes=10485760,
132
+ backupCount=5,
133
+ )
134
+ op_file_handler.setLevel(logging.INFO)
135
+ if config.logging.log_as_json:
136
+ op_file_handler.setFormatter(JsonFormatter(BRL_JSON_FORMAT))
137
+ else:
138
+ op_file_handler.setFormatter(logging.Formatter(BRL_LOG_FORMAT, BRL_DATE_FORMAT))
139
+ logger.addHandler(op_file_handler)
140
+
141
+ if log_level <= logging.ERROR:
142
+ err_file_handler = logging.handlers.RotatingFileHandler(
143
+ os.path.join(config.logging.log_directory, f"{name}.err"),
144
+ maxBytes=10485760,
145
+ backupCount=5,
146
+ )
147
+ err_file_handler.setLevel(logging.ERROR)
148
+ if config.logging.log_as_json:
149
+ err_file_handler.setFormatter(JsonFormatter(BRL_JSON_FORMAT))
150
+ else:
151
+ err_file_handler.setFormatter(logging.Formatter(BRL_LOG_FORMAT, BRL_DATE_FORMAT))
152
+ logger.addHandler(err_file_handler)
153
+
154
+ if config.logging.log_to_console:
155
+ console = logging.StreamHandler()
156
+ if config.logging.log_as_json:
157
+ console.setFormatter(JsonFormatter(BRL_JSON_FORMAT))
158
+ else:
159
+ console.setFormatter(logging.Formatter(BRL_LOG_FORMAT, BRL_DATE_FORMAT))
160
+ logger.addHandler(console)
161
+
162
+ if config.logging.log_to_syslog and config.logging.syslog_host and config.logging.syslog_port:
163
+ syslog_handler = logging.handlers.SysLogHandler(
164
+ address=(config.logging.syslog_host, config.logging.syslog_port)
165
+ )
166
+ syslog_handler.formatter = logging.Formatter(BRL_SYSLOG_FORMAT)
167
+ logger.addHandler(syslog_handler)
168
+
169
+ return logger.getChild(name)
170
+
171
+
172
+ def get_logger(name: Optional[str] = None, parent: Optional[logging.Logger] = None) -> logging.Logger:
173
+ """Gets the logger for a specified python file
174
+
175
+ Args:
176
+ name (Optional[str], optional): The name of the python file. Defaults to None.
177
+ parent (Optional[logging.Logger], optional): The parent of the file. Defaults to None.
178
+
179
+ Returns:
180
+ logging.Logger: _description_
181
+ """
182
+ if name:
183
+ name = (
184
+ re.sub(r".+clue(-api)?/", "", re.sub(r".+plugins/", "", name))
185
+ .replace("/", ".")
186
+ .replace(".__init__", "")
187
+ .replace(".py", "")
188
+ )
189
+ name = re.sub(r"^api\.?", "", name)
190
+
191
+ logger = parent or init_logging("api")
192
+
193
+ if name:
194
+ logger = logger.getChild(name)
195
+
196
+ return logger
197
+
198
+
199
+ def get_traceback_info(tb):
200
+ """Gets the traceback info of a traceback"""
201
+ tb_list = []
202
+ tb_id = 0
203
+ last_ui = None
204
+ while tb is not None:
205
+ f = tb.tb_frame
206
+ line_no = tb.tb_lineno
207
+ tb_list.append((f, line_no))
208
+ tb = tb.tb_next
209
+ if "/ui/" in f.f_code.co_filename:
210
+ last_ui = tb_id
211
+ tb_id += 1
212
+
213
+ if last_ui is not None:
214
+ tb_frame, line = tb_list[last_ui]
215
+ user = tb_frame.f_locals.get("kwargs", {}).get("user", None)
216
+
217
+ if not user:
218
+ temp = tb_frame.f_locals.get("_", {})
219
+ if isinstance(temp, dict):
220
+ user = temp.get("user", None)
221
+
222
+ if not user:
223
+ user = tb_frame.f_locals.get("user", None)
224
+
225
+ if not user:
226
+ user = tb_frame.f_locals.get("impersonator", None)
227
+
228
+ if user:
229
+ return user, tb_frame.f_code.co_filename, tb_frame.f_code.co_name, line
230
+
231
+ return None
232
+
233
+ return None
234
+
235
+
236
+ def dumb_log(log, msg, is_exception=False):
237
+ """Logs a message to a generic logger."""
238
+ args: Union[str, bytes] = request.query_string
239
+ if isinstance(args, bytes):
240
+ args = args.decode()
241
+
242
+ if args:
243
+ args = f"?{args}"
244
+
245
+ message = f"{msg} - {request.path}{args}"
246
+ if is_exception:
247
+ log.exception(message)
248
+ else:
249
+ log.warning(message)
250
+
251
+
252
+ def log_with_traceback(traceback, msg, is_exception=False, audit=False):
253
+ """Logs a message with a traceback"""
254
+ log = get_logger("traceback") if not audit else logging.getLogger("clue.api.audit")
255
+
256
+ tb_info = get_traceback_info(traceback)
257
+ if tb_info:
258
+ tb_user, tb_file, tb_function, tb_line_no = tb_info
259
+ args: Optional[Union[str, bytes]] = request.query_string
260
+ if args:
261
+ args = f"?{args if isinstance(args, str) else args.decode()}"
262
+ else:
263
+ args = ""
264
+
265
+ # noinspection PyBroadException
266
+ try:
267
+ message = (
268
+ f'{tb_user["uname"]} [{tb_user["classification"]}] :: {msg} - {tb_file}:{tb_function}:{tb_line_no}'
269
+ f'[{os.environ.get("CLUE_VERSION", "0.0.0.dev0")}] ({request.path}{args})'
270
+ )
271
+ if is_exception:
272
+ log.exception(message)
273
+ else:
274
+ log.warning(message)
275
+ except Exception:
276
+ dumb_log(log, msg, is_exception=is_exception)
277
+ else:
278
+ dumb_log(log, msg, is_exception=is_exception)
279
+
280
+
281
+ def log_error(logger, msg, err=None, status_code=None):
282
+ """Log a standard error string, with a unique id reference for logging."""
283
+ err_id = str(uuid.uuid4())
284
+ error = [msg]
285
+ if status_code:
286
+ error.append(f"{status_code=}")
287
+ if err:
288
+ error.append(f"{err=}")
289
+ error.append(f"{err_id}")
290
+ logger.error(" :: ".join(error))
291
+ return err_id
@@ -0,0 +1,157 @@
1
+ import logging
2
+ import os
3
+ import sys
4
+
5
+ from flask import request
6
+
7
+ from clue.common.logging.format import (
8
+ BRL_AUDIT_FORMAT,
9
+ BRL_DATE_FORMAT,
10
+ BRL_ISO_DATE_FORMAT,
11
+ BRL_LOG_FORMAT,
12
+ )
13
+ from clue.config import DEBUG, config
14
+
15
+ AUDIT = config.api.audit
16
+
17
+ AUDIT_KW_TARGET = [
18
+ "sid",
19
+ "sha256",
20
+ "copy_sid",
21
+ "filter",
22
+ "query",
23
+ "username",
24
+ "group",
25
+ "rev",
26
+ "wq_id",
27
+ "index",
28
+ "cache_key",
29
+ "alert_key",
30
+ "alert_id",
31
+ "url",
32
+ "q",
33
+ "fq",
34
+ "file_hash",
35
+ "heuristic_id",
36
+ "error_key",
37
+ "mac",
38
+ "vm_type",
39
+ "vm_name",
40
+ "config_name",
41
+ "servicename",
42
+ "vm",
43
+ "transition",
44
+ "data",
45
+ "id",
46
+ "comment_id",
47
+ "label_set",
48
+ "tool_name",
49
+ "operation_id",
50
+ "category",
51
+ "label",
52
+ "type_name",
53
+ "type",
54
+ "value",
55
+ ]
56
+
57
+ AUDIT_LOG = logging.getLogger("clue.api.audit")
58
+ AUDIT_LOG.propagate = False
59
+
60
+ if AUDIT:
61
+ AUDIT_LOG.setLevel(logging.DEBUG)
62
+
63
+ if not os.path.exists(config.logging.log_directory):
64
+ os.makedirs(config.logging.log_directory)
65
+
66
+ fh = logging.FileHandler(os.path.join(config.logging.log_directory, "brl_audit.log"))
67
+ fh.setLevel(logging.DEBUG)
68
+ fh.setFormatter(
69
+ logging.Formatter(
70
+ BRL_LOG_FORMAT if DEBUG else BRL_AUDIT_FORMAT,
71
+ BRL_DATE_FORMAT if DEBUG else BRL_ISO_DATE_FORMAT,
72
+ )
73
+ )
74
+ AUDIT_LOG.addHandler(fh)
75
+
76
+ ch = logging.StreamHandler(sys.stdout)
77
+ ch.setLevel(logging.INFO)
78
+ ch.setFormatter(
79
+ logging.Formatter(
80
+ BRL_LOG_FORMAT if DEBUG else BRL_AUDIT_FORMAT,
81
+ BRL_DATE_FORMAT if DEBUG else BRL_ISO_DATE_FORMAT,
82
+ )
83
+ )
84
+ AUDIT_LOG.addHandler(ch)
85
+
86
+ #########################
87
+ # End of prepare logger #
88
+ #########################
89
+
90
+
91
+ def audit(args, kwargs, user, func, impersonator=None):
92
+ """Audit
93
+
94
+ Arguments:
95
+
96
+ """
97
+ try:
98
+ json_blob = request.json
99
+ if not isinstance(json_blob, dict):
100
+ json_blob = {}
101
+ except Exception:
102
+ json_blob = {}
103
+
104
+ try:
105
+ req_args = ["%s='%s'" % (k, v) for k, v in request.args.items() if k in AUDIT_KW_TARGET]
106
+ method = request.method
107
+ path = request.path
108
+ except RuntimeError:
109
+ req_args = []
110
+ method = "N/A"
111
+ path = "N/A"
112
+
113
+ params_list = (
114
+ list(args)
115
+ + ["%s='%s'" % (k, v) for k, v in kwargs.items() if k in AUDIT_KW_TARGET]
116
+ + req_args
117
+ + ["%s='%s'" % (k, v) for k, v in json_blob.items() if k in AUDIT_KW_TARGET]
118
+ )
119
+
120
+ if impersonator:
121
+ audit_user = f"{impersonator} on behalf of {user['uname']}"
122
+ else:
123
+ audit_user = user["uname"]
124
+
125
+ if DEBUG:
126
+ # In debug mode, you'll get an output like:
127
+ # 23/03/20 14:26:56 DEBUG clue.api.audit | goose - search(index='...', query='...')
128
+ AUDIT_LOG.debug(
129
+ "%s [%s]- %s(%s)",
130
+ audit_user,
131
+ user["classification"],
132
+ func.__name__,
133
+ ", ".join(params_list),
134
+ )
135
+ else:
136
+ # In prod, you'll get an output like:
137
+ # {
138
+ # "date": "2023-03-20T18:33:27-0400",
139
+ # "type": "audit",
140
+ # "app_name": "clue",
141
+ # "api": "clue.api.audit",
142
+ # "severity": "INFO",
143
+ # "user": "goose",
144
+ # "function": "search(index='hit', query='blah blah')",
145
+ # "method": "POST",
146
+ # "path": "/api/v1/search/hit/"
147
+ # }
148
+ AUDIT_LOG.info(
149
+ "",
150
+ extra={
151
+ "user": audit_user,
152
+ "function": f"{func.__name__}({', '.join(params_list)})",
153
+ "method": method,
154
+ "path": path,
155
+ "classification": user["classification"],
156
+ },
157
+ )
@@ -0,0 +1,42 @@
1
+ import json
2
+
3
+ from clue.common.str_utils import default_string_value
4
+
5
+ hostname = "unknownhost"
6
+ # noinspection PyBroadException
7
+ try:
8
+ import socket
9
+
10
+ hostname = socket.gethostname()
11
+ except Exception: # noqa: S110
12
+ pass
13
+
14
+ APP_NAME: str = default_string_value(env_name="APP_NAME", default="clue") # type: ignore[assignment]
15
+
16
+ BRL_SYSLOG_FORMAT = f"HWL %(levelname)8s {hostname} %(process)5d %(name)40s | %(message)s"
17
+ BRL_LOG_FORMAT = "%(asctime)s %(levelname)s %(name)s | %(message)s"
18
+ BRL_DATE_FORMAT = "%y/%m/%d %H:%M:%S"
19
+ BRL_JSON_FORMAT = (
20
+ f"{{"
21
+ f'"@timestamp": "%(asctime)s", '
22
+ f'"event": {{ "module": "{APP_NAME}", "dataset": "%(name)s" }}, '
23
+ f'"host": {{ "hostname": "{hostname}" }}, '
24
+ f'"log": {{ "level": "%(levelname)s", "logger": "%(name)s" }}, '
25
+ f'"process": {{ "pid": "%(process)d" }}, '
26
+ f'"message": %(message)s}}'
27
+ )
28
+ BRL_ISO_DATE_FORMAT = "%Y-%m-%dT%H:%M:%S%z"
29
+ BRL_AUDIT_FORMAT = json.dumps(
30
+ {
31
+ "date": "%(asctime)s",
32
+ "type": "audit",
33
+ "app_name": APP_NAME,
34
+ "api": f"{APP_NAME}.api.audit",
35
+ "severity": "%(levelname)s",
36
+ "user": "%(user)s",
37
+ "classification": "%(classification)s",
38
+ "function": "%(function)s",
39
+ "method": "%(method)s",
40
+ "path": "%(path)s",
41
+ }
42
+ ).replace('"msg"', "%(message)s")
clue/common/regex.py ADDED
@@ -0,0 +1,31 @@
1
+ DOMAIN_REGEX = r"(?:(?=[a-z0-9-]{1,63}\.)(?:xn--)?[a-z0-9-]+(?:-[a-z0-9]+)*\.)+[a-z]{2,63}"
2
+ DOMAIN_ONLY_REGEX = f"^{DOMAIN_REGEX}$"
3
+ DOMAIN_EXCLUDED_NORM_CHARS = "./?@#"
4
+ EMAIL_REGEX = f"^[a-zA-Z0-9!#$%&'*+/=?^_‘{{|}}~-]+(?:\\.[a-zA-Z0-9!#$%&'*+/=?^_‘{{|}}~-]+)*@({DOMAIN_REGEX})$"
5
+ IPV4_REGEX = r"(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)"
6
+ IPV6_REGEX = (
7
+ r"(?:(?:[0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|(?:[0-9a-fA-F]{1,4}:){1,7}:|"
8
+ r"(?:[0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|(?:[0-9a-fA-F]{1,4}:){1,5}(?::[0-9a-fA-F]{1,4}){1,2}|"
9
+ r"(?:[0-9a-fA-F]{1,4}:){1,4}(?::[0-9a-fA-F]{1,4}){1,3}|(?:[0-9a-fA-F]{1,4}:){1,3}(?::[0-9a-fA-F]{1,4}){1,4}|"
10
+ r"(?:[0-9a-fA-F]{1,4}:){1,2}(?::[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:(?:(?::[0-9a-fA-F]{1,4}){1,6})|"
11
+ r":(?:(?::[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(?::[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|"
12
+ r"::(?:ffff(?::0{1,4}){0,1}:){0,1}(?:(?:25[0-5]|(?:2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(?:25[0-5]|"
13
+ r"(?:2[0-4]|1{0,1}[0-9]){0,1}[0-9])|(?:[0-9a-fA-F]{1,4}:){1,4}:(?:(?:25[0-5]|"
14
+ r"(?:2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(?:25[0-5]|(?:2[0-4]|1{0,1}[0-9]){0,1}[0-9]))"
15
+ )
16
+ IP_REGEX = f"(?:{IPV4_REGEX}|{IPV6_REGEX})"
17
+ IP_ONLY_REGEX = f"^{IP_REGEX}$"
18
+ IPV4_ONLY_REGEX = f"^{IPV4_REGEX}$"
19
+ IPV6_ONLY_REGEX = f"^{IPV6_REGEX}$"
20
+ PORT_REGEX = r"^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])$"
21
+ MD5_REGEX = r"^[a-fA-F0-9]{32}$"
22
+ SHA1_REGEX = r"^[a-fA-F0-9]{40}$"
23
+ SHA256_REGEX = r"^[a-fA-F0-9]{64}$"
24
+ URI_PATH = r"([/?#]\S*)"
25
+ URI_REGEX = (
26
+ f"((?:(?:[A-Za-z][A-Za-z0-9+.-]*:)//)(?:[^/?#\\s]+@)?({IP_REGEX}|{DOMAIN_REGEX})(?::\\d{{1,5}})?{URI_PATH}?)"
27
+ )
28
+ URI_ONLY = f"^{URI_REGEX}$"
29
+
30
+ EMAIL_PATH_REGEX = r"^[A-Z]*_EMAIL://.*"
31
+ HBS_AGENT_ID_REGEX = r"[0-9a-fA-F]{1,4}.[0-9a-fA-F]{1,4}.[0-9a-fA-F]{1,4}.[0-9a-fA-F]{1,4}"