salesforce-data-customcode 6.1.0.dev6__py3-none-any.whl → 6.1.0.dev7__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.
- datacustomcode/named_credential/direct/auth.py +128 -0
- datacustomcode/named_credential/direct/credentials.py +1 -0
- datacustomcode/templates/script/examples/external_callout/README.md +5 -1
- {salesforce_data_customcode-6.1.0.dev6.dist-info → salesforce_data_customcode-6.1.0.dev7.dist-info}/METADATA +1 -1
- {salesforce_data_customcode-6.1.0.dev6.dist-info → salesforce_data_customcode-6.1.0.dev7.dist-info}/RECORD +8 -8
- {salesforce_data_customcode-6.1.0.dev6.dist-info → salesforce_data_customcode-6.1.0.dev7.dist-info}/WHEEL +0 -0
- {salesforce_data_customcode-6.1.0.dev6.dist-info → salesforce_data_customcode-6.1.0.dev7.dist-info}/entry_points.txt +0 -0
- {salesforce_data_customcode-6.1.0.dev6.dist-info → salesforce_data_customcode-6.1.0.dev7.dist-info}/licenses/LICENSE.txt +0 -0
|
@@ -19,11 +19,15 @@ Inject external credential auth into an outgoing request.
|
|
|
19
19
|
from __future__ import annotations
|
|
20
20
|
|
|
21
21
|
import base64
|
|
22
|
+
import datetime
|
|
23
|
+
import hashlib
|
|
24
|
+
import hmac
|
|
22
25
|
from typing import (
|
|
23
26
|
TYPE_CHECKING,
|
|
24
27
|
Any,
|
|
25
28
|
Dict,
|
|
26
29
|
)
|
|
30
|
+
import urllib.parse
|
|
27
31
|
|
|
28
32
|
from requests.auth import AuthBase
|
|
29
33
|
|
|
@@ -32,6 +36,8 @@ from datacustomcode.named_credential.direct.credentials import AuthType
|
|
|
32
36
|
if TYPE_CHECKING:
|
|
33
37
|
from requests.models import PreparedRequest
|
|
34
38
|
|
|
39
|
+
_SIGV4_ALGORITHM = "AWS4-HMAC-SHA256"
|
|
40
|
+
|
|
35
41
|
|
|
36
42
|
class DynamicAuthHandler(AuthBase):
|
|
37
43
|
def __init__(self, cred_config: Dict[str, Any]) -> None:
|
|
@@ -57,7 +63,129 @@ class DynamicAuthHandler(AuthBase):
|
|
|
57
63
|
)
|
|
58
64
|
request.headers["Authorization"] = f"Bearer {bearer}"
|
|
59
65
|
|
|
66
|
+
elif self.auth_type == AuthType.AWS_SIG_V4.value:
|
|
67
|
+
self._sign_aws_sigv4(request)
|
|
68
|
+
|
|
60
69
|
else:
|
|
61
70
|
raise ValueError(f"Unsupported auth_type '{self.auth_type}'.")
|
|
62
71
|
|
|
63
72
|
return request
|
|
73
|
+
|
|
74
|
+
def _sign_aws_sigv4(self, request: PreparedRequest) -> None:
|
|
75
|
+
"""Sign ``request`` with AWS Signature Version 4.
|
|
76
|
+
|
|
77
|
+
Requires ``aws_access_key_id``, ``aws_secret_access_key``, ``aws_region``,
|
|
78
|
+
and ``aws_service`` in the credential config; ``aws_session_token`` is
|
|
79
|
+
optional (for temporary credentials). The signed date, payload hash, and
|
|
80
|
+
(when present) session token are added as ``x-amz-*`` headers so the sent
|
|
81
|
+
request matches what was signed.
|
|
82
|
+
"""
|
|
83
|
+
access_key = self.config.get("aws_access_key_id")
|
|
84
|
+
secret_key = self.config.get("aws_secret_access_key")
|
|
85
|
+
region = self.config.get("aws_region")
|
|
86
|
+
service = self.config.get("aws_service")
|
|
87
|
+
session_token = self.config.get("aws_session_token")
|
|
88
|
+
missing = [
|
|
89
|
+
name
|
|
90
|
+
for name, value in (
|
|
91
|
+
("aws_access_key_id", access_key),
|
|
92
|
+
("aws_secret_access_key", secret_key),
|
|
93
|
+
("aws_region", region),
|
|
94
|
+
("aws_service", service),
|
|
95
|
+
)
|
|
96
|
+
if not value
|
|
97
|
+
]
|
|
98
|
+
if missing:
|
|
99
|
+
raise ValueError(f"'{self.auth_type}' auth requires {', '.join(missing)}.")
|
|
100
|
+
access_key = str(access_key)
|
|
101
|
+
secret_key = str(secret_key)
|
|
102
|
+
region = str(region)
|
|
103
|
+
service = str(service)
|
|
104
|
+
|
|
105
|
+
parsed = urllib.parse.urlsplit(str(request.url or ""))
|
|
106
|
+
host = parsed.netloc
|
|
107
|
+
canonical_uri = urllib.parse.quote(parsed.path or "/", safe="/-_.~")
|
|
108
|
+
canonical_query = _canonical_query_string(parsed.query)
|
|
109
|
+
|
|
110
|
+
body = request.body or b""
|
|
111
|
+
if isinstance(body, str):
|
|
112
|
+
body = body.encode("utf-8")
|
|
113
|
+
payload_hash = hashlib.sha256(body).hexdigest()
|
|
114
|
+
|
|
115
|
+
now = datetime.datetime.now(datetime.timezone.utc)
|
|
116
|
+
amz_date = now.strftime("%Y%m%dT%H%M%SZ")
|
|
117
|
+
datestamp = now.strftime("%Y%m%d")
|
|
118
|
+
|
|
119
|
+
request.headers["x-amz-date"] = amz_date
|
|
120
|
+
request.headers["x-amz-content-sha256"] = payload_hash
|
|
121
|
+
if session_token:
|
|
122
|
+
request.headers["x-amz-security-token"] = session_token
|
|
123
|
+
|
|
124
|
+
signed = {
|
|
125
|
+
"host": host,
|
|
126
|
+
"x-amz-content-sha256": payload_hash,
|
|
127
|
+
"x-amz-date": amz_date,
|
|
128
|
+
}
|
|
129
|
+
if session_token:
|
|
130
|
+
signed["x-amz-security-token"] = session_token
|
|
131
|
+
signed_headers = ";".join(sorted(signed))
|
|
132
|
+
canonical_headers = "".join(
|
|
133
|
+
f"{name}:{signed[name]}\n" for name in sorted(signed)
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
canonical_request = "\n".join(
|
|
137
|
+
[
|
|
138
|
+
request.method or "GET",
|
|
139
|
+
canonical_uri,
|
|
140
|
+
canonical_query,
|
|
141
|
+
canonical_headers,
|
|
142
|
+
signed_headers,
|
|
143
|
+
payload_hash,
|
|
144
|
+
]
|
|
145
|
+
)
|
|
146
|
+
credential_scope = f"{datestamp}/{region}/{service}/aws4_request"
|
|
147
|
+
string_to_sign = "\n".join(
|
|
148
|
+
[
|
|
149
|
+
_SIGV4_ALGORITHM,
|
|
150
|
+
amz_date,
|
|
151
|
+
credential_scope,
|
|
152
|
+
hashlib.sha256(canonical_request.encode("utf-8")).hexdigest(),
|
|
153
|
+
]
|
|
154
|
+
)
|
|
155
|
+
signing_key = _derive_signing_key(secret_key, datestamp, region, service)
|
|
156
|
+
signature = hmac.new(
|
|
157
|
+
signing_key, string_to_sign.encode("utf-8"), hashlib.sha256
|
|
158
|
+
).hexdigest()
|
|
159
|
+
|
|
160
|
+
request.headers["Authorization"] = (
|
|
161
|
+
f"{_SIGV4_ALGORITHM} Credential={access_key}/{credential_scope}, "
|
|
162
|
+
f"SignedHeaders={signed_headers}, Signature={signature}"
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def _canonical_query_string(query: str) -> str:
|
|
167
|
+
"""Build the AWS Sig V4 canonical query string from a raw query string."""
|
|
168
|
+
pairs = urllib.parse.parse_qsl(query, keep_blank_values=True)
|
|
169
|
+
encoded = [
|
|
170
|
+
(
|
|
171
|
+
urllib.parse.quote(key, safe="-_.~"),
|
|
172
|
+
urllib.parse.quote(value, safe="-_.~"),
|
|
173
|
+
)
|
|
174
|
+
for key, value in pairs
|
|
175
|
+
]
|
|
176
|
+
encoded.sort()
|
|
177
|
+
return "&".join(f"{key}={value}" for key, value in encoded)
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def _derive_signing_key(
|
|
181
|
+
secret_key: str, datestamp: str, region: str, service: str
|
|
182
|
+
) -> bytes:
|
|
183
|
+
"""Derive the AWS Sig V4 signing key via the chained HMAC-SHA256 sequence."""
|
|
184
|
+
|
|
185
|
+
def _hmac(key: bytes, msg: str) -> bytes:
|
|
186
|
+
return hmac.new(key, msg.encode("utf-8"), hashlib.sha256).digest()
|
|
187
|
+
|
|
188
|
+
k_date = _hmac(f"AWS4{secret_key}".encode(), datestamp)
|
|
189
|
+
k_region = _hmac(k_date, region)
|
|
190
|
+
k_service = _hmac(k_region, service)
|
|
191
|
+
return _hmac(k_service, "aws4_request")
|
|
@@ -122,7 +122,7 @@ matching entries in `config.json`) to point at your own DLOs.
|
|
|
122
122
|
`auth_type` selects how auth is injected for local testing. It should mirror the
|
|
123
123
|
External Credential your Named Credential uses in the org, so local and deployed
|
|
124
124
|
runs behave the same. This example uses `Custom` (Gemini's `X-goog-api-key`);
|
|
125
|
-
all
|
|
125
|
+
all supported types:
|
|
126
126
|
|
|
127
127
|
| `auth_type` | Fields read | Header sent |
|
|
128
128
|
| ----------- | ------------------------------- | --------------------------------------- |
|
|
@@ -130,7 +130,11 @@ all four supported types:
|
|
|
130
130
|
| `Custom` | `custom_headers` (sent verbatim)| the headers you list |
|
|
131
131
|
| `OAuth` | `access_token` or `token` | `Authorization: Bearer <token>` |
|
|
132
132
|
| `Jwt` | `access_token` or `token` | `Authorization: Bearer <token>` |
|
|
133
|
+
| `AwsSv4` | `aws_access_key_id`, `aws_secret_access_key`, `aws_region`, `aws_service`, optional `aws_session_token` | `Authorization: AWS4-HMAC-SHA256 ...` plus `x-amz-date` / `x-amz-content-sha256` (and `x-amz-security-token` when a session token is set) |
|
|
133
134
|
|
|
134
135
|
`OAuth`/`Jwt` take a token you supply for the local run — the SDK does not fetch
|
|
135
136
|
or refresh it. In the Data Cloud runtime the Named Credential handles token
|
|
136
137
|
acquisition; this local config only stands in for that during testing.
|
|
138
|
+
|
|
139
|
+
`AwsSv4` signs the request with AWS Signature Version 4 using the keys you
|
|
140
|
+
supply, mirroring an AWS Signature Version 4 External Credential in the org.
|
|
@@ -57,8 +57,8 @@ datacustomcode/named_credential/__init__.py,sha256=XD_UI8ggOkFvkqCOZI_3dP1AkviqP
|
|
|
57
57
|
datacustomcode/named_credential/base.py,sha256=-LMskZkKNoaFg-Zc80M_4IPLF7ru5pfnbkfLI7YXrFc,1816
|
|
58
58
|
datacustomcode/named_credential/default.py,sha256=z03B0daZvTPiTmJWGAtDCICB1srelESg7QmPH2iRK5o,3339
|
|
59
59
|
datacustomcode/named_credential/direct/__init__.py,sha256=zqErMI1GqEhseLnYkrWtq3WipRi_dP153c8wwLvgI-o,799
|
|
60
|
-
datacustomcode/named_credential/direct/auth.py,sha256=
|
|
61
|
-
datacustomcode/named_credential/direct/credentials.py,sha256=
|
|
60
|
+
datacustomcode/named_credential/direct/auth.py,sha256=I1vdB8zVxdREwABDnjY7qwpTZFhT9OHJTsgpxPIuT84,6837
|
|
61
|
+
datacustomcode/named_credential/direct/credentials.py,sha256=B1UpqwRXnzkjycUi50KfxJA5ZeAhR33gElRMzNOit4M,4560
|
|
62
62
|
datacustomcode/named_credential/direct/transport.py,sha256=UL5eJJsyoAjr8ovJDQKQY3O20DivvbWFPGRyM8Y2or4,4078
|
|
63
63
|
datacustomcode/named_credential/direct/url_resolver.py,sha256=KItv12Az0qoxnVacCTcM9t9AY7qB9_tqPWMgiP-7Y6s,3823
|
|
64
64
|
datacustomcode/named_credential/errors.py,sha256=zyMEgljTf85digXrLrlR9HBh5Un4WtG0WsZx0K4EWt0,1212
|
|
@@ -111,7 +111,7 @@ datacustomcode/templates/script/account.ipynb,sha256=LIbxgiVxflNASdspF2lfpMKkKAT
|
|
|
111
111
|
datacustomcode/templates/script/build_native_dependencies.sh,sha256=ICRrp4f1ATBwPaUiuVGjY-MwiubFswNJLf8gMGU6YNg,197
|
|
112
112
|
datacustomcode/templates/script/examples/employee_hierarchy/employee_data.csv,sha256=C7ggLBfoyi3M2BdMLNyOeKqF-5OO-Da76lkwgWg2cVQ,302
|
|
113
113
|
datacustomcode/templates/script/examples/employee_hierarchy/entrypoint.py,sha256=Mfm3iQtEHTQRW6cmZTwRKdTh3IeGWR-teXrd6x-YLSY,2223
|
|
114
|
-
datacustomcode/templates/script/examples/external_callout/README.md,sha256=
|
|
114
|
+
datacustomcode/templates/script/examples/external_callout/README.md,sha256=CNLPSS6anLxRQyZ6hxAXUuvUPH09HMv2GPoQQMsuT94,6056
|
|
115
115
|
datacustomcode/templates/script/examples/external_callout/entrypoint.py,sha256=KgSAmkpJ1On7R3Lr4_Slf9rTHgLr3TjC43QSFbaIzUY,5005
|
|
116
116
|
datacustomcode/templates/script/examples/external_callout/external_callout_config.json,sha256=PUCmw_humbVoCyswFneuGYx0INbqSTPOumDqtGOztiM,320
|
|
117
117
|
datacustomcode/templates/script/examples/streaming_deltas/entrypoint.py,sha256=UQr1EhJURFFGI5626efvv-MxGHltzcpJcEJVmz5ZQdo,2333
|
|
@@ -122,8 +122,8 @@ datacustomcode/templates/script/requirements-dev.txt,sha256=OWwuy1awesqOZOS3Zizm
|
|
|
122
122
|
datacustomcode/templates/script/requirements.txt,sha256=qJbqzs5z0Hrx590U3T7dHq_m8UQEx2TdhgrJSz-QHIQ,40
|
|
123
123
|
datacustomcode/token_provider.py,sha256=qA_e4vqSXnxn0MccFFPULWO8ZeUOEAu5QpBnoYCg-fc,6839
|
|
124
124
|
datacustomcode/version.py,sha256=9LlbVrzwBvut1L308QbwNBgxdQk5CrghH39JARVSxms,989
|
|
125
|
-
salesforce_data_customcode-6.1.0.
|
|
126
|
-
salesforce_data_customcode-6.1.0.
|
|
127
|
-
salesforce_data_customcode-6.1.0.
|
|
128
|
-
salesforce_data_customcode-6.1.0.
|
|
129
|
-
salesforce_data_customcode-6.1.0.
|
|
125
|
+
salesforce_data_customcode-6.1.0.dev7.dist-info/METADATA,sha256=jzc55cJlMHH-nGsNSzIrLu-GhN4nlmIOfK9fj8nZ1u4,27417
|
|
126
|
+
salesforce_data_customcode-6.1.0.dev7.dist-info/WHEEL,sha256=eY7nduwzv-ldUxpzbRlxwvC693Hg6PX8bWDjEHjZ_dk,88
|
|
127
|
+
salesforce_data_customcode-6.1.0.dev7.dist-info/entry_points.txt,sha256=WpQ94UB7UuRCYGOLtJV3vAgm1tl7iVf43VYRWIuNu5Y,74
|
|
128
|
+
salesforce_data_customcode-6.1.0.dev7.dist-info/licenses/LICENSE.txt,sha256=iOi8EmQpfkFhMENi7VYtkl2EqS14pEOccoXHiW2dyPU,11443
|
|
129
|
+
salesforce_data_customcode-6.1.0.dev7.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|