2025-08-02 22:37:17 +02:00
|
|
|
import requests
|
|
|
|
|
import urllib.parse
|
|
|
|
|
import re
|
|
|
|
|
from config.constants import AUTH_BASE_URL, ANNY_BASE_URL, DEFAULT_HEADERS
|
|
|
|
|
from utils.helpers import extract_html_value
|
2026-01-22 16:17:05 +01:00
|
|
|
from auth.providers import get_provider, SSOProvider
|
|
|
|
|
|
2025-08-02 22:37:17 +02:00
|
|
|
|
|
|
|
|
class AnnySession:
|
2026-01-22 16:17:05 +01:00
|
|
|
def __init__(self, username: str, password: str, provider_name: str = "kit"):
|
2025-08-02 22:37:17 +02:00
|
|
|
self.session = requests.Session()
|
|
|
|
|
self.username = username
|
|
|
|
|
self.password = password
|
|
|
|
|
|
2026-01-22 16:17:05 +01:00
|
|
|
# Initialize the SSO provider
|
|
|
|
|
provider_class = get_provider(provider_name)
|
|
|
|
|
self.provider: SSOProvider = provider_class(username, password)
|
|
|
|
|
|
2025-08-02 22:37:17 +02:00
|
|
|
def login(self):
|
|
|
|
|
try:
|
|
|
|
|
self._init_headers()
|
|
|
|
|
self._sso_login()
|
2026-01-22 16:17:05 +01:00
|
|
|
self._provider_auth()
|
2025-08-02 22:37:17 +02:00
|
|
|
self._consume_saml()
|
2026-01-22 16:17:05 +01:00
|
|
|
print(f"✅ Login successful via {self.provider.name}.")
|
2025-08-02 22:37:17 +02:00
|
|
|
return self.session.cookies
|
2026-01-22 16:17:05 +01:00
|
|
|
except requests.RequestException as e:
|
|
|
|
|
print(f"[Login Error] Network error: {type(e).__name__}")
|
|
|
|
|
return None
|
|
|
|
|
except ValueError as e:
|
2025-08-02 22:37:17 +02:00
|
|
|
print(f"[Login Error] {e}")
|
|
|
|
|
return None
|
2026-01-22 16:17:05 +01:00
|
|
|
except KeyError as e:
|
|
|
|
|
print(f"[Login Error] Missing expected field: {e}")
|
|
|
|
|
return None
|
2025-08-02 22:37:17 +02:00
|
|
|
|
|
|
|
|
def _init_headers(self):
|
|
|
|
|
self.session.headers.update({
|
|
|
|
|
**DEFAULT_HEADERS,
|
|
|
|
|
'accept': 'text/html, application/xhtml+xml',
|
|
|
|
|
'referer': AUTH_BASE_URL + '/',
|
|
|
|
|
'origin': AUTH_BASE_URL
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
def _sso_login(self):
|
|
|
|
|
r1 = self.session.get(f"{AUTH_BASE_URL}/login/sso")
|
|
|
|
|
self.session.headers['X-XSRF-TOKEN'] = urllib.parse.unquote(r1.cookies['XSRF-TOKEN'])
|
|
|
|
|
|
|
|
|
|
page_data = extract_html_value(r1.text, r'data-page="(.*?)"')
|
|
|
|
|
version = re.search(r'"version"\s*:\s*"([a-f0-9]{32})"', page_data)
|
|
|
|
|
x_inertia_version = version.group(1) if version else '66b32acea13402d3aef4488ccd239c93'
|
|
|
|
|
|
|
|
|
|
self.session.headers.update({
|
|
|
|
|
'x-requested-with': 'XMLHttpRequest',
|
|
|
|
|
'x-inertia': 'true',
|
|
|
|
|
'x-inertia-version': x_inertia_version
|
|
|
|
|
})
|
|
|
|
|
|
2026-01-22 16:17:05 +01:00
|
|
|
r2 = self.session.post(f"{AUTH_BASE_URL}/login/sso", json={"domain": self.provider.domain})
|
2025-08-02 22:37:17 +02:00
|
|
|
redirect_url = r2.headers['x-inertia-location']
|
2026-01-22 16:17:05 +01:00
|
|
|
redirect_response = self.session.get(redirect_url)
|
2025-08-02 22:37:17 +02:00
|
|
|
|
2026-01-22 16:17:05 +01:00
|
|
|
# Pass session and redirect response to provider
|
|
|
|
|
self.provider.set_session(self.session)
|
|
|
|
|
self.provider.set_redirect_response(redirect_response)
|
2025-08-02 22:37:17 +02:00
|
|
|
|
2026-01-22 16:17:05 +01:00
|
|
|
def _provider_auth(self):
|
|
|
|
|
"""Delegate authentication to the SSO provider."""
|
|
|
|
|
self.saml_response_html = self.provider.authenticate()
|
2025-08-02 22:37:17 +02:00
|
|
|
|
|
|
|
|
def _consume_saml(self):
|
|
|
|
|
consume_url = extract_html_value(self.saml_response_html, r'form action="([^"]+)"')
|
|
|
|
|
relay_state = extract_html_value(self.saml_response_html, r'name="RelayState" value="([^"]+)"')
|
|
|
|
|
saml_response = extract_html_value(self.saml_response_html, r'name="SAMLResponse" value="([^"]+)"')
|
|
|
|
|
|
|
|
|
|
self.session.post(consume_url, data={
|
|
|
|
|
'RelayState': relay_state,
|
|
|
|
|
'SAMLResponse': saml_response
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
self.session.get(f"{ANNY_BASE_URL}/en-us/login?target=/en-us/home?withoutIntent=true")
|