Seguridad
PowerPortalsPro ofrece un modelo de seguridad flexible y basado en código que te permite controlar el acceso tanto a nivel de tabla como a nivel de registro. La seguridad se implementa creando clases de manejadores de permisos y registrándolas en el contenedor de inyección de dependencias.
Resumen
Hay dos interfaces que puedes implementar dependiendo del nivel de control que necesites:
ITablePermissionHandler— Controla si un usuario puede realizar operaciones de Crear, Leer, Actualizar, Eliminar, Añadir o Añadir a una tabla determinada. Las decisiones se basan únicamente en la identidad del usuario.ITableRecordPermissionHandler— ExtiendeITablePermissionHandlercon sobrecargas a nivel de registro que reciben el procedimiento realTableRecord, permitiendo reglas detalladas como "los usuarios solo pueden editar sus propios registros."
Propina
Si solo necesitas comprobaciones a nivel de tabla (por ejemplo, "los usuarios autenticados pueden leer esta tabla"), usa
ITablePermissionHandler. Si necesitas lógica a nivel de registro (por ejemplo, "los usuarios solo pueden eliminar registros que poseen"), usaITableRecordPermissionHandler.
Buenas prácticas — filtrar en la fuente
Los manejadores de permisos hacen cumplir el acceso, pero no sustituyen el filtrado. Los gestores a nivel de registro se ejecutan después de que se recupere una página de registros de Dataverse, por lo que depender de ellos para ocultar filas distorsiona la paginación de la cuadrícula (una página de 50 puede mostrar menos) y el recuento total, y desperdicia consultas recuperando filas que luego se descartan. Siempre que los datos deban asignarse al usuario actual, filtra a nivel de consulta — con una
IFetchXmlQueryFilterInterceptorvista (ver más abajo) o personalizada que tenga en cuenta al usuario actual — para que Dataverse devuelva solo las filas que el usuario debería ver y cuadrícula la página correctamente. Mantén a los responsables de permisos como garantía de cumplimiento.
Dataverse-Native Security (Vista previa)
El modelo de seguridad recomendado para el futuro permite que Dataverse imponga la seguridad para los usuarios de tu portal, basado en la función unificada de seguridad Dataverse de Power Pages. En lugar de escribir manejadores de permisos en código, gestionas el acceso con roles de seguridad estándar de Dataverse — los mismos roles, privilegios y herramientas administrativas que se usan para los usuarios internos. El modelo está actualmente en vista previa — tanto la función de Microsoft como el soporte de Power Portals Pro — y Power Portals Pro seguirá el calendario de lanzamientos de Microsoft hasta la disponibilidad general.
Cuando está habilitado, PowerPortalsPro provisiona automáticamente un sistema gestionado por el sistema (C2) systemuser para cada contacto del portal — al registrarse para nuevos contactos y de forma perezosa al iniciar sesión para los existentes — junto con el powerpagesusermapping registro que vincula el contacto, el usuario del sistema y tu sitio Power Pages. Cada llamada de Dataverse para la sesión se hace pasar por ese usuario del sistema, por lo que los roles de seguridad que Power Pages sincroniza desde tus permisos web de roles y tablas gobiernan exactamente lo que el usuario puede ver y hacer. La sesión lleva un PortalUserType de ContactStubUser para que tu interfaz siempre pueda distinguir a estos usuarios provisionados de los usuarios internos reales.
Nota
Para usar este modelo de seguridad debes crear un sitio web de Power Pages en tu entorno Dataverse — esto es lo que instala los componentes del framework de Microsoft y te permite activar la función de seguridad unificada. No necesitas un plan de pago de PowerPages, y puedes eliminar la web inmediatamente después de instalar y activar la función. El registro del sitio de Power Pages (
powerpagesite) al quePowerPagesSiteIdse refiere debe existir en el entorno, ya que los mapeos de usuario y la sincronización de seguridad-rol se resuelven en su contra.
Habilitación de la seguridad nativa de Dataverse
Activa el modelo desde SecurityOptions el Program.csservidor , proporcionando el id del registro de tu sitio de Power Pages:
builder.Services.Configure<SecurityOptions>(options =>
{
options.DataverseSecurity.ProvisionSystemUsers = true;
options.DataverseSecurity.PowerPagesSiteId = new Guid("<your powerpagesite id>");
});
La configuración se valida al iniciar la aplicación: cuando ProvisionSystemUsers está habilitada, PowerPagesSiteId debe establecerse y debe resolverse a un registro existente powerpagesite , de lo contrario la aplicación no arranca con guía.
Concede acceso exactamente como lo hace Power Pages: configura los roles web y permisos de las tablas en la app Power Pages Management, y asigna roles web a los contactos (a través de la app Management o de forma programática). La plataforma sincroniza esos roles de seguridad generados automáticamente (llamados PowerPages_*) y los asigna automáticamente como cambios de membresía en el rol web — como la configuración está completamente en Power Pages, tu portal puede moverse hacia o desde un sitio real de Power Pages sin fricciones. Ten en cuenta que los cambios de roles y privilegios pueden tardar desde unos segundos hasta un par de minutos en propagarse a través de las cachés de Dataverse antes de hacer efecto.
Sigue la guía paso a paso de configuración
Empezando
1. Crear un gestor de permisos
Crea una clase que implemente ITablePermissionHandler o ITableRecordPermissionHandler. Establece la Table propiedad en el nombre lógico de la tabla Dataverse a la que se aplica el manejador e implementa cada método de permisos.
Aquí tienes un ejemplo sencillo que otorga acceso completo a todos los usuarios para una tabla 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. Registrar en la inyección de dependencias
Registra tu manejador en el contenedor DI del archivo de Program.cs tu proyecto (o en un método de extensión de colección de servicios):
builder.Services.AddSingleton<ITablePermissionHandler, MyTablePermissionHandler>();
Dado que los manejadores se resuelven desde el contenedor DI, puedes inyectar cualquier servicio requerido (como un contexto de base de datos o un servicio de usuario) mediante inyección de constructores.
Seguridad a nivel de récord
Para escenarios en los que el acceso depende del registro específico en el que se está operando, implementa ITableRecordPermissionHandler. Esta interfaz se extiende ITablePermissionHandler con sobrecargas que reciben el TableRecord.
El siguiente ejemplo permite a todos los usuarios leer registros globalmente, pero restringe las operaciones de escritura a los registros propiedad del usuario actual:
public class ContactPermissionHandler : ITableRecordPermissionHandler
{
public string Table => "contact";
public List<string> RequiredColumns => [];
// A nivel de mesa: permitir el acceso de forma amplia
public Task<bool> CanReadAsync(Guid? userId) => Task.FromResult(true);
public Task<bool> CanCreateAsync(Guid? userId)
=> Task.FromResult(userId != null && userId != Guid.Empty);
// Nivel de registro: restringir las escrituras al propietario del 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
La
CanUpdateAsyncsobrecarga a nivel de registro proporciona tanto al registro las actualizaciones propuestas (recordWithUpdates) como al registro en su estado persistido actual (currentRecord), lo que permite comparar valores o validar cambios específicos en el campo.
Columnas requeridas
La RequiredColumns propiedad especifica qué columnas deben recuperarse para que el manejador evalúe permisos a nivel de registro. El framework añade automáticamente estas columnas a las consultas FetchXML cuando no están ya incluidas, asegurando que el gestor siempre tenga los datos que necesita — incluso si la vista o consulta no incluye esas columnas.
El siguiente ejemplo utiliza la ppp_owningportaluserid columna de búsqueda para restringir el acceso a registros propiedad del usuario actual:
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
Si
RequiredColumnsdevuelve una lista vacía y los métodos a nivel de registro del manejador solo usanrecord.Id, no se añaden columnas adicionales a la consulta. Laownerpropiedad siempre es proporcionada por la plataforma, independientemente deRequiredColumns.
Interceptores de filtro de consulta FetchXML
Además de la seguridad a nivel de tabla y registro, PowerPortalsPro soporta IFetchXmlQueryFilterInterceptor — una interfaz que permite modificar consultas FetchXML antes de que se ejecuten. Esto es útil para hacer cumplir el filtrado a nivel de fila, como asegurar que un usuario solo pueda ver los registros que le pertenecen a él o a su equipo.
IFetchXmlQueryFilterInterceptor extiende ITablePermissionHandler, por lo que cada interceptor también define permisos a nivel de tabla mediante la Table propiedad y los métodos de permisos estándar. El método del OnQueryAsync interceptor solo se llama para consultas que se dirigen a su tabla especificada.
A diferencia de los gestores de permisos a nivel de registro que evalúan cada registro tras la recuperación, los interceptores de filtro de consulta modifican la consulta antes de que llegue a Dataverse. Los registros filtrados nunca se recuperan, lo que mejora el rendimiento y la seguridad — y, lo que es importante, mantiene la paginación en cuadrícula y los conteos totales precisos, ya que el filtrado aplicado tras la recuperación dejaría las páginas cortas y contaría mal los totales.
1. Crear un interceptor de filtro de consulta
Implementa IFetchXmlQueryFilterInterceptor, establece la Table propiedad al nombre lógico de la tabla, implementa los métodos de permisos y usa el FetchXMLBuilder in OnQueryAsync para añadir filtros a la consulta. El FetchXmlParameters registro proporciona el archivo del usuario EntityReferenceactual
FetchXMLBuilder
El siguiente ejemplo restringe las consultas en la contact tabla para que solo devuelvan el registro que coincide con el usuario actual:
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. Registrar en la inyección de dependencias
Registra tu interceptor tanto como un IFetchXmlQueryFilterInterceptor como ITablePermissionHandler un para que se apliquen tanto el filtrado de consultas como los permisos a nivel de tabla. Se pueden registrar múltiples interceptores y cada uno se ejecutará para consultas dirigidas a su tabla.
builder.Services.AddTransient<ContactQueryFilterInterceptor>();
builder.Services.AddTransient<ITablePermissionHandler>(
sp => sp.GetRequiredService<ContactQueryFilterInterceptor>());
builder.Services.AddTransient<IFetchXmlQueryFilterInterceptor>(
sp => sp.GetRequiredService<ContactQueryFilterInterceptor>());
Propina
Porque
IFetchXmlQueryFilterInterceptorextiendeITablePermissionHandler, una sola clase gestiona tanto permisos a nivel de tabla como filtrado a nivel de consulta. ElOnQueryAsyncmétodo solo se invoca para consultas que coinciden con la propiedad delTableinterceptor, por lo que no es necesario comprobar el nombre de la tabla dentro del método.
Clases base integradas
El marco proporciona clases base para simplificar patrones de seguridad comunes:
AnonymousPermissionHandler— Concede todas las operaciones a todos los usuarios (incluidos los anónimos). Útil para tablas accesibles públicamente. Simplemente hereda y establece laTablepropiedad.
Referencia API
ITablePermissionHandler Interface
Propiedades
Nombre | Tipo | Default | Descripción |
|---|---|---|---|
Table | string | Nombre lógico de la tabla a la que se aplica este manejador de permisos. |
TableMétodos
Nombre | Parámetros | Tipo | Descripción |
|---|---|---|---|
CanAppendAsync | Guid? userId | Task<bool> | Método para determinar si un usuario debería poder añadir un registro. |
CanAppendToAsync | Guid? userId | Task<bool> | Método para determinar si un usuario debe poder añadir a un registro. |
CanCreateAsync | Guid? userId | Task<bool> | Método para determinar si un usuario debe ser capaz de crear un registro. |
CanDeleteAsync | Guid? userId | Task<bool> | Método para determinar si un usuario debería poder eliminar un registro. |
CanReadAsync | Guid? userId | Task<bool> | Método para determinar si un usuario debería ser capaz de leer un registro. |
CanUpdateAsync | Guid? userId | Task<bool> | Método para determinar si un usuario debe poder actualizar un registro. |
CanAppendAsyncCanAppendToAsyncCanCreateAsyncCanDeleteAsyncCanReadAsyncCanUpdateAsyncITableRecordPermissionHandler Interface
Propiedades
Nombre | Tipo | Default | Descripción |
|---|---|---|---|
RequiredColumns | List<string> | Lista de nombres lógicos de columnas que deben recuperarse para que este manejador pueda evaluar permisos a nivel de registro. | |
Table | string | Nombre lógico de la tabla a la que se aplica este manejador de permisos. |
RequiredColumnsTableMétodos
Nombre | Parámetros | Tipo | Descripción |
|---|---|---|---|
CanAppendAsync | Guid? userId TableRecord record | Task<bool> | Método para determinar si un usuario debe poder añadir el registro proporcionado. |
CanAppendToAsync | Guid? userId TableRecord record | Task<bool> | Método para determinar si un usuario debe poder añadir al registro proporcionado. |
CanCreateAsync | Guid? userId TableRecord record | Task<bool> | Método para determinar si un usuario debe poder crear el registro proporcionado. |
CanDeleteAsync | Guid? userId TableRecord record | Task<bool> | Método para determinar si un usuario debe poder eliminar el registro proporcionado. |
CanReadAsync | Guid? userId TableRecord record | Task<bool> | Método para determinar si un usuario debe poder leer el registro proporcionado. |
CanUpdateAsync | Guid? userId TableRecord recordWithUpdates TableRecord currentRecord | Task<bool> | Método para determinar si un usuario debe poder actualizar el registro proporcionado. |
CanAppendAsyncTableRecord record
CanAppendToAsyncTableRecord record
CanCreateAsyncTableRecord record
CanDeleteAsyncTableRecord record
CanReadAsyncTableRecord record
CanUpdateAsyncTableRecord recordWithUpdates
TableRecord currentRecord
IFetchXmlQueryFilterInterceptor Interface
Propiedades
Nombre | Tipo | Default | Descripción |
|---|---|---|---|
Table | string | Nombre lógico de la tabla a la que se aplica este manejador de permisos. |
TableMétodos
Nombre | Parámetros | Tipo | Descripción |
|---|---|---|---|
CanAppendAsync | Guid? userId | Task<bool> | Método para determinar si un usuario debería poder añadir un registro. |
CanAppendToAsync | Guid? userId | Task<bool> | Método para determinar si un usuario debe poder añadir a un registro. |
CanCreateAsync | Guid? userId | Task<bool> | Método para determinar si un usuario debe ser capaz de crear un registro. |
CanDeleteAsync | Guid? userId | Task<bool> | Método para determinar si un usuario debería poder eliminar un registro. |
CanReadAsync | Guid? userId | Task<bool> | Método para determinar si un usuario debería ser capaz de leer un registro. |
CanUpdateAsync | Guid? userId | Task<bool> | Método para determinar si un usuario debe poder actualizar un registro. |
OnQueryAsync | FetchXMLBuilder fetchXmlBuilder FetchXmlParameters parameters | Task<FetchXMLBuilder> | Se llama para permitir la modificación de una consulta FetchXML antes de que se ejecute. |
CanAppendAsyncCanAppendToAsyncCanCreateAsyncCanDeleteAsyncCanReadAsyncCanUpdateAsyncOnQueryAsyncFetchXmlParameters parameters
