This is an updated version of the series Securing Dynamic Data Preview 4 from July 2009 here I playnto streamline the class libraries for the RTM version of Dynamic Data 4 and Visual Studio 2010.
This version is mostly the same as in Part 1 except I’ve done a great deal of refactoring and so I will list everything again here. The main difference is that there are now no user controls to replace the Delete buttons. Also I have changed the permissions system to be restrictive by default at Table level i.e. you must have a permission set on every table for the table to be seen, but a Column level you deny columns you don’t want to be seen.
Permissions Enums
The TableActions (renamed from TableDeny) enum Listing 1 has had a CombinedActions class Listing 2 added that combine sets of TableActions into logical security groups (i.e. ReadOnly equates to combining TablesActions Details and List to give an more descriptive was of assigning rights to a security Role).
/// <summary> /// Table permissions enum, allows different /// levels of permission to be set for each /// table on a per role bassis. /// </summary> [Flags] public enum TableActions { /// <summary> /// Default no permissions /// </summary> None = 0x00, /// <summary> /// Details page /// </summary> Details = 0x01, /// <summary> /// List page /// </summary> List = 0x02, /// <summary> /// Edit page /// </summary> Edit = 0x04, /// <summary> /// Insert page /// </summary> Insert = 0x08, /// <summary> /// Delete operations /// </summary> Delete = 0x10, }
Listing 1 – TableActions
/// <summary> /// Combines Table permissions enums /// into logical security groups /// i.e. ReadOnly combines TableActions /// Details and List /// </summary> public static class CombinedActions { /// <summary> /// Read Only access /// TableActions.Details or /// TableActions.List /// </summary> public const TableActions ReadOnly = TableActions.Details | TableActions.List; /// <summary> /// Read and Write access /// TableActions.Details or /// TableActions.List or /// TableActions.Edit /// </summary> public const TableActions ReadWrite = TableActions.Details | TableActions.List | TableActions.Edit; /// <summary> /// Read Insert access /// TableActions.Details or /// TableActions.List or /// TableActions.Insert /// /// </summary> public const TableActions ReadInsert = TableActions.Details | TableActions.List | TableActions.Insert; /// <summary> /// Read Insert and Delete access /// TableActions.Details or /// TableActions.List or /// TableActions.Insert or /// TableActions.Delete) /// </summary> public const TableActions ReadInsertDelete = TableActions.Details | TableActions.List | TableActions.Insert | TableActions.Delete; /// <summary> /// Read and Write access /// TableActions.Details or /// TableActions.List or /// TableActions.Edit or /// TableActions.Insert) /// </summary> public const TableActions ReadWriteInsert = TableActions.Details | TableActions.List | TableActions.Edit | TableActions.Insert; /// <summary> /// Full access /// TableActions.Delete or /// TableActions.Details or /// TableActions.Edit or /// TableActions.Insert or /// TableActions.List) /// </summary> public const TableActions Full = TableActions.Delete | TableActions.Details | TableActions.Edit | TableActions.Insert | TableActions.List; }
Listing 2 – CombinedActions
ColumnActions Listing 3 are used to deny either Write or Read access.
/// <summary> /// Actions a Column can /// have assigned to itself. /// </summary> [Flags] public enum ColumnActions { /// <summary> /// Action on a column/property /// </summary> DenyRead = 1, /// <summary> /// Action on a column/property /// </summary> DenyWrite = 2, }
Listing 3 – ColumnActions
Secure Dynamic Data Route Handler
The SecureDynamicDataRouteHandler has changed very little since the original article all I have added is the catch all tp.Permission == CombinedActions.Full in the if statement to streamline the code.
/// <summary> /// The SecureDynamicDataRouteHandler enables the /// user to access a table based on the following: /// the Roles and TableDeny values assigned to /// the SecureTableAttribute. /// </summary> public class SecureDynamicDataRouteHandler : DynamicDataRouteHandler { /// <summary> /// Creates the handler. /// </summary> /// <param name="route">The route.</param> /// <param name="table">The table.</param> /// <param name="action">The action.</param> /// <returns>An IHttpHandler</returns> public override IHttpHandler CreateHandler( DynamicDataRoute route, MetaTable table, string action) { var httpContext = HttpContext.Current; if (httpContext != null && httpContext.User != null) { var usersRoles = Roles.GetRolesForUser(httpContext.User.Identity.Name); var tablePermissions = table.Attributes.OfType<SecureTableAttribute>(); // if no permission exist then full access is granted if (tablePermissions.Count() == 0) return null; foreach (var tp in tablePermissions) { if (tp.HasAnyRole(usersRoles)) { // if no action is allowed return no route var tpAction = tp.Permission.ToString().Split(new char[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries); if (tp.Permission == CombinedActions.Full || tpAction.Contains(action)) return base.CreateHandler(route, table, action); } } } return null; } }
Listing 4 – Secure Dynamic Data Route Handler
This then covers all Edit, Insert and Details actions but not Delete.
Delete Actions
In the previous article we had a User Control that handled securing the Delete action, here we have a SecureLinkButton. All we do is override the Render method and test to see if the button is disabled via the users security roles.
/// <summary> /// Secures the link button when used for delete actions /// </summary> public class SecureLinkButton : LinkButton { private const String DISABLED_NAMES = "SecureLinkButtonDeleteCommandNames"; private String[] delete = new String[] { "delete" }; /// <summary> /// Raises the <see cref="E:System.Web.UI.Control.Init"/> event. /// </summary> /// <param name="e"> /// An <see cref="T:System.EventArgs"/> /// object that contains the event data. /// </param> protected override void OnInit(EventArgs e) { if (ConfigurationManager.AppSettings.AllKeys.Contains(DISABLED_NAMES)) delete = ConfigurationManager.AppSettings[DISABLED_NAMES] .ToLower() .Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries); base.OnInit(e); } /// <summary> /// Renders the control to the specified HTML writer. /// </summary> /// <param name="writer"> /// The <see cref="T:System.Web.UI.HtmlTextWriter"/> /// object that receives the control content. /// </param> protected override void Render(HtmlTextWriter writer) { if (!IsDisabled()) base.Render(writer); else writer.Write(String.Format("<a>{0}</a>", Text)); } /// <summary> /// Determines whether this instance is disabled. /// </summary> /// <returns> /// <c>true</c> if this instance is /// disabled; otherwise, <c>false</c>. /// </returns> private Boolean IsDisabled() { if (!delete.Contains(CommandName.ToLower())) return false; // get restrictions for the current // users access to this table var table = DynamicDataRouteHandler.GetRequestMetaTable(Context); var usersRoles = Roles.GetRolesForUser(); var tableRestrictions = table.Attributes.OfType<SecureTableAttribute>(); // restrictive permissions if (tableRestrictions.Count() == 0) return true; foreach (var tp in tableRestrictions) { // the LinkButton is considered disabled if delete is denied. var action = CommandName.ToEnum<TableActions>(); if (tp.HasAnyRole(usersRoles) && (tp.Actions & action) == action) return false; } return true; } }
Listing 5 – Secure Link Button
In more detail the IsDisabled method check to see if the LinkButtons CommandName is the same as the the “SecureLinkButtonDeleteCommandNames” application setting set in the web.config, note the default is “delete”. And then if the user does not have Delete permission then the button is disabled.
So how do we use this SecureLinkButton we add a tagMapping in the web.config file see Listing 6.
<configuration> <system.web> <pages> <controls> <!-- custom tag assignments --> <add tagPrefix="asp" namespace="NotAClue.Web.DynamicData" assembly="NotAClue.Web.DynamicData" /> </controls> <!-- custom tag mappings --> <tagMapping> <add tagType="System.Web.UI.WebControls.LinkButton" mappedTagType="NotAClue.Web.DynamicData.SecureLinkButton" /> </tagMapping> </pages> </system.web> </configuration>
Listing 6 – Tag Mapping in web.config
This means that our SecureLinkButton will replace the LinkButton throughout the site, however if you do not like this you can just rename each delete <asp:LinkButton to <asp:SecureLinkButton and then you will get the same effect and not add the tagMapping section to the web.config.
The Secure Meta Model
Here the two main parts are the SecureMetaTable and the three MetaColumn types (SecureMetaColumn, SecureMetaForeignKeyColumn and SecureMetaChildrenColumn)
SecureMetaTable
In the SecureMetaTable we override the GetScaffoldColumns method and filter the column list to where columns do not have a DenyRead action applied for any of the current users security roles.
SecureMetaColumn, SecureMetaForeignKeyColumn and SecureMetaChildrenColumn
With these types we do have to repeat ourselves a little as we override the IsReadOnly property to check to see if the column has a DenyWrite action applied for one of the users roles.
There is one issue I found and that is the default FieldTemplateFactory caches the DynamicControl model (ReadOnly, Edit and Insert) I did toy with adding the relevant code the default EntityTemplates see Listing 7, but decided again it.
protected void DynamicControl_Init(object sender, EventArgs e) { DynamicControl dynamicControl = (DynamicControl)sender; dynamicControl.DataField = currentColumn.Name; // test for read-only column if (currentColumn.IsReadOnly) dynamicControl.Mode = DataBoundControlMode.ReadOnly; }
Listing 7 – adding control mode code to the default EntityTemplates
Instead I decided to use a custom FieldTemplateFactory see Listing 8
public class SecureFieldTemplateFactory : FieldTemplateFactory { public override IFieldTemplate CreateFieldTemplate(MetaColumn column, DataBoundControlMode mode, string uiHint) { // code to fix caching issue if (column.IsReadOnly) mode = DataBoundControlMode.ReadOnly; return base.CreateFieldTemplate(column, mode, uiHint); } }
Listing 8 – Secure Field Template Factory
The code here is simple we just check to see if the column is read-only (remembering that the SecureMetaColumns are already checking this for us) then set the Mode to DataBoundControlMode.ReadOnly. This nicely keeps our code DRY.
Secure Table and Column Attributes
These are essentially unchanged from the previous series of articles with just a little refactoring to make the code more readable.
Putting It Together
Nearly all the work to get Secure Dynamic Data working is done simply in the Global.asax file.
Figure 1 – Adding Security to Dynamic Data
Also you need the tag mapping from Listing 6, there are some more bits we need to do but they are standard ASP.Net Security, so let’s get that done next.
<authentication mode="Forms"> <forms loginUrl="~/Login.aspx" protection="All" defaultUrl="~/Default.aspx" path="/"/> </authentication> <authorization> <deny users="?"/> </authorization> <membership> <providers> <remove name="AspNetSqlMembershipProvider"/> <add name="AspNetSqlMembershipProvider" type="System.Web.Security.SqlMembershipProvider, System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a connectionStringName="LocalSqlServer" enablePasswordRetrieval="false" enablePasswordReset="true" requiresQuestionAndAnswer="true" applicationName="/" requiresUniqueEmail="false" passwordFormat="Hashed" maxInvalidPasswordAttempts="5" minRequiredPasswordLength="7" minRequiredNonalphanumericCharacters="0" passwordAttemptWindow="10" passwordStrengthRegularExpression=""/> </providers> </membership> <roleManager enabled="true" />
Listing 9 – Adding standard ASP.Net security to web.config
<location path="Site.css"> <system.web> <authorization> <allow users="*"/> </authorization> </system.web> </location>
Listing 10 – Allowing access to style sheet.
With SQL Server 200x Express edition installed you will get the ASPNETDB created automatically.
Figure 1 - ASP.Net Configuration Utility
Downloads
I think that is about it, so here is the download it contains three projects the Class Library and two sample projects one Entity Framework and one Linq to SQL. Have fun.
40 comments:
Man...You have answers on all my questions... I write dynamic data asp.net application for final exam on college and here I found all the answers I need... You are excellent! My concerns was about roles and Dynamic Data ASP.NET Web Linq to SQL application, customizing field templates,... MSDN helps too, but You are much concrete..Thank You a lot... In my country said that we are young that much are thoughts are young...You're daughters are wrong, You cant be that old, You are still pretty much in shape.. Sorry about bad English... Keep with good work...
Thanks Joe :)
Steve
hi,
I downloaded this sample and change connection string in EF web config and i get this Exception after i logged in "There are no accessible tables. Make sure that at least one data model is registered in Global.asax and scaffolding is enabled or implement custom pages"
Seif
There must be an issue with your connection string sorry about that.
Steve
Thanks Stephen for this great blog,it contains very helpful articles,thanks again for this great work.
I solve the previous issue,but i has another issued,i trying Walkthrough sample for filtering Rows in Tables That Have a Parent-Child Relationship (http://msdn.microsoft.com/en-us/library/dd985039.aspx ),I get this Exception Unable to cast object of type 'NotAClue.Web.DynamicData.SecureMetaColumn' to type 'System.Web.DynamicData.MetaForeignKeyColumn'
Seif
Hi Seif, not sure what is going ont here as I cannot think of a reason why that cast would occur.
Steve :(
Hi steve, would it be possible to do this with inline editing enabled?
Hi Mr Anonymous :) it should just work as is as long as your InLine Edit page get's it's columns the normal way. I have tested this method with the Telerik RadGrid and that works fine, and I should be blogging about that as soon as time allows.
Steve
Hi Steve, thanks for your quick response for Mr. Anonymous =)
Mind checking to see if i'm doing anything wrong? "User" role are still able to edit/delete (through inline editing). However everything works if i went back into separate-page mode.
routes.Add(new DynamicDataRoute( {table}/ListDetails.aspx") {
Action = PageAction.List,
ViewName = "ListDetails",
RouteHandler = new SecureDynamicDataRouteHandler(),
Model = DefaultModel
});
routes.Add(new DynamicDataRoute("{table}/ListDetails.aspx") {
Action = PageAction.Details,
ViewName = "ListDetails",
RouteHandler = new SecureDynamicDataRouteHandler(),
Model = DefaultModel
});
unless i putting the "routehandler" at the wrong place.
Hi Eddy that looks fine the issue will be with the control you are using. By default all the pages in DD4 use LinkButton or DynamicHyperLink I've made sure that LinkButton is fixed but I had'nt noticed the use of the DynamicHyperLink this will probably be the issue.
Steve
Hi Steve, everything has been on default. I had manually changed LinkButton to SecureLinkButton. Do not see any DynamicHyperLink. Using DD4 & EF. All controls are default. When i put my mouse over Edit/Delete, the hyperlinks appear to be "javascript:__doPostBack('ctl00$ContentPlaceHolder1$GridView1','Delete$0')" & " javascript:__doPostBack('ctl00$ContentPlaceHolder1$GridView1','Edit$0')"
I will try to test it with the standard List DetailsPage as soon as I get the time
Steve
any luck??? =/
Have you tried running the sample on it's own?
Steve
Hi Steve, thank you for your great work.
Have you released a improved version for your database based secure DD?
Not yet, but I do play to for DD4
Steve
I tried this and got the following error message.
Any clues?
The database '5E98F77B1B99BCB7839B5BA9D7C561BC_RE DYNAMIC DATA\SECURINGDYNAMICDATA - 2010-06-13A\SECURINGDYNAMICDATA_L2S\APP_DATA\ASPNETDB.MDF' cannot be opened because it is version 661. This server supports version 612 and earlier. A downgrade path is not supported.
you will need to delete the ASPNET DB and create again as it is for SQL Server 'Express 2008 R2
Steve
Hi Steve,
Do you have an updated version of this? I might be running into the same issue as Eddy above. Everything worked beautifuly when i was using the List.aspx Page template but after switching over to ListDetails.aspx..not so much. I have specified the 2 different TableActions (ReadOnly for one role and Full for another role) on the samet table and everything seems to go pear shaped ... I always get full access..i haven't stepped into the code yet but does this framework currently support "stacking" table actions for different roles? Let me know.
Sorry no I will look at doing somthing about the ListDetaisl page though in the future.
Steve
Hi Steve, Thanks for your great work.
I configured my Application to run with SecureMetaModel and now I would like to see TableActions and ColumnAction enums selected from a DropDownList. How I can do this with Entity Framework?
Enrico
Sorry Enrico, not aure what you are after this version only has static metadata?
Steve
Ok, now I have it working. I had to retrieve authorization settings from a Sql db; I tried to use some ideas from "DynamicData: Database Based Permissions - Part 2". But I need to load also the 'CombinedActions' joined with 'TableActions'.
:-D
Ok, with [FilterUIHint("Enumeration")] and
[EnumDataType(typeof(SelectedTableActions))] before "permission" I have solved those issues with enum data type. Thank you and Dynamic Data 4 team!
Hi Stephen,
Could you provide us a newer version of the populated AspNetDB.mdf with the 2 users (Admin,User)?
Thank you.
Just user ASP.Net Configuration from VS to configure this.
Steve
Thanks Steve you have been a great help for me because I've used DD for my master thesis.
Hi KOsmix, thanks glad I was able to help.
Steve
Hi Steve,
I'm having some issues with using "DenyWrite" on columns. I can set up table permissions just fine and DenyRead on columns works, but if I set DenyWrite, I get errors. The edit screen displays correctly (showing the field as read only), but once I click update, I'm getting errors about how the read only field can't be null, even though it was populated in the edit screen with a value.
I've looked at the OSM in the SavingChanges method, and for some reason, the entity shows up with all null values (except its ID number). As such, it's throwing exceptions because required fields are null. It's not just the fields that have the SecureColumn attribute with DenyWrite in the metadata - if any field has it, all of the fields show up as null in the object state manager.
I'm using the DLL from the NotAClue.DynamicData project in the sample.
Any ideas why this might be happening?
Thanks.
Hi Amy, I've only had this sort of issue once and is was a bug in EF when I used mapped SPROCS for CRUD in the EF model, I reproed it with normal webformas pages. if your not using mapped SPROCs for CRUD I don't know what is happening.
Steve
That's exactly what it was. Removed the SQL stored procedures and it works fine. I suppose I need to go incorporate those into code now, but at least it works.
Thanks for your help!
Yes that is a nast bug and it only shows up when you have a column missing from the edit or read only :(
Steve
That is really fantastic! Thanks Steve for this useful code. Just one more little question: i would like to hide the hyperlinks "edit" and "delete" in case of CombinedActions.ReadOnly. The delete-link ist rendered in SecureLinkButton.cs, therefor i replaced "writer.Write(String.Format("{0}", Text))" by "writer.Write(String.Format("", Text))". But what would be the best approach to render the "edit" hyperlink?
Regards Tillmann
Hi Tillmann, direct e-mail and we can discuss in detail.
Steve
Hi Steve,
Great code again! Do you have implemented your securing solution in AD environment? I am trying to modify your solution, but I have a problem to access session object from SecureDynamicDataRouteHandler. I store users AD roles in session object. Thanks
Hi Miloš, yes it works fine with the AD provider, your only issue is the Roles, if you want to use AD Groups the you would need an AD Roles provider and that has issues. I rolled my own and is does caching of users Roles as AD so slow it really slowed down my web site. If you want the code please send me an e-mail direct my address in on my blog.
Steve
Exactly, my problem is unacceptable slow performance of web application after adding AD security. I have send you an email with my details. Thanks
Thats it Steve, thanks for your final code! My AD rolled application is now working like a charm!
Hi Steve,
I am unable to see your downloads. Any other way I could get them for reference?
Thanks!
Hi Sabrina, you will have to search my One Drive public folder here
https://onedrive.live.com/?cid=96845E7B0FAC1EED&id=96845E7B0FAC1EED%21112
to find the download Microsoft did a change a while back to SkyDrive that messedup ALL my downloads, but they are still there on One Drive :)
Steve
Post a Comment