otdf-python 0.4.0__py3-none-any.whl → 0.4.2__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.
- otdf_python/__init__.py +1 -2
- otdf_python/__main__.py +1 -2
- otdf_python/address_normalizer.py +8 -10
- otdf_python/aesgcm.py +8 -0
- otdf_python/assertion_config.py +21 -0
- otdf_python/asym_crypto.py +18 -22
- otdf_python/auth_headers.py +7 -6
- otdf_python/autoconfigure_utils.py +21 -7
- otdf_python/cli.py +5 -5
- otdf_python/collection_store.py +13 -1
- otdf_python/collection_store_impl.py +5 -0
- otdf_python/config.py +13 -0
- otdf_python/connect_client.py +1 -0
- otdf_python/constants.py +2 -0
- otdf_python/crypto_utils.py +4 -0
- otdf_python/dpop.py +3 -5
- otdf_python/ecc_constants.py +12 -14
- otdf_python/ecc_mode.py +7 -2
- otdf_python/ecdh.py +24 -31
- otdf_python/eckeypair.py +5 -0
- otdf_python/header.py +5 -0
- otdf_python/invalid_zip_exception.py +6 -2
- otdf_python/kas_client.py +66 -55
- otdf_python/kas_connect_rpc_client.py +75 -38
- otdf_python/kas_info.py +4 -3
- otdf_python/kas_key_cache.py +10 -9
- otdf_python/key_type.py +4 -0
- otdf_python/key_type_constants.py +4 -11
- otdf_python/manifest.py +24 -0
- otdf_python/nanotdf.py +30 -28
- otdf_python/nanotdf_ecdsa_struct.py +5 -11
- otdf_python/nanotdf_type.py +13 -1
- otdf_python/policy_binding_serializer.py +6 -4
- otdf_python/policy_info.py +6 -0
- otdf_python/policy_object.py +8 -0
- otdf_python/policy_stub.py +2 -0
- otdf_python/resource_locator.py +22 -13
- otdf_python/sdk.py +51 -73
- otdf_python/sdk_builder.py +60 -47
- otdf_python/sdk_exceptions.py +11 -1
- otdf_python/symmetric_and_payload_config.py +6 -0
- otdf_python/tdf.py +47 -10
- otdf_python/tdf_reader.py +10 -13
- otdf_python/tdf_writer.py +5 -0
- otdf_python/token_source.py +4 -3
- otdf_python/version.py +5 -0
- otdf_python/zip_reader.py +10 -2
- otdf_python/zip_writer.py +11 -0
- {otdf_python-0.4.0.dist-info → otdf_python-0.4.2.dist-info}/METADATA +3 -2
- {otdf_python-0.4.0.dist-info → otdf_python-0.4.2.dist-info}/RECORD +81 -72
- {otdf_python-0.4.0.dist-info → otdf_python-0.4.2.dist-info}/WHEEL +1 -1
- otdf_python_proto/__init__.py +2 -6
- otdf_python_proto/authorization/__init__.py +10 -0
- otdf_python_proto/authorization/authorization_connect.py +250 -0
- otdf_python_proto/authorization/v2/authorization_connect.py +315 -0
- otdf_python_proto/entityresolution/__init__.py +10 -0
- otdf_python_proto/entityresolution/entity_resolution_connect.py +185 -0
- otdf_python_proto/entityresolution/v2/entity_resolution_connect.py +185 -0
- otdf_python_proto/kas/__init__.py +2 -2
- otdf_python_proto/kas/kas_connect.py +259 -0
- otdf_python_proto/policy/actions/__init__.py +11 -0
- otdf_python_proto/policy/actions/actions_connect.py +380 -0
- otdf_python_proto/policy/attributes/__init__.py +11 -0
- otdf_python_proto/policy/attributes/attributes_connect.py +1310 -0
- otdf_python_proto/policy/kasregistry/__init__.py +11 -0
- otdf_python_proto/policy/kasregistry/key_access_server_registry_connect.py +912 -0
- otdf_python_proto/policy/keymanagement/__init__.py +11 -0
- otdf_python_proto/policy/keymanagement/key_management_connect.py +380 -0
- otdf_python_proto/policy/namespaces/__init__.py +11 -0
- otdf_python_proto/policy/namespaces/namespaces_connect.py +648 -0
- otdf_python_proto/policy/registeredresources/__init__.py +11 -0
- otdf_python_proto/policy/registeredresources/registered_resources_connect.py +770 -0
- otdf_python_proto/policy/resourcemapping/__init__.py +11 -0
- otdf_python_proto/policy/resourcemapping/resource_mapping_connect.py +790 -0
- otdf_python_proto/policy/subjectmapping/__init__.py +11 -0
- otdf_python_proto/policy/subjectmapping/subject_mapping_connect.py +851 -0
- otdf_python_proto/policy/unsafe/__init__.py +11 -0
- otdf_python_proto/policy/unsafe/unsafe_connect.py +705 -0
- otdf_python_proto/wellknownconfiguration/__init__.py +10 -0
- otdf_python_proto/wellknownconfiguration/wellknown_configuration_connect.py +124 -0
- otdf_python_proto/authorization/authorization_pb2_connect.py +0 -191
- otdf_python_proto/authorization/v2/authorization_pb2_connect.py +0 -233
- otdf_python_proto/entityresolution/entity_resolution_pb2_connect.py +0 -149
- otdf_python_proto/entityresolution/v2/entity_resolution_pb2_connect.py +0 -149
- otdf_python_proto/kas/kas_pb2_connect.py +0 -192
- otdf_python_proto/policy/actions/actions_pb2_connect.py +0 -275
- otdf_python_proto/policy/attributes/attributes_pb2_connect.py +0 -863
- otdf_python_proto/policy/kasregistry/key_access_server_registry_pb2_connect.py +0 -611
- otdf_python_proto/policy/keymanagement/key_management_pb2_connect.py +0 -275
- otdf_python_proto/policy/namespaces/namespaces_pb2_connect.py +0 -443
- otdf_python_proto/policy/registeredresources/registered_resources_pb2_connect.py +0 -527
- otdf_python_proto/policy/resourcemapping/resource_mapping_pb2_connect.py +0 -527
- otdf_python_proto/policy/subjectmapping/subject_mapping_pb2_connect.py +0 -569
- otdf_python_proto/policy/unsafe/unsafe_pb2_connect.py +0 -485
- otdf_python_proto/wellknownconfiguration/wellknown_configuration_pb2_connect.py +0 -107
- {otdf_python-0.4.0.dist-info → otdf_python-0.4.2.dist-info}/licenses/LICENSE +0 -0
otdf_python/sdk.py
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Python port of the main SDK class for OpenTDF platform interaction.
|
|
3
|
-
"""
|
|
1
|
+
"""The main SDK class for OpenTDF platform interaction."""
|
|
4
2
|
|
|
5
3
|
from contextlib import AbstractContextManager
|
|
6
4
|
from io import BytesIO
|
|
@@ -13,13 +11,10 @@ from otdf_python.tdf import TDF, TDFReader, TDFReaderConfig
|
|
|
13
11
|
|
|
14
12
|
|
|
15
13
|
class KAS(AbstractContextManager):
|
|
16
|
-
"""
|
|
17
|
-
KAS (Key Access Service) interface to define methods related to key access and management.
|
|
18
|
-
"""
|
|
14
|
+
"""KAS (Key Access Service) interface to define methods related to key access and management."""
|
|
19
15
|
|
|
20
16
|
def get_public_key(self, kas_info: Any) -> Any:
|
|
21
|
-
"""
|
|
22
|
-
Retrieves the public key from the KAS for RSA operations.
|
|
17
|
+
"""Retrieve the public key from KAS for RSA operations.
|
|
23
18
|
If the public key is cached, returns the cached value.
|
|
24
19
|
Otherwise, makes a request to the KAS.
|
|
25
20
|
|
|
@@ -31,6 +26,7 @@ class KAS(AbstractContextManager):
|
|
|
31
26
|
|
|
32
27
|
Raises:
|
|
33
28
|
SDKException: If there's an error retrieving the public key
|
|
29
|
+
|
|
34
30
|
"""
|
|
35
31
|
# Delegate to the underlying KAS client which handles authentication properly
|
|
36
32
|
return self._kas_client.get_public_key(kas_info)
|
|
@@ -42,14 +38,14 @@ class KAS(AbstractContextManager):
|
|
|
42
38
|
sdk_ssl_verify=True,
|
|
43
39
|
use_plaintext=False,
|
|
44
40
|
):
|
|
45
|
-
"""
|
|
46
|
-
Initialize the KAS client
|
|
41
|
+
"""Initialize the KAS client.
|
|
47
42
|
|
|
48
43
|
Args:
|
|
49
44
|
platform_url: URL of the platform
|
|
50
45
|
token_source: Function that returns an authentication token
|
|
51
46
|
sdk_ssl_verify: Whether to verify SSL certificates
|
|
52
47
|
use_plaintext: Whether to use plaintext HTTP connections instead of HTTPS
|
|
48
|
+
|
|
53
49
|
"""
|
|
54
50
|
from .kas_client import KASClient
|
|
55
51
|
|
|
@@ -64,8 +60,7 @@ class KAS(AbstractContextManager):
|
|
|
64
60
|
self._use_plaintext = use_plaintext
|
|
65
61
|
|
|
66
62
|
def get_ec_public_key(self, kas_info: Any, curve: Any) -> Any:
|
|
67
|
-
"""
|
|
68
|
-
Retrieves the EC public key from the KAS.
|
|
63
|
+
"""Retrieve the EC public key from KAS.
|
|
69
64
|
|
|
70
65
|
Args:
|
|
71
66
|
kas_info: KASInfo object containing the URL
|
|
@@ -73,6 +68,7 @@ class KAS(AbstractContextManager):
|
|
|
73
68
|
|
|
74
69
|
Returns:
|
|
75
70
|
Updated KASInfo object with KID and PublicKey populated
|
|
71
|
+
|
|
76
72
|
"""
|
|
77
73
|
# Set algorithm to "ec:<curve>"
|
|
78
74
|
from copy import copy
|
|
@@ -82,8 +78,7 @@ class KAS(AbstractContextManager):
|
|
|
82
78
|
return self.get_public_key(kas_info_copy)
|
|
83
79
|
|
|
84
80
|
def unwrap(self, key_access: Any, policy: str, session_key_type: Any) -> bytes:
|
|
85
|
-
"""
|
|
86
|
-
Unwraps the key using the KAS.
|
|
81
|
+
"""Unwraps the key using the KAS.
|
|
87
82
|
|
|
88
83
|
Args:
|
|
89
84
|
key_access: KeyAccess object containing the wrapped key
|
|
@@ -92,6 +87,7 @@ class KAS(AbstractContextManager):
|
|
|
92
87
|
|
|
93
88
|
Returns:
|
|
94
89
|
Unwrapped key as bytes
|
|
90
|
+
|
|
95
91
|
"""
|
|
96
92
|
return self._kas_client.unwrap(key_access, policy, session_key_type)
|
|
97
93
|
|
|
@@ -104,8 +100,7 @@ class KAS(AbstractContextManager):
|
|
|
104
100
|
kas_private_key: str | None = None,
|
|
105
101
|
mock: bool = False,
|
|
106
102
|
) -> bytes:
|
|
107
|
-
"""
|
|
108
|
-
Unwraps the NanoTDF key using the KAS. If mock=True, performs local unwrap using the private key (for tests).
|
|
103
|
+
"""Unwraps the NanoTDF key using the KAS. If mock=True, performs local unwrap using the private key (for tests).
|
|
109
104
|
|
|
110
105
|
Args:
|
|
111
106
|
curve: EC curve used
|
|
@@ -117,6 +112,7 @@ class KAS(AbstractContextManager):
|
|
|
117
112
|
|
|
118
113
|
Returns:
|
|
119
114
|
Unwrapped key as bytes
|
|
115
|
+
|
|
120
116
|
"""
|
|
121
117
|
if mock and wrapped_key and kas_private_key:
|
|
122
118
|
from .asym_crypto import AsymDecryption
|
|
@@ -128,33 +124,33 @@ class KAS(AbstractContextManager):
|
|
|
128
124
|
raise NotImplementedError("KAS unwrap_nanotdf not implemented.")
|
|
129
125
|
|
|
130
126
|
def get_key_cache(self) -> Any:
|
|
131
|
-
"""
|
|
132
|
-
Returns the KAS key cache.
|
|
127
|
+
"""Return the KAS key cache.
|
|
133
128
|
|
|
134
129
|
Returns:
|
|
135
130
|
The KAS key cache object
|
|
131
|
+
|
|
136
132
|
"""
|
|
137
133
|
return self._kas_client.get_key_cache()
|
|
138
134
|
|
|
139
135
|
def close(self):
|
|
140
|
-
"""
|
|
141
|
-
|
|
136
|
+
"""Close resources associated with KAS interface."""
|
|
137
|
+
if self._kas_client:
|
|
138
|
+
self._kas_client.close()
|
|
142
139
|
|
|
143
140
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
144
141
|
self.close()
|
|
145
142
|
|
|
146
143
|
|
|
147
144
|
class SDK(AbstractContextManager):
|
|
145
|
+
"""SDK for OpenTDF platform interaction."""
|
|
146
|
+
|
|
148
147
|
def new_tdf_config(
|
|
149
148
|
self,
|
|
150
149
|
attributes: list[str] | None = None,
|
|
151
150
|
kas_info_list: list[KASInfo] | None = None,
|
|
152
151
|
**kwargs,
|
|
153
152
|
) -> TDFConfig:
|
|
154
|
-
"""
|
|
155
|
-
Create a TDFConfig with default kas_info_list from the SDK's platform_url.
|
|
156
|
-
"""
|
|
157
|
-
|
|
153
|
+
"""Create a TDFConfig with default kas_info_list from the SDK's platform_url."""
|
|
158
154
|
if self.platform_url is None:
|
|
159
155
|
raise SDKException("Cannot create TDFConfig: SDK platform_url is not set.")
|
|
160
156
|
|
|
@@ -214,20 +210,16 @@ class SDK(AbstractContextManager):
|
|
|
214
210
|
"""
|
|
215
211
|
|
|
216
212
|
class Services(AbstractContextManager):
|
|
217
|
-
"""
|
|
218
|
-
The Services interface provides access to various platform service clients and KAS.
|
|
219
|
-
"""
|
|
213
|
+
"""The Services interface provides access to various platform service clients and KAS."""
|
|
220
214
|
|
|
221
215
|
def kas(self) -> KAS:
|
|
222
|
-
"""
|
|
223
|
-
Returns the KAS client for key access operations.
|
|
216
|
+
"""Return the KAS client for key access operations.
|
|
224
217
|
This should be implemented to return an instance of KAS.
|
|
225
218
|
"""
|
|
226
219
|
raise NotImplementedError
|
|
227
220
|
|
|
228
221
|
def close(self):
|
|
229
|
-
"""
|
|
230
|
-
pass
|
|
222
|
+
"""Close resources associated with the services."""
|
|
231
223
|
|
|
232
224
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
233
225
|
self.close()
|
|
@@ -239,14 +231,14 @@ class SDK(AbstractContextManager):
|
|
|
239
231
|
ssl_verify: bool = True,
|
|
240
232
|
use_plaintext: bool = False,
|
|
241
233
|
):
|
|
242
|
-
"""
|
|
243
|
-
Initializes a new SDK instance.
|
|
234
|
+
"""Initialize a new SDK instance.
|
|
244
235
|
|
|
245
236
|
Args:
|
|
246
237
|
services: The services interface implementation
|
|
247
238
|
platform_url: Optional platform base URL
|
|
248
239
|
ssl_verify: Whether to verify SSL certificates (default: True)
|
|
249
240
|
use_plaintext: Whether to use HTTP instead of HTTPS (default: False)
|
|
241
|
+
|
|
250
242
|
"""
|
|
251
243
|
self.services = services
|
|
252
244
|
self.platform_url = platform_url
|
|
@@ -254,20 +246,20 @@ class SDK(AbstractContextManager):
|
|
|
254
246
|
self._use_plaintext = use_plaintext
|
|
255
247
|
|
|
256
248
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
257
|
-
"""Clean up resources when exiting context manager"""
|
|
249
|
+
"""Clean up resources when exiting context manager."""
|
|
258
250
|
self.close()
|
|
259
251
|
|
|
260
252
|
def close(self):
|
|
261
|
-
"""Close the SDK and release resources"""
|
|
253
|
+
"""Close the SDK and release resources."""
|
|
262
254
|
if hasattr(self.services, "close"):
|
|
263
255
|
self.services.close()
|
|
264
256
|
|
|
265
257
|
def get_services(self) -> "SDK.Services":
|
|
266
|
-
"""
|
|
258
|
+
"""Return the services interface."""
|
|
267
259
|
return self.services
|
|
268
260
|
|
|
269
261
|
def get_platform_url(self) -> str | None:
|
|
270
|
-
"""
|
|
262
|
+
"""Return the platform URL if set."""
|
|
271
263
|
return self.platform_url
|
|
272
264
|
|
|
273
265
|
def load_tdf(
|
|
@@ -275,8 +267,7 @@ class SDK(AbstractContextManager):
|
|
|
275
267
|
tdf_data: bytes | BinaryIO | BytesIO,
|
|
276
268
|
config: TDFReaderConfig | None = None,
|
|
277
269
|
) -> TDFReader:
|
|
278
|
-
"""
|
|
279
|
-
Loads a TDF from the provided data, optionally according to the config.
|
|
270
|
+
"""Load a TDF from the provided data, optionally according to config.
|
|
280
271
|
|
|
281
272
|
Args:
|
|
282
273
|
tdf_data: The TDF data as bytes, file object, or BytesIO
|
|
@@ -287,6 +278,7 @@ class SDK(AbstractContextManager):
|
|
|
287
278
|
|
|
288
279
|
Raises:
|
|
289
280
|
SDKException: If there's an error loading the TDF
|
|
281
|
+
|
|
290
282
|
"""
|
|
291
283
|
tdf = TDF(self.services)
|
|
292
284
|
if config is None:
|
|
@@ -300,8 +292,7 @@ class SDK(AbstractContextManager):
|
|
|
300
292
|
config,
|
|
301
293
|
output_stream: BinaryIO | None = None,
|
|
302
294
|
):
|
|
303
|
-
"""
|
|
304
|
-
Creates a TDF with the provided payload.
|
|
295
|
+
"""Create a TDF with the provided payload.
|
|
305
296
|
|
|
306
297
|
Args:
|
|
307
298
|
payload: The payload data as bytes, file object, or BytesIO
|
|
@@ -313,6 +304,7 @@ class SDK(AbstractContextManager):
|
|
|
313
304
|
|
|
314
305
|
Raises:
|
|
315
306
|
SDKException: If there's an error creating the TDF
|
|
307
|
+
|
|
316
308
|
"""
|
|
317
309
|
tdf = TDF(self.services)
|
|
318
310
|
return tdf.create_tdf(payload, config, output_stream)
|
|
@@ -320,8 +312,7 @@ class SDK(AbstractContextManager):
|
|
|
320
312
|
def create_nano_tdf(
|
|
321
313
|
self, payload: bytes | BytesIO, output_stream: BinaryIO, config: "NanoTDFConfig"
|
|
322
314
|
) -> int:
|
|
323
|
-
"""
|
|
324
|
-
Creates a NanoTDF with the provided payload.
|
|
315
|
+
"""Create a NanoTDF with the provided payload.
|
|
325
316
|
|
|
326
317
|
Args:
|
|
327
318
|
payload: The payload data as bytes or BytesIO
|
|
@@ -333,6 +324,7 @@ class SDK(AbstractContextManager):
|
|
|
333
324
|
|
|
334
325
|
Raises:
|
|
335
326
|
SDKException: If there's an error creating the NanoTDF
|
|
327
|
+
|
|
336
328
|
"""
|
|
337
329
|
nano_tdf = NanoTDF(self.services)
|
|
338
330
|
return nano_tdf.create_nano_tdf(payload, output_stream, config)
|
|
@@ -343,8 +335,7 @@ class SDK(AbstractContextManager):
|
|
|
343
335
|
output_stream: BinaryIO,
|
|
344
336
|
config: NanoTDFConfig,
|
|
345
337
|
) -> None:
|
|
346
|
-
"""
|
|
347
|
-
Reads a NanoTDF and writes the payload to the output stream.
|
|
338
|
+
"""Read a NanoTDF and write the payload to the output stream.
|
|
348
339
|
|
|
349
340
|
Args:
|
|
350
341
|
nano_tdf_data: The NanoTDF data as bytes or BytesIO
|
|
@@ -353,20 +344,21 @@ class SDK(AbstractContextManager):
|
|
|
353
344
|
|
|
354
345
|
Raises:
|
|
355
346
|
SDKException: If there's an error reading the NanoTDF
|
|
347
|
+
|
|
356
348
|
"""
|
|
357
349
|
nano_tdf = NanoTDF(self.services)
|
|
358
350
|
nano_tdf.read_nano_tdf(nano_tdf_data, output_stream, config)
|
|
359
351
|
|
|
360
352
|
@staticmethod
|
|
361
353
|
def is_tdf(data: bytes | BinaryIO) -> bool:
|
|
362
|
-
"""
|
|
363
|
-
Checks if the provided data is a TDF.
|
|
354
|
+
"""Check if the provided data is a TDF.
|
|
364
355
|
|
|
365
356
|
Args:
|
|
366
357
|
data: The data to check
|
|
367
358
|
|
|
368
359
|
Returns:
|
|
369
360
|
bool: True if the data is a TDF, False otherwise
|
|
361
|
+
|
|
370
362
|
"""
|
|
371
363
|
import zipfile
|
|
372
364
|
from io import BytesIO
|
|
@@ -383,54 +375,40 @@ class SDK(AbstractContextManager):
|
|
|
383
375
|
|
|
384
376
|
# Exception classes - SDK-specific exceptions that can occur during operations
|
|
385
377
|
class SplitKeyException(SDKException):
|
|
386
|
-
"""
|
|
387
|
-
|
|
388
|
-
pass
|
|
378
|
+
"""Throw when SDK encounters error related to split key operations."""
|
|
389
379
|
|
|
390
380
|
class DataSizeNotSupported(SDKException):
|
|
391
|
-
"""
|
|
392
|
-
|
|
393
|
-
pass
|
|
381
|
+
"""Throw when user attempts to create TDF larger than maximum size."""
|
|
394
382
|
|
|
395
383
|
class KasInfoMissing(SDKException):
|
|
396
|
-
"""
|
|
397
|
-
|
|
398
|
-
pass
|
|
384
|
+
"""Throw during TDF creation when no KAS information is present."""
|
|
399
385
|
|
|
400
386
|
class KasPublicKeyMissing(SDKException):
|
|
401
|
-
"""
|
|
402
|
-
|
|
403
|
-
pass
|
|
387
|
+
"""Throw during encryption when SDK cannot retrieve public key for KAS."""
|
|
404
388
|
|
|
405
389
|
class TamperException(SDKException):
|
|
406
|
-
"""Base class for exceptions related to signature mismatches"""
|
|
390
|
+
"""Base class for exceptions related to signature mismatches."""
|
|
407
391
|
|
|
408
392
|
def __init__(self, error_message: str):
|
|
393
|
+
"""Initialize tamper exception."""
|
|
409
394
|
super().__init__(f"[tamper detected] {error_message}")
|
|
410
395
|
|
|
411
396
|
class RootSignatureValidationException(TamperException):
|
|
412
|
-
"""
|
|
413
|
-
|
|
414
|
-
pass
|
|
397
|
+
"""Throw when root signature validation fails."""
|
|
415
398
|
|
|
416
399
|
class SegmentSignatureMismatch(TamperException):
|
|
417
|
-
"""
|
|
418
|
-
|
|
419
|
-
pass
|
|
400
|
+
"""Throw when segment signature does not match expected value."""
|
|
420
401
|
|
|
421
402
|
class KasBadRequestException(SDKException):
|
|
422
|
-
"""
|
|
423
|
-
|
|
424
|
-
pass
|
|
403
|
+
"""Throw when KAS returns bad request response."""
|
|
425
404
|
|
|
426
405
|
class KasAllowlistException(SDKException):
|
|
427
|
-
"""
|
|
428
|
-
|
|
429
|
-
pass
|
|
406
|
+
"""Throw when KAS allowlist check fails."""
|
|
430
407
|
|
|
431
408
|
class AssertionException(SDKException):
|
|
432
|
-
"""
|
|
409
|
+
"""Throw when an assertion validation fails."""
|
|
433
410
|
|
|
434
411
|
def __init__(self, error_message: str, assertion_id: str):
|
|
412
|
+
"""Initialize exception."""
|
|
435
413
|
super().__init__(error_message)
|
|
436
414
|
self.assertion_id = assertion_id
|
otdf_python/sdk_builder.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
"""
|
|
2
|
-
|
|
1
|
+
"""SDKBuilder class for OpenTDF platform interaction.
|
|
2
|
+
|
|
3
3
|
Provides methods to configure and build SDK instances.
|
|
4
4
|
"""
|
|
5
5
|
|
|
@@ -19,6 +19,8 @@ logger = logging.getLogger(__name__)
|
|
|
19
19
|
|
|
20
20
|
@dataclass
|
|
21
21
|
class OAuthConfig:
|
|
22
|
+
"""OAuth configuration."""
|
|
23
|
+
|
|
22
24
|
client_id: str
|
|
23
25
|
client_secret: str
|
|
24
26
|
grant_type: str = "client_credentials"
|
|
@@ -28,9 +30,7 @@ class OAuthConfig:
|
|
|
28
30
|
|
|
29
31
|
|
|
30
32
|
class SDKBuilder:
|
|
31
|
-
"""
|
|
32
|
-
A builder class for creating instances of the SDK class.
|
|
33
|
-
"""
|
|
33
|
+
"""A builder class for creating instances of the SDK class."""
|
|
34
34
|
|
|
35
35
|
PLATFORM_ISSUER = "platform_issuer"
|
|
36
36
|
|
|
@@ -38,6 +38,7 @@ class SDKBuilder:
|
|
|
38
38
|
_platform_url = None
|
|
39
39
|
|
|
40
40
|
def __init__(self):
|
|
41
|
+
"""Initialize SDK builder."""
|
|
41
42
|
self.platform_endpoint: str | None = None
|
|
42
43
|
self.issuer_endpoint: str | None = None
|
|
43
44
|
self.oauth_config: OAuthConfig | None = None
|
|
@@ -49,29 +50,33 @@ class SDKBuilder:
|
|
|
49
50
|
|
|
50
51
|
@staticmethod
|
|
51
52
|
def new_builder() -> "SDKBuilder":
|
|
52
|
-
"""
|
|
53
|
-
|
|
53
|
+
"""Create a new SDKBuilder instance.
|
|
54
|
+
|
|
54
55
|
Returns:
|
|
55
56
|
SDKBuilder: A new builder instance
|
|
57
|
+
|
|
56
58
|
"""
|
|
57
59
|
return SDKBuilder()
|
|
58
60
|
|
|
59
61
|
@staticmethod
|
|
60
62
|
def get_platform_url() -> str | None:
|
|
61
|
-
"""
|
|
62
|
-
|
|
63
|
+
"""Get the last set platform URL.
|
|
64
|
+
|
|
63
65
|
Returns:
|
|
64
66
|
str | None: The platform URL or None if not set
|
|
67
|
+
|
|
65
68
|
"""
|
|
66
69
|
return SDKBuilder._platform_url
|
|
67
70
|
|
|
68
71
|
def ssl_context_from_directory(self, certs_dir_path: str) -> "SDKBuilder":
|
|
69
|
-
"""
|
|
70
|
-
|
|
72
|
+
"""Add SSL context with trusted certs from certDirPath.
|
|
73
|
+
|
|
71
74
|
Args:
|
|
72
75
|
certs_dir_path: Path to a directory containing .pem or .crt trusted certs
|
|
76
|
+
|
|
73
77
|
Returns:
|
|
74
78
|
self: The builder instance for chaining
|
|
79
|
+
|
|
75
80
|
"""
|
|
76
81
|
self.cert_paths = []
|
|
77
82
|
|
|
@@ -91,13 +96,14 @@ class SDKBuilder:
|
|
|
91
96
|
return self
|
|
92
97
|
|
|
93
98
|
def client_secret(self, client_id: str, client_secret: str) -> "SDKBuilder":
|
|
94
|
-
"""
|
|
95
|
-
|
|
99
|
+
"""Set client credentials for OAuth 2.0 client_credentials grant.
|
|
100
|
+
|
|
96
101
|
Args:
|
|
97
102
|
client_id: The OAuth client ID
|
|
98
103
|
client_secret: The OAuth client secret
|
|
99
104
|
Returns:
|
|
100
105
|
self: The builder instance for chaining
|
|
106
|
+
|
|
101
107
|
"""
|
|
102
108
|
self.oauth_config = OAuthConfig(
|
|
103
109
|
client_id=client_id, client_secret=client_secret
|
|
@@ -105,17 +111,16 @@ class SDKBuilder:
|
|
|
105
111
|
return self
|
|
106
112
|
|
|
107
113
|
def set_platform_endpoint(self, endpoint: str) -> "SDKBuilder":
|
|
108
|
-
"""
|
|
109
|
-
|
|
114
|
+
"""Set the OpenTDF platform endpoint URL.
|
|
115
|
+
|
|
110
116
|
Args:
|
|
111
117
|
endpoint: The platform endpoint URL
|
|
112
118
|
Returns:
|
|
113
119
|
self: The builder instance for chaining
|
|
120
|
+
|
|
114
121
|
"""
|
|
115
122
|
# Normalize the endpoint URL
|
|
116
|
-
if endpoint and not (
|
|
117
|
-
endpoint.startswith("http://") or endpoint.startswith("https://")
|
|
118
|
-
):
|
|
123
|
+
if endpoint and not (endpoint.startswith(("http://", "https://"))):
|
|
119
124
|
if self.use_plaintext:
|
|
120
125
|
endpoint = f"http://{endpoint}"
|
|
121
126
|
else:
|
|
@@ -127,17 +132,16 @@ class SDKBuilder:
|
|
|
127
132
|
return self
|
|
128
133
|
|
|
129
134
|
def set_issuer_endpoint(self, issuer: str) -> "SDKBuilder":
|
|
130
|
-
"""
|
|
131
|
-
|
|
135
|
+
"""Set the OpenID Connect issuer endpoint URL.
|
|
136
|
+
|
|
132
137
|
Args:
|
|
133
138
|
issuer: The issuer endpoint URL
|
|
134
139
|
Returns:
|
|
135
140
|
self: The builder instance for chaining
|
|
141
|
+
|
|
136
142
|
"""
|
|
137
143
|
# Normalize the issuer URL
|
|
138
|
-
if issuer and not (
|
|
139
|
-
issuer.startswith("http://") or issuer.startswith("https://")
|
|
140
|
-
):
|
|
144
|
+
if issuer and not (issuer.startswith(("http://", "https://"))):
|
|
141
145
|
issuer = f"https://{issuer}"
|
|
142
146
|
|
|
143
147
|
self.issuer_endpoint = issuer
|
|
@@ -146,12 +150,13 @@ class SDKBuilder:
|
|
|
146
150
|
def use_insecure_plaintext_connection(
|
|
147
151
|
self, use_plaintext: bool = True
|
|
148
152
|
) -> "SDKBuilder":
|
|
149
|
-
"""
|
|
150
|
-
|
|
153
|
+
"""Configure whether to use plain text (HTTP) instead of HTTPS.
|
|
154
|
+
|
|
151
155
|
Args:
|
|
152
156
|
use_plaintext: Whether to use plain text connection
|
|
153
157
|
Returns:
|
|
154
158
|
self: The builder instance for chaining
|
|
159
|
+
|
|
155
160
|
"""
|
|
156
161
|
self.use_plaintext = use_plaintext
|
|
157
162
|
|
|
@@ -168,12 +173,13 @@ class SDKBuilder:
|
|
|
168
173
|
return self
|
|
169
174
|
|
|
170
175
|
def use_insecure_skip_verify(self, skip_verify: bool = True) -> "SDKBuilder":
|
|
171
|
-
"""
|
|
172
|
-
|
|
176
|
+
"""Configure whether to skip SSL verification.
|
|
177
|
+
|
|
173
178
|
Args:
|
|
174
179
|
skip_verify: Whether to skip SSL verification
|
|
175
180
|
Returns:
|
|
176
181
|
self: The builder instance for chaining
|
|
182
|
+
|
|
177
183
|
"""
|
|
178
184
|
self.insecure_skip_verify = skip_verify
|
|
179
185
|
|
|
@@ -184,21 +190,23 @@ class SDKBuilder:
|
|
|
184
190
|
return self
|
|
185
191
|
|
|
186
192
|
def bearer_token(self, token: str) -> "SDKBuilder":
|
|
187
|
-
"""
|
|
188
|
-
|
|
193
|
+
"""Set a bearer token to use for authorization.
|
|
194
|
+
|
|
189
195
|
Args:
|
|
190
196
|
token: The bearer token
|
|
191
197
|
Returns:
|
|
192
198
|
self: The builder instance for chaining
|
|
199
|
+
|
|
193
200
|
"""
|
|
194
201
|
self.auth_token = token
|
|
195
202
|
return self
|
|
196
203
|
|
|
197
204
|
def _discover_token_endpoint_from_platform(self) -> None:
|
|
198
|
-
"""
|
|
199
|
-
|
|
205
|
+
"""Discover token endpoint using OpenTDF platform configuration.
|
|
206
|
+
|
|
200
207
|
Raises:
|
|
201
208
|
AutoConfigureException: If discovery fails
|
|
209
|
+
|
|
202
210
|
"""
|
|
203
211
|
if not self.platform_endpoint or not self.oauth_config:
|
|
204
212
|
return
|
|
@@ -232,12 +240,13 @@ class SDKBuilder:
|
|
|
232
240
|
self._discover_token_endpoint_from_issuer(platform_issuer)
|
|
233
241
|
|
|
234
242
|
def _discover_token_endpoint_from_issuer(self, issuer_url: str) -> None:
|
|
235
|
-
"""
|
|
236
|
-
|
|
243
|
+
"""Discover token endpoint using OIDC discovery from issuer.
|
|
244
|
+
|
|
237
245
|
Args:
|
|
238
246
|
issuer_url: The issuer URL to use for discovery
|
|
239
247
|
Raises:
|
|
240
248
|
AutoConfigureException: If discovery fails
|
|
249
|
+
|
|
241
250
|
"""
|
|
242
251
|
if not self.oauth_config:
|
|
243
252
|
return
|
|
@@ -260,10 +269,11 @@ class SDKBuilder:
|
|
|
260
269
|
)
|
|
261
270
|
|
|
262
271
|
def _discover_token_endpoint(self) -> None:
|
|
263
|
-
"""
|
|
264
|
-
|
|
272
|
+
"""Discover the token endpoint using available configuration.
|
|
273
|
+
|
|
265
274
|
Raises:
|
|
266
275
|
AutoConfigureException: If discovery fails
|
|
276
|
+
|
|
267
277
|
"""
|
|
268
278
|
# Try platform endpoint first
|
|
269
279
|
if self.platform_endpoint:
|
|
@@ -283,7 +293,7 @@ class SDKBuilder:
|
|
|
283
293
|
pass
|
|
284
294
|
raise AutoConfigureException(
|
|
285
295
|
f"Error during token endpoint discovery: {e!s}"
|
|
286
|
-
)
|
|
296
|
+
) from e
|
|
287
297
|
|
|
288
298
|
# Fall back to explicit issuer endpoint
|
|
289
299
|
if self.issuer_endpoint:
|
|
@@ -297,12 +307,13 @@ class SDKBuilder:
|
|
|
297
307
|
)
|
|
298
308
|
|
|
299
309
|
def _get_token_from_client_credentials(self) -> str:
|
|
300
|
-
"""
|
|
301
|
-
|
|
310
|
+
"""Obtain an OAuth token using client credentials.
|
|
311
|
+
|
|
302
312
|
Returns:
|
|
303
|
-
str: The access token
|
|
313
|
+
str: The OAuth access token
|
|
304
314
|
Raises:
|
|
305
315
|
AutoConfigureException: If token acquisition fails
|
|
316
|
+
|
|
306
317
|
"""
|
|
307
318
|
if not self.oauth_config:
|
|
308
319
|
raise AutoConfigureException("OAuth configuration is not set")
|
|
@@ -341,15 +352,18 @@ class SDKBuilder:
|
|
|
341
352
|
)
|
|
342
353
|
|
|
343
354
|
except Exception as e:
|
|
344
|
-
raise AutoConfigureException(
|
|
355
|
+
raise AutoConfigureException(
|
|
356
|
+
f"Error during token acquisition: {e!s}"
|
|
357
|
+
) from e
|
|
345
358
|
|
|
346
359
|
def _create_services(self) -> SDK.Services:
|
|
347
|
-
"""
|
|
348
|
-
|
|
360
|
+
"""Create service client instances.
|
|
361
|
+
|
|
349
362
|
Returns:
|
|
350
363
|
SDK.Services: The service client instances
|
|
351
364
|
Raises:
|
|
352
365
|
AutoConfigureException: If service creation fails
|
|
366
|
+
|
|
353
367
|
"""
|
|
354
368
|
# For now, return a simple implementation of Services
|
|
355
369
|
# In a real implementation, this would create actual service clients
|
|
@@ -364,9 +378,7 @@ class SDKBuilder:
|
|
|
364
378
|
self._builder = builder_instance
|
|
365
379
|
|
|
366
380
|
def kas(self) -> KAS:
|
|
367
|
-
"""
|
|
368
|
-
Returns the KAS interface with SSL verification settings.
|
|
369
|
-
"""
|
|
381
|
+
"""Return the KAS interface with SSL verification settings."""
|
|
370
382
|
platform_url = SDKBuilder.get_platform_url()
|
|
371
383
|
|
|
372
384
|
# Create a token source function that can refresh tokens
|
|
@@ -394,12 +406,13 @@ class SDKBuilder:
|
|
|
394
406
|
return ServicesImpl(self)
|
|
395
407
|
|
|
396
408
|
def build(self) -> SDK:
|
|
397
|
-
"""
|
|
398
|
-
|
|
409
|
+
"""Build and return an SDK instance with configured properties.
|
|
410
|
+
|
|
399
411
|
Returns:
|
|
400
412
|
SDK: The configured SDK instance
|
|
401
413
|
Raises:
|
|
402
414
|
AutoConfigureException: If the build fails
|
|
415
|
+
|
|
403
416
|
"""
|
|
404
417
|
if not self.platform_endpoint:
|
|
405
418
|
raise AutoConfigureException("Platform endpoint is not set")
|
otdf_python/sdk_exceptions.py
CHANGED
|
@@ -1,16 +1,26 @@
|
|
|
1
|
+
"""SDK-specific exception classes."""
|
|
2
|
+
|
|
3
|
+
|
|
1
4
|
class SDKException(Exception):
|
|
5
|
+
"""Base SDK exception class."""
|
|
6
|
+
|
|
2
7
|
def __init__(self, message, reason=None):
|
|
8
|
+
"""Initialize exception."""
|
|
3
9
|
super().__init__(message)
|
|
4
10
|
self.reason = reason
|
|
5
11
|
|
|
6
12
|
|
|
7
13
|
class AutoConfigureException(SDKException):
|
|
14
|
+
"""Exception for SDK auto-configuration failures."""
|
|
15
|
+
|
|
8
16
|
def __init__(self, message, cause=None):
|
|
17
|
+
"""Initialize exception."""
|
|
9
18
|
super().__init__(message, cause)
|
|
10
19
|
|
|
11
20
|
|
|
12
21
|
class KASBadRequestException(SDKException):
|
|
13
|
-
"""
|
|
22
|
+
"""Exception for KAS bad request or client errors."""
|
|
14
23
|
|
|
15
24
|
def __init__(self, message):
|
|
25
|
+
"""Initialize exception."""
|
|
16
26
|
super().__init__(message)
|