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.
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. Esto significa que los registros filtrados nunca se recuperan, lo que mejora tanto el rendimiento como la seguridad.
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
