SSO — Auth Infrastructure¶
What: Base classes for authenticated access to SUSTech systems. Handles CAS tickets (headless) and Shibboleth/CARSI (browser-based).
Use for: When building new integrations with SUSTech systems. Do not use directly — use the auth class for your target system.
Auth Rules (Non-Negotiable for Agents)¶
✅ Always Do¶
- Use
@require_auth(AuthorizerClass)(or callensure()before any sequence of operations) as the first line of defense - Call
ensure()before any HTTP request to a protected endpoint - For multi-call sequences: call
ensure()once at the start, then useauth.sessionto get arequests.Sessionwith cookies + headers already set - For CLI tools: login once per session; the in-memory session persists for the process lifetime
❌ Never Do¶
- Do not navigate to
cas.sustech.edu.cnmanually in a browser — it has a hidden reCAPTCHA that blocks automation - Do not store session cookies to disk — in-memory only, no
session.jsonwrites - Do not ask the user for credentials in plaintext — let the authorizer use the configured source; agents must not open the credential file themselves
- Do not try to automate the CAS login page with Playwright — the captcha will silently block you
🔑 Quick Reference¶
# GOOD — @require_auth decorator auto-injects the Authorizer
from sustech_survival.sso import require_auth, TISAuth
@require_auth(TISAuth)
def my_endpoint(auth=None):
r = auth.session.get("/xszykb/querydangqianxnxq") # cookies + headers pre-set
# GOOD — explicit ensure() + in-memory session
ok, reason = auth.ensure()
if not ok:
raise AuthorizerError(reason)
r = auth.session.get(url)
# GOOD — check vs. force-refresh
ok, reason = auth.check() # (ok, reason) — auto-refreshes if expired
auth.refresh() # bool — force a fresh CAS login when needed
# BAD — never do this
ok = auth._refresh() # private — use the public auth.refresh() instead
cookies = auth.load() # REMOVED — disk cache is gone
session = auth.requests_session # REMOVED — use auth.session
Credentials¶
The authorizer reads credentials internally. Resolution order: cred_set() in memory → SUSTECH_CREDENTIALS env var → ~/.sustech_survival/credentials.txt (the project's home default; relocated with $SUSTECH_HOME). Format: sid:password. Do not print or log auth.password.
Setting Up Credentials¶
Set credentials from an interactive terminal so the password is hidden:
Pick any of these locations (first match wins):
| # | Location | When to use |
|---|---|---|
| 1 | $SUSTECH_CREDENTIALS env var (path to file) |
CI, containers, agents |
| 2 | ~/.sustech_survival/credentials.txt (default; $SUSTECH_HOME relocates) |
Shared across projects (recommended) |
The repository root is not a credential lookup location. The CLI writes the credential file with owner-only permissions; sessions stay in memory. A same-user agent with unrestricted file access can still read the file, so file permissions do not isolate credentials from that agent.
Verifying credentials¶
# CLI — verify stored credentials against CAS
sustech sso check
# Python — ensure() returns (ok: bool, reason: str)
from sustech_survival.sso import TISAuth
ok, reason = TISAuth().ensure()
print(ok, reason) # True "Logged in as <your name>" or False "..."
Authorizer (ABC)¶
auth.check() # (bool, str) — verify session, auto-refresh if expired
auth.ensure() # (bool, str) — check + auto-refresh (recommended)
auth.refresh() # bool — force a fresh CAS login
auth.login() # headful Playwright browser login (last-resort fallback)
# Property
auth.session # requests.Session with cookies + headers already set
Sessions are stored in memory only. No session.json writes. Call ensure() before any HTTP request — it will auto-refresh if the session is missing or expired.
CASAuthorizer¶
Adds CAS ticket-grinding via requests. Inherit with _get_ticket_cookies() for custom CAS flows (or use the existing TISAuth, BBAuth, LibAuth, PMSAuth, etc. directly).
Inheritors: TISAuth, BBAuth, LibAuth, PMSAuth, WSAuth
ShibbolethAuthorizer¶
Adds Shibboleth WAYF/DS flow. Navigates to SP-initiated URL → institution dropdown → CARSI WAYF → SUSTech CAS → IdP consent → SP ACS.
CARSI Wayf Login¶
from sustech_survival.sso.providers.carsi import login_via_carsi
login_via_carsi(page, wayf_url)
# Searches for '南方科技大学' in WAYF, clicks result,
# calls selectidp() with correct entityID, returns on SP ACS redirect
Authlib Classes¶
The sso.authlib subpackage provides Authorizer subclasses for the
research-database providers. All use Shibboleth/CARSI Playwright login
(except PMSAuthorizer, which is CAS-based).
| Class | Auth Method | Status (2026-07-11) |
|---|---|---|
RSCAuthorizer |
Shibboleth/CARSI (Playwright) | ✅ Works |
WoSAuth |
Shibboleth/CARSI (Playwright) | ✅ Works |
CNKIAuth |
FSSO/Shibboleth (Playwright) | ✅ Works |
PMSAuth |
CAS tickets (headless) | ✅ Works |
IEEEAuth, SpringerAuth, WileyAuth, ScopusAuth, JSTORAuth, ACSAuth, PubMedAuth |
Shibboleth/CARSI | ✅ Available |
LibAuth SSL Fix (2026-05-30)¶
LibAuth had two SSL bugs blocking headless access to Primo:
ssl.OP_LEGACY_SERVER_CONNECT— only exists in Python 3.12+. Fix:getattr(ssl, 'OP_LEGACY_SERVER_CONNECT', 0x4)._urllib3_request_context()signature — urllib3 2.6+ requires a 4thpoolmanagerarg. Fix: addpoolmanager=Noneparam and passself.poolmanager.
Both fixes applied to providers/cas.py (_build_session()), authorizer.py (check()), and sso/__init__.py (LibAuth.session override).
All browser-based logins expose auth.page, auth.browser, auth.ctx after login() returns True. Caller must browser.close().
Off-Campus Trap¶
Direct requests calls to SUSTech services fail off-campus (timeout). Cloudscraper also fails off-campus. Use CARSI/Shibboleth SSO — it works from anywhere.