import { ArchbaseResourceService } from './ArchbaseResourceService'; import { SimpleActionDto, SimpleResourceDto } from './SecurityDomain'; /** * O que uma tela pode declarar sobre uma capacidade, além do nome e da descrição. * *
Tudo opcional, e omitir tudo é o comportamento de sempre. Cada campo tem uma regra própria de * semeadura no servidor — ver {@link ISecurityManager#registerAction}. */ export interface RegisterActionOptions { /** Rótulo curto — "Aprovar". Ausente significa "use a descrição". */ label?: string; /** Agrupamento dentro do recurso — "Operação". */ category?: string; /** * As capacidades sem as quais este gesto não funciona — tipicamente os endpoints que ele chama. * *
`'view'` resolve contra o recurso da própria tela; `'tms.ordemservico:aprovar_custo'` aponta * para outro. É o que permite à tela de permissões oferecer as dependências junto no momento da * concessão, e avisar ao revogar. * *
**Ausente e lista vazia são coisas diferentes.** Ausente significa "não declarei" e não toca * em nada — é o que todo cliente anterior envia. Lista vazia significa "declaro que não há * nenhuma", e remove as que existirem. */ requires?: string[]; } export interface ISecurityManager { registerAction(actionName: string, actionDescription: string, opcoes?: RegisterActionOptions): void; } export declare class ArchbaseSecurityManager implements ISecurityManager { protected resourceService: ArchbaseResourceService; protected resource: SimpleResourceDto; protected actions: SimpleActionDto[]; protected permissions: string[]; protected alreadyApplied: boolean; /** * A requisição ainda não terminou — **separado de `alreadyApplied` de propósito**. * *
`alreadyApplied` é a trava de reentrância: vira `true` no INÍCIO do `apply()`, antes do * `await`, para que duas telas montando juntas não disparem duas requisições. Isso a torna * inútil como resposta a "já dá para ler `permissions`?" — e era exatamente assim que o * `isLoading()` a usava.
*/ protected loading: boolean; /** * A requisição em curso — **uma só, compartilhada por todos os que pedirem**. * *Existe porque o mesmo manager é reaproveitado entre montagens (o hook o guarda no store, com * chave por usuário e recurso). Sem ela, a segunda montagem via `alreadyApplied === true`, saía * sem fazer nada e sem avisar ninguém.
*/ protected pendente: PromiseRótulo e categoria são **semeados**: o servidor os grava apenas quando a capacidade ainda não * os tem, para que o texto não oscile entre duas telas que registram a mesma ação. `requires` é * **reconciliado** por ação: o que a tela declara substitui o que ela havia declarado antes, sem * tocar nas ações que não vieram no payload nem nas arestas que o `@HasPermission` declarou. */ registerAction(actionName: string, actionDescription: string, opcoes?: RegisterActionOptions): void; /** * Envia o recurso e as ações registradas, e guarda o que o usuário pode neste recurso. * *
Aguarda de verdade. Antes o método era `async` mas devolvia antes de a requisição * terminar — o `.then` era encadeado sem `return`. Quem escrevia `await manager.apply()` seguia * em frente com `permissions` ainda vazio, e o `catch` de quem chamava nunca era alcançado * porque a falha morria no `.catch` interno, virando o campo `error` que ninguém lia. * *
A exceção volta a ser lançada, além de gravada em `error`: quem prefere o campo continua * podendo lê-lo, e quem espera um `catch` finalmente o recebe. * *
Antes, TODO usuário chamava `POST /resource/register` ao abrir cada tela. Esse endpoint * escreve o catálogo de segurança e o backend o marca como administrativo — então, em * qualquer projeto com `archbase.security.admin-endpoints.policy=admin-only`, todo não-admin * tomava 403 em toda tela: `permissions` ficava vazio, `hasPermission` respondia `false` * para tudo, e o menu inteiro aparecia desabilitado. Nenhuma permissão concedida no banco * mudava isso, porque a resposta que as carregaria nunca chegava.
* *O efeito colateral era pior que a tela cinza: dois projetos voltaram a política para * `permit` para destravar o produto, e `permit` deixa qualquer autenticado alcançar * criar usuário e conceder permissão. Um método que só precisava LER estava * cobrando poder de ESCRITA, e o preço foi pago afrouxando a autorização inteira.
* *A separação já existia no backend e não estava sendo usada: `GET /permissions/{recurso}` é * `selfService` e devolve o mesmo DTO. Agora o administrador registra — que é quando o catálogo * de fato precisa ser escrito — e os demais leem o que podem.
* *O que isto exige em troca: o catálogo de um recurso passa a nascer quando um * administrador abre aquela tela. Tela que nenhum admin abriu não tem ação cadastrada, e sem ação * cadastrada não há o que conceder. É o comportamento correto — o catálogo descreve o sistema, * não quem passou por ele —, mas quem esconde telas do admin por papel precisa saber que está * escondendo também o registro delas.
*/ apply(callback?: Function): PromiseIsto devolvia `!alreadyApplied`, e o `alreadyApplied` vira `true` no INÍCIO do `apply()`, * antes do `await`. Ou seja: `isLoading()` respondia "pronto" no instante em que a requisição * partia.
* *O `ArchbaseAdvancedSidebar` decide com !isLoading() && !isError(), monta o menu
* com disabled: !hasPermission(label) e então trava — marca o item como
* processado e não recalcula quando a resposta chega. Resultado: ele lia `permissions` ainda
* vazio e desabilitava tudo, de forma permanente.
Só aparecia para quem não é administrador, porque `hasPermission` termina em
* || this.isAdmin — o admin passa mesmo com a lista vazia. E era uma corrida: se a
* resposta chegasse antes do efeito do sidebar rodar, funcionava. Defeito que depende de tempo
* some quando se procura, e reaparece na máquina de outra pessoa.
Agora existe um sinalizador próprio, ligado no construtor e desligado no `finally` do * `apply()`. `alreadyApplied` continua sendo a trava de reentrância, que é o que ele sempre foi — * as duas perguntas apenas deixaram de compartilhar a mesma resposta.
*/ isLoading(): boolean; /** * Verifica múltiplas permissões */ hasAnyPermission(permissions: string[]): boolean; /** * Verifica se tem todas as permissões */ hasAllPermissions(permissions: string[]): boolean; /** * Retorna informações detalhadas sobre uma permissão */ getPermissionInfo(actionName: string): { hasPermission: boolean; isAdmin: boolean; reason: string; }; /** * Registra múltiplas ações de uma vez */ registerActions(actions: Array<{ actionName: string; actionDescription: string; } & RegisterActionOptions>): void; /** * Retorna todas as ações registradas */ getRegisteredActions(): SimpleActionDto[]; /** * O recurso que este manager representa — nome técnico e descrição. * *Existe para o inspetor de ações: sem ele, quem tem o manager em mãos sabe QUAIS ações a tela * declarou, mas não A QUAL recurso elas pertencem, e é o par `recurso:acao` que se concede. */ getResource(): SimpleResourceDto; }