in src/Core/Authorization/AuthorizationResolver.cs [260:389]
private void SetEntityPermissionMap(RuntimeConfig runtimeConfig)
{
foreach ((string entityName, Entity entity) in runtimeConfig.Entities)
{
EntityMetadata entityToRoleMap = new();
bool isStoredProcedureEntity = entity.Source.Type is EntitySourceType.StoredProcedure;
if (isStoredProcedureEntity)
{
SupportedHttpVerb[] methods;
if (entity.Rest.Methods is not null)
{
methods = entity.Rest.Methods;
}
else
{
methods = (entity.Rest.Enabled) ? new SupportedHttpVerb[] { SupportedHttpVerb.Post } : Array.Empty<SupportedHttpVerb>();
}
entityToRoleMap.StoredProcedureHttpVerbs = new(methods);
}
// Store the allowedColumns for anonymous role.
// In case the authenticated role is not defined on the entity,
// this will help in copying over permissions from anonymous role to authenticated role.
HashSet<string> allowedColumnsForAnonymousRole = new();
string dataSourceName = runtimeConfig.GetDataSourceNameFromEntityName(entityName);
ISqlMetadataProvider metadataProvider = _metadataProviderFactory.GetMetadataProvider(dataSourceName);
foreach (EntityPermission permission in entity.Permissions)
{
string role = permission.Role;
RoleMetadata roleToOperation = new();
EntityAction[] entityActions = permission.Actions;
foreach (EntityAction entityAction in entityActions)
{
EntityActionOperation operation = entityAction.Action;
OperationMetadata operationToColumn = new();
// Use a HashSet to store all the backing field names
// that are accessible to the user.
HashSet<string> allowedColumns = new();
IEnumerable<string> allTableColumns = ResolveEntityDefinitionColumns(entityName, metadataProvider);
if (entityAction.Fields is null)
{
operationToColumn.Included.UnionWith(ResolveEntityDefinitionColumns(entityName, metadataProvider));
}
else
{
// When a wildcard (*) is defined for Included columns, all of the table's
// columns must be resolved and placed in the operationToColumn Key/Value store.
// This is especially relevant for find requests, where actual column names must be
// resolved when no columns were included in a request.
if (entityAction.Fields.Include is null ||
(entityAction.Fields.Include.Count == 1 && entityAction.Fields.Include.Contains(WILDCARD)))
{
operationToColumn.Included.UnionWith(ResolveEntityDefinitionColumns(entityName, metadataProvider));
}
else
{
operationToColumn.Included = entityAction.Fields.Include;
}
// When a wildcard (*) is defined for Excluded columns, all of the table's
// columns must be resolved and placed in the operationToColumn Key/Value store.
if (entityAction.Fields.Exclude is null ||
(entityAction.Fields.Exclude.Count == 1 && entityAction.Fields.Exclude.Contains(WILDCARD)))
{
operationToColumn.Excluded.UnionWith(ResolveEntityDefinitionColumns(entityName, metadataProvider));
}
else
{
operationToColumn.Excluded = entityAction.Fields.Exclude;
}
}
if (entityAction.Policy is not null && entityAction.Policy.Database is not null)
{
operationToColumn.DatabasePolicy = entityAction.Policy.Database;
}
// Calculate the set of allowed backing column names.
allowedColumns.UnionWith(operationToColumn.Included.Except(operationToColumn.Excluded));
// Populate allowed exposed columns for each entity/role/operation combination during startup,
// so that it doesn't need to be evaluated per request.
PopulateAllowedExposedColumns(operationToColumn.AllowedExposedColumns, entityName, allowedColumns, metadataProvider);
IEnumerable<EntityActionOperation> operations = GetAllOperationsForObjectType(operation, entity.Source.Type);
foreach (EntityActionOperation crudOperation in operations)
{
// Try to add the opElement to the map if not present.
// Builds up mapping: i.e. Operation.Create permitted in {Role1, Role2, ..., RoleN}
if (!entityToRoleMap.OperationToRolesMap.TryAdd(crudOperation, new List<string>(new string[] { role })))
{
entityToRoleMap.OperationToRolesMap[crudOperation].Add(role);
}
foreach (string allowedColumn in allowedColumns)
{
entityToRoleMap.FieldToRolesMap.TryAdd(key: allowedColumn, CreateOperationToRoleMap(entity.Source.Type));
entityToRoleMap.FieldToRolesMap[allowedColumn][crudOperation].Add(role);
}
roleToOperation.OperationToColumnMap[crudOperation] = operationToColumn;
}
if (ROLE_ANONYMOUS.Equals(role, StringComparison.OrdinalIgnoreCase))
{
// Saving the allowed columns for anonymous role in case we need to copy the
// allowed columns for authenticated role. This reduces the time complexity
// for copying over permissions to authenticated role from anonymous role.
allowedColumnsForAnonymousRole = allowedColumns;
}
}
entityToRoleMap.RoleToOperationMap[role] = roleToOperation;
}
// Check if anonymous role is defined but authenticated is not. If that is the case,
// then the authenticated role derives permissions that are atleast equal to anonymous role.
if (entityToRoleMap.RoleToOperationMap.ContainsKey(ROLE_ANONYMOUS) &&
!entityToRoleMap.RoleToOperationMap.ContainsKey(ROLE_AUTHENTICATED))
{
CopyOverPermissionsFromAnonymousToAuthenticatedRole(entityToRoleMap, allowedColumnsForAnonymousRole);
}
EntityPermissionsMap[entityName] = entityToRoleMap;
}
}