tina4-python 0.2.194__tar.gz → 0.2.197__tar.gz
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.
- {tina4_python-0.2.194 → tina4_python-0.2.197}/PKG-INFO +1 -1
- {tina4_python-0.2.194 → tina4_python-0.2.197}/pyproject.toml +2 -1
- {tina4_python-0.2.194 → tina4_python-0.2.197}/tina4_python/Response.py +10 -3
- {tina4_python-0.2.194 → tina4_python-0.2.197}/tina4_python/Router.py +7 -4
- {tina4_python-0.2.194 → tina4_python-0.2.197}/tina4_python/Session.py +7 -2
- {tina4_python-0.2.194 → tina4_python-0.2.197}/tina4_python/Webserver.py +34 -10
- {tina4_python-0.2.194 → tina4_python-0.2.197}/.gitignore +0 -0
- {tina4_python-0.2.194 → tina4_python-0.2.197}/README.md +0 -0
- {tina4_python-0.2.194 → tina4_python-0.2.197}/tina4_python/Api.py +0 -0
- {tina4_python-0.2.194 → tina4_python-0.2.197}/tina4_python/Auth.py +0 -0
- {tina4_python-0.2.194 → tina4_python-0.2.197}/tina4_python/CLAUDE.md +0 -0
- {tina4_python-0.2.194 → tina4_python-0.2.197}/tina4_python/CRUD.py +0 -0
- {tina4_python-0.2.194 → tina4_python-0.2.197}/tina4_python/Constant.py +0 -0
- {tina4_python-0.2.194 → tina4_python-0.2.197}/tina4_python/Database.py +0 -0
- {tina4_python-0.2.194 → tina4_python-0.2.197}/tina4_python/DatabaseResult.py +0 -0
- {tina4_python-0.2.194 → tina4_python-0.2.197}/tina4_python/DatabaseTypes.py +0 -0
- {tina4_python-0.2.194 → tina4_python-0.2.197}/tina4_python/Debug.py +0 -0
- {tina4_python-0.2.194 → tina4_python-0.2.197}/tina4_python/DevReload.py +0 -0
- {tina4_python-0.2.194 → tina4_python-0.2.197}/tina4_python/Env.py +0 -0
- {tina4_python-0.2.194 → tina4_python-0.2.197}/tina4_python/FieldTypes.py +0 -0
- {tina4_python-0.2.194 → tina4_python-0.2.197}/tina4_python/HtmlElement.py +0 -0
- {tina4_python-0.2.194 → tina4_python-0.2.197}/tina4_python/Localization.py +0 -0
- {tina4_python-0.2.194 → tina4_python-0.2.197}/tina4_python/Messages.py +0 -0
- {tina4_python-0.2.194 → tina4_python-0.2.197}/tina4_python/MiddleWare.py +0 -0
- {tina4_python-0.2.194 → tina4_python-0.2.197}/tina4_python/Migration.py +0 -0
- {tina4_python-0.2.194 → tina4_python-0.2.197}/tina4_python/ORM.py +0 -0
- {tina4_python-0.2.194 → tina4_python-0.2.197}/tina4_python/Queue.py +0 -0
- {tina4_python-0.2.194 → tina4_python-0.2.197}/tina4_python/Request.py +0 -0
- {tina4_python-0.2.194 → tina4_python-0.2.197}/tina4_python/ShellColors.py +0 -0
- {tina4_python-0.2.194 → tina4_python-0.2.197}/tina4_python/Swagger.py +0 -0
- {tina4_python-0.2.194 → tina4_python-0.2.197}/tina4_python/Template.py +0 -0
- {tina4_python-0.2.194 → tina4_python-0.2.197}/tina4_python/Testing.py +0 -0
- {tina4_python-0.2.194 → tina4_python-0.2.197}/tina4_python/WSDL.py +0 -0
- {tina4_python-0.2.194 → tina4_python-0.2.197}/tina4_python/Websocket.py +0 -0
- {tina4_python-0.2.194 → tina4_python-0.2.197}/tina4_python/__init__.py +0 -0
- {tina4_python-0.2.194 → tina4_python-0.2.197}/tina4_python/cli.py +0 -0
- {tina4_python-0.2.194 → tina4_python-0.2.197}/tina4_python/messages.pot +0 -0
- {tina4_python-0.2.194 → tina4_python-0.2.197}/tina4_python/public/css/readme.md +0 -0
- {tina4_python-0.2.194 → tina4_python-0.2.197}/tina4_python/public/favicon.ico +0 -0
- {tina4_python-0.2.194 → tina4_python-0.2.197}/tina4_python/public/images/403.png +0 -0
- {tina4_python-0.2.194 → tina4_python-0.2.197}/tina4_python/public/images/404.png +0 -0
- {tina4_python-0.2.194 → tina4_python-0.2.197}/tina4_python/public/images/500.png +0 -0
- {tina4_python-0.2.194 → tina4_python-0.2.197}/tina4_python/public/images/logo.png +0 -0
- {tina4_python-0.2.194 → tina4_python-0.2.197}/tina4_python/public/images/readme.md +0 -0
- {tina4_python-0.2.194 → tina4_python-0.2.197}/tina4_python/public/js/readme.md +0 -0
- {tina4_python-0.2.194 → tina4_python-0.2.197}/tina4_python/public/js/reconnecting-websocket.js +0 -0
- {tina4_python-0.2.194 → tina4_python-0.2.197}/tina4_python/public/js/tina4helper.js +0 -0
- {tina4_python-0.2.194 → tina4_python-0.2.197}/tina4_python/public/swagger/index.html +0 -0
- {tina4_python-0.2.194 → tina4_python-0.2.197}/tina4_python/public/swagger/oauth2-redirect.html +0 -0
- {tina4_python-0.2.194 → tina4_python-0.2.197}/tina4_python/templates/components/crud.twig +0 -0
- {tina4_python-0.2.194 → tina4_python-0.2.197}/tina4_python/templates/errors/403.twig +0 -0
- {tina4_python-0.2.194 → tina4_python-0.2.197}/tina4_python/templates/errors/404.twig +0 -0
- {tina4_python-0.2.194 → tina4_python-0.2.197}/tina4_python/templates/errors/500.twig +0 -0
- {tina4_python-0.2.194 → tina4_python-0.2.197}/tina4_python/templates/readme.md +0 -0
- {tina4_python-0.2.194 → tina4_python-0.2.197}/tina4_python/translations/af/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-0.2.194 → tina4_python-0.2.197}/tina4_python/translations/af/LC_MESSAGES/messages.po +0 -0
- {tina4_python-0.2.194 → tina4_python-0.2.197}/tina4_python/translations/en/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-0.2.194 → tina4_python-0.2.197}/tina4_python/translations/en/LC_MESSAGES/messages.po +0 -0
- {tina4_python-0.2.194 → tina4_python-0.2.197}/tina4_python/translations/es/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-0.2.194 → tina4_python-0.2.197}/tina4_python/translations/es/LC_MESSAGES/messages.po +0 -0
- {tina4_python-0.2.194 → tina4_python-0.2.197}/tina4_python/translations/fr/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-0.2.194 → tina4_python-0.2.197}/tina4_python/translations/fr/LC_MESSAGES/messages.po +0 -0
- {tina4_python-0.2.194 → tina4_python-0.2.197}/tina4_python/translations/ja/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-0.2.194 → tina4_python-0.2.197}/tina4_python/translations/ja/LC_MESSAGES/messages.po +0 -0
- {tina4_python-0.2.194 → tina4_python-0.2.197}/tina4_python/translations/zh/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-0.2.194 → tina4_python-0.2.197}/tina4_python/translations/zh/LC_MESSAGES/messages.po +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "tina4-python"
|
|
3
|
-
version = "0.2.
|
|
3
|
+
version = "0.2.197"
|
|
4
4
|
description = "Tina4Python - This is not another framework for Python"
|
|
5
5
|
authors = [
|
|
6
6
|
{name = "Andre van Zuydam",email = "andrevanzuydam@gmail.com"}
|
|
@@ -32,6 +32,7 @@ dev = [
|
|
|
32
32
|
"pydoc-markdown>=4.8.2",
|
|
33
33
|
"pytest>=8.3.5",
|
|
34
34
|
"pytest-asyncio>=1.3.0",
|
|
35
|
+
"pytest-cov>=7.0.0",
|
|
35
36
|
"python-keycloak>=5.8.1",
|
|
36
37
|
"ruff>=0.11.9",
|
|
37
38
|
"safety>=3.5.0",
|
|
@@ -124,7 +124,9 @@ class Response:
|
|
|
124
124
|
:param redirect_url:
|
|
125
125
|
:return:
|
|
126
126
|
"""
|
|
127
|
-
|
|
127
|
+
# Strip CR/LF to prevent HTTP response splitting (header injection)
|
|
128
|
+
safe_url = redirect_url.replace("\r", "").replace("\n", "")
|
|
129
|
+
headers = {"Location": safe_url}
|
|
128
130
|
from tina4_python import Messages
|
|
129
131
|
return Response(Messages.MSG_REDIRECTING, http_code_in, Constant.TEXT_HTML, headers)
|
|
130
132
|
|
|
@@ -193,16 +195,21 @@ class Response:
|
|
|
193
195
|
@staticmethod
|
|
194
196
|
def add_header(key, value):
|
|
195
197
|
"""
|
|
196
|
-
Adds a header for the response (concurrency-safe via contextvars)
|
|
198
|
+
Adds a header for the response (concurrency-safe via contextvars).
|
|
199
|
+
CR/LF characters are stripped from both key and value to prevent
|
|
200
|
+
HTTP response splitting.
|
|
197
201
|
:param key:
|
|
198
202
|
:param value:
|
|
199
203
|
:return:
|
|
200
204
|
"""
|
|
205
|
+
# Sanitise to prevent header injection
|
|
206
|
+
safe_key = str(key).replace("\r", "").replace("\n", "")
|
|
207
|
+
safe_value = str(value).replace("\r", "").replace("\n", "")
|
|
201
208
|
h = _pending_headers.get()
|
|
202
209
|
if h is None:
|
|
203
210
|
h = {}
|
|
204
211
|
_pending_headers.set(h)
|
|
205
|
-
h[
|
|
212
|
+
h[safe_key] = safe_value
|
|
206
213
|
|
|
207
214
|
@staticmethod
|
|
208
215
|
def wsdl(wsdl_instance):
|
|
@@ -373,6 +373,7 @@ class Router:
|
|
|
373
373
|
token = headers["authorization"].replace("Bearer", "").strip()
|
|
374
374
|
if tina4_python.tina4_auth.valid(token):
|
|
375
375
|
validated = True
|
|
376
|
+
has_form_token = True
|
|
376
377
|
|
|
377
378
|
if request["params"] is not None and "formToken" in request["params"]:
|
|
378
379
|
token = request["params"]["formToken"]
|
|
@@ -528,15 +529,17 @@ class Router:
|
|
|
528
529
|
result.headers["FreshToken"] = tina4_python.tina4_auth.get_token({"path": url})
|
|
529
530
|
if "cache" in route and route["cache"] is not None:
|
|
530
531
|
if not route["cache"]["cached"]:
|
|
531
|
-
result.headers["Cache-Control"] = "
|
|
532
|
+
result.headers["Cache-Control"] = "no-store, no-cache, must-revalidate"
|
|
532
533
|
result.headers["Pragma"] = "no-cache"
|
|
533
534
|
else:
|
|
534
535
|
result.headers["Cache-Control"] = "max-age=" + str(
|
|
535
536
|
route["cache"]["max_age"]) + ", must-revalidate"
|
|
536
537
|
result.headers["Pragma"] = "cache"
|
|
537
538
|
else:
|
|
538
|
-
|
|
539
|
-
|
|
539
|
+
# Default: don't cache — prevents sensitive data leaking
|
|
540
|
+
# via shared proxies or browser disk cache
|
|
541
|
+
result.headers["Cache-Control"] = "no-store, no-cache, must-revalidate"
|
|
542
|
+
result.headers["Pragma"] = "no-cache"
|
|
540
543
|
finally:
|
|
541
544
|
sys.stdout = old_stdout
|
|
542
545
|
|
|
@@ -572,7 +575,7 @@ class Router:
|
|
|
572
575
|
)
|
|
573
576
|
|
|
574
577
|
twig_headers = {
|
|
575
|
-
"Cache-Control": "
|
|
578
|
+
"Cache-Control": "no-store, no-cache, must-revalidate",
|
|
576
579
|
"Pragma": "no-cache"
|
|
577
580
|
}
|
|
578
581
|
if has_form_token:
|
|
@@ -745,10 +745,15 @@ class LazySession:
|
|
|
745
745
|
self._activate().session_hash = value
|
|
746
746
|
|
|
747
747
|
def start(self, session_hash=None):
|
|
748
|
-
|
|
748
|
+
result = self._activate().start(session_hash)
|
|
749
|
+
# Keep cookie dict in sync with the (possibly new) session hash
|
|
750
|
+
self._cookies[self._name] = result
|
|
751
|
+
return result
|
|
749
752
|
|
|
750
753
|
def load(self, session_hash):
|
|
751
|
-
|
|
754
|
+
self._activate().load(session_hash)
|
|
755
|
+
# Keep cookie dict in sync with loaded session hash
|
|
756
|
+
self._cookies[self._name] = self._real.session_hash
|
|
752
757
|
|
|
753
758
|
def set(self, key, value):
|
|
754
759
|
return self._activate().set(key, value)
|
|
@@ -239,22 +239,33 @@ class Webserver:
|
|
|
239
239
|
|
|
240
240
|
async def send_basic_headers(self, headers_list: list):
|
|
241
241
|
"""
|
|
242
|
-
Add
|
|
242
|
+
Add CORS, keep-alive, and security headers (used for most responses).
|
|
243
243
|
|
|
244
244
|
Args:
|
|
245
245
|
headers_list (list): List to which headers are appended.
|
|
246
246
|
"""
|
|
247
|
-
|
|
247
|
+
# CORS — reflect the request Origin when credentials are needed,
|
|
248
|
+
# otherwise fall back to * for simple cross-origin requests.
|
|
249
|
+
origin = self.lowercase_headers.get("origin", "")
|
|
250
|
+
if origin:
|
|
251
|
+
self.send_header("Access-Control-Allow-Origin", origin, headers_list)
|
|
252
|
+
self.send_header("Access-Control-Allow-Credentials", "true", headers_list)
|
|
253
|
+
else:
|
|
254
|
+
self.send_header("Access-Control-Allow-Origin", "*", headers_list)
|
|
255
|
+
|
|
248
256
|
self.send_header(
|
|
249
257
|
"Access-Control-Allow-Headers",
|
|
250
258
|
"Origin, X-Requested-With, Content-Type, Accept, Authorization",
|
|
251
259
|
headers_list,
|
|
252
260
|
)
|
|
253
|
-
self.send_header("Access-Control-Allow-Credentials", "true", headers_list)
|
|
254
261
|
self.send_header("Connection", "Keep-Alive", headers_list)
|
|
255
262
|
self.send_header("Keep-Alive", "timeout=5, max=30", headers_list)
|
|
256
263
|
self.send_header("Timing-Allow-Origin", "*", headers_list)
|
|
257
264
|
|
|
265
|
+
# Security headers
|
|
266
|
+
self.send_header("X-Content-Type-Options", "nosniff", headers_list)
|
|
267
|
+
self.send_header("X-Frame-Options", "SAMEORIGIN", headers_list)
|
|
268
|
+
|
|
258
269
|
@staticmethod
|
|
259
270
|
async def get_headers(header_lines: list, protocol: str, status_code: int) -> bytes:
|
|
260
271
|
"""
|
|
@@ -307,14 +318,19 @@ class Webserver:
|
|
|
307
318
|
# ------------------------------------------------------------------
|
|
308
319
|
if method == "OPTIONS":
|
|
309
320
|
headers = []
|
|
310
|
-
|
|
321
|
+
# Reflect Origin for credentialed preflight, otherwise wildcard
|
|
322
|
+
origin = self.lowercase_headers.get("origin", "")
|
|
323
|
+
if origin:
|
|
324
|
+
self.send_header("Access-Control-Allow-Origin", origin, headers)
|
|
325
|
+
self.send_header("Access-Control-Allow-Credentials", "true", headers)
|
|
326
|
+
else:
|
|
327
|
+
self.send_header("Access-Control-Allow-Origin", "*", headers)
|
|
311
328
|
self.send_header("Access-Control-Allow-Methods", "GET, POST, PUT, PATCH, DELETE, OPTIONS", headers)
|
|
312
329
|
self.send_header(
|
|
313
330
|
"Access-Control-Allow-Headers",
|
|
314
331
|
"Origin, X-Requested-With, Content-Type, Accept, Authorization",
|
|
315
332
|
headers,
|
|
316
333
|
)
|
|
317
|
-
self.send_header("Access-Control-Allow-Credentials", "true", headers)
|
|
318
334
|
|
|
319
335
|
headers_bytes = await self.get_headers(headers, self.response_protocol, HTTP_OK)
|
|
320
336
|
class _Tina4Response:
|
|
@@ -434,11 +450,19 @@ class Webserver:
|
|
|
434
450
|
self.send_header("Content-Type", response.content_type or "text/html", headers)
|
|
435
451
|
await self.send_basic_headers(headers)
|
|
436
452
|
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
453
|
+
# Preserve session cookie (only if session was used) — must be sent
|
|
454
|
+
# even on redirects so the browser keeps the session across the redirect
|
|
455
|
+
session_name = os.getenv("TINA4_SESSION", "PY_SESS")
|
|
456
|
+
session_activated = not hasattr(self.session, 'activated') or self.session.activated
|
|
457
|
+
if session_activated and session_name in self.cookies:
|
|
458
|
+
# Add Secure flag when behind TLS (reverse proxy or direct HTTPS)
|
|
459
|
+
proto = self.lowercase_headers.get("x-forwarded-proto", "http")
|
|
460
|
+
secure_flag = "; Secure" if proto == "https" else ""
|
|
461
|
+
self.send_header(
|
|
462
|
+
"Set-Cookie",
|
|
463
|
+
f"{session_name}={self.cookies[session_name]}; Path=/; HttpOnly; SameSite=Lax{secure_flag}",
|
|
464
|
+
headers,
|
|
465
|
+
)
|
|
442
466
|
|
|
443
467
|
# Custom headers from route
|
|
444
468
|
for name, value in response.headers.items():
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tina4_python-0.2.194 → tina4_python-0.2.197}/tina4_python/public/js/reconnecting-websocket.js
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tina4_python-0.2.194 → tina4_python-0.2.197}/tina4_python/public/swagger/oauth2-redirect.html
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tina4_python-0.2.194 → tina4_python-0.2.197}/tina4_python/translations/af/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-0.2.194 → tina4_python-0.2.197}/tina4_python/translations/af/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
{tina4_python-0.2.194 → tina4_python-0.2.197}/tina4_python/translations/en/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-0.2.194 → tina4_python-0.2.197}/tina4_python/translations/en/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
{tina4_python-0.2.194 → tina4_python-0.2.197}/tina4_python/translations/es/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-0.2.194 → tina4_python-0.2.197}/tina4_python/translations/es/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
{tina4_python-0.2.194 → tina4_python-0.2.197}/tina4_python/translations/fr/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-0.2.194 → tina4_python-0.2.197}/tina4_python/translations/fr/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
{tina4_python-0.2.194 → tina4_python-0.2.197}/tina4_python/translations/ja/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-0.2.194 → tina4_python-0.2.197}/tina4_python/translations/ja/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
{tina4_python-0.2.194 → tina4_python-0.2.197}/tina4_python/translations/zh/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-0.2.194 → tina4_python-0.2.197}/tina4_python/translations/zh/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|