OAuth 관리자 진입 경로 정규화
This commit is contained in:
parent
f41066e3e8
commit
998acb44b0
3 changed files with 205 additions and 2 deletions
|
|
@ -97,6 +97,40 @@ def _fixture_saml_response(
|
|||
return base64.b64encode(xml.encode("utf-8")).decode("ascii")
|
||||
|
||||
|
||||
def _managed_user(
|
||||
*,
|
||||
email: str,
|
||||
role: str = "learner",
|
||||
admin_access: bool = False,
|
||||
) -> auth_sessions.ManagedUser:
|
||||
return auth_sessions.ManagedUser(
|
||||
user_id=auth_sessions.user_id_from_email(email),
|
||||
email=email,
|
||||
display_name=email,
|
||||
role=role,
|
||||
admin_access=admin_access,
|
||||
account_status="approved",
|
||||
cohort_ids=[],
|
||||
affiliation="",
|
||||
legal_name="",
|
||||
department="",
|
||||
grade_level="",
|
||||
phone="",
|
||||
contact_address="",
|
||||
nickname="",
|
||||
self_introduction="",
|
||||
avatar_url="",
|
||||
consent_at=None,
|
||||
profile_completed_at=1.0,
|
||||
terms_agreed_at=1.0,
|
||||
privacy_agreed_at=1.0,
|
||||
terms_version="",
|
||||
privacy_version="",
|
||||
created_at=1.0,
|
||||
last_seen_at=1.0,
|
||||
)
|
||||
|
||||
|
||||
class AuthProviderScaffoldTest(unittest.IsolatedAsyncioTestCase):
|
||||
async def asyncSetUp(self) -> None:
|
||||
auth_routes._oauth_states.clear()
|
||||
|
|
@ -695,6 +729,52 @@ class AuthProviderScaffoldTest(unittest.IsolatedAsyncioTestCase):
|
|||
self.assertNotIn("SAMLResponse", cookie_blob)
|
||||
self.assertNotIn(relay_state, auth_routes._saml_states)
|
||||
|
||||
async def test_saml_acs_sends_admin_entitled_generic_next_to_admin(self) -> None:
|
||||
relay_state = "relay-state"
|
||||
auth_routes._saml_states[relay_state] = auth_routes.SamlState(
|
||||
request_id="_request",
|
||||
next_path="/teach",
|
||||
created_at=1_800_000_000.0,
|
||||
)
|
||||
request = _form_request(
|
||||
"/auth/saml/acs",
|
||||
{
|
||||
"RelayState": relay_state,
|
||||
"SAMLResponse": _fixture_saml_response(
|
||||
email="operator@hs.ac.kr",
|
||||
display_name="Operator",
|
||||
role="teacher",
|
||||
),
|
||||
},
|
||||
)
|
||||
managed = _managed_user(email="operator@hs.ac.kr", role="learner", admin_access=True)
|
||||
|
||||
create_session_mock = AsyncMock(return_value=("opaque-session", object()))
|
||||
with (
|
||||
patched_settings(
|
||||
auth_saml_enabled=True,
|
||||
saml_sp_entity_id="https://api-vignette.chanpaca.net/auth/saml/metadata",
|
||||
saml_sso_url="https://sso.hs.ac.kr/idp/profile/SAML2/Redirect/SSO",
|
||||
saml_x509_cert_fingerprint="",
|
||||
frontend_base_url="https://vignette.test",
|
||||
environment="dev",
|
||||
),
|
||||
patch.object(auth_routes, "get_managed_user_by_email", AsyncMock(return_value=managed)),
|
||||
patch.object(auth_routes, "create_session", create_session_mock),
|
||||
):
|
||||
response = await auth_routes.saml_acs(request)
|
||||
|
||||
self.assertEqual(response.status_code, 302)
|
||||
self.assertEqual(response.headers["location"], "https://vignette.test/admin")
|
||||
create_session_mock.assert_awaited_once_with(
|
||||
email="operator@hs.ac.kr",
|
||||
display_name="Operator",
|
||||
role="learner",
|
||||
cohort_ids=[],
|
||||
external_id="saml:operator@hs.ac.kr",
|
||||
)
|
||||
self.assertNotIn(relay_state, auth_routes._saml_states)
|
||||
|
||||
async def test_saml_acs_rejects_bad_relay_state(self) -> None:
|
||||
request = _form_request(
|
||||
"/auth/saml/acs",
|
||||
|
|
@ -929,6 +1009,90 @@ class AuthProviderScaffoldTest(unittest.IsolatedAsyncioTestCase):
|
|||
self.assertNotIn("browser-must-not-see-this", cookie_blob)
|
||||
self.assertNotIn(state, auth_routes._oauth_states)
|
||||
|
||||
async def test_google_callback_sends_admin_entitled_generic_next_to_admin(self) -> None:
|
||||
state = "state-token"
|
||||
auth_routes._oauth_states[state] = auth_routes.OAuthState(
|
||||
code_verifier="verifier",
|
||||
next_path="/learn",
|
||||
created_at=1_800_000_000.0,
|
||||
)
|
||||
managed = _managed_user(email="operator@hs.ac.kr", role="learner", admin_access=True)
|
||||
|
||||
class FakeResponse:
|
||||
def __init__(self, status_code: int, payload: dict[str, Any]) -> None:
|
||||
self.status_code = status_code
|
||||
self._payload = payload
|
||||
|
||||
def json(self) -> dict[str, Any]:
|
||||
return self._payload
|
||||
|
||||
class FakeAsyncClient:
|
||||
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
||||
pass
|
||||
|
||||
async def __aenter__(self) -> "FakeAsyncClient":
|
||||
return self
|
||||
|
||||
async def __aexit__(self, exc_type: object, exc: object, tb: object) -> None:
|
||||
return None
|
||||
|
||||
async def post(self, url: str, **kwargs: Any) -> FakeResponse:
|
||||
return FakeResponse(200, {"id_token": "id-token"})
|
||||
|
||||
async def get(self, url: str, **kwargs: Any) -> FakeResponse:
|
||||
return FakeResponse(
|
||||
200,
|
||||
{
|
||||
"aud": "google-client",
|
||||
"iss": "https://accounts.google.com",
|
||||
"email": "operator@hs.ac.kr",
|
||||
"email_verified": "true",
|
||||
"name": "Operator",
|
||||
"hd": "hs.ac.kr",
|
||||
},
|
||||
)
|
||||
|
||||
create_session_mock = AsyncMock(return_value=("opaque-session", object()))
|
||||
with (
|
||||
patched_settings(
|
||||
oauth_google_client_id="google-client",
|
||||
oauth_google_client_secret="google-secret",
|
||||
oauth_redirect_uri="https://api-vignette.test/auth/callback",
|
||||
frontend_base_url="https://vignette.test",
|
||||
environment="prod",
|
||||
auth_admin_emails=[],
|
||||
auth_super_admin_emails=[],
|
||||
),
|
||||
patch.object(auth_routes.httpx, "AsyncClient", FakeAsyncClient),
|
||||
patch.object(auth_routes, "get_managed_user_by_email", AsyncMock(return_value=managed)),
|
||||
patch.object(auth_routes, "create_session", create_session_mock),
|
||||
):
|
||||
response = await auth_routes.callback(
|
||||
_request(),
|
||||
code="auth-code",
|
||||
state=state,
|
||||
oauth_state_cookie=state,
|
||||
)
|
||||
|
||||
self.assertEqual(response.status_code, 302)
|
||||
self.assertEqual(response.headers["location"], "https://vignette.test/admin")
|
||||
create_session_mock.assert_awaited_once_with(
|
||||
email="operator@hs.ac.kr",
|
||||
display_name="Operator",
|
||||
role="learner",
|
||||
cohort_ids=[],
|
||||
external_id="google:operator@hs.ac.kr",
|
||||
)
|
||||
self.assertEqual(
|
||||
auth_routes._post_login_next_path(
|
||||
"/learn/session/session-id",
|
||||
email="operator@hs.ac.kr",
|
||||
role=Role.LEARNER,
|
||||
managed_user=managed,
|
||||
),
|
||||
"/learn/session/session-id",
|
||||
)
|
||||
|
||||
async def test_google_callback_accepts_signed_state_after_process_restart(self) -> None:
|
||||
with patched_settings(
|
||||
environment="prod",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue