Automatic End to End Authorization (Part 3)

I promised to show you how does the SAF framework really do the magic. I changed my test model from my last post a little bit so I post it back again here:

<pre> [Grant(Roles = new[] { "Everyone" }, Permission = Permission.View)]
[Grant(Roles = new[] { "QLDAdmin", "NSWAdmin" }, Permission = Permission.View | Permission.Edit | Permission.Create)]
[Grant(Roles = new[] { "Deleter" }, Permission = Permission.Delete)]
[Grant(Roles = new[] { "God" }, Permission = Permission.View | Permission.Own)]
[Deny(Roles = new[] { "NSWAdmin" }, Permission = Permission.Edit)]
[AuthorizationCustom(CustomType = typeof(TestObject), Method = "OnlyQld")]
public class TestObject
{
[Grant(Roles = new[] { "Everyone" }, Permission = Permission.View)]
[Deny(Roles = new[] { "God" }, Permission = Permission.View)]
public int YouCanSeeMe { get; set; }

[Grant(Roles = new[] { "Everyone" }, Permission = Permission.Edit)]
[Deny(Roles = new[] { "God" }, Permission = Permission.Edit | Permission.View)]
public string YouCanEditMe { get; set; }

[Deny(Roles = new[] { "Everyone" }, Permission = Permission.View)]
public string YouCanNotSeeMe { get; set; }

[Deny(Roles = new[] { "Everyone" }, Permission = Permission.Edit)]
public string YouCanSeeMeButNotEditMe { get; set; }

[Grant(Roles = new[] { "*" }, Permission = Permission.View)]
[Deny(Roles = new[] { "QLDMan" }, Permission = Permission.All)]
public string EverybodyCanSeeMe { get; set; }

public string[] States { get; set; }

public static Permission? OnlyQld(IPrincipal pri, object instance)
{
var user = pri as TestUser;
if (user.Roles.Any(r => r.Contains("QLD"))
&&
(((TestObject)instance).States != null && ((TestObject)instance).States.Contains("QLD"))
)
return Permission.All;
return null;
}
}</pre>

Note that the Permission for the custom authorization method is nullable this time. It actually won’t work if the return type is not nullable.

Now the magic comes:

  1. Filter what user should not see automatically using FilterAuthorized() extension method.
  2. Send authorization tokens to client. Client can use AuthorizationToken.Visible or AuthorizationToken.Editable methods to bind view parts to data.

Below is an example case for more clarity:

<pre>[TestMethod()]
public void FilterAuthorizedTest()
{
IMetadataClassProvider sm = new SelfMetadata();
var everyone = new TestUser() { Roles = new[] { "Everyone" } };
var qldMan = new TestUser() { Roles = new[] { "QLDMan" } };

var coll = new[]
{
new TestObject() {YouCanSeeMe = 0, States = new[] {"QLD", "NSW"}},
new TestObject() {YouCanSeeMe = 1},
new TestObject() {YouCanSeeMe = 2, YouCanNotSeeMe = "a", States = new[] {"QLD"}},
new TestObject() {YouCanSeeMe = 3},
new TestObject() {YouCanSeeMe = 4, YouCanNotSeeMe = "b"}
};
var filtered = coll.FilterAuthorized(sm, everyone).ToList();
Assert.AreEqual(5, filtered.Count());

filtered = coll.FilterAuthorized(sm, qldMan).ToList();
Assert.AreEqual(2, filtered.Count());
}</pre>

The framework auto-magically cares about what user can see and what he can’t see.

In case you were not following me, here is the link to SAF Framework.

The major hole here is that the framework does not scrap properties that are not available to user at this stage. This should be a quick fix once I get some more time. Feel free, if you like to contribute. The architecture is quiet extensible and easy to understand.

Advertisement

Automatic End to End Authorization (Part 2)

In my previous post, I talked about the importance of an end to end authorization system. I had an open source project here a while ago for this purpose. I describe the framework with some example here. An old saying says that a piece of code talk more than 1000 words:

Below is a sample class annotated with different authentication attributes.

<pre>
[Grant(Roles = new[] { "Everyone" }, Permission = Permission.View)]
[Grant(Roles = new[] { "QLDAdmin", "NSWAdmin" }, Permission = Permission.View | Permission.Edit | Permission.Create)]
[Grant(Roles = new[] { "Deleter" }, Permission = Permission.Delete)]
[Grant(Roles = new[] { "God" }, Permission = Permission.View | Permission.Own)]
[Deny(Roles = new[] { "NSWAdmin" }, Permission = Permission.Edit)]
[AuthorizationCustom(CustomType = typeof(TestObject), Method = "OnlyQld")]
public class TestObject
{
[Grant(Roles = new[] { "Everyone" }, Permission = Permission.View)]
[Deny(Roles = new[] { "God" }, Permission = Permission.View)]
public int YouCanSeeMe { get; set; }

[Grant(Roles = new[] { "Everyone" }, Permission = Permission.Edit)]
[Deny(Roles = new[] { "God" }, Permission = Permission.Edit | Permission.View)]
public string YouCanEditMe { get; set; }

[Deny(Roles = new[] { "Everyone" }, Permission = Permission.View)]
public string YouCanNotSeeMe { get; set; }

[Deny(Roles = new[] { "Everyone" }, Permission = Permission.Edit)]
public string YouCanSeeMeButNotEditMe { get; set; }

[Grant(Roles = new[] { "*" }, Permission = Permission.View)]
public string EverybodyCanSeeMe { get; set; }

public string[] States { get; set; }

public static Permission OnlyQld( IPrincipal pri, object instance )
{
var user = pri as TestUser;
if (user.Roles.Any(r => r.Contains("QLD"))
&&
(((TestObject)instance).States != null && ((TestObject)instance).States.Contains("QLD"))
)
return Permission.All;
return Permission.None;
}
}

</pre>

Next step is to check the permission for them in different scenarios:

<pre>[TestMethod()]
public void GetObjectLevelPremissionTest()
{
IMetadataClassProvider metadataProvider = new SelfMetadata();
var to = new TestObject();
var everyone = new TestUser() { Roles = new[] { "Everyone" } };
var god = new TestUser() { Roles = new[] { "God" } };
var adminNsw = new TestUser() { Roles = new[] { "NSWAdmin" } };
var adminQld = new TestUser() { Roles = new[] { "QLDAdmin" } };
var userWA = new TestUser() { Roles = new[] { "WAuser" } };
var userQLD = new TestUser() { Roles = new[] { "QLDuser" } };

var actual = PermissionHelper.GetObjectLevelPremission(metadataProvider, typeof(TestObject), to, everyone);
Assert.AreEqual(true, actual.Key.HasFlag(Permission.View));
Assert.AreEqual(false, actual.Key.HasFlag(Permission.Edit));
Assert.AreEqual(false, actual.Key.HasFlag(Permission.Create));

actual = PermissionHelper.GetObjectLevelPremission(metadataProvider, typeof(TestObject), to, god);
Assert.AreEqual(true, actual.Key.HasFlag(Permission.Own));

actual = PermissionHelper.GetObjectLevelPremission(metadataProvider, typeof(TestObject), to, adminNsw);
Assert.AreEqual(false, actual.Key.HasFlag(Permission.Edit));
Assert.AreEqual(true, actual.Key.HasFlag(Permission.View));

actual = PermissionHelper.GetObjectLevelPremission(metadataProvider, typeof(TestObject), to, adminQld);
Assert.AreEqual(true, actual.Key.HasFlag(Permission.Edit));
Assert.AreEqual(true, actual.Key.HasFlag(Permission.View));

to = new TestObject() { States = new[] { "QLD", "NSW" } };
actual = PermissionHelper.GetObjectLevelPremission(metadataProvider, typeof(TestObject), to, userWA);
Assert.AreEqual(false, actual.Key.HasFlag(Permission.View));

actual = PermissionHelper.GetObjectLevelPremission(metadataProvider, typeof(TestObject), to, userQLD);
Assert.AreEqual(true, actual.Key.HasFlag(Permission.View));
Assert.AreEqual(true, actual.Key.HasFlag(Permission.Create));

actual = PermissionHelper.GetObjectLevelPremission(metadataProvider, typeof(TestObject), to, adminQld);
Assert.AreEqual(true, actual.Key.HasFlag(Permission.Delete));
Assert.AreEqual(true, actual.Key.HasFlag(Permission.View));
}</pre>

Above code tests the permissions at the object level. We can get permissions at property level also in an array:

<pre>[TestMethod()]
public void GetPropertyLevelPremissionsTest()
{
IMetadataClassProvider metadataProvider = new SelfMetadata();
var to = new TestObject();
var everyone = new TestUser() { Roles = new[] { "Everyone" } };
var god = new TestUser() { Roles = new[] { "God" } };
var anon = new TestUser() { Roles = new string[0] };

var parent = PermissionHelper.GetObjectLevelPremission(metadataProvider, typeof(TestObject), to, everyone);
var actual = PermissionHelper.GetPropertyLevelPremissions(metadataProvider, parent, typeof(TestObject), to, everyone);
Assert.AreEqual(true, actual["YouCanSeeMe"].Key.HasFlag(Permission.View));
Assert.AreEqual(false, actual["YouCanSeeMe"].Key.HasFlag(Permission.Edit));
Assert.AreEqual(false, actual["YouCanSeeMe"].Key.HasFlag(Permission.Create));
Assert.AreEqual(false, actual["YouCanNotSeeMe"].Key.HasFlag(Permission.View));
Assert.AreEqual(true, actual["YouCanSeeMeButNotEditMe"].Key.HasFlag(Permission.View));
Assert.AreEqual(false, actual["YouCanSeeMeButNotEditMe"].Key.HasFlag(Permission.Edit));

parent = PermissionHelper.GetObjectLevelPremission(metadataProvider, typeof(TestObject), to, god);
actual = PermissionHelper.GetPropertyLevelPremissions(metadataProvider, parent, typeof(TestObject), to, god);
Assert.AreEqual(false, actual["YouCanSeeMe"].Key.HasFlag(Permission.View));
Assert.AreEqual(false, actual["YouCanEditMe"].Key.HasFlag(Permission.View));
Assert.AreEqual(false, actual["YouCanEditMe"].Key.HasFlag(Permission.Edit));
Assert.AreEqual(false, actual.ContainsKey("YouCanNotSeeMe"));

parent = null;
actual = PermissionHelper.GetPropertyLevelPremissions(metadataProvider, parent, typeof(TestObject), to, everyone);
Assert.AreEqual(true, actual["YouCanSeeMe"].Key.HasFlag(Permission.View));

parent = PermissionHelper.GetObjectLevelPremission(metadataProvider, typeof(TestObject), to, anon);
actual = PermissionHelper.GetPropertyLevelPremissions(metadataProvider, parent, typeof(TestObject), to, anon);
Assert.AreEqual(true, actual["EverybodyCanSeeMe"].Key.HasFlag(Permission.View));
}</pre>

Don’t get too excited. So far we haven’t seen much. The real beauty is when we get to automatically filter collections and set the client view visibility using the framework with one line of code.

To be continued…

Automatic End to End Authorization (Part 1)

Authorization is still a dilemma in development of business applications. ASP.Net provides the membership provider which supports user/role membership and domain service also supports it so you can use UserContext in the client side and User.IsInRole() gives you the user’s role memberships.

This looks good, but is certainly not enough. First of all authorization can be quiet complex such that authorization rules should be applied on a record-by-record basis and also the authorization rules can be more complicated than a single IsInRole check. For example a use in certain rolls can access a filed of a record only if the state of the user (e.g. Queensland) is similar to the state of the object (e.g. Pharmacy in Queensland).

Currently, you have to implement authorization on the server-side. Then you have to implement the same thing in your database using SQL techniques and you have to implement all the scraps again in the client side to figure out which filed or views are visible and which are read only!!! This is owe-full, isn’t it?

The way I would like to do all my authorization is as follows:

  1. In the service side I want to be able to assign authorization attributes to the model’s metadata, (similar to authorized attribute in MVC which you can apply to controllers) . I should be able to have custom authorizations.
  2. In the service side for Queries, I want to have a filter athorized method, which get’s an IEnumerable of roles and/or users and filters the records, it should remove the records that user can not have access to, scrap the fileds that user has no access to (if he has access to the record) and attach an access attribute to the whole collection and each individual field (if different from the collection).
  3. In the service side for Commands, I want to have a check authorized method, which get’s an IEnumerable of roles and/or users, and the object which is supposed to be changed and returns a boolean identifying if is allowed or not.
  4. It would be great if in the service side, exists an IsAllowed method which get’s the data context and a list of users and roles and returns if the modifications in the context can be authorized.
  5. In the client side, there should be an attribute attached to each returned object from the service. The attribute contains the fields the accessible fields. For example “Name” is visible and read-only, “DateOfBirth” is visible and editable, etc. This attribute can be used to change the view appearance for visible and read-only fields. For MVVM, the visibility and edit-ability of parts of a view can be bound to these attributes (of course through a method or using a converter).
  6. In the client side, there should be a service that generates a dummy empty object with it’s authorization attributes to be bound o the view for creating a new object.
  7. In the Database side,  a function should be automatically generated, to do the authorization. Although this may look impossible in presence of custom authorization, there are ways to do that: First, to use a provider model and force user to implement the same custom authorization as SQL function. Second, (only for SQL Server) use CLR and create a CLR class that uses the same service side methods for this reason.

Extra benefit of using this approach is that, the authorization rules can be changed without any change to the client side. Full loose coupling!

Note that although this approach provide full end to end authorization by only defining rules at one place (if implemented completely) effectively, it can not be efficient (specially in DB side). User still should use their own personalized queries to query large datasets. However, his job can be simplified by shortlisting the results by more general rules, and fine tune authorization by filtering using the generated authorization functions.

I  try to implement this in my spare time and update implementation details in the blog.

ASP.Net MVVM

MVVM tend to be a very pleasant pattern for rich internet applications. After working with Silverlight for a while I feel this pattern is a great pattern, is much easier to work in compare to MVC but has almost all of the benefits. However, the whole MVVM pattern becomes possible because there are such wonderful binding capabilities in xaml.

I was thinking about developing a website with MVVM pattern, and bringing at least some of the goodness of MVVM to web pages. It might look a bit eccentric at start, but considering all the power that ASP.Net web forms and WCF gives us, it is certainly possible and worth a try. The only real limitation is that the ViewModel code should be in JavaScrip, because it is being run on the client side. I think some real man should someday jump in GWT source code and start a similar project that compiles C# into JavaScript, so that C# developers can keep writing in C# for the client side. However, till that time all the client code should be in JavaScript.

Here is the big picture: Lets have a bunch of WCF services that return JSON objects. Same as when you work with WPF or Silverlight (but services are configured to return JSON instead of binary). On the view end, we have the normal ASP.Net web form which consist of only following controls: A XMLDataSource and a bunch of DataBound controls. All the controls in the web form are bound to the XMLDataSource. We have our View and Model handy, but the missing part of the puzzle is ViewModel and binding it to the View.

The default (and possibly optimum) for the MVVM is that ViewModel should be a flattened representation of the model. Thus, the only work here is to provide a set of JavaScript helpers to simplify following tasks:

1- Loading model from the Service (jQuery does this)

2- Binding the ViewModel JavaScript object to the XMLDataSource through Web Form client side scripts. (*The challenge)

3- Some pattern similar to INotifyPropertyChange for JavaScript. (Not too hard)

4- Some simple JavaScript data context manager to manage changes in the context and provide related update or insert commands (Lower priority)

5- Implement the Command pattern with jQuery.

6- Develop a pattern for binding nested views (Possibly a bootstrapper to load up ascx controls in AJAX placeholders).

By sorting the above problems out, we can have a pretty feasible and powerful MVVM pattern for web forms which is much nicer to implement than MVC and much more sensible than Web Forms. There would be still limitations such as  binding controls together, but we can live without them.

I am not going to start this project anytime soon, but I will be really happy if some of you take this idea and start something.