shield-python 0.1.4__py3-none-any.whl → 0.1.6__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.
- shield/__init__.py +1 -1
- shield/client.py +34 -11
- shield/exceptions.py +4 -12
- {shield_python-0.1.4.dist-info → shield_python-0.1.6.dist-info}/METADATA +10 -1
- shield_python-0.1.6.dist-info/RECORD +11 -0
- shield_python-0.1.4.dist-info/RECORD +0 -11
- {shield_python-0.1.4.dist-info → shield_python-0.1.6.dist-info}/WHEEL +0 -0
- {shield_python-0.1.4.dist-info → shield_python-0.1.6.dist-info}/top_level.txt +0 -0
shield/__init__.py
CHANGED
shield/client.py
CHANGED
|
@@ -3,7 +3,7 @@ import hmac as hmac_mod
|
|
|
3
3
|
import json
|
|
4
4
|
import time
|
|
5
5
|
from typing import Any, Dict, Optional
|
|
6
|
-
from urllib.parse import
|
|
6
|
+
from urllib.parse import urlencode
|
|
7
7
|
|
|
8
8
|
import requests
|
|
9
9
|
|
|
@@ -12,6 +12,10 @@ from .resources.sessions import Sessions
|
|
|
12
12
|
from .resources.events import Events
|
|
13
13
|
from .resources.verify import Verify
|
|
14
14
|
|
|
15
|
+
# ARCH-019: kept static to avoid leaking Python version / OS into server logs.
|
|
16
|
+
# Bumped alongside setup.py version on each release.
|
|
17
|
+
SDK_USER_AGENT = "shield-python/0.1.6"
|
|
18
|
+
|
|
15
19
|
|
|
16
20
|
class Client:
|
|
17
21
|
"""Official Shield Python SDK client.
|
|
@@ -64,12 +68,29 @@ class Client:
|
|
|
64
68
|
Raises:
|
|
65
69
|
ShieldError: On non-2xx responses or request failures.
|
|
66
70
|
"""
|
|
67
|
-
url = f"{self.base_url}{path}"
|
|
68
71
|
method = method.upper()
|
|
69
72
|
|
|
73
|
+
# C-1 (v0.1.6): fold query params into `path` BEFORE signing so the
|
|
74
|
+
# canonical message matches what the server validates against
|
|
75
|
+
# http.Request.URL.RequestURI(). Previously `params` was passed to
|
|
76
|
+
# requests.request() separately — requests would append them to the
|
|
77
|
+
# wire URL, but the SDK signed only the bare path, guaranteeing a
|
|
78
|
+
# signature mismatch on any request with query params. We pre-encode
|
|
79
|
+
# here and pass params=None to requests so there is exactly one
|
|
80
|
+
# canonical query string, controlled by this SDK, for both signing
|
|
81
|
+
# and transmission. doseq=True matches requests' own encoding for
|
|
82
|
+
# list-valued params (?tag=a&tag=b).
|
|
83
|
+
if params:
|
|
84
|
+
path_with_query = f"{path}?{urlencode(params, doseq=True)}"
|
|
85
|
+
else:
|
|
86
|
+
path_with_query = path
|
|
87
|
+
|
|
88
|
+
url = f"{self.base_url}{path_with_query}"
|
|
89
|
+
|
|
70
90
|
headers = {
|
|
71
91
|
"X-Shield-Key": self.api_key,
|
|
72
92
|
"Content-Type": "application/json",
|
|
93
|
+
"User-Agent": SDK_USER_AGENT,
|
|
73
94
|
}
|
|
74
95
|
|
|
75
96
|
# Serialize body
|
|
@@ -83,7 +104,7 @@ class Client:
|
|
|
83
104
|
timestamp = str(int(time.time()))
|
|
84
105
|
nonce = str(uuid.uuid4())
|
|
85
106
|
body_hash = hashlib.sha256(body).hexdigest()
|
|
86
|
-
message = f"{timestamp}.{method}.{
|
|
107
|
+
message = f"{timestamp}.{method}.{path_with_query}.{body_hash}"
|
|
87
108
|
signature = hmac_mod.new(
|
|
88
109
|
self.hmac_secret.encode("utf-8"),
|
|
89
110
|
message.encode("utf-8"),
|
|
@@ -99,29 +120,31 @@ class Client:
|
|
|
99
120
|
url=url,
|
|
100
121
|
headers=headers,
|
|
101
122
|
data=body if body else None,
|
|
102
|
-
params=
|
|
123
|
+
params=None,
|
|
103
124
|
timeout=self.timeout,
|
|
104
125
|
)
|
|
105
126
|
except requests.RequestException as e:
|
|
106
127
|
raise ShieldError(
|
|
107
128
|
message=f"Request failed: {str(e)}",
|
|
108
129
|
status_code=0,
|
|
109
|
-
code="request_error",
|
|
110
130
|
)
|
|
111
131
|
|
|
112
132
|
if not (200 <= response.status_code < 300):
|
|
113
|
-
|
|
114
|
-
|
|
133
|
+
# Backend returns {"error": "..."} (always) and sometimes
|
|
134
|
+
# {"message": "..."} with extra detail. Prefer message when present.
|
|
135
|
+
error_message = response.text or response.reason
|
|
115
136
|
try:
|
|
116
137
|
error_body = response.json()
|
|
117
|
-
error_message =
|
|
118
|
-
|
|
119
|
-
|
|
138
|
+
error_message = (
|
|
139
|
+
error_body.get("message")
|
|
140
|
+
or error_body.get("error")
|
|
141
|
+
or error_message
|
|
142
|
+
)
|
|
143
|
+
except ValueError:
|
|
120
144
|
pass
|
|
121
145
|
raise ShieldError(
|
|
122
146
|
message=error_message,
|
|
123
147
|
status_code=response.status_code,
|
|
124
|
-
code=error_code,
|
|
125
148
|
)
|
|
126
149
|
|
|
127
150
|
if raw_response:
|
shield/exceptions.py
CHANGED
|
@@ -1,23 +1,15 @@
|
|
|
1
1
|
class ShieldError(Exception):
|
|
2
2
|
"""Base exception for Shield SDK errors."""
|
|
3
3
|
|
|
4
|
-
def __init__(self, message: str, status_code: int = None
|
|
4
|
+
def __init__(self, message: str, status_code: int = None):
|
|
5
5
|
super().__init__(message)
|
|
6
6
|
self.message = message
|
|
7
7
|
self.status_code = status_code
|
|
8
|
-
self.code = code
|
|
9
8
|
|
|
10
9
|
def __str__(self):
|
|
11
|
-
parts = []
|
|
12
10
|
if self.status_code:
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
parts.append(f"({self.code})")
|
|
16
|
-
parts.append(self.message)
|
|
17
|
-
return " ".join(parts)
|
|
11
|
+
return f"[{self.status_code}] {self.message}"
|
|
12
|
+
return self.message
|
|
18
13
|
|
|
19
14
|
def __repr__(self):
|
|
20
|
-
return (
|
|
21
|
-
f"ShieldError(message={self.message!r}, "
|
|
22
|
-
f"status_code={self.status_code!r}, code={self.code!r})"
|
|
23
|
-
)
|
|
15
|
+
return f"ShieldError(message={self.message!r}, status_code={self.status_code!r})"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: shield-python
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.6
|
|
4
4
|
Summary: Official Shield SDK for Python
|
|
5
5
|
License: MIT
|
|
6
6
|
Project-URL: Homepage, https://getshield.dev
|
|
@@ -264,3 +264,12 @@ Shield Standard Event Taxonomy v1.0 ??37 event types across 7 categories:
|
|
|
264
264
|
| `shield.evidence.exported` | Evidence was exported |
|
|
265
265
|
| `shield.evidence.verified` | Evidence was verified |
|
|
266
266
|
| `shield.evidence.tampered_detected` | Evidence tampering was detected |
|
|
267
|
+
|
|
268
|
+
## Versioning & API compatibility
|
|
269
|
+
|
|
270
|
+
This SDK follows [Semantic Versioning](https://semver.org/).
|
|
271
|
+
|
|
272
|
+
- **Pre-1.0** (current): minor-version bumps may ship breaking changes. Pin the full version in `requirements.txt`.
|
|
273
|
+
- **1.0 and later**: the public API is stable within a major version. Breaking changes require a major-version bump.
|
|
274
|
+
|
|
275
|
+
The Shield HTTP API is versioned at the URL path (`/api/v1`). This SDK targets `/api/v1` and will not transparently follow a server-side version bump — a new server major version will be delivered as a new SDK major version so callers opt in explicitly.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
shield/__init__.py,sha256=Uktse_UZlAOJnYVIKLKzeXFL6kiSJEEqNy0BMDHIYM4,122
|
|
2
|
+
shield/client.py,sha256=1bVkTyvgtCQX4vxpAOPTFE6z5OEPMHoKR4PHw90l0wE,5281
|
|
3
|
+
shield/exceptions.py,sha256=GgC2gtEvgCTxqekJyu7BJdHGi3sY1NzGWOEWkARpci8,500
|
|
4
|
+
shield/resources/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
|
+
shield/resources/events.py,sha256=uH0TG235kRsoS6sOp-zPIcy5WbbtaPNAhC8aFMy00Po,1093
|
|
6
|
+
shield/resources/sessions.py,sha256=qOnJlpCx2Zk5nEh5TCFkf6v05l4M5SKAeF7klQ9YCQM,1268
|
|
7
|
+
shield/resources/verify.py,sha256=vGN51EWU56jOl8BVfNlfiP0ivkNvkLykicupGIGcpAw,498
|
|
8
|
+
shield_python-0.1.6.dist-info/METADATA,sha256=GvbinXKIOVhxlBz5irtD6pcFvadRMy_qwd8hPw2F-AI,8641
|
|
9
|
+
shield_python-0.1.6.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
10
|
+
shield_python-0.1.6.dist-info/top_level.txt,sha256=XumpIzJqChucal6arFU8u2d4ua7RA9yPcBSJFUi0JDU,7
|
|
11
|
+
shield_python-0.1.6.dist-info/RECORD,,
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
shield/__init__.py,sha256=nXw1gar8DlzsZvb057b9Z3Gn4Fuudow06cbyizD4yfM,122
|
|
2
|
-
shield/client.py,sha256=bTt-QylDQ92sIVBo3Q5aBe9aCDNmWmlQDYby8s5dGl4,4091
|
|
3
|
-
shield/exceptions.py,sha256=PunpNnkqyP-9ZhLwPAwH4ZqpxL6rg_KxwaQi9_hdo9I,717
|
|
4
|
-
shield/resources/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
|
-
shield/resources/events.py,sha256=uH0TG235kRsoS6sOp-zPIcy5WbbtaPNAhC8aFMy00Po,1093
|
|
6
|
-
shield/resources/sessions.py,sha256=qOnJlpCx2Zk5nEh5TCFkf6v05l4M5SKAeF7klQ9YCQM,1268
|
|
7
|
-
shield/resources/verify.py,sha256=vGN51EWU56jOl8BVfNlfiP0ivkNvkLykicupGIGcpAw,498
|
|
8
|
-
shield_python-0.1.4.dist-info/METADATA,sha256=fUMubnzO_KaApcKE6hLQJzbgveIocWYNwO_v_itkXYI,8044
|
|
9
|
-
shield_python-0.1.4.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
10
|
-
shield_python-0.1.4.dist-info/top_level.txt,sha256=XumpIzJqChucal6arFU8u2d4ua7RA9yPcBSJFUi0JDU,7
|
|
11
|
-
shield_python-0.1.4.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|