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
- DynamicData - Automatic Column Update
IAutoFieldGenerator
This is the heart of controlling which fields are shown in the List, Details, Edit views etc. Listing 1 is a skeleton of the Column/Row Generator, as you can see it is a class that is passed a table and has a loop which returns a ICollection of DynamicFields. The foreach loop is where the columns are processed similarly to the if (!column.Scaffold) which tests to see if the column to scaffolded.
using System.Collections.Generic; using System.Web.DynamicData; public class FilteredFieldsManager : IAutoFieldGenerator { protected MetaTable _table; public FilteredFieldsManager(MetaTable table) { _table = table; } public ICollection GenerateFields(Control control) { List<DynamicField> oFields = new List<DynamicField>(); // where do I put this test I think in the page? foreach (MetaColumn column in _table.Columns) { // carry on the loop at the next column // if scaffold column is set to false if (!column.Scaffold) continue; // create new DynamicField DynamicField newField = new DynamicField(); // assign column name and add it to the collection newField.DataField = column.Name; oFields.Add(newField); } return oFields; } }
The class is used like:
// code to add column level security table = GridDataSource.GetTable(); GridView1.ColumnsGenerator = new FilteredFieldsManager(table);
Listing 2
The code in Listing 2 is added to the Page_Init event, this is all that is needed in the pages code behind to add column level security.
Customising the FieldGenerator class
The first change is to add a new member variable _roles and change the Constructor to take an array of Strings.
protected MetaTable _table; protected String[] _usersRoles; public FilteredFieldsManager(MetaTable table, params String[] roles) { _table = table; _usersRoles = roles; }
Then in the GenerateFields method the permission for the current column will be assigned a local variable and then add a test to the if (!column.Scaffold) so that if the column is DenyRead then the column will also be skipped see Listing 4.
// get permissions for current column for current user roles. var fieldPermissions = column.GetFieldPermissions(Roles.GetRolesForUser()); // carry on the loop at the next column // if scaffold table is set to false or DenyRead if (!column.Scaffold fieldPermissions.Contains(FieldPermissionsAttribute.Permissions.DenyRead)) continue;
Listing 4
The next step is to add a DynamicReadonlyField class so that id a field is marked DenyEdit then a read only field can be returned instead of the regular DynamicField see Listing 5.
public class DynamicReadonlyField : DynamicField { public override void InitializeCell( DataControlFieldCell cell, DataControlCellType cellType, DataControlRowState rowState, int rowIndex) { if (cellType == DataControlCellType.DataCell) { var control = new DynamicControl() { DataField = DataField }; // Copy various properties into the control control.UIHint = UIHint; control.HtmlEncode = HtmlEncode; control.NullDisplayText = NullDisplayText; // this the default for DynamicControl and has to be // manually changed you do not need this line of code // its there just to remind us what we are doing. control.Mode = DataBoundControlMode.ReadOnly; cell.Controls.Add(control); } else { base.InitializeCell(cell, cellType, rowState, rowIndex); } } }
Listing 5 - Thanks to David Ebbo for this class and the explanation here on the DynamicData forum.
Now if we add a test for the column has a DenyEdit security attribute assigned then the field can now be set to an DynanicReadOnlyField. In Listing 6 the fieldPermissions variable can be tested to see if it contains a DenyEdit permission.
DynamicField f; if (fieldPermissions.Contains(FieldPermissionsAttribute.Permissions.DenyEdit)) { f = new DynamicReadonlyField(); } else { f = new DynamicField(); } f.DataField = column.Name; oFields.Add(f);
Listing 6
Listing 7 shows the code to test for foreign Key tables parent and child table permissions and if DenyRead do not show the field for the column.
//if foreign key table is hidden then hide the column that references it in this table if (column.GetType() == typeof(MetaChildrenColumn)) { // Get permissions for current columns child table var childTablePermissions = column.GetChildrenTablePermissions(Roles.GetRolesForUser()); // carry on the loop at next column if (childTablePermissions.Contains(TablePermissionsAttribute.Permissions.DenyRead)) continue; } //if foreign key table is hidden then hide the column that references it in this table if (column.GetType() == typeof(MetaForeignKeyColumn)) { // Get permissions for current columns parent table var parentTablePermissions = column.GetFkTablePermissions(Roles.GetRolesForUser()); // carry on the loop at next column if (parentTablePermissions.Contains(TablePermissionsAttribute.Permissions.DenyRead)) continue; }
Listing 7
Put it all together in Listing 8.
public class FilteredFieldsManager : IAutoFieldGenerator { protected MetaTable _table; protected String[] _usersRoles; public FilteredFieldsManager(MetaTable table, params String[] roles) { _table = table; _usersRoles = roles; } public ICollection GenerateFields(Control control) { List<DynamicField> oFields = new List<DynamicField>(); // Get table permissions var tablePermissions = _table.GetTablePermissions(this._usersRoles); // if table is DenyRead then do not output any fields if (!tablePermissions.Contains(TablePermissionsAttribute.Permissions.DenyRead)) { foreach (MetaColumn column in _table.Columns) { // get permissions for current column for current user roles. var fieldPermissions = column.GetFieldPermissions(Roles.GetRolesForUser()); // carry on the loop at the next column // if scaffold table is set to false or DenyRead if (!column.Scaffold fieldPermissions .Contains(FieldPermissionsAttribute.Permissions.DenyRead)) continue; // if foreign key table is hidden then hide // the column that references it in this table if (column.GetType() == typeof(MetaChildrenColumn)) { // Get permissions for current columns child table var childTablePermissions = column.GetChildrenTablePermissions(Roles.GetRolesForUser()); // carry on the loop at next column if (childTablePermissions .Contains(TablePermissionsAttribute.Permissions.DenyRead)) continue; } // if foreign key table is hidden then hide // the column that references it in this table if (column.GetType() == typeof(MetaForeignKeyColumn)) { // Get permissions for current columns parent table var parentTablePermissions = column.GetFkTablePermissions(Roles.GetRolesForUser()); // carry on the loop at next column if (parentTablePermissions .Contains(TablePermissionsAttribute.Permissions.DenyRead)) continue; } DynamicField f; if (fieldPermissions .Contains(FieldPermissionsAttribute.Permissions.DenyEdit)) { f = new DynamicReadonlyField(); } else { f = new DynamicField(); } f.DataField = column.Name; oFields.Add(f); } } return oFields; } }
Listing 8
having done all this remember all you have to add to the page is:
// code to add column level security table = GridDataSource.GetTable(); GridView1.ColumnsGenerator = new FilteredFieldsManager(table, Roles.GetRolesForUser());
SQL Server 2005
SQL Server 2008
the sample already has Login and Roles setup (Account details below).
Website users:
- admin
- fred
- sue
- pam
all passwords are: password



8 comments:
Hi Steve,
I finally got around to looking into table and column permissions for my application. Your sample code is great. Thanks for that. One question though. I noticed when I was running up the sample app that although I logged in as Fred (which shouldn't have read permissions on the order_detail table) that if I manually manipulated the url then I could get access to the that table. It seems to me that marking metadata on tables and columns is a tool to aid in the display of database tables/columns and not to 100% lock out access. Is this correct?
Cormac
Hi Cormac, if you look at part 4 and the section entitled "Some Error Handling for Pages Reached with Tables that are DenyRead" you will see the code that need to be added to each page to stop what you said is happening. So it's a bug, I will review the download and update where nessacery.
Steve
P.S. have a look at this new series here DynamicData: Database Based Permissions - Part 1 on my blog :D
Steve,
You mentioned that you are using a Deny Pattern for your permissions, where you limit functionality as additional attributes are tagged.
I setup my application with users and roles as follows:
Roles: Admin, Consumer, Buyer
User A is an Admin, Consumer, and Buyer
User B is a Consumer and a Buyer
User C is a Consumer
... you can see where I'm going.
When I ran the application User A was being denied access to functionality, this is because one of the other roles this person had was denied functionality. To get everything working with role based permissions setup like I just described I had to refactor the HasAnyRole method in the FieldPermissionsAttribute class to HasAllRoles:
public Boolean HasAllRoles(String[] roles)
{
foreach (var role in roles)
{
if (!HasRole(role))
return false;
}
return true;
}
Thoughts on this?
Yep thats the way I made it, of course you could make the logic more complicated, but I was just showing how and that level of complexity will suit more peoples uses :D
Steve
Steve,
Thanks for your excellent work. Your explanations and code help me to understand DD a lot better :-)
Thanks again,
Sylvester
Steve,
This is the init of list.aspx.cs:
protected void Page_Init(object sender, EventArgs e) {
table = DynamicDataRouteHandler.GetRequestMetaTable(Context);
GridView1.SetMetaTable(table);
GridDataSource.EntityTypeFilter = table.EntityType.Name;
table = GridDataSource.GetTable();
GridView1.ColumnsGenerator = new FilteredFieldsManager(table);
}
I added the class to the project as well, But my app does not go into public ICollection GenerateFields(Control control).
What is going wrong?
Hi Suicidesquad, from the code you have posted it looks like you are using either Preview 4 or Beta 1 VS2010, I have not tested in this environment yet so I'm not sure what the reason is but I will look into it ASAP.
Steve :D
Aparently there IAutoFieldGenerator works the same as in V1 according to my contacts. So I'll have a quick look later and see what happens. I'll use Preview4 and Beta 1 to test this.
Steve :D
Post a Comment