tina4-python 0.2.122__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 (47) hide show
  1. tina4_python/Auth.py +222 -0
  2. tina4_python/Constant.py +43 -0
  3. tina4_python/Database.py +591 -0
  4. tina4_python/DatabaseResult.py +107 -0
  5. tina4_python/DatabaseTypes.py +15 -0
  6. tina4_python/Debug.py +126 -0
  7. tina4_python/Env.py +37 -0
  8. tina4_python/Localization.py +42 -0
  9. tina4_python/Messages.py +30 -0
  10. tina4_python/MiddleWare.py +90 -0
  11. tina4_python/Migration.py +107 -0
  12. tina4_python/ORM.py +639 -0
  13. tina4_python/Queue.py +615 -0
  14. tina4_python/Request.py +19 -0
  15. tina4_python/Response.py +121 -0
  16. tina4_python/Router.py +423 -0
  17. tina4_python/Session.py +342 -0
  18. tina4_python/ShellColors.py +20 -0
  19. tina4_python/Swagger.py +228 -0
  20. tina4_python/Template.py +107 -0
  21. tina4_python/Webserver.py +429 -0
  22. tina4_python/Websocket.py +49 -0
  23. tina4_python/__init__.py +392 -0
  24. tina4_python/messages.pot +83 -0
  25. tina4_python/public/css/readme.md +0 -0
  26. tina4_python/public/favicon.ico +0 -0
  27. tina4_python/public/images/403.png +0 -0
  28. tina4_python/public/images/404.png +0 -0
  29. tina4_python/public/images/500.png +0 -0
  30. tina4_python/public/images/logo.png +0 -0
  31. tina4_python/public/images/readme.md +0 -0
  32. tina4_python/public/js/readme.md +0 -0
  33. tina4_python/public/js/reconnecting-websocket.js +365 -0
  34. tina4_python/public/js/tina4helper.js +397 -0
  35. tina4_python/public/swagger/index.html +90 -0
  36. tina4_python/public/swagger/oauth2-redirect.html +63 -0
  37. tina4_python/templates/errors/403.twig +10 -0
  38. tina4_python/templates/errors/404.twig +10 -0
  39. tina4_python/templates/errors/500.twig +11 -0
  40. tina4_python/templates/readme.md +1 -0
  41. tina4_python/translations/en/LC_MESSAGES/messages.mo +0 -0
  42. tina4_python/translations/en/LC_MESSAGES/messages.po +80 -0
  43. tina4_python/translations/fr/LC_MESSAGES/messages.mo +0 -0
  44. tina4_python/translations/fr/LC_MESSAGES/messages.po +84 -0
  45. tina4_python-0.2.122.dist-info/METADATA +465 -0
  46. tina4_python-0.2.122.dist-info/RECORD +47 -0
  47. tina4_python-0.2.122.dist-info/WHEEL +4 -0
tina4_python/Auth.py ADDED
@@ -0,0 +1,222 @@
1
+ #
2
+ # Tina4 - This is not a 4ramework.
3
+ # Copy-right 2007 - current Tina4
4
+ # License: MIT https://opensource.org/licenses/MIT
5
+ #
6
+ # flake8: noqa: E501
7
+ import datetime
8
+ import os
9
+ import jwt
10
+ import bcrypt
11
+ from json import JSONEncoder
12
+ from cryptography import x509
13
+ from cryptography.x509 import NameOID
14
+ from cryptography.hazmat.primitives import serialization, hashes
15
+ from cryptography.hazmat.primitives.asymmetric import rsa
16
+ from cryptography.hazmat.backends import default_backend
17
+
18
+
19
+ class AuthJSONSerializer(JSONEncoder):
20
+ def default(self, o):
21
+ if isinstance(o, datetime.datetime):
22
+ return o.isoformat()
23
+ else:
24
+ raise TypeError(f'Object of type {o.__class__.__name__} '
25
+ f'is not JSON serializable')
26
+
27
+
28
+ class Auth:
29
+ secret = None
30
+ private_key = None
31
+ root_path = None
32
+ loaded_private_key = None
33
+ loaded_public_key = None
34
+
35
+ def hash_password(self, text):
36
+ """
37
+ Generates a Bcrypt password hash
38
+ :param text:
39
+ :return:
40
+ """
41
+ password_bytes = text.encode('utf-8')
42
+ # Generate a salt
43
+ salt = bcrypt.gensalt()
44
+ # Hash the password
45
+ return bcrypt.hashpw(password_bytes, salt).decode('utf-8')
46
+
47
+ def check_password(self, password_hash, text):
48
+ """
49
+ Checks a Bcrypt password hash
50
+ :param password_hash:
51
+ :param text:
52
+ :return:
53
+ """
54
+ password_bytes = text.encode('utf-8')
55
+ return bcrypt.checkpw(password_bytes, password_hash.encode('utf-8'))
56
+
57
+ def load_private_key(self):
58
+ """
59
+ Loads the private key from the secrets folder
60
+ :return:
61
+ """
62
+ if self.loaded_private_key:
63
+ return self.loaded_private_key
64
+ with open(self.private_key, "rb") as f:
65
+ private_key = serialization.load_pem_private_key(
66
+ f.read(), b"{self.secret}", backend=default_backend()
67
+ )
68
+ self.loaded_private_key = private_key
69
+ return private_key
70
+
71
+ def load_public_key(self):
72
+ """
73
+ Loads the public key from the secrets folder
74
+ :return:
75
+ """
76
+ if self.loaded_public_key:
77
+ return self.loaded_public_key
78
+ with open(self.public_key, "rb") as f:
79
+ public_key = serialization.load_pem_public_key(
80
+ f.read(), backend=default_backend()
81
+ )
82
+ self.loaded_public_key = public_key
83
+ return public_key
84
+
85
+ def __init__(self, root_path):
86
+ self.root_path = root_path
87
+ self.secret = os.environ.get("SECRET", None)
88
+ self.private_key = root_path + os.sep + "secrets" + os.sep + "private.key"
89
+ self.public_key = root_path + os.sep + "secrets" + os.sep + "public.key"
90
+ self.self_signed = root_path + os.sep + "secrets" + os.sep + "domain.cert"
91
+
92
+ # check if we have a secrets folder
93
+ if not os.path.exists(root_path + os.sep + "secrets"):
94
+ os.makedirs(root_path + os.sep + "secrets")
95
+
96
+ if not os.path.isfile(self.private_key):
97
+ private_key = rsa.generate_private_key(
98
+ public_exponent=65537,
99
+ key_size=2048,
100
+ )
101
+ with open(self.private_key, "wb") as f:
102
+ f.write(private_key.private_bytes(
103
+ encoding=serialization.Encoding.PEM,
104
+ format=serialization.PrivateFormat.TraditionalOpenSSL,
105
+ encryption_algorithm=serialization.BestAvailableEncryption(b"{self.secret}"),
106
+ ))
107
+ else:
108
+ private_key = self.load_private_key()
109
+
110
+ if not os.path.isfile(self.public_key):
111
+ public_key = private_key.public_key()
112
+ public_pem = public_key.public_bytes(
113
+ encoding=serialization.Encoding.PEM,
114
+ format=serialization.PublicFormat.SubjectPublicKeyInfo
115
+ )
116
+ with open(self.public_key, 'wb') as f:
117
+ f.write(public_pem)
118
+
119
+ if not os.path.isfile(self.self_signed):
120
+ subject = issuer = x509.Name([
121
+ x509.NameAttribute(NameOID.COUNTRY_NAME, os.environ.get('COUNTRY', 'ZA')),
122
+ x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, os.environ.get('STATE', 'WESTERN CAPE')),
123
+ x509.NameAttribute(NameOID.LOCALITY_NAME, os.environ.get('CITY', 'CAPE TOWN')),
124
+ x509.NameAttribute(NameOID.ORGANIZATION_NAME, os.environ.get('ORGANIZATION', 'Tina4')),
125
+ x509.NameAttribute(NameOID.COMMON_NAME, os.environ.get('DOMAIN_NAME', 'localhost'))
126
+ ])
127
+ cert = (x509.CertificateBuilder().subject_name(
128
+ subject
129
+ ).issuer_name(
130
+ issuer
131
+ ).public_key(
132
+ private_key.public_key()
133
+ ).serial_number(
134
+ x509.random_serial_number()
135
+ ).not_valid_before(
136
+ datetime.datetime.now(datetime.timezone.utc)
137
+ ).not_valid_after(
138
+ datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(days=99999)
139
+ ).add_extension(
140
+ x509.SubjectAlternativeName([x509.DNSName("localhost")]),
141
+ critical=False,
142
+ ).sign(private_key, hashes.SHA256()))
143
+
144
+ with open(self.self_signed, "wb") as f:
145
+ f.write(cert.public_bytes(serialization.Encoding.PEM))
146
+
147
+ def get_token(self, payload_data, expiry_minutes=0):
148
+ """
149
+ Returns a token to be used as bearer
150
+ :param payload_data:
151
+ :param expiry_minutes:
152
+ :return:
153
+ """
154
+ private_key = self.load_private_key()
155
+ now = datetime.datetime.now()
156
+
157
+ if "expires" not in payload_data:
158
+ token_limit_minutes = int(os.environ.get("TINA4_TOKEN_LIMIT", 2))
159
+ if expiry_minutes != 0:
160
+ token_limit_minutes = expiry_minutes
161
+ expiry_time = now + datetime.timedelta(minutes=token_limit_minutes)
162
+ payload_data["expires"] = expiry_time.isoformat()
163
+
164
+ token = jwt.encode(
165
+ payload=payload_data,
166
+ key=private_key,
167
+ algorithm='RS256',
168
+ json_encoder=AuthJSONSerializer
169
+ )
170
+
171
+ return token
172
+
173
+ def get_payload(self, token):
174
+ """
175
+ Gets the payload of the token to be evaluated
176
+ :param token:
177
+ :return:
178
+ """
179
+ public_key = self.load_public_key()
180
+ try:
181
+ payload = jwt.decode(token, key=public_key, algorithms=['RS256'])
182
+ except jwt.exceptions.InvalidSignatureError:
183
+ payload = None
184
+
185
+ return payload
186
+
187
+ def validate(self, token):
188
+ """
189
+ Validates the token
190
+ :param token:
191
+ :return:
192
+ """
193
+ # first check for API_KEY = token, simplest form
194
+ if os.environ.get("API_KEY", None) is not None:
195
+ if token == os.environ.get("API_KEY"):
196
+ return True
197
+
198
+ public_key = self.load_public_key()
199
+ try:
200
+ payload = jwt.decode(token, key=public_key, algorithms=['RS256'])
201
+ if "expires" not in payload:
202
+ return False
203
+
204
+ if "expires" in payload:
205
+ now = datetime.datetime.now()
206
+ expiry_time = datetime.datetime.fromisoformat(payload["expires"])
207
+ if now > expiry_time:
208
+ return False
209
+ else:
210
+ return True
211
+ except Exception:
212
+ return False
213
+
214
+ return False
215
+
216
+ def valid(self, token):
217
+ """
218
+ Validates the token
219
+ :param token:
220
+ :return:
221
+ """
222
+ return self.validate(token)
@@ -0,0 +1,43 @@
1
+ #
2
+ # Tina4 - This is not a 4ramework.
3
+ # Copy-right 2007 - current Tina4
4
+ # License: MIT https://opensource.org/licenses/MIT
5
+ #
6
+ # flake8: noqa: E501
7
+ TINA4_LOG_INFO = "INFO"
8
+ TINA4_LOG_WARNING = "WARN"
9
+ TINA4_LOG_DEBUG = "DEBUG"
10
+ TINA4_LOG_ERROR = "ERROR"
11
+ TINA4_LOG_ALL = "ALL"
12
+
13
+ TINA4_GET = "GET"
14
+ TINA4_POST = "POST"
15
+ TINA4_ANY = "ANY"
16
+ TINA4_PATCH = "PATCH"
17
+ TINA4_PUT = "PUT"
18
+ TINA4_DELETE = "DELETE"
19
+ TINA4_OPTIONS = "OPTIONS"
20
+
21
+ HTTP_OK = 200
22
+ HTTP_CREATED = 201
23
+ HTTP_ACCEPTED = 202
24
+ HTTP_NO_CONTENT = 204
25
+ HTTP_PARTIAL_CONTENT = 206
26
+ HTTP_BAD_REQUEST = 400
27
+ HTTP_UNAUTHORIZED = 401
28
+ HTTP_FORBIDDEN = 403
29
+ HTTP_NOT_FOUND = 404
30
+ HTTP_REDIRECT = 302
31
+ HTTP_SERVER_ERROR = 500
32
+
33
+ LOOKUP_HTTP_CODE = {HTTP_OK: "OK", HTTP_CREATED: "Created", HTTP_ACCEPTED: "Accepted", HTTP_BAD_REQUEST: "Bad Request",
34
+ HTTP_PARTIAL_CONTENT: "Partial Content", HTTP_UNAUTHORIZED: "Unauthorized",
35
+ HTTP_FORBIDDEN: "Forbidden", HTTP_NOT_FOUND: "Not Found", HTTP_NO_CONTENT: "No Content",
36
+ HTTP_REDIRECT: "Redirect",
37
+ HTTP_SERVER_ERROR: "Internal Server Error"}
38
+
39
+ TEXT_HTML = "text/html"
40
+ TEXT_CSS = "text/css"
41
+ TEXT_PLAIN = "text/plain"
42
+ APPLICATION_JSON = "application/json"
43
+ APPLICATION_XML = "application/xml"