Files
Investidor-V2/tools/portal-audit-update-mfa.py
nala 6ddafc95ba
tests / ci (push) Canceled after 0s
oportunidades 4
2026-10-10 07:03:59 +02:00

111 lines
11 KiB
Python

"""Record C37 in the existing audit, without closing visual/active criteria."""
import json
import re
import xml.etree.ElementTree as ET
from collections import deque
from pathlib import Path
root = Path(__file__).resolve().parent.parent
private = root / 'storage/app/private/investmoz-v2/portal-audit'
docs = root / 'docs/investmoz/v2/auditoria-portal'
http = json.loads((private / 'mfa-http-qa.json').read_text(encoding='utf-8-sig'))
diagnosis = json.loads((private / 'mfa-key-diagnosis-qa.json').read_text(encoding='utf-8-sig'))
invalidated = json.loads((private / 'mfa-session-invalidation-qa.json').read_text(encoding='utf-8-sig'))
persistence = json.loads((private / 'mfa-persistence-qa.json').read_text(encoding='utf-8-sig'))
runtime = json.loads((private / 'investor-path-qa-runtime.json').read_text(encoding='utf-8-sig'))
cases = list(ET.parse(private / 'mfa-auth-qa.xml').iter('testcase'))
assert len(cases) == 17 and all(not list(c) for c in cases)
assert http['target'] == diagnosis['target'] == invalidated['target'] == persistence['target'] == runtime['target']
assert http['login_mfa_logout_new_session_passed']
current_mfa_revision = http['code_hash'] == runtime['code_hash']
assert http['stable_key_matches_cli_web'] and not http['interface_observed'] and http['active_writes'] == 0
assert invalidated['qa_sessions_invalidated'] == 2 and invalidated['account_row_unchanged']
assert persistence['original_account_13_unchanged'] and persistence['source_env_unchanged'] and not persistence['key_generated_or_rotated']
assert diagnosis['encrypted_field_checks']['two_factor_secret'] == {'qa': False, 'source_env': True}
# Retain every frame, omitting all arguments and request data.
lines = list(deque((root / 'storage/logs/laravel.log').open(encoding='utf-8', errors='replace'), maxlen=5000))
stacks = []
for i, line in enumerate(lines):
if 'testing.ERROR: The MAC is invalid.' not in line:
continue
frames = []
for frame in lines[i+1:]:
if re.match(r'^\[\d{4}-', frame):
break
if frame.startswith('#'):
location, sep, call = frame.partition('): ')
if sep:
frames.append(location + '): ' + call.split('(')[0] + '(arguments omitted)')
elif '{main}' in frame:
frames.append(frame.strip())
if frames and 'TwoFactorLoginRequest.php' in frames[0]:
stacks.append({'observed': line.split(' {')[0].strip(), 'frames': frames, 'arguments_exported': False})
assert len(stacks) >= 2 and all(len(s['frames']) == 68 for s in stacks[-2:])
(private / 'mfa-original-stack-redacted.json').write_text(json.dumps(stacks[-2:], ensure_ascii=False, indent=2), encoding='utf-8')
def insert(filename, heading, body):
path = docs / filename
text = path.read_text(encoding='utf-8-sig')
block = heading + '\n\n' + body.strip() + '\n\n'
start = text.find(heading + '\n')
if start >= 0:
end = text.find('\n## ', start + len(heading))
text = text[:start] + block + (text[end+1:] if end >= 0 else '')
else:
first = text.find('\n')
text = text[:first+1] + '\n' + block + text[first+1:].lstrip('\n')
path.write_text(text, encoding='utf-8')
body = f'''**C37/P0 — histórico da correção do tratamento do MAC inválido e ensaio HTTP em {http['captured_at']}, revisão {http['code_hash']}.** Correspondência com a revisão atual: {current_mfa_revision}. Esta evidência não fecha o ciclo manual reaberto nem a interface; consultar o diagnóstico C37 mais recente no início deste documento. URL: http://127.0.0.1:18097/investimentos. Alvo real confirmado antes de qualquer escrita: `{runtime['target']['database']}:55441`.
Stack completa dos erros20:39:16/20:41:52 (68frames por ocorrência) retida sem argumentos em mfa-original-stack-redacted.json. Falha em TwoFactorLoginRequest::hasValidCode() ao desencriptar users.two_factor_secret da conta13; PreserveFormFeedback apenas chama o próximo middleware. A conta restaurada não é sintética: secret/recovery abrem com a chave do .env de origem, mas não com a chaveQA. Cache de configuração não carregado; source cache também ausente. A ferramenta de preparação do percurso antes não fixava APP_KEY e podia herdar origem. portal-qa-environment.php centraliza a mesma chaveQA já existente entre preparação/web, força alvo/restauro, configura caminho QA de cache separado e recusa cache não reconciliado. Não gera chave, não usa chaves ativas como fallback nem modifica .env/cache ativo.
Duas sessões pendentes da conta13 removidas exclusivamente do diretório de sessões QA, após guarda do destino e paths. Original conta13 preservado byte a byte pelo hash da linha; nenhuma chave/MFA ativa ou da conta13 alterada. Novo investidor sintético QA: actor{http['actor_id']}, inscrição normal pelos endpoints Fortify (confirmar password→enable→secret-key→confirm), oito recovery codes, sem injeção direta do segredo nem desativação de MFA. Dados de acesso/segredo somente em investor-mfa-qa-private.json local privado; não exportados aqui.
POST /two-factor-challenge com DecryptException agora invalida desafio/sessão e retorna422 com mensagem controlada em JSON ou redireciona ao login com erro visível; nenhuma autenticação. Negativas reais de secret/recovery verificam401 após erro e remoção do desafio. A resposta HTML/Inertia contém a mensagem, sem ser prova de renderização visual. Logs controlados incluem apenas referência e ID pendente; as provas não exportam Cookie, X-XSRF-TOKEN, OTP ou corpos.
No mesmo servidor18097: login/MFA/logout/nova sessão passaram. A retomado por HTTP em `/investimentos/{http['scenario_A']['id']}`: criação→logout→nova sessãoMFA→recuperação→aplicabilidade semterra/semfinanciamento→draft próprio→resultado→Balcão draft→tarefa concluída pela gestão→nova sessãoMFA recupera tudo. Gestão não recebe resultado financeiro privado. Função efetiva do dossier recalcula20unidades/0.00 a partir do resultado recuperado. QA atual{runtime['qa_path_journeys']}instâncias/{runtime['qa_path_steps']}etapas; originals anteriores preservados. Esta conta não vê os A/B da outra contaQA.
Evidências: mfa-key-diagnosis-qa.json, mfa-session-invalidation-qa.json, mfa-persistence-qa.json, mfa-http-qa.json e mfa-auth-qa.xml. Os17 ensaios autenticam ou recusam os percursos concretos; não substituem interface. Fingerprints passam a incluir bootstrap/Fortify, invalidando ready/provas anteriores para ativação. `integrated_ui_ab_passed=false`, `candidate_revision_fixed=false`, `activation_ready=false`; nenhuma escrita ativa. Mesma matriz3354requisitos/5830origens, sem fecho amplo novo.
'''
for filename in ['relatorio-executivo.md', 'execucao-percurso-i1-i3.md', 'cobertura-percursos-i0-i3.md']:
insert(filename, '## C37 — MFA QA recuperado; cenário A visual por retomar', body)
insert('backlog.md', '## Próximo passo após C37 — retomar A com MFA', '''
1. Retomar A primeiro na interface18097 com o investidor sintético MFA; iniciar janela privada nova. Preservar o500 reportado como falha bloqueante histórica; conta13 restaurada não deve servir de conta sintéticaQA. Nova criação manual continua exigida, não apenas abrir o dossier HTTP.
2. Registar login/desafio/mensagem/recuperação, depois ferramenta→resultado→Balcão→acompanhamento e negativas; omitir Cookie/X-XSRF-TOKEN/corpo do código das capturas. Corrigir outras falhas antes de fixar candidata.
3. B, países/estatísticas e seletores/mapas/documentos continuam no mesmo backlog. API/persistência das operações não aguardam browser; ClamD mantém âmbito específico.
4. Após resultados manuais: renovar backup/restauro/compatibilidade sobre a revisão final. Bootstrap/Fortify agora entram nos fingerprints; não reutilizar ready/provas de revisão anterior. Nenhuma ativação ativa autorizada.
''')
insert('plano-ativacao.md', '## C37 — revisão ainda sem candidatura ou ativação', '''
Correção MFA é código, não migration/importação/publicação. Aplicação e preparação QA usam chave estável existente; não executar key:generate nem rekey dos dados. Conta13 ativa/copiada preservada; contas sintéticas e invalidação de sessões exclusivamenteQA. Fingerprints incluem bootstrap/app.php e configuração/provider Fortify; provas antigas devem ser renovadas depois da retoma visual A/B e correções. Ordem de recursos030000, percurso190000, flag e cartografia/divulgação continua no plano abaixo, sem repetir instalações. Ação ativa não autorizada.
''')
insert('roteiro-manual-qa-i0-i3.md', '## Retoma obrigatória após C37 — sessão e investidor MFA', '''
Endereço exato: http://127.0.0.1:18097/investimentos; login /login. Fechar a sessão anterior e abrir janela privada nova; as duas sessõesQA da conta13 foram invalidadas. Confirmar X-Investmoz-QA-Target=desk_restore_20261009_174726_test:55441 antes de escrever. Não usar a conta13 restaurada como fixtureQA.
Usar o investidor sintético MFA preparado (actor18). As credenciais e segredo para configurar o autenticador ficam somente no ficheiro local privado storage/app/private/investmoz-v2/portal-audit/investor-mfa-qa-private.json; não colar conteúdo/código/cookies nesta conversa. MFA foi ativado/confirmado normalmente pelo Fortify e é obrigatório no login. Gestão e outra contaQA conservadas para os restantes passos/permissões.
1. Entrar com a contaQA, confirmar desafioMFA antes do acesso ao investimento, usar o autenticador e abrir /investimentos. Esperado: entrada depois do código; nenhum500.
2. Criar um A visual novo, guardar; logout, fechar janela e nova sessão com password+MFA. Esperado: mesmoID/título recuperado.
3. Continuar a sequência A/B deste roteiro: aplicabilidade→ferramenta→resultado→Balcão→acompanhamento; registar cada prova. O dossier A HTTP atual é apenas inspeção complementar, não prova de criação na interface.
4. Para falha de desencriptação, esperado422JSON/mensagem controlada ou retorno ao login; nunca autenticação. Esse negativo foi ensaiado com fixture sintética separada, sem alterar conta13. Não adulterar manualmente a conta do investidor para o reproduzir.
Capturas/requisições exportadas devem omitir Cookie, X-XSRF-TOKEN, passwords e corpo com code/recovery_code. As provas exportadas pela ferramenta registam somente método/URL/status/alvo, não cabeçalhos/corpos. Retoma visual permanece pendente até resultados observados pelo utilizador.
''')
insert('casos-reproducao.md', '## C37 — MAC inválido no desafio MFA bloqueia cenário A — P0', body)
proof_file = private / 'investor-path-evidence.json'
proof = json.loads(proof_file.read_text(encoding='utf-8-sig'))
proof.update({'mfa_http_qa_evidence': 'mfa-http-qa.json', 'mfa_persistence_qa': 'mfa-persistence-qa.json',
'mfa_blocking_case': 'C37', 'manual_scenario_A_failed_before_fix': True, 'manual_scenario_A_retested_after_fix': False,
'integrated_ui_ab_passed': False, 'candidate_revision_fixed': False, 'activation_ready': False})
proof_file.write_text(json.dumps(proof, ensure_ascii=False, indent=2), encoding='utf-8')
if proof.get('c37_reopened_browser_login_loop'):
# Historical HTTP evidence must never undo the user's reopened browser blocker.
import runpy
runpy.run_path(str(root / 'tools/portal-audit-reopen-c37.py'), run_name='__main__')
else:
print(json.dumps({'case': 'C37', 'qa_login_mfa_new_session': 'passed', 'sessions_invalidated': 2, 'scenario_A_http_resumed': True,
'visual_retest': 'pending', 'requirements_closed': 0, 'active_writes': 0}))