user-engine/tests/test_account_clarity.py

78 lines
4.7 KiB
Python
Raw Normal View History

"""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)