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,403 @@
1
+ # -*- coding: utf-8 -*-
2
+ # This code was generously pilfered from https://bitbucket.org/Jeffrey/gevent-websocket
3
+ # written by Jeffrey Gelens (http://noppo.pro/) and licensed under the Apache License, Version 2.0
4
+ import struct
5
+ from socket import error as socket_error
6
+ from mojo.ws4redis.utf8validator import Utf8Validator
7
+ from mojo.ws4redis.exceptions import WebSocketError, FrameTooLargeException
8
+
9
+ from mojo.helpers.logit import get_logger
10
+ logger = get_logger("async", filename="async.log")
11
+
12
+
13
+ class WebSocket(object):
14
+ __slots__ = ('_closed', 'stream', 'utf8validator', 'utf8validate_last')
15
+
16
+ OPCODE_CONTINUATION = 0x00
17
+ OPCODE_TEXT = 0x01
18
+ OPCODE_BINARY = 0x02
19
+ OPCODE_CLOSE = 0x08
20
+ OPCODE_PING = 0x09
21
+ OPCODE_PONG = 0x0a
22
+
23
+ def __init__(self, wsgi_input):
24
+ self._closed = False
25
+ self.stream = Stream(wsgi_input)
26
+ self.utf8validator = Utf8Validator()
27
+ self.utf8validate_last = None
28
+
29
+ def __del__(self):
30
+ try:
31
+ self.close()
32
+ except:
33
+ # close() may fail if __init__ didn't complete
34
+ pass
35
+
36
+ def _decode_bytes(self, bytestring):
37
+ """
38
+ Internal method used to convert the utf-8 encoded bytestring into unicode.
39
+ If the conversion fails, the socket will be closed.
40
+ """
41
+ if not bytestring:
42
+ return u''
43
+ try:
44
+ return bytestring.decode('utf-8')
45
+ except UnicodeDecodeError:
46
+ self.close(1007)
47
+ raise
48
+
49
+ def _encode_bytes(self, text):
50
+ """
51
+ :returns: The utf-8 byte string equivalent of `text`.
52
+ """
53
+ if isinstance(text, bytes):
54
+ return text
55
+ if not isinstance(text, bytes):
56
+ text = str(text or '')
57
+ return text.encode('utf-8')
58
+
59
+ def _is_valid_close_code(self, code):
60
+ """
61
+ :returns: Whether the returned close code is a valid hybi return code.
62
+ """
63
+ if code < 1000:
64
+ return False
65
+ if 1004 <= code <= 1006:
66
+ return False
67
+ if 1012 <= code <= 1016:
68
+ return False
69
+ if code == 1100:
70
+ # not sure about this one but the autobahn fuzzer requires it.
71
+ return False
72
+ if 2000 <= code <= 2999:
73
+ return False
74
+ return True
75
+
76
+ def get_file_descriptor(self):
77
+ """Return the file descriptor for the given websocket"""
78
+ return self.stream.fileno
79
+
80
+ @property
81
+ def closed(self):
82
+ return self._closed
83
+
84
+ def handle_close(self, header, payload):
85
+ """
86
+ Called when a close frame has been decoded from the stream.
87
+
88
+ :param header: The decoded `Header`.
89
+ :param payload: The bytestring payload associated with the close frame.
90
+ """
91
+ if not payload:
92
+ self.close(1000, None)
93
+ return
94
+ if len(payload) < 2:
95
+ raise WebSocketError('Invalid close frame: {0} {1}'.format(header, payload))
96
+ rv = payload[:2]
97
+ code = struct.unpack('!H', bytes(rv))[0]
98
+ payload = payload[2:]
99
+ if payload:
100
+ validator = Utf8Validator()
101
+ val = validator.validate(payload)
102
+ if not val[0]:
103
+ raise UnicodeError
104
+ if not self._is_valid_close_code(code):
105
+ raise WebSocketError('Invalid close code {0}'.format(code))
106
+ self.close(code, payload)
107
+
108
+ def handle_ping(self, header, payload):
109
+ self.send_frame(payload, self.OPCODE_PONG)
110
+
111
+ def handle_pong(self, header, payload):
112
+ pass
113
+
114
+ def read_frame(self):
115
+ """
116
+ Block until a full frame has been read from the socket.
117
+
118
+ This is an internal method as calling this will not cleanup correctly
119
+ if an exception is called. Use `receive` instead.
120
+
121
+ :return: The header and payload as a tuple.
122
+ """
123
+ header = Header.decode_header(self.stream)
124
+ if header.flags:
125
+ raise WebSocketError
126
+ if not header.length:
127
+ return header, ''
128
+ try:
129
+ payload = self.stream.read(header.length)
130
+ except socket_error:
131
+ payload = ''
132
+ except Exception as e:
133
+ logger.debug("{}: {}".format(type(e), str(e)))
134
+ payload = ''
135
+ if len(payload) != header.length:
136
+ raise WebSocketError('Unexpected EOF reading frame payload')
137
+ if header.mask:
138
+ payload = header.unmask_payload(payload)
139
+ return header, payload
140
+
141
+ def validate_utf8(self, payload):
142
+ # Make sure the frames are decodable independently
143
+ self.utf8validate_last = self.utf8validator.validate(payload)
144
+
145
+ if not self.utf8validate_last[0]:
146
+ raise UnicodeError("Encountered invalid UTF-8 while processing "
147
+ "text message at payload octet index "
148
+ "{0:d}".format(self.utf8validate_last[3]))
149
+
150
+ def read_message(self):
151
+ """
152
+ Return the next text or binary message from the socket.
153
+
154
+ This is an internal method as calling this will not cleanup correctly
155
+ if an exception is called. Use `receive` instead.
156
+ """
157
+ opcode = None
158
+ message = None
159
+ while True:
160
+ header, payload = self.read_frame()
161
+ f_opcode = header.opcode
162
+ if f_opcode in (self.OPCODE_TEXT, self.OPCODE_BINARY):
163
+ # a new frame
164
+ if opcode:
165
+ raise WebSocketError("The opcode in non-fin frame is expected to be zero, got {0!r}".format(f_opcode))
166
+ # Start reading a new message, reset the validator
167
+ self.utf8validator.reset()
168
+ self.utf8validate_last = (True, True, 0, 0)
169
+ opcode = f_opcode
170
+ elif f_opcode == self.OPCODE_CONTINUATION:
171
+ if not opcode:
172
+ raise WebSocketError("Unexpected frame with opcode=0")
173
+ elif f_opcode == self.OPCODE_PING:
174
+ self.handle_ping(header, payload)
175
+ continue
176
+ elif f_opcode == self.OPCODE_PONG:
177
+ self.handle_pong(header, payload)
178
+ continue
179
+ elif f_opcode == self.OPCODE_CLOSE:
180
+ self.handle_close(header, payload)
181
+ return
182
+ else:
183
+ raise WebSocketError("Unexpected opcode={0!r}".format(f_opcode))
184
+ if opcode == self.OPCODE_TEXT:
185
+ self.validate_utf8(payload)
186
+ payload = payload.decode()
187
+ if message is None:
188
+ message = str() if opcode == self.OPCODE_TEXT else bytes()
189
+ message += payload
190
+ if header.fin:
191
+ break
192
+ if opcode == self.OPCODE_TEXT:
193
+ self.validate_utf8(message.encode())
194
+ return message
195
+ else:
196
+ return bytearray(message)
197
+
198
+ def receive(self):
199
+ """
200
+ Read and return a message from the stream. If `None` is returned, then
201
+ the socket is considered closed/errored.
202
+ """
203
+ if self._closed:
204
+ raise WebSocketError("Connection is already closed")
205
+ try:
206
+ return self.read_message()
207
+ except UnicodeError as e:
208
+ logger.info('websocket.receive: UnicodeError {}'.format(e))
209
+ self.close(1007)
210
+ except WebSocketError as e:
211
+ logger.info('websocket.receive: WebSocketError {}'.format(e))
212
+ self.close(1002)
213
+ except Exception as e:
214
+ logger.info('websocket.receive: Unknown error {}'.format(e))
215
+ raise e
216
+
217
+ def flush(self):
218
+ """
219
+ Flush a websocket. In this implementation intentionally it does nothing.
220
+ """
221
+ pass
222
+
223
+ def send_frame(self, message, opcode):
224
+ """
225
+ Send a frame over the websocket with message as its payload
226
+ """
227
+ if self._closed:
228
+ raise WebSocketError("Connection is already closed")
229
+ if opcode == self.OPCODE_TEXT:
230
+ message = self._encode_bytes(message)
231
+ elif opcode == self.OPCODE_BINARY:
232
+ message = bytes(message)
233
+ header = Header.encode_header(True, opcode, '', len(message), 0)
234
+ try:
235
+ self.stream.write(header + message)
236
+ except socket_error:
237
+ raise WebSocketError("Socket is dead")
238
+
239
+ def send(self, message, binary=False):
240
+ """
241
+ Send a frame over the websocket with message as its payload
242
+ """
243
+ if binary is None:
244
+ binary = not isinstance(message, str)
245
+ opcode = self.OPCODE_BINARY if binary else self.OPCODE_TEXT
246
+ try:
247
+ self.send_frame(message, opcode)
248
+ except WebSocketError:
249
+ raise WebSocketError("Socket is dead")
250
+
251
+ def close(self, code=1000, message=''):
252
+ """
253
+ Close the websocket and connection, sending the specified code and
254
+ message. The underlying socket object is _not_ closed, that is the
255
+ responsibility of the initiator.
256
+ """
257
+ try:
258
+ message = self._encode_bytes(message)
259
+ self.send_frame(
260
+ struct.pack('!H%ds' % len(message), code, message),
261
+ opcode=self.OPCODE_CLOSE)
262
+ except WebSocketError:
263
+ # Failed to write the closing frame but it's ok because we're
264
+ # closing the socket anyway.
265
+ logger.debug("Failed to write closing frame -> closing socket")
266
+ finally:
267
+ logger.debug("Closed WebSocket")
268
+ self._closed = True
269
+ self.stream = None
270
+
271
+
272
+ class Stream(object):
273
+ """
274
+ Wraps the handler's socket/rfile attributes and makes it in to a file like
275
+ object that can be read from/written to by the lower level websocket api.
276
+ """
277
+
278
+ __slots__ = ('read', 'write', 'fileno')
279
+
280
+ def __init__(self, wsgi_input):
281
+ self.read = wsgi_input.raw._sock.recv
282
+ self.write = wsgi_input.raw._sock.sendall
283
+ self.fileno = wsgi_input.fileno()
284
+
285
+
286
+ class Header(object):
287
+ __slots__ = ('fin', 'mask', 'opcode', 'flags', 'length')
288
+
289
+ FIN_MASK = 0x80
290
+ OPCODE_MASK = 0x0f
291
+ MASK_MASK = 0x80
292
+ LENGTH_MASK = 0x7f
293
+ RSV0_MASK = 0x40
294
+ RSV1_MASK = 0x20
295
+ RSV2_MASK = 0x10
296
+
297
+ # bitwise mask that will determine the reserved bits for a frame header
298
+ HEADER_FLAG_MASK = RSV0_MASK | RSV1_MASK | RSV2_MASK
299
+
300
+ def __init__(self, fin=0, opcode=0, flags=0, length=0):
301
+ self.mask = ''
302
+ self.fin = fin
303
+ self.opcode = opcode
304
+ self.flags = flags
305
+ self.length = length
306
+
307
+ def mask_payload(self, payload):
308
+ payload = bytearray(payload)
309
+ mask = bytearray(self.mask)
310
+ for i in range(self.length):
311
+ payload[i] ^= mask[i % 4]
312
+ return bytes(payload)
313
+
314
+ # it's the same operation
315
+ unmask_payload = mask_payload
316
+
317
+ def __repr__(self):
318
+ return ("<Header fin={0} opcode={1} length={2} flags={3} at "
319
+ "0x{4:x}>").format(self.fin, self.opcode, self.length,
320
+ self.flags, id(self))
321
+
322
+ @classmethod
323
+ def decode_header(cls, stream):
324
+ """
325
+ Decode a WebSocket header.
326
+
327
+ :param stream: A file like object that can be 'read' from.
328
+ :returns: A `Header` instance.
329
+ """
330
+ read = stream.read
331
+ data = read(2)
332
+ if len(data) != 2:
333
+ raise WebSocketError("Unexpected EOF while decoding header")
334
+ first_byte, second_byte = struct.unpack('!BB', data)
335
+ header = cls(
336
+ fin=first_byte & cls.FIN_MASK == cls.FIN_MASK,
337
+ opcode=first_byte & cls.OPCODE_MASK,
338
+ flags=first_byte & cls.HEADER_FLAG_MASK,
339
+ length=second_byte & cls.LENGTH_MASK)
340
+ has_mask = second_byte & cls.MASK_MASK == cls.MASK_MASK
341
+ if header.opcode > 0x07:
342
+ if not header.fin:
343
+ raise WebSocketError('Received fragmented control frame: {0!r}'.format(data))
344
+ # Control frames MUST have a payload length of 125 bytes or less
345
+ if header.length > 125:
346
+ raise FrameTooLargeException('Control frame cannot be larger than 125 bytes: {0!r}'.format(data))
347
+ if header.length == 126:
348
+ # 16 bit length
349
+ data = read(2)
350
+ if len(data) != 2:
351
+ raise WebSocketError('Unexpected EOF while decoding header')
352
+ header.length = struct.unpack('!H', data)[0]
353
+ elif header.length == 127:
354
+ # 64 bit length
355
+ data = read(8)
356
+ if len(data) != 8:
357
+ raise WebSocketError('Unexpected EOF while decoding header')
358
+ header.length = struct.unpack('!Q', data)[0]
359
+ if has_mask:
360
+ mask = read(4)
361
+ if len(mask) != 4:
362
+ raise WebSocketError('Unexpected EOF while decoding header')
363
+ header.mask = mask
364
+ return header
365
+
366
+ @classmethod
367
+ def encode_header(cls, fin, opcode, mask, length, flags):
368
+ """
369
+ Encodes a WebSocket header.
370
+
371
+ :param fin: Whether this is the final frame for this opcode.
372
+ :param opcode: The opcode of the payload, see `OPCODE_*`
373
+ :param mask: Whether the payload is masked.
374
+ :param length: The length of the frame.
375
+ :param flags: The RSV* flags.
376
+ :return: A bytestring encoded header.
377
+ """
378
+ first_byte = opcode
379
+ second_byte = 0
380
+ extra = b''
381
+ if fin:
382
+ first_byte |= cls.FIN_MASK
383
+ if flags & cls.RSV0_MASK:
384
+ first_byte |= cls.RSV0_MASK
385
+ if flags & cls.RSV1_MASK:
386
+ first_byte |= cls.RSV1_MASK
387
+ if flags & cls.RSV2_MASK:
388
+ first_byte |= cls.RSV2_MASK
389
+ # now deal with length complexities
390
+ if length < 126:
391
+ second_byte += length
392
+ elif length <= 0xffff:
393
+ second_byte += 126
394
+ extra = struct.pack('!H', length)
395
+ elif length <= 0xffffffffffffffff:
396
+ second_byte += 127
397
+ extra = struct.pack('!Q', length)
398
+ else:
399
+ raise FrameTooLargeException
400
+ if mask:
401
+ second_byte |= cls.MASK_MASK
402
+ extra += mask
403
+ return bytes([first_byte, second_byte]) + extra
testit/__init__.py ADDED
File without changes
testit/client.py ADDED
@@ -0,0 +1,147 @@
1
+ import requests
2
+ import base64
3
+ from objict import objict
4
+
5
+
6
+ class RestClient:
7
+ """
8
+ A simple REST client for making HTTP requests to a specified host.
9
+ """
10
+
11
+ def __init__(self, host, logger=None):
12
+ """
13
+ Initializes the SimpleRestClient with a host URL.
14
+
15
+ Args:
16
+ host (str): The base URL of the host for making requests.
17
+ """
18
+ self.host = host if host[-1] == "/" else f"{host}/"
19
+ self.logger = logger
20
+ self.access_token = None
21
+ self.is_authenticated = False
22
+ self.bearer = "bearer"
23
+ self.access_token = None
24
+
25
+ def login(self, username, password):
26
+ self.logout()
27
+ resp = self.post("/api/login", dict(username=username, password=password))
28
+ if resp.response.data and resp.response.data.access_token:
29
+ self.is_authenticated = True
30
+ self.access_token = resp.response.data.access_token
31
+ junk, self.jwt_data = decode_jwt(self.access_token)
32
+ return self.is_authenticated
33
+
34
+ def logout(self):
35
+ self.is_authenticated = False
36
+ self.bearer = "bearer"
37
+ self.access_token = None
38
+
39
+ def get_headers(self):
40
+ if self.is_authenticated:
41
+ return dict(Authorization=f"{self.bearer} {self.access_token}")
42
+ return {}
43
+
44
+ def _make_request(self, method, path, **kwargs):
45
+ """
46
+ Makes an HTTP request using the specified method and path.
47
+
48
+ Args:
49
+ method (str): The HTTP method to use for the request (e.g., 'GET', 'POST').
50
+ path (str): The endpoint path to append to the base host URL.
51
+ **kwargs: Additional arguments to pass to the request (e.g., headers, params).
52
+
53
+ Returns:
54
+ dict: A dictionary containing the response data and status code. If an error occurs,
55
+ returns a dictionary with an error message instead.
56
+ """
57
+ if path[0] == "/":
58
+ path = path[1:]
59
+ url = f"{self.host}{path}"
60
+ headers = self.get_headers()
61
+ response = requests.request(method, url, headers=headers, **kwargs)
62
+ if self.logger:
63
+ self.logger.info("REQUEST", f"{method}:{url}", headers)
64
+ self.logger.info(kwargs.get("json", ""))
65
+ try:
66
+ data = objict.fromdict(response.json()) if response.content else None
67
+ response_data = objict(response=data, status_code=response.status_code)
68
+ if not response.ok:
69
+ response_data['error_reason'] = response.reason
70
+ if self.logger:
71
+ self.logger.info("RESPONSE", f"{method}:{url}")
72
+ self.logger.info(response_data)
73
+ return response_data
74
+ except Exception as e:
75
+ if self.logger:
76
+ self.logger.error("RESPONSE", f"{method}:{url}")
77
+ self.logger.exception(str(e), response.text)
78
+ return objict(error=str(e), text=response.text)
79
+
80
+ def get(self, path, **kwargs):
81
+ """
82
+ Sends a GET request to the specified path.
83
+
84
+ Args:
85
+ path (str): The endpoint path to append to the base host URL.
86
+ **kwargs: Additional arguments to pass to the request (e.g., headers, params).
87
+
88
+ Returns:
89
+ dict: A dictionary containing the response data and status code.
90
+ """
91
+ return self._make_request('GET', path, **kwargs)
92
+
93
+ def post(self, path, json=None, **kwargs):
94
+ """
95
+ Sends a POST request to the specified path.
96
+
97
+ Args:
98
+ path (str): The endpoint path to append to the base host URL.
99
+ json (dict, optional): The JSON data to include in the request body.
100
+ **kwargs: Additional arguments to pass to the request (e.g., headers).
101
+
102
+ Returns:
103
+ dict: A dictionary containing the response data and status code.
104
+ """
105
+ return self._make_request('POST', path, json=json, **kwargs)
106
+
107
+ def put(self, path, json=None, **kwargs):
108
+ """
109
+ Sends a PUT request to the specified path.
110
+
111
+ Args:
112
+ path (str): The endpoint path to append to the base host URL.
113
+ json (dict, optional): The JSON data to include in the request body.
114
+ **kwargs: Additional arguments to pass to the request (e.g., headers).
115
+
116
+ Returns:
117
+ dict: A dictionary containing the response data and status code.
118
+ """
119
+ return self._make_request('PUT', path, json=json, **kwargs)
120
+
121
+ def delete(self, path, **kwargs):
122
+ """
123
+ Sends a DELETE request to the specified path.
124
+
125
+ Args:
126
+ path (str): The endpoint path to append to the base host URL.
127
+ **kwargs: Additional arguments to pass to the request (e.g., headers).
128
+
129
+ Returns:
130
+ dict: A dictionary containing the response data and status code.
131
+ """
132
+ return self._make_request('DELETE', path, **kwargs)
133
+
134
+
135
+
136
+ def base64_decode(data):
137
+ """Decode base64-encoded data."""
138
+ padding = '=' * (-len(data) % 4)
139
+ return base64.urlsafe_b64decode(data + padding)
140
+
141
+
142
+ def decode_jwt(token):
143
+ """Decode a JWT token using base64 decoding."""
144
+ headers, payload, signature = token.split('.')
145
+ decoded_headers = objict.fromJSON(base64_decode(headers))
146
+ decoded_payload = objict.fromJSON(base64_decode(payload))
147
+ return decoded_headers, decoded_payload
testit/faker.py ADDED
@@ -0,0 +1,20 @@
1
+ from faker import Faker
2
+
3
+ fake = Faker()
4
+
5
+ def generate_person():
6
+ return {
7
+ 'first_name': fake.first_name(),
8
+ 'last_name': fake.last_name(),
9
+ 'dob': fake.date_of_birth(),
10
+ 'city': fake.city(),
11
+ 'state': fake.state(),
12
+ 'zipcode': fake.zipcode()
13
+ }
14
+
15
+ def generate_name():
16
+ return fake.catch_phrase()
17
+
18
+
19
+ def generate_text():
20
+ return fake.text()