django-nativemojo 0.1.10__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 (194) hide show
  1. django_nativemojo-0.1.10.dist-info/LICENSE +19 -0
  2. django_nativemojo-0.1.10.dist-info/METADATA +96 -0
  3. django_nativemojo-0.1.10.dist-info/NOTICE +8 -0
  4. django_nativemojo-0.1.10.dist-info/RECORD +194 -0
  5. django_nativemojo-0.1.10.dist-info/WHEEL +4 -0
  6. mojo/__init__.py +3 -0
  7. mojo/apps/account/__init__.py +1 -0
  8. mojo/apps/account/admin.py +91 -0
  9. mojo/apps/account/apps.py +16 -0
  10. mojo/apps/account/migrations/0001_initial.py +77 -0
  11. mojo/apps/account/migrations/0002_user_is_email_verified_user_is_phone_verified.py +23 -0
  12. mojo/apps/account/migrations/0003_group_mojo_secrets_user_mojo_secrets.py +23 -0
  13. mojo/apps/account/migrations/__init__.py +0 -0
  14. mojo/apps/account/models/__init__.py +3 -0
  15. mojo/apps/account/models/group.py +98 -0
  16. mojo/apps/account/models/member.py +95 -0
  17. mojo/apps/account/models/pkey.py +18 -0
  18. mojo/apps/account/models/user.py +211 -0
  19. mojo/apps/account/rest/__init__.py +3 -0
  20. mojo/apps/account/rest/group.py +25 -0
  21. mojo/apps/account/rest/user.py +47 -0
  22. mojo/apps/account/utils/__init__.py +0 -0
  23. mojo/apps/account/utils/jwtoken.py +72 -0
  24. mojo/apps/account/utils/passkeys.py +54 -0
  25. mojo/apps/fileman/README.md +549 -0
  26. mojo/apps/fileman/__init__.py +0 -0
  27. mojo/apps/fileman/apps.py +15 -0
  28. mojo/apps/fileman/backends/__init__.py +117 -0
  29. mojo/apps/fileman/backends/base.py +319 -0
  30. mojo/apps/fileman/backends/filesystem.py +397 -0
  31. mojo/apps/fileman/backends/s3.py +398 -0
  32. mojo/apps/fileman/examples/configurations.py +378 -0
  33. mojo/apps/fileman/examples/usage_example.py +665 -0
  34. mojo/apps/fileman/management/__init__.py +1 -0
  35. mojo/apps/fileman/management/commands/__init__.py +1 -0
  36. mojo/apps/fileman/management/commands/cleanup_expired_uploads.py +222 -0
  37. mojo/apps/fileman/models/__init__.py +7 -0
  38. mojo/apps/fileman/models/file.py +292 -0
  39. mojo/apps/fileman/models/manager.py +227 -0
  40. mojo/apps/fileman/models/render.py +0 -0
  41. mojo/apps/fileman/rest/__init__ +0 -0
  42. mojo/apps/fileman/rest/__init__.py +23 -0
  43. mojo/apps/fileman/rest/fileman.py +13 -0
  44. mojo/apps/fileman/rest/upload.py +92 -0
  45. mojo/apps/fileman/utils/__init__.py +19 -0
  46. mojo/apps/fileman/utils/upload.py +616 -0
  47. mojo/apps/incident/__init__.py +1 -0
  48. mojo/apps/incident/handlers/__init__.py +3 -0
  49. mojo/apps/incident/handlers/event_handlers.py +142 -0
  50. mojo/apps/incident/migrations/0001_initial.py +83 -0
  51. mojo/apps/incident/migrations/0002_rename_bundle_ruleset_bundle_minutes_event_hostname_and_more.py +44 -0
  52. mojo/apps/incident/migrations/0003_alter_event_model_id.py +18 -0
  53. mojo/apps/incident/migrations/0004_alter_incident_model_id.py +18 -0
  54. mojo/apps/incident/migrations/__init__.py +0 -0
  55. mojo/apps/incident/models/__init__.py +3 -0
  56. mojo/apps/incident/models/event.py +135 -0
  57. mojo/apps/incident/models/incident.py +33 -0
  58. mojo/apps/incident/models/rule.py +247 -0
  59. mojo/apps/incident/parsers/__init__.py +0 -0
  60. mojo/apps/incident/parsers/ossec/__init__.py +1 -0
  61. mojo/apps/incident/parsers/ossec/core.py +82 -0
  62. mojo/apps/incident/parsers/ossec/parsed.py +23 -0
  63. mojo/apps/incident/parsers/ossec/rules.py +124 -0
  64. mojo/apps/incident/parsers/ossec/utils.py +169 -0
  65. mojo/apps/incident/reporter.py +42 -0
  66. mojo/apps/incident/rest/__init__.py +2 -0
  67. mojo/apps/incident/rest/event.py +23 -0
  68. mojo/apps/incident/rest/ossec.py +22 -0
  69. mojo/apps/logit/__init__.py +0 -0
  70. mojo/apps/logit/admin.py +37 -0
  71. mojo/apps/logit/migrations/0001_initial.py +32 -0
  72. mojo/apps/logit/migrations/0002_log_duid_log_payload_log_username.py +28 -0
  73. mojo/apps/logit/migrations/0003_log_level.py +18 -0
  74. mojo/apps/logit/migrations/__init__.py +0 -0
  75. mojo/apps/logit/models/__init__.py +1 -0
  76. mojo/apps/logit/models/log.py +57 -0
  77. mojo/apps/logit/rest.py +9 -0
  78. mojo/apps/metrics/README.md +79 -0
  79. mojo/apps/metrics/__init__.py +12 -0
  80. mojo/apps/metrics/redis_metrics.py +331 -0
  81. mojo/apps/metrics/rest/__init__.py +1 -0
  82. mojo/apps/metrics/rest/base.py +152 -0
  83. mojo/apps/metrics/rest/db.py +0 -0
  84. mojo/apps/metrics/utils.py +227 -0
  85. mojo/apps/notify/README.md +91 -0
  86. mojo/apps/notify/README_NOTIFICATIONS.md +566 -0
  87. mojo/apps/notify/__init__.py +0 -0
  88. mojo/apps/notify/admin.py +52 -0
  89. mojo/apps/notify/handlers/__init__.py +0 -0
  90. mojo/apps/notify/handlers/example_handlers.py +516 -0
  91. mojo/apps/notify/handlers/ses/__init__.py +25 -0
  92. mojo/apps/notify/handlers/ses/bounce.py +0 -0
  93. mojo/apps/notify/handlers/ses/complaint.py +25 -0
  94. mojo/apps/notify/handlers/ses/message.py +86 -0
  95. mojo/apps/notify/management/__init__.py +0 -0
  96. mojo/apps/notify/management/commands/__init__.py +1 -0
  97. mojo/apps/notify/management/commands/process_notifications.py +370 -0
  98. mojo/apps/notify/mod +0 -0
  99. mojo/apps/notify/models/__init__.py +12 -0
  100. mojo/apps/notify/models/account.py +128 -0
  101. mojo/apps/notify/models/attachment.py +24 -0
  102. mojo/apps/notify/models/bounce.py +68 -0
  103. mojo/apps/notify/models/complaint.py +40 -0
  104. mojo/apps/notify/models/inbox.py +113 -0
  105. mojo/apps/notify/models/inbox_message.py +173 -0
  106. mojo/apps/notify/models/outbox.py +129 -0
  107. mojo/apps/notify/models/outbox_message.py +288 -0
  108. mojo/apps/notify/models/template.py +30 -0
  109. mojo/apps/notify/providers/__init__.py +0 -0
  110. mojo/apps/notify/providers/aws.py +73 -0
  111. mojo/apps/notify/rest/__init__.py +0 -0
  112. mojo/apps/notify/rest/ses.py +0 -0
  113. mojo/apps/notify/utils/__init__.py +2 -0
  114. mojo/apps/notify/utils/notifications.py +404 -0
  115. mojo/apps/notify/utils/parsing.py +202 -0
  116. mojo/apps/notify/utils/render.py +144 -0
  117. mojo/apps/tasks/README.md +118 -0
  118. mojo/apps/tasks/__init__.py +11 -0
  119. mojo/apps/tasks/manager.py +489 -0
  120. mojo/apps/tasks/rest/__init__.py +2 -0
  121. mojo/apps/tasks/rest/hooks.py +0 -0
  122. mojo/apps/tasks/rest/tasks.py +62 -0
  123. mojo/apps/tasks/runner.py +174 -0
  124. mojo/apps/tasks/tq_handlers.py +14 -0
  125. mojo/decorators/__init__.py +3 -0
  126. mojo/decorators/auth.py +25 -0
  127. mojo/decorators/cron.py +31 -0
  128. mojo/decorators/http.py +132 -0
  129. mojo/decorators/validate.py +14 -0
  130. mojo/errors.py +88 -0
  131. mojo/helpers/__init__.py +0 -0
  132. mojo/helpers/aws/__init__.py +0 -0
  133. mojo/helpers/aws/client.py +8 -0
  134. mojo/helpers/aws/s3.py +268 -0
  135. mojo/helpers/aws/setup_email.py +0 -0
  136. mojo/helpers/cron.py +79 -0
  137. mojo/helpers/crypto/__init__.py +4 -0
  138. mojo/helpers/crypto/aes.py +60 -0
  139. mojo/helpers/crypto/hash.py +59 -0
  140. mojo/helpers/crypto/privpub/__init__.py +1 -0
  141. mojo/helpers/crypto/privpub/hybrid.py +97 -0
  142. mojo/helpers/crypto/privpub/rsa.py +104 -0
  143. mojo/helpers/crypto/sign.py +36 -0
  144. mojo/helpers/crypto/too.l.py +25 -0
  145. mojo/helpers/crypto/utils.py +26 -0
  146. mojo/helpers/daemon.py +94 -0
  147. mojo/helpers/dates.py +69 -0
  148. mojo/helpers/dns/__init__.py +0 -0
  149. mojo/helpers/dns/godaddy.py +62 -0
  150. mojo/helpers/filetypes.py +128 -0
  151. mojo/helpers/logit.py +310 -0
  152. mojo/helpers/modules.py +95 -0
  153. mojo/helpers/paths.py +63 -0
  154. mojo/helpers/redis.py +10 -0
  155. mojo/helpers/request.py +89 -0
  156. mojo/helpers/request_parser.py +269 -0
  157. mojo/helpers/response.py +14 -0
  158. mojo/helpers/settings.py +146 -0
  159. mojo/helpers/sysinfo.py +140 -0
  160. mojo/helpers/ua.py +0 -0
  161. mojo/middleware/__init__.py +0 -0
  162. mojo/middleware/auth.py +26 -0
  163. mojo/middleware/logging.py +55 -0
  164. mojo/middleware/mojo.py +21 -0
  165. mojo/migrations/0001_initial.py +32 -0
  166. mojo/migrations/__init__.py +0 -0
  167. mojo/models/__init__.py +2 -0
  168. mojo/models/meta.py +262 -0
  169. mojo/models/rest.py +538 -0
  170. mojo/models/secrets.py +59 -0
  171. mojo/rest/__init__.py +1 -0
  172. mojo/rest/info.py +26 -0
  173. mojo/serializers/__init__.py +0 -0
  174. mojo/serializers/models.py +165 -0
  175. mojo/serializers/openapi.py +188 -0
  176. mojo/urls.py +38 -0
  177. mojo/ws4redis/README.md +174 -0
  178. mojo/ws4redis/__init__.py +2 -0
  179. mojo/ws4redis/client.py +283 -0
  180. mojo/ws4redis/connection.py +327 -0
  181. mojo/ws4redis/exceptions.py +32 -0
  182. mojo/ws4redis/redis.py +183 -0
  183. mojo/ws4redis/servers/__init__.py +0 -0
  184. mojo/ws4redis/servers/base.py +86 -0
  185. mojo/ws4redis/servers/django.py +171 -0
  186. mojo/ws4redis/servers/uwsgi.py +63 -0
  187. mojo/ws4redis/settings.py +45 -0
  188. mojo/ws4redis/utf8validator.py +128 -0
  189. mojo/ws4redis/websocket.py +403 -0
  190. testit/__init__.py +0 -0
  191. testit/client.py +147 -0
  192. testit/faker.py +20 -0
  193. testit/helpers.py +198 -0
  194. testit/runner.py +262 -0
@@ -0,0 +1,269 @@
1
+ import ujson
2
+ from typing import Any, Dict, List
3
+ from django.http import QueryDict
4
+ from objict import objict, nobjict
5
+
6
+
7
+ class RequestDataParser:
8
+ """
9
+ A robust parser for Django request data that consolidates GET, POST, JSON, and FILES
10
+ into a single objict with support for dotted notation and array handling.
11
+ """
12
+
13
+ def __init__(self, use_objict=True):
14
+ """
15
+ Initialize parser.
16
+
17
+ Args:
18
+ use_objict: Whether to use objict() or regular dict() for nested structures
19
+ """
20
+ self.dict_factory = objict if use_objict else dict
21
+
22
+ def parse(self, request) -> 'objict':
23
+ """
24
+ Main entry point - parses all request data into a single objict.
25
+
26
+ Args:
27
+ request: Django HttpRequest object
28
+
29
+ Returns:
30
+ objict containing all parsed request data
31
+ """
32
+ result = self.dict_factory()
33
+
34
+ # Process in order of precedence (later sources can override earlier ones)
35
+ self._process_query_params(request.GET, result)
36
+ self._process_form_data(request, result)
37
+ self._process_json_data(request, result)
38
+ self._process_files(request.FILES, result)
39
+
40
+ return result
41
+
42
+ def _process_query_params(self, query_dict: QueryDict, target: 'objict') -> None:
43
+ """Process GET parameters from QueryDict."""
44
+ if not query_dict:
45
+ return
46
+
47
+ for key in query_dict.keys():
48
+ values = query_dict.getlist(key)
49
+ normalized_key = self._normalize_key(key)
50
+ self._set_nested_value(target, normalized_key, values)
51
+
52
+ def _process_form_data(self, request, target: 'objict') -> None:
53
+ """Process POST form data (non-JSON requests)."""
54
+ if request.method not in {'POST', 'PUT', 'PATCH', 'DELETE'}:
55
+ return
56
+
57
+ # Skip if this is a JSON request
58
+ content_type = getattr(request, 'content_type', '').lower()
59
+ if 'application/json' in content_type:
60
+ return
61
+
62
+ if not request.POST:
63
+ return
64
+
65
+ for key in request.POST.keys():
66
+ values = request.POST.getlist(key)
67
+ normalized_key = self._normalize_key(key)
68
+ self._set_nested_value(target, normalized_key, values)
69
+
70
+ def _process_json_data(self, request, target: 'objict') -> None:
71
+ """Process JSON request body."""
72
+ if request.method not in {'POST', 'PUT', 'PATCH', 'DELETE'}:
73
+ return
74
+
75
+ content_type = getattr(request, 'content_type', '').lower()
76
+ if 'application/json' not in content_type:
77
+ return
78
+
79
+ try:
80
+ if hasattr(request, 'body') and request.body:
81
+ json_data = ujson.loads(request.body.decode('utf-8'))
82
+ if isinstance(json_data, dict):
83
+ self._merge_dict_data(json_data, target)
84
+ else:
85
+ # Handle case where JSON root is not an object
86
+ target['_json_data'] = json_data
87
+ except Exception as e:
88
+ # Store error info for debugging
89
+ target['_json_parse_error'] = str(e)
90
+
91
+ def _process_files(self, files_dict, target: 'objict') -> None:
92
+ """Process uploaded files."""
93
+ if not files_dict:
94
+ return
95
+
96
+ files_obj = nobjict()
97
+
98
+ for key in files_dict.keys():
99
+ files = files_dict.getlist(key)
100
+ normalized_key = self._normalize_key(key)
101
+
102
+ # Store single file directly, multiple files as list
103
+ file_value = files[0] if len(files) == 1 else files
104
+ self._set_nested_value(files_obj, normalized_key, [file_value])
105
+
106
+ if files_obj:
107
+ target['files'] = files_obj
108
+
109
+ def _normalize_key(self, key: str) -> str:
110
+ """
111
+ Normalize keys by removing array notation brackets.
112
+
113
+ Examples:
114
+ 'tags[]' -> 'tags'
115
+ 'user[name][]' -> 'user.name'
116
+ 'items[0][name]' -> 'items.0.name'
117
+ """
118
+ # Remove trailing []
119
+ key = key.rstrip('[]')
120
+
121
+ # Convert bracket notation to dot notation
122
+ # user[name] -> user.name
123
+ # items[0][name] -> items.0.name
124
+ import re
125
+ key = re.sub(r'\[([^\]]+)\]', r'.\1', key)
126
+
127
+ return key
128
+
129
+ def _merge_dict_data(self, source: Dict[str, Any], target: 'objict') -> None:
130
+ """Recursively merge dictionary data into target objict."""
131
+ for key, value in source.items():
132
+ self._set_nested_value(target, key, [value])
133
+
134
+ def _set_nested_value(self, target: 'objict', dotted_key: str, values: List[Any]) -> None:
135
+ """
136
+ Set a value in nested objict structure using dotted key notation.
137
+
138
+ Args:
139
+ target: Target objict to modify
140
+ dotted_key: Key like 'user.profile.name' or 'tags'
141
+ values: List of values (handles single values and arrays)
142
+ """
143
+ if not dotted_key:
144
+ return
145
+
146
+ parts = dotted_key.split('.')
147
+ current = target
148
+
149
+ # Navigate to the parent of the final key
150
+ for part in parts[:-1]:
151
+ if not part: # Skip empty parts from double dots
152
+ continue
153
+
154
+ if part not in current:
155
+ current[part] = self.dict_factory()
156
+ elif not isinstance(current[part], (dict, objict, nobjict)):
157
+ # Convert existing non-dict value to dict to avoid conflicts
158
+ current[part] = self.dict_factory()
159
+
160
+ current = current[part]
161
+
162
+ final_key = parts[-1]
163
+ if not final_key: # Handle edge case of trailing dot
164
+ return
165
+
166
+ # Determine final value based on number of values
167
+ if len(values) == 0:
168
+ final_value = None
169
+ elif len(values) == 1:
170
+ final_value = values[0]
171
+ else:
172
+ final_value = values
173
+
174
+ # Handle existing values - merge into arrays if needed
175
+ if final_key in current:
176
+ existing = current[final_key]
177
+ if isinstance(existing, list):
178
+ if isinstance(final_value, list):
179
+ existing.extend(final_value)
180
+ else:
181
+ existing.append(final_value)
182
+ else:
183
+ if isinstance(final_value, list):
184
+ current[final_key] = [existing] + final_value
185
+ else:
186
+ current[final_key] = [existing, final_value]
187
+ else:
188
+ current[final_key] = final_value
189
+
190
+
191
+ # Convenience functions for backward compatibility
192
+ def parse_request_data(request) -> 'objict':
193
+ """
194
+ Parse Django request data into a single objict.
195
+
196
+ Args:
197
+ request: Django HttpRequest object
198
+
199
+ Returns:
200
+ objict containing all parsed request data
201
+ """
202
+ parser = RequestDataParser()
203
+ return parser.parse(request)
204
+
205
+
206
+ def parse_request_data_debug(request) -> 'objict':
207
+ """
208
+ Parse request data with debug information printed.
209
+ """
210
+ print("=== REQUEST PARSING DEBUG ===")
211
+ print(f"Method: {request.method}")
212
+ print(f"Content-Type: {getattr(request, 'content_type', 'Not set')}")
213
+ print(f"GET keys: {list(request.GET.keys())}")
214
+ print(f"POST keys: {list(request.POST.keys())}")
215
+ print(f"FILES keys: {list(request.FILES.keys())}")
216
+
217
+ if hasattr(request, 'body'):
218
+ body_preview = request.body[:200] if request.body else b''
219
+ print(f"Body preview: {body_preview}")
220
+
221
+ result = parse_request_data(request)
222
+ print(f"Parsed result keys: {list(result.keys())}")
223
+ print("=== END DEBUG ===")
224
+
225
+ return result
226
+
227
+
228
+ # Example usage and test cases
229
+ def test_parser():
230
+ """
231
+ Test function to demonstrate parser capabilities.
232
+ """
233
+ from django.http import QueryDict
234
+ from io import StringIO
235
+ import sys
236
+
237
+ # Capture output for testing
238
+ old_stdout = sys.stdout
239
+ sys.stdout = buffer = StringIO()
240
+
241
+ try:
242
+ # Mock request object for testing
243
+ class MockRequest:
244
+ def __init__(self):
245
+ self.method = 'POST'
246
+ self.content_type = 'application/json'
247
+ self.GET = QueryDict('user.name=John&tags[]=python&tags[]=django&user.age=30')
248
+ self.POST = QueryDict()
249
+ self.FILES = QueryDict()
250
+ self.body = b'{"api_key": "secret", "nested": {"value": 42}}'
251
+
252
+ request = MockRequest()
253
+ result = parse_request_data(request)
254
+
255
+ print("Test Results:")
256
+ print(f"User name: {result.get('user', {}).get('name')}")
257
+ print(f"User age: {result.get('user', {}).get('age')}")
258
+ print(f"Tags: {result.get('tags')}")
259
+ print(f"API Key: {result.get('api_key')}")
260
+ print(f"Nested value: {result.get('nested', {}).get('value')}")
261
+
262
+ finally:
263
+ sys.stdout = old_stdout
264
+ output = buffer.getvalue()
265
+ print(output)
266
+
267
+
268
+ if __name__ == "__main__":
269
+ test_parser()
@@ -0,0 +1,14 @@
1
+ import ujson
2
+ from django.http import HttpResponse
3
+
4
+
5
+ class JsonResponse(HttpResponse):
6
+ def __init__(self, data, status=200, safe=True, **kwargs):
7
+ if safe and not isinstance(data, dict):
8
+ raise TypeError(
9
+ 'In order to allow non-dict objects to be serialized set the '
10
+ 'safe parameter to False.'
11
+ )
12
+ kwargs.setdefault('content_type', 'application/json')
13
+ data = ujson.dumps(data)
14
+ super().__init__(content=data, status=status, **kwargs)
@@ -0,0 +1,146 @@
1
+ import importlib
2
+ from typing import Any, Union
3
+
4
+ UNKNOWN = Ellipsis
5
+
6
+
7
+ def load_settings_profile(context):
8
+ from mojo.helpers import modules, paths
9
+ # Set default profile
10
+ profile = "local"
11
+ # Check if a profile file exists and override profile
12
+ profile_file = paths.VAR_ROOT / "profile"
13
+ if profile_file.exists():
14
+ with open(profile_file, 'r') as file:
15
+ profile = file.read().strip()
16
+ modules.load_module_to_globals("settings.defaults", context)
17
+ modules.load_module_to_globals(f"settings.{profile}", context)
18
+
19
+ def load_settings_config(context):
20
+ # Load config from django.conf file
21
+ from mojo.helpers import paths
22
+ config_path = paths.VAR_ROOT / "django.conf"
23
+ if not config_path.exists():
24
+ raise Exception(f"Required configuration file not found: {config_path}")
25
+
26
+ with open(config_path, 'r') as file:
27
+ for line in file:
28
+ if '=' in line:
29
+ key, value = line.strip().split('=', 1)
30
+ value = value.strip()
31
+ if value.startswith('"') and value.endswith('"'):
32
+ value = value[1:-1]
33
+ elif value.startswith("'") and value.endswith("'"):
34
+ value = value[1:-1]
35
+ if value.startswith('f"') or value.startswith("f'"):
36
+ value = eval(value)
37
+ elif value.lower() == 'true':
38
+ value = True
39
+ elif value.lower() == 'false':
40
+ value = False
41
+ else:
42
+ try:
43
+ if '.' in value:
44
+ value = float(value)
45
+ else:
46
+ value = int(value)
47
+ except ValueError:
48
+ pass
49
+ context[key.strip()] = value
50
+
51
+ if context.get("ALLOW_ADMIN_SITE", True):
52
+ if "django.contrib.admin" not in context["INSTALLED_APPS"]:
53
+ context["INSTALLED_APPS"].insert(0, "django.contrib.admin")
54
+
55
+
56
+ class SettingsHelper:
57
+ """
58
+ A helper class for accessing Django settings with support for:
59
+ - Default values if settings are missing.
60
+ - App-specific settings loading from `{app_name}.settings`.
61
+ - Dictionary-style (`settings["KEY"]`) and attribute-style (`settings.KEY`) access.
62
+ """
63
+
64
+ def __init__(self, root_settings=None, defaults=None):
65
+ """
66
+ Initialize the settings helper.
67
+
68
+ :param root_settings: The primary settings source (Django settings or a dictionary).
69
+ :param defaults: An optional defaults module or dictionary.
70
+ """
71
+ self.root = root_settings
72
+ self.defaults = defaults
73
+ self._app_cache = {}
74
+
75
+ def load_settings(self):
76
+ from django.conf import settings as django_settings
77
+ self.root = django_settings
78
+
79
+ def get_app_settings(self, app_name: str) -> "SettingsHelper":
80
+ """
81
+ Get settings for a specific app, attempting to load `{app_name}.settings`.
82
+
83
+ :param app_name: The Django app name.
84
+ :return: A `SettingsHelper` instance for the app settings.
85
+ """
86
+ key = f"{app_name.upper()}_SETTINGS"
87
+
88
+ if key in self._app_cache:
89
+ return self._app_cache[key]
90
+
91
+ try:
92
+ app_defaults = importlib.import_module(f"{app_name}.settings")
93
+ except ModuleNotFoundError:
94
+ app_defaults = {}
95
+
96
+ self._app_cache[key] = SettingsHelper(self.get(key, {}), app_defaults)
97
+ return self._app_cache[key]
98
+
99
+ def get(self, name: str, default: Any = UNKNOWN) -> Any:
100
+ """
101
+ Retrieve a setting, falling back to defaults if needed.
102
+
103
+ :param name: The setting name.
104
+ :param default: The default value if the setting is not found.
105
+ :return: The setting value or the provided default.
106
+ """
107
+ if self.root is None:
108
+ self.load_settings()
109
+ if isinstance(self.root, dict):
110
+ value = self.root.get(name, UNKNOWN)
111
+ else:
112
+ value = getattr(self.root, name, UNKNOWN)
113
+
114
+ return value if value is not UNKNOWN else self.get_default(name, default)
115
+
116
+ def get_default(self, name: str, default: Any = None) -> Any:
117
+ """
118
+ Retrieve a setting from the defaults, if provided.
119
+
120
+ :param name: The setting name.
121
+ :param default: The default value if the setting is not found in defaults.
122
+ :return: The default setting value.
123
+ """
124
+ if isinstance(self.defaults, dict):
125
+ return self.defaults.get(name, default)
126
+
127
+ return getattr(self.defaults, name, default)
128
+
129
+ def is_app_installed(self, app_label):
130
+ return app_label in self.get("INSTALLED_APPS")
131
+
132
+ def __getattr__(self, name: str) -> Any:
133
+ """
134
+ Access settings as attributes (`settings.KEY`).
135
+ """
136
+ return self.get(name, None)
137
+
138
+ def __getitem__(self, key: str) -> Any:
139
+ """
140
+ Access settings as dictionary keys (`settings["KEY"]`).
141
+ """
142
+ return self.get(key)
143
+
144
+
145
+ # Create a global settings helper for accessing Django settings
146
+ settings = SettingsHelper()
@@ -0,0 +1,140 @@
1
+ import platform
2
+ import socket
3
+
4
+ from datetime import datetime
5
+ import time
6
+
7
+ try:
8
+ import psutil
9
+ except ImportError:
10
+ psutil = None
11
+
12
+
13
+ def get_host_ip():
14
+ """Get the IP address associated with the current hostname."""
15
+ hostname = socket.gethostname()
16
+ return socket.gethostbyname(hostname)
17
+
18
+ def get_tcp_established_count():
19
+ """Return the count of established TCP connections."""
20
+ return len(get_tcp_established())
21
+
22
+ def get_tcp_established(filter_type=None):
23
+ """
24
+ Get a list of established TCP connections, optionally filtering by type.
25
+
26
+ :param filter_type: Optionally filter by type ('https', 'redis', 'postgres', etc.)
27
+ :return: List of connections according to filter
28
+ """
29
+ connections = psutil.net_connections(kind="tcp")
30
+ established_connections = [c for c in connections if c.status == psutil.CONN_ESTABLISHED]
31
+ return filter_connections(established_connections, filter_type)
32
+
33
+ def filter_connections(connections, filter_type):
34
+ """
35
+ Filter connections based on the specified filter type.
36
+
37
+ :param connections: List of connections
38
+ :param filter_type: Type to filter (options: 'https', 'redis', 'postgres')
39
+ :return: List of filtered connections
40
+ """
41
+ filters = {
42
+ "https": lambda c: c.laddr.port == 443,
43
+ "redis": lambda c: c.raddr.port == 6379,
44
+ "postgres": lambda c: c.raddr.port == 5432,
45
+ "unknown": lambda c: c.raddr.port not in [5432, 6379] and c.laddr.port != 443
46
+ }
47
+
48
+ if filter_type in filters:
49
+ return [c for c in connections if filters[filter_type](c)]
50
+ elif filter_type and ":" in filter_type:
51
+ addr, port = filter_type.split(':')
52
+ port = int(port)
53
+ return [c for c in connections if (addr == "raddr" and c.raddr.port == port) or (addr == "laddr" and c.laddr.port == port)]
54
+
55
+ return connections
56
+
57
+ def connections_to_dict(connections):
58
+ """
59
+ Convert a list of connections to a dictionary representation.
60
+
61
+ :param connections: List of connections
62
+ :return: List of dictionaries representing connections
63
+ """
64
+ return [
65
+ {
66
+ "id": idx,
67
+ "type": c.type.name,
68
+ "status": c.status,
69
+ "family": c.family.name,
70
+ "raddr": {
71
+ "port": c.raddr.port if c.raddr else None,
72
+ "ip": c.raddr.ip if c.raddr else None,
73
+ },
74
+ "laddr": {
75
+ "port": c.laddr.port,
76
+ "ip": c.laddr.ip
77
+ }
78
+ }
79
+ for idx, c in enumerate(connections, start=1)
80
+ ]
81
+
82
+
83
+ def get_host_info(include_versions=False, include_blocked=False):
84
+ """
85
+ Gather information about the host.
86
+
87
+ :param include_versions: Include software versions if True
88
+ :param include_blocked: Include blocked hosts if True
89
+ :return: Dictionary with host information
90
+ """
91
+ mem = psutil.virtual_memory()
92
+ disk = psutil.disk_usage('/')
93
+ net = psutil.net_io_counters()
94
+
95
+ host_info = {
96
+ "time": time.time(),
97
+ "datetime": datetime.now().isoformat(),
98
+ "version": version.VERSION, # check if this needs to be imported correctly or version info retrieval needs adaptation
99
+ "os": {
100
+ "system": platform.system(),
101
+ "version": platform.version(),
102
+ "hostname": platform.node(),
103
+ "release": platform.release(),
104
+ "processor": platform.processor(),
105
+ "machine": platform.machine()
106
+ },
107
+ "boot_time": psutil.boot_time(),
108
+ "cpu_load": psutil.cpu_percent(),
109
+ "cpus_load": psutil.cpu_percent(percpu=True),
110
+ "memory": {
111
+ "total": mem.total,
112
+ "used": mem.used,
113
+ "available": mem.available,
114
+ "percent": mem.percent
115
+ },
116
+ "disk": {
117
+ "total": disk.total,
118
+ "used": disk.used,
119
+ "free": disk.free,
120
+ "percent": disk.percent
121
+ },
122
+ "users": psutil.users(),
123
+ "cpu": {
124
+ "count": psutil.cpu_count(),
125
+ "freq": psutil.cpu_freq()._asdict() if psutil.cpu_freq() else None,
126
+ },
127
+ "network": {
128
+ "tcp_cons": get_tcp_established_count(),
129
+ "bytes_sent": net.bytes_sent,
130
+ "bytes_recv": net.bytes_recv,
131
+ "packets_sent": net.packets_sent,
132
+ "packets_recv": net.packets_recv,
133
+ "errin": net.errin,
134
+ "errout": net.errout,
135
+ "dropin": net.dropin,
136
+ "dropout": net.dropout
137
+ }
138
+ }
139
+
140
+ return host_info
mojo/helpers/ua.py ADDED
File without changes
File without changes
@@ -0,0 +1,26 @@
1
+ from django.utils.deprecation import MiddlewareMixin
2
+ # from django.http import JsonResponse
3
+ from mojo.helpers.response import JsonResponse
4
+ from mojo.apps.account.utils.jwtoken import JWToken
5
+ from mojo.apps.account.models.user import User
6
+ from mojo.helpers import request as rhelper
7
+ from mojo.helpers.request import get_user_agent
8
+ from objict import objict
9
+
10
+
11
+
12
+ class AuthenticationMiddleware(MiddlewareMixin):
13
+ def process_request(self, request):
14
+ token = request.META.get('HTTP_AUTHORIZATION', None)
15
+ if token is None:
16
+ return
17
+ prefix, token = token.split()
18
+ request.auth_token = objict(prefix=prefix, token=token)
19
+ if prefix.lower() != 'bearer':
20
+ return
21
+ # decode data to find the user
22
+ user, error = User.validate_jwt(token)
23
+ if error is not None:
24
+ return JsonResponse({'error': error}, status=401)
25
+ request.user = user
26
+ user.touch()
@@ -0,0 +1,55 @@
1
+ from mojo.apps.logit.models import Log
2
+ from mojo.helpers.settings import settings
3
+ from mojo.helpers import logit
4
+ from mojo.helpers.response import JsonResponse
5
+
6
+ API_PREFIX = "/".join([settings.get("MOJO_PREFIX", "api/").rstrip("/"), ""])
7
+ LOGIT_DB_ALL = settings.get("LOGIT_DB_ALL", False)
8
+ LOGIT_FILE_ALL = settings.get("LOGIT_FILE_ALL", False)
9
+ LOGIT_RETURN_REAL_ERROR = settings.get("LOGIT_RETURN_REAL_ERROR", True)
10
+ LOGGER = logit.get_logger("requests", "requests.log")
11
+ ERROR_LOGGER = logit.get_logger("error", "error.log")
12
+ LOGIT_NO_LOG_PREFIX = settings.get("LOGIT_NO_LOG_PREFIX", [])
13
+
14
+ class LoggerMiddleware:
15
+ def __init__(self, get_response):
16
+ self.get_response = get_response
17
+
18
+ def __call__(self, request):
19
+ # Log the request before calling get_response
20
+ self.log_request(request)
21
+ try:
22
+ response = self.get_response(request)
23
+ except Exception as e:
24
+ # Log or store the exception here
25
+ err = ERROR_LOGGER.exception()
26
+ Log.logit(request, err, "api_error")
27
+ error = "system error"
28
+ if LOGIT_RETURN_REAL_ERROR:
29
+ error = str(e)
30
+ response = JsonResponse(dict(status=False, error=error), status=500)
31
+ # Log the response after get_response has been called
32
+ self.log_response(request, response)
33
+ return response
34
+
35
+ def can_log(self, request):
36
+ prefixes = LOGIT_NO_LOG_PREFIX
37
+ if not isinstance(prefixes, (list, set, tuple)) or not prefixes:
38
+ return True
39
+ return not any(request.path.startswith(prefix) for prefix in prefixes)
40
+
41
+ def log_request(self, request):
42
+ if not self.can_log(request):
43
+ return
44
+ if LOGIT_DB_ALL:
45
+ request.request_log = Log.logit(request, request.DATA.to_json(as_string=True), "request")
46
+ if LOGIT_FILE_ALL:
47
+ LOGGER.info(f"REQUEST - {request.method} - {request.ip} - {request.path}", request.body)
48
+
49
+ def log_response(self, request, response):
50
+ if not self.can_log(request):
51
+ return
52
+ if LOGIT_DB_ALL:
53
+ Log.logit(request, response.content, "response")
54
+ if LOGIT_FILE_ALL:
55
+ LOGGER.info(f"RESPONSE - {request.method} - {request.ip} - {request.path}", response.content)
@@ -0,0 +1,21 @@
1
+ from mojo.helpers import request as rhelper
2
+ import time
3
+ from objict import objict
4
+
5
+ ANONYMOUS_USER = objict(is_authenticated=False)
6
+
7
+
8
+ class MojoMiddleware:
9
+ def __init__(self, get_response):
10
+ self.get_response = get_response
11
+
12
+ def __call__(self, request):
13
+ request.started_at = time.time()
14
+ request.user = ANONYMOUS_USER
15
+ request.group = None
16
+ request.request_log = None
17
+ request.ip = rhelper.get_remote_ip(request)
18
+ request.user_agent = rhelper.get_user_agent(request)
19
+ request.duid = rhelper.get_device_id(request)
20
+ request.DATA = rhelper.parse_request_data(request)
21
+ return self.get_response(request)