» 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.
|