Authorization Sample 305 – Permission-Based Authorization for Silverlight

In Authorization Sample 301 I explained the ins-and-outs of the authorization sample and offered a few hints as to how it could be used. In this post, I will show a specific example where I show how to extend the authorization library to support read and write permissions.

image

Permissions

The first challenge with this sample was to figure out how to represent permissions in metadata in a way that could be used by AuthorizationAttributes. After churning the design for a while, I came away with something simple that made authorization convenient.

The first step was to create a Permission, a type thatcombines a name and a list of allowed activities. The name property needs to be treated as the unique key for the permission.

   public class Permission
  {
    public string Name { get; }
    public PermissionKind Kind { get; }
  }

The next step was to define the all the activity types in a PermissionKind enumeration.

   [Flags]
  public enum PermissionKind
  {
    None = 0x0,
    Read = 0x1,
    Write = 0x2,
    All = Read | Write,
  }

With these two types, I was able to create an AuthorizationAttribute for requiring permissions.

   public class RequiresPermissionAttribute : AuthorizationAttribute
  {
    private readonly IEnumerable<string> _permissionNames;

    public RequiresPermissionAttribute(params string[] permissionNames)
    {
      if (permissionNames == null)
      {
        permissionNames = new string[0];
      }
      this._permissionNames = permissionNames.ToArray();
    }

    public IEnumerable<string> PermissionNames
    {
      get { return this._permissionNames; }
    }

    protected override AuthorizationResult IsAuthorized(
                         IPrincipal principal,
                         AuthorizationContext authorizationContext)
    {
      PermissionKind kind = PermissionKind.All;
      if (authorizationContext.Items.ContainsKey(
                                       typeof(PermissionKind)))
      {
        kind = (PermissionKind)
                 authorizationContext.Items[typeof(PermissionKind)];
      }

      User user = (User)principal;
      foreach (string name in this.PermissionNames)
      {
        Permission permission = user.Permissions.FirstOrDefault(
                                                   p => p.Name == name);
        if ((permission != null) && ((kind & permission.Kind) == kind))
        {
          return AuthorizationResult.Allowed;
        }
      }

      return new AuthorizationResult("User does not have permission.");
    }
  }

It may look a little complex at first, so let’s break it down. First, the RequiresPermissionAttribute is created with a permission name.

   [RequiresPermission("Model")]

To be authorized, the user must have the “Model” permission with PermissionKind.All. Also, like the RequiresRolesAttribute, the attribute can be declared with a list of names. In that case, the user must match at least one of the permissions in the list to be authorized.

Apart from the primary authorization mode, the RequiresPermissionAttribute can be queried for one particular PermissionKind. This allows calling code to determine if the user has only a subset of the PermissionKinds; only PermissionKind.Read, for example. To query for a specific kind, an entry for the kind can be added to the AuthorizationContext.

UI Modes

The second challenge was how to represent permissions with respect to the UI. I found I needed to create a second enumeration to identify the states that were interesting from a UI perspective.

   public enum ReadWriteMode
  {
    None = 0,
    Read,
    ReadOnly,
    ReadWrite,
  }

Read – This mode is designed for switching control visibility on authorization. If the user has the right read permission the control will be visible.

ReadOnly – This mode is designed to work in tandem with ReadWrite. When the user has permission to read but not write the control will be visible.

ReadWrite – This mode works with ReadOnly. When the user has permission to read and write the control will be visible.

The pairing of ReadOnly and ReadWrite allows controls for viewing and editing to be declared alongside each other in the xaml. Setting the mode ensures they will never be visible at the same time.

   <!-- Value -->
  <TextBlock Grid.Row="1" Grid.Column="0"
             Text="Value"
             rws:ReadWriteAuthorization.RequiresPermission="Model"
             rws:ReadWriteAuthorization.Mode="Read" /> 
  <TextBlock Grid.Row="1" Grid.Column="1"
             Text="{Binding Value}"
             rws:ReadWriteAuthorization.RequiresPermission="Model"
             rws:ReadWriteAuthorization.Mode="ReadOnly" />
  <ComboBox Grid.Row="1" Grid.Column="1"
            SelectedValue="{Binding Value, Mode=TwoWay}"
            rws:ReadWriteAuthorization.RequiresPermission="Model"
            rws:ReadWriteAuthorization.Mode="ReadWrite" />

Rules, Behaviors, and Sources

Writing a permission-based solution takes advantage of (and was a catalyst for) most of the extensibility points in the authorization library. First there is a custom AuthorizationRule that ties the whole thing together. It creates RequiresPermissionAttributes for authorization from the permission names in the attached ReadWriteAuthorization.RequiresPermission dependency property. The rule also creates custom AuthorizationBehaviors that handle updating the UI using the attached Authorization.TargetProperties and ReadWriteAuthorization.Mode dependency properties.

To control the UI behavior, the custom ReadWriteAuthorizationBehavior creates a binding between the target dependency property and a custom AuthorizationSource. Based on the mode, it will bind to one of the Result, ReadOnlyResult, or ReadWriteResult properties on the source.

The ReadWriteAuthorizationSource has three result properties to match up with the ReadWriteMode enumeration; Result, ReadOnlyResult, and ReadWriteResult. The default Result property matches up with ReadWriteMode.Read. The source is created with one or more RequiresPermissionAttributes and uses them to authorize the current user and determine each of the results. Also, each of these properties can be listened to for changes and will update in response to changes in WebContext.Current.User.

I hope you’re still following, but that was a lot of types and relationships condensed into a few short paragraphs. In summary, there is a custom AuthorizationRule, a custom AuthorizationBehavior, and a custom AuthorizationSource. Together they work to require permissions and update the UI.

A Custom Rule for Text

One of the benefits of rule extensibility is the control it provides. In this sample, I created a custom rule to apply only to TextBoxes.

   public class ReadWriteTextAuthorizationRule : AuthorizationRule
  {
    public override IEnumerable<AuthorizationBehavior>
                      GetAuthorizationBehaviors(object target)
    {
      return new[]
      {
        new ReadWriteAuthorizationBehavior(
              "Visibility", ReadWriteMode.Read),
        new ReadWriteAuthorizationBehavior(
              "IsReadOnly", ReadWriteMode.ReadOnly),
      };
    }
  }

It uses the custom ReadWriteAuthorizationBehavior to bind the Visibility and IsReadOnly properties to an AuthorizationSource. I chose to make it the default rule for TextBoxes, but it could just as easily be applied to individual controls. Also, since a target can have more than one rule, I can use the ReadWriteAuthorization.RequiresPermission property to define the RequiresPermissionAttributes used for authorization and the default text rule to define the behaviors and binding to apply.

   <TextBox Text="{Binding Text, Mode=TwoWay}"
           rws:ReadWriteAuthorization.RequiresPermission="Model" />

Wrap Up

There’s a lot of code in this sample, but I wanted to show how a more complex authorization scenario could be implemented. I imagine this same design could be scaled up to support more than just Read and Write permissions. For each PermissionKind that was added, one or more corresponding UI Modes could be added as well.

I’ve included the source here for your reference. I hope it’s a helpful reference point for implementing authorization in your own applications.