Auto-commit local changes before build (2026-01-15 12:31:08)
This commit is contained in:
parent
83d487a206
commit
1a2ca59d16
@ -1 +1 @@
|
||||
v20260115-05-autotask-queues-picklist-fix
|
||||
v20260115-06-autotask-auth-fallback
|
||||
|
||||
@ -33,32 +33,51 @@ class AutotaskClient:
|
||||
self.timeout_seconds = timeout_seconds
|
||||
|
||||
self._zone_info: Optional[AutotaskZoneInfo] = None
|
||||
self._zoneinfo_base_used: Optional[str] = None
|
||||
|
||||
def _zoneinfo_base(self) -> str:
|
||||
# Production zone lookup endpoint: webservices.autotask.net
|
||||
# Sandbox is typically pre-release: webservices2.autotask.net
|
||||
def _zoneinfo_bases(self) -> List[str]:
|
||||
"""Return a list of zoneInformation base URLs to try.
|
||||
|
||||
Autotask tenants can behave differently for Sandbox vs Production.
|
||||
To keep connection testing reliable, we try the expected base first
|
||||
and fall back to the alternative if needed.
|
||||
"""
|
||||
prod = "https://webservices.autotask.net/atservicesrest"
|
||||
sb = "https://webservices2.autotask.net/atservicesrest"
|
||||
if self.environment == "sandbox":
|
||||
return "https://webservices2.autotask.net/atservicesrest"
|
||||
return "https://webservices.autotask.net/atservicesrest"
|
||||
return [sb, prod]
|
||||
return [prod, sb]
|
||||
|
||||
def get_zone_info(self) -> AutotaskZoneInfo:
|
||||
if self._zone_info is not None:
|
||||
return self._zone_info
|
||||
|
||||
url = f"{self._zoneinfo_base().rstrip('/')}/v1.0/zoneInformation"
|
||||
params = {"user": self.username}
|
||||
try:
|
||||
resp = requests.get(url, params=params, timeout=self.timeout_seconds)
|
||||
except Exception as exc:
|
||||
raise AutotaskError(f"ZoneInformation request failed: {exc}") from exc
|
||||
last_error: Optional[str] = None
|
||||
data: Optional[Dict[str, Any]] = None
|
||||
for base in self._zoneinfo_bases():
|
||||
url = f"{base.rstrip('/')}/v1.0/zoneInformation"
|
||||
params = {"user": self.username}
|
||||
try:
|
||||
resp = requests.get(url, params=params, timeout=self.timeout_seconds)
|
||||
except Exception as exc:
|
||||
last_error = f"ZoneInformation request failed for {base}: {exc}"
|
||||
continue
|
||||
|
||||
if resp.status_code >= 400:
|
||||
raise AutotaskError(f"ZoneInformation request failed (HTTP {resp.status_code}).")
|
||||
if resp.status_code >= 400:
|
||||
last_error = f"ZoneInformation request failed for {base} (HTTP {resp.status_code})."
|
||||
continue
|
||||
|
||||
try:
|
||||
data = resp.json()
|
||||
except Exception as exc:
|
||||
raise AutotaskError("ZoneInformation response is not valid JSON.") from exc
|
||||
try:
|
||||
data = resp.json()
|
||||
except Exception:
|
||||
last_error = f"ZoneInformation response from {base} is not valid JSON."
|
||||
continue
|
||||
|
||||
self._zoneinfo_base_used = base
|
||||
break
|
||||
|
||||
if data is None:
|
||||
raise AutotaskError(last_error or "ZoneInformation request failed.")
|
||||
|
||||
zone = AutotaskZoneInfo(
|
||||
zone_name=str(data.get("zoneName") or ""),
|
||||
@ -74,8 +93,11 @@ class AutotaskClient:
|
||||
return zone
|
||||
|
||||
def _headers(self) -> Dict[str, str]:
|
||||
# Autotask REST API requires the APIIntegrationcode header for API-only users.
|
||||
# Autotask REST API requires the ApiIntegrationCode header.
|
||||
# Some tenants/proxies appear picky despite headers being case-insensitive,
|
||||
# so we include both common casings for maximum compatibility.
|
||||
return {
|
||||
"ApiIntegrationCode": self.api_integration_code,
|
||||
"APIIntegrationcode": self.api_integration_code,
|
||||
"Content-Type": "application/json",
|
||||
"Accept": "application/json",
|
||||
@ -85,20 +107,41 @@ class AutotaskClient:
|
||||
zone = self.get_zone_info()
|
||||
base = zone.api_url.rstrip("/")
|
||||
url = f"{base}/v1.0/{path.lstrip('/')}"
|
||||
try:
|
||||
resp = requests.request(
|
||||
headers = self._headers()
|
||||
|
||||
def do_request(use_basic_auth: bool, extra_headers: Optional[Dict[str, str]] = None):
|
||||
h = dict(headers)
|
||||
if extra_headers:
|
||||
h.update(extra_headers)
|
||||
return requests.request(
|
||||
method=method.upper(),
|
||||
url=url,
|
||||
headers=self._headers(),
|
||||
headers=h,
|
||||
params=params or None,
|
||||
auth=(self.username, self.password),
|
||||
auth=(self.username, self.password) if use_basic_auth else None,
|
||||
timeout=self.timeout_seconds,
|
||||
)
|
||||
|
||||
try:
|
||||
# Primary auth method: HTTP Basic (username + API secret)
|
||||
resp = do_request(use_basic_auth=True)
|
||||
|
||||
# Compatibility fallback: some environments accept credentials only via headers.
|
||||
if resp.status_code == 401:
|
||||
resp = do_request(
|
||||
use_basic_auth=False,
|
||||
extra_headers={"UserName": self.username, "Secret": self.password},
|
||||
)
|
||||
except Exception as exc:
|
||||
raise AutotaskError(f"Request failed: {exc}") from exc
|
||||
|
||||
if resp.status_code == 401:
|
||||
raise AutotaskError("Authentication failed (HTTP 401). Check username/password and APIIntegrationcode.")
|
||||
zi_base = self._zoneinfo_base_used or "unknown"
|
||||
raise AutotaskError(
|
||||
"Authentication failed (HTTP 401). "
|
||||
"Verify API Username, API Secret, and ApiIntegrationCode. "
|
||||
f"Environment={self.environment}, ZoneInfoBase={zi_base}, ZoneApiUrl={zone.api_url}."
|
||||
)
|
||||
if resp.status_code == 403:
|
||||
raise AutotaskError("Access forbidden (HTTP 403). API user permissions may be insufficient.")
|
||||
if resp.status_code == 404:
|
||||
|
||||
@ -46,6 +46,15 @@ Changes:
|
||||
- Normalized picklist values so IDs and display labels are handled consistently in settings dropdowns.
|
||||
- Fixed Autotask connection test to rely on picklist availability, preventing false 404 errors.
|
||||
|
||||
## v20260115-06-autotask-auth-fallback
|
||||
|
||||
### Changes:
|
||||
- Improved Autotask authentication handling to support sandbox-specific behavior.
|
||||
- Implemented automatic fallback authentication flow when initial Basic Auth returns HTTP 401.
|
||||
- Added support for header-based authentication using UserName and Secret headers alongside the Integration Code.
|
||||
- Extended authentication error diagnostics to include selected environment and resolved Autotask zone information.
|
||||
- Increased reliability of Autotask connection testing across different tenants and sandbox configurations.
|
||||
|
||||
***
|
||||
|
||||
## v0.1.21
|
||||
|
||||
Loading…
Reference in New Issue
Block a user