From 85035b44d6b2f243ebed34cf9a793dafb7d64a22 Mon Sep 17 00:00:00 2001 From: ATM Platform Date: Wed, 7 Oct 2026 12:25:37 +0000 Subject: [PATCH] molde do realm: papel atm_responsavel e mapper acompanhante_id (app dos pais); 2FA TOTP obrigatorio para atm_admin (browser-2fa-dono) --- moldes/realm-do-tenant.LEIA-ME.md | 22 +++ moldes/realm-do-tenant.json | 223 ++++++++++++++++++++++++++++++ 2 files changed, 245 insertions(+) diff --git a/moldes/realm-do-tenant.LEIA-ME.md b/moldes/realm-do-tenant.LEIA-ME.md index 07d2d03..af6d84b 100644 --- a/moldes/realm-do-tenant.LEIA-ME.md +++ b/moldes/realm-do-tenant.LEIA-ME.md @@ -5,6 +5,14 @@ DERIVADO de `tenants/plataforma/realm/athleticmap-realm.json`, que e o realm que existe hoje. Os nove papeis e o cliente `spa` sao os de la — NAO inventei nenhum, e um papel a mais aqui seria um papel que nenhum codigo verifica. +06/10/2026 — DECIMO papel: `atm_responsavel` (responsavel legal: app dos pais +e o portal). Verificado por codigo: o cadastro o da ao marcar o acompanhante +como responsavel (AcessoAoAppServico), o SPA leva quem so o tem ao /portal, e +a TravaDaEquipe o deixa FORA da leitura da equipe. Junto, o mapper +`acompanhante_id` (atributo -> claim) nos clients `spa` e `app`, como o +`funcionario_id`. Realm que ja existe nao reimporta: rodar +`scripts/papel_responsavel_nos_realms.py --check` / `--aplicar`. + ── OS MARCADORES ─────────────────────────────────────────────────────────── {{REALM}} `athleticmap-` — decidido em 2026-09-19 @@ -76,3 +84,17 @@ pelos runtimes. Nao da para resolver so no `KC_HOSTNAME` porque ele e um so, e a plataforma depende do valor antigo — os servicos dela estao configurados com `auth-plataforma`. Dai o `frontendUrl` por realm, que e exatamente para isto. + +── DUPLO FATOR DO DONO (07/10/2026) ──────────────────────────────────────── + +`browserFlow: browser-2fa-dono` e os quatro `authenticationFlows`: copia do +`browser` de fabrica do 26.7.5 com OTP OBRIGATORIO para `atm_admin` (quem nao +tem configura no primeiro login) e passkey dispensando o codigo. Politica OTP +TOTP/SHA1/6 digitos/30 s (Google e Microsoft Authenticator). Os outros fluxos +(`direct grant`, `clients`...) o Keycloak cria sozinho na importacao — provado +num 26.7.5 descartavel. Realms que ja existem: `scripts/dois_fatores_nos_realms.py` +(le ESTE arquivo). Recuperacao e saida de emergencia: docs/dois-fatores-do-dono.md. + +ATENCAO ao testar este molde num Keycloak local: o `frontendUrl` aponta para +`https://auth.athleticmap.com` — o formulario de login POSTA EM PRODUCAO. Zere +`attributes.frontendUrl` no realm de teste. diff --git a/moldes/realm-do-tenant.json b/moldes/realm-do-tenant.json index e5988fc..573242c 100644 --- a/moldes/realm-do-tenant.json +++ b/moldes/realm-do-tenant.json @@ -16,6 +16,197 @@ "webAuthnPolicyPasswordlessPasskeysEnabled": true, "webAuthnPolicyPasswordlessUserVerificationRequirement": "required", "webAuthnPolicyPasswordlessRpEntityName": "Athletic Map", + "otpPolicyType": "totp", + "otpPolicyAlgorithm": "HmacSHA1", + "otpPolicyDigits": 6, + "otpPolicyPeriod": 30, + "otpPolicyLookAheadWindow": 1, + "otpPolicyCodeReusable": false, + "browserFlow": "browser-2fa-dono", + "authenticationFlows": [ + { + "alias": "browser-2fa-dono", + "description": "Copia do browser padrao (26.7.5) com OTP obrigatorio para quem tem atm_admin", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": false, + "authenticationExecutions": [ + { + "requirement": "ALTERNATIVE", + "priority": 10, + "userSetupAllowed": false, + "authenticatorFlow": false, + "authenticator": "auth-cookie" + }, + { + "requirement": "DISABLED", + "priority": 20, + "userSetupAllowed": false, + "authenticatorFlow": false, + "authenticator": "auth-spnego" + }, + { + "requirement": "ALTERNATIVE", + "priority": 25, + "userSetupAllowed": false, + "authenticatorFlow": false, + "authenticator": "identity-provider-redirector" + }, + { + "requirement": "ALTERNATIVE", + "priority": 30, + "userSetupAllowed": false, + "authenticatorFlow": true, + "flowAlias": "browser-2fa-dono-forms" + } + ] + }, + { + "alias": "browser-2fa-dono-forms", + "description": "Usuario e senha (ou passkey), e o segundo fator", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": false, + "authenticationExecutions": [ + { + "requirement": "REQUIRED", + "priority": 10, + "userSetupAllowed": false, + "authenticatorFlow": false, + "authenticator": "auth-username-password-form" + }, + { + "requirement": "CONDITIONAL", + "priority": 20, + "userSetupAllowed": false, + "authenticatorFlow": true, + "flowAlias": "browser-2fa-dono-otp-obrigatorio" + }, + { + "requirement": "CONDITIONAL", + "priority": 30, + "userSetupAllowed": false, + "authenticatorFlow": true, + "flowAlias": "browser-2fa-dono-2fa-opcional" + } + ] + }, + { + "alias": "browser-2fa-dono-otp-obrigatorio", + "description": "atm_admin: OTP obrigatorio (configura no 1o login); passkey dispensa", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": false, + "authenticationExecutions": [ + { + "requirement": "REQUIRED", + "priority": 10, + "userSetupAllowed": false, + "authenticatorFlow": false, + "authenticator": "conditional-user-role", + "authenticatorConfig": "2fa-dono-papel-atm-admin" + }, + { + "requirement": "REQUIRED", + "priority": 20, + "userSetupAllowed": false, + "authenticatorFlow": false, + "authenticator": "conditional-credential", + "authenticatorConfig": "2fa-dono-sem-passkey" + }, + { + "requirement": "REQUIRED", + "priority": 30, + "userSetupAllowed": false, + "authenticatorFlow": false, + "authenticator": "auth-otp-form" + } + ] + }, + { + "alias": "browser-2fa-dono-2fa-opcional", + "description": "Os demais: como o padrao (2FA so de quem configurou)", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": false, + "authenticationExecutions": [ + { + "requirement": "REQUIRED", + "priority": 10, + "userSetupAllowed": false, + "authenticatorFlow": false, + "authenticator": "conditional-user-configured" + }, + { + "requirement": "REQUIRED", + "priority": 20, + "userSetupAllowed": false, + "authenticatorFlow": false, + "authenticator": "conditional-credential", + "authenticatorConfig": "2fa-opcional-sem-passkey" + }, + { + "requirement": "REQUIRED", + "priority": 30, + "userSetupAllowed": false, + "authenticatorFlow": false, + "authenticator": "conditional-user-role", + "authenticatorConfig": "2fa-opcional-nao-dono" + }, + { + "requirement": "ALTERNATIVE", + "priority": 40, + "userSetupAllowed": false, + "authenticatorFlow": false, + "authenticator": "auth-otp-form" + }, + { + "requirement": "DISABLED", + "priority": 50, + "userSetupAllowed": false, + "authenticatorFlow": false, + "authenticator": "webauthn-authenticator" + }, + { + "requirement": "DISABLED", + "priority": 60, + "userSetupAllowed": false, + "authenticatorFlow": false, + "authenticator": "auth-recovery-authn-code-form" + } + ] + } + ], + "authenticatorConfig": [ + { + "alias": "2fa-dono-papel-atm-admin", + "config": { + "condUserRole": "atm_admin", + "negate": "false" + } + }, + { + "alias": "2fa-dono-sem-passkey", + "config": { + "credentials": "webauthn-passwordless", + "included": "false" + } + }, + { + "alias": "2fa-opcional-sem-passkey", + "config": { + "credentials": "webauthn-passwordless", + "included": "false" + } + }, + { + "alias": "2fa-opcional-nao-dono", + "config": { + "condUserRole": "atm_admin", + "negate": "true" + } + } + ], "roles": { "realm": [ { @@ -44,6 +235,10 @@ }, { "name": "atm_financeiro-leitura" + }, + { + "name": "atm_responsavel", + "description": "Responsavel legal (pai, mae ou outro): app dos pais e o portal web. Dado pelo cadastro ao marcar o acompanhante como responsavel (06/10/2026)." } ] }, @@ -80,6 +275,20 @@ "access.token.claim": "true", "userinfo.token.claim": "true" } + }, + { + "name": "acompanhante_id", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "user.attribute": "acompanhante_id", + "claim.name": "acompanhante_id", + "jsonType.label": "String", + "id.token.claim": "true", + "access.token.claim": "true", + "userinfo.token.claim": "true" + } } ] }, @@ -123,6 +332,20 @@ "access.token.claim": "true", "userinfo.token.claim": "true" } + }, + { + "name": "acompanhante_id", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "user.attribute": "acompanhante_id", + "claim.name": "acompanhante_id", + "jsonType.label": "String", + "id.token.claim": "true", + "access.token.claim": "true", + "userinfo.token.claim": "true" + } } ] }