Segurança da mesa
O Power Portals Pro protege os dados em três níveis: quais tabelas o usuário pode tocar, quais colunas dessas tabelas pode ver e alterar, e quais linhas retornam. Os três são decididos em um único registro por tabela. Descreva esse registro com AddTableAccess — é a forma de conceder uma tabela e o que os templates de projeto usam para cada tabela que eles registrarem. Quando uma regra é indescritível, as mesmas três respostas podem ser escritas como uma ITablePermissionProvider classe; ambas produzem a mesma coisa e compõem da mesma forma. Nada é concedido até que uma delas o diga: uma tabela que nenhum registro cobre é inacessível, que é o padrão fail-closed.
Dica
Você não precisa escrever isso a partir de uma página em branco. Projetos gerados entregam uma
secure-a-tablehabilidade: peça ao seu agente de codificação para garantir uma tabela e ele trabalha através de qual tabela, para quem ela é, quais linhas, quais operações e quais colunas, então escreve o registro e faz as alterações correspondentes no Dataverse através do servidor Power Portals Pro MCP — criando o papel do portal se o acesso estiver vinculado ao papel e concedendo ao usuário do aplicativo os privilégios que as respostas sugerem. Depois,check_setupreporta qualquer tabela à qual suas páginas estejam vinculadas e que nada conceda acesso, e qualquer registro que opte por não participar da segurança das colunas. Veja Como Começar → Servidor MCP.
Todos, ou Só Algumas Pessoas
Cada registro diz para quem é, e há duas respostas. Registre-o no portal e ele aplica a um público seus nomes-código — todo mundo, ou cada usuário logado. Registre-o dentro AddPortalRole e ele se aplica aos detentores desse papel, que é uma decisão de dados que o administrador toma no Dataverse. O acesso em si é descrito da mesma forma em ambos.
| Registro | Aplica-se a | Para mudar quem recebe |
|---|---|---|
AddTableAccess("account", t => t.ForSignedInUsers()…) |
todos que a plateia cobre, a cada pedido | editar o código e redistribuir |
AddPortalRole("Credit Manager", role => role.WithTableAccess("account", …)) |
contatos que ocupavam aquele Papel de Portal, e mais ninguém | assignar ou desatribuir a função no Dataverse — sem implantação |
Uma tabela pode ser registrada uma vez para cada público em todo o portal — ForEveryone() para o que qualquer pessoa pode alcançar, ForSignedInUsers() para o que o login adiciona — e as duas somam. Um visitante recebe a primeira; um usuário logado recebe ambas, com cada operação restrita às linhas do registro que a concede. Com os dois registros abaixo, todos lêem todos os artigos, e um autor logado também pode escrever um e editar o seu próprio:
builder.Services.AddTableAccess("ppp_article", t => t
.ForEveryone()
.AllRows()
.Allow(TableSecurityPermission.Read)
.Reading("ppp_title", "ppp_body"));
builder.Services.AddTableAccess("ppp_article", t => t
.ForSignedInUsers()
.RowsOwnedByContact("ppp_authorid")
.Allow(TableSecurityPermission.Create | TableSecurityPermission.Write)
.Writing("ppp_title", "ppp_body"));
Os papéis somam; eles nunca subtraem. O acesso do usuário é o registro em todo o portal mais cada função que ele ocupa, unida — então a função para todo o portal é o piso em que todos estão, e um papel só pode aumentá-lo. Um papel não pode recuperar uma coluna ou linha que o registro em todo o portal concedeu; para reter algo da maioria das pessoas, conceder em um papel em vez de em todo o portal. (Definido SecurityOptions.Roles.CompositionBehavior para Restrictive se você precisar que todos os grupos concordem.)
Então: se todo usuário desse tipo tiver acesso, registre-o no portal. Se ao menos alguns deverem — e qual pessoa é uma pergunta cuja resposta muda sem liberação — coloquem em um papel. O registro de função não concede nada até que alguém crie um registro de Portal com esse nome e o atribua, por isso é possível sentar em um projeto novo sem causar dano.
Descrevendo o Acesso de uma Tabela
A maioria das tabelas quer as mesmas poucas regras: para quem é a tabela, quais linhas alcançam, o que podem fazer e quais colunas veem. AddTableAccess diz o registro dos quatro em um e produz um ordinário ITablePermissionProvider — nada a jusante sabe a diferença. O que isso compra é que as três decisões que precisam concordar (o filtro, a criação e as colunas graváveis) são tomadas uma vez em vez de três.
builder.Services.AddTableAccess("incident", t => t
.ForSignedInUsers()
.RowsOwnedByContact("customerid")
.Allow(TableSecurityPermission.Read | TableSecurityPermission.Create)
.Reading("ticketnumber", "statuscode", "createdon")
.Writing("title", "description"));
Um público, um escopo de linha e Allow(…) todos são necessários. Nenhum deles tem padrão, porque todo padrão plausível é uma forma de publicar dados deixando algo de fora — e um registro que erra falha na inicialização em vez de em produção.
Escopos de Linha
O escopo filtra todas as consultas da tabela — grades, consultas, subgrades, gráficos e a busca antes de um save ou delete. Os escopos nomeados cobrem as formas que os portais continuam precisando:
| Quais linhas | Escopo | No criar |
|---|---|---|
| Cada fileira | AllRows() |
Nada definido |
| Os próprios registros do usuário | RowsOwnedByContact("customerid") |
a busca está configurada para eles |
| Registros de colegas, na mesma conta | RowsOfTheirAccount("customerid") |
a busca é configurada para a conta deles |
| Registros pendurados em um dos pais que eles conseguem alcançar | RowsViaParent("salesorderid", "salesorder", p => …) |
Nada definido — o acesso dos pais decide |
| Registros que apontam para eles de mais de uma forma | RowsMatchingAny(r => r.RowsOwnedByContact("customerid"), r => r.RowsOfTheirAccount("customerid")) |
O que o primeiro escopo de propriedade listou define |
| A disputa de contatos deles | OwnRecordOnly() |
não criar |
Os escopos de propriedade definem a coluna quando um registro é criado, após as verificações de permissões: o usuário não precisa de acesso de escrita a ela nem de Append To na tabela para a qual ela aponta, o valor vem da sessão e não da solicitação, e um save posterior que mudaria isso é recusado. É isso que mantém um registro criado dentro do filtro ao qual está sujeito. Use OwnedBy('column') quando a coluna proprietária não for aquela que filtrou as linhas, e WithoutStamping() quando um plugin já a configura.
Quando Ler e Mudar Diferem
One scope responde a todas as operações, a menos que você diga o contrário. RowsForWriting Reduz as linhas que um usuário pode alterar sem restringir as linhas que pode ver — "todos lêem cada contato, cada um edita apenas o seu" é essa distinção e nada mais — e RowsForDeleting reduz a destruição ainda mais, porque poder corrigir um registro não é o mesmo que poder destruí-lo. Cada um volta para o que está acima, então um registro que não faz distinção não faz diferença nenhum. Criações não são afetadas: ainda não há linha para filtrar.
builder.Services.AddTableAccess("contact", t => t
.ForSignedInUsers()
.AllRows() // reads: every contact
.RowsForWriting(r => r.OwnRecordOnly()) // updates and deletes: their own row
.Allow(TableSecurityPermission.Read | TableSecurityPermission.Write)
.ReadingAllColumns());
Quando as lunetas nomeadas não encaixam
RowsMatching Usa um filtro próprio sem sair do registro. Dê FetchXML — um <filter> elemento ou um todo <fetch> cujos filtros e joins são copiados, com {currentUserId} para o contato logado — ou um callback que roda por requisição e retorna o FetchXML, composto pelo que seus serviços respondem, ou um filtro que ele construiu. Ambos recebem o mesmo TableQueryContext que um provedor recebe, então eles sabem quem está perguntando e se as linhas estão prestes a ser lidas, atualizadas ou excluídas. Não devolver nada não concede linhas: TableQueryFilter.AllRows é como você diz cada linha.
.RowsMatching("<filter><condition attribute='ppp_owner' operator='eq' value='{currentUserId}' /></filter>")
.RowsMatching(async (context, services) =>
{
var territories = services.GetRequiredService<ITerritoryService>();
var filter = context.CreateFilter();
filter.AddCondition("ppp_territory", ConditionOperator.In, await territories.ForContactAsync(context.User.Id));
return filter;
})
Descrevendo o Acesso de um Cargo
Dentro AddPortalRole do papel está o público, então o registro não nomeia outro. Papéis somam e nunca subtraem: o acesso do usuário são os registros em todo o portal mais todos os cargos que ele ocupa, sindicalizados. Os próprios papéis do portal — o que são, como são atribuídos e como compõem — são a próxima seção.
builder.Services.AddPortalRole("Case Manager", role => role
.WithTableAccess("incident", t => t
.RowsOfTheirAccount("customerid")
.Allow(TableSecurityPermission.Read | TableSecurityPermission.Write)
.Reading("ticketnumber", "statuscode")
.Writing("title", "description")));
Nota
Uma classe de provedor permanece como forma de expressar tudo o que uma descrição não pode, e ela compõe de forma idêntica —
AddTablePermissionProvider<T>eWithPermissions<T>permanece inalterada. Os projetos gerados não entregam mais uma: toda tabela registrada é descrita, incluindocontacte o papel de Gerente de Crédito.
Funções no Portal
Um papel de portal é a forma preferida de proteger um portal. Ele vincula os provedores de permissão a um nome que os administradores atribuem aos contatos no Dataverse, de modo que os provedores se aplicam apenas às pessoas que ocupam essa função — e quem a detém são dados, não código. Um provedor registrado Program.cs diretamente se aplica a todos os usuários, em comparação.
A divisão é deliberada. O Dataverse detém os nomes e as atribuições, que um administrador gerencia no aplicativo orientado ao modelo. Seu código é dono do significado de cada nome. Conceder ou revogar o acesso de alguém é então uma mudança de dados: sem reconstrução, sem reimplantação.
1. Criar o papel e atribuí-lo
Crie um registro de Papel no Portal e atribua-o aos contatos que deveriam tê-lo. O registro de função contém apenas um nome e uma descrição — nenhum dado de permissão está presente no Dataverse.
2. Vincular o papel aos seus provedores
Compare o nome do registro de vaga e liste o que ele concede. Os tipos de prestadores são registrados para você, então você não os registra pessoalmente.
builder.Services.AddPortalRole("Dealer", role => role
.WithPermissions<DealerAccountPermissionProvider>()
.WithPermissions<DealerOpportunityPermissionProvider>());
Esse último ponto importa: nunca registre também o provedor de um papel com AddTablePermissionProvider<T>(). Isso coloca o cargo no conjunto global e concede a todos, anulando silenciosamente o cargo. Reserve AddTablePermissionProvider<T>() regras que realmente se apliquem a todos os usuários.
Como os papéis se combinam
As permissões de um usuário vêm dos provedores registrados globalmente, além de cada função que ocupa, e esses grupos se juntam assim:
- Entre papéis — aditivo. Se algum papel concede uma operação, o usuário a tem. Ter mais funções só pode dar mais acesso, então uma função nunca pode tirar algo. Defina
SecurityOptions.Roles.CompositionBehaviorparaRestrictivese você quiser que os papéis se reduzam mutuamente. - Dentro de um mesmo papel — inalterado. Vários provedores na mesma função se dobram sob
SecurityOptions.MultiplePermissionHandlersBehavior, exatamente como os provedores registrados globalmente sempre fizeram. - Colunas também somam. Como o acesso a colunas é opt-in, um papel que nunca menciona uma coluna não pode ampliar o que outro papel publicado — mas um papel que nomeia uma coluna concede isso, porque nomeá-la foi um ato deliberado. Mude
RoleCompositionBehaviorparaRestrictivee tanto operações quanto colunas se cruzam.
Dica
Na composição aditiva, nada nega — algo permanece fechado apenas enquanto nenhum grupo o abre. Um provedor registrado globalmente é o local certo para uma regra que se aplica a todo usuário, porque ela está sempre no fold, mas não é um veto: um papel que concede a operação, ou nomeia a coluna, a reabre. Para manter algo fechado, quaisquer que sejam os papéis que um usuário tenha, conceda em nenhum papel — ou defina
SecurityOptions.Roles.CompositionBehaviorparaRestrictive, onde todo grupo tem que concordar.
Papéis nas páginas e navegação
Os nomes dos papéis chegam ao navegador como reivindicações padrão ASP.NET de papéis, então a autorização de página e a interface condicional funcionam sem nada extra:
@attribute [Authorize(Roles = "Dealer")]
<AuthorizeView Roles="Dealer">
<p>Dealer-only content</p>
</AuthorizeView>
A pilha React lê os mesmos nomes de auth.user.roles, e <AuthorizedView roles="Dealer"> porta o conteúdo da mesma forma. Logins internos (usuários do sistema) carregam aqui seus nomes de papéis de segurança do Dataverse, então uma verificação cobre ambos os tipos de usuário.
Autorização para o tipo certo de função
Um papel de portal e um papel de segurança do Dataverse podem compartilhar um nome, e [Authorize(Roles = "…")] não podem diferenciá-los — ele corresponde à reivindicação de papel não tipado, que carrega ambos. Dois atributos fazem a pergunta mais restrita:
@attribute [AuthorizePortalRole("Dealer")]
// Qualquer um dos cargos entra.
[AuthorizePortalRole("Dealer", "Distributor")]
public class PricingController : Controller { }
// Um papel de portal chamado "Administrador de Sistema" não atende a isso.
[AuthorizeSecurityRole("System Administrator")]
public class ImportController : Controller { }
Os nomes dos papéis vão literalmente: vírgulas, dois-pontos e aspas são todos aceitáveis, porque o nome nunca é separado de uma lista. Empilhar dois atributos requer ambos, do mesmo jeito que os atributos empilhados [Authorize] sempre fizeram; listar dois nomes em um atributo exige qualquer um dos dois. A mesma verificação está disponível no código como User.IsInPortalRole("Dealer") e User.IsInSecurityRole("Sales Manager").
A mesma distinção na marcação Blazor. <AuthorizeView Roles="…"> lê a reivindicação de papel não tipado e, portanto, carrega a mesma ambiguidade do atributo plain; <PortalRoleView> pede um tipo. Dê / PortalRole SecurityRole para um único nome, ou PortalRoles / SecurityRoles para vários — uma coleção em vez de uma cadeia separada por vírgulas, porque os nomes do Dataverse rotineiramente contêm vírgulas:
<PortalRoleView PortalRole="Dealer">
<a href="/pricing">Dealer pricing</a>
</PortalRoleView>
@* Uma função de portal chamada "Administrador de Sistema" não vai abrir isso. *@
<PortalRoleView SecurityRole="System Administrator">
<BulkImport />
</PortalRoleView>
React <AuthorizedView> assume os mesmos dois portais, junto com o existente roles . Fornecer vários nomes em um portal significa qualquer um deles; fornecer dois portais significa ambos. Os predicados atrás deles — hasPortalRole, hasSecurityRole, hasRole — são exportados para verificações imperativas:
<AuthorizedView portalRoles="Dealer">
<Link to="/pricing">Dealer pricing</Link>
</AuthorizedView>
<AuthorizedView securityRoles="System Administrator">
<BulkImport />
</AuthorizedView>
Boa prática — filtre na fonte
Procure esses documentos sempre que o que está sendo protegido for realmente administrativo. Criar um papel de portal é um padrão propositalmente mais baixo do que receber um papel de segurança no Dataverse, então uma verificação que aceite permite que um administrador distribua acesso que o papel do Dataverse estava protegendo simplesmente nomeando um papel de portal em homenagem a ele.
Nota
Os papéis não são gratuitos. Cada um que o usuário mantém é escrito no cookie de autenticação duas vezes — uma como a reivindicação padrão de papel, então
[Authorize(Roles)]funciona, e outra como a reivindicação específica de tipo que torna possíveis as verificações acima — que mede cerca de 160 bytes por função. Cerca de quinze papéis por usuário ainda acomodam um único cookie; além disso, o cookie se divide em blocos, e a primeira coisa a ceder geralmente é o limite de cabeçalho de um proxy reverso, que reporta um opaco 400 em vez de algo que mencione papéis. Prefira alguns papéis amplos em vez de muitos restritos.
Dando a novos usuários um papel inicial
Nomeie os papéis que toda nova conta deve ter e o papel que se aplica aos visitantes desconectados:
builder.Services.Configure<SecurityOptions>(options =>
{
options.Roles.DefaultRoleNames = ["Customer"];
options.Roles.AnonymousRoleName = "Anonymous";
});
Ou decidir no Dataverse, sem uma liberação. A tabela de Configuração de Funções do Portal emparelha uma função com um modo de atribuição, e um administrador pode alterar quem recebe o quê do aplicativo orientado ao modelo:
- Nenhum — configurado, mas dormente. A linha não concede nada até que alguém mude o modo.
- Atribuir automaticamente a novos usuários — toda conta recebe esse papel conforme é criado, desde que tanto a linha de configuração quanto o papel que ela nomeia estejam ativos. Desativar qualquer um deles o impede: uma linha de configuração é uma instrução permanente para distribuir um papel, e um papel desativado não concede nada, então uma linha ativa nomeando um papel inativo é uma instrução sem nada por trás. Reativar o papel recomeça.
- Atribuir a novos usuários a partir dos critérios — reservado para correspondência com base nos dados do novo usuário. Os critérios ainda não são avaliados, então uma linha nesse modo atribui a função a ninguém em vez de a todos, e o portal registra um aviso nomeando para que não pareça uma regra que não foi executada e não correspondeu a ninguém.
Quando as funções dependem da pessoa — seu domínio de e-mail, o provedor com quem ela fez login, um convite — registre-se e IPortalRoleAssignmentProvider em vez disso. Todo provedor registrado é consultado, então o seu adiciona à lista configurada em vez de substituí-la. De qualquer forma, a atribuição é executada uma vez, quando a conta é criada; ela nunca é reaplicada no login, então uma função removida por um administrador permanece removida.
Nota
Um usuário adquire uma função recém-atribuída ao sair e entrar novamente — ao fazer login relê seus papéis. As atribuições de papéis são armazenadas em cache por usuário por 24 horas, então, para aplicar uma alteração a alguém que já está logado, limpe o cache de Papéis do Portal da área de administração do site: a sessão deles o retoma na próxima atualização, em dez minutos, e um papel removido para de funcionar e assim que um papel adicionado começa. A tabela de Configuração de Função do Portal fica em cache separadamente por quinze minutos, então uma linha adicionada ali alcança o próximo registro por conta própria — limpe o cache "PortalRoleConfiguration" para torná-lo imediato. Desativar uma linha de configuração impede que ela distribua seu papel, da mesma forma que desativar um papel o revoga.
Escrevendo um provedor em vez disso
Busque uma classe quando uma descrição não pode dizer o que você quer dizer — uma regra que depende de algo que só seu código sabe, ou uma que precisa inspecionar o registro em andamento. ITablePermissionProvider É o que todo registro se torna de qualquer forma: um provedor governa uma tabela, respondendo o que um usuário pode fazer com ela, quais colunas pode ler e escrever, e — através do filtro de consulta abaixo — quais linhas ele pode ver. Um registro descrito e um escrito compõem de forma idêntica, então um portal pode usar ambos, e uma tabela pode ser concedida por um e ampliada pelo outro.
A interface possui um membro obrigatório. GetPermissionsAsync recebe um UserInfo que carrega o ID do usuário, seus papéis de portal e seus papéis de segurança do Dataverse, e retorna um TablePermissions descrevendo tudo que o usuário recebe:
public class AccountPermissionProvider : ITablePermissionProvider
{
public string Table => "account";
public Task<TablePermissions> GetPermissionsAsync(UserInfo user)
{
if (!user.IsAuthenticated)
return Task.FromResult(TablePermissions.None);
// As colunas são opt-in: apenas o que está nomeado aqui retorna.
return Task.FromResult(TablePermissions
.Allowing(TableSecurityPermission.Read | TableSecurityPermission.Write)
.Reading("name", "telephone1", "websiteurl", "creditlimit")
.Writing("telephone1", "websiteurl"));
}
}
Cadastrando um provedor
Um provedor registrado dessa forma se aplica a todos os usuários, logados ou não:
builder.Services.AddTablePermissionProvider<AccountPermissionProvider>();
Essa também é a única forma de tirar algo de todos: os papéis compõem aditivamente por padrão, então um papel pode adicionar acesso, mas nunca removê-lo. Restrições em todo o portal pertencem a um provedor registrado globalmente, que sempre faz parte do grupo.
Operações
TablePermissions.Operations é um TableSecurityPermission valor de bandeira — Read, Create, Write, Delete, e Append AppendTo. Duas coisas que valem a pena saber sobre ele:
- É uma porta em nível de tabela, não um filtro de linha. Conceda
Readse o usuário puder ver qualquer uma das linhas da tabela, então estreite quais estão emGetQueryFilterAsync. - Uma tabela sem provedor é inalcançável. Esse é o padrão fail-closed, e se aplica também às colunas: um provedor que concede
Read, mas não nomeia nenhuma coluna, retorna registros que só contêm seu id.
Nota
Não existe deliberadamente um gancho de "inspecionar cada linha após carregá-la". Filtrar dessa forma quebra a paginação — uma página de 50 retorna com menos — faz com que o total de contagens esteja errado e paga ao Dataverse para recuperar as linhas que ele depois descarta. O escopo da consulta, em vez disso, mantém a paginação e as contagens corretas por construção.
Boa prática — filtre na fonte
Escopar dados na consulta, não depois dele. O framework deliberadamente não oferece nenhum hook que inspecione cada linha depois de buscada: filtrar dessa forma quebra a paginação (uma página de 50 chega a menos), faz a contagem total estar errada e paga ao Dataverse para recuperar as linhas que ele então descarta.
GetQueryFilterAsyncadiciona as condições antes da consulta executar, então paginação e contagens permanecem corretas por construção e as linhas que um usuário pode não ver nunca são recuperadas.
Segurança em nível de linha
GetQueryFilterAsync retorna um filtro que o framework adiciona à consulta antes da execução do Dataverse, então linhas que o usuário pode não ver nunca são buscadas. Ele se aplica a toda consulta que o portal emite para a tabela — grades, consultas, subgrades, gráficos e cargas de registro único — e roda sob ambos os modelos de segurança, permanecendo em vigor mesmo onde a segurança nativa do Dataverse contorna o portão de operação.
Construa o filtro com context.CreateFilter() e retorne-o; retornando null significa sem restrição de linha. O contexto carrega o usuário atual como EntityReferenceum , os papéis que ele possui e para que as linhas estão prestes a ser usadas. Condições são adicionadas pela mesma FetchXMLBuilder API usada em todos os outros lugares:
FetchXMLBuilder
Este provedor concede três colunas de contact e escopa cada consulta de contato ao registro do usuário logado:
public class ContactPermissionProvider : ITablePermissionProvider
{
public string Table => "contact";
public Task<TablePermissions> GetPermissionsAsync(UserInfo user)
=> Task.FromResult(TablePermissions
.Allowing(TableSecurityPermission.Read | TableSecurityPermission.Write)
.ReadingAndWriting("firstname", "lastname", "telephone1"));
public Task<TableQueryFilter?> GetQueryFilterAsync(TableQueryContext context)
{
var filter = context.CreateFilter();
filter.AddCondition("contactid", ConditionOperator.Equal, context.CurrentUser.Id);
return Task.FromResult<TableQueryFilter?>(filter);
}
}
Leitura, atualização e exclusão
TableQueryContext.Operation Diz por que as linhas estão sendo buscadas, para que um provedor possa defini-las de forma diferente para cada uma:
Read— uma grade, consulta, subgrade, gráfico ou carga de registro. As linhas restantes são as linhas que o usuário pode ver.Update— a busca pré-voo antes de um save. As linhas restantes são as linhas que o usuário pode alterar, que geralmente é um conjunto mais restrito. "Veja todas as contas, edite apenas a minha" é essa distinção e nada mais.Delete— a busca pré-voo antes de um deleção. Mais restrito na maioria dos portais: poder corrigir um registro não é o mesmo que poder destruí-lo.
public Task<TableQueryFilter?> GetQueryFilterAsync(TableQueryContext context)
{
// Todos lêem tudo; apenas as escritas são encaminhadas ao dono.
if (context.Operation == TableQueryOperation.Read)
return Task.FromResult<TableQueryFilter?>(TableQueryFilter.AllRows);
var filter = context.CreateFilter();
filter.AddCondition("ownerid", ConditionOperator.Equal, context.CurrentUser.Id);
return Task.FromResult<TableQueryFilter?>(filter);
}
Como vários filtros se combinam
Uma tabela pode ter mais de um provedor — um registrado globalmente, mais um de cada função que o usuário ocupa. O framework decide como seus filtros se combinam, então um provedor nunca precisa saber o que os outros fizeram:
- Dentro de um mesmo grupo, eles se cruzam. Dois provedores registrados globalmente, ou dois vinculados ao mesmo papel, devem concordar, então uma linha precisa satisfazer ambos.
- Entre os papéis, eles se unem. Assumir outro cargo amplia as fileiras que você vê, combinando com a forma como as operações se compõem. Mude
RoleCompositionBehaviorparaRestrictivee eles se cruzam. - As junções são cuidadas. Um alias que colide com um que a consulta já usa é renomeado e as referências do filtro o seguem; quando filtros estão sendo unionados, uma junção interna é relaxada para externa para que a junção de um papel não possa excluir as linhas que outro papel permite; e uma junção que pode retornar o mesmo registro mais de uma vez tem esses duplicados colapsados.
Nota
Linhas falham fechadas, como colunas: um provedor que retorna
null— ou um filtro sem condições — não concede linhas , e dentro de um grupo que esvazia o grupo. Cada linha precisa ser solicitada comTableQueryFilter.AllRows. É aí que reside a assimetria: a união de toda linha com qualquer coisa é toda linha, então se qualquer grupo que concede a tabela respondeAllRows, a tabela retorna sem filtro, por mais cuidadosamente que os outros papéis tenham definido o escopo. Isso é o que significa composição aditiva, mas é a única forma pelo filtro de um papel parecer que está sendo ignorado.
Filtragem através de uma tabela relacionada
AddLink junta uma tabela para que o filtro possa testar suas colunas. Coloque o teste no filtro em vez de no link — condições dentro do filtro continuam funcionando quando a estrutura relaxa a junção para fora, e as condições no link não iriam:
public Task<TableQueryFilter?> GetQueryFilterAsync(TableQueryContext context)
{
var filter = context.CreateFilter(LogicalOperator.Or);
// O alias é tornado único se a consulta já o utiliza.
filter.AddLink("ppp_membership", from: "ppp_accountid", to: "accountid", alias: "m");
filter.AddCondition("primarycontactid", ConditionOperator.Equal, context.CurrentUser.Id);
filter.AddCondition("m", "ppp_contactid", ConditionOperator.Equal, context.CurrentUser.Id);
return Task.FromResult<TableQueryFilter?>(filter);
}
Nota
A junção acima chega até muitas linhas, então o Dataverse devolve a conta uma vez por membro — uma grade repetindo linhas e um total que as conta. O framework reconhece essa forma e marca a consulta
distinct, projetando a chave primária da tabela para que a deduplicação só remova as linhas que a junção adicionou. Uma junção que se estende para cima , juntando-se à própria chave primária da tabela relacionada, corresponde no máximo a uma linha e fica exatamente como antes. De qualquer forma, você escreve o filtro e nada mais — mas vale saber que uma junção não é gratuita, e que uma regra que você pode expressar como condições planas nas próprias colunas da tabela é a forma mais barata.
Dica
Quando uma tabela precisa de um escopo diferente para cada função, você ainda pode escrever um provedor e ramificar —
context.User.PortalRolesmas não precisa mais. Dois papéis podem carregar seu próprio provedor e o framework unifica seus filtros para você.
Quem possui um novo disco
Um filtro de linhas escopa leituras, atualizações e deleções, mas um criar não tem linha para filtrar. Assim, uma tabela com escopo para "linhas que eu possuo" ainda permitiria que um usuário crie uma linha pertencente a outra pessoa — ou a ninguém, que seu criador então não poderia ver. Declare a coluna que diz quem é o dono de um registro, e o framework o estabelece:
// Uma conta pertence ao contato que a cria.
builder.Services.AddContactOwnership("account", "primarycontactid");
- Ao criar , a coluna é definida para o contato logado após as verificações de permissões, e qualquer valor enviado pela solicitação é descartado. O usuário não precisa de acesso de gravação à coluna e não
AppendTocontactliga: vincular um registro a você mesmo não é algo com que ele seja confiável. - Na atualização , um pedido que mudaria o proprietário é recusado, e outro que repete o proprietário armazenado é ignorado.
- Usuários internos (log-ins de usuários-sistema) são isentos: o Dataverse garante sua segurança, e a realocação de um registro faz parte da administração.
Pare-o com um filtro de linha na mesma coluna, assim os registros criados pelo usuário são os registros que ele pode ver. Para a propriedade que não é simplesmente o contato criador — a conta do criador, por exemplo — implemente ITableRecordOwnershipProvider e registre em AddTableRecordOwnershipProvider<T>(). Não defina um proprietário em um ITableRecordInterceptor instead: interceptors rodam antes das verificações de permissões, então o que eles definem é julgado como escrita do usuário.
public sealed class CaseOwnershipProvider(IOrganizationServiceProvider organizationServiceProvider)
: ITableRecordOwnershipProvider
{
public string Table => "incident";
public IReadOnlyList<string> Columns { get; } = ["customerid"];
public async Task<IReadOnlyDictionary<string, LookupValue>> GetOwnersAsync(TableRecordOwnershipContext context)
{
// Leia como o usuário do aplicativo: a qual empresa alguém pertence não é o pedido para dizer.
var contact = await organizationServiceProvider.GetOrganizationService()
.RetrieveAsync("contact", context.CurrentUser.Id, new ColumnSet("parentcustomerid"), context.CancellationToken);
var company = contact.GetAttributeValue<EntityReference>("parentcustomerid");
// Registre o processo sob a empresa deles, ou sob eles quando não tiverem nenhum.
var owner = company?.LogicalName == "account"
? new LookupValue { TableName = "account", Value = company.Id }
: new LookupValue { TableName = "contact", Value = context.CurrentUser.Id };
return new Dictionary<string, LookupValue> { ["customerid"] = owner };
}
}
// Program.cs
builder.Services.AddTableRecordOwnershipProvider<CaseOwnershipProvider>();
O framework chama em cada criação daquela tabela, após as verificações de permissões, e define cada coluna Columns no que retorna — descartando qualquer coisa que a solicitação enviou e recusando uma atualização posterior que a alterasse, exatamente como para OwnedBy. Pare-o com um filtro de linha que corresponda ao que ele define, assim as pessoas sob as quais ele arquiva um registro são as que podem ver — aqui RowsMatchingAny(r => r.RowsOfTheirAccount("customerid"), r => r.RowsOwnedByContact("customerid")), com WithoutStamping() então o registro deixa a coluna para este provedor: dois que configuram uma coluna de forma diferente falham no salvamento.
Segurança em nível de coluna
A segurança das colunas é opt-in. Um provedor concede as colunas que pretende expor, e todo o resto — incluindo uma coluna que alguém adiciona à tabela no ano seguinte — permanece retido até ser nomeado. O id principal do registro é sempre legível, já que um registro que o framework não consegue identificar não serve para ninguém.
Leituras e escritas são conjuntos independentes: uma coluna pode ser legível, mas não gravável (o caso "mostrar o limite de créditos, não deixar que eles mudem"), ou ser gravável, mas não legível. Nomeie-as com Reading, Writing e ReadingAndWriting, ou desligue totalmente de uma dimensão com ReadingAllColumns / WritingAllColumns:
public Task<TablePermissions> GetPermissionsAsync(UserInfo user)
{
// Todas as colunas eram legíveis; duas delas não eram graváveis.
return Task.FromResult(TablePermissions
.Allowing(TableSecurityPermission.Read | TableSecurityPermission.Write)
.ReadingAllColumns()
.WritingAllColumnsExcept("creditlimit", "creditonhold"));
}
Nota
ReadingAllColumns()é algo que você precisa digitar deliberadamente. Ele não abre para qualquer coisa adicionada à tabela depois, o que é a negociação correta apenas quando a tabela é realmente pública — então o framework não assumirá isso em seu nome.
A escrita também pode diferir entre criar um registro e alterar um. Writing cobre ambos. WritingOnCreate permite que o usuário defina uma coluna ao criar o registro e o torne somente leitura a partir daí — a prioridade de um caso, escolhida quando é registrado. WritingOnUpdate é o inverso: alterável em um registro existente, não configurável no início. Ambos também tornam a coluna legível, como Writing, e ambos existem ativados TablePermissions para um provedor seu. Eles espelham as permissões separadas de criação e atualização concedidas pela segurança de campo do Dataverse:
builder.Services.AddTableAccess("incident", t => t
.ForSignedInUsers()
.RowsOwnedByContact("customerid")
.Allow(TableSecurityPermission.Read | TableSecurityPermission.Create | TableSecurityPermission.Write)
.Reading("ticketnumber", "statuscode")
.Writing("title", "description") // Definido quando criado, alterável depois
.WritingOnCreate("prioritycode") // definido quando criado, somente leitura após
.WritingOnUpdate("resolution")); // Pode ser trocado depois, não definido no início
O framework impõe o resultado em cada caminho que os dados cruzam a fronteira do servidor:
- Leituras — uma coluna retida é omitida completamente do registro ou retornada com seu valor mascarado, e é oculta dos metadados das tabelas e das colunas da grade, de modo que um formulário nunca renderiza um editor vinculado a um valor que nunca chegará.
- Consultas — uma leitura nunca é rejeitada por permissões de coluna. Visualizações são rotineiramente compartilhadas entre audiências, e um usuário que vê uma coluna que outro não pode não pode se tornar um erro que obrigue você a manter uma visualização separada por audiência. Toda referência a uma coluna retida é descartada: projeções, ordenações, condições de filtro (junto com o filtro quando ela está vazia) e agregados ou agrupamentos por conteúdo. Eliminar uma condição amplia o conjunto de resultados, mas apenas entre linhas que o usuário já pode ler — a visibilidade das linhas vem dos filtros de consulta dos provedores, que são adicionados após essa passagem e nunca são tocados por eles.
- Escritas — ao contrário das leituras, uma criação ou atualização que altera uma coluna que o usuário não pode escrever é rejeitada em vez de silenciosamente cortada: um save rejeitado é a única forma de avisar ao usuário que sua edição não foi executada, enquanto uma leitura restringida é invisível e inofensiva. Um criar é julgado com base no que pode ser definido na criação, e uma atualização em relação ao que pode ser alterado — tanto por um único save quanto por um lote.
Nota
A coluna principal de id de uma tabela nunca é retida, seja lá o que o conjunto de leitura disser. O id é identidade, não dado — ele já está na URL que o chamador usou para acessar o registro — e chaves de linha, links de edição, relacionamentos de subgrade e exclusão todos se desfazem sem isso. Uma lista de permissão que nomeia as colunas que uma página precisa e esquece o id desmontaria o portal sem proteger nada.
Como os Componentes se Comportam
Os componentes do framework honram as permissões das colunas sozinhos — você não conecta nada. Uma coluna que o usuário não pode escrever é renderizada somente leitura e desativada onde quer que apareça: todo editor a pega por meio da resolução compartilhada de somente leitura, e a grade não dá à célula um editor inline, então ele volta ao texto de exibição simples. Um sistema fornecido readOnly="false" pelo consumidor não o substitui — o servidor rejeitaria o save de qualquer forma.
Uma coluna que o usuário pode não ler — uma mascarada — é renderizada em <ColumnDisplayValue> vez de um editor, em ambas as pilhas. Isso não é apenas uma gentileza: uma coluna mascarada não tem valor algum, apenas a máscara como seu valor formatado , então um editor vinculado a ela ficaria em branco; ColumnDisplayValue lê o valor formatado e assim mostra o marcador de lugar qual for o tipo da coluna. Colunas retidas por omissão nunca chegam a um componente — elas não estão no registro e os metadados da tabela não as listam.
O que o cliente recebe
Permissões de coluna vêm como um ColumnPermissions mapa — nome lógico de coluna para um ColumnSecurityPermission par de flags — para as colunas cujo acesso é restringido, o que Write significa que a coluna pode ser alterada em um registro existente. Qualquer coisa ausente tem acesso total, então uma tabela irrestrita não carrega nada. Um registro recuperado sozinho carrega o mapa; registros recuperados juntos compartilham um mapa, enviado uma vez na resposta, já que as permissões pertencem ao usuário e à tabela em vez de uma linha — os clientes de ambas as pilhas colocam isso de volta em cada registro, então ler as permissões de um registro funciona da mesma forma em ambos os casos. Respostas em grade também as reportam por coluna em ResolvedColumn.Permissions. Um registro sendo criado ainda não tem mapa próprio, então o endpoint de permissões de tabela também retorna NewRecordColumnPermissions — a mesma forma, julgada pelo que pode ser definido na criação — e ambas as RecordContext pilhas colocam isso em um novo registro para você. Colunas retidas por omissão estão deliberadamente ausentes de todos os mapas — omissão existe para que nada na resposta revele a coluna, e nomeá-la ali desfaria isso. Leia você mesmo através record.CanReadColumn(column) de / record.CanWriteColumn(column) em Blazor, ou os correspondentes canReadColumn / canWriteColumn ajudantes em @powerportalspro/core React.
Omissão ou Mascaramento
Por padrão, uma coluna retida é mascarada: o registro não carrega valor para ela e SecurityOptions.WithheldColumnMask (***** por padrão) como seu valor formatado, então um formulário ou grade ainda renderiza o campo e deixa claro que o valor está retido em vez de em branco. A máscara é a mesma independentemente de a coluna ter valor ou não, então ela também esconde isso. Defina SecurityOptions.WithheldColumnBehavior para Omit eliminar completamente a coluna do registro, como se a consulta nunca tivesse pedido, de modo que nada na resposta revela que a coluna existe — a escolha certa quando a própria existência da coluna é sensível. Reter uma coluna também retende qualquer coluna que a republique — o nome acompanhante de uma consulta, o gêmeo de moeda base de uma coluna de dinheiro — então uma lista de negação que nomeia uma coluna não pode ser contornada por outra. Uma única tabela pode sobrescrever a escolha do site em todo o local através TableColumnPermissions.WithheldColumnBehaviorde .
builder.Services.Configure<SecurityOptions>(options =>
{
options.WithheldColumnBehavior = WithheldColumnBehavior.Mask;
options.WithheldColumnMask = "*****";
});
Nota
Pareie uma coluna mascarada com uma negação de escrita. Uma coluna mascarada não tem valor no registro, então um chamador que posta um registro carregado inteiro de volta não envia nada por ele — mas o framework aceita um payload salvo pelo valor nominal, então uma solicitação que define a coluna, incluindo a máscara, escreve sobre o valor armazenado se a coluna for gravável. A negação de escrita é o que a recusa.
Colunas que republicam outra coluna
Reter uma coluna também retém qualquer coluna que tenha o mesmo valor sob um segundo nome. O Dataverse cria para você: uma consulta …name e …yominame parceiros, o rótulo de uma escolha e um gêmeo de moeda base para cada coluna de moeda — creditlimit_base junto com creditlimit. São colunas comuns, então uma nomenclatura creditlimit de lista de negação não diz nada sobre creditlimit_base, e sem isso um valor mascarado voltaria diretamente por seu gêmeo. O link vem dos próprios metadados da plataforma, e não de uma regra de nomeação, então ele vale para suas próprias colunas também, e se aplica em leituras, em uma consulta que nomeia explicitamente o companheiro, em metadados de tabelas, em colunas de grade e em saves.
Boa prática — filtre na fonte
Um caso que o framework não consegue ver para você: uma coluna calculada ou rollup, e as colunas compostas da plataforma —
fullname,yomifullname,address1_compositee seus irmãos. O Dataverse não informa quais colunas as alimentam, então reterlastnamenãofullnameretende , e uma coluna calculada sobre uma coluna que você negou ainda mostrará seu resultado. Nomei-as você mesmo no conjunto de leitura quando suas entradas forem sensíveis. Uma lista de permissões (ColumnPermissions.Allow(…)) evita o problema completamente, já que nada é legível até que você a nomeie — que é o padrão melhor para uma tabela contendo qualquer coisa que valha a pena reter.
Nota
Quando vários provedores cobrem uma tabela, seus conjuntos de colunas compõem da mesma forma que suas operações: aditivamente entre papéis, e sob
MultiplePermissionHandlersBehaviordentro de um mesmo grupo. Um provedor que existe apenas para escopar linhas e não diz nada sobre colunas é completamente deixado de fora da dobra da coluna, então ele não amplia nem restringe o que os outros concederam.
Dataverse-Native Security (Prévia)
O modelo de segurança recomendado para o futuro permite que o próprio Dataverse impeç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 funções web e permissões de tabela do Power Pages, que a plataforma transforma em papéis de segurança do Dataverse que o Dataverse aplica em cada solicitação. 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: configure papéis web e permissões de tabelas — no app Power Pages Management, ou na página Web Roles do seu portal da solução Power Portals Pro Enhanced Authorization — e atribua funções web aos contatos (através do app de Gerenciamento, programaticamente ou automaticamente para novos usuários com regras naquela mesma página). 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 a papéis 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 atritos. Note que mudanças de papéis e privilégios podem levar de alguns segundos a alguns minutos para se propagar pelos caches do Dataverse antes de entrarem em vigor.
Siga o guia passo a passo de configuração
Associando um Login Externo a um Contato
Quando alguém faz login com a Microsoft, Google ou outro provedor externo, o portal precisa descobrir a qual contato existente — se houver — esse login pertence. Ele resolve isso em duas etapas:
- Um link existente. Se a conta já fez login antes, uma
adx_externalidentitylinha já a vincula a um contato. Essa é uma declaração explícita e armazenada "esta conta é esta pessoa", então ela sempre vence e nunca é afetada pelas configurações abaixo. - O recurso de e-mail alternativo. Ainda sem link, o portal corresponde ao endereço de e-mail fornecido pelo provedor com as colunas do contato. Essa é a etapa que você pode configurar — e a etapa que decide se um cliente retornado entra no contato atual ou recebe uma duplicata.
Por padrão, duas colunas são pesquisadas, primeiro nome de usuário do portal. Contatos criados pelo portal carregam adx_identity_username, enquanto os contatos importados ou digitados pela equipe geralmente têm apenas emailaddress1 — pesquisar ambos é o que permite que um cliente existente faça login e fique em seu próprio registro, em vez de um novo:
builder.Services.Configure<SecurityOptions>(options =>
{
// The default: portal username first, then the primary email column.
options.ContactMatching.ExternalLoginEmailColumns =
[
ContactEmailColumn.IdentityUsername,
ContactEmailColumn.EmailAddress1,
];
});
A ordem importa: é a precedência, não apenas uma lista. As colunas são pesquisadas juntas em uma única consulta, e a primeira coluna da sua lista que corresponde a qualquer coisa decide o vencedor — então, se um contato tem o endereço como nome de usuário do portal e outro o mantiver em emailaddress1, a correspondência de nome de usuário vence na ordem padrão.
Liste menos colunas para o restrito de correspondência: [ContactEmailColumn.IdentityUsername] restringe a contas criadas pelo portal, e uma lista vazia desativa completamente o recurso de e-mail, deixando um link de identidade externa existente como a única forma de uma conta resolver para um contato. Adicione ContactEmailColumn.EmailAddress2 e ContactEmailColumn.EmailAddress3 amplie — elas são deixadas de fora do padrão deliberadamente, pelo motivo abaixo.
Boa prática — filtre na fonte
Cada coluna que você lista é uma forma de que quem controla um endereço de e-mail seja reconhecido como o contato que o detém. Liste apenas colunas que seu processo de entrada de dados trate como pertencentes ao próprio contato — se
emailaddress2for onde sua equipe arquiva o endereço de um assistente ou uma caixa de entrada compartilhada da empresa, listá-la permite que quem ler essa caixa de entrada entre como esse contato.
Boa prática — filtre na fonte
A correspondência faz do endereço de e-mail um identificador, não apenas um rótulo — ele decide qual contato existente um login se torna. Assim, o portal só pula o e-mail de confirmação quando o provedor de identidade realmente verificou exatamente esse endereço. Se o endereço foi digitado sobre o fornecido pelo provedor, ou o provedor nunca verificou, um link de confirmação é enviado e o login externo não é associado ao contato até que esse link seja seguido.
Quais logins contam como verificados não são propriedade do provedor. O Google verifica todos os endereços que ele afirma. A Microsoft não: uma conta pessoal da Microsoft verificou seu endereço, mas uma conta de trabalho ou escola do Entra fornece o que estiver no atributo do
verified_primary_emailouverified_secondary_emailreivindicação o lista, ouxms_edovdiz que seu domínio pertence ao próprio locatário do usuário. O Entra envia essas reivindicações apenas quando o registro do aplicativo as solicita (veja Configuração de Login do ID do Entra); sem elas, os logins no trabalho e na escola confirmam por e-mail na primeira vez.
Nota
Essas configurações se aplicam apenas ao login externo. O login, registro e redefinição de senha locais, continuam a coincidir
adx_identity_usernamesozinhos, deliberadamente — o fluxo de redefinição de senha envia seu link para o endereço inserido, então combinar colunas secundárias de e-mail ali permitiria que alguém redefinisse a senha de outra pessoa.
Permitindo apenas que contatos existentes façam login
Por padrão, qualquer pessoa pode se tornar um usuário do portal: registrar-se ou fazer login com um provedor externo sob um endereço que nenhum contato possui, cria um contato. Quando o portal é para pessoas que você já possui no Dataverse — clientes, membros, parceiros — desative o cadastro, e somente eles podem fazer login:
builder.Services.Configure<AuthClientOptions>(options => options.SignUpEnabled = false);
A página de registro e seus links desaparecem, e o servidor se recusa a criar um usuário conforme solicitado. Um login externo ainda encontra seu contato conforme descrito acima e faz links para ele; um que não corresponde a ninguém é recusado em vez de criar um contato. Contas locais não são afetadas para quem já tem um.
Nota
Um login recusado só é explicado na tela quando o provedor verificou exatamente esse endereço, porque então a pessoa que faz login já provou que é a proprietária. Para qualquer outro endereço, o portal responde exatamente como para um endereço que tem um contato — "verifique seu e-mail" — e envia o endereço com um aviso curto informando que não está registrado. Avisar diretamente o visitante permitiria que qualquer pessoa digitasse endereços no formulário para descobrir quem tem uma conta.
O aviso é enviado pelo próprio remetente de e-mail do portal. Um host que o substitua por sua própria pilha de e-mails também deve registrar um
ISignUpRefusalEmailSender; sem um, a recusa é registrada e nenhum aviso é enviado.
Classes Base Integradas
A estrutura fornece classes base para simplificar padrões comuns de segurança:
AnonymousPermissionHandler— concede uma tabela a todos, tenham ou não sido registrados, incluindo todas as colunas. É o único lugar onde o padrão opt-in é dispensado, porque dela deriva a decisão de publicar a tabela. SubstituirGetPermissionsAsyncpara publicar menos.
Referência API
AuthorizePortalRoleAttribute Classe
Propriedades
Nome | Tipo | Padrão | Descrição |
|---|---|---|---|
AuthenticationSchemes | string? | ||
Policy | string? | ||
RoleNames | IReadOnlyList<string> | Os nomes dos papéis que esse atributo exige, qualquer um dos quais o satisfaz. | |
Roles | string? | ||
TypeId | Object |
AuthenticationSchemesPolicyRoleNamesRolesTypeIdMétodos
Nome | Parâmetros | Tipo | Descrição |
|---|---|---|---|
IsDefaultAttribute | bool | ||
Match | Object obj | bool |
IsDefaultAttributeMatchITablePermissionProvider Interface
Propriedades
Nome | Tipo | Padrão | Descrição |
|---|---|---|---|
Table | string | Nome lógico da tabela à qual esse provedor se aplica. Correspondeu exatamente, então deve ser o Nome lógico do Dataverse em minúsculas. |
TableMétodos
Nome | Parâmetros | Tipo | Descrição |
|---|---|---|---|
GetPermissionsAsync | UserInfo user | Task<TablePermissions> | O que user pode fazer com esta tabela e quais de suas colunas podem ser lidas e escrever. Retorno TablePermissions.None para um usuário que não deveria ter acesso. TableSecurityPermission.Read se o usuário puder ver alguma das tabelas linhas e escopo quais estão em Services.TableQueryContext). TableSecurityPermission.Read Sozinho expõe apenas o ID do registro. Use Reading(System.String[]) e Writing(System.String[]) nomeá-los, ou TablePermissions.AllColumns para optar por não usar a segurança de colunas para esta tabela. |
GetQueryFilterAsync | TableQueryContext context | Task<TableQueryFilter> | Escopos sobre quais linhas dessa tabela o usuário pode agir: um filtro construído com Query.LogicalOperator), TableQueryFilter.AllRows para todo reme conforme as operações permitirem, ou nullsem linhas. null — ou um filtro sem condições — não concede nenhuma, então Esquecer de colocar o escopo de uma tabela nega isso em vez de publicar todas as linhas. Por exemplo, TableQueryFilter.AllRows Quando cada briga é o que você quer dizer. TableQueryContext.Operation distingue. Também executa sob ambos os modelos de segurança, então permanece em vigor quando a segurança nativa do Dataverse Ignora Services.UserInfo). PortalRoleOptions.CompositionBehavior para RoleCompositionBehavior.Restrictive para cruzar papéis também. null responde não deixa linhas no grupo, e TableQueryFilter.AllRows deixa os filtros dos outros provedores decidir. De lado para o outro lado grupos, um grupo concede TableQueryFilter.AllRows amplia o aditivo padrão união a cada linha — então o filtro de um papel só significa algo quando os outros grupos concedem Esse filtro de mesa também. |
GetPermissionsAsyncuser pode fazer com esta tabela e quais de suas colunas podem ser lidas e escrever. Retorno TablePermissions.None para um usuário que não deveria ter acesso. TableSecurityPermission.Read se o usuário puder ver alguma das tabelas linhas e escopo quais estão em Services.TableQueryContext). TableSecurityPermission.Read Sozinho expõe apenas o ID do registro. Use Reading(System.String[]) e Writing(System.String[]) nomeá-los, ou TablePermissions.AllColumns para optar por não usar a segurança de colunas para esta tabela. GetQueryFilterAsyncQuery.LogicalOperator), TableQueryFilter.AllRows para todo reme conforme as operações permitirem, ou nullsem linhas. null — ou um filtro sem condições — não concede nenhuma, então Esquecer de colocar o escopo de uma tabela nega isso em vez de publicar todas as linhas. Por exemplo, TableQueryFilter.AllRows Quando cada briga é o que você quer dizer. TableQueryContext.Operation distingue. Também executa sob ambos os modelos de segurança, então permanece em vigor quando a segurança nativa do Dataverse Ignora Services.UserInfo). PortalRoleOptions.CompositionBehavior para RoleCompositionBehavior.Restrictive para cruzar papéis também. null responde não deixa linhas no grupo, e TableQueryFilter.AllRows deixa os filtros dos outros provedores decidir. De lado para o outro lado grupos, um grupo concede TableQueryFilter.AllRows amplia o aditivo padrão união a cada linha — então o filtro de um papel só significa algo quando os outros grupos concedem Esse filtro de mesa também. TablePermissions Classe
Propriedades
Nome | Tipo | Padrão | Descrição |
|---|---|---|---|
Columns | TableColumnPermissions | As colunas que o usuário pode ler e escrever. TableColumnPermissions.None a menos que Algo chamado eles: Acesso a colunas é concedido, nunca assumido. | |
Operations | TableSecurityPermission | As operações que o usuário pode realizar na tabela são um valor de flags. Isso é um nível de tabela gate, não um filtro de linha — inclua TableSecurityPermission.Read se o usuário puder veja qualquer uma das linhas da tabela e faça o escopo de quais passam por Services.TableQueryContext). |
ColumnsTableColumnPermissions.None a menos que Algo chamado eles: Acesso a colunas é concedido, nunca assumido.OperationsTableSecurityPermission.Read se o usuário puder veja qualquer uma das linhas da tabela e faça o escopo de quais passam por Services.TableQueryContext).Métodos
Nome | Parâmetros | Tipo | Descrição |
|---|---|---|---|
AllColumns | TablePermissions | Opta por não participar totalmente da segurança em nível de coluna — toda coluna legível e gravável. O Comportamento pré-segurança na coluna, detalhado em vez de herdado. | |
Allow | TableSecurityPermission operations | TablePermissions | Isso acrescenta operations ao que esse valor já concede. |
Allowing | TableSecurityPermission operations | TablePermissions | Concessões operations e sem colunas. Corrente Reading(System.String[]), Writing(System.String[]) Ou TablePermissions.ReadingAllColumns para abrir colunas para cima. |
Reading | string[] columns | TablePermissions | Permite a leitura das colunas nomeadas, além de qualquer já permitida. Indistinto a maiúsculas minúsculas, Então a grafia do portal do criador funciona tão bem quanto o nome lógico. |
ReadingAllColumns | TablePermissions | Opta por não usar segurança de leitura em nível de coluna para esta tabela: todas as colunas, incluindo as adicionadas para a mesa depois. Deliberadamente explícito — o arcabouço não vai assumir isso. | |
ReadingAllColumnsExcept | string[] columns | TablePermissions | Opta por não usar segurança de leitura em nível de coluna, exceto para as colunas nomeadas — todas as colunas, exceto Essas, incluindo as adicionadas depois. Não abre para nada novo, então prefiro Reading(System.String[]) em tabelas que contêm dados sensíveis. |
ReadingAndWriting | string[] columns | TablePermissions | Permite tanto a leitura quanto a escrita das colunas nomeadas. |
WithColumns | TableColumnPermissions columns | TablePermissions | Substitui as regras da coluna no atacado, para um provedor que prefere construir um Services.TableColumnPermissions ela mesma que encadear os métodos fluentes. |
Withholding | WithheldColumnBehavior behavior | TablePermissions | Como as colunas retidas dessa tabela voltam — omitidas, ou apresentam um valor mascarado. Anula o site para SecurityOptions.WithheldColumnBehavior toda a tabela desta tabela. |
Writing | string[] columns | TablePermissions | Permite escrever as colunas nomeadas — quando um registro é criado e quando ele é atualizado — em além de qualquer que já seja permitido. Escrever uma coluna não implica lê-la — nomei-a Reading(System.String[]) também se o usuário deve ver o que escreveu. |
WritingAllColumns | TablePermissions | Opta por não usar segurança de escrita em nível de coluna para esta tabela: o usuário pode escrever qualquer coluna, ao criar e ao atualizar. Substitui ambos os conjuntos de escrita, então chame WritingOnCreate(System.String[]) / WritingOnUpdate(System.String[]) Depois disso, não antes. | |
WritingAllColumnsExcept | string[] columns | TablePermissions | Opta por não usar segurança de escrita em nível de coluna, exceto nas colunas nomeadas, que permanecem somente leitura. Substitui ambos os conjuntos de escrita, então chame WritingOnCreate(System.String[]) / WritingOnUpdate(System.String[]) Depois disso, não antes. |
WritingOnCreate | string[] columns | TablePermissions | Permite definir as colunas nomeadas quando um registro é criado, e somente então: uma vez que ele exista Eles são somente leitura, mesmo que uma chamada anterior — Writing(System.String[]), TablePermissions.WritingAllColumns — os tornava escritos. A prioridade de um caso, escolhido quando é registrado e corrigido a partir daí. Writing(System.String[]), não implica leitura. |
WritingOnUpdate | string[] columns | TablePermissions | Permite alterar as colunas nomeadas em um registro existente, mas não defini-las quando está criados, mesmo que uma chamada anterior os tornasse graváveis — notas de resolução que só significam algo quando o caso existir. Writing(System.String[]), não implica leitura. |
AllColumnsAllowoperations ao que esse valor já concede.Allowingoperations e sem colunas. Corrente Reading(System.String[]), Writing(System.String[]) Ou TablePermissions.ReadingAllColumns para abrir colunas para cima.ReadingReadingAllColumnsReadingAllColumnsExceptReading(System.String[]) em tabelas que contêm dados sensíveis.ReadingAndWritingWithColumnsServices.TableColumnPermissions ela mesma que encadear os métodos fluentes.WithholdingSecurityOptions.WithheldColumnBehavior toda a tabela desta tabela.WritingReading(System.String[]) também se o usuário deve ver o que escreveu.WritingAllColumnsWritingOnCreate(System.String[]) / WritingOnUpdate(System.String[]) Depois disso, não antes.WritingAllColumnsExceptWritingOnCreate(System.String[]) / WritingOnUpdate(System.String[]) Depois disso, não antes.WritingOnCreateWriting(System.String[]), TablePermissions.WritingAllColumns — os tornava escritos. A prioridade de um caso, escolhido quando é registrado e corrigido a partir daí. Writing(System.String[]), não implica leitura.WritingOnUpdateWriting(System.String[]), não implica leitura.AuthClientOptions Classe
Propriedades
Nome | Tipo | Padrão | Descrição |
|---|---|---|---|
LocalAccountsEnabled | bool | True | Se o portal aceita contas locais de usuário/senha. |
SignUpEnabled | bool | True | Se fazer login pode criar um usuário do portal. |
LocalAccountsEnabledSignUpEnabled