118 lines
15 KiB
Python
118 lines
15 KiB
Python
"""Keep the existing audit and historical MAC evidence; reopen browser authentication."""
|
||
import csv
|
||
import json
|
||
from pathlib import Path
|
||
|
||
root = Path(__file__).resolve().parent.parent
|
||
docs = root / 'docs/investmoz/v2/auditoria-portal'
|
||
private = root / 'storage/app/private/investmoz-v2/portal-audit'
|
||
heading = '## C37 reaberto — ciclo de login da conta QA 18 — P0'
|
||
old_heading = heading
|
||
body = '''C37 continua aberto e bloqueia a demonstração visual A. O utilizador reporta ciclo login→MFA→redirecionamento na QA18097 com a conta18. A correção anterior do MAC inválido da conta13 e as provas HTTP são históricas específicas; não demonstram a continuidade da sessão neste navegador. Nenhum login foi repetido nesta investigação.
|
||
|
||
GET/login QA confirmou alvo desk_restore_20261009_174726_test:55441 antes de instrumentar. Configuração efetiva: cookie opportunity_resources_qa, domínio vazio (host-only), caminho/, Secure=false, HttpOnly=true, SameSite=lax; XSRF-TOKEN no mesmo domínio/caminho, HttpOnly=false. Guard padrão e Fortify=web, sessão file120min, cache de configuração não carregado. Os atributos observados não identificam uma causa. Portas partilham o âmbito do cookie no mesmo host; o router antigo18096 usa o mesmo nome/diretório por código, mas seu GET atual devolve erro PHP sem Set-Cookie. Não está demonstrado que sobrescreveu o cookie na tentativa reportada.
|
||
|
||
O router QA, depois da guarda real de destino, ativa tools/portal-qa-auth-trace.php. auth-session-trace-qa.jsonl guarda método/caminho sem query, horaUTC, estado antes/depois da rota, pseudónimo HMAC da sessão e do cookie aceite, utilizador pendente/autenticado, persistência efetiva no handler, atributos Set-Cookie e destino Location/X-Inertia-Location. Não guarda Cookie, X-XSRF-TOKEN, credenciais, códigos MFA, corpos ou valores de cookies. Identificador não secreto X-Investmoz-QA-Auth-Trace permite correlacionar resposta manual. Instrumentação temporária exclusivamente na QA18097; não altera login, MFA, chave ou dados ativos. auth-trace-readiness-qa.json demonstra continuidade convidada e captura antes da autorização, persistência legível e diretório efetivo correto; não é prova de login. Os bundles servidos usam /login e /two-factor-challenge relativos. A configuração de origem declara APP_NAME=Laravel e não SESSION_COOKIE (default laravel_session), distinta do nomeQA; isto é leitura documental de configuração, não consulta ao servidor ativo. XSRF-TOKEN genérico pode partilhar o âmbito entre portas; nenhum conflito concreto foi observado. tools/portal-qa-auth-diagnosis.py lê as provas sem fazer pedidos de login e destaca a primeira divergência da conta18; divergência não é automaticamente causa.
|
||
|
||
Primeiro diagnóstico manual: usar o navegador afetado e cookies atuais; abrir http://127.0.0.1:18097/investimentos, entrar com a conta18 e confirmar MFA uma vez. Parar no resultado e comunicar hora aproximada/URL final. Não limpar cookies nem trocar de janela antes desta captura. A anterior instrução de janela privada aplica-se ao ensaio posterior, não à localização desta perda. Comparar primeiro: pending18 após POST/login→mesma sessão GET/MFA→POST/MFA autentica/regenera e grava18→GET destino recebe a nova sessão e carrega18. O primeiro desvio identifica o ponto da perda; sem essa sequência observada, a causa fica indeterminada.
|
||
|
||
Aceitação ainda pendente: /investimentos visível e autorizado após MFA, reload mantém18, logout retira acesso, novo login+MFA recupera o dossier, sem mistura entre projetos/perfis. Depois retomar A/B completos. Correção somente após causa concreta; não fechar por HTTP/unitários. MFA preservado, sem key:generate; zero escritas ativas, candidate_revision_fixed=false e activation_ready=false.
|
||
|
||
Ativação: C37 precede revisão candidata, ensaio visual A/B e renovação de backup/restauro. O trace QA não integra instalação ativa, recursos ou importação cartográfica. Reversão da instrumentação: remover o carregamento e os três registos de middleware no router QA; guardar provas sanitizadas privadas, não eliminar histórico. Não repetir instalações existentes nem ativar flags.'''
|
||
|
||
confirmed = (private / 'c37-manual-2122-2124-qa.json').exists() and (private / 'c37-auth-redirect-qa.json').exists()
|
||
if confirmed:
|
||
manual = json.loads((private / 'c37-manual-2122-2124-qa.json').read_text(encoding='utf-8'))
|
||
http = json.loads((private / 'c37-auth-redirect-qa.json').read_text(encoding='utf-8'))
|
||
runtime = json.loads((private / 'investor-path-qa-runtime.json').read_text(encoding='utf-8-sig'))
|
||
assert manual['cause_confirmed'] and http['mfa_browser_redirect_protocol_passed'] and not http['visual_acceptance_passed']
|
||
heading = '## C37 — causa confirmada; seleção de conta QA corrigida — P0, visual pendente'
|
||
body = f'''O pedido de reabertura referia a conta esperada18. A captura manual21:22–21:24UTC demonstra que foi selecionada a conta13 restaurada. O MFA foi alcançado, mas a autenticação nunca se completou. Causa concreta: credenciais MFA da conta13 cifradas pela chave de origem, incompatíveis com a chave estávelQA; a conta18 sintética não foi desafiada nessa tentativa. Nenhuma chave, MFA ou conta13 foi alterada para contornar o problema.
|
||
|
||
| UTC09/10 | Pedido | Resultado observado |
|
||
|---|---|---|
|
||
|21:22:10|GET/investimentos|302/login; sessão convidada guardada, destino pretendido /investimentos.|
|
||
|21:22:11|GET/login|200; mesmo cookie/sessão, sem autenticação.|
|
||
|21:22:44|POST/login|302/two-factor-challenge; login.id=13, não18.|
|
||
|21:22:44|GET/two-factor-challenge|200; desafio13 recuperado da mesma sessão; ecrã confirmado pelo utilizador.|
|
||
|21:23:04|POST/two-factor-challenge|Falha de desencriptação; warning correlacionado conta13;302/login, invalidação intencional e nova sessão convidada.|
|
||
|21:23:04|GET/login|200; cookie aceite corresponde à sessão regenerada; sem utilizador autenticado.|
|
||
|
||
Primeiro desvio do ensaio: POST/login desafia13. Primeira falha técnica: POST/two-factor-challenge às21:23:04 (trace ec68d040a3291cb8, referência26e15b04-07b8-4211-a2a0-7f70509e03f6). Não se tratou de código MFA rejeitado pelo verificador nem de perda após autenticação. Os dois campos cifrados (secret/recovery) são incompatíveis; como nenhum corpo foi retido, não se afirma qual modalidade de entrada foi usada nesta tentativa. Não foram observados conflitos de cookies, mudança de chave/guard ou redirecionamento para outra origem.
|
||
|
||
CorreçãoQA, após guarda real desk_restore_20261009_174726_test:55441: login identifica/preenche o email não secreto da conta18; o ecrã MFA identifica a conta realmente desafiada. PortalQaAccountSelection, carregado exclusivamente pelo routerQA, recusa endereços não sintéticos antes do desafio e cancela desafios antigos de contas restauradas, com erro controlado. Os perfis sintéticosQA mantêm o login Fortify normal e respetivo MFA. Contas restauradas e dados ativos preservados. A conta13 continua cifrada como no original; não foi reencryptada nem usa chave ativa como fallback.
|
||
|
||
Lacuna de feedback encontrada no código: o Form recém-criado no login começa com erros locais vazios e não lia o errors.email devolvido pela página após redirecionamento do MFA. Login agora apresenta também esse erro da página. A mensagem de falha de desencriptação continua controlada e não autentica; sua apresentação visual ainda exige confirmação.
|
||
|
||
Evidência atual{http['captured_at_utc']} em c37-auth-redirect-qa.json, pelo servidor18097 e protocolo Inertia/HTML com redirects reais: seletor13 recusado antes do MFA; desafio18 identificado; código inválido mantém desafio sem autenticar; MFA válido regenera/grava18 e redireciona /investimentos; GET e reload mantêm18; logout revoga acesso; novo cookie jar+login+MFA recupera o mesmo dossier413ba1f6-bdf6-402a-a561-c4e2d4098d1a; outra conta403. Negativo sintético19 demonstra falha de desencriptação→login com mensagem, sem autenticação. Nenhuma criação extra de dossier. Estes são resultados de resposta/persistência, não um ensaio visual bem-sucedido.
|
||
|
||
Evidências: c37-manual-2122-2124-qa.json conserva os seis pedidos e warning sanitizados; auth-session-trace-qa.jsonl mantém histórico append-only e pseudónimos HMAC; c37-qa-account-auth.xml verifica políticaQA, preservação, MFA válido/inválido e falhas secret/recovery; c37-installation-auth-fingerprint.xml verifica guardas/importação/reversão. TypeScript e build passaram. mfa-persistence-qa.json reconfirma conta13 e .env byte a byte, MFA18 confirmado/oito códigos, chave não gerada/rodada e zero escritas ativas. Cookies, passwords, OTP e corpos não são exportados.
|
||
|
||
Estado: causa identificada, preparaçãoQA corrigida e protocolo/persistência demonstrados; C37 permanece aberto para /investimentos visível→reload→logout→novo login da conta18 no navegador. A captura manual demonstrou apenas o MFA da conta13 e retorno ao login; não certifica o percursoA/B nem o feedback atualizado. Próximo teste deve confirmar que o ecrã diz contaQA18 antes de usar o autenticador dessa conta. URL exato http://127.0.0.1:18097/investimentos. Credenciais/segredo permanecem somente no ficheiro privado local investor-mfa-qa-private.json, sem exportação. Workflow/contratos especializados continuam no backlog, sem jornadas educativas novas.
|
||
|
||
Ativação não autorizada e revisão candidata não fixada. Fingerprints agora incluem também login.tsx e two-factor-challenge.tsx: percurso{runtime['code_hash']}; cartografia{runtime['geometry_code_hash']}; recursos{runtime['resource_code_hash']}. Ready/provas de versões anteriores não demonstram esta revisão. Política/seletor/rastreioQA não integram instalação ativa. Após aceitação visualA/B e correções, renovar backup/restauro/compatibilidade e manter a ordem já documentada de recursos→percurso→flag→importação cartográfica→publicação separada, sem repetir o que estiver instalado. ReversãoQA: retirar registo/configuração de PortalQaAccountSelection e overrides Fortify do router; retirar os registos de rastreio; preservar provas. A correção do feedback é código compatível sem migration, alteração de chave ou modificação de dados.'''
|
||
|
||
for name in ['relatorio-executivo.md', 'backlog.md', 'casos-reproducao.md', 'roteiro-manual-qa-i0-i3.md', 'plano-ativacao.md', 'execucao-percurso-i0-i3.md']:
|
||
path = docs / name
|
||
if not path.exists():
|
||
continue
|
||
text = path.read_text(encoding='utf-8-sig')
|
||
start = text.find(heading + '\n')
|
||
if start < 0 and confirmed:
|
||
start = text.find(old_heading + '\n')
|
||
if start >= 0:
|
||
end = text.find('\n## ', start + 3)
|
||
text = text[:start] + heading + '\n\n' + body + '\n\n' + (text[end+1:] if end >= 0 else '')
|
||
else:
|
||
first = text.find('\n')
|
||
text = text[:first+1] + '\n' + heading + '\n\n' + body + '\n\n' + text[first+1:].lstrip('\n')
|
||
path.write_text(text, encoding='utf-8')
|
||
|
||
matrix = docs / 'matriz-requisitos.csv'
|
||
with matrix.open(encoding='utf-8-sig', newline='') as f:
|
||
reader = csv.DictReader(f)
|
||
fields = reader.fieldnames
|
||
rows = list(reader)
|
||
ids = ['AUD-UI-01', 'AUD-DASH-01', 'AUD-SIM-01', 'AUD-DESK-02', 'AUD-GUIDE-01', 'AUD-CRM-01']
|
||
assert len(rows) == 3354
|
||
assert sum(int(row['Quantidade de linhas de origem']) for row in rows) == 5830
|
||
suffix = ' | C37 REABERTO/P0: ciclo login da conta18 no navegador18097. auth-cookie-config-qa.json confirma atributos/guard QA; auth-session-trace-qa.jsonl preparado para primeira perda antes/depois MFA. Causa e continuidade visual não demonstradas; provas HTTP anteriores não fecham este caso.'
|
||
if confirmed:
|
||
suffix = ' | C37 DIAGNÓSTICO CONFIRMADO/P0: manual21:22–21:24UTC desafia conta13, não18; POST/MFA21:23:04 falha na desencriptação e invalida intencionalmente; GET/login aceita nova sessão. Sem autenticação concluída/perda posterior/conflito de cookies. c37-manual-2122-2124-qa.json e c37-auth-redirect-qa.json: QA seleciona18, bloqueia restauradas antesMFA, apresenta erro da página; MFA18→/investimentos→reload→logout→nova sessão/dossier HTTP passou; visual18 pendente.'
|
||
for row in rows:
|
||
if row['ID'] not in ids:
|
||
continue
|
||
row['Prioridade'] = 'P0'
|
||
if 'C37 REABERTO/P0:' not in row['Evidência funcional']:
|
||
if not confirmed:
|
||
row['Evidência funcional'] += suffix
|
||
row['Lacuna'] += ' | C37 reaberto: localizar perda; corrigir causa e demonstrar /investimentos→reload→logout→novo login/MFA no navegador.'
|
||
if confirmed:
|
||
row['Evidência funcional'] = row['Evidência funcional'].replace('C37 REABERTO/P0:', 'Histórico C37 REABERTO/P0, antes da captura21:22–21:24UTC:')
|
||
row['Lacuna'] = row['Lacuna'].replace('C37 reaberto: localizar perda;', 'Histórico da reaberturaC37, antes da captura: localizar perda;')
|
||
if 'C37 DIAGNÓSTICO CONFIRMADO/P0:' not in row['Evidência funcional']:
|
||
row['Evidência funcional'] += suffix
|
||
row['Lacuna'] += ' | C37 após captura: causa conhecida e seleção/feedbackQA corrigidos; falta entrada visual bem-sucedida da conta18, reload/logout/nova sessão e retomaA/B.'
|
||
row['Interface'] = 'Manual: MFA13 alcançado e retorno/login observado21:22–21:24UTC; seleção/feedbackQA preparados. Sucesso visual da conta18/reload/logout/nova sessão pendente.'
|
||
row['API'] += '' if 'c37-auth-redirect-qa.json' in row['API'] else ' | c37-auth-redirect-qa.json: redirects Inertia/HTML no servidor18097, MFA18 e negativas diferenciadas.'
|
||
row['Recuperação'] += '' if 'C37 nova sessão18' in row['Recuperação'] else ' | C37 nova sessão18 por HTTP recupera o dossier próprio apóslogout; outra conta403. Interface pendente.'
|
||
else:
|
||
row['Interface'] = 'Pendente/com falha reportada: C37 ciclo de login conta18 QA18097; trace manual preparado, causa por localizar.'
|
||
with matrix.open('w', encoding='utf-8-sig', newline='') as f:
|
||
writer = csv.DictWriter(f, fieldnames=fields)
|
||
writer.writeheader()
|
||
writer.writerows(rows)
|
||
|
||
file = private / 'investor-path-evidence.json'
|
||
proof = json.loads(file.read_text(encoding='utf-8-sig'))
|
||
proof.update({'mfa_blocking_case': 'C37', 'c37_reopened_browser_login_loop': True,
|
||
'c37_browser_session_loss_identified': False, 'c37_browser_acceptance_passed': False,
|
||
'integrated_ui_ab_passed': False, 'candidate_revision_fixed': False, 'activation_ready': False})
|
||
if confirmed:
|
||
proof.update({'c37_cause_identified': True, 'c37_manual_observed_actor': 13,
|
||
'c37_session_loss_after_auth_ruled_out_in_manual_attempt': True,
|
||
'c37_qa_account_selection_corrected': True, 'c37_auth_redirect_qa_evidence': 'c37-auth-redirect-qa.json',
|
||
'c37_manual_interval_evidence': 'c37-manual-2122-2124-qa.json', 'c37_browser_acceptance_passed': False})
|
||
file.write_text(json.dumps(proof, ensure_ascii=False, indent=2), encoding='utf-8')
|
||
print(json.dumps({'case': 'C37 P0 visual pending', 'cause_confirmed': confirmed, 'requirements': len(rows), 'origins': 5830, 'active_writes': 0}))
|