allowly 0.2.0__tar.gz → 0.3.0__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.
- {allowly-0.2.0 → allowly-0.3.0}/PKG-INFO +9 -4
- {allowly-0.2.0 → allowly-0.3.0}/README.md +6 -1
- {allowly-0.2.0 → allowly-0.3.0}/allowly/verify.py +4 -2
- {allowly-0.2.0 → allowly-0.3.0}/pyproject.toml +3 -3
- {allowly-0.2.0 → allowly-0.3.0}/tests/test_client.py +3 -3
- {allowly-0.2.0 → allowly-0.3.0}/tests/test_verify.py +33 -9
- {allowly-0.2.0 → allowly-0.3.0}/uv.lock +6 -6
- {allowly-0.2.0 → allowly-0.3.0}/.github/workflows/publish.yml +0 -0
- {allowly-0.2.0 → allowly-0.3.0}/.github/workflows/verify.yml +0 -0
- {allowly-0.2.0 → allowly-0.3.0}/.gitignore +0 -0
- {allowly-0.2.0 → allowly-0.3.0}/LICENSE +0 -0
- {allowly-0.2.0 → allowly-0.3.0}/allowly/__init__.py +0 -0
- {allowly-0.2.0 → allowly-0.3.0}/allowly/client.py +0 -0
- {allowly-0.2.0 → allowly-0.3.0}/allowly/error.py +0 -0
- {allowly-0.2.0 → allowly-0.3.0}/allowly/identifiers.py +0 -0
- {allowly-0.2.0 → allowly-0.3.0}/allowly/mcp.py +0 -0
- {allowly-0.2.0 → allowly-0.3.0}/allowly/types.py +0 -0
- {allowly-0.2.0 → allowly-0.3.0}/tests/test_identifiers.py +0 -0
- {allowly-0.2.0 → allowly-0.3.0}/tests/test_mcp.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: allowly
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Python SDK for the Allowly API
|
|
5
5
|
Project-URL: Repository, https://github.com/Allowly-AI/allowly-sdk-python
|
|
6
6
|
License: MIT
|
|
@@ -8,7 +8,7 @@ License-File: LICENSE
|
|
|
8
8
|
Requires-Python: >=3.10
|
|
9
9
|
Requires-Dist: httpx>=0.27.0
|
|
10
10
|
Provides-Extra: dev
|
|
11
|
-
Requires-Dist: allowly-receipt-format<
|
|
11
|
+
Requires-Dist: allowly-receipt-format<5.0.0,>=4.0.0; extra == 'dev'
|
|
12
12
|
Requires-Dist: cryptography>=42; extra == 'dev'
|
|
13
13
|
Requires-Dist: fastmcp<3,>=2.9; extra == 'dev'
|
|
14
14
|
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
@@ -17,7 +17,7 @@ Requires-Dist: respx>=0.21; extra == 'dev'
|
|
|
17
17
|
Provides-Extra: fastmcp
|
|
18
18
|
Requires-Dist: fastmcp<3,>=2.9; extra == 'fastmcp'
|
|
19
19
|
Provides-Extra: verifier
|
|
20
|
-
Requires-Dist: allowly-receipt-format<
|
|
20
|
+
Requires-Dist: allowly-receipt-format<5.0.0,>=4.0.0; extra == 'verifier'
|
|
21
21
|
Description-Content-Type: text/markdown
|
|
22
22
|
|
|
23
23
|
# Allowly Python SDK
|
|
@@ -112,9 +112,14 @@ integration examples honest and makes SDK gaps visible early.
|
|
|
112
112
|
## Offline receipt verification
|
|
113
113
|
|
|
114
114
|
Install `allowly[verifier]` to verify signed receipts locally. The extra uses
|
|
115
|
-
`allowly-receipt-format>=
|
|
115
|
+
`allowly-receipt-format>=4.0.0,<5.0.0`, which verifies receipt wire format 4 (the package major equals the wire format). `alg` and
|
|
116
116
|
`key_id` are signed top-level fields, and `signature` is the base64url string.
|
|
117
117
|
|
|
118
|
+
Wire format 4 also supports daily `receipt.checkpoint` commitments. For an
|
|
119
|
+
untrusted receipt bundle, pass caller-trusted key fingerprints to
|
|
120
|
+
`verify_receipt`; a fingerprint copied from that same bundle is not a trust
|
|
121
|
+
anchor.
|
|
122
|
+
|
|
118
123
|
Key-document fetching requires HTTPS by default. For the documented local
|
|
119
124
|
Caddy endpoint only, pass `dangerously_allow_insecure_base_url=True` and its
|
|
120
125
|
`edge_token` to `fetch_keys_doc`, matching the client options above.
|
|
@@ -90,9 +90,14 @@ integration examples honest and makes SDK gaps visible early.
|
|
|
90
90
|
## Offline receipt verification
|
|
91
91
|
|
|
92
92
|
Install `allowly[verifier]` to verify signed receipts locally. The extra uses
|
|
93
|
-
`allowly-receipt-format>=
|
|
93
|
+
`allowly-receipt-format>=4.0.0,<5.0.0`, which verifies receipt wire format 4 (the package major equals the wire format). `alg` and
|
|
94
94
|
`key_id` are signed top-level fields, and `signature` is the base64url string.
|
|
95
95
|
|
|
96
|
+
Wire format 4 also supports daily `receipt.checkpoint` commitments. For an
|
|
97
|
+
untrusted receipt bundle, pass caller-trusted key fingerprints to
|
|
98
|
+
`verify_receipt`; a fingerprint copied from that same bundle is not a trust
|
|
99
|
+
anchor.
|
|
100
|
+
|
|
96
101
|
Key-document fetching requires HTTPS by default. For the documented local
|
|
97
102
|
Caddy endpoint only, pass `dangerously_allow_insecure_base_url=True` and its
|
|
98
103
|
`edge_token` to `fetch_keys_doc`, matching the client options above.
|
|
@@ -21,7 +21,7 @@ from typing import Any
|
|
|
21
21
|
from urllib.parse import quote, urlparse
|
|
22
22
|
|
|
23
23
|
# Offline verification is powered by the published reference verifier,
|
|
24
|
-
# allowly-receipt-format
|
|
24
|
+
# allowly-receipt-format 4.x (import path allowly_receipt_format). It ships as an
|
|
25
25
|
# optional extra so the core SDK stays dependency-light:
|
|
26
26
|
# pip install 'allowly[verifier]'
|
|
27
27
|
def _import_verifier():
|
|
@@ -35,7 +35,7 @@ def _import_verifier():
|
|
|
35
35
|
return verify_receipt, load_keys_from_json, VerificationError, PublicKey
|
|
36
36
|
except ImportError as exc:
|
|
37
37
|
raise ImportError(
|
|
38
|
-
"Receipt verification requires allowly-receipt-format>=
|
|
38
|
+
"Receipt verification requires allowly-receipt-format>=4.0.0. "
|
|
39
39
|
"Install the verifier extra: pip install 'allowly[verifier]'"
|
|
40
40
|
) from exc
|
|
41
41
|
|
|
@@ -52,6 +52,7 @@ def verify_receipt(
|
|
|
52
52
|
public_keys: list[PublicKey],
|
|
53
53
|
*,
|
|
54
54
|
expected_workspace_id: str,
|
|
55
|
+
trusted_key_fingerprints: set[str] | frozenset[str] | None = None,
|
|
55
56
|
now: datetime | None = None,
|
|
56
57
|
) -> None:
|
|
57
58
|
_verify_receipt(
|
|
@@ -59,6 +60,7 @@ def verify_receipt(
|
|
|
59
60
|
public_keys,
|
|
60
61
|
now=now,
|
|
61
62
|
expected_workspace_id=expected_workspace_id,
|
|
63
|
+
trusted_key_fingerprints=trusted_key_fingerprints,
|
|
62
64
|
)
|
|
63
65
|
|
|
64
66
|
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "allowly"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.3.0"
|
|
8
8
|
description = "Python SDK for the Allowly API"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -21,10 +21,10 @@ fastmcp = [
|
|
|
21
21
|
"fastmcp>=2.9,<3",
|
|
22
22
|
]
|
|
23
23
|
verifier = [
|
|
24
|
-
"allowly-receipt-format>=
|
|
24
|
+
"allowly-receipt-format>=4.0.0,<5.0.0",
|
|
25
25
|
]
|
|
26
26
|
dev = [
|
|
27
|
-
"allowly-receipt-format>=
|
|
27
|
+
"allowly-receipt-format>=4.0.0,<5.0.0",
|
|
28
28
|
"cryptography>=42",
|
|
29
29
|
"fastmcp>=2.9,<3",
|
|
30
30
|
"pytest>=8.0",
|
|
@@ -59,7 +59,7 @@ def _authorization_payload(**overrides) -> dict:
|
|
|
59
59
|
|
|
60
60
|
|
|
61
61
|
SIGNED_RECEIPT = {
|
|
62
|
-
"schema_version": "
|
|
62
|
+
"schema_version": "4",
|
|
63
63
|
"receipt_id": "rcp_abc",
|
|
64
64
|
"decision": "allow",
|
|
65
65
|
"alg": "Ed25519",
|
|
@@ -1434,7 +1434,7 @@ async def test_receipts_get_pending(client):
|
|
|
1434
1434
|
@pytest.mark.asyncio
|
|
1435
1435
|
async def test_receipts_get_signed(client):
|
|
1436
1436
|
signed = {
|
|
1437
|
-
"schema_version": "
|
|
1437
|
+
"schema_version": "4", "receipt_id": "rcp_abc", "decision": "allow",
|
|
1438
1438
|
"alg": "Ed25519", "key_id": "k", "signature": "sig",
|
|
1439
1439
|
}
|
|
1440
1440
|
respx.get(f"{BASE}/v1/receipts/rcp_abc").mock(return_value=httpx.Response(200, json={
|
|
@@ -1677,7 +1677,7 @@ async def test_fetch_signed_default_timeout_covers_one_signer_tick(client, monke
|
|
|
1677
1677
|
|
|
1678
1678
|
monkeypatch.setattr("allowly.client.asyncio.sleep", fake_sleep)
|
|
1679
1679
|
|
|
1680
|
-
signed = {"schema_version": "
|
|
1680
|
+
signed = {"schema_version": "4", "receipt_id": "rcp_abc"}
|
|
1681
1681
|
|
|
1682
1682
|
async def fake_get(receipt_id):
|
|
1683
1683
|
if fake_now >= 61.0:
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import base64
|
|
3
4
|
import hashlib
|
|
4
5
|
|
|
5
6
|
import httpx
|
|
6
7
|
import pytest
|
|
8
|
+
from allowly_receipt_format import canonicalize
|
|
9
|
+
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
|
|
7
10
|
|
|
8
11
|
from allowly.verify import (
|
|
9
12
|
VerificationError,
|
|
@@ -27,21 +30,30 @@ VALID_DOC = {
|
|
|
27
30
|
],
|
|
28
31
|
}
|
|
29
32
|
|
|
30
|
-
|
|
33
|
+
def _b64u(value: bytes) -> str:
|
|
34
|
+
return base64.urlsafe_b64encode(value).rstrip(b"=").decode()
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
_TEST_PRIVATE_KEY = Ed25519PrivateKey.from_private_bytes(bytes(32))
|
|
38
|
+
_TEST_PUBLIC_KEY = _TEST_PRIVATE_KEY.public_key().public_bytes_raw()
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
V4_DOC = {
|
|
31
42
|
"workspace_id": "ws_test",
|
|
32
43
|
"keys": [
|
|
33
44
|
{
|
|
34
45
|
"key_id": "test-key/v1",
|
|
35
46
|
"alg": "Ed25519",
|
|
36
|
-
"public_key":
|
|
47
|
+
"public_key": _b64u(_TEST_PUBLIC_KEY),
|
|
48
|
+
"public_key_fingerprint": f"sha256:{hashlib.sha256(_TEST_PUBLIC_KEY).hexdigest()}",
|
|
37
49
|
"active_from": "2026-01-01T00:00:00.000Z",
|
|
38
50
|
"active_until": None,
|
|
39
51
|
}
|
|
40
52
|
],
|
|
41
53
|
}
|
|
42
54
|
|
|
43
|
-
|
|
44
|
-
"schema_version": "
|
|
55
|
+
V4_RECEIPT = {
|
|
56
|
+
"schema_version": "4",
|
|
45
57
|
"receipt_id": "rcp_01HXZMINIMAL0000000000000",
|
|
46
58
|
"workspace_id": "ws_test",
|
|
47
59
|
"issued_at": "2026-04-21T14:32:17.482Z",
|
|
@@ -56,8 +68,8 @@ V2_RECEIPT = {
|
|
|
56
68
|
"engine_version": "2026-04-17.1",
|
|
57
69
|
"alg": "Ed25519",
|
|
58
70
|
"key_id": "test-key/v1",
|
|
59
|
-
"signature": "O98ntdo49t38E6a2M-19qjaC-2TzTw8tYqOn-fsUAYzfyf0dWTx9uje9NQlkvCl-fP68o_ATkBSW3mpyguoODQ",
|
|
60
71
|
}
|
|
72
|
+
V4_RECEIPT["signature"] = _b64u(_TEST_PRIVATE_KEY.sign(canonicalize(V4_RECEIPT)))
|
|
61
73
|
|
|
62
74
|
|
|
63
75
|
@pytest.fixture(autouse=True)
|
|
@@ -206,14 +218,26 @@ def test_verify_receipt_requires_and_forwards_workspace_binding(monkeypatch):
|
|
|
206
218
|
seen.update(kwargs)
|
|
207
219
|
|
|
208
220
|
monkeypatch.setattr("allowly.verify._verify_receipt", verifier)
|
|
209
|
-
|
|
221
|
+
trusted = {"sha256:" + "0" * 64}
|
|
222
|
+
verify_receipt(
|
|
223
|
+
{},
|
|
224
|
+
[],
|
|
225
|
+
expected_workspace_id="ws_1",
|
|
226
|
+
trusted_key_fingerprints=trusted,
|
|
227
|
+
)
|
|
210
228
|
assert seen["expected_workspace_id"] == "ws_1"
|
|
229
|
+
assert seen["trusted_key_fingerprints"] == trusted
|
|
211
230
|
|
|
212
231
|
|
|
213
|
-
def
|
|
214
|
-
keys = load_keys_from_json(
|
|
232
|
+
def test_verify_v4_receipt_with_signed_algorithm_and_key_id():
|
|
233
|
+
keys = load_keys_from_json(V4_DOC)
|
|
215
234
|
|
|
216
|
-
verify_receipt(
|
|
235
|
+
verify_receipt(
|
|
236
|
+
V4_RECEIPT,
|
|
237
|
+
keys,
|
|
238
|
+
expected_workspace_id="ws_test",
|
|
239
|
+
trusted_key_fingerprints={V4_DOC["keys"][0]["public_key_fingerprint"]},
|
|
240
|
+
)
|
|
217
241
|
|
|
218
242
|
|
|
219
243
|
def test_fetch_keys_doc_encodes_workspace_id():
|
|
@@ -4,7 +4,7 @@ requires-python = ">=3.10"
|
|
|
4
4
|
|
|
5
5
|
[[package]]
|
|
6
6
|
name = "allowly"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.3.0"
|
|
8
8
|
source = { editable = "." }
|
|
9
9
|
dependencies = [
|
|
10
10
|
{ name = "httpx" },
|
|
@@ -28,8 +28,8 @@ verifier = [
|
|
|
28
28
|
|
|
29
29
|
[package.metadata]
|
|
30
30
|
requires-dist = [
|
|
31
|
-
{ name = "allowly-receipt-format", marker = "extra == 'dev'", specifier = ">=
|
|
32
|
-
{ name = "allowly-receipt-format", marker = "extra == 'verifier'", specifier = ">=
|
|
31
|
+
{ name = "allowly-receipt-format", marker = "extra == 'dev'", specifier = ">=4.0.0,<5.0.0" },
|
|
32
|
+
{ name = "allowly-receipt-format", marker = "extra == 'verifier'", specifier = ">=4.0.0,<5.0.0" },
|
|
33
33
|
{ name = "cryptography", marker = "extra == 'dev'", specifier = ">=42" },
|
|
34
34
|
{ name = "fastmcp", marker = "extra == 'dev'", specifier = ">=2.9,<3" },
|
|
35
35
|
{ name = "fastmcp", marker = "extra == 'fastmcp'", specifier = ">=2.9,<3" },
|
|
@@ -42,14 +42,14 @@ provides-extras = ["fastmcp", "verifier", "dev"]
|
|
|
42
42
|
|
|
43
43
|
[[package]]
|
|
44
44
|
name = "allowly-receipt-format"
|
|
45
|
-
version = "
|
|
45
|
+
version = "4.0.0"
|
|
46
46
|
source = { registry = "https://pypi.org/simple" }
|
|
47
47
|
dependencies = [
|
|
48
48
|
{ name = "cryptography" },
|
|
49
49
|
]
|
|
50
|
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
|
50
|
+
sdist = { url = "https://files.pythonhosted.org/packages/86/4d/cd30ade1b597ee0ee1bdbfea33fd6f77c294999f4cc42504152690df13e2/allowly_receipt_format-4.0.0.tar.gz", hash = "sha256:27cd96a06edea1a23c340d9015ef1c0434b6dc1d4b2d7b56653d18b3991d6eec", size = 18059, upload-time = "2026-08-02T07:14:52.971Z" }
|
|
51
51
|
wheels = [
|
|
52
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
52
|
+
{ url = "https://files.pythonhosted.org/packages/60/d9/45545ccdda5810e31d907bb5d26d5c4953864425a79b94708b61fccd23fe/allowly_receipt_format-4.0.0-py3-none-any.whl", hash = "sha256:18a038b9ffe9be25c63bd2f39b1f26fffbebb89e43622de9d2b479b06ea14b93", size = 16630, upload-time = "2026-08-02T07:14:51.615Z" },
|
|
53
53
|
]
|
|
54
54
|
|
|
55
55
|
[[package]]
|
|
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
|