"""Real HTTP MFA, recovery, privilege and session-expiration boundaries.""" import re from application_harness import Application app=Application();checks=0 def check(ok,message): global checks if not ok:raise AssertionError(message) checks+=1;print('PASS:',message) def sign_password(email='admin@example.test',password='Admin test password 123!'): _,body,_=app.request() return app.request(data={'action':'SignIn.login','csrf_token':app.csrf(body),'user_email':email,'user_password':password}) def security(body,code,password='Admin test password 123!',**extra): return app.request(data={'action':'security','csrf_token':app.csrf(body),'mfa_code':code,'current_password':password,**extra}) def sid():return next(c.value for c in app.client.handlers[0].cookiejar if c.name=='HUNTINGCLUBROSTER') def age(**values): # Find the cookie processor without assuming handler ordering. jar=next(h.cookiejar for h in app.client.handlers if hasattr(h,'cookiejar')) cookie=next(c.value for c in jar if c.name=='HUNTINGCLUBROSTER') app.fixture('sessionAge',cookie,str(values.get('absolute','')),str(values.get('idle','')),str(values.get('recent',''))) try: ids=app.fixture('tenantSeed') status,body,_=sign_password() check(status==200 and b'setup key' in body,'Password alone exposes setup and no administrator workspace') token=app.csrf(body) check(app.request(data={'action':'Users.deleteUser','csrf_token':token,'user_serial':str(ids['membera'])},ajax=True)[0]==403,'AJAX cannot bypass mandatory enrollment') check(app.request('/main.php?Xaction=Roster.workspace&club_serial='+str(ids['clubA']))[1].find(b'setup key')>=0,'Direct club URLs cannot bypass enrollment') check(app.request(data={'action':'security','mfa_code':'000000','current_password':'Admin test password 123!'})[0]==403,'MFA enrollment requires CSRF') secret=re.search(rb'
([A-Z2-7]+)',body).group(1).decode();code=app.totp(secret) _,bad,_=security(body,code,'incorrect password') check(b'Current password is incorrect' in bad and not app.fixture('mfaState','admin@example.test')['factor'],'Setup requires the current password and commits no partial factor') status,body,_=security(bad,code) codes=[c.decode() for c in re.findall(rb'\b[a-f0-9]{32}\b',body)] check(status==200 and len(codes)==10 and b'Save your recovery codes' in body,'Valid setup shows ten recovery codes once') state=app.fixture('mfaState','admin@example.test') check(secret not in state['factor']['secret_cipher'] and all(c not in state['recovery'] for c in codes),'Database stores encrypted secret and hashed recovery codes') _,body,_=app.request();app.token=app.csrf(body) check(b'Save your recovery codes' not in body,'Recovery code plaintext is absent on subsequent pages') app.logout();_,body,_=sign_password() check(b'setup key' not in body and b'Authentication code' in body,'Enrolled administrator must supply a second factor on a new login') _,body,_=security(body,code) check(b'Password or authentication code is incorrect' in body,'An authenticator code already used during setup cannot be replayed') _,body,_=security(body,codes[0]);app.token=app.csrf(body) check(b'Club workspace' in body and len(app.fixture('mfaState','admin@example.test')['recovery'])==9,'Recovery code completes login and is consumed') app.logout();_,body,_=sign_password();_,body,_=security(body,codes[0]) check(b'Password or authentication code is incorrect' in body,'Consumed recovery code cannot be reused') _,body,_=security(body,codes[1]);app.token=app.csrf(body) age(recent=301) check(app.post('Users.changePassword')[0]==403,'Administrator password changes also require recent second-factor confirmation') check(app.post('Roster.workspace',step='member',club_serial=ids['clubA'],membership_serial=ids['clubA_shared'],membership_status='active',joined_on='2026-01-01',membership_role='administrator')[0]==403,'Privilege changes reject stale recent authentication') _,body,_=app.request('/main.php?Xaction=security');_,body,_=security(body,codes[2]);app.token=app.csrf(body) check(app.post('Roster.workspace',step='member',club_serial=ids['clubA'],membership_serial=ids['clubA_shared'],membership_status='active',joined_on='2026-01-01',membership_role='administrator')[0]==200,'Privilege change succeeds after fresh password and second factor') age(absolute=28800,idle=0) check(app.post('toggleTheme')[0]==401,'Active session still expires at the eight-hour absolute boundary') _,body,_=sign_password();_,body,_=security(body,codes[3]);app.token=app.csrf(body);age(absolute=28799,idle=1801) check(app.post('toggleTheme')[0]==401,'Idle timeout remains enforced independently') _,body,_=sign_password();_,body,_=security(body,codes[4]);app.token=app.csrf(body) _,body,_=app.request('/main.php?Xaction=security');_,body,_=security(body,codes[5],factor_action='replace') check(b'setup key' in body and app.fixture('mfaState','admin@example.test')['factor'] and len(app.fixture('mfaState','admin@example.test')['recovery'])==4,'Pending replacement preserves the old factor and remaining recovery codes') check(b'Club workspace' in app.request('/main.php?Xaction=Roster.workspace')[1],'Authorized session retains its existing MFA access during replacement') newsecret=re.search(rb'
([A-Z2-7]+)',body).group(1).decode() check(newsecret!=secret,'Replacement generates a fresh authenticator secret') status,limited,_=security(body,app.totp(newsecret),factor_action='confirmReplacement') check(status==429 and b'Too many authentication attempts' in limited,'Repeated MFA attempts enforce a persistent account budget') app.fixture('clearTestAttempts') _,body,_=security(limited,app.totp(newsecret),factor_action='confirmReplacement');check(b'Save your recovery codes' in body,'Replacement authenticator can be enrolled with fresh recovery codes') _,body,_=app.request();app.token=app.csrf(body);app.logout();app.login('membera@example.test','Tenant test password 123!') check(b'Club workspace' in app.request()[1],'Ordinary unenrolled members retain password login') app.logout();_,body,_=sign_password('shared@example.test','Tenant test password 123!') check(b'setup key' in body,'Newly promoted administrator must enroll despite an ordinary global account role') print(checks,'security capacity HTTP checks passed.') except Exception: app.log.seek(0);print(app.log.read().decode(errors='replace'));raise finally:app.close()