In a previous post I explained how you could extend Rhino.Security to add whatever you want.
Marco asked me how we are using field level security in our application. In this post I will explain how we do it. ASP.NET MVC contains a HtmlHelper that you can use to add for example textboxes, dropdowns, hidden fields,... to your view. In our approach we created extensions methods for the HtmlHelper. This approach was initiated by my colleague Gino and together with another colleague Bavo, who definitely needs to start blogging about his experiences with Rhino.DSL, Rhino.ETL and Boo, we took it one step further.
We still have some issues to tackle, mostly concerning GUI stuff. We need to make sure for example that a page still looks good if fields start to disappear in that page. That's something that can be solved using JQuery. Another thing is the damn taborder ;-). Also something that can possibly be solved using JQuery but we are not quite sure yet how.
We also need to do some optimizations. The HTML is now mainly generated using "string.Format". It would be better to use the HTMLTextWriter for this task.
Anyway let's explain how you can do it. In my view I add a textbox as follows:
1: <%=Html.SecuredTextBox<User>("UserName", FleetBox.Resources.User.UserName, c => c.UserName, ViewData.Model.User,
2: new Dictionary<string, object>{{"tabindex",1}})%>
Following parameters are passed to the method:
- Name of the textbox;
- Name for the label that will be added to the textbox;
- The property for which the authorization needs to be checked;
- The instance: will be used to set the value of the textbox;
- A dictionary with HTML attributes.
The implemenation of SecuredTextBox looks as follows:
1: public static string SecuredTextBox<T>(this HtmlHelper htmlHelper, string name, string labelText,
2: Expression<Func<T,object>> property, object instance, IDictionary<string, object> htmlAttributes)
3: {
4: var level = GetAccessLevel(name, property);
5:
6: if (level == AccessLevel.None)
7: {
8: return String.Empty;
9: }
10:
11: htmlAttributes = htmlAttributes ?? new Dictionary<string, object>();
12: htmlAttributes.Add("id", name);
13:
14: // set the value if an instance was passed
15: if (instance != null)
16: {
17: var compiled = property.Compile();
18: var value = compiled((T)instance);
19:
20: if (value != null)
21: htmlAttributes.Add("value", value);
22: }
23:
24: // readonly? -> add readonly attribute
25: if (level == AccessLevel.Read)
26: htmlAttributes.Add("readonly", "readonly");
27:
28: return string.Format("<p><label for=\"{0}\">{1}</label>{2}</p>", name, htmlHelper.Encode(labelText), htmlHelper.TextBox(name, htmlAttributes));
29: }
One of the first things we do there is getting the access level.
1: private static AccessLevel GetAccessLevel<T>(string name, Expression<Func<T, object>> property)
2: {
3: if (name == null) throw new ArgumentNullException("name");
4: if (property == null) throw new ArgumentNullException("property");
5:
6: var body = (MemberExpression)property.Body;
7:
8: if(body==null)
9: {
10: throw new ArgumentException("The supplied property expression must call a member", "property");
11: }
12:
13: var propertyInfo = body.Member as PropertyInfo;
14:
15: if (propertyInfo == null)
16: throw new ArgumentException("The supplied property expression must be a PropertyInfo", "property");
17:
18: var type = typeof(T);
19: var editOperation = string.Format("/{0}/{1}/Edit", type.Name, propertyInfo.Name);
20: var readOperation = string.Format("/{0}/{1}/Read", type.Name, propertyInfo.Name);
21:
22: var user = userService.GetUser(HttpContext.Current.User.Identity.Name);
23: if (user != null && authorizationService.IsAllowed(user,editOperation))
24: {
25: return AccessLevel.All;
26: }
27: if (user != null && authorizationService.IsAllowed(user, readOperation))
28: {
29: return AccessLevel.Read;
30: }
31: return AccessLevel.None;
32: }
I think the rest of this code is pretty self-explanatory. If someone has an idea on how to deal with taborders in this scenario, I would love to hear about it ;-)

7 comments:
This is really cool
Very interesting - we've come around to a pretty similar solution using html helpers. We've abstracted out the actual security check to a static class - the HtmlHelper just takes a boolean for "editable". And instead of not showing the control we just disable it, but more or less the same idea.
The one thing that was bothering me was the amount of code in the view - although it looks like you've got about the same amount we do. Have you been able to stream-line this any further?
Cheers & Great blog!
Dave
I have only this in the views:
Html.SecuredTextBox("UserName", FleetBox.Resources.User.UserName, c => c.UserName, ViewData.Model.User)"
This actually renders the textbox, the label for the textbox,the surrounding "p" tag and sets the value for the textbox if needed. It keeps the views pretty clean until now.
Hi, I downloaded the source of winecellamanager, but I didn't find an example of usage of the SecuredTextBox, can you provide it in some source code? Thanks.
There is no example in winecellarmanager for now but pretty much all of the code is available in the post.
Thanks for your post and welcome to check: here
.
Post a Comment