otdf-python 0.1.9__py3-none-any.whl → 0.3.0__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 +25 -0
- otdf_python/__main__.py +12 -0
- otdf_python/address_normalizer.py +84 -0
- otdf_python/aesgcm.py +55 -0
- otdf_python/assertion_config.py +84 -0
- otdf_python/asym_crypto.py +85 -0
- otdf_python/asym_decryption.py +53 -0
- otdf_python/asym_encryption.py +75 -0
- otdf_python/auth_headers.py +21 -0
- otdf_python/autoconfigure_utils.py +113 -0
- otdf_python/cli.py +570 -0
- otdf_python/collection_store.py +41 -0
- otdf_python/collection_store_impl.py +22 -0
- otdf_python/config.py +69 -0
- otdf_python/connect_client.py +0 -0
- otdf_python/constants.py +1 -0
- otdf_python/crypto_utils.py +78 -0
- otdf_python/dpop.py +81 -0
- otdf_python/ecc_mode.py +32 -0
- otdf_python/eckeypair.py +75 -0
- otdf_python/header.py +143 -0
- otdf_python/invalid_zip_exception.py +8 -0
- otdf_python/kas_client.py +603 -0
- otdf_python/kas_connect_rpc_client.py +207 -0
- otdf_python/kas_info.py +25 -0
- otdf_python/kas_key_cache.py +52 -0
- otdf_python/key_type.py +31 -0
- otdf_python/key_type_constants.py +43 -0
- otdf_python/manifest.py +215 -0
- otdf_python/nanotdf.py +553 -0
- otdf_python/nanotdf_ecdsa_struct.py +132 -0
- otdf_python/nanotdf_type.py +43 -0
- otdf_python/policy_binding_serializer.py +39 -0
- otdf_python/policy_info.py +78 -0
- otdf_python/policy_object.py +22 -0
- otdf_python/policy_stub.py +2 -0
- otdf_python/resource_locator.py +44 -0
- otdf_python/sdk.py +528 -0
- otdf_python/sdk_builder.py +448 -0
- otdf_python/sdk_exceptions.py +16 -0
- otdf_python/symmetric_and_payload_config.py +30 -0
- otdf_python/tdf.py +479 -0
- otdf_python/tdf_reader.py +153 -0
- otdf_python/tdf_writer.py +23 -0
- otdf_python/token_source.py +34 -0
- otdf_python/version.py +57 -0
- otdf_python/zip_reader.py +47 -0
- otdf_python/zip_writer.py +70 -0
- otdf_python-0.3.0.dist-info/METADATA +231 -0
- otdf_python-0.3.0.dist-info/RECORD +137 -0
- {otdf_python-0.1.9.dist-info → otdf_python-0.3.0.dist-info}/WHEEL +1 -2
- {otdf_python-0.1.9.dist-info → otdf_python-0.3.0.dist-info/licenses}/LICENSE +1 -1
- otdf_python_proto/__init__.py +37 -0
- otdf_python_proto/authorization/__init__.py +1 -0
- otdf_python_proto/authorization/authorization_pb2.py +80 -0
- otdf_python_proto/authorization/authorization_pb2.pyi +161 -0
- otdf_python_proto/authorization/authorization_pb2_connect.py +191 -0
- otdf_python_proto/authorization/v2/authorization_pb2.py +105 -0
- otdf_python_proto/authorization/v2/authorization_pb2.pyi +134 -0
- otdf_python_proto/authorization/v2/authorization_pb2_connect.py +233 -0
- otdf_python_proto/common/__init__.py +1 -0
- otdf_python_proto/common/common_pb2.py +52 -0
- otdf_python_proto/common/common_pb2.pyi +61 -0
- otdf_python_proto/entity/__init__.py +1 -0
- otdf_python_proto/entity/entity_pb2.py +47 -0
- otdf_python_proto/entity/entity_pb2.pyi +50 -0
- otdf_python_proto/entityresolution/__init__.py +1 -0
- otdf_python_proto/entityresolution/entity_resolution_pb2.py +57 -0
- otdf_python_proto/entityresolution/entity_resolution_pb2.pyi +55 -0
- otdf_python_proto/entityresolution/entity_resolution_pb2_connect.py +149 -0
- otdf_python_proto/entityresolution/v2/entity_resolution_pb2.py +55 -0
- otdf_python_proto/entityresolution/v2/entity_resolution_pb2.pyi +55 -0
- otdf_python_proto/entityresolution/v2/entity_resolution_pb2_connect.py +149 -0
- otdf_python_proto/kas/__init__.py +9 -0
- otdf_python_proto/kas/kas_pb2.py +103 -0
- otdf_python_proto/kas/kas_pb2.pyi +170 -0
- otdf_python_proto/kas/kas_pb2_connect.py +192 -0
- otdf_python_proto/legacy_grpc/__init__.py +1 -0
- otdf_python_proto/legacy_grpc/authorization/authorization_pb2_grpc.py +163 -0
- otdf_python_proto/legacy_grpc/authorization/v2/authorization_pb2_grpc.py +206 -0
- otdf_python_proto/legacy_grpc/common/common_pb2_grpc.py +4 -0
- otdf_python_proto/legacy_grpc/entity/entity_pb2_grpc.py +4 -0
- otdf_python_proto/legacy_grpc/entityresolution/entity_resolution_pb2_grpc.py +122 -0
- otdf_python_proto/legacy_grpc/entityresolution/v2/entity_resolution_pb2_grpc.py +120 -0
- otdf_python_proto/legacy_grpc/kas/kas_pb2_grpc.py +172 -0
- otdf_python_proto/legacy_grpc/logger/audit/test_pb2_grpc.py +4 -0
- otdf_python_proto/legacy_grpc/policy/actions/actions_pb2_grpc.py +249 -0
- otdf_python_proto/legacy_grpc/policy/attributes/attributes_pb2_grpc.py +873 -0
- otdf_python_proto/legacy_grpc/policy/kasregistry/key_access_server_registry_pb2_grpc.py +602 -0
- otdf_python_proto/legacy_grpc/policy/keymanagement/key_management_pb2_grpc.py +251 -0
- otdf_python_proto/legacy_grpc/policy/namespaces/namespaces_pb2_grpc.py +427 -0
- otdf_python_proto/legacy_grpc/policy/objects_pb2_grpc.py +4 -0
- otdf_python_proto/legacy_grpc/policy/registeredresources/registered_resources_pb2_grpc.py +524 -0
- otdf_python_proto/legacy_grpc/policy/resourcemapping/resource_mapping_pb2_grpc.py +516 -0
- otdf_python_proto/legacy_grpc/policy/selectors_pb2_grpc.py +4 -0
- otdf_python_proto/legacy_grpc/policy/subjectmapping/subject_mapping_pb2_grpc.py +551 -0
- otdf_python_proto/legacy_grpc/policy/unsafe/unsafe_pb2_grpc.py +485 -0
- otdf_python_proto/legacy_grpc/wellknownconfiguration/wellknown_configuration_pb2_grpc.py +77 -0
- otdf_python_proto/logger/__init__.py +1 -0
- otdf_python_proto/logger/audit/test_pb2.py +43 -0
- otdf_python_proto/logger/audit/test_pb2.pyi +45 -0
- otdf_python_proto/policy/__init__.py +1 -0
- otdf_python_proto/policy/actions/actions_pb2.py +75 -0
- otdf_python_proto/policy/actions/actions_pb2.pyi +87 -0
- otdf_python_proto/policy/actions/actions_pb2_connect.py +275 -0
- otdf_python_proto/policy/attributes/attributes_pb2.py +234 -0
- otdf_python_proto/policy/attributes/attributes_pb2.pyi +328 -0
- otdf_python_proto/policy/attributes/attributes_pb2_connect.py +863 -0
- otdf_python_proto/policy/kasregistry/key_access_server_registry_pb2.py +266 -0
- otdf_python_proto/policy/kasregistry/key_access_server_registry_pb2.pyi +450 -0
- otdf_python_proto/policy/kasregistry/key_access_server_registry_pb2_connect.py +611 -0
- otdf_python_proto/policy/keymanagement/key_management_pb2.py +79 -0
- otdf_python_proto/policy/keymanagement/key_management_pb2.pyi +87 -0
- otdf_python_proto/policy/keymanagement/key_management_pb2_connect.py +275 -0
- otdf_python_proto/policy/namespaces/namespaces_pb2.py +117 -0
- otdf_python_proto/policy/namespaces/namespaces_pb2.pyi +147 -0
- otdf_python_proto/policy/namespaces/namespaces_pb2_connect.py +443 -0
- otdf_python_proto/policy/objects_pb2.py +150 -0
- otdf_python_proto/policy/objects_pb2.pyi +464 -0
- otdf_python_proto/policy/registeredresources/registered_resources_pb2.py +139 -0
- otdf_python_proto/policy/registeredresources/registered_resources_pb2.pyi +196 -0
- otdf_python_proto/policy/registeredresources/registered_resources_pb2_connect.py +527 -0
- otdf_python_proto/policy/resourcemapping/resource_mapping_pb2.py +139 -0
- otdf_python_proto/policy/resourcemapping/resource_mapping_pb2.pyi +194 -0
- otdf_python_proto/policy/resourcemapping/resource_mapping_pb2_connect.py +527 -0
- otdf_python_proto/policy/selectors_pb2.py +57 -0
- otdf_python_proto/policy/selectors_pb2.pyi +90 -0
- otdf_python_proto/policy/subjectmapping/subject_mapping_pb2.py +127 -0
- otdf_python_proto/policy/subjectmapping/subject_mapping_pb2.pyi +189 -0
- otdf_python_proto/policy/subjectmapping/subject_mapping_pb2_connect.py +569 -0
- otdf_python_proto/policy/unsafe/unsafe_pb2.py +113 -0
- otdf_python_proto/policy/unsafe/unsafe_pb2.pyi +145 -0
- otdf_python_proto/policy/unsafe/unsafe_pb2_connect.py +485 -0
- otdf_python_proto/wellknownconfiguration/__init__.py +1 -0
- otdf_python_proto/wellknownconfiguration/wellknown_configuration_pb2.py +51 -0
- otdf_python_proto/wellknownconfiguration/wellknown_configuration_pb2.pyi +32 -0
- otdf_python_proto/wellknownconfiguration/wellknown_configuration_pb2_connect.py +107 -0
- otdf_python/_gotdf_python.cpython-312-darwin.so +0 -0
- otdf_python/build.py +0 -190
- otdf_python/go.py +0 -1478
- otdf_python/gotdf_python.py +0 -383
- otdf_python-0.1.9.dist-info/METADATA +0 -149
- otdf_python-0.1.9.dist-info/RECORD +0 -10
- otdf_python-0.1.9.dist-info/top_level.txt +0 -1
|
@@ -0,0 +1,448 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Python port of the SDKBuilder class for OpenTDF platform interaction.
|
|
3
|
+
Provides methods to configure and build SDK instances.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import logging
|
|
7
|
+
import os
|
|
8
|
+
import ssl
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
import httpx
|
|
13
|
+
|
|
14
|
+
from otdf_python.sdk import KAS, SDK
|
|
15
|
+
from otdf_python.sdk_exceptions import AutoConfigureException
|
|
16
|
+
|
|
17
|
+
# Configure logging
|
|
18
|
+
logger = logging.getLogger(__name__)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass
|
|
22
|
+
class OAuthConfig:
|
|
23
|
+
client_id: str
|
|
24
|
+
client_secret: str
|
|
25
|
+
grant_type: str = "client_credentials"
|
|
26
|
+
scope: str = "openid profile email"
|
|
27
|
+
token_endpoint: str | None = None
|
|
28
|
+
access_token: str | None = None
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class SDKBuilder:
|
|
32
|
+
"""
|
|
33
|
+
A builder class for creating instances of the SDK class.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
PLATFORM_ISSUER = "platform_issuer"
|
|
37
|
+
|
|
38
|
+
# Class variable to store the latest platform URL
|
|
39
|
+
_platform_url = None
|
|
40
|
+
|
|
41
|
+
def __init__(self):
|
|
42
|
+
self.platform_endpoint: str | None = None
|
|
43
|
+
self.issuer_endpoint: str | None = None
|
|
44
|
+
self.oauth_config: OAuthConfig | None = None
|
|
45
|
+
self.use_plaintext: bool = False
|
|
46
|
+
self.insecure_skip_verify: bool = False
|
|
47
|
+
self.ssl_context: ssl.SSLContext | None = None
|
|
48
|
+
self.auth_token: str | None = None
|
|
49
|
+
self.cert_paths: list[str] = []
|
|
50
|
+
|
|
51
|
+
@staticmethod
|
|
52
|
+
def new_builder() -> "SDKBuilder":
|
|
53
|
+
"""
|
|
54
|
+
Creates a new SDKBuilder instance.
|
|
55
|
+
Returns:
|
|
56
|
+
SDKBuilder: A new builder instance
|
|
57
|
+
"""
|
|
58
|
+
return SDKBuilder()
|
|
59
|
+
|
|
60
|
+
@staticmethod
|
|
61
|
+
def get_platform_url() -> str | None:
|
|
62
|
+
"""
|
|
63
|
+
Gets the last set platform URL.
|
|
64
|
+
Returns:
|
|
65
|
+
str | None: The platform URL or None if not set
|
|
66
|
+
"""
|
|
67
|
+
return SDKBuilder._platform_url
|
|
68
|
+
|
|
69
|
+
def ssl_context_from_directory(self, certs_dir_path: str) -> "SDKBuilder":
|
|
70
|
+
"""
|
|
71
|
+
Add SSL Context with trusted certs from certDirPath
|
|
72
|
+
Args:
|
|
73
|
+
certs_dir_path: Path to a directory containing .pem or .crt trusted certs
|
|
74
|
+
Returns:
|
|
75
|
+
self: The builder instance for chaining
|
|
76
|
+
"""
|
|
77
|
+
self.cert_paths = []
|
|
78
|
+
|
|
79
|
+
# Find all .pem and .crt files in the directory
|
|
80
|
+
for filename in os.listdir(certs_dir_path):
|
|
81
|
+
if filename.endswith(".pem") or filename.endswith(".crt"):
|
|
82
|
+
self.cert_paths.append(os.path.join(certs_dir_path, filename))
|
|
83
|
+
|
|
84
|
+
# Create SSL context with these certificates
|
|
85
|
+
if self.cert_paths:
|
|
86
|
+
context = ssl.create_default_context(ssl.Purpose.SERVER_AUTH)
|
|
87
|
+
for cert_path in self.cert_paths:
|
|
88
|
+
context.load_verify_locations(cert_path)
|
|
89
|
+
self.ssl_context = context
|
|
90
|
+
|
|
91
|
+
return self
|
|
92
|
+
|
|
93
|
+
def client_secret(self, client_id: str, client_secret: str) -> "SDKBuilder":
|
|
94
|
+
"""
|
|
95
|
+
Sets client credentials for OAuth 2.0 client_credentials grant.
|
|
96
|
+
Args:
|
|
97
|
+
client_id: The OAuth client ID
|
|
98
|
+
client_secret: The OAuth client secret
|
|
99
|
+
Returns:
|
|
100
|
+
self: The builder instance for chaining
|
|
101
|
+
"""
|
|
102
|
+
self.oauth_config = OAuthConfig(
|
|
103
|
+
client_id=client_id, client_secret=client_secret
|
|
104
|
+
)
|
|
105
|
+
return self
|
|
106
|
+
|
|
107
|
+
def set_platform_endpoint(self, endpoint: str) -> "SDKBuilder":
|
|
108
|
+
"""
|
|
109
|
+
Sets the OpenTDF platform endpoint URL.
|
|
110
|
+
Args:
|
|
111
|
+
endpoint: The platform endpoint URL
|
|
112
|
+
Returns:
|
|
113
|
+
self: The builder instance for chaining
|
|
114
|
+
"""
|
|
115
|
+
# Normalize the endpoint URL
|
|
116
|
+
if endpoint and not (
|
|
117
|
+
endpoint.startswith("http://") or endpoint.startswith("https://")
|
|
118
|
+
):
|
|
119
|
+
if self.use_plaintext:
|
|
120
|
+
endpoint = f"http://{endpoint}"
|
|
121
|
+
else:
|
|
122
|
+
endpoint = f"https://{endpoint}"
|
|
123
|
+
|
|
124
|
+
self.platform_endpoint = endpoint
|
|
125
|
+
# Store in class variable for access from other components
|
|
126
|
+
SDKBuilder._platform_url = endpoint
|
|
127
|
+
return self
|
|
128
|
+
|
|
129
|
+
def set_issuer_endpoint(self, issuer: str) -> "SDKBuilder":
|
|
130
|
+
"""
|
|
131
|
+
Sets the OpenID Connect issuer endpoint URL.
|
|
132
|
+
Args:
|
|
133
|
+
issuer: The issuer endpoint URL
|
|
134
|
+
Returns:
|
|
135
|
+
self: The builder instance for chaining
|
|
136
|
+
"""
|
|
137
|
+
# Normalize the issuer URL
|
|
138
|
+
if issuer and not (
|
|
139
|
+
issuer.startswith("http://") or issuer.startswith("https://")
|
|
140
|
+
):
|
|
141
|
+
issuer = f"https://{issuer}"
|
|
142
|
+
|
|
143
|
+
self.issuer_endpoint = issuer
|
|
144
|
+
return self
|
|
145
|
+
|
|
146
|
+
def use_insecure_plaintext_connection(
|
|
147
|
+
self, use_plaintext: bool = True
|
|
148
|
+
) -> "SDKBuilder":
|
|
149
|
+
"""
|
|
150
|
+
Configures whether to use plain text (HTTP) connection instead of HTTPS.
|
|
151
|
+
Args:
|
|
152
|
+
use_plaintext: Whether to use plain text connection
|
|
153
|
+
Returns:
|
|
154
|
+
self: The builder instance for chaining
|
|
155
|
+
"""
|
|
156
|
+
self.use_plaintext = use_plaintext
|
|
157
|
+
|
|
158
|
+
# Update platform endpoint protocol if necessary
|
|
159
|
+
if self.platform_endpoint:
|
|
160
|
+
if use_plaintext and self.platform_endpoint.startswith("https://"):
|
|
161
|
+
self.platform_endpoint = f"http://{self.platform_endpoint[8:]}"
|
|
162
|
+
elif not use_plaintext and self.platform_endpoint.startswith("http://"):
|
|
163
|
+
self.platform_endpoint = f"https://{self.platform_endpoint[7:]}"
|
|
164
|
+
|
|
165
|
+
# Update the class variable as well since kas() method uses it
|
|
166
|
+
SDKBuilder._platform_url = self.platform_endpoint
|
|
167
|
+
|
|
168
|
+
return self
|
|
169
|
+
|
|
170
|
+
def use_insecure_skip_verify(self, skip_verify: bool = True) -> "SDKBuilder":
|
|
171
|
+
"""
|
|
172
|
+
Configures whether to skip SSL verification.
|
|
173
|
+
Args:
|
|
174
|
+
skip_verify: Whether to skip SSL verification
|
|
175
|
+
Returns:
|
|
176
|
+
self: The builder instance for chaining
|
|
177
|
+
"""
|
|
178
|
+
self.insecure_skip_verify = skip_verify
|
|
179
|
+
|
|
180
|
+
# If skipping verification, create a default SSL context that does not verify
|
|
181
|
+
if skip_verify:
|
|
182
|
+
self.ssl_context = ssl._create_unverified_context()
|
|
183
|
+
|
|
184
|
+
return self
|
|
185
|
+
|
|
186
|
+
def bearer_token(self, token: str) -> "SDKBuilder":
|
|
187
|
+
"""
|
|
188
|
+
Sets a bearer token to use for authorization.
|
|
189
|
+
Args:
|
|
190
|
+
token: The bearer token
|
|
191
|
+
Returns:
|
|
192
|
+
self: The builder instance for chaining
|
|
193
|
+
"""
|
|
194
|
+
self.auth_token = token
|
|
195
|
+
return self
|
|
196
|
+
|
|
197
|
+
def _discover_token_endpoint_from_platform(self) -> None:
|
|
198
|
+
"""
|
|
199
|
+
Discover token endpoint using OpenTDF platform configuration.
|
|
200
|
+
Raises:
|
|
201
|
+
AutoConfigureException: If discovery fails
|
|
202
|
+
"""
|
|
203
|
+
if not self.platform_endpoint or not self.oauth_config:
|
|
204
|
+
return
|
|
205
|
+
|
|
206
|
+
# Try to get OpenTDF configuration first
|
|
207
|
+
well_known_url = f"{self.platform_endpoint}/.well-known/opentdf-configuration"
|
|
208
|
+
response = httpx.get(well_known_url, verify=not self.insecure_skip_verify)
|
|
209
|
+
|
|
210
|
+
if response.status_code != 200:
|
|
211
|
+
raise AutoConfigureException(
|
|
212
|
+
f"Failed to retrieve OpenTDF configuration from {well_known_url} (status: {response.status_code}). "
|
|
213
|
+
"Please provide an explicit issuer endpoint or check platform URL."
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
config_doc = response.json()
|
|
217
|
+
configuration = config_doc.get("configuration", {})
|
|
218
|
+
|
|
219
|
+
# Try to get token endpoint from IDP configuration
|
|
220
|
+
idp_config = configuration.get("idp", {})
|
|
221
|
+
if idp_config.get("token_endpoint"):
|
|
222
|
+
self.oauth_config.token_endpoint = idp_config["token_endpoint"]
|
|
223
|
+
return
|
|
224
|
+
|
|
225
|
+
# Fall back to using platform_issuer for OIDC discovery
|
|
226
|
+
platform_issuer = configuration.get("platform_issuer")
|
|
227
|
+
if not platform_issuer:
|
|
228
|
+
raise AutoConfigureException(
|
|
229
|
+
"No platform_issuer found in OpenTDF configuration"
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
self._discover_token_endpoint_from_issuer(platform_issuer)
|
|
233
|
+
|
|
234
|
+
def _discover_token_endpoint_from_issuer(self, issuer_url: str) -> None:
|
|
235
|
+
"""
|
|
236
|
+
Discover token endpoint using OIDC discovery from issuer.
|
|
237
|
+
Args:
|
|
238
|
+
issuer_url: The issuer URL to use for discovery
|
|
239
|
+
Raises:
|
|
240
|
+
AutoConfigureException: If discovery fails
|
|
241
|
+
"""
|
|
242
|
+
if not self.oauth_config:
|
|
243
|
+
return
|
|
244
|
+
|
|
245
|
+
oidc_discovery_url = f"{issuer_url}/.well-known/openid-configuration"
|
|
246
|
+
oidc_response = httpx.get(
|
|
247
|
+
oidc_discovery_url, verify=not self.insecure_skip_verify
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
if oidc_response.status_code != 200:
|
|
251
|
+
raise AutoConfigureException(
|
|
252
|
+
f"Failed to retrieve OIDC configuration from {oidc_discovery_url}: {oidc_response.status_code}"
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
oidc_doc = oidc_response.json()
|
|
256
|
+
self.oauth_config.token_endpoint = oidc_doc.get("token_endpoint")
|
|
257
|
+
if not self.oauth_config.token_endpoint:
|
|
258
|
+
raise AutoConfigureException(
|
|
259
|
+
"Token endpoint not found in OIDC discovery document"
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
def _discover_token_endpoint(self) -> None:
|
|
263
|
+
"""
|
|
264
|
+
Discover the token endpoint using available configuration.
|
|
265
|
+
Raises:
|
|
266
|
+
AutoConfigureException: If discovery fails
|
|
267
|
+
"""
|
|
268
|
+
# Try platform endpoint first
|
|
269
|
+
if self.platform_endpoint:
|
|
270
|
+
try:
|
|
271
|
+
self._discover_token_endpoint_from_platform()
|
|
272
|
+
return
|
|
273
|
+
except Exception as e:
|
|
274
|
+
# If platform fails and we have an explicit issuer, try that
|
|
275
|
+
if self.issuer_endpoint:
|
|
276
|
+
try:
|
|
277
|
+
realm_name = "opentdf" # Default realm name
|
|
278
|
+
issuer_url = f"{self.issuer_endpoint}/realms/{realm_name}"
|
|
279
|
+
self._discover_token_endpoint_from_issuer(issuer_url)
|
|
280
|
+
return
|
|
281
|
+
except Exception:
|
|
282
|
+
# Re-raise the original platform error
|
|
283
|
+
pass
|
|
284
|
+
raise AutoConfigureException(
|
|
285
|
+
f"Error during token endpoint discovery: {e!s}"
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
# Fall back to explicit issuer endpoint
|
|
289
|
+
if self.issuer_endpoint:
|
|
290
|
+
realm_name = "opentdf" # Default realm name
|
|
291
|
+
issuer_url = f"{self.issuer_endpoint}/realms/{realm_name}"
|
|
292
|
+
self._discover_token_endpoint_from_issuer(issuer_url)
|
|
293
|
+
return
|
|
294
|
+
|
|
295
|
+
raise AutoConfigureException(
|
|
296
|
+
"Platform endpoint or issuer endpoint must be configured for OIDC token discovery"
|
|
297
|
+
)
|
|
298
|
+
|
|
299
|
+
def _get_token_from_client_credentials(self) -> str:
|
|
300
|
+
"""
|
|
301
|
+
Obtains an OAuth token using client credentials.
|
|
302
|
+
Returns:
|
|
303
|
+
str: The access token
|
|
304
|
+
Raises:
|
|
305
|
+
AutoConfigureException: If token acquisition fails
|
|
306
|
+
"""
|
|
307
|
+
if not self.oauth_config:
|
|
308
|
+
raise AutoConfigureException("OAuth configuration is not set")
|
|
309
|
+
|
|
310
|
+
if not self.oauth_config.token_endpoint:
|
|
311
|
+
self._discover_token_endpoint()
|
|
312
|
+
|
|
313
|
+
# Ensure we have a token endpoint before proceeding
|
|
314
|
+
if not self.oauth_config.token_endpoint:
|
|
315
|
+
raise AutoConfigureException("Token endpoint discovery failed")
|
|
316
|
+
|
|
317
|
+
# Request the token
|
|
318
|
+
try:
|
|
319
|
+
token_data = {
|
|
320
|
+
"grant_type": self.oauth_config.grant_type,
|
|
321
|
+
"client_id": self.oauth_config.client_id,
|
|
322
|
+
"client_secret": self.oauth_config.client_secret,
|
|
323
|
+
"scope": self.oauth_config.scope,
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
response = httpx.post(
|
|
327
|
+
self.oauth_config.token_endpoint,
|
|
328
|
+
data=token_data,
|
|
329
|
+
verify=not self.insecure_skip_verify,
|
|
330
|
+
)
|
|
331
|
+
|
|
332
|
+
if response.status_code == 200:
|
|
333
|
+
token_response = response.json()
|
|
334
|
+
access_token = token_response.get("access_token")
|
|
335
|
+
if not access_token:
|
|
336
|
+
raise AutoConfigureException("No access_token in token response")
|
|
337
|
+
return access_token
|
|
338
|
+
else:
|
|
339
|
+
raise AutoConfigureException(
|
|
340
|
+
f"Token request failed: {response.status_code} - {response.text}"
|
|
341
|
+
)
|
|
342
|
+
|
|
343
|
+
except Exception as e:
|
|
344
|
+
raise AutoConfigureException(f"Error during token acquisition: {e!s}")
|
|
345
|
+
|
|
346
|
+
def _create_auth_interceptor(self) -> Any:
|
|
347
|
+
"""
|
|
348
|
+
Creates an authentication interceptor for API requests (httpx).
|
|
349
|
+
Returns:
|
|
350
|
+
Any: An auth interceptor object
|
|
351
|
+
Raises:
|
|
352
|
+
AutoConfigureException: If auth configuration fails
|
|
353
|
+
"""
|
|
354
|
+
# For now, this is just a placeholder returning a dict with auth headers
|
|
355
|
+
# In a real implementation, this would create a proper interceptor object
|
|
356
|
+
# that injects auth headers into httpx requests
|
|
357
|
+
|
|
358
|
+
token = None
|
|
359
|
+
|
|
360
|
+
if self.auth_token:
|
|
361
|
+
# Use provided token
|
|
362
|
+
token = self.auth_token
|
|
363
|
+
elif self.oauth_config:
|
|
364
|
+
# Get token from OAuth
|
|
365
|
+
token = self._get_token_from_client_credentials()
|
|
366
|
+
|
|
367
|
+
if token:
|
|
368
|
+
return {"Authorization": f"Bearer {token}"}
|
|
369
|
+
|
|
370
|
+
return None
|
|
371
|
+
|
|
372
|
+
def _create_services(self) -> SDK.Services:
|
|
373
|
+
"""
|
|
374
|
+
Creates service client instances.
|
|
375
|
+
Returns:
|
|
376
|
+
SDK.Services: The service client instances
|
|
377
|
+
Raises:
|
|
378
|
+
AutoConfigureException: If service creation fails
|
|
379
|
+
"""
|
|
380
|
+
# For now, return a simple implementation of Services
|
|
381
|
+
# In a real implementation, this would create actual service clients
|
|
382
|
+
# connecting to the platform endpoints
|
|
383
|
+
|
|
384
|
+
ssl_verify = not self.insecure_skip_verify
|
|
385
|
+
auth_interceptor = self._create_auth_interceptor()
|
|
386
|
+
|
|
387
|
+
class ServicesImpl(SDK.Services):
|
|
388
|
+
def __init__(self, builder_instance):
|
|
389
|
+
self.closed = False
|
|
390
|
+
self._ssl_verify = ssl_verify
|
|
391
|
+
self._auth_headers = auth_interceptor if auth_interceptor else {}
|
|
392
|
+
self._builder = builder_instance
|
|
393
|
+
|
|
394
|
+
def kas(self) -> KAS:
|
|
395
|
+
"""
|
|
396
|
+
Returns the KAS interface with SSL verification settings.
|
|
397
|
+
"""
|
|
398
|
+
platform_url = SDKBuilder.get_platform_url()
|
|
399
|
+
|
|
400
|
+
# Create a token source function that can refresh tokens
|
|
401
|
+
def token_source():
|
|
402
|
+
if self._builder.auth_token:
|
|
403
|
+
return self._builder.auth_token
|
|
404
|
+
elif self._builder.oauth_config:
|
|
405
|
+
return self._builder._get_token_from_client_credentials()
|
|
406
|
+
return None
|
|
407
|
+
|
|
408
|
+
kas_impl = KAS(
|
|
409
|
+
platform_url=platform_url,
|
|
410
|
+
token_source=token_source,
|
|
411
|
+
sdk_ssl_verify=self._ssl_verify,
|
|
412
|
+
use_plaintext=self._builder.use_plaintext,
|
|
413
|
+
)
|
|
414
|
+
return kas_impl
|
|
415
|
+
|
|
416
|
+
def close(self):
|
|
417
|
+
self.closed = True
|
|
418
|
+
|
|
419
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
420
|
+
self.close()
|
|
421
|
+
|
|
422
|
+
return ServicesImpl(self)
|
|
423
|
+
|
|
424
|
+
def build(self) -> SDK:
|
|
425
|
+
"""
|
|
426
|
+
Builds and returns an SDK instance with the configured properties.
|
|
427
|
+
Returns:
|
|
428
|
+
SDK: The configured SDK instance
|
|
429
|
+
Raises:
|
|
430
|
+
AutoConfigureException: If the build fails
|
|
431
|
+
"""
|
|
432
|
+
if not self.platform_endpoint:
|
|
433
|
+
raise AutoConfigureException("Platform endpoint is not set")
|
|
434
|
+
|
|
435
|
+
# Create the auth interceptor
|
|
436
|
+
auth_interceptor = self._create_auth_interceptor()
|
|
437
|
+
|
|
438
|
+
# Create services
|
|
439
|
+
services = self._create_services()
|
|
440
|
+
|
|
441
|
+
# Return the SDK instance, platform_url is set for new_tdf_config
|
|
442
|
+
return SDK(
|
|
443
|
+
services=services,
|
|
444
|
+
auth_interceptor=auth_interceptor,
|
|
445
|
+
platform_url=self.platform_endpoint,
|
|
446
|
+
ssl_verify=not self.insecure_skip_verify,
|
|
447
|
+
use_plaintext=getattr(self, "use_plaintext", False),
|
|
448
|
+
)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
class SDKException(Exception):
|
|
2
|
+
def __init__(self, message, reason=None):
|
|
3
|
+
super().__init__(message)
|
|
4
|
+
self.reason = reason
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class AutoConfigureException(SDKException):
|
|
8
|
+
def __init__(self, message, cause=None):
|
|
9
|
+
super().__init__(message, cause)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class KASBadRequestException(SDKException):
|
|
13
|
+
"""Thrown when the KAS returns a bad request response or other client request errors."""
|
|
14
|
+
|
|
15
|
+
def __init__(self, message):
|
|
16
|
+
super().__init__(message)
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
class SymmetricAndPayloadConfig:
|
|
2
|
+
def __init__(
|
|
3
|
+
self,
|
|
4
|
+
cipher_type: int = 0,
|
|
5
|
+
signature_ecc_mode: int = 0,
|
|
6
|
+
has_signature: bool = True,
|
|
7
|
+
):
|
|
8
|
+
self.cipher_type = cipher_type
|
|
9
|
+
self.signature_ecc_mode = signature_ecc_mode
|
|
10
|
+
self.has_signature = has_signature
|
|
11
|
+
|
|
12
|
+
def set_has_signature(self, flag: bool):
|
|
13
|
+
self.has_signature = flag
|
|
14
|
+
|
|
15
|
+
def set_signature_ecc_mode(self, mode: int):
|
|
16
|
+
self.signature_ecc_mode = mode
|
|
17
|
+
|
|
18
|
+
def set_symmetric_cipher_type(self, cipher_type: int):
|
|
19
|
+
self.cipher_type = cipher_type
|
|
20
|
+
|
|
21
|
+
def get_cipher_type(self) -> int:
|
|
22
|
+
return self.cipher_type
|
|
23
|
+
|
|
24
|
+
def get_symmetric_and_payload_config_as_byte(self) -> int:
|
|
25
|
+
# Most significant bit: has_signature, next 3 bits: signature_ecc_mode, lower 4 bits: cipher_type
|
|
26
|
+
return (
|
|
27
|
+
((1 if self.has_signature else 0) << 7)
|
|
28
|
+
| ((self.signature_ecc_mode & 0x07) << 4)
|
|
29
|
+
| (self.cipher_type & 0x0F)
|
|
30
|
+
)
|