test: add comprehensive integration test suite for IAM platform
Add 162 integration tests covering authentication flows, TOTP MFA, SSH key/certificate management, organization workflows, multi-org access, self-service features, admin operations, authorization, security edge cases, department/principal management, CA management, policy compliance, WebAuthn passkeys, and ZeroTier network access. Includes: - Reusable API client library with session management - Test fixtures for users, organizations, memberships, and CAs - Helper functions for SSH key generation and verification - Documentation for running and writing tests Also update test configuration to disable conflicting maas plugins and configure WebAuthn/session settings for localhost testing.
This commit is contained in:
@@ -0,0 +1,95 @@
|
||||
"""MFA (TOTP) client for integration tests."""
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class MfaClient:
|
||||
"""Wraps TOTP MFA-related API calls."""
|
||||
|
||||
def __init__(self, client):
|
||||
self._client = client
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# TOTP Enrollment
|
||||
# ------------------------------------------------------------------
|
||||
def enroll_totp(self) -> dict:
|
||||
"""Begin TOTP enrollment.
|
||||
|
||||
Returns:
|
||||
Response dict containing ``secret``, ``provisioning_uri``,
|
||||
``qr_code``, and ``backup_codes``.
|
||||
"""
|
||||
logger.info("[MfaClient] Enrolling TOTP")
|
||||
return self._client.post("/auth/totp/enroll")
|
||||
|
||||
def verify_enrollment(self, code: str, client_timestamp: str | None = None) -> dict:
|
||||
"""Complete TOTP enrollment by verifying the first code.
|
||||
|
||||
Args:
|
||||
code: 6-digit TOTP code generated from the secret.
|
||||
client_timestamp: Optional ISO-8601 timestamp for drift calc.
|
||||
"""
|
||||
payload = {"code": code}
|
||||
if client_timestamp:
|
||||
payload["client_timestamp"] = client_timestamp
|
||||
logger.info("[MfaClient] Verifying TOTP enrollment")
|
||||
return self._client.post("/auth/totp/verify-enrollment", data=payload)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# TOTP Verification (during login)
|
||||
# ------------------------------------------------------------------
|
||||
def verify_totp(self, code: str, is_backup_code: bool = False, client_timestamp: str | None = None) -> dict:
|
||||
"""Verify TOTP code during the multi-step login flow.
|
||||
|
||||
This is called AFTER ``AuthClient.login`` returns
|
||||
``requires_totp=True`` and stores the pending user id in the
|
||||
server-side session.
|
||||
|
||||
Args:
|
||||
code: 6-digit TOTP code or backup code.
|
||||
is_backup_code: True if ``code`` is a backup code.
|
||||
client_timestamp: Optional ISO-8601 timestamp.
|
||||
|
||||
Returns:
|
||||
Response dict containing ``user``, ``token``, ``expires_at``.
|
||||
"""
|
||||
payload = {"code": code, "is_backup_code": is_backup_code}
|
||||
if client_timestamp:
|
||||
payload["client_timestamp"] = client_timestamp
|
||||
logger.info(f"[MfaClient] Verifying TOTP — backup={is_backup_code}")
|
||||
result = self._client.post("/auth/totp/verify", data=payload)
|
||||
token = result.get("data", {}).get("token")
|
||||
if token:
|
||||
self._client.set_token(token)
|
||||
logger.info("[MfaClient] TOTP verification successful — token stored")
|
||||
return result
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# TOTP Management
|
||||
# ------------------------------------------------------------------
|
||||
def get_totp_status(self) -> dict:
|
||||
"""Return current TOTP status and remaining backup codes."""
|
||||
return self._client.get("/auth/totp/status")
|
||||
|
||||
def disable_totp(self, password: str) -> dict:
|
||||
"""Disable TOTP for the current user.
|
||||
|
||||
Args:
|
||||
password: Current account password (required for confirmation).
|
||||
"""
|
||||
return self._client.delete("/auth/totp/disable", data={"password": password})
|
||||
|
||||
def regenerate_backup_codes(self, password: str) -> dict:
|
||||
"""Generate a fresh set of backup codes.
|
||||
|
||||
Args:
|
||||
password: Current account password (required for confirmation).
|
||||
|
||||
Returns:
|
||||
Response dict containing ``backup_codes``.
|
||||
"""
|
||||
return self._client.post(
|
||||
"/auth/totp/regenerate-backup-codes",
|
||||
data={"password": password},
|
||||
)
|
||||
Reference in New Issue
Block a user