dynamic tools for .net developers™  LaMarvin Home
home » constable » faq » determinism

A word about non-deterministic authorization policies

Our experience shows that determinism is rare in modeling real-world business processes. Application users wear many hats and this often results in conflicting authorization rules.

Let's consider a very simple example:

[Visual Basic]
Dim policy As New AuthorizationPolicy

policy.States.AddNew("Default")

policy.Roles.AddNew("User")
policy.Roles.AddNew("Admin")

policy.Actions.AddNew("Logoff")
policy.Actions.AddNew("Shutdown")

policy.ActionRules.AddNew("Logoff", "User")
policy.ActionRules.AddNew("Shutdown", "User").IsEnabled = False

policy.ActionRules.AddNew("Logoff", "Admin")
policy.ActionRules.AddNew("Shutdown", "Admin")

[C#]
AuthorizationPolicy policy = new AuthorizationPolicy();

policy.States.AddNew("Default");

policy.Roles.AddNew("User");
policy.Roles.AddNew("Admin");

policy.Actions.AddNew("Logoff");
policy.Actions.AddNew("Shutdown");

policy.ActionRules.AddNew("Logoff", "User");
policy.ActionRules.AddNew("Shutdown", "User").IsEnabled = false;

policy.ActionRules.AddNew("Logoff", "Admin");
policy.ActionRules.AddNew("Shutdown", "Admin");
We've created a simple authorization policy with two roles - User and Admin. The User can execute the Logoff action, but it has the Shutdown action explicitly disabled. The Admin can execute both actions.

So what happens if we now try to execute the following code:
policy.ExecuteAction("Shutdown")
Of course, the answer depends on the current principal and its role membership. If the current principal is member of the Admin role, executing the Shutdown action is allowed. If the principal is member of the User role, executing the Shutdown action is disabled (and the policy.ExecuteAction("Shutdown") call throws an exception).

What happens if the current principal is member of both roles? The Shutdown action is allowed for the Admin role, but it is denied for the User role.

CAZE employs a "disabled wins" principle meaning that if there are conflicting authorization rules, the most restrictive of them is applied. Considering our example, this means that if the current principal is member of both User and Author roles, the Shutdown action is disabled and the policy.ExecuteAction("Shutdown") call will throw an authorization exception. For example:
[Visual Basic]
policy.CurrentPrincipal = New DynamicPrincipal(True)
policy.ExecuteAction("Shutdown") ' this call will throw an exception

[C#]
policy.CurrentPrincipal = new DynamicPrincipal(true);
policy.ExecuteAction("Shutdown"); // this call will throw an exception
The constructor used to initialize a new instance of the DynamicPrincipal class results in the principal being member of both User and Admin roles, thus making the Shutdown action disabled.

Duplicate action authorization rules

This is another non-deterministic pattern that is quite common in real world authorization policies. Two action authorization rules are duplicate if they refer to the same action and state, but specify a different target state. Let's consider a primitive, three-state document approval policy (not to be confused with the policy discussed in the tutorial):
[Visual Basic]
Dim policy As New Policy("DocumentApproval")

policy.States.AddNew("Editing")
policy.States.AddNew("Reviewing")
policy.States.AddNew("Published")

policy.Roles.AddNew("User")
policy.Roles.AddNew("CEO")

policy.Actions.AddNew("Send")

policy.ActionRules.AddNew("Send", "User", "Editing", "Reviewing")
policy.ActionRules.AddNew("Send", "CEO", "Editing", "Published")

[C#]
AuthorizationPolicy policy = new AuthorizationPolicy("DocumentApproval");

policy.States.AddNew("Editing");
policy.States.AddNew("Reviewing");
policy.States.AddNew("Published");

policy.Roles.AddNew("User");
policy.Roles.AddNew("CEO");

policy.Actions.AddNew("Send");

policy.ActionRules.AddNew("Send", "User", "Editing", "Reviewing");
policy.ActionRules.AddNew("Send", "CEO", "Editing", "Published");
The Editing state means the document is being worked on. The Reviewing state means that the document is being reviewed. The Published state means the document has been released to the public. The idea here is that if a principal in the User role executes the Send action, the document should be reviewed. However, if a principal is in the CEO role, the document doesn't have to be reviewed and it can be immediately published.

The policy is non-deterministic, because the two authorization rules are duplicate - they both refer to the Send action in the Editing state, but they result in different target states. And while this is not a bad design in general, you should be aware of the implications when coding against the policy. For example, if you use the ExecuteAction method overload that takes the action Id (or the Action object itself), your code might fail:
[Visual Basic]
policy.ExecuteAction("Send") ' does it work?

[C#]
policy.ExecuteAction("Send"); // does it work?
The above call will work correctly if the current principal is in either User or CEO role. It won't work however, if the principal is in both roles. This might seem confusing at first, because apparently the principal is allowed to execute the Send action. The problem lies in the non-determinism - the CAZE policy will refuse to execute an action if the action's target state cannot be exactly determined. That is, the current authorization context causes the Send action to by allowed by two rules (for the User and the CEO roles). Because the two rules have different target states, the policy will fail to execute the action.

In situations like these, you'll have to use the other ExecuteAction overload - the one that accepts the actual ActionRule as its argument:
[Visual Basic]
' Execute the Send action defined for CEOs.
Dim sendAsCeoRule As ActionRule = _
  policy.ActionRules.Lookup("Send", "CEO", "Editing")
policy.ExecuteAction(sendAsCeoRule)

[C#]
// Execute the Send action defined for CEOs.
ActionRule sendAsCeoRule = 
  policy.ActionRules.Lookup("Send", "CEO", "Editing");
policy.ExecuteAction(sendAsCeoRule);
First, we've looked up the authorization rule for the CEO role and then the associated Send action was executed moving the policy to the Published state. The call was deterministic because we've chosen the exact rule for the associated Send action (and the call will always succeed as long as the current principal if member of the CEO role and the current state is Editing).

The problem is that sometimes you might really need to use the ExecuteAction(string) overload, perhaps because you have only a string Id of the action you want to execute. Because due to non-determinism, calling the ExecuteAction(string) might fail even if the action in question is enabled, you'll have to do some more work in order to avoid throwing false "action not allowed" exceptions:
[Visual Basic]
Dim actionID As String = "Send"

Dim enabledRules() As ActionRule = 
  policy.GetExecutableActionRules().Lookup(actionID)

if (enabledRules.Length == 0)
  Throw New System.Security.SecurityException(actionID & " not allowed.")

policy.ExecuteAction(enabledRules(0))

[C#]
string actionID = "Send";

ActionRule[] enabledRules = 
  policy.GetExecutableActionRules().Lookup(actionID);

if (enabledRules.Length == 0)
  throw new System.Security.SecurityException(actionID + " not allowed.");

policy.ExecuteAction(enabledRules[0]);
The GetExecutableActionRules method returns all executable rules for the current principal and the current state. The Lookup method of the returned ActionRuleCollection instance filters out only rules for the Send action. If there are one or more such rules, the first one is selected and the action is executed. If there isn't any rule available, it means that the action is disabled and a distinguished exception is thrown.

If you feel like the above non-deterministic behavior should be default for the CAZE policy, don't worry. You can derive your own implementation and by overriding one method, you can make the non-deterministic behavior the default:
[Visual Basic]
Public Class NondeterministicAuthorizationPolicy
  Inherits Policy

  Protected Overrides Sub ResolveExecutableActionRules( _
    ByVal action As Action, _ 
    ByVal authorizedRules As ActionRuleCollection, _
    ByVal resolveMultipleTargetStates As Boolean, _
    ByVal resolvedRules As ActionRuleCollection)
    
    If authorizedRules.Count > 0 Then
      resolvedRules.Add(authorizedRules(0))
    End If
  End Sub

End Class

[C#]
public class NondeterministicAuthorizationPolicy : Policy
{
  protected override void ResolveExecutableActionRules(
    Action action, 
    ActionRuleCollection authorizedRules, 
    bool resolveMultipleTargetStates, 
    ActionRuleCollection resolvedRules)
  {
    if (authorizedRules.Count > 0)
      resolvedRules.Add(authorizedRules[0]);
  }
}
The job of the ResolveExecutableActionRules is to fill the resolvedRules collection with entries from the authorizedRules collection. For deterministic policies, the authorizedRules collection will contain always at most one element. For non-deterministic policies, the authorizedRules collection can contain more than one element. The above implementation simply adds the first authorized rule element to the output resolvedRules collection, making the policy execute the associated action and move to the associated target state.

Visibility of actions and properties

The RuleBase class, which is a common base class for ActionRule and PropertyRule classes, defines also the IsVisible property with the semantics meant to reflect "visibility" of the associated action or property to the application's end-user. However, this is simply a hint that must be honored by the application code (i.e. to display or not to display a particular action or property to the user).The IsVisible property is not used by the CAZE for authorization decisions; CAZE uses only the IsEnabled property to implement the above-described "disabled wins" behavior.



© 2002-2007 LaMarvin. All Rights Reserved.     [Terms of use]     [Privacy] This site doesn't open new browser windows.