Download source code
One of the recurrent questions people ask me in the forums is about the applying of security constraint to the applications being developed. The last releases of useful tools like WCF Ria Services added the capability of bringing the security context from the server to the plugin running into the browser. This let the developer to have the current logged-in user and his roles available to apply rules to elements of the user interface.
With WCF Ria Services you can create easily an AuthenticationDomainContext and when you make a reference to it from a Silverlight project you will have available a class named WebContext. This class is the container where the security context takes place. The pattern to decide if a control is available or not is like the following snippet:
if (WebContext.Current.User.IsInRole("Administrator"))
element.Visibility = Visibility.Visible;
else
element.Visibility = Visibility.Collapsed;
Since it is really simple to understand, doing this work programmatically is a repetitive task prone to errors. This is only a simplified example but as you may understand there will be many cases where you will have to deal with multiple roles and complex logic.
I've decided to work to find a solution to the problem ad in this article I will show you my achievement.
What do we really need?
Since applying roles programmatically is and hard task, prior of finding a reliable solution we need to understand what will be better for the application development. In my opinion, the task of apply roles to the user interface is not something that can be made by a developer. I imagine that a developer have to build the application without concerning about who have to see a field or not. He simply must remember that some parts of the UI may be disabled or hidden and the layout have not to be ruined from the missing parts. From the logic point of view this have not to become a problem. It is easily understandable that if a control may be hidden, his value have not to be compulsory for the application so there will be not any error for the missing informations. So perhaps a field will be nullable on the database or it will have a default value.
I imagine that the security behavior of the user interface may be designed by the same person or team that is responsible of the design and usability of the interface itself. The designer will not has to handle rows of code written in C# but he has to use a declarative syntax to describe what user have to see or not.
In the following box I've written a sample of what I'm saying. In the xaml tag you see a imaginary attribute, called Roles, containing the roles that may see the UI component.
<Button x:Name="SaveButton" Content="Ok" Roles="Admin,User" />
Another thing we may need is to completely hide or show a control when the user is logged or not. The simplest example if the login/logout buttons that have to be alternatively hidden or shown when the user is logged or not. Here is the snippet of xaml showing how the attribute has to appear:
<HyperlinkButton Content="Login" VisibilityIfLogged="Collapsed" />
<HyperlinkButton Content="Logout" VisibilityIfLogged="Visible" />
As you may have figured out the solution of extending all the controls, to add the required properties, is not an option. What we need is to have a way to define a property that we can add to every elemenent. So let me introduce Attached Properties and how they work.
Two words about Attached Properties
The reason of avoiding to add properties by extending each control, is first of all a question of opportunity, but it is not the sole reason. As you may understand, having a control with a property named VisibilityIfLogged make sense only if the control itselft is put inside an authenticated page. In all the other cases the property is completely useless. So the better is to have some kind of property that I can add to a control only when I need it.
In xaml there is a bunch of properties that behave this way. Think at the absolute positioning of elements inside of a Canvas where you use a special syntax to assign Canvas.Left and Canvas.Top. This kind of property is called Attached Properties meaning element properties which value is stored inside the element - taking advantage of the Dependency Property system - but managed by an external object.
The pattern for creating an Attached Property is simple and I suspect it is widely known because it comes from the first WPF release. You only need to create a couple of methods on an external class exposing "Set" and "Get" operations over a given property name. Then you register the property in the Dependency Property system and the game is over. Here is the definition of one of the Attached Properties I will speak about in the next paragraph:
public static readonly DependencyProperty VisibilityIfLoggedProperty =
DependencyProperty.RegisterAttached(
"VisibilityIfLogged",
typeof(Visibility),
typeof(RoleManager),
new PropertyMetadata(Visibility.Collapsed, (s, e) => ApplyRulesToElement(s)));
public static Visibility GetVisibilityIfLogged(DependencyObject obj)
{
return (Visibility)obj.GetValue(VisibilityIfLoggedProperty);
}
public static void SetVisibilityIfLogged(DependencyObject obj, Visibility value)
{
obj.SetValue(VisibilityIfLoggedProperty, value);
}
After the registration you can reference the class in the XAML markup and start assigning values to the property. As usual you have to add a xmlns attribute to define a namespace and create a reference to the clr namespace where the defining class is located. The syntax to assign values require you specify the namespace, then the name of the defining class and finally, the name of the property.
<HyperlinkButton x:Name="logout" security:RoleManager.VisibilityIfLogged="Visible" />
What you do with the collected values is in your own hands. There is a number of examples of use of the Attached Properties, many of them able to add features you never thinked as possible. Be aware of the power of this tool and follow me in the next paragraph to see how to take advantage of them to simplify you security concerns.
Preparing the RoleManager
Once you have understand Attached Properties it is time to start building a component - from here I will call RoleManager - capable to collect security attributes placed on markup elements and apply some rules to the target objects to show or hide parts of the UI.
My idea is to retain an index of the components having roles specified by an attached property and make it operate to update the indexed controls to reflect current logged user capabilities.
For this purpose I've decided to create these properties:
For this purpose I've decided to create these properties:
RoleManager.Manager - define the manager instance to be used by the referring element.
RoleManager.Roles - collect the roles that are enabled to see the element and all the inner part of the visual tree.
RoleManager.VisibilityIfLogged - let the developer specify if an element is visible or collapsed when the WebContext is authenticated
Starting from the first property named RegionManager.Manager, it take a reference to a RegionManager instance that you have to put inside the resources. The reason of having a reference to a resource instead of having a static class is that I strongly want to avoid having a unique instance collecting all the element's references from the entire application. This imply that when you need to update a page you have to crawl all the elements of the whole application.
So when you reference a RoleManager from a control, your manager will be called and in this moment it will save a reference to the original control. Here is the code:
public static readonly DependencyProperty ManagerProperty =
DependencyProperty.RegisterAttached(
"Manager",
typeof(RoleManager),
typeof(RoleManager),
new PropertyMetadata(null, (s,e) => OnManagerChanged(s,e)));
public static RoleManager GetManager(DependencyObject obj)
{
return (RoleManager)obj.GetValue(ManagerProperty);
}
public static void SetManager(DependencyObject obj, RoleManager value)
{
obj.SetValue(ManagerProperty, value);
}
private static void OnManagerChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
UIElement element = obj as UIElement;
if (element != null)
{
if (e.OldValue != null && e.OldValue is RoleManager)
((RoleManager)e.OldValue).Clear();
if (e.NewValue != null && e.NewValue is RoleManager)
((RoleManager)e.NewValue).AddElement(element);
}
}
Then you have simply to intercept the assignment of the properties and update the controls referenced by the RoleManager. For this I use the PropertyChanged callback of the attached property. This callback notify the developer when the value of the element is changed. Inside the callback I retrieve the manager referenced by the element then I call a method that apply the roles to all the elements inside of it. Some code is for sure simpler than thousand words:
public static readonly DependencyProperty VisibilityIfLoggedProperty =
DependencyProperty.RegisterAttached(
"VisibilityIfLogged",
typeof(Visibility),
typeof(RoleManager),
new PropertyMetadata(Visibility.Collapsed, (s, e) => ApplyRulesToElement(s)));
private static void ApplyRulesToElement(DependencyObject element)
{
RoleManager manager = RoleManager.GetManager(element);
if (manager != null)
manager.ApplyRules();
}
The ApplyRules method is defined public for a precise reason. When you change the WebContext, for example when the application pass from not-logged to logged-in, you need to call manually this method because there is not any way of being notified about this transition. So when the login is completed and all the roles have been loaded in the WebContext you have to programmatically call this method to have the whole user interface updated. Here is the body of the method where I evaluate roles one by one:
public void ApplyRules()
{
foreach (UIElement element in this.Elements)
{
string roles = RoleManager.GetRoles(element);
if (!string.IsNullOrEmpty(roles))
{
foreach (string role in from rl in roles.Split(',')
select rl.Trim())
{
if (WebContext.Current.User.IsInRole(role))
{
element.Visibility = Visibility.Visible;
break;
}
else
element.Visibility = Visibility.Collapsed;
}
}
else
{
Visibility visibility = RoleManager.GetVisibilityIfLogged(element);
if (WebContext.Current.User.IsAuthenticated)
element.Visibility = visibility;
else
element.Visibility = visibility == Visibility.Collapsed ? Visibility.Visible : Visibility.Collapsed;
}
}
}
Apply the roles
After all the words I spent it should becomes evident how to use the tools I proposed. In the attached sample there is a short sample showing how to use the RoleManager but in the last box I show the assignment of a couple of roles to a button. The code comes from the attached code:
<HyperlinkButton TargetName="ContentFrame" NavigateUri="/Orders"
security:RoleManager.Roles="Administrative"
security:RoleManager.Manager="{StaticResource roleManager}"
Content="Orders" Margin="5,5,5,0" />
... or ...
<HyperlinkButton x:Name="login"
security:RoleManager.Manager="{StaticResource roleManager}"
security:RoleManager.VisibilityIfLogged="Collapsed"
Margin="0,5,5,5" VerticalAlignment="Center" Content="Login" Foreground="Black"
HorizontalAlignment="Right" HorizontalContentAlignment="Right"/>
While the first part show how to apply roles (remember you can specify multiple roles using a comma to separate them), the second part show the usage of the VisibilityIfLogged attribute. The sole recommendation is to use a resource local to the page instead of a global resource declared into the page. This because a global resource will have to update the entire controls of the whole application.