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 that combines 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.

Comments (11)

  1. Hatem says:

    Thank you for the help, I realy like your code.

    but I have a problem with current User info.

    what I want to do set a user profile variable on the Client. like

    WebContext.Current.User.FriendlyName

    then if I get a user object on the server like:

    User u = new Authentication().GetUser();

    I can find the modified value u.FriendlyName.

    I can find the FriendlyName value anywhere on the server.

    am i doing this right ?!!

    thanks

  2. kylemc says:

    @Hatem

    Here is a great place to start. It describes how to use the default RIA Services profile support for properties like FriendlyName.

    msdn.microsoft.com/…/ee707350(v=VS.91).aspx

  3. Hatem says:

    thank you for such a fast reply,

    this link doesn't help me, because:

    1- this don't have how to access "FriendlyName from a service"  btw, Authentication.GetUser() on the server returns a fresh set of variables (FriendlyName = "") .

    2- I don't want to User DB to save the variables I've added to the User class, I want to use them on the fly.

    any Ideas ?

    Thanks

  4. kylemc says:

    @Hatem

    Sure. The technique is called adding a shared, calculated property. In summary you add a shared file to the server, MyType.shared.cs, and write a partial class with a new property in that file.

    public partial class MyType

    {

     public int MyProperty { return MyOtherProperty * 2; }

    }

    There's some additional complexity getting the property change notifications wired up on the client (through the use of another partial class). In the latest SL TV episode, Deespesh showed how that could work.

    channel9.msdn.com/…/Silverlight-TV-44-Top-Four-Questions-from-the-WCF-RIA-Services-Forum

    I didn't go into a lot of detail here, but hopefully it was just enough to help you know what else to look for.

  5. joe says:

    Kyle, just what I was looking for, but I'm missing one piece: how to tie a Permission back to a Role in the User.Roles collection? Or is in implied that the Permission.Name == Role?

  6. rob says:

    The sample source is no longer available it seems.  Can you point me to where I can find it?

    Also, does any of this framework rely on RIA or could it be used with any Silverlight application?

  7. kylemc says:

    @Rob

    Thanks. I've finally migrated all the source to code.msdn samples. Hope it helps. (Officially this stuff does rely on RIA, but only on the WebContext object. I don't think it'd be terribly hard to swap out that small dependency if you needed to.)

  8. rob says:

    Excellent Kyle, thanks very much!  This framework is right up our alley for what we are looking for.

  9. rob says:

    Fyi that the code has compile errors where the WebContext is new'd up such as :

    this.ApplicationLifetimeObjects.Add(new WebContext() { Authentication = new MockAuthentication(User.Users.First()) });

    Error 1 The type or namespace name 'WebContext' could not be found (are you missing a using directive or an assembly reference?) C:tempAuthenticationInSilverlightMvvmAuthzSampleMvvmAuthzSampleApp.xaml.cs 28 53 MvvmAuthzSample (MvvmAuthzSampleMvvmAuthzSample)

  10. kylemc says:

    Hmm… built on my machine. 🙂 Is RIA Services installed? Also, what happens if you build twice?

  11. rob says:

    Doh!  No Ria = No WebContext as you mentioned earlier. Sorry for the false alarm.