otdf-python 0.4.0__py3-none-any.whl → 0.4.1__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 +22 -6
- otdf_python/cli.py +5 -5
- otdf_python/collection_store.py +13 -0
- 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 -25
- 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 +48 -55
- otdf_python/kas_connect_rpc_client.py +16 -19
- 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 +34 -24
- otdf_python/nanotdf_ecdsa_struct.py +5 -9
- otdf_python/nanotdf_type.py +12 -0
- 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 +49 -57
- otdf_python/sdk_builder.py +58 -41
- 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.1.dist-info}/METADATA +1 -1
- {otdf_python-0.4.0.dist-info → otdf_python-0.4.1.dist-info}/RECORD +52 -52
- {otdf_python-0.4.0.dist-info → otdf_python-0.4.1.dist-info}/WHEEL +1 -1
- {otdf_python-0.4.0.dist-info → otdf_python-0.4.1.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,16 +124,16 @@ 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
|
-
"""
|
|
136
|
+
"""Close resources associated with KAS interface."""
|
|
141
137
|
pass
|
|
142
138
|
|
|
143
139
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
@@ -145,16 +141,15 @@ class KAS(AbstractContextManager):
|
|
|
145
141
|
|
|
146
142
|
|
|
147
143
|
class SDK(AbstractContextManager):
|
|
144
|
+
"""SDK for OpenTDF platform interaction."""
|
|
145
|
+
|
|
148
146
|
def new_tdf_config(
|
|
149
147
|
self,
|
|
150
148
|
attributes: list[str] | None = None,
|
|
151
149
|
kas_info_list: list[KASInfo] | None = None,
|
|
152
150
|
**kwargs,
|
|
153
151
|
) -> TDFConfig:
|
|
154
|
-
"""
|
|
155
|
-
Create a TDFConfig with default kas_info_list from the SDK's platform_url.
|
|
156
|
-
"""
|
|
157
|
-
|
|
152
|
+
"""Create a TDFConfig with default kas_info_list from the SDK's platform_url."""
|
|
158
153
|
if self.platform_url is None:
|
|
159
154
|
raise SDKException("Cannot create TDFConfig: SDK platform_url is not set.")
|
|
160
155
|
|
|
@@ -214,19 +209,16 @@ class SDK(AbstractContextManager):
|
|
|
214
209
|
"""
|
|
215
210
|
|
|
216
211
|
class Services(AbstractContextManager):
|
|
217
|
-
"""
|
|
218
|
-
The Services interface provides access to various platform service clients and KAS.
|
|
219
|
-
"""
|
|
212
|
+
"""The Services interface provides access to various platform service clients and KAS."""
|
|
220
213
|
|
|
221
214
|
def kas(self) -> KAS:
|
|
222
|
-
"""
|
|
223
|
-
Returns the KAS client for key access operations.
|
|
215
|
+
"""Return the KAS client for key access operations.
|
|
224
216
|
This should be implemented to return an instance of KAS.
|
|
225
217
|
"""
|
|
226
218
|
raise NotImplementedError
|
|
227
219
|
|
|
228
220
|
def close(self):
|
|
229
|
-
"""
|
|
221
|
+
"""Close resources associated with the services."""
|
|
230
222
|
pass
|
|
231
223
|
|
|
232
224
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
@@ -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,54 @@ class SDK(AbstractContextManager):
|
|
|
383
375
|
|
|
384
376
|
# Exception classes - SDK-specific exceptions that can occur during operations
|
|
385
377
|
class SplitKeyException(SDKException):
|
|
386
|
-
"""
|
|
378
|
+
"""Throw when SDK encounters error related to split key operations."""
|
|
387
379
|
|
|
388
380
|
pass
|
|
389
381
|
|
|
390
382
|
class DataSizeNotSupported(SDKException):
|
|
391
|
-
"""
|
|
383
|
+
"""Throw when user attempts to create TDF larger than maximum size."""
|
|
392
384
|
|
|
393
385
|
pass
|
|
394
386
|
|
|
395
387
|
class KasInfoMissing(SDKException):
|
|
396
|
-
"""
|
|
388
|
+
"""Throw during TDF creation when no KAS information is present."""
|
|
397
389
|
|
|
398
390
|
pass
|
|
399
391
|
|
|
400
392
|
class KasPublicKeyMissing(SDKException):
|
|
401
|
-
"""
|
|
393
|
+
"""Throw during encryption when SDK cannot retrieve public key for KAS."""
|
|
402
394
|
|
|
403
395
|
pass
|
|
404
396
|
|
|
405
397
|
class TamperException(SDKException):
|
|
406
|
-
"""Base class for exceptions related to signature mismatches"""
|
|
398
|
+
"""Base class for exceptions related to signature mismatches."""
|
|
407
399
|
|
|
408
400
|
def __init__(self, error_message: str):
|
|
401
|
+
"""Initialize tamper exception."""
|
|
409
402
|
super().__init__(f"[tamper detected] {error_message}")
|
|
410
403
|
|
|
411
404
|
class RootSignatureValidationException(TamperException):
|
|
412
|
-
"""
|
|
413
|
-
|
|
414
|
-
pass
|
|
405
|
+
"""Throw when root signature validation fails."""
|
|
415
406
|
|
|
416
407
|
class SegmentSignatureMismatch(TamperException):
|
|
417
|
-
"""
|
|
408
|
+
"""Throw when segment signature does not match expected value."""
|
|
418
409
|
|
|
419
410
|
pass
|
|
420
411
|
|
|
421
412
|
class KasBadRequestException(SDKException):
|
|
422
|
-
"""
|
|
413
|
+
"""Throw when KAS returns bad request response."""
|
|
423
414
|
|
|
424
415
|
pass
|
|
425
416
|
|
|
426
417
|
class KasAllowlistException(SDKException):
|
|
427
|
-
"""
|
|
418
|
+
"""Throw when KAS allowlist check fails."""
|
|
428
419
|
|
|
429
420
|
pass
|
|
430
421
|
|
|
431
422
|
class AssertionException(SDKException):
|
|
432
|
-
"""
|
|
423
|
+
"""Throw when an assertion validation fails."""
|
|
433
424
|
|
|
434
425
|
def __init__(self, error_message: str, assertion_id: str):
|
|
426
|
+
"""Initialize exception."""
|
|
435
427
|
super().__init__(error_message)
|
|
436
428
|
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,12 +111,13 @@ 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
123
|
if endpoint and not (
|
|
@@ -127,12 +134,13 @@ class SDKBuilder:
|
|
|
127
134
|
return self
|
|
128
135
|
|
|
129
136
|
def set_issuer_endpoint(self, issuer: str) -> "SDKBuilder":
|
|
130
|
-
"""
|
|
131
|
-
|
|
137
|
+
"""Set the OpenID Connect issuer endpoint URL.
|
|
138
|
+
|
|
132
139
|
Args:
|
|
133
140
|
issuer: The issuer endpoint URL
|
|
134
141
|
Returns:
|
|
135
142
|
self: The builder instance for chaining
|
|
143
|
+
|
|
136
144
|
"""
|
|
137
145
|
# Normalize the issuer URL
|
|
138
146
|
if issuer and not (
|
|
@@ -146,12 +154,13 @@ class SDKBuilder:
|
|
|
146
154
|
def use_insecure_plaintext_connection(
|
|
147
155
|
self, use_plaintext: bool = True
|
|
148
156
|
) -> "SDKBuilder":
|
|
149
|
-
"""
|
|
150
|
-
|
|
157
|
+
"""Configure whether to use plain text (HTTP) instead of HTTPS.
|
|
158
|
+
|
|
151
159
|
Args:
|
|
152
160
|
use_plaintext: Whether to use plain text connection
|
|
153
161
|
Returns:
|
|
154
162
|
self: The builder instance for chaining
|
|
163
|
+
|
|
155
164
|
"""
|
|
156
165
|
self.use_plaintext = use_plaintext
|
|
157
166
|
|
|
@@ -168,12 +177,13 @@ class SDKBuilder:
|
|
|
168
177
|
return self
|
|
169
178
|
|
|
170
179
|
def use_insecure_skip_verify(self, skip_verify: bool = True) -> "SDKBuilder":
|
|
171
|
-
"""
|
|
172
|
-
|
|
180
|
+
"""Configure whether to skip SSL verification.
|
|
181
|
+
|
|
173
182
|
Args:
|
|
174
183
|
skip_verify: Whether to skip SSL verification
|
|
175
184
|
Returns:
|
|
176
185
|
self: The builder instance for chaining
|
|
186
|
+
|
|
177
187
|
"""
|
|
178
188
|
self.insecure_skip_verify = skip_verify
|
|
179
189
|
|
|
@@ -184,21 +194,23 @@ class SDKBuilder:
|
|
|
184
194
|
return self
|
|
185
195
|
|
|
186
196
|
def bearer_token(self, token: str) -> "SDKBuilder":
|
|
187
|
-
"""
|
|
188
|
-
|
|
197
|
+
"""Set a bearer token to use for authorization.
|
|
198
|
+
|
|
189
199
|
Args:
|
|
190
200
|
token: The bearer token
|
|
191
201
|
Returns:
|
|
192
202
|
self: The builder instance for chaining
|
|
203
|
+
|
|
193
204
|
"""
|
|
194
205
|
self.auth_token = token
|
|
195
206
|
return self
|
|
196
207
|
|
|
197
208
|
def _discover_token_endpoint_from_platform(self) -> None:
|
|
198
|
-
"""
|
|
199
|
-
|
|
209
|
+
"""Discover token endpoint using OpenTDF platform configuration.
|
|
210
|
+
|
|
200
211
|
Raises:
|
|
201
212
|
AutoConfigureException: If discovery fails
|
|
213
|
+
|
|
202
214
|
"""
|
|
203
215
|
if not self.platform_endpoint or not self.oauth_config:
|
|
204
216
|
return
|
|
@@ -232,12 +244,13 @@ class SDKBuilder:
|
|
|
232
244
|
self._discover_token_endpoint_from_issuer(platform_issuer)
|
|
233
245
|
|
|
234
246
|
def _discover_token_endpoint_from_issuer(self, issuer_url: str) -> None:
|
|
235
|
-
"""
|
|
236
|
-
|
|
247
|
+
"""Discover token endpoint using OIDC discovery from issuer.
|
|
248
|
+
|
|
237
249
|
Args:
|
|
238
250
|
issuer_url: The issuer URL to use for discovery
|
|
239
251
|
Raises:
|
|
240
252
|
AutoConfigureException: If discovery fails
|
|
253
|
+
|
|
241
254
|
"""
|
|
242
255
|
if not self.oauth_config:
|
|
243
256
|
return
|
|
@@ -260,10 +273,11 @@ class SDKBuilder:
|
|
|
260
273
|
)
|
|
261
274
|
|
|
262
275
|
def _discover_token_endpoint(self) -> None:
|
|
263
|
-
"""
|
|
264
|
-
|
|
276
|
+
"""Discover the token endpoint using available configuration.
|
|
277
|
+
|
|
265
278
|
Raises:
|
|
266
279
|
AutoConfigureException: If discovery fails
|
|
280
|
+
|
|
267
281
|
"""
|
|
268
282
|
# Try platform endpoint first
|
|
269
283
|
if self.platform_endpoint:
|
|
@@ -283,7 +297,7 @@ class SDKBuilder:
|
|
|
283
297
|
pass
|
|
284
298
|
raise AutoConfigureException(
|
|
285
299
|
f"Error during token endpoint discovery: {e!s}"
|
|
286
|
-
)
|
|
300
|
+
) from e
|
|
287
301
|
|
|
288
302
|
# Fall back to explicit issuer endpoint
|
|
289
303
|
if self.issuer_endpoint:
|
|
@@ -297,12 +311,13 @@ class SDKBuilder:
|
|
|
297
311
|
)
|
|
298
312
|
|
|
299
313
|
def _get_token_from_client_credentials(self) -> str:
|
|
300
|
-
"""
|
|
301
|
-
|
|
314
|
+
"""Obtain an OAuth token using client credentials.
|
|
315
|
+
|
|
302
316
|
Returns:
|
|
303
|
-
str: The access token
|
|
317
|
+
str: The OAuth access token
|
|
304
318
|
Raises:
|
|
305
319
|
AutoConfigureException: If token acquisition fails
|
|
320
|
+
|
|
306
321
|
"""
|
|
307
322
|
if not self.oauth_config:
|
|
308
323
|
raise AutoConfigureException("OAuth configuration is not set")
|
|
@@ -341,15 +356,18 @@ class SDKBuilder:
|
|
|
341
356
|
)
|
|
342
357
|
|
|
343
358
|
except Exception as e:
|
|
344
|
-
raise AutoConfigureException(
|
|
359
|
+
raise AutoConfigureException(
|
|
360
|
+
f"Error during token acquisition: {e!s}"
|
|
361
|
+
) from e
|
|
345
362
|
|
|
346
363
|
def _create_services(self) -> SDK.Services:
|
|
347
|
-
"""
|
|
348
|
-
|
|
364
|
+
"""Create service client instances.
|
|
365
|
+
|
|
349
366
|
Returns:
|
|
350
367
|
SDK.Services: The service client instances
|
|
351
368
|
Raises:
|
|
352
369
|
AutoConfigureException: If service creation fails
|
|
370
|
+
|
|
353
371
|
"""
|
|
354
372
|
# For now, return a simple implementation of Services
|
|
355
373
|
# In a real implementation, this would create actual service clients
|
|
@@ -364,9 +382,7 @@ class SDKBuilder:
|
|
|
364
382
|
self._builder = builder_instance
|
|
365
383
|
|
|
366
384
|
def kas(self) -> KAS:
|
|
367
|
-
"""
|
|
368
|
-
Returns the KAS interface with SSL verification settings.
|
|
369
|
-
"""
|
|
385
|
+
"""Return the KAS interface with SSL verification settings."""
|
|
370
386
|
platform_url = SDKBuilder.get_platform_url()
|
|
371
387
|
|
|
372
388
|
# Create a token source function that can refresh tokens
|
|
@@ -394,12 +410,13 @@ class SDKBuilder:
|
|
|
394
410
|
return ServicesImpl(self)
|
|
395
411
|
|
|
396
412
|
def build(self) -> SDK:
|
|
397
|
-
"""
|
|
398
|
-
|
|
413
|
+
"""Build and return an SDK instance with configured properties.
|
|
414
|
+
|
|
399
415
|
Returns:
|
|
400
416
|
SDK: The configured SDK instance
|
|
401
417
|
Raises:
|
|
402
418
|
AutoConfigureException: If the build fails
|
|
419
|
+
|
|
403
420
|
"""
|
|
404
421
|
if not self.platform_endpoint:
|
|
405
422
|
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)
|
|
@@ -1,10 +1,16 @@
|
|
|
1
|
+
"""Symmetric encryption and payload configuration."""
|
|
2
|
+
|
|
3
|
+
|
|
1
4
|
class SymmetricAndPayloadConfig:
|
|
5
|
+
"""Symmetric and payload configuration."""
|
|
6
|
+
|
|
2
7
|
def __init__(
|
|
3
8
|
self,
|
|
4
9
|
cipher_type: int = 0,
|
|
5
10
|
signature_ecc_mode: int = 0,
|
|
6
11
|
has_signature: bool = True,
|
|
7
12
|
):
|
|
13
|
+
"""Initialize symmetric and payload configuration."""
|
|
8
14
|
self.cipher_type = cipher_type
|
|
9
15
|
self.signature_ecc_mode = signature_ecc_mode
|
|
10
16
|
self.has_signature = has_signature
|