pyxecm 3.8.0__tar.gz → 3.8.2__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.
- {pyxecm-3.8.0 → pyxecm-3.8.2}/PKG-INFO +1 -1
- {pyxecm-3.8.0 → pyxecm-3.8.2}/pyproject.toml +1 -1
- {pyxecm-3.8.0 → pyxecm-3.8.2}/src/pyxecm_customizer/customizer.py +1 -0
- {pyxecm-3.8.0 → pyxecm-3.8.2}/src/pyxecm_customizer/guidewire.py +21 -2
- {pyxecm-3.8.0 → pyxecm-3.8.2}/src/pyxecm_customizer/payload.py +23 -4
- {pyxecm-3.8.0 → pyxecm-3.8.2}/src/pyxecm_customizer/sap.py +30 -7
- {pyxecm-3.8.0 → pyxecm-3.8.2}/src/pyxecm_customizer/sap_models.py +8 -2
- {pyxecm-3.8.0 → pyxecm-3.8.2}/.gitignore +0 -0
- {pyxecm-3.8.0 → pyxecm-3.8.2}/README.md +0 -0
- {pyxecm-3.8.0 → pyxecm-3.8.2}/src/pyxecm/__init__.py +0 -0
- {pyxecm-3.8.0 → pyxecm-3.8.2}/src/pyxecm/avts.py +0 -0
- {pyxecm-3.8.0 → pyxecm-3.8.2}/src/pyxecm/coreshare.py +0 -0
- {pyxecm-3.8.0 → pyxecm-3.8.2}/src/pyxecm/helper/__init__.py +0 -0
- {pyxecm-3.8.0 → pyxecm-3.8.2}/src/pyxecm/helper/assoc.py +0 -0
- {pyxecm-3.8.0 → pyxecm-3.8.2}/src/pyxecm/helper/data.py +0 -0
- {pyxecm-3.8.0 → pyxecm-3.8.2}/src/pyxecm/helper/logadapter.py +0 -0
- {pyxecm-3.8.0 → pyxecm-3.8.2}/src/pyxecm/helper/web.py +0 -0
- {pyxecm-3.8.0 → pyxecm-3.8.2}/src/pyxecm/helper/xml.py +0 -0
- {pyxecm-3.8.0 → pyxecm-3.8.2}/src/pyxecm/otac.py +0 -0
- {pyxecm-3.8.0 → pyxecm-3.8.2}/src/pyxecm/otawp.py +0 -0
- {pyxecm-3.8.0 → pyxecm-3.8.2}/src/pyxecm/otca.py +0 -0
- {pyxecm-3.8.0 → pyxecm-3.8.2}/src/pyxecm/otcs.py +0 -0
- {pyxecm-3.8.0 → pyxecm-3.8.2}/src/pyxecm/otds.py +0 -0
- {pyxecm-3.8.0 → pyxecm-3.8.2}/src/pyxecm/otiv.py +0 -0
- {pyxecm-3.8.0 → pyxecm-3.8.2}/src/pyxecm/otkd.py +0 -0
- {pyxecm-3.8.0 → pyxecm-3.8.2}/src/pyxecm/otmm.py +0 -0
- {pyxecm-3.8.0 → pyxecm-3.8.2}/src/pyxecm/otpd.py +0 -0
- {pyxecm-3.8.0 → pyxecm-3.8.2}/src/pyxecm/py.typed +0 -0
- {pyxecm-3.8.0 → pyxecm-3.8.2}/src/pyxecm_api/__init__.py +0 -0
- {pyxecm-3.8.0 → pyxecm-3.8.2}/src/pyxecm_api/__main__.py +0 -0
- {pyxecm-3.8.0 → pyxecm-3.8.2}/src/pyxecm_api/app.py +0 -0
- {pyxecm-3.8.0 → pyxecm-3.8.2}/src/pyxecm_api/auth/__init__.py +0 -0
- {pyxecm-3.8.0 → pyxecm-3.8.2}/src/pyxecm_api/auth/functions.py +0 -0
- {pyxecm-3.8.0 → pyxecm-3.8.2}/src/pyxecm_api/auth/models.py +0 -0
- {pyxecm-3.8.0 → pyxecm-3.8.2}/src/pyxecm_api/auth/router.py +0 -0
- {pyxecm-3.8.0 → pyxecm-3.8.2}/src/pyxecm_api/common/__init__.py +0 -0
- {pyxecm-3.8.0 → pyxecm-3.8.2}/src/pyxecm_api/common/functions.py +0 -0
- {pyxecm-3.8.0 → pyxecm-3.8.2}/src/pyxecm_api/common/metrics.py +0 -0
- {pyxecm-3.8.0 → pyxecm-3.8.2}/src/pyxecm_api/common/models.py +0 -0
- {pyxecm-3.8.0 → pyxecm-3.8.2}/src/pyxecm_api/common/router.py +0 -0
- {pyxecm-3.8.0 → pyxecm-3.8.2}/src/pyxecm_api/settings.py +0 -0
- {pyxecm-3.8.0 → pyxecm-3.8.2}/src/pyxecm_api/terminal/__init__.py +0 -0
- {pyxecm-3.8.0 → pyxecm-3.8.2}/src/pyxecm_api/terminal/router.py +0 -0
- {pyxecm-3.8.0 → pyxecm-3.8.2}/src/pyxecm_api/v1_maintenance/__init__.py +0 -0
- {pyxecm-3.8.0 → pyxecm-3.8.2}/src/pyxecm_api/v1_maintenance/functions.py +0 -0
- {pyxecm-3.8.0 → pyxecm-3.8.2}/src/pyxecm_api/v1_maintenance/models.py +0 -0
- {pyxecm-3.8.0 → pyxecm-3.8.2}/src/pyxecm_api/v1_maintenance/router.py +0 -0
- {pyxecm-3.8.0 → pyxecm-3.8.2}/src/pyxecm_api/v1_otcs/__init__.py +0 -0
- {pyxecm-3.8.0 → pyxecm-3.8.2}/src/pyxecm_api/v1_otcs/functions.py +0 -0
- {pyxecm-3.8.0 → pyxecm-3.8.2}/src/pyxecm_api/v1_otcs/router.py +0 -0
- {pyxecm-3.8.0 → pyxecm-3.8.2}/src/pyxecm_api/v1_payload/__init__.py +0 -0
- {pyxecm-3.8.0 → pyxecm-3.8.2}/src/pyxecm_api/v1_payload/functions.py +0 -0
- {pyxecm-3.8.0 → pyxecm-3.8.2}/src/pyxecm_api/v1_payload/log_reader.py +0 -0
- {pyxecm-3.8.0 → pyxecm-3.8.2}/src/pyxecm_api/v1_payload/models.py +0 -0
- {pyxecm-3.8.0 → pyxecm-3.8.2}/src/pyxecm_api/v1_payload/router.py +0 -0
- {pyxecm-3.8.0 → pyxecm-3.8.2}/src/pyxecm_customizer/__init__.py +0 -0
- {pyxecm-3.8.0 → pyxecm-3.8.2}/src/pyxecm_customizer/__main__.py +0 -0
- {pyxecm-3.8.0 → pyxecm-3.8.2}/src/pyxecm_customizer/browser_automation.py +0 -0
- {pyxecm-3.8.0 → pyxecm-3.8.2}/src/pyxecm_customizer/exceptions.py +0 -0
- {pyxecm-3.8.0 → pyxecm-3.8.2}/src/pyxecm_customizer/k8s.py +0 -0
- {pyxecm-3.8.0 → pyxecm-3.8.2}/src/pyxecm_customizer/log.py +0 -0
- {pyxecm-3.8.0 → pyxecm-3.8.2}/src/pyxecm_customizer/m365.py +0 -0
- {pyxecm-3.8.0 → pyxecm-3.8.2}/src/pyxecm_customizer/manager_push.py +0 -0
- {pyxecm-3.8.0 → pyxecm-3.8.2}/src/pyxecm_customizer/payload_list.py +0 -0
- {pyxecm-3.8.0 → pyxecm-3.8.2}/src/pyxecm_customizer/salesforce.py +0 -0
- {pyxecm-3.8.0 → pyxecm-3.8.2}/src/pyxecm_customizer/servicenow.py +0 -0
- {pyxecm-3.8.0 → pyxecm-3.8.2}/src/pyxecm_customizer/settings.py +0 -0
- {pyxecm-3.8.0 → pyxecm-3.8.2}/src/pyxecm_customizer/successfactors.py +0 -0
- {pyxecm-3.8.0 → pyxecm-3.8.2}/src/pyxecm_customizer/translate.py +0 -0
- {pyxecm-3.8.0 → pyxecm-3.8.2}/src/pyxecm_customizer/version.py +0 -0
- {pyxecm-3.8.0 → pyxecm-3.8.2}/src/pyxecm_maintenance_page/__init__.py +0 -0
- {pyxecm-3.8.0 → pyxecm-3.8.2}/src/pyxecm_maintenance_page/__main__.py +0 -0
- {pyxecm-3.8.0 → pyxecm-3.8.2}/src/pyxecm_maintenance_page/app.py +0 -0
- {pyxecm-3.8.0 → pyxecm-3.8.2}/src/pyxecm_maintenance_page/settings.py +0 -0
- {pyxecm-3.8.0 → pyxecm-3.8.2}/src/pyxecm_maintenance_page/static/favicon.avif +0 -0
- {pyxecm-3.8.0 → pyxecm-3.8.2}/src/pyxecm_maintenance_page/templates/maintenance.html +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: pyxecm
|
|
3
|
-
Version: 3.8.
|
|
3
|
+
Version: 3.8.2
|
|
4
4
|
Summary: A Python library to interact with Opentext Content Management Rest API
|
|
5
5
|
Project-URL: Homepage, https://github.com/opentext/pyxecm
|
|
6
6
|
Author-email: Kai Gatzweiler <kgatzweiler@opentext.com>, "Dr. Marc Diefenbruch" <mdiefenb@opentext.com>
|
|
@@ -1792,6 +1792,7 @@ class Customizer:
|
|
|
1792
1792
|
otca_object=self.otca_object,
|
|
1793
1793
|
otkd_object=self.otkd_object,
|
|
1794
1794
|
avts_object=self.avts_object,
|
|
1795
|
+
settings=self.settings,
|
|
1795
1796
|
logger=self.logger,
|
|
1796
1797
|
)
|
|
1797
1798
|
# Load the payload file and initialize the payload sections:
|
|
@@ -1470,7 +1470,12 @@ class Guidewire:
|
|
|
1470
1470
|
|
|
1471
1471
|
# end method definition
|
|
1472
1472
|
|
|
1473
|
-
def search_policy(
|
|
1473
|
+
def search_policy(
|
|
1474
|
+
self,
|
|
1475
|
+
attributes: dict,
|
|
1476
|
+
filter_field: str | None = None,
|
|
1477
|
+
filter_value: str | None = None,
|
|
1478
|
+
) -> dict:
|
|
1474
1479
|
"""Search a specific policy based on its attributes.
|
|
1475
1480
|
|
|
1476
1481
|
See: https://docs.guidewire.com/cloud/pc/202407/apiref/generated/Policy%20API/search-policies--post
|
|
@@ -1488,6 +1493,10 @@ class Guidewire:
|
|
|
1488
1493
|
* "firstName" (str)
|
|
1489
1494
|
* "lastName" (str)
|
|
1490
1495
|
* "officialId" (str)
|
|
1496
|
+
filter_field (str | None, optional):
|
|
1497
|
+
Additional policy attribute used to filter the results.
|
|
1498
|
+
filter_value (str | None, optional):
|
|
1499
|
+
Value that filter_field must equal.
|
|
1491
1500
|
|
|
1492
1501
|
Returns:
|
|
1493
1502
|
dict:
|
|
@@ -1528,7 +1537,17 @@ class Guidewire:
|
|
|
1528
1537
|
|
|
1529
1538
|
request_url = self.config()["policySearchUrl"]
|
|
1530
1539
|
|
|
1531
|
-
|
|
1540
|
+
response = self.do_request(method="POST", json_data=body, url=request_url)
|
|
1541
|
+
policies = response.get("data", [])
|
|
1542
|
+
if filter_field and filter_value is not None and isinstance(policies, list):
|
|
1543
|
+
response["data"] = [
|
|
1544
|
+
policy
|
|
1545
|
+
for policy in policies
|
|
1546
|
+
if policy.get("attributes", {}).get(filter_field) == filter_value
|
|
1547
|
+
]
|
|
1548
|
+
response["count"] = len(response["data"])
|
|
1549
|
+
|
|
1550
|
+
return response
|
|
1532
1551
|
|
|
1533
1552
|
# end method definition
|
|
1534
1553
|
|
|
@@ -248,6 +248,7 @@ class Payload:
|
|
|
248
248
|
otca_object: OTCA | None = None,
|
|
249
249
|
otkd_object: OTKD | None = None,
|
|
250
250
|
avts_object: AVTS | None = None,
|
|
251
|
+
settings: Settings | None = None,
|
|
251
252
|
logger: logging.Logger = default_logger,
|
|
252
253
|
) -> None:
|
|
253
254
|
"""Initialize the Payload object.
|
|
@@ -306,6 +307,9 @@ class Payload:
|
|
|
306
307
|
An optional Knowledge Discovery object.
|
|
307
308
|
avts_object (AVTS | None, optional):
|
|
308
309
|
An optional Aviator Search object.
|
|
310
|
+
settings (Settings | None, optional):
|
|
311
|
+
The customizer settings. They are the fallback for values that are
|
|
312
|
+
not defined in the payload. Defaults to the settings of the environment.
|
|
309
313
|
logger (logging.Logger, optional):
|
|
310
314
|
The logging object to use for all log messages. Defaults to default_logger.
|
|
311
315
|
|
|
@@ -315,6 +319,7 @@ class Payload:
|
|
|
315
319
|
for logfilter in logger.filters:
|
|
316
320
|
self.logger.addFilter(logfilter)
|
|
317
321
|
|
|
322
|
+
self._settings = settings if settings is not None else Settings()
|
|
318
323
|
self._stop_on_error = stop_on_error
|
|
319
324
|
self._payload_source = payload_source
|
|
320
325
|
self._k8s = k8s_object
|
|
@@ -12152,6 +12157,8 @@ class Payload:
|
|
|
12152
12157
|
object_type: str,
|
|
12153
12158
|
search_field: str,
|
|
12154
12159
|
search_value: str,
|
|
12160
|
+
filter_field: str | None = None,
|
|
12161
|
+
filter_value: str | None = None,
|
|
12155
12162
|
) -> str | None:
|
|
12156
12163
|
"""Get the Guidewire ID (str) of a Guidewire object by querying the Guidewire API.
|
|
12157
12164
|
|
|
@@ -12165,6 +12172,10 @@ class Payload:
|
|
|
12165
12172
|
Search field to find business object in external system.
|
|
12166
12173
|
search_value (str):
|
|
12167
12174
|
Search value to find business object in external system.
|
|
12175
|
+
filter_field (str | None, optional):
|
|
12176
|
+
Policy attribute used to filter the search results.
|
|
12177
|
+
filter_value (str | None, optional):
|
|
12178
|
+
Value that filter_field must equal.
|
|
12168
12179
|
|
|
12169
12180
|
Returns:
|
|
12170
12181
|
str | None:
|
|
@@ -12207,6 +12218,8 @@ class Payload:
|
|
|
12207
12218
|
case "Policy" | "policy" | "gw.policy":
|
|
12208
12219
|
response = guidewire_object.search_policy(
|
|
12209
12220
|
attributes={search_field: search_value},
|
|
12221
|
+
filter_field=filter_field,
|
|
12222
|
+
filter_value=filter_value,
|
|
12210
12223
|
)
|
|
12211
12224
|
bo_id = guidewire_object.get_result_value(response=response, key="policyNumber")
|
|
12212
12225
|
case "Claim" | "claim" | "gw.claim":
|
|
@@ -12718,6 +12731,8 @@ class Payload:
|
|
|
12718
12731
|
bo_id = business_object_data["bo_id"]
|
|
12719
12732
|
bo_search_field = None
|
|
12720
12733
|
bo_search_value = None
|
|
12734
|
+
bo_filter_field = None
|
|
12735
|
+
bo_filter_value = None
|
|
12721
12736
|
elif "bo_search_field" not in business_object_data or "bo_search_value" not in business_object_data:
|
|
12722
12737
|
self.logger.error(
|
|
12723
12738
|
"Missing BO search fields (bo_search_field, bo_search_value) in Business Object payload without BO ID for workspace -> '%s'.",
|
|
@@ -12727,6 +12742,8 @@ class Payload:
|
|
|
12727
12742
|
else:
|
|
12728
12743
|
bo_search_field = business_object_data["bo_search_field"]
|
|
12729
12744
|
bo_search_value = business_object_data["bo_search_value"]
|
|
12745
|
+
bo_filter_field = business_object_data.get("bo_filter_field")
|
|
12746
|
+
bo_filter_value = business_object_data.get("bo_filter_value")
|
|
12730
12747
|
bo_id = None
|
|
12731
12748
|
|
|
12732
12749
|
# Lookup external system in this payload or a former processed payload:
|
|
@@ -12811,6 +12828,8 @@ class Payload:
|
|
|
12811
12828
|
object_type=bo_type,
|
|
12812
12829
|
search_field=bo_search_field,
|
|
12813
12830
|
search_value=bo_search_value,
|
|
12831
|
+
filter_field=bo_filter_field,
|
|
12832
|
+
filter_value=bo_filter_value,
|
|
12814
12833
|
)
|
|
12815
12834
|
if not bo_id:
|
|
12816
12835
|
self.logger.warning(
|
|
@@ -20816,13 +20835,13 @@ class Payload:
|
|
|
20816
20835
|
# Principal propagation (pyxecm as SAML 2.0 identity provider, see SAP.get_saml_assertion())
|
|
20817
20836
|
# First priority is taking it from the external system payload - otherwise fall back to the global settings
|
|
20818
20837
|
saml_idp_private_key_file = (
|
|
20819
|
-
sap_external_system.get("saml_idp_private_key_file") or
|
|
20838
|
+
sap_external_system.get("saml_idp_private_key_file") or self._settings.sap.saml_idp_private_key_file
|
|
20820
20839
|
)
|
|
20821
20840
|
saml_idp_certificate_file = (
|
|
20822
|
-
sap_external_system.get("saml_idp_certificate_file") or
|
|
20841
|
+
sap_external_system.get("saml_idp_certificate_file") or self._settings.sap.saml_idp_certificate_file
|
|
20823
20842
|
)
|
|
20824
|
-
saml_issuer = sap_external_system.get("saml_issuer") or
|
|
20825
|
-
saml_audience = sap_external_system.get("saml_audience") or
|
|
20843
|
+
saml_issuer = sap_external_system.get("saml_issuer") or self._settings.sap.saml_issuer
|
|
20844
|
+
saml_audience = sap_external_system.get("saml_audience") or self._settings.sap.saml_audience
|
|
20826
20845
|
# Read the PEM files (typically mounted from a Kubernetes secret). The file names are
|
|
20827
20846
|
# not checked (e.g. a Kubernetes TLS secret mounts "tls.key" / "tls.crt") - SAP()
|
|
20828
20847
|
# validates the content and logs an error if it is not a matching PEM key pair:
|
|
@@ -976,6 +976,24 @@ class SAP:
|
|
|
976
976
|
|
|
977
977
|
# end method definition
|
|
978
978
|
|
|
979
|
+
def oauth_token_url(self) -> str:
|
|
980
|
+
"""Return the URL of SAP's OAuth 2.0 token endpoint, including the SAP client.
|
|
981
|
+
|
|
982
|
+
SAP checks that the Recipient of a SAML bearer assertion is exactly the URL the
|
|
983
|
+
token request is sent to - including the sap-client query parameter. Both
|
|
984
|
+
get_saml_assertion() and get_oauth_access_token() therefore use this URL.
|
|
985
|
+
|
|
986
|
+
Returns:
|
|
987
|
+
str:
|
|
988
|
+
The token endpoint URL, e.g.
|
|
989
|
+
"https://s4.example.com:44300/sap/bc/sec/oauth2/token?sap-client=100".
|
|
990
|
+
|
|
991
|
+
"""
|
|
992
|
+
|
|
993
|
+
return "{}{}?{}".format(self._base_url, OAUTH_TOKEN_PATH, urllib.parse.urlencode({"sap-client": self._client}))
|
|
994
|
+
|
|
995
|
+
# end method definition
|
|
996
|
+
|
|
979
997
|
def get_saml_assertion(
|
|
980
998
|
self,
|
|
981
999
|
user_id: str,
|
|
@@ -1023,8 +1041,9 @@ class SAP:
|
|
|
1023
1041
|
Defaults to "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified".
|
|
1024
1042
|
recipient (str | None, optional):
|
|
1025
1043
|
The token endpoint URL the assertion is posted to (SubjectConfirmationData
|
|
1026
|
-
Recipient). Defaults to None =
|
|
1027
|
-
needed if SAP
|
|
1044
|
+
Recipient). Defaults to None = oauth_token_url(), i.e.
|
|
1045
|
+
"<base_url>/sap/bc/sec/oauth2/token?sap-client=<client>". Only needed if SAP
|
|
1046
|
+
sees the token request under a different URL (e.g. behind a reverse proxy).
|
|
1028
1047
|
audience (str | None, optional):
|
|
1029
1048
|
The AudienceRestriction. Defaults to None = saml_audience of the constructor.
|
|
1030
1049
|
validity_seconds (int, optional):
|
|
@@ -1073,7 +1092,7 @@ class SAP:
|
|
|
1073
1092
|
)
|
|
1074
1093
|
return None
|
|
1075
1094
|
|
|
1076
|
-
recipient = recipient or
|
|
1095
|
+
recipient = recipient or self.oauth_token_url()
|
|
1077
1096
|
audience = audience or self._saml_audience
|
|
1078
1097
|
|
|
1079
1098
|
now = datetime.now(tz=UTC)
|
|
@@ -1167,7 +1186,7 @@ class SAP:
|
|
|
1167
1186
|
("Authorization: Bearer <access_token>", see the 'auth' parameter of the SAP class)
|
|
1168
1187
|
run with the SAP user's own authorizations - no SAP password of the user is needed.
|
|
1169
1188
|
|
|
1170
|
-
The request is sent as form data to
|
|
1189
|
+
The request is sent as form data to oauth_token_url(). It does
|
|
1171
1190
|
not use the session of this object (the token endpoint authenticates the OAuth
|
|
1172
1191
|
client, not the technical user).
|
|
1173
1192
|
|
|
@@ -1219,7 +1238,8 @@ class SAP:
|
|
|
1219
1238
|
if assertion.lstrip().startswith("<"):
|
|
1220
1239
|
assertion = base64.urlsafe_b64encode(assertion.strip().encode("utf-8")).decode("ascii").rstrip("=")
|
|
1221
1240
|
|
|
1222
|
-
|
|
1241
|
+
# Same URL (incl. sap-client) as the default Recipient of the assertion - SAP compares both:
|
|
1242
|
+
token_url = self.oauth_token_url()
|
|
1223
1243
|
data = {"grant_type": OAUTH_SAML2_BEARER_GRANT_TYPE, "assertion": assertion, "client_id": client_id}
|
|
1224
1244
|
if scope:
|
|
1225
1245
|
data["scope"] = scope
|
|
@@ -1229,7 +1249,6 @@ class SAP:
|
|
|
1229
1249
|
try:
|
|
1230
1250
|
response = requests.post(
|
|
1231
1251
|
url=token_url,
|
|
1232
|
-
params={"sap-client": self._client},
|
|
1233
1252
|
data=data,
|
|
1234
1253
|
auth=(client_id, client_secret),
|
|
1235
1254
|
headers={"User-Agent": USER_AGENT, "Accept": "application/json"},
|
|
@@ -1278,6 +1297,7 @@ class SAP:
|
|
|
1278
1297
|
client_secret: str,
|
|
1279
1298
|
scope: str | None = None,
|
|
1280
1299
|
nameid_format: str = SAML_NAMEID_FORMAT_UNSPECIFIED,
|
|
1300
|
+
recipient: str | None = None,
|
|
1281
1301
|
) -> dict | None:
|
|
1282
1302
|
"""Get an SAP OAuth 2.0 access token for a user via principal propagation, in one call.
|
|
1283
1303
|
|
|
@@ -1302,6 +1322,9 @@ class SAP:
|
|
|
1302
1322
|
nameid_format (str, optional):
|
|
1303
1323
|
The NameID format - must match transaction SAML2 (see get_saml_assertion()).
|
|
1304
1324
|
Defaults to "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified".
|
|
1325
|
+
recipient (str | None, optional):
|
|
1326
|
+
The Recipient of the assertion - only needed behind a reverse proxy (see
|
|
1327
|
+
get_saml_assertion()). Defaults to None = oauth_token_url().
|
|
1305
1328
|
|
|
1306
1329
|
Returns:
|
|
1307
1330
|
dict | None:
|
|
@@ -1310,7 +1333,7 @@ class SAP:
|
|
|
1310
1333
|
|
|
1311
1334
|
"""
|
|
1312
1335
|
|
|
1313
|
-
assertion = self.get_saml_assertion(user_id=user_id, nameid_format=nameid_format)
|
|
1336
|
+
assertion = self.get_saml_assertion(user_id=user_id, nameid_format=nameid_format, recipient=recipient)
|
|
1314
1337
|
if not assertion:
|
|
1315
1338
|
return None
|
|
1316
1339
|
|
|
@@ -1255,7 +1255,7 @@ class SAPMaintenanceOrderModel(SAPODataCreateModel):
|
|
|
1255
1255
|
basic_start_datetime: ODataV2DateTime = Field(
|
|
1256
1256
|
default=None,
|
|
1257
1257
|
alias="MaintOrdBasicStartDateTime",
|
|
1258
|
-
description="Basic start date and time of the order, ISO 8601, e.g. '2026-11-01T08:00:00Z'; without timezone treated as UTC.",
|
|
1258
|
+
description="Basic start date and time of the order, ISO 8601, e.g. '2026-11-01T08:00:00Z'; without timezone treated as UTC. Defaults to now if neither start nor end is given.",
|
|
1259
1259
|
)
|
|
1260
1260
|
basic_end_datetime: ODataV2DateTime = Field(
|
|
1261
1261
|
default=None,
|
|
@@ -1271,7 +1271,13 @@ class SAPMaintenanceOrderModel(SAPODataCreateModel):
|
|
|
1271
1271
|
|
|
1272
1272
|
@model_validator(mode="after")
|
|
1273
1273
|
def check_dates(self) -> Self:
|
|
1274
|
-
"""
|
|
1274
|
+
"""Default the basic start date and validate that the basic end date is not before it."""
|
|
1275
|
+
|
|
1276
|
+
# The API does not default the basic dates - without them SAP rejects the order with
|
|
1277
|
+
# IW/426 "Forward scheduling: Please enter start date". An end date alone is left
|
|
1278
|
+
# as is (backward scheduling):
|
|
1279
|
+
if self.basic_start_datetime is None and self.basic_end_datetime is None:
|
|
1280
|
+
self.basic_start_datetime = datetime.now(tz=UTC).replace(microsecond=0)
|
|
1275
1281
|
|
|
1276
1282
|
# Compare as UTC timestamps (naive datetimes are treated as UTC, see serialize_odata_datetime()):
|
|
1277
1283
|
start, end = (
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|