user-engine/tests/test_authentication_policy.py

71 lines
5 KiB
Python
Raw Normal View History

import time
from dataclasses import replace
from urllib.parse import parse_qs, urlsplit
from test_journey_roles import JourneyFixture
from test_web import invoke
class PolicyFixture:
def __init__(self): self.calls=[]; self.unavailable=False
def call(self, token, body):
self.calls.append((token,body))
if self.unavailable: raise RuntimeError('private-provider-error')
result=dict(success=True,status='current',revision=1,clients=[dict(id='vergabe-demo-company',name='Demo Company',mode='optional_after_enrollment')],history=[])
if body['action'] in {'preview','rollback'}:
result.update(status='preview',client='vergabe-demo-company',before='optional_after_enrollment',after='mandatory',reference='p06-case',confirmation='fixture-confirmation')
if body['action']=='apply':
result.update(status='recorded',receipt=dict(client='vergabe-demo-company',after='mandatory',reference='p06-case'))
return result
class AuthenticationPolicyJourney(JourneyFixture):
def setUp(self):
super().setUp(); self.provider=PolicyFixture(); self.app.authentication_policy=self.provider
session=self.oidc.sessions['operator']
self.oidc.sessions['operator']=replace(session,id_token='server-only-id-token',claims=dict(session.claims,assurance=dict(level='aal2',mfa=True,at=time.time())))
def test_aal1_account_access_does_not_grant_administration(self):
for who in ['member','admin','operator']:
self.oidc.sessions[who].claims['assurance']=dict(level='aal1',mfa=False)
for path in ['/','/onboarding','/security']:
response,_=invoke(self.app,path,cookie='ue_session='+who)
self.assertEqual('200 OK',response['status'])
for path in ['/platform','/admin/tenant:trial:demo-company','/platform/authentication-policy']:
_,body=invoke(self.app,path,cookie='ue_session='+who)
self.assertIn(b'Verify with MFA to administer',body)
response,_=self.post('/api/v1/platform/tenants',who=who)
self.assertEqual('403 Forbidden',response['status'])
self.assertEqual([],self.provider.calls)
self.assertEqual([],self.app.provisioning.actions)
def test_role_csrf_and_stale_mfa_deny_before_policy_service(self):
for who in ['member','admin']:
response,_=self.post('/platform/authentication-policy',who=who,action='preview')
self.assertEqual('403 Forbidden',response['status'])
response,_=self.post('/platform/authentication-policy',who='operator',csrf_token='wrong',action='preview')
self.assertEqual('403 Forbidden',response['status'])
self.oidc.sessions['operator'].claims['assurance']['at']=time.time()-301
_,body=invoke(self.app,'/platform/authentication-policy',cookie='ue_session=operator')
self.assertIn(b'fresh MFA sign-in',body);self.assertEqual([],self.provider.calls)
def test_review_explains_lockout_scope_rollback_and_receipts(self):
_,body=self.post('/platform/authentication-policy',who='operator',action='preview',client='vergabe-demo-company',mode='mandatory',reference='p06-case')
self.assertIn(b'Review policy change',body);self.assertIn(b'unable to complete sign-in',body)
self.assertIn(b'does not revoke already issued tokens',body);self.assertIn(b'Cancel without changes',body)
self.assertNotIn(b'server-only-id-token',body)
self.assertEqual('preview',self.provider.calls[-1][1]['action'])
_,body=self.post('/platform/authentication-policy',who='operator',action='apply',confirmation='fixture-confirmation',acknowledged='yes')
self.assertIn(b'Policy change recorded',body);self.assertIn(b'p06-case',body)
self.assertTrue(self.provider.calls[-1][1]['acknowledged'])
_,body=self.post('/platform/authentication-policy',who='operator',action='rollback',client='vergabe-demo-company',reference='rollback')
self.assertIn(b'Review policy change',body)
def test_outage_is_redacted_and_current_status_is_retryable(self):
self.provider.unavailable=True
_,body=invoke(self.app,'/platform/authentication-policy',cookie='ue_session=operator')
self.assertIn(b'Existing policy remains in force',body);self.assertNotIn(b'private-provider-error',body)
self.provider.unavailable=False
_,body=invoke(self.app,'/platform/authentication-policy',cookie='ue_session=operator')
self.assertIn(b'Current policy',body)
def test_policy_stepup_binds_only_supported_return_path(self):
response,_=invoke(self.app,'/login',query='recovery=1&return_path=/platform/authentication-policy')
query=parse_qs(urlsplit(dict(response['headers'])['Location']).query)
self.assertEqual(['aal2'],query['acr_values']);self.assertEqual(['login'],query['prompt'])
self.assertEqual('/platform/authentication-policy',self.oidc.pending[query['state'][0]].return_path)
response,_=invoke(self.app,'/login',query='recovery=1&return_path=https://evil.example')
self.assertEqual('400 Bad Request',response['status'])