Um controller Apex compila, passa nos testes, vai para produção. Meses depois, um usuário de perfil básico abre um relatório e enxerga o campo de salário de um contato que não era para ele ver. O código estava certo na lógica. O problema é que ninguém disse a ele para respeitar a permissão do usuário, e Apex, por padrão, não respeita.
Apex roda em system mode: por padrão ignora o CRUD e o FLS do usuário, e só aplica sharing se a
classe for declarada with sharing. Todo código que lê ou grava dados precisa
respeitar três níveis de acesso de propósito: o objeto (CRUD), o campo (FLS) e o registro (sharing).
Este é o checklist.
Por que o Apex ignora as permissões por padrão?
A plataforma parte do princípio de que o código precisa fazer o trabalho dele mesmo quando o usuário logado não teria permissão direta. Um trigger que soma valores, um batch que atualiza milhares de registros, uma automação que roda em nome de todos: se cada linha travasse na permissão do usuário da vez, nada funcionaria. Por isso o padrão é system mode, com acesso total.
O efeito colateral é que a responsabilidade de barrar o acesso indevido passa a ser sua. Quando o código devolve dados direto para a tela (um controller de LWC, um endpoint), ele tem que voltar ao ponto de vista do usuário. É aí que entram as três camadas.
Nível 1: CRUD, o acesso ao objeto inteiro
CRUD (Create, Read, Update, Delete) responde a uma pergunta simples: o usuário pode mexer neste objeto?
Antes de um insert ou de uma query, o código deveria confirmar que o perfil dele tem esse
acesso. Na mão, a checagem usa o describe do objeto:
if (!Schema.sObjectType.Contact.isCreateable()) {
throw new AuraHandledException('Sem permissão para criar contatos');
}
insert novoContato;
Funciona, mas fazer isso objeto por objeto vira ruído. Guarde essa checagem: mais abaixo o
USER_MODE resolve os três níveis de uma vez.
Nível 2: FLS, campo a campo
FLS (Field-Level Security) é a camada que mais escapa em code review. O usuário pode ter acesso ao
objeto Contact, mas não ao campo Salario__c. Se o seu SOQL traz esse campo e você devolve o
registro cru para a tela, você acabou de furar o FLS, mesmo que o CRUD estivesse correto. A defesa
cirúrgica é o Security.stripInaccessible, que remove dos registros os campos que o usuário
não pode ler:
SObjectAccessDecision decisao = Security.stripInaccessible(
AccessType.READABLE, contatos);
List<Contact> limpos = decisao.getRecords();
// campos sem FLS saem da lista, nada vaza para a tela
Nível 3: sharing, o registro em si
O usuário pode ver o objeto e todos os campos, mas ainda assim não pode ver aquele registro,
porque ele pertence a outra equipe. Isso é sharing, e é a camada que o Apex silenciosamente ignora se
você não declarar. A regra é declarar toda classe que lida com dados de usuário como
with sharing:
public with sharing class ContatoController {
// as queries desta classe respeitam o sharing do usuário
}
Se você quer entender por baixo do capô quais regras decidem cada registro, eu detalho o mecanismo no post sobre por que um usuário vê (ou não vê) um registro no Salesforce.
USER_MODE: o atalho que cobre os três de uma vez
Em vez de espalhar checagem de CRUD, describe de FLS e with sharing por todo lado, a
plataforma passou a oferecer o modo de usuário direto na operação. Numa query, use
WITH USER_MODE. Numa DML, use as user. O Salesforce aplica CRUD, FLS e sharing
daquela operação de uma vez, do ponto de vista do usuário logado:
List<Contact> cs = [SELECT Id, Name, Salario__c
FROM Contact WITH USER_MODE];
insert as user novoContato;
update as user contatosEditados;
É a mesma ideia que já aplico ao montar
SOQL dinâmico sem abrir brecha de injection,
onde a query final também roda em AccessLevel.USER_MODE. Vale reforçar: USER_MODE
na query não dispensa declarar a classe with sharing. Trate as duas coisas como cinto e
airbag.
Os erros clássicos que eu pego em code review
Numa revisão de código de uma org de demonstração (a Vetra, distribuidora fictícia do meu portfólio), os
três problemas que mais aparecem são sempre os mesmos. Primeiro: classe de controller sem
with sharing, devolvendo para a LWC registros que o usuário não deveria alcançar. Segundo:
SOQL trazendo um campo sensível e o registro indo cru para a tela, sem stripInaccessible nem
USER_MODE. Terceiro, e o mais sutil: o teste passa porque roda como o usuário admin da suíte,
que enxerga tudo, então a falha de FLS nunca aparece no verde da cobertura.
A correção para o terceiro é o teste que de fato prova a segurança: criar um usuário de perfil restrito e
rodar a lógica dentro de System.runAs, verificando que ele não recebe o
campo ou o registro proibido. Cobertura alta com todos os testes rodando como admin é um verde que mente.
Quer um code review de segurança do seu Apex antes de ir para produção?
Eu reviso classes Apex e controllers de LWC contra os três níveis de acesso, ligo USER_MODE e stripInaccessible onde falta e escrevo o teste que prova que nada vaza. Comece com um diagnóstico gratuito de 45 minutos.
Falar no WhatsApp Ver serviçosPerguntas frequentes
Apex respeita as permissões do usuário por padrão?
Não. Classes Apex rodam em system mode e ignoram CRUD e FLS por padrão. Sharing só é aplicado se a
classe for declarada with sharing. Cabe ao código forçar a checagem.
Qual a diferença entre CRUD, FLS e sharing?
CRUD controla o acesso ao objeto inteiro. FLS controla campo a campo dentro do objeto. Sharing controla quais registros daquele objeto o usuário pode ver. São três camadas independentes.
WITH USER_MODE substitui with sharing?
WITH USER_MODE numa query aplica CRUD, FLS e sharing daquela consulta. Mas
insert, update e delete ainda precisam de as user,
e declarar a classe with sharing continua sendo a rede de segurança.
USER_MODE funciona em Apex de teste?
Sim. Você cria um usuário de perfil restrito, roda a lógica dentro de System.runAs e
verifica que ele não enxerga o campo ou o registro que não deveria. Esse é o teste que prova a
checagem de acesso.
