Simple Authorization in RIA Services and Silverlight using SAF-Framework


SAF is designed with simplicity and extensibility in mind. All you have to do to use it in RIA-Services and Silverlight  to manage end-to-end security is not much. The following example shows how to use SAF to manage Authorization in a project with RIA services and Silverlight.

We have an Australia wide shop. We use ASP.Net membership provider. And three Roles: Customer, StateAdmin, and NationalAdmin.

1. In your data model:

Assign security attributes:

	[MetadataType(typeof(Metadata)]
	public partial class ShoppingItem
	{
		[Grant(Roles=new[]{"StateAdmin"}, Permissions = Permission.All)]
		[Grant(Roles=new[]{"NationalAdmin"}, Permissions = Permission.All)]
		[Deny(Roles=new[]{"StateAdmin"}, Permissions = Permission.All, ConditionMethod = "NotInState")]
                [Grant(Roles=new[]{"Customer"}, Permissions = Permission.View)]
		[Deny(Roles=new[]{"Customer"}, Permissions = Permission.View, ConditionMethod = "NotInState")]
		public class Metadata
		{
			public static bool NotInState(object instance, IPrincipal principal)
			{
				var user = principal as CustomUser;
				var shoppingItem = instance as ShoppingItem;
				//You should check for null in production code
				if (!user.ActivityStates.Contains(instance.State))
					return true;
				return false;
			}

			[Deny(Roles=new[]{"Customer"}, Permission.View)]
			public decimal PriceThatWeBought { get; set; };

			[Deny(Roles=new[]{"StateAdmin"}, Permission.Edit)]
			public decimal PriceThatWeSell {get; set; };
		}
	}

Above code means that you have a ShoppingItem in your model, and following security rules apply:

  1. Customers only can view items and can not change anything.
  2. State admins have all the rights over items in their states of activity. They have no access to anything in other states. (They can view other state items only if they have Customer role as well and this is because the grant view to customer if after deny to the StateAdmin)
  3. NationalAdmins can do anything
  4. Customers can not see the PriceThatWeBought
  5. SateAdmins can not change the PriceThatWeSell because CEO only trusts himself.

As you can see writing the above rules in code is as easy as describing them in English.

2. At your Service Level:

You have three methods. One to get a specific shopping item. Second to get all shopping items. And third to save a shopping item. Here is all the code you have to write:

	public partial class MyDomainService
	{
		public Tuple<ShoppingItem, AuthorizationToken> GetShoppingItemById(int id)
		{
			DomainContext.ShoppingItems
				.Where( s => s.Id = id)
				.Select(s => s)
				.FilterUnAtuthorizedWithToken()  //What user should not see will be scapped here.
				.First();
		}

		public IEnumerable<Tuple<ShoppingItem, AuthorizationToken>> GetShoppingItems()
		{
			DomainContext.ShoppingItems
				.Select(s => s)
				.FilterUnAtuthorizedWithToken()  //What user should not see will be scapped here.
				.First();
		}

		[RequiresPermission(Permissions.Write)]
		public SaveShoppingItem(ShoppingItem item)
		{
			DmoainContext.SaveChanges();
		}
	}

We have only written three lines of code here to do all the authorization:
First and Second are the FilterUnAuthorizedWithToken() extension method that filters everything based on the user’s privileges and scraps fields that user should not see like PriceThatWeBought.
Third is the RequresPremission attribute which infers only someone can run this method that has Write permission over ShoppingItem object. For example an StateAdmin of Queensland can not edit a shopping item from NSW.

You can see the return types are Tuple<ShoppingItem, AuthorizationToken>. This is because the FilterUnAuthorizedWithToken() will attach an authorization token to any item returned. You can use FilterUnAuthorized() method if you don’t want an authorization token.

3. At the View (and ViewModel)

In you silverlight, the data context for your view is your view model.

	public class ShoppingItemViewModel
	{
		private ShoppingItem _shoppingItem;
		private AuthorizationToken _authorizationToken;

		public void Load()
		{
			//Load the data and authorization tokens here.
		}

		public string ItemName {get{...}set{...}}
		public bool ItemNameVisible {get{return _authorizationToken.IsVisible("ItemName");}}
		public bool ItemNameEditable {get{return _authorizationToken.IsEditable("ItemName");}}

		//Do the same for all other properties.
	}

The only change here is the AuthorizationToken which we saw is return by the FilterUnAuthorizedWithToken() method in the previous section.
Now you have to specify the visibility and editability properties and bind them to the visibility and editability of you XAML controls.
If you want to be more smart in the client side you can write a converter for visibility and editability. And instead of using strings in your code for “ItemName” you can use the property.Name to be type safe.
The magic here is that you can change anything in your authorization rules and policies, but the only place you have to change is your model. You should not worry about the service or view, they get updated auto-magically.

Advertisements

4 Responses to “Simple Authorization in RIA Services and Silverlight using SAF-Framework”

  1. Andrejs Nepomnascihs Says:

    Hello, Naiem!

    Thank you for all your work on this project. It looks very promising, yet I’m struggling to make it all work in simple project built on top of Business Application template. By any chance do you have some explanation how AuthorizationContext, AttributeAuthorizationProvider and IPrincipalProvider are meant to be used? Or maybe you have a working solution, so everyone who is interested can get a sense on how it should be used?

    Kind Regards and once again thank you for developing this project.

    • naiem Says:

      Adnrejs,

      Thanx for using SAF.
      You have to creatae the AuthorizationContext on your DomainService.Initialize() override method.
      You just have to pass it an AuthorizationRuleProvider and an IPrincipalProvider.
      You have to implement your IPrincipalProvider which has a method GetCurrentPrincipal(). The easiest way is to set your domainService to implement IPrincipalProvider and return DomainContext.User in the GetCurrentPrincipal() method. Then pass this along with a new AuthorizationRuleProvider to the AuthorizationContext().
      You have to keep this AuthorizationContext object in your domianService, so that other domain service methods can use it.
      if you needed to pass MetadataProvider anywhere, use ComponentModelMetaDataProvider for RIA Services.
      Let me know if you still have problem.

  2. Andrejs Nepomnascihs Says:

    Thank you for your answer.

    Ok, looks like it started to work for me now, thank you once again.

    But I do have more questions:

    1) What’s the designed use for FilterUnAuthorizedWithToken? The reason I’m asking is because as I can see there’s no way it can be used int Silverlight through RIA as Tuple is not marked as Serializable.

    2) Am I correct that there’s no mechanism to prevent one from modifying inaccesibble property when he/she has write permission for the object itself?

    Example:

    [MetadataTypeAttribute(typeof(Book.BookMetadata))]
    public partial class Book
    {
    [Grant(Roles = new[] { “Role1” }, Permission = Permission.All)]
    [Grant(Roles = new[] { “Role2” }, Permission = Permission.All)]
    internal sealed class SampleModelMetadata
    {
    private BookMetadata()
    {
    }

    [Key]
    public int ID { get; set; }

    [Grant(Roles = new[] { “Role1” }, Permission = Permission.All)]
    [Grant(Roles = new[] { “Role2” }, Permission = Permission.All)]
    public string PublicName { get; set; }

    [Grant(Roles = new[] { “Role1” }, Permission = Permission.All)]
    [Deny (Roles = new[] { “Role2” }, Permission = Permission.All)]
    public string InternalName { get; set; }
    }
    }

    In this case one that holds Role2 role when quering service will get book object with InternalName scrapped from it but will be able to set InternalName through update method of service because he/she holds write permission for the Book itself?

    3) It’s tightly connected with the question #1, but how can I acquire AuthorizationToken in ViewModel? As I said earlier, when I try to return Tuple from query method of DomainService, that method dissappears from clients DomainContext class.

    P.S. In any case do you need a simple project with custom Domain Service that demonstrates how it works?

    • naiem Says:

      Hi Andrejs,
      Saf framework is not designed specifically for Ria Services. Ria Services support is added only in the DomainServicesWrapper project.
      To answer your first question, unfortunately Ria Services does not support Tuple directly (even though Tuple is serializable).
      You should create a custom DTO (a class which contains the token and the object, and a [Key] and [Associate…] attribute to glue them together for Ria Services to recognize it. Can you think of a more graceful approach?
      For your second question, I can think of a solution, however, it is not yet implemented in the DomainServiceWrapper.
      You can get the AuthToken for an object before submit and make sure that the properties that are not allowed to be changed are not changed (using reflection). However, this is not very easy for Entity Framework.
      You can write a function like this as an extension to AuthorizationContext.

      public ReconcileChange(this AuthorizationContext context,  ChangeSet changeset, IEnumberable<Entity> originalEntities)
      {
      ...
      }
      

      In the function you can get the AuthorizationTokens for each object in the changeset and using reflection, set the value of the properties that are not editable to the values in OriginalEntity. (using reflection).

      Writing the above function is easy enough, but the problem is to get the original entities in Entity Framework and it is not possible without querying DB once more before submitting changes.
      This will not be very fast, but it will be KGB secure (from app perspective) and to justify the performance cost, you can also modify above function to generate the Auditing record using these information. (I.e. what is changed to what by who). So you have solved two major enterprise app problems (auditing, and authorization) and it may be worth the extra query.
      You can overwrite the OnSubmit method (or one of the other relevant methods) of the DomainService and do all above before submit.
      It all seems hard work, but if you managed to do it, please send me your code to include in the framework for others benefit.
      And for your last question, sure, it would be great to have a sample solution especially if it includes sending tokens to the client.


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: