analytics-platform 0.3.8__tar.gz → 0.3.9__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.
- {analytics_platform-0.3.8 → analytics_platform-0.3.9}/PKG-INFO +5 -1
- {analytics_platform-0.3.8 → analytics_platform-0.3.9}/analytics_platform/core.py +14 -9
- {analytics_platform-0.3.8 → analytics_platform-0.3.9}/analytics_platform/proxy.py +17 -2
- {analytics_platform-0.3.8 → analytics_platform-0.3.9}/analytics_platform.egg-info/PKG-INFO +5 -1
- {analytics_platform-0.3.8 → analytics_platform-0.3.9}/analytics_platform.egg-info/SOURCES.txt +2 -0
- {analytics_platform-0.3.8 → analytics_platform-0.3.9}/pyproject.toml +7 -1
- analytics_platform-0.3.9/tests/test_headers.py +28 -0
- analytics_platform-0.3.9/tests/test_proxy_answer.py +27 -0
- {analytics_platform-0.3.8 → analytics_platform-0.3.9}/LICENSE +0 -0
- {analytics_platform-0.3.8 → analytics_platform-0.3.9}/README.md +0 -0
- {analytics_platform-0.3.8 → analytics_platform-0.3.9}/analytics_platform/__init__.py +0 -0
- {analytics_platform-0.3.8 → analytics_platform-0.3.9}/analytics_platform/_env.py +0 -0
- {analytics_platform-0.3.8 → analytics_platform-0.3.9}/analytics_platform/analytics.py +0 -0
- {analytics_platform-0.3.8 → analytics_platform-0.3.9}/analytics_platform/audit.py +0 -0
- {analytics_platform-0.3.8 → analytics_platform-0.3.9}/analytics_platform/calls.py +0 -0
- {analytics_platform-0.3.8 → analytics_platform-0.3.9}/analytics_platform/celery.py +0 -0
- {analytics_platform-0.3.8 → analytics_platform-0.3.9}/analytics_platform/challenge.py +0 -0
- {analytics_platform-0.3.8 → analytics_platform-0.3.9}/analytics_platform/crawlers.py +0 -0
- {analytics_platform-0.3.8 → analytics_platform-0.3.9}/analytics_platform/django.py +0 -0
- {analytics_platform-0.3.8 → analytics_platform-0.3.9}/analytics_platform/dramatiq.py +0 -0
- {analytics_platform-0.3.8 → analytics_platform-0.3.9}/analytics_platform/errors.py +0 -0
- {analytics_platform-0.3.8 → analytics_platform-0.3.9}/analytics_platform/firewall.py +0 -0
- {analytics_platform-0.3.8 → analytics_platform-0.3.9}/analytics_platform/flags.py +0 -0
- {analytics_platform-0.3.8 → analytics_platform-0.3.9}/analytics_platform/forms.py +0 -0
- {analytics_platform-0.3.8 → analytics_platform-0.3.9}/analytics_platform/inventory.py +0 -0
- {analytics_platform-0.3.8 → analytics_platform-0.3.9}/analytics_platform/maintenance.py +0 -0
- {analytics_platform-0.3.8 → analytics_platform-0.3.9}/analytics_platform/performance.py +0 -0
- {analytics_platform-0.3.8 → analytics_platform-0.3.9}/analytics_platform/posture.py +0 -0
- {analytics_platform-0.3.8 → analytics_platform-0.3.9}/analytics_platform/queries.py +0 -0
- {analytics_platform-0.3.8 → analytics_platform-0.3.9}/analytics_platform/queues.py +0 -0
- {analytics_platform-0.3.8 → analytics_platform-0.3.9}/analytics_platform/request_rules.py +0 -0
- {analytics_platform-0.3.8 → analytics_platform-0.3.9}/analytics_platform/resources.py +0 -0
- {analytics_platform-0.3.8 → analytics_platform-0.3.9}/analytics_platform/rq.py +0 -0
- {analytics_platform-0.3.8 → analytics_platform-0.3.9}/analytics_platform/start.py +0 -0
- {analytics_platform-0.3.8 → analytics_platform-0.3.9}/analytics_platform.egg-info/dependency_links.txt +0 -0
- {analytics_platform-0.3.8 → analytics_platform-0.3.9}/analytics_platform.egg-info/top_level.txt +0 -0
- {analytics_platform-0.3.8 → analytics_platform-0.3.9}/setup.cfg +0 -0
- {analytics_platform-0.3.8 → analytics_platform-0.3.9}/tests/test_calls_queues.py +0 -0
- {analytics_platform-0.3.8 → analytics_platform-0.3.9}/tests/test_celery.py +0 -0
- {analytics_platform-0.3.8 → analytics_platform-0.3.9}/tests/test_challenge.py +0 -0
- {analytics_platform-0.3.8 → analytics_platform-0.3.9}/tests/test_checkin.py +0 -0
- {analytics_platform-0.3.8 → analytics_platform-0.3.9}/tests/test_crawlers.py +0 -0
- {analytics_platform-0.3.8 → analytics_platform-0.3.9}/tests/test_errors.py +0 -0
- {analytics_platform-0.3.8 → analytics_platform-0.3.9}/tests/test_firewall.py +0 -0
- {analytics_platform-0.3.8 → analytics_platform-0.3.9}/tests/test_hardening.py +0 -0
- {analytics_platform-0.3.8 → analytics_platform-0.3.9}/tests/test_limits.py +0 -0
- {analytics_platform-0.3.8 → analytics_platform-0.3.9}/tests/test_page_limits.py +0 -0
- {analytics_platform-0.3.8 → analytics_platform-0.3.9}/tests/test_proxy.py +0 -0
- {analytics_platform-0.3.8 → analytics_platform-0.3.9}/tests/test_queries.py +0 -0
- {analytics_platform-0.3.8 → analytics_platform-0.3.9}/tests/test_redis.py +0 -0
- {analytics_platform-0.3.8 → analytics_platform-0.3.9}/tests/test_request_rules.py +0 -0
- {analytics_platform-0.3.8 → analytics_platform-0.3.9}/tests/test_resources.py +0 -0
- {analytics_platform-0.3.8 → analytics_platform-0.3.9}/tests/test_rq_dramatiq.py +0 -0
- {analytics_platform-0.3.8 → analytics_platform-0.3.9}/tests/test_start.py +0 -0
- {analytics_platform-0.3.8 → analytics_platform-0.3.9}/tests/test_static_late.py +0 -0
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: analytics-platform
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.9
|
|
4
4
|
Summary: Firewall, error monitoring and server analytics for your Python server, run from your analytics dashboard.
|
|
5
5
|
Author: steviec87
|
|
6
6
|
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://sitegoose.com
|
|
8
|
+
Project-URL: Source, https://github.com/StevieC87/Sitegoose-Packages/tree/main/platform-python
|
|
9
|
+
Project-URL: Issues, https://github.com/StevieC87/Sitegoose-Packages/issues
|
|
10
|
+
Project-URL: Security, https://sitegoose.com/security
|
|
7
11
|
Keywords: firewall,waf,rate-limit,error-monitoring,analytics,server-monitoring,django,flask,fastapi,celery,rq,dramatiq,wsgi,asgi
|
|
8
12
|
Classifier: Development Status :: 4 - Beta
|
|
9
13
|
Classifier: Intended Audience :: Developers
|
|
@@ -23,7 +23,7 @@ import urllib.error
|
|
|
23
23
|
import urllib.parse
|
|
24
24
|
import urllib.request
|
|
25
25
|
|
|
26
|
-
VERSION = "0.3.
|
|
26
|
+
VERSION = "0.3.9"
|
|
27
27
|
TIMEOUT = 5
|
|
28
28
|
MIN_SYNC = 30
|
|
29
29
|
RETRY = 60
|
|
@@ -285,19 +285,24 @@ CLIENTS = ("python", "django")
|
|
|
285
285
|
|
|
286
286
|
|
|
287
287
|
|
|
288
|
-
# A dashboard header is added only when it is
|
|
289
|
-
#
|
|
290
|
-
#
|
|
291
|
-
#
|
|
292
|
-
#
|
|
293
|
-
|
|
294
|
-
|
|
288
|
+
# A dashboard header is added only when it is one of the nine the dashboard
|
|
289
|
+
# can produce (expresscollect/Analytics/platform.js, headersOf), in any case,
|
|
290
|
+
# with a value of visible ASCII, spaces and tabs, so no line break, control
|
|
291
|
+
# character or byte a server rejects gets through. A list of what's allowed,
|
|
292
|
+
# not of what isn't: whoever got into our servers still couldn't make this
|
|
293
|
+
# app grant another site its users' data (Access-Control-Allow-*), send every
|
|
294
|
+
# page elsewhere (Refresh), log everyone out (Clear-Site-Data) or change what
|
|
295
|
+
# a response is. Shared cases: sdk/protocols/header-cases.json.
|
|
296
|
+
SECURITY_HEADERS = frozenset((
|
|
297
|
+
"strict-transport-security", "x-content-type-options", "x-frame-options", "referrer-policy",
|
|
298
|
+
"permissions-policy", "cross-origin-opener-policy", "content-security-policy",
|
|
299
|
+
"content-security-policy-report-only", "reporting-endpoints"))
|
|
295
300
|
_HEADER_VALUE = re.compile(r"[\x20-\x7e\t]*")
|
|
296
301
|
|
|
297
302
|
|
|
298
303
|
def safe_header(pair):
|
|
299
304
|
return (isinstance(pair, (list, tuple)) and len(pair) == 2 and isinstance(pair[0], str)
|
|
300
|
-
and
|
|
305
|
+
and pair[0].lower() in SECURITY_HEADERS
|
|
301
306
|
and isinstance(pair[1], str) and len(pair[1]) <= 4000 and _HEADER_VALUE.fullmatch(pair[1]) is not None)
|
|
302
307
|
|
|
303
308
|
class Core:
|
|
@@ -49,6 +49,21 @@ PASS_HEADERS = ("content-type", "user-agent", "accept-language", "referer", "ori
|
|
|
49
49
|
"sec-fetch-site", "sec-ch-ua", "sec-ch-ua-mobile", "sec-ch-ua-platform")
|
|
50
50
|
SAFE_PATH = re.compile(r"[A-Za-z0-9/_.-]{1,120}\Z")
|
|
51
51
|
FILE_PATH = re.compile(r"v(\d+(?:\.\d+\.\d+)?)/([a-z]+\.js)\Z")
|
|
52
|
+
# What a collector answer may be served as from the site's own domain. Its
|
|
53
|
+
# type comes from the collector, so anything but these three becomes plain
|
|
54
|
+
# text, and nosniff stops a browser guessing: a server that is no longer ours
|
|
55
|
+
# still can't answer <prefix>/api/... with a page that runs as the customer's
|
|
56
|
+
# own site. Shared cases: sdk/protocols/proxy-answer-cases.json.
|
|
57
|
+
ANSWER_TYPES = frozenset(("application/json", "text/plain", "image/webp"))
|
|
58
|
+
_VISIBLE = re.compile(r"[\x20-\x7e]*")
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def answer_type(value):
|
|
62
|
+
value = value if isinstance(value, str) else ""
|
|
63
|
+
kind = value.split(";", 1)[0].strip().lower()
|
|
64
|
+
return value if kind in ANSWER_TYPES and _VISIBLE.fullmatch(value) else "text/plain; charset=utf-8"
|
|
65
|
+
|
|
66
|
+
|
|
52
67
|
NOT_FOUND = (404, [("Content-Type", "text/plain; charset=utf-8")], b"Not found")
|
|
53
68
|
|
|
54
69
|
|
|
@@ -236,8 +251,8 @@ class Proxy:
|
|
|
236
251
|
|
|
237
252
|
@staticmethod
|
|
238
253
|
def _answer_headers(headers):
|
|
239
|
-
out = []
|
|
240
|
-
for name in ("
|
|
254
|
+
out = [("Content-Type", answer_type(headers.get("Content-Type"))), ("X-Content-Type-Options", "nosniff")]
|
|
255
|
+
for name in ("Cache-Control", "Retry-After"):
|
|
241
256
|
value = headers.get(name)
|
|
242
257
|
if value:
|
|
243
258
|
out.append((name, value))
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: analytics-platform
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.9
|
|
4
4
|
Summary: Firewall, error monitoring and server analytics for your Python server, run from your analytics dashboard.
|
|
5
5
|
Author: steviec87
|
|
6
6
|
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://sitegoose.com
|
|
8
|
+
Project-URL: Source, https://github.com/StevieC87/Sitegoose-Packages/tree/main/platform-python
|
|
9
|
+
Project-URL: Issues, https://github.com/StevieC87/Sitegoose-Packages/issues
|
|
10
|
+
Project-URL: Security, https://sitegoose.com/security
|
|
7
11
|
Keywords: firewall,waf,rate-limit,error-monitoring,analytics,server-monitoring,django,flask,fastapi,celery,rq,dramatiq,wsgi,asgi
|
|
8
12
|
Classifier: Development Status :: 4 - Beta
|
|
9
13
|
Classifier: Intended Audience :: Developers
|
{analytics_platform-0.3.8 → analytics_platform-0.3.9}/analytics_platform.egg-info/SOURCES.txt
RENAMED
|
@@ -39,9 +39,11 @@ tests/test_crawlers.py
|
|
|
39
39
|
tests/test_errors.py
|
|
40
40
|
tests/test_firewall.py
|
|
41
41
|
tests/test_hardening.py
|
|
42
|
+
tests/test_headers.py
|
|
42
43
|
tests/test_limits.py
|
|
43
44
|
tests/test_page_limits.py
|
|
44
45
|
tests/test_proxy.py
|
|
46
|
+
tests/test_proxy_answer.py
|
|
45
47
|
tests/test_queries.py
|
|
46
48
|
tests/test_redis.py
|
|
47
49
|
tests/test_request_rules.py
|
|
@@ -2,9 +2,15 @@
|
|
|
2
2
|
requires = ["setuptools>=77"]
|
|
3
3
|
build-backend = "setuptools.build_meta"
|
|
4
4
|
|
|
5
|
+
[project.urls]
|
|
6
|
+
Homepage = "https://sitegoose.com"
|
|
7
|
+
Source = "https://github.com/StevieC87/Sitegoose-Packages/tree/main/platform-python"
|
|
8
|
+
Issues = "https://github.com/StevieC87/Sitegoose-Packages/issues"
|
|
9
|
+
Security = "https://sitegoose.com/security"
|
|
10
|
+
|
|
5
11
|
[project]
|
|
6
12
|
name = "analytics-platform"
|
|
7
|
-
version = "0.3.
|
|
13
|
+
version = "0.3.9"
|
|
8
14
|
description = "Firewall, error monitoring and server analytics for your Python server, run from your analytics dashboard."
|
|
9
15
|
readme = "README.md"
|
|
10
16
|
requires-python = ">=3.8"
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""The security headers taken from the platform config, against the cases every
|
|
2
|
+
client shares: sdk/protocols/header-cases.json. Only the nine the dashboard can
|
|
3
|
+
produce get through, so all the packages add the same ones."""
|
|
4
|
+
import json
|
|
5
|
+
import os
|
|
6
|
+
import unittest
|
|
7
|
+
|
|
8
|
+
from analytics_platform.core import safe_header
|
|
9
|
+
|
|
10
|
+
CASES = os.path.join(os.path.dirname(__file__), "..", "..", "protocols", "header-cases.json")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class HeaderCases(unittest.TestCase):
|
|
14
|
+
def test_shared_cases(self):
|
|
15
|
+
with open(CASES, encoding="utf-8") as f:
|
|
16
|
+
cases = json.load(f)["cases"]
|
|
17
|
+
for case in cases:
|
|
18
|
+
value = case["value"] * case.get("repeat", 1)
|
|
19
|
+
with self.subTest(name=case["name"], value=case["value"][:40], repeat=case.get("repeat")):
|
|
20
|
+
self.assertEqual(safe_header([case["name"], value]), case["ok"])
|
|
21
|
+
|
|
22
|
+
def test_only_pairs_of_strings(self):
|
|
23
|
+
for pair in (None, "X-Frame-Options", ["X-Frame-Options"], ["X-Frame-Options", 1], ["X-Frame-Options", "DENY", "x"]):
|
|
24
|
+
self.assertFalse(safe_header(pair))
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
if __name__ == "__main__":
|
|
28
|
+
unittest.main()
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""The type a collector answer is served with from the site's own domain,
|
|
2
|
+
against the cases every client shares: sdk/protocols/proxy-answer-cases.json."""
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
import unittest
|
|
6
|
+
|
|
7
|
+
from analytics_platform.proxy import Proxy, answer_type
|
|
8
|
+
|
|
9
|
+
CASES = os.path.join(os.path.dirname(__file__), "..", "..", "protocols", "proxy-answer-cases.json")
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ProxyAnswerCases(unittest.TestCase):
|
|
13
|
+
def test_shared_cases(self):
|
|
14
|
+
with open(CASES, encoding="utf-8") as f:
|
|
15
|
+
cases = json.load(f)["cases"]
|
|
16
|
+
for case in cases:
|
|
17
|
+
with self.subTest(type=case["type"]):
|
|
18
|
+
self.assertEqual(answer_type(case["type"]), case["served"])
|
|
19
|
+
|
|
20
|
+
def test_nosniff_and_only_cache_and_retry(self):
|
|
21
|
+
upstream = {"Content-Type": "text/html", "Cache-Control": "no-store", "Set-Cookie": "a=b", "Refresh": "0;url=x"}
|
|
22
|
+
self.assertEqual(Proxy._answer_headers(upstream), [
|
|
23
|
+
("Content-Type", "text/plain; charset=utf-8"), ("X-Content-Type-Options", "nosniff"), ("Cache-Control", "no-store")])
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
if __name__ == "__main__":
|
|
27
|
+
unittest.main()
|
|
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
|
{analytics_platform-0.3.8 → analytics_platform-0.3.9}/analytics_platform.egg-info/top_level.txt
RENAMED
|
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
|