Segurança
O PowerPortalsPro oferece um modelo de segurança flexível e orientado por código, que permite controlar o acesso tanto no nível da tabela quanto no nível do registro. A segurança é implementada criando classes de manipuladores de permissões e registrando-as no container de injeção de dependências.
Visão geral
Existem duas interfaces que você pode implementar dependendo do nível de controle que precisa:
ITablePermissionHandler— Controle se um usuário pode realizar operações de Crear, Lê, Atualizar, Deletar, Anexar ou Anexar em uma determinada tabela. As decisões são baseadas unicamente na identidade do usuário.ITableRecordPermissionHandler— EstendeITablePermissionHandlercom sobrecargas em nível de registro que recebem o realTableRecordem que está sendo operado, permitindo regras detalhadas como "os usuários só podem editar seus próprios registros."
Dica
Se você só precisar de verificações em nível de tabela (por exemplo, "usuários autenticados podem ler esta tabela"), use
ITablePermissionHandler. Se você precisar de lógica em nível de registro (por exemplo, "usuários só podem deletar registros que possuem"), useITableRecordPermissionHandler.
Boa prática — filtre na fonte
Os manipuladores de permissões impõem acesso, mas não substituem o filtro. Os manipuladores em nível de registro são executados após a recuperação de uma página de registros do Dataverse, então depender deles para ocultar linhas distorce a paginação da grade (uma página com 50 pode mostrar menos) e a contagem total, além de desperdiçar consultas buscando linhas que depois são descartadas. Sempre que os dados precisarem ser direcionados ao usuário atual, filtre no nível da consulta — com uma
IFetchXmlQueryFilterInterceptor(veja abaixo) ou uma visualização personalizada que leve em conta o usuário atual — para que o Dataverse retorne apenas as linhas que o usuário deve ver e gradeie a página corretamente. Mantenha os responsáveis pela permissão como garantia de fiscalização.
Dataverse-Native Security (Prévia)
O modelo de segurança recomendado para o futuro permite que a própria Dataverse aplique a segurança para os usuários do seu portal, baseado no recurso unificado de segurança do Dataverse do Power Pages. Em vez de escrever manipuladores de permissões em código, você gerencia o acesso com papéis de segurança padrão do Dataverse — os mesmos papéis, privilégios e ferramentas administrativas usadas para usuários internos. O modelo está atualmente em prévia — tanto o recurso da Microsoft quanto o suporte do Power Portals Pro — e o Power Portals Pro seguirá o cronograma de lançamentos da Microsoft até a disponibilidade geral.
Quando ativado, o PowerPortalsPro fornece automaticamente um sistema gerenciado (C2) systemuser para cada contato do portal — no registro para novos contatos e preguiçosamente no login para os existentes — junto com o powerpagesusermapping registro que liga o contato, o usuário do sistema e seu site Power Pages. Cada chamada Dataverse para a sessão então se passa por esse usuário do sistema, então os papéis de segurança que o Power Pages sincroniza a partir dos seus papéis web e permissões de tabela governam exatamente o que o usuário pode ver e fazer. A sessão carrega um PortalUserType de ContactStubUser para que sua interface possa sempre distinguir esses usuários provisionados dos usuários internos reais.
Nota
Para usar esse modelo de segurança, você deve criar um site Power Pages no seu ambiente Dataverse — é isso que instala os componentes do framework Microsoft e permite habilitar o recurso de segurança unificada. Você não precisa de um plano pago do Power Pages, e pode deletar o site imediatamente após instalar e ativar o recurso. O registro do site do Power Pages (
powerpagesite) referenciadoPowerPagesSiteIdpor deve existir no ambiente, já que os mapeamentos do usuário e a sincronização de papéis de segurança são resolvidos contra ele.
Ativando a Segurança Dataverse-Native
Ative o modelo no SecurityOptions Program.csseu servidor, fornecendo o ID do seu registro do site Power Pages:
builder.Services.Configure<SecurityOptions>(options =>
{
options.DataverseSecurity.ProvisionSystemUsers = true;
options.DataverseSecurity.PowerPagesSiteId = new Guid("<your powerpagesite id>");
});
A configuração é validada na inicialização da aplicação: quando ProvisionSystemUsers ativada, PowerPagesSiteId deve ser definida e resolvida para um registro existente powerpagesite , caso contrário a aplicação não inicia com a orientação.
Conceda acesso exatamente como o Power Pages faz: configure funções web e permissões de tabela no app Power Pages Management, e atribua funções web aos contatos (pelo app Management ou programaticamente). A plataforma sincroniza esses papéis em papéis de segurança gerados automaticamente (nomeados PowerPages_*) e os atribui automaticamente conforme mudanças de associação à função web — como a configuração está inteiramente no Power Pages, seu portal pode se mover para ou de um site real do Power Pages sem atrito. Note que mudanças de função e privilégio podem levar de alguns segundos até alguns minutos para se propagar pelos caches do Dataverse antes de entrarem em vigor.
Siga o guia passo a passo de configuração
Começando
1. Criar um Manipulador de Permissão
Crie uma classe que implemente ITablePermissionHandler ou ITableRecordPermissionHandler. Defina a Table propriedade para o nome lógico da tabela Dataverse à qual o handler se aplica e implemente cada método de permissão.
Aqui está um exemplo simples que concede acesso total a todos os usuários para uma tabela específica:
public class MyTablePermissionHandler : ITablePermissionHandler
{
public string Table => "my_customtable";
public Task<bool> CanCreateAsync(Guid? userId) => Task.FromResult(true);
public Task<bool> CanReadAsync(Guid? userId) => Task.FromResult(true);
public Task<bool> CanUpdateAsync(Guid? userId) => Task.FromResult(true);
public Task<bool> CanDeleteAsync(Guid? userId) => Task.FromResult(true);
public Task<bool> CanAppendAsync(Guid? userId) => Task.FromResult(true);
public Task<bool> CanAppendToAsync(Guid? userId) => Task.FromResult(true);
}
2. Registro na Injeção de Dependência
Registre seu handler no contêiner DI do Program.cs arquivo do seu projeto (ou em um método de extensão de coleção de serviços):
builder.Services.AddSingleton<ITablePermissionHandler, MyTablePermissionHandler>();
Como os handlers são resolvidos a partir do contêiner DI, você pode injetar quaisquer serviços necessários (como contexto de banco de dados ou serviço ao usuário) por meio da injeção de construtores.
Segurança em Nível de Registro
Para cenários em que o acesso depende do registro específico em que está sendo operado, implemente ITableRecordPermissionHandler. Essa interface se estende ITablePermissionHandler com sobrecargas que recebem o TableRecord.
O exemplo a seguir permite que todos os usuários leiam registros globalmente, mas restringe as operações de escrita aos registros pertencentes ao usuário atual:
public class ContactPermissionHandler : ITableRecordPermissionHandler
{
public string Table => "contact";
public List<string> RequiredColumns => [];
// Nível de mesa: permitir acesso amplo
public Task<bool> CanReadAsync(Guid? userId) => Task.FromResult(true);
public Task<bool> CanCreateAsync(Guid? userId)
=> Task.FromResult(userId != null && userId != Guid.Empty);
// Nível de registro: restringa as escritas ao proprietário do disco
public Task<bool> CanUpdateAsync(
Guid? userId, TableRecord recordWithUpdates, TableRecord currentRecord)
{
return Task.FromResult(userId == currentRecord.Id);
}
public Task<bool> CanDeleteAsync(Guid? userId, TableRecord record)
{
return Task.FromResult(userId == record.Id);
}
// ... implementar métodos restantes
}
Nota
A
CanUpdateAsyncsobrecarga em nível de registro fornece tanto ao registro as atualizações propostas (recordWithUpdates) quanto ao registro em seu estado persistido atualmente (currentRecord), permitindo que você compare valores ou valide mudanças específicas de campo.
Colunas Obrigatórias
A RequiredColumns propriedade especifica quais colunas devem ser recuperadas para que o manipulador avalie permissões em nível de registro. O framework adiciona automaticamente essas colunas às consultas FetchXML quando elas ainda não estão incluídas, garantindo que o manipulador sempre tenha os dados necessários — mesmo que a visualização ou consulta não inclua essas colunas.
O exemplo a seguir usa a ppp_owningportaluserid coluna de consulta para restringir o acesso a registros pertencentes ao usuário atual:
public class RegionPermissionHandler : ITableRecordPermissionHandler
{
public string Table => "ppp_region";
public List<string> RequiredColumns => ["ppp_owningportaluserid"];
public Task<bool> CanReadAsync(Guid? userId) => Task.FromResult(true);
public Task<bool> CanCreateAsync(Guid? userId) => Task.FromResult(userId != null);
public Task<bool> CanReadAsync(Guid? userId, TableRecord record)
{
return Task.FromResult(
record.GetValueOrDefault<LookupValue>("ppp_owningportaluserid")?.Value == userId);
}
// ... implementar métodos restantes
}
Nota
Se
RequiredColumnsretorna uma lista vazia e os métodos em nível de registro do handler usamrecord.Idapenas , nenhuma coluna adicional é adicionada à consulta. Aownerpropriedade é sempre fornecida pela plataforma, independentemente deRequiredColumns.
Interceptadores de Filtro de Consulta FetchXML
Além da segurança em nível de tabela e registro, o PowerPortalsPro suporta IFetchXmlQueryFilterInterceptor — uma interface que permite modificar consultas FetchXML antes de serem executadas. Isso é útil para impor filtragem em nível de linha, como garantir que um usuário só possa ver registros que pertencem a ele ou à sua equipe.
IFetchXmlQueryFilterInterceptor estende ITablePermissionHandler, então cada interceptor também define permissões em nível de tabela por meio da Table propriedade e dos métodos padrão de permissões. O método do OnQueryAsync interceptor é chamado apenas para consultas que têm como alvo sua tabela especificada.
Diferente dos manipuladores de permissões em nível de registro, que avaliam cada registro após a recuperação, interceptadores de filtro de consulta modificam a consulta antes que ela chegue ao Dataverse. Registros filtrados nunca são recuperados, o que melhora o desempenho e a segurança — e, o que é importante, mantém a paginação em grade e as contagens totais precisas, já que o filtro aplicado após a recuperação deixaria as páginas curtas e os totais de contagem incorreta.
1. Criar um interceptador de filtro de consulta
Implemente IFetchXmlQueryFilterInterceptor, defina a Table propriedade para o nome lógico da tabela, implemente os métodos de permissão e use o FetchXMLBuilder in OnQueryAsync para adicionar filtros à consulta. O FetchXmlParameters registro fornece o arquivo do usuário EntityReferenceatual
FetchXMLBuilder
O exemplo a seguir restringe consultas na contact tabela para devolver apenas o registro correspondente ao usuário atual:
public class ContactQueryFilterInterceptor : IFetchXmlQueryFilterInterceptor
{
public string Table => "contact";
public Task<bool> CanCreateAsync(Guid? userId) => Task.FromResult(false);
public Task<bool> CanReadAsync(Guid? userId) => Task.FromResult(true);
public Task<bool> CanUpdateAsync(Guid? userId) => Task.FromResult(false);
public Task<bool> CanDeleteAsync(Guid? userId) => Task.FromResult(false);
public Task<bool> CanAppendAsync(Guid? userId) => Task.FromResult(false);
public Task<bool> CanAppendToAsync(Guid? userId) => Task.FromResult(false);
public Task<FetchXMLBuilder> OnQueryAsync(
FetchXMLBuilder fetchXmlBuilder, FetchXmlParameters parameters)
{
var filter = fetchXmlBuilder.Fetch.Entity.AddFilter();
var condition = filter.AddCondition();
condition.Column = "contactid";
condition.Operator = ConditionOperator.Equal;
condition.Value = parameters.CurrentUser.Id.ToString();
return Task.FromResult(fetchXmlBuilder);
}
}
2. Registro na Injeção de Dependência
Registre seu interceptor tanto como um IFetchXmlQueryFilterInterceptor quanto como um ITablePermissionHandler para que tanto o filtro de consulta quanto as permissões em nível de tabela sejam aplicados. Múltiplos interceptadores podem ser registrados e cada um será executado para consultas direcionadas à sua tabela.
builder.Services.AddTransient<ContactQueryFilterInterceptor>();
builder.Services.AddTransient<ITablePermissionHandler>(
sp => sp.GetRequiredService<ContactQueryFilterInterceptor>());
builder.Services.AddTransient<IFetchXmlQueryFilterInterceptor>(
sp => sp.GetRequiredService<ContactQueryFilterInterceptor>());
Dica
Como
IFetchXmlQueryFilterInterceptorestendeITablePermissionHandler, uma única classe lida tanto com permissões em nível de tabela quanto com filtragem em nível de consulta. OOnQueryAsyncmétodo é invocado apenas para consultas que correspondem à propriedade doTableinterceptor, então não há necessidade de verificar o nome da tabela dentro do método.
Classes Base Integradas
A estrutura fornece classes base para simplificar padrões comuns de segurança:
AnonymousPermissionHandler— Concede todas as operações a todos os usuários (incluindo anônimos). Útil para tabelas acessíveis publicamente. Simplesmente herde e estabelece oTableimóvel.
Referência API
ITablePermissionHandler Interface
Propriedades
Nome | Tipo | Padrão | Descrição |
|---|---|---|---|
Table | string | Nome lógico da tabela à qual esse manipulador de permissão se aplica. |
TableMétodos
Nome | Parâmetros | Tipo | Descrição |
|---|---|---|---|
CanAppendAsync | Guid? userId | Task<bool> | Método para determinar se um usuário deve ser capaz de anexar um registro. |
CanAppendToAsync | Guid? userId | Task<bool> | Método para determinar se um usuário deve ser capaz de anexar a um registro. |
CanCreateAsync | Guid? userId | Task<bool> | Método para determinar se um usuário deve ser capaz de criar um registro. |
CanDeleteAsync | Guid? userId | Task<bool> | Método para determinar se um usuário deve ser capaz de excluir um registro. |
CanReadAsync | Guid? userId | Task<bool> | Método para determinar se um usuário deve ser capaz de ler um registro. |
CanUpdateAsync | Guid? userId | Task<bool> | Método para determinar se um usuário deve ser capaz de atualizar um registro. |
CanAppendAsyncCanAppendToAsyncCanCreateAsyncCanDeleteAsyncCanReadAsyncCanUpdateAsyncITableRecordPermissionHandler Interface
Propriedades
Nome | Tipo | Padrão | Descrição |
|---|---|---|---|
RequiredColumns | List<string> | Lista de nomes lógicos de colunas que devem ser recuperados para que este manipulador possa avaliar permissões em nível de registro. | |
Table | string | Nome lógico da tabela à qual esse manipulador de permissão se aplica. |
RequiredColumnsTableMétodos
Nome | Parâmetros | Tipo | Descrição |
|---|---|---|---|
CanAppendAsync | Guid? userId TableRecord record | Task<bool> | Método para determinar se um usuário deve ser capaz de anexar o registro fornecido. |
CanAppendToAsync | Guid? userId TableRecord record | Task<bool> | Método para determinar se um usuário deve ser capaz de adicionar ao registro fornecido. |
CanCreateAsync | Guid? userId TableRecord record | Task<bool> | Método para determinar se um usuário deve ser capaz de criar o registro fornecido. |
CanDeleteAsync | Guid? userId TableRecord record | Task<bool> | Método para determinar se um usuário deve ser capaz de excluir o registro fornecido. |
CanReadAsync | Guid? userId TableRecord record | Task<bool> | Método para determinar se um usuário deve ser capaz de ler o registro fornecido. |
CanUpdateAsync | Guid? userId TableRecord recordWithUpdates TableRecord currentRecord | Task<bool> | Método para determinar se um usuário deve ser capaz de atualizar o registro fornecido. |
CanAppendAsyncTableRecord record
CanAppendToAsyncTableRecord record
CanCreateAsyncTableRecord record
CanDeleteAsyncTableRecord record
CanReadAsyncTableRecord record
CanUpdateAsyncTableRecord recordWithUpdates
TableRecord currentRecord
IFetchXmlQueryFilterInterceptor Interface
Propriedades
Nome | Tipo | Padrão | Descrição |
|---|---|---|---|
Table | string | Nome lógico da tabela à qual esse manipulador de permissão se aplica. |
TableMétodos
Nome | Parâmetros | Tipo | Descrição |
|---|---|---|---|
CanAppendAsync | Guid? userId | Task<bool> | Método para determinar se um usuário deve ser capaz de anexar um registro. |
CanAppendToAsync | Guid? userId | Task<bool> | Método para determinar se um usuário deve ser capaz de anexar a um registro. |
CanCreateAsync | Guid? userId | Task<bool> | Método para determinar se um usuário deve ser capaz de criar um registro. |
CanDeleteAsync | Guid? userId | Task<bool> | Método para determinar se um usuário deve ser capaz de excluir um registro. |
CanReadAsync | Guid? userId | Task<bool> | Método para determinar se um usuário deve ser capaz de ler um registro. |
CanUpdateAsync | Guid? userId | Task<bool> | Método para determinar se um usuário deve ser capaz de atualizar um registro. |
OnQueryAsync | FetchXMLBuilder fetchXmlBuilder FetchXmlParameters parameters | Task<FetchXMLBuilder> | Chamado para permitir a modificação de uma consulta FetchXML antes de sua execução. |
CanAppendAsyncCanAppendToAsyncCanCreateAsyncCanDeleteAsyncCanReadAsyncCanUpdateAsyncOnQueryAsyncFetchXmlParameters parameters
