Implement scoped P06 authentication policy and guarded optional onboarding
Assistant: codex Assistant-Model: gpt-6-astra Assistant-Session: 01a09cbb-87c6-7900-a145-4ce53ba9f1a6
This commit is contained in:
parent
0801ec55ac
commit
3bd1827a7f
10 changed files with 298 additions and 8 deletions
56
src/user_engine/authentication_policy.py
Normal file
56
src/user_engine/authentication_policy.py
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
"""Platform policy review and transport; issuer remains policy authority."""
|
||||
from html import escape
|
||||
from user_engine.factor_recovery import FactorRecoveryClient
|
||||
|
||||
class PolicyClient(FactorRecoveryClient):
|
||||
def __init__(self, url):
|
||||
super().__init__(url)
|
||||
self.url = url.rstrip('/') + '/platform/authentication-policy'
|
||||
|
||||
LABELS = {'mandatory': 'MFA required', 'optional_after_enrollment': 'Optional until an authenticator is activated'}
|
||||
|
||||
def page(csrf, result=None):
|
||||
result = result or {}
|
||||
def hidden(name, value):
|
||||
return '<input type="hidden" name="'+name+'" value="'+escape(str(value), quote=True)+'">'
|
||||
common = hidden('csrf_token', csrf)
|
||||
html = '<h1>Authentication policy</h1><p>Choose the sign-in requirement for one reviewed application. Other applications keep their existing policy.</p>'
|
||||
html += '<p><strong>Application step-up always wins.</strong> An explicit MFA request still requires an authenticator. Portal administration requires MFA; policy changes and lost-factor recovery require recent MFA.</p>'
|
||||
failure = result.get('failure')
|
||||
if failure:
|
||||
messages = {
|
||||
'fresh_platform_mfa_required': 'Verify your identity with a fresh MFA sign-in before viewing or changing policy.',
|
||||
'preview_expired_or_changed': 'The review expired, changed or belongs to another session. Check the current policy and review again.',
|
||||
'policy_changed_review_again': 'Policy changed after this review. Check the current policy and review again.',
|
||||
'reference_already_used': 'This reference is already recorded. Check the history; use a new reference for a new change.',
|
||||
'policy_unchanged': 'The selected policy is already active. No change was made.',
|
||||
'unsupported_policy': 'This policy or application is unsupported. Choose one of the available policies.',
|
||||
}
|
||||
html += '<p role="alert">'+escape(messages.get(failure, 'The policy service is unavailable. Existing policy remains in force. Check the current state before retrying a change.'))+'</p>'
|
||||
if failure == 'fresh_platform_mfa_required':
|
||||
html += '<p><a class="button" href="/login?recovery=1&return_path=/platform/authentication-policy">Verify with MFA</a> · <a href="/security">Set up or recover an authenticator</a></p>'
|
||||
if result.get('status') == 'recorded':
|
||||
receipt = result.get('receipt', {})
|
||||
html += '<h2>Policy change recorded</h2><p>Reference: '+escape(str(receipt.get('reference','')))+'. Application: '+escape(str(receipt.get('client','')))+'. Recorded policy: '+escape(LABELS.get(receipt.get('after'), 'Unknown'))+'. Check the current policy below; later changes may supersede this receipt.</p>'
|
||||
if result.get('status') == 'preview':
|
||||
html += '<section><h2>Review policy change</h2><p>Application: <strong>'+escape(str(result.get('client','')))+ '</strong>. Change from '+escape(LABELS.get(result.get('before'),'Unknown'))+' to <strong>'+escape(LABELS.get(result.get('after'),'Unknown'))+'</strong>.</p>'
|
||||
if result.get('after') == 'mandatory':
|
||||
html += '<p>People without a confirmed working authenticator will be unable to complete sign-in. Verify enrollment and a recovery route before applying. This preview does not count unenrolled users.</p>'
|
||||
else:
|
||||
html += '<p>People with no confirmed authenticator can sign in with their password. Once an authenticator is activated, MFA is required. Pending or cancelled setup does not activate MFA; provider lookup failures deny sign-in.</p>'
|
||||
html += '<p>This affects subsequent authorization requests. It does not revoke already issued tokens or change another application. Rollback restores the previous policy through another reviewed change.</p>'
|
||||
html += '<form method="post" action="/platform/authentication-policy">'+common+hidden('action','apply')+hidden('confirmation',result.get('confirmation',''))+'<label><input type="checkbox" name="acknowledged" value="yes" required> I reviewed who may lose access and verified the enrollment and recovery route.</label><button type="submit">Apply this policy</button></form><p><a href="/platform/authentication-policy">Cancel without changes</a></p></section>'
|
||||
for client in result.get('clients') or []:
|
||||
if not isinstance(client, dict): continue
|
||||
html += '<section><h2>'+escape(str(client.get('name') or client.get('id')))+ '</h2><p>Current policy: <strong>'+escape(LABELS.get(client.get('mode'),'Unknown'))+'</strong>. Revision '+escape(str(result.get('revision','unknown')))+'.</p>'
|
||||
html += '<form method="post" action="/platform/authentication-policy">'+common+hidden('client',client.get('id',''))+'<label>Policy <select name="mode">'
|
||||
for value,label in LABELS.items():
|
||||
html += '<option value="'+value+'"'+(' selected' if value==client.get('mode') else '')+'>'+label+'</option>'
|
||||
html += '</select></label><label>Change reference <input name="reference" maxlength="150" required></label><button name="action" value="preview">Preview policy</button><button name="action" value="rollback">Preview rollback</button></form></section>'
|
||||
history = result.get('history') or []
|
||||
if history:
|
||||
html += '<h2>Recent policy changes</h2><ul>'
|
||||
for receipt in reversed(history):
|
||||
html += '<li>'+escape(str(receipt.get('reference','')))+' — '+escape(str(receipt.get('client','')))+': '+escape(LABELS.get(receipt.get('before'),'Unknown'))+' → '+escape(LABELS.get(receipt.get('after'),'Unknown'))+'; actor '+escape(str(receipt.get('actor','')))+', revision '+escape(str(receipt.get('revision','')))+'.</li>'
|
||||
html += '</ul>'
|
||||
return html+'<p><a href="/platform/authentication-policy">Check current policy</a> · <a href="/platform">Return to platform administration</a></p>'
|
||||
Loading…
Add table
Add a link
Reference in a new issue