DynamicData Attribute Based Security - part 1 (original) (raw)
Articles in this Series
- Introduction - A DynamicData Attribute Based Permission Solution using User Roles.
- Part 1 - Permissions Attribute (Metadata) Classes.
- Part 2 - Sample Metadata for project.
- Part 3 - The Helper Extension Methods.
- Part 4 - Limit Tables shown on Default page and List, Edit & Details etc.
- Part 5 - Generate Columns/Rows (using IAutoFieldGenerator)
- Part 6 - Miscellaneous bits
- Part 7 - Updating the ListDetails Page
- DynamicData - Limit the Filter Fields
Permissions Attribute (Metadata) Classes
An attributes class inherits from System.Attribute and must have the following properties set, AttributeUsage with at least one of the 16 possible targets see below and also AllowMultiple set to true so that multiple occurrences of the attribute can decorate a class or property.
AttributeTargets
All | GenericParameter |
---|---|
Assembly | Interface |
Class | Method |
Constructor | Module |
Delegate | Parameter |
Enum | Property |
Event | ReturnValue |
Field | Struct |
The AttributeTargets options that the PermissionsAttributes classes will use are class and Property.
Listing 1 shows a simplified attribute that can be applied to a class multiple times see Listing 2 where the attribute is applied to the Orders table once each for "Accounts" and "Sales" roles.
[AttributeUsage(AttributeTargets.class, AllowMultiple = true)] public class TableDenyWriteAttribute: System.Attribute { ... } Listing 1
Note: By convention, the name of the attribute class ends with the word Attribute. While not required, this convention is recommended for readability. When the attribute is applied, the inclusion of the word Attribute is optional.
[TableDenyWrite ("Sales")] [TableDenyWrite ("Accounts")] public partial class Order { } Listing 2
Two types of attribute are required for the permissions attribute, Class (Entity/Table) and Property(Field/Column). Looking at Listing 2 this is all right for a simplistic approach but will make extracting the roles for each permission a bit clunky. So a better approach would be to have an attribute that is more flexible see Listing 3. The effective permissions for the class Order shown in Listing 3 are listed in Table 1.
[TablePermissions(TablePermissionsAttribute.Permissions.DenyInserts, "Sales")] [TablePermissions(TablePermissionsAttribute.Permissions.DenyDelete, "Sales", "Production",)] [TablePermissions(TablePermissionsAttribute.Permissions.DenyRead, "Accounts", "HR")] public partial class Order { } Listing 3
Roles | Insert | Delete | Read |
---|---|---|---|
Accounts | 1n | n | n |
HR | n | n | n |
Sales | n | n | y |
Production | y | n | y |
Table 1
1. Note you will not be able to do an insert if the table cannot be viewed.
Listing 4 contain the finished attribute classes.
The changes in the finished classes include:
- A permission parameter
- The role parameter has become roles and is a string array
- Each attribute class has an enum which contain the table and column permissions.
- Helper method HasRole which return true if the role is present.
- Helper method HasAnyRole which return true if any of the roles passed in are present.
using System; using System.Collections.Generic; using System.Linq; using System.Web.DynamicData;
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] public class TablePermissionsAttribute : System.Attribute { // this property is required to work with "AllowMultiple = true" ref David Ebbo // As implemented, this identifier is merely the Type of the attribute. However, // it is intended that the unique identifier be used to identify two // attributes of the same type. public override object TypeId { get { return this; } }
/// <summary>
/// Constructor
/// </summary>
/// <param name="permission"></param>
/// <param name="roles"></param>
public TablePermissionsAttribute(Permissions permission, params String[] roles)
{
this._permission = permission;
this._roles = roles;
}
private String[] _roles;
public String[] Roles
{
get { return this._roles; }
set { this._roles = value; }
}
private Permissions _permission;
public Permissions Permission
{
get { return this._permission; }
set { this._permission = value; }
}
/// <summary>
/// helper method to check for roles in this attribute
/// the comparison is case insensitive
/// </summary>
/// <param name="role"></param>
/// <returns></returns>
public Boolean HasRole(String role)
{
// call extension method to convert array to lower case for compare
String[] rolesLower = _roles.AllToLower();
return rolesLower.Contains(role.ToLower());
}
/// <summary>
/// helper method to check for roles in this attribute
/// the comparison is case insensitive
/// </summary>
/// <param name="roles"></param>
/// <returns></returns>
public Boolean HasAnyRole(String[] roles)
{
// call extension method to convert array to lower case for compare
foreach (var role in roles.AllToLower())
{
if (_roles.AllToLower().Contains(role.ToLower()))
return true;
}
return false;
}
/// <summary>
/// list of Deny permissions as the default is read write on everything
/// this model apply the most severe permission restriction
/// </summary>
public enum Permissions
{
DenyRead,
DenyEdit,
DenyInserts,
DenyDelete,
DenyDetails,
DenySelectItem, // Don't know wether this will be any use???
}
}
[AttributeUsage(AttributeTargets.Property, AllowMultiple = true)] public class FieldPermissionsAttribute : System.Attribute { // this property is required to work with "AllowMultiple = true" ref David Ebbo // As implemented, this identifier is merely the Type of the attribute. However, // it is intended that the unique identifier be used to identify two // attributes of the same type. public override object TypeId { get { return this; } }
// Constructor
public FieldPermissionsAttribute(Permissions permission, params String[] roles)
{
this._permission = permission;
this._roles = roles;
}
private String[] _roles;
public String[] Roles
{
get { return this._roles; }
set { this._roles = value; }
}
private Permissions _permission;
public Permissions Permission
{
get { return this._permission; }
set { this._permission = value; }
}
/// <summary>
/// helper method to check for roles in this attribute
/// the comparison is case insensitive
/// </summary>
/// <param name="role"></param>
/// <returns></returns>
public Boolean HasRole(String role)
{
// call extension method to convert array to lower case for compare
String[] rolesLower = _roles.AllToLower();
return rolesLower.Contains(role.ToLower());
}
/// <summary>
/// helper method to check for roles in this attribute
/// the comparison is case insensitive
/// </summary>
/// <param name="roles"></param>
/// <returns></returns>
public Boolean HasAnyRole(String[] roles)
{
// call extension method to convert array to lower case for compare
foreach (var role in roles.AllToLower())
{
if (_roles.AllToLower().Contains(role.ToLower()))
return true;
}
return false;
}
/// <summary>
/// list of Deny permissions as the default is read write on everything
/// this model apply the most severe permission restriction
/// </summary>
public enum Permissions
{
DenyRead,
DenyEdit,
}
} Listing 4
Next step is the metadata helper classes.