using System;
using System.Collections.Generic;
using System.Text;
namespace ScrewTurn.Wiki.AclEngine {
///
/// Implements tools for evaluating permissions.
///
public static class AclEvaluator {
///
/// Decides whether a user, member of some groups, is authorized to perform an action on a resource.
///
/// The resource.
/// The action on the resource.
/// The user, in the form 'U.Name'.
/// The groups the user is member of, in the form 'G.Name'.
/// The available ACL entries for the resource.
/// The positive, negative, or indeterminate result.
/// If , , , or are null.
/// If , , are empty, or if equals .
public static Authorization AuthorizeAction(string resource, string action, string user, string[] groups, AclEntry[] entries) {
if(resource == null) throw new ArgumentNullException("resource");
if(resource.Length == 0) throw new ArgumentException("Resource cannot be empty", "resource");
if(action == null) throw new ArgumentNullException("action");
if(action.Length == 0) throw new ArgumentException("Action cannot be empty", "action");
if(action == AclEntry.FullControlAction) throw new ArgumentException("Action cannot be the FullControl flag", "action");
if(user == null) throw new ArgumentNullException("user");
if(user.Length == 0) throw new ArgumentException("User cannot be empty", "user");
if(groups == null) throw new ArgumentNullException("groups");
if(entries == null) throw new ArgumentNullException("entries");
// Simple ACL model
// Sort entries so that FullControl ones are at the bottom
// First look for an entry specific for the user
// If not found, look for a group that denies the permission
AclEntry[] sortedEntries = new AclEntry[entries.Length];
Array.Copy(entries, sortedEntries, entries.Length);
Array.Sort(sortedEntries, delegate(AclEntry x, AclEntry y) {
return x.Action.CompareTo(y.Action);
});
Array.Reverse(sortedEntries);
foreach(AclEntry entry in sortedEntries) {
if(entry.Resource == resource && (entry.Action == action || entry.Action == AclEntry.FullControlAction) && entry.Subject == user) {
if(entry.Value == Value.Grant) return Authorization.Granted;
else if(entry.Value == Value.Deny) return Authorization.Denied;
else throw new NotSupportedException("Entry value not supported");
}
}
// For each group, a decision is made
Dictionary groupFullControlGrant = new Dictionary();
Dictionary groupExplicitGrant = new Dictionary();
Dictionary groupFullControlDeny = new Dictionary();
foreach(string group in groups) {
foreach(AclEntry entry in entries) {
if(entry.Resource == resource && entry.Subject == group) {
if(!groupFullControlGrant.ContainsKey(group)) {
groupFullControlGrant.Add(group, false);
groupExplicitGrant.Add(group, false);
groupFullControlDeny.Add(group, false);
}
if(entry.Action == action) {
// Explicit action
if(entry.Value == Value.Grant) {
// An explicit grant only wins if there are no other explicit deny
groupExplicitGrant[group] = true;
}
else if(entry.Value == Value.Deny) {
// An explicit deny wins over all other entries
return Authorization.Denied;
}
}
else if(entry.Action == AclEntry.FullControlAction) {
// Full control, lower priority
if(entry.Value == Value.Deny) {
groupFullControlDeny[group] = true;
}
else if(entry.Value == Value.Grant) {
groupFullControlGrant[group] = true;
}
}
}
}
}
// Any explicit grant found at this step wins, because all explicit deny have been processed previously
bool tentativeGrant = false;
bool tentativeDeny = false;
foreach(string group in groupFullControlGrant.Keys) {
if(groupExplicitGrant[group]) return Authorization.Granted;
if(groupFullControlGrant[group] && !groupFullControlDeny[group]) tentativeGrant = true;
if(!groupFullControlGrant[group] && groupFullControlDeny[group]) tentativeDeny = true;
}
if(tentativeGrant && !tentativeDeny) return Authorization.Granted;
else if(tentativeDeny) return Authorization.Denied;
else return Authorization.Unknown;
}
}
///
/// Lists legal authorization values.
///
public enum Authorization {
///
/// Authorization granted.
///
Granted,
///
/// Authorization denied.
///
Denied,
///
/// No information available.
///
Unknown
}
}