key-cape/scripts/keycape_onboarding_guard.py
tegwick e0b3c25f06
All checks were successful
Authentication acceptance / acceptance (push) Successful in 1m22s
Authentication acceptance / provider-contract (push) Successful in 14s
Build and Publish Container Image / build-and-push (push) Successful in 41s
Implement scoped P06 authentication policy and guarded optional onboarding
Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a09cbb-87c6-7900-a145-4ce53ba9f1a6
2026-09-14 00:00:05 +02:00

27 lines
1.4 KiB
Python

"""Provider enrollment hook: password-only self-service cannot replace an active factor.
Loaded explicitly by provider configuration, so a missing module fails startup.
Pending setup can be regenerated/confirmed; active-factor replacement goes through
fresh-MFA audited recovery. Admin actions retain their existing provider policies.
"""
def check(request, action):
from flask import g
from privacyidea.lib.error import PolicyError
from privacyidea.lib.user import User
from privacyidea.lib.token import get_tokens
principal = g.logged_in_user
if principal.get('role') != 'user':
return True
user = User(principal.get('username', ''), principal.get('realm', ''))
if user.is_empty() or action != 'init':
raise PolicyError('Use the account recovery process to replace an active authenticator.')
tokens = get_tokens(user=user, active=True)
if any(token.token.rollout_state not in {'verify', 'clientwait', 'pending'} for token in tokens):
raise PolicyError('An active authenticator already exists. Use the account recovery process to replace it.')
serial = request.all_data.get('serial')
if serial:
matches = [token for token in tokens if token.token.serial == serial]
if len(matches) != 1 or matches[0].token.rollout_state != 'verify':
raise PolicyError('Only your unfinished authenticator setup can be confirmed or regenerated.')
return True