fix: preserve directory subjects in factor recovery
All checks were successful
Authentication acceptance / acceptance (push) Successful in 57s
Authentication acceptance / provider-contract (push) Successful in 14s

Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a12d13-27f2-7163-b8c0-f682af02772c
This commit is contained in:
tegwick 2026-10-12 01:23:29 +02:00
parent f8ea591ba4
commit 1344fc80e5
4 changed files with 145 additions and 22 deletions

View file

@ -2,7 +2,18 @@
import hashlib,json,re
ACTION='keycape.factor.recovery'
class RecoveryError(Exception):pass
class RecoveryError(Exception):
def __init__(self, code, field=None):
super().__init__(code)
self.field=field
def validate_actor(value):
# The issuer's subject is an opaque identity (LLDAP uses a DN), not a login.
# Preserve its exact bytes for ticket/receipt binding; never normalize it.
if (not isinstance(value,str) or not value or len(value)>150
or not value.isascii() or not value.isprintable() or not value.strip()):
raise RecoveryError('invalid_actor',field='actor')
return value
def fingerprint(snapshot):
return hashlib.sha256(json.dumps(snapshot,sort_keys=True,separators=(',',':')).encode()).hexdigest()
@ -13,9 +24,10 @@ def recover(store, request):
return _recover(store,request)
def _recover(store, request):
for key in ('user','serial','realm','actor','reference'):
validate_actor(request.get('actor'))
for key in ('user','serial','realm','reference'):
value=request.get(key)
if not isinstance(value,str) or not value or len(value)>200 or not re.fullmatch(r'[A-Za-z0-9_.@:/-]+',value):raise RecoveryError('invalid_request')
if not isinstance(value,str) or not value or len(value)>200 or not re.fullmatch(r'[A-Za-z0-9_.@:/-]+',value):raise RecoveryError('invalid_request',field=key)
if request['realm']!=store.realm:raise RecoveryError('wrong_realm')
snapshot=store.snapshot(request['user'],request['serial'])
if snapshot is None:raise RecoveryError('factor_not_owned_by_target')