109 lines
4.2 KiB
Plaintext
109 lines
4.2 KiB
Plaintext
envelope = f"""<?xml version="1.0" encoding="utf-8"?>
|
|
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"
|
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
|
|
<s:Header>
|
|
<ApplicationToken xmlns="http://www.superoffice.com/superid/partnersystemuser/0.1">{APPLICATION_TOKEN}</ApplicationToken>
|
|
<ContextIdentifier xmlns="http://www.superoffice.com/superid/partnersystemuser/0.1">{CONTEXT_IDENTIFIER}</ContextIdentifier>
|
|
</s:Header>
|
|
<s:Body>
|
|
<AuthenticationRequest xmlns="http://www.superoffice.com/superid/partnersystemuser/0.1">
|
|
<SignedSystemToken>{SIGNED_SYSTEM_TOKEN}</SignedSystemToken>
|
|
<ReturnTokenType>{RETURN_TOKEN_TYPE}</ReturnTokenType>
|
|
</AuthenticationRequest>
|
|
</s:Body>
|
|
</s:Envelope>
|
|
""".strip()
|
|
|
|
headers = {
|
|
"Content-Type": "text/xml; charset=utf-8",
|
|
"SOAPAction": "http://www.superoffice.com/superid/partnersystemuser/0.1/IPartnerSystemUserService/Authenticate",
|
|
}
|
|
|
|
resp = requests.post(
|
|
SOAP_URL, data=envelope.encode("utf-8"), headers=headers, timeout=30
|
|
)
|
|
print(resp)
|
|
# --- Useful diagnostics if server responds with HTML or error ---
|
|
ct = resp.headers.get("Content-Type", "")
|
|
if "xml" not in ct.lower():
|
|
print("Unexpected response (not XML). Status:", resp.status_code)
|
|
print("Content-Type:", ct)
|
|
print(resp.text[:1200])
|
|
raise SystemExit("Check URL, SOAPAction, or required SOAP headers/values.")
|
|
|
|
# --- Parse SOAP response per WSDL ---
|
|
root = ET.fromstring(resp.text)
|
|
ns = {
|
|
"s": "http://schemas.xmlsoap.org/soap/envelope/",
|
|
"tns": "http://www.superoffice.com/superid/partnersystemuser/0.1",
|
|
}
|
|
|
|
# Fault?
|
|
fault = root.find(".//s:Fault", ns)
|
|
if fault is not None:
|
|
print(resp.text)
|
|
raise SystemExit("SOAP Fault returned. See XML above.")
|
|
|
|
# Extract AuthenticationResponse
|
|
is_ok_el = root.find(".//tns:AuthenticationResponse/tns:IsSuccessful", ns)
|
|
token_el = root.find(".//tns:AuthenticationResponse/tns:Token", ns)
|
|
err_el = root.find(".//tns:AuthenticationResponse/tns:ErrorMessage", ns)
|
|
|
|
is_ok = is_ok_el is not None and (is_ok_el.text or "").strip().lower() == "true"
|
|
token = token_el.text.strip() if token_el is not None and token_el.text else None
|
|
error_msg = (err_el.text or "").strip() if err_el is not None else ""
|
|
|
|
if not is_ok:
|
|
print("Authenticate returned IsSuccessful = false")
|
|
print("ErrorMessage:", error_msg)
|
|
print(resp.text)
|
|
raise SystemExit("Authentication failed.")
|
|
|
|
print("Authenticate succeeded.")
|
|
print("Token (truncated):", (token[:50] + "...") if token else None)
|
|
|
|
# If ReturnTokenType=Jwt, you can inspect claims to find the SOTicket claim key
|
|
if RETURN_TOKEN_TYPE.lower() == "jwt" and token and "." in token:
|
|
|
|
def b64url_decode(seg: str) -> bytes:
|
|
seg += "=" * ((4 - len(seg) % 4) % 4)
|
|
return base64.urlsafe_b64decode(seg.encode("ascii"))
|
|
|
|
header_b64, payload_b64, sig_b64 = token.split(".", 2)
|
|
payload = json.loads(b64url_decode(payload_b64))
|
|
print("JWT payload keys:", list(payload.keys()))
|
|
|
|
# Try to locate a ticket-like claim (key name may vary by env)
|
|
ticket = None
|
|
for k, v in payload.items():
|
|
if isinstance(v, str) and (
|
|
"ticket" in k.lower() or v.startswith(("7T:", "8A:", "8C:"))
|
|
):
|
|
ticket = v
|
|
break
|
|
|
|
if ticket:
|
|
print("Extracted system-user ticket:", ticket)
|
|
# Example REST call using SOTicket + SO-AppToken headers:
|
|
TENANT_BASE = (
|
|
"https://online.superoffice.com/Cust26703" # use your actual sodN host!
|
|
)
|
|
rest_headers = {
|
|
"Authorization": f"SOTicket {ticket}",
|
|
"SO-AppToken": APPLICATION_TOKEN, # same value as ApplicationToken
|
|
"Accept": "application/json",
|
|
}
|
|
test = requests.get(
|
|
f"{TENANT_BASE}/api/v1/contact/1", headers=rest_headers, timeout=30
|
|
)
|
|
print("REST test:", test.status_code, test.text)
|
|
else:
|
|
print(
|
|
"No obvious 'ticket' claim found in JWT. Inspect payload above and pick the correct claim manually."
|
|
)
|
|
else:
|
|
print(
|
|
"Returned token is not a JWT (or you requested SAML). Use the token as intended by your flow."
|
|
)
|
|
|