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 } }