Assistant: codex Assistant-Model: gpt-6-astra Assistant-Session: 01a092fe-13b1-7f12-ac74-7d258af4d79c
77 lines
4.7 KiB
Python
77 lines
4.7 KiB
Python
"""Role-independent browser session state and recovery acceptance checks."""
|
|
import unittest
|
|
import test_portal_navigation
|
|
from test_web import invoke
|
|
from user_engine.web import PortalApplication
|
|
|
|
class AccountClarityTests(unittest.TestCase):
|
|
setUp = test_portal_navigation.PortalNavigationTests.setUp
|
|
|
|
def test_anonymous_and_expired_sessions_have_login_without_logout(self):
|
|
self.oidc.sessions['operator'].expires_at = 0
|
|
for cookie in [None, 'ue_session=unknown', 'ue_session=operator']:
|
|
for path in ['/', '/access-recovery', '/logged-out', '/security']:
|
|
with self.subTest(path=path, cookie=cookie):
|
|
response, body = invoke(self.app, path, cookie=cookie)
|
|
self.assertEqual('200 OK', response['status'])
|
|
self.assertIn(b'href="/login">Sign in', body)
|
|
self.assertNotIn(b'href="/logout"', body)
|
|
self.assertNotIn(b'action="/logout"', body)
|
|
self.assertNotIn(b'Signed in to this portal as', body)
|
|
self.assertNotIn(b'You have logged out', body)
|
|
self.assertEqual('no-store', response['headers']['Cache-Control'])
|
|
|
|
def test_authenticated_roles_have_logout_without_login(self):
|
|
for who in ['operator', 'member']:
|
|
for path in ['/', '/access-recovery', '/security', '/onboarding']:
|
|
with self.subTest(path=path, who=who):
|
|
response, body = invoke(self.app, path, cookie='ue_session='+who)
|
|
self.assertEqual('200 OK', response['status'])
|
|
self.assertIn(b'Signed in to this portal as', body)
|
|
self.assertIn(b'href="/logout"', body)
|
|
self.assertNotIn(b'href="/login"', body)
|
|
self.assertNotIn(b'Verify my current identity', body)
|
|
response, _ = invoke(self.app, '/register', cookie='ue_session='+who)
|
|
self.assertEqual('/onboarding', response['headers']['Location'])
|
|
|
|
def test_wrong_shared_identity_recovery_does_not_claim_a_known_session(self):
|
|
_, body = invoke(self.app, '/access-recovery')
|
|
self.assertIn(b'<details><summary>Wrong account', body)
|
|
self.assertIn(b'https://kc.example/account/logout', body)
|
|
self.assertIn(b'Use another account', body)
|
|
self.assertNotIn(b'Sign out of NetKingdom', body)
|
|
|
|
def test_browser_denial_is_recoverable_while_api_remains_json(self):
|
|
for cookie in [None, 'ue_session=member']:
|
|
response, body = invoke(self.app, '/platform', cookie=cookie, headers={'HTTP_ACCEPT':'text/html'})
|
|
self.assertEqual('403 Forbidden', response['status'])
|
|
self.assertIn('text/html', response['headers']['Content-Type'])
|
|
self.assertIn(b'href="/access-recovery"', body)
|
|
self.assertIn(b'corr_test', body)
|
|
self.assertNotIn(b'href="/platform"', body)
|
|
response, body = invoke(self.app, '/api/v1/me', headers={'HTTP_ACCEPT':'text/html'})
|
|
self.assertEqual('403 Forbidden', response['status'])
|
|
self.assertIn('application/json', response['headers']['Content-Type'])
|
|
self.assertIn(b'"access_denied"', body)
|
|
|
|
def test_otp_help_is_available_without_portal_login_and_never_claims_activation(self):
|
|
response, body = invoke(self.app, '/security', query='enabled=true&username=forged&next=https://evil.example')
|
|
self.assertEqual('200 OK', response['status'])
|
|
self.assertIn(b'Authenticator setup is temporarily unavailable', body)
|
|
self.assertIn(b'cannot currently confirm', body)
|
|
self.assertNotIn(b'Manage authenticator app</a>', body)
|
|
for value in [b'forged', b'evil.example', b'<img', b'name="otp"']:
|
|
self.assertNotIn(value, body)
|
|
self.app.mfa_management_url='https://authenticator.example/account'
|
|
_, body = invoke(self.app, '/security')
|
|
self.assertIn(b'href="https://authenticator.example/account">Manage authenticator app', body)
|
|
self.assertIn(b'Wait for the sign-in service to confirm activation', body)
|
|
self.assertIn(b'Opening the setup page does not activate', body)
|
|
self.assertNotIn(b'Authenticator setup is temporarily unavailable', body)
|
|
|
|
def test_provider_handoff_rejects_unsafe_configuration(self):
|
|
for target in ['http://provider.example', '//provider.example', 'https://u:p@provider.example',
|
|
'https://provider.example/?next=evil', 'https://provider.example/#secret', 'https://bad host/']:
|
|
with self.subTest(target=target), self.assertRaises(ValueError):
|
|
PortalApplication(self.app.service, trusted_proxy_secret=self.app.trusted_proxy_secret,
|
|
login_url=self.app.login_url, mfa_management_url=target)
|