esd-services-api-client 1.1.3__tar.gz → 1.1.5__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {esd_services_api_client-1.1.3 → esd_services_api_client-1.1.5}/PKG-INFO +1 -1
- esd_services_api_client-1.1.5/esd_services_api_client/_version.py +1 -0
- {esd_services_api_client-1.1.3 → esd_services_api_client-1.1.5}/esd_services_api_client/beast/v3/_connector.py +10 -0
- esd_services_api_client-1.1.5/esd_services_api_client/boxer/README.md +103 -0
- {esd_services_api_client-1.1.3 → esd_services_api_client-1.1.5}/esd_services_api_client/boxer/_connector.py +112 -93
- esd_services_api_client-1.1.5/esd_services_api_client/boxer/_models.py +75 -0
- {esd_services_api_client-1.1.3 → esd_services_api_client-1.1.5}/pyproject.toml +1 -1
- esd_services_api_client-1.1.3/esd_services_api_client/_version.py +0 -1
- esd_services_api_client-1.1.3/esd_services_api_client/boxer/README.md +0 -91
- esd_services_api_client-1.1.3/esd_services_api_client/boxer/_helpers.py +0 -46
- esd_services_api_client-1.1.3/esd_services_api_client/boxer/_models.py +0 -131
- {esd_services_api_client-1.1.3 → esd_services_api_client-1.1.5}/LICENSE +0 -0
- {esd_services_api_client-1.1.3 → esd_services_api_client-1.1.5}/README.md +0 -0
- {esd_services_api_client-1.1.3 → esd_services_api_client-1.1.5}/esd_services_api_client/__init__.py +0 -0
- {esd_services_api_client-1.1.3 → esd_services_api_client-1.1.5}/esd_services_api_client/beast/__init__.py +0 -0
- {esd_services_api_client-1.1.3 → esd_services_api_client-1.1.5}/esd_services_api_client/beast/v2/__init__.py +0 -0
- {esd_services_api_client-1.1.3 → esd_services_api_client-1.1.5}/esd_services_api_client/beast/v2/_connector.py +0 -0
- {esd_services_api_client-1.1.3 → esd_services_api_client-1.1.5}/esd_services_api_client/beast/v2/_models.py +0 -0
- {esd_services_api_client-1.1.3 → esd_services_api_client-1.1.5}/esd_services_api_client/beast/v3/__init__.py +0 -0
- {esd_services_api_client-1.1.3 → esd_services_api_client-1.1.5}/esd_services_api_client/beast/v3/_models.py +0 -0
- {esd_services_api_client-1.1.3 → esd_services_api_client-1.1.5}/esd_services_api_client/boxer/__init__.py +0 -0
- {esd_services_api_client-1.1.3 → esd_services_api_client-1.1.5}/esd_services_api_client/boxer/_auth.py +0 -0
- {esd_services_api_client-1.1.3 → esd_services_api_client-1.1.5}/esd_services_api_client/boxer/_base.py +0 -0
- {esd_services_api_client-1.1.3 → esd_services_api_client-1.1.5}/esd_services_api_client/common/__init__.py +0 -0
- {esd_services_api_client-1.1.3 → esd_services_api_client-1.1.5}/esd_services_api_client/crystal/__init__.py +0 -0
- {esd_services_api_client-1.1.3 → esd_services_api_client-1.1.5}/esd_services_api_client/crystal/_api_versions.py +0 -0
- {esd_services_api_client-1.1.3 → esd_services_api_client-1.1.5}/esd_services_api_client/crystal/_connector.py +0 -0
- {esd_services_api_client-1.1.3 → esd_services_api_client-1.1.5}/esd_services_api_client/crystal/_models.py +0 -0
@@ -0,0 +1 @@
|
|
1
|
+
__version__ = '1.1.5'
|
@@ -254,6 +254,16 @@ class BeastConnector:
|
|
254
254
|
|
255
255
|
return response.json()["lifeCycleStage"]
|
256
256
|
|
257
|
+
def get_request_runtime_info(self, request_id: str) -> Optional[dict]:
|
258
|
+
"""
|
259
|
+
Returns the runtime information for the given request. Returns None in case error retry fails to resolve within given timeout.
|
260
|
+
:param request_id: A request identifier to read runtime info for.
|
261
|
+
"""
|
262
|
+
response = self.http.get(f"{self.base_url}/job/requests/{request_id}")
|
263
|
+
response.raise_for_status()
|
264
|
+
|
265
|
+
return response.json()
|
266
|
+
|
257
267
|
def start_job(self, job_params: BeastJobParams, job_name: str) -> Optional[str]:
|
258
268
|
"""
|
259
269
|
Starts a job through Beast.
|
@@ -0,0 +1,103 @@
|
|
1
|
+
# Boxer API Connector
|
2
|
+
|
3
|
+
Conenctor for working with [Boxer](https://github.com/SneaksAndData/boxer) AuthZ/AuthN API.
|
4
|
+
|
5
|
+
## Usage
|
6
|
+
|
7
|
+
Two environment variables must be set before you can use this connector:
|
8
|
+
|
9
|
+
```shell
|
10
|
+
export BOXER_CONSUMER_ID="my_app_consumer"
|
11
|
+
export BOXER_PRIVATE_KEY="MIIEpAIBAA..."
|
12
|
+
```
|
13
|
+
|
14
|
+
### Retrieving Claims:
|
15
|
+
|
16
|
+
```python
|
17
|
+
from esd_services_api_client.boxer import select_authentication, BoxerClaimConnector
|
18
|
+
auth = select_authentication("azuread", "test")
|
19
|
+
conn = BoxerClaimConnector(base_url="https://boxer-claim.test.sneaksanddata.com", auth=auth)
|
20
|
+
resp = conn.get_claims("email@ecco.com", "azuread")
|
21
|
+
for claim in resp:
|
22
|
+
print(claim.to_dict())
|
23
|
+
```
|
24
|
+
Output:
|
25
|
+
```bash
|
26
|
+
{'claim_name':'test1.test.sneaksanddata.com/.*', 'claim_value':'.*'}
|
27
|
+
{'claim_name':'test2.test.sneaksanddata.com/.*', 'claim_value':'.*'}
|
28
|
+
```
|
29
|
+
|
30
|
+
### Insert claims:
|
31
|
+
```python
|
32
|
+
from esd_services_api_client.boxer import select_authentication, BoxerClaimConnector, Claim
|
33
|
+
auth = select_authentication("azuread", "test")
|
34
|
+
conn = BoxerClaimConnector(base_url="https://boxer-claim.test.sneaksanddata.com", auth=auth)
|
35
|
+
claims = [Claim("some-test-1.test.sneaksanddata.com", ".*"), Claim("some-test-2.test.sneaksanddata.com", ".*")]
|
36
|
+
resp = conn.add_claim("email@ecco.com", "azuread", claims)
|
37
|
+
print(resp)
|
38
|
+
```
|
39
|
+
Output:
|
40
|
+
```bash
|
41
|
+
ClaimResponse(identity_provider='azuread', user_id='email@ecco.com', claims=[{'some-test-1.test.sneaksanddata.com': '.*'}, {'some-test-2.test.sneaksanddata.com': '.*'}], billing_id= None}
|
42
|
+
```
|
43
|
+
|
44
|
+
### Remove claims:
|
45
|
+
```python
|
46
|
+
from esd_services_api_client.boxer import select_authentication, BoxerClaimConnector, Claim
|
47
|
+
auth = select_authentication("azuread", "test")
|
48
|
+
conn = BoxerClaimConnector(base_url="https://boxer-claim.test.sneaksanddata.com", auth=auth)
|
49
|
+
claims = [Claim("some-test-1.test.sneaksanddata.com", ".*"), Claim("some-test-2.test.sneaksanddata.com", ".*")]
|
50
|
+
resp = conn.remove_claim("email@ecco.com", "azuread", claims)
|
51
|
+
print(resp)
|
52
|
+
```
|
53
|
+
Output:
|
54
|
+
```bash
|
55
|
+
ClaimResponse(identity_provider='azuread', user_id='email@ecco.com', claims=[], billing_id= None}
|
56
|
+
```
|
57
|
+
|
58
|
+
### Add a user:
|
59
|
+
```python
|
60
|
+
from esd_services_api_client.boxer import select_authentication, BoxerClaimConnector, Claim
|
61
|
+
auth = select_authentication("azuread", "test")
|
62
|
+
conn = BoxerClaimConnector(base_url="https://boxer-claim.test.sneaksanddata.com", auth=auth)
|
63
|
+
resp = conn.add_user("test@ecco.com", "azuread")
|
64
|
+
print(resp)
|
65
|
+
```
|
66
|
+
Output:
|
67
|
+
```bash
|
68
|
+
ClaimResponse(identity_provider='azuread', user_id='test@ecco.com', claims=[], billing_id=None)
|
69
|
+
```
|
70
|
+
|
71
|
+
### Remove a user:
|
72
|
+
```python
|
73
|
+
from esd_services_api_client.boxer import select_authentication, BoxerClaimConnector, Claim
|
74
|
+
auth = select_authentication("azuread", "test")
|
75
|
+
conn = BoxerClaimConnector(base_url="https://boxer-claim.test.sneaksanddata.com", auth=auth)
|
76
|
+
resp = conn.remove_user("test@ecco.com", "azuread")
|
77
|
+
print(resp.status_code)
|
78
|
+
```
|
79
|
+
Output:
|
80
|
+
```bash
|
81
|
+
200
|
82
|
+
```
|
83
|
+
|
84
|
+
### Using as an authentication provider for other connectors
|
85
|
+
```python
|
86
|
+
from esd_services_api_client.boxer import BoxerConnector, RefreshableExternalTokenAuth, BoxerTokenAuth
|
87
|
+
from esd_services_api_client.crystal import CrystalConnector
|
88
|
+
|
89
|
+
auth_method = "example"
|
90
|
+
|
91
|
+
def get_external_token() -> str:
|
92
|
+
return "example_token"
|
93
|
+
|
94
|
+
# Configure authentication with boxer
|
95
|
+
external_auth = RefreshableExternalTokenAuth(lambda: get_external_token(), auth_method)
|
96
|
+
boxer_connector = BoxerConnector(base_url="https://example.com", auth=external_auth)
|
97
|
+
|
98
|
+
# Inject boxer auth to Crystal connector
|
99
|
+
connector = CrystalConnector(base_url="https://example.com", auth=BoxerTokenAuth(boxer_connector))
|
100
|
+
|
101
|
+
# Use Crystal connector with boxer auth
|
102
|
+
connector.await_runs("algorithm", ["id"])
|
103
|
+
```
|
@@ -17,12 +17,12 @@
|
|
17
17
|
#
|
18
18
|
|
19
19
|
import os
|
20
|
-
from
|
20
|
+
from functools import reduce
|
21
|
+
from typing import Optional, Iterator, final
|
21
22
|
|
22
|
-
import jwt
|
23
23
|
from adapta.security.clients import AzureClient
|
24
24
|
from adapta.utils import session_with_retries
|
25
|
-
from requests import Session
|
25
|
+
from requests import Session, Response
|
26
26
|
|
27
27
|
from esd_services_api_client.boxer._base import BoxerTokenProvider
|
28
28
|
from esd_services_api_client.boxer._auth import (
|
@@ -32,11 +32,116 @@ from esd_services_api_client.boxer._auth import (
|
|
32
32
|
ExternalAuthBase,
|
33
33
|
RefreshableExternalTokenAuth,
|
34
34
|
)
|
35
|
-
from esd_services_api_client.boxer.
|
36
|
-
|
37
|
-
|
35
|
+
from esd_services_api_client.boxer._models import (
|
36
|
+
BoxerToken,
|
37
|
+
Claim,
|
38
|
+
ClaimPayload,
|
39
|
+
ClaimResponse,
|
38
40
|
)
|
39
|
-
|
41
|
+
|
42
|
+
|
43
|
+
@final
|
44
|
+
class BoxerClaimConnector:
|
45
|
+
"""
|
46
|
+
Boxer Claims API connector
|
47
|
+
"""
|
48
|
+
|
49
|
+
def __init__(self, *, base_url: str, auth: Optional[BoxerTokenAuth] = None):
|
50
|
+
"""Creates Boxer Claims connector, capable of managing claims
|
51
|
+
:param base_url: Base URL for Boxer Claims endpoint
|
52
|
+
:param auth: Boxer-based authentication
|
53
|
+
"""
|
54
|
+
self._base_url = base_url
|
55
|
+
self._http = session_with_retries()
|
56
|
+
if auth and isinstance(auth, BoxerTokenAuth):
|
57
|
+
self._http.hooks["response"].append(auth.get_refresh_hook(self._http))
|
58
|
+
self._http.auth = auth
|
59
|
+
|
60
|
+
def get_claims(self, user_id: str, provider: str) -> Optional[Iterator[Claim]]:
|
61
|
+
"""
|
62
|
+
Returns the claims assigned to the specified user_id and provider
|
63
|
+
"""
|
64
|
+
response = self._http.get(f"{self._base_url}/claim/{provider}/{user_id}")
|
65
|
+
if response.status_code == 404:
|
66
|
+
return None
|
67
|
+
response.raise_for_status()
|
68
|
+
return self._iterate_user_claims_response(response)
|
69
|
+
|
70
|
+
def add_user(self, user_id: str, provider: str) -> ClaimResponse:
|
71
|
+
"""
|
72
|
+
Adds a new user_id, provider pair
|
73
|
+
"""
|
74
|
+
response = self._http.post(f"{self._base_url}/claim/{provider}/{user_id}")
|
75
|
+
response.raise_for_status()
|
76
|
+
return ClaimResponse.from_dict(response.json())
|
77
|
+
|
78
|
+
def remove_user(self, user_id: str, provider: str) -> Response:
|
79
|
+
"""
|
80
|
+
Removes the specified user_id, provider pair and assigned claims
|
81
|
+
"""
|
82
|
+
response = self._http.delete(f"{self._base_url}/claim/{provider}/{user_id}")
|
83
|
+
response.raise_for_status()
|
84
|
+
return response
|
85
|
+
|
86
|
+
def add_claim(
|
87
|
+
self, user_id: str, provider: str, claims: list[Claim]
|
88
|
+
) -> Optional[ClaimResponse]:
|
89
|
+
"""
|
90
|
+
Adds a new claim to an existing user_id, provider pair
|
91
|
+
"""
|
92
|
+
payload_json = self._prepare_claim_payload(user_id, provider, claims, "Insert")
|
93
|
+
response = self._http.patch(
|
94
|
+
f"{self._base_url}/claim/{provider}/{user_id}",
|
95
|
+
data=payload_json,
|
96
|
+
headers={"Content-Type": "application/json"},
|
97
|
+
)
|
98
|
+
response.raise_for_status()
|
99
|
+
return ClaimResponse.from_dict(response.json())
|
100
|
+
|
101
|
+
def remove_claim(
|
102
|
+
self, user_id: str, provider: str, claims: list[Claim]
|
103
|
+
) -> Optional[ClaimResponse]:
|
104
|
+
"""
|
105
|
+
Removes the specified claim
|
106
|
+
"""
|
107
|
+
payload_json = self._prepare_claim_payload(user_id, provider, claims, "Delete")
|
108
|
+
response = self._http.patch(
|
109
|
+
f"{self._base_url}/claim/{provider}/{user_id}",
|
110
|
+
data=payload_json,
|
111
|
+
headers={"Content-Type": "application/json"},
|
112
|
+
)
|
113
|
+
return ClaimResponse.from_dict(response.json())
|
114
|
+
|
115
|
+
def _prepare_claim_payload(
|
116
|
+
self, user_id: str, provider: str, claims: list[Claim], operation: str
|
117
|
+
) -> Optional[str]:
|
118
|
+
"""
|
119
|
+
Prepare payload for Inserting/Deleting claims
|
120
|
+
"""
|
121
|
+
if self.get_claims(user_id, provider) is not None:
|
122
|
+
payload = ClaimPayload(operation, {})
|
123
|
+
claim_payload = reduce(
|
124
|
+
lambda cp, claim: cp.add_claim(claim), claims, payload
|
125
|
+
)
|
126
|
+
|
127
|
+
return claim_payload.to_json()
|
128
|
+
return None
|
129
|
+
|
130
|
+
def _iterate_user_claims_response(
|
131
|
+
self, user_claim_response: Response
|
132
|
+
) -> Optional[Iterator[Claim]]:
|
133
|
+
"""Creates an iterator to iterate user claims from Json Response
|
134
|
+
:param user_claim_response: HTTP Response
|
135
|
+
"""
|
136
|
+
response_json = user_claim_response.json()
|
137
|
+
if response_json and "claims" in response_json:
|
138
|
+
for claim in response_json["claims"]:
|
139
|
+
if isinstance(claim, dict) and len(claim) == 1:
|
140
|
+
for key, value in claim.items():
|
141
|
+
yield Claim.from_dict({"claim_name": key, "claim_value": value})
|
142
|
+
break
|
143
|
+
else:
|
144
|
+
raise ValueError("Expected response body of type application/json")
|
40
145
|
|
41
146
|
|
42
147
|
class BoxerConnector(BoxerTokenProvider):
|
@@ -65,92 +170,6 @@ class BoxerConnector(BoxerTokenProvider):
|
|
65
170
|
self.http.hooks["response"].append(auth.get_refresh_hook(self.http))
|
66
171
|
self.retry_attempts = retry_attempts
|
67
172
|
|
68
|
-
def push_user_claim(self, claim: BoxerClaim, user_id: str):
|
69
|
-
"""Adds/Overwrites a new Boxer Claim to a user
|
70
|
-
:param claim: Boxer Claim
|
71
|
-
:param user_id: User's UPN
|
72
|
-
:return:
|
73
|
-
"""
|
74
|
-
target_url = f"{self.base_url}/claims/user/{user_id}"
|
75
|
-
claim_json = claim.to_dict()
|
76
|
-
response = self.http.post(target_url, json=claim_json)
|
77
|
-
response.raise_for_status()
|
78
|
-
print(f"Successfully pushed user claim for user {user_id}")
|
79
|
-
|
80
|
-
def push_group_claim(self, claim: BoxerClaim, group_name: str):
|
81
|
-
"""Adds/Overwrites a new Boxer Claim to a user
|
82
|
-
:param claim: Boxer Claim
|
83
|
-
:param group_name: Group Name
|
84
|
-
:return:
|
85
|
-
"""
|
86
|
-
target_url = f"{self.base_url}/claims/group/{group_name}"
|
87
|
-
claim_json = claim.to_dict()
|
88
|
-
response = self.http.post(target_url, json=claim_json)
|
89
|
-
response.raise_for_status()
|
90
|
-
print(f"Successfully pushed user claim for group {group_name}")
|
91
|
-
|
92
|
-
def get_claims_by_type(self, claims_type: str) -> Iterator[UserClaim]:
|
93
|
-
"""Reads claims of specified type from Boxer.
|
94
|
-
:param claims_type: claim type to filter claims by.
|
95
|
-
:return: Iterator[UserClaim]
|
96
|
-
"""
|
97
|
-
target_url = f"{self.base_url}/claims/type/{claims_type}"
|
98
|
-
response = self.http.get(target_url)
|
99
|
-
response.raise_for_status()
|
100
|
-
return _iterate_user_claims_response(response)
|
101
|
-
|
102
|
-
def get_claims_by_user(self, user_id: str) -> Iterator[BoxerClaim]:
|
103
|
-
"""Reads user claims from Boxer
|
104
|
-
:param user_id: user upn to load claims for
|
105
|
-
:return: Iterator[UserClaim]
|
106
|
-
"""
|
107
|
-
empty_user_token = jwt.encode({"upn": user_id}, "_", algorithm="HS256")
|
108
|
-
target_url = f"{self.base_url}/claims/user/{empty_user_token}"
|
109
|
-
response = self.http.get(target_url)
|
110
|
-
response.raise_for_status()
|
111
|
-
return _iterate_boxer_claims_response(response)
|
112
|
-
|
113
|
-
def get_claims_by_group(self, group_name: str) -> Iterator[BoxerClaim]:
|
114
|
-
"""Reads group claims from Boxer
|
115
|
-
:param group_name: group name to load claims for
|
116
|
-
:return: Iterator[UserClaim]
|
117
|
-
"""
|
118
|
-
target_url = f"{self.base_url}/claims/group/{group_name}"
|
119
|
-
response = self.http.get(target_url)
|
120
|
-
response.raise_for_status()
|
121
|
-
return _iterate_boxer_claims_response(response)
|
122
|
-
|
123
|
-
def get_claims_for_token(self, jwt_token: str) -> Iterator[BoxerClaim]:
|
124
|
-
"""Reads user claims from Boxer based on jwt token
|
125
|
-
:param jwt_token: jwt token with UPN set
|
126
|
-
:return: Iterator[UserClaim]
|
127
|
-
"""
|
128
|
-
target_url = f"{self.base_url}/claims/user/{jwt_token}"
|
129
|
-
response = self.http.get(target_url)
|
130
|
-
response.raise_for_status()
|
131
|
-
return _iterate_boxer_claims_response(response)
|
132
|
-
|
133
|
-
def create_consumer(self, consumer_id: str, overwrite: bool = False) -> str:
|
134
|
-
"""Adds/Overwrites a new Boxer Claim to a user
|
135
|
-
:param consumer_id: Consumer ID of new consumer
|
136
|
-
:param overwrite: Flag to overwrite if consumer with given consumer_id already exists
|
137
|
-
:return: New consumer's private key (Base64 Encoded)
|
138
|
-
"""
|
139
|
-
target_url = f"{self.base_url}/consumer/{consumer_id}?overwrite={overwrite}"
|
140
|
-
response = self.http.post(target_url, json={})
|
141
|
-
response.raise_for_status()
|
142
|
-
return response.text
|
143
|
-
|
144
|
-
def get_consumer_public_key(self, consumer_id: str) -> str:
|
145
|
-
"""Reads Consumer's public key
|
146
|
-
:param consumer_id: Boxer Claim
|
147
|
-
:return: public key (Base64 Encoded)
|
148
|
-
"""
|
149
|
-
target_url = f"{self.base_url}/consumer/publicKey/{consumer_id}"
|
150
|
-
response = self.http.get(target_url, json={})
|
151
|
-
response.raise_for_status()
|
152
|
-
return response.text
|
153
|
-
|
154
173
|
def get_token(self) -> BoxerToken:
|
155
174
|
"""
|
156
175
|
Authorize with external token and return BoxerToken
|
@@ -0,0 +1,75 @@
|
|
1
|
+
"""
|
2
|
+
Models for Boxer
|
3
|
+
"""
|
4
|
+
# Copyright (c) 2023. ECCO Sneaks & Data
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
# you may not use this file except in compliance with the License.
|
8
|
+
# You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
#
|
18
|
+
|
19
|
+
from dataclasses import dataclass
|
20
|
+
|
21
|
+
from dataclasses_json import LetterCase, dataclass_json, DataClassJsonMixin
|
22
|
+
|
23
|
+
|
24
|
+
@dataclass_json
|
25
|
+
@dataclass
|
26
|
+
class Claim(DataClassJsonMixin):
|
27
|
+
"""
|
28
|
+
Boxer Claim
|
29
|
+
"""
|
30
|
+
|
31
|
+
claim_name: str
|
32
|
+
claim_value: str
|
33
|
+
|
34
|
+
|
35
|
+
@dataclass_json
|
36
|
+
@dataclass
|
37
|
+
class ClaimPayload(DataClassJsonMixin):
|
38
|
+
"""
|
39
|
+
Boxer Claim Payload for Deleting/Inserting claims
|
40
|
+
"""
|
41
|
+
|
42
|
+
operation: str
|
43
|
+
claims: dict
|
44
|
+
|
45
|
+
def add_claim(self, claim: Claim) -> "ClaimPayload":
|
46
|
+
"""
|
47
|
+
Add a claim to the ClaimPayload
|
48
|
+
"""
|
49
|
+
self.claims |= {claim.claim_name: claim.claim_value}
|
50
|
+
return self
|
51
|
+
|
52
|
+
|
53
|
+
@dataclass_json(letter_case=LetterCase.CAMEL)
|
54
|
+
@dataclass
|
55
|
+
class ClaimResponse(DataClassJsonMixin):
|
56
|
+
"""
|
57
|
+
Boxer Claim request response
|
58
|
+
"""
|
59
|
+
|
60
|
+
identity_provider: str
|
61
|
+
user_id: str
|
62
|
+
claims: list[dict]
|
63
|
+
billing_id: str
|
64
|
+
|
65
|
+
|
66
|
+
class BoxerToken:
|
67
|
+
"""
|
68
|
+
Represents token created by BoxerConnector.get_token
|
69
|
+
"""
|
70
|
+
|
71
|
+
def __init__(self, token: str):
|
72
|
+
self._token = token
|
73
|
+
|
74
|
+
def __str__(self):
|
75
|
+
return self._token
|
@@ -1,6 +1,6 @@
|
|
1
1
|
[tool.poetry]
|
2
2
|
name = "esd-services-api-client"
|
3
|
-
version = "1.1.
|
3
|
+
version = "1.1.5"
|
4
4
|
description = "Python clients for ESD services"
|
5
5
|
authors = ["ECCO Sneaks & Data <esdsupport@ecco.com>"]
|
6
6
|
maintainers = ['GZU <gzu@ecco.com>', 'JRB <ext-jrb@ecco.com>', 'VISA <visa@ecco.com>']
|
@@ -1 +0,0 @@
|
|
1
|
-
__version__ = '1.1.3'
|
@@ -1,91 +0,0 @@
|
|
1
|
-
# Boxer API Connector
|
2
|
-
|
3
|
-
Conenctor for working with [Boxer](https://github.com/SneaksAndData/boxer) AuthZ/AuthN API.
|
4
|
-
|
5
|
-
## Usage
|
6
|
-
|
7
|
-
Two environment variables must be set before you can use this connector:
|
8
|
-
|
9
|
-
```shell
|
10
|
-
export BOXER_CONSUMER_ID="my_app_consumer"
|
11
|
-
export BOXER_PRIVATE_KEY="MIIEpAIBAA..."
|
12
|
-
```
|
13
|
-
|
14
|
-
### Retrieving User Claims:
|
15
|
-
|
16
|
-
```python
|
17
|
-
from esd_services_api_client.boxer import BoxerConnector
|
18
|
-
conn = BoxerConnector(base_url="https://boxer.test.sneaksanddata.com")
|
19
|
-
claims = conn.get_claims_by_user("user@domain.tld")
|
20
|
-
for claim in claims:
|
21
|
-
print(claim.to_dict())
|
22
|
-
```
|
23
|
-
|
24
|
-
### Retrieving Claims by Type:
|
25
|
-
|
26
|
-
```python
|
27
|
-
from esd_services_api_client.boxer import BoxerConnector
|
28
|
-
conn = BoxerConnector(base_url="https://boxer.test.sneaksanddata.com")
|
29
|
-
claims = conn.get_claims_by_type("App1.AccessPolicy")
|
30
|
-
```
|
31
|
-
|
32
|
-
### Retrieving Group Claims:
|
33
|
-
|
34
|
-
```python
|
35
|
-
from esd_services_api_client.boxer import BoxerConnector
|
36
|
-
conn = BoxerConnector(base_url="https://boxer.test.sneaksanddata.com")
|
37
|
-
claims = conn.get_claims_by_group("ad_group_name")
|
38
|
-
```
|
39
|
-
|
40
|
-
### Setting a user claim:
|
41
|
-
```python
|
42
|
-
from esd_services_api_client.boxer import BoxerConnector
|
43
|
-
from esd_services_api_client.boxer import BoxerClaim
|
44
|
-
claim = BoxerClaim(issuer="App1", claim_type="App1.CanManageConsumers", claim_value="true")
|
45
|
-
conn = BoxerConnector(base_url="https://boxer.test.sneaksanddata.com")
|
46
|
-
conn.push_user_claim(claim, "app1admin")
|
47
|
-
```
|
48
|
-
|
49
|
-
### Setting a group claim:
|
50
|
-
```python
|
51
|
-
from esd_services_api_client.boxer import BoxerConnector
|
52
|
-
from esd_services_api_client.boxer import BoxerClaim
|
53
|
-
claim = BoxerClaim(issuer="App1", claim_type="App1.CanManageConsumers", claim_value="true")
|
54
|
-
conn = BoxerConnector(base_url="https://boxer.test.sneaksanddata.com")
|
55
|
-
conn.push_group_claim(claim, "ad_group_name")
|
56
|
-
```
|
57
|
-
|
58
|
-
### Creating a new Auth Consumer (obtaining private key):
|
59
|
-
```python
|
60
|
-
from esd_services_api_client.boxer import BoxerConnector
|
61
|
-
conn = BoxerConnector(base_url="https://boxer.test.sneaksanddata.com")
|
62
|
-
priv_key = conn.create_consumer("app_consumer")
|
63
|
-
```
|
64
|
-
|
65
|
-
### Getting Consumer's public key:
|
66
|
-
```python
|
67
|
-
from esd_services_api_client.boxer import BoxerConnector
|
68
|
-
conn = BoxerConnector(base_url="https://boxer.test.sneaksanddata.com")
|
69
|
-
pub_key = conn.get_consumer_public_key("app_consumer")
|
70
|
-
```
|
71
|
-
|
72
|
-
### Using as an authentication provider for other connectors
|
73
|
-
```python
|
74
|
-
from esd_services_api_client.boxer import BoxerConnector, RefreshableExternalTokenAuth, BoxerTokenAuth
|
75
|
-
from esd_services_api_client.crystal import CrystalConnector
|
76
|
-
|
77
|
-
auth_method = "example"
|
78
|
-
|
79
|
-
def get_external_token() -> str:
|
80
|
-
return "example_token"
|
81
|
-
|
82
|
-
# Configure authentication with boxer
|
83
|
-
external_auth = RefreshableExternalTokenAuth(lambda: get_external_token(), auth_method)
|
84
|
-
boxer_connector = BoxerConnector(base_url="https://example.com", auth=external_auth)
|
85
|
-
|
86
|
-
# Inject boxer auth to Crystal connector
|
87
|
-
connector = CrystalConnector(base_url="https://example.com", auth=BoxerTokenAuth(boxer_connector))
|
88
|
-
|
89
|
-
# Use Crystal connector with boxer auth
|
90
|
-
connector.await_runs("algorithm", ["id"])
|
91
|
-
```
|
@@ -1,46 +0,0 @@
|
|
1
|
-
""" Helper functions to parse responses
|
2
|
-
"""
|
3
|
-
# Copyright (c) 2023. ECCO Sneaks & Data
|
4
|
-
#
|
5
|
-
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
-
# you may not use this file except in compliance with the License.
|
7
|
-
# You may obtain a copy of the License at
|
8
|
-
#
|
9
|
-
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
-
#
|
11
|
-
# Unless required by applicable law or agreed to in writing, software
|
12
|
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
-
# See the License for the specific language governing permissions and
|
15
|
-
# limitations under the License.
|
16
|
-
#
|
17
|
-
|
18
|
-
from requests import Response
|
19
|
-
|
20
|
-
from esd_services_api_client.boxer._models import UserClaim, BoxerClaim
|
21
|
-
|
22
|
-
|
23
|
-
def _iterate_user_claims_response(user_claim_response: Response):
|
24
|
-
"""Creates an iterator to iterate user claims from Json Response
|
25
|
-
:param user_claim_response: HTTP Response containing json array of type UserClaim
|
26
|
-
"""
|
27
|
-
response_json = user_claim_response.json()
|
28
|
-
|
29
|
-
if response_json:
|
30
|
-
for api_response_item in response_json:
|
31
|
-
yield UserClaim.from_dict(api_response_item)
|
32
|
-
else:
|
33
|
-
raise ValueError("Expected response body of type application/json")
|
34
|
-
|
35
|
-
|
36
|
-
def _iterate_boxer_claims_response(boxer_claim_response: Response):
|
37
|
-
"""Creates an iterator to iterate user claims from Json Response
|
38
|
-
:param boxer_claim_response: HTTP Response containing json array of type BoxerClaim
|
39
|
-
"""
|
40
|
-
response_json = boxer_claim_response.json()
|
41
|
-
|
42
|
-
if response_json:
|
43
|
-
for api_response_item in response_json:
|
44
|
-
yield BoxerClaim.from_dict(api_response_item)
|
45
|
-
else:
|
46
|
-
raise ValueError("Expected response body of type application/json")
|
@@ -1,131 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
Models for Boxer
|
3
|
-
"""
|
4
|
-
# Copyright (c) 2023. ECCO Sneaks & Data
|
5
|
-
#
|
6
|
-
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
-
# you may not use this file except in compliance with the License.
|
8
|
-
# You may obtain a copy of the License at
|
9
|
-
#
|
10
|
-
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
-
#
|
12
|
-
# Unless required by applicable law or agreed to in writing, software
|
13
|
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
-
# See the License for the specific language governing permissions and
|
16
|
-
# limitations under the License.
|
17
|
-
#
|
18
|
-
|
19
|
-
from dataclasses import dataclass
|
20
|
-
from typing import Dict
|
21
|
-
|
22
|
-
|
23
|
-
@dataclass
|
24
|
-
class BoxerClaim:
|
25
|
-
"""
|
26
|
-
Boxer Claim
|
27
|
-
"""
|
28
|
-
|
29
|
-
claim_type: str
|
30
|
-
claim_value: str
|
31
|
-
issuer: str
|
32
|
-
|
33
|
-
def to_dict(self) -> Dict:
|
34
|
-
"""Convert to Dictionary
|
35
|
-
:return: Dictionary
|
36
|
-
"""
|
37
|
-
return {
|
38
|
-
"claimType": self.claim_type,
|
39
|
-
"claimValue": self.claim_value,
|
40
|
-
"issuer": self.issuer,
|
41
|
-
}
|
42
|
-
|
43
|
-
@classmethod
|
44
|
-
def from_dict(cls, json_data: Dict):
|
45
|
-
"""Initialize from Dictionary
|
46
|
-
:param json_data: Dictionary
|
47
|
-
:return:
|
48
|
-
"""
|
49
|
-
return BoxerClaim(
|
50
|
-
claim_type=json_data["claimType"],
|
51
|
-
claim_value=json_data["claimValue"],
|
52
|
-
issuer=json_data["issuer"],
|
53
|
-
)
|
54
|
-
|
55
|
-
|
56
|
-
@dataclass
|
57
|
-
class UserClaim:
|
58
|
-
"""
|
59
|
-
Boxer User Claim
|
60
|
-
"""
|
61
|
-
|
62
|
-
user_id: str
|
63
|
-
user_claim_id: str
|
64
|
-
claim: BoxerClaim
|
65
|
-
|
66
|
-
def to_dict(self) -> Dict:
|
67
|
-
"""Convert to Dictionary
|
68
|
-
:return: Dictionary
|
69
|
-
"""
|
70
|
-
return {
|
71
|
-
"userId": self.user_id,
|
72
|
-
"userClaimId": self.user_claim_id,
|
73
|
-
"claim": self.claim.to_dict(),
|
74
|
-
}
|
75
|
-
|
76
|
-
@classmethod
|
77
|
-
def from_dict(cls, json_data: Dict):
|
78
|
-
"""Initialize from Dictionary
|
79
|
-
:param json_data: Dictionary
|
80
|
-
:return:
|
81
|
-
"""
|
82
|
-
return UserClaim(
|
83
|
-
user_id=json_data["userId"],
|
84
|
-
user_claim_id=json_data["userClaimId"],
|
85
|
-
claim=BoxerClaim.from_dict(json_data["claim"]),
|
86
|
-
)
|
87
|
-
|
88
|
-
|
89
|
-
@dataclass
|
90
|
-
class GroupClaim:
|
91
|
-
"""
|
92
|
-
Boxer Group Claim
|
93
|
-
"""
|
94
|
-
|
95
|
-
group_name: str
|
96
|
-
group_claim_id: str
|
97
|
-
claim: BoxerClaim
|
98
|
-
|
99
|
-
def to_dict(self) -> Dict:
|
100
|
-
"""Convert to Dictionary
|
101
|
-
:return: Dictionary
|
102
|
-
"""
|
103
|
-
return {
|
104
|
-
"groupName": self.group_name,
|
105
|
-
"groupClaimId": self.group_claim_id,
|
106
|
-
"claim": self.claim.to_dict(),
|
107
|
-
}
|
108
|
-
|
109
|
-
@classmethod
|
110
|
-
def from_dict(cls, json_data: Dict):
|
111
|
-
"""Initialize from Dictionary
|
112
|
-
:param json_data: Dictionary
|
113
|
-
:return:
|
114
|
-
"""
|
115
|
-
return GroupClaim(
|
116
|
-
group_name=json_data["groupName"],
|
117
|
-
group_claim_id=json_data["groupClaimId"],
|
118
|
-
claim=BoxerClaim.from_dict(json_data["claim"]),
|
119
|
-
)
|
120
|
-
|
121
|
-
|
122
|
-
class BoxerToken:
|
123
|
-
"""
|
124
|
-
Represents token created by BoxerConnector.get_token
|
125
|
-
"""
|
126
|
-
|
127
|
-
def __init__(self, token: str):
|
128
|
-
self._token = token
|
129
|
-
|
130
|
-
def __str__(self):
|
131
|
-
return self._token
|
File without changes
|
File without changes
|
{esd_services_api_client-1.1.3 → esd_services_api_client-1.1.5}/esd_services_api_client/__init__.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|