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.