// ----------------------------------------------------------------------------
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// ----------------------------------------------------------------------------
namespace Microsoft.Singularity.Security
{
using System;
using System.Collections;
using System.Text;
using Microsoft.Singularity.Security;
///
/// This class represents a policy engine for generating Access Control Lists.
/// The policy engine consists of a collection of policy rules applicable to paths
/// in a hierarchy. Given a resource path, the engine will generate an access
/// control list. If no suitable rule is found, the engine will return the null
/// Acl, which restricts all access.
///
/// When multiple rules apply to the given resource path, the engine will choose
/// the one that has the longest matching prefix/suffix (depending on the rule type).
///
/// This class is synchronized.
///
/// This class implements policy rules. A policy rule consists of
/// a resource expression and a collection of access control lists (one per
/// each supported access mode).
///
/// Resource expressions are path prefixes. Rules apply to the prefix presented
/// and all descendants.
///
/// ACL generators are Access Control Lists with placeholders. A placeholder is
/// substituted with an nth arc of the tested path. One specifies a placeholder
/// using {index}, where index is the index of the arc to be used.
///
/// Examples:
/// rule: /restricted/more
/// aclgenerator: {/users/{2}}
///
/// testpath: /restricted/more/aydan/test
///
/// Matching testpath against the rule will give: {/users/aydan}
///
public class PathPolicyEngine : IAclPolicy
{
// In the rule hashtable, all trailing "/" characters omitted from key.
// Root key is "".
private Hashtable! ruleTable;
public PathPolicyEngine()
{
ruleTable = new Hashtable();
base();
}
///
/// Add an ACL generator for the given resource
///
public void AddRule(string! resource, string! acl)
{
string key = SanitizeResource(resource);
Rule r = new Rule(acl);
lock (this)
// this asserts single-writer throughout all HTs
// we assume HTs are single-writer/multi-reader
{
object o = ruleTable[key];
if (o != null) {
Rule rr = (Rule) o;
rr.SetValid(false);
}
ruleTable[key] = r;
// add placeholder entries (toward the root of the hierarchy)
// so that callers to lookup can tell instantly when to terminate the
// search (e.g. due to the lack of a placeholder)
for (;;) {
key = PreviousKey(key);
if (key == null || ruleTable[key] != null) break;
ruleTable[key] = Rule.GetPlaceholder();
}
}
}
///
/// Obtain and ACL for the specified resource path
/// Method of IAclPolicy.
///
/// We assume here that all resource paths obey the following rules:
/// a) no instances of "//"
/// b) no trailing '/'
/// This is true for the Node.sg/AccessInstance.sg subclass.
///
public Acl LookupAndExpand(string! resource, out IAclRule rule)
{
Rule r = null;
string key = "";
while (key != null && ruleTable[key] != null) {
// if nothing in this slot, (e.g. no rules or placeholders) there are
// no more rules that match this resource
Rule rr = (Rule!) ruleTable[key];
if (!rr.Placeholder) {
r = rr;
}
if (key.Length == resource.Length || resource.Length == 1)
// special case here for "/"
break;
int i = resource.IndexOf('/', key.Length+1);
if (i < 0)
key = resource;
else
key = resource.Substring(0, i);
}
if (r != null) {
Acl acl = r.Expand(resource);
if (acl.val != null) {
rule = r;
return acl;
}
}
rule = null;
return Acl.nullAcl;
}
private string! SanitizeResource(string! s)
{
while (s.IndexOf("//") >= 0)
s = s.Replace("//", "/");
int i = s.Length;
while (i > 0 && s[i - 1] == '/') i--;
if (i != s.Length)
s = s.Substring(0, i);
return s;
}
private string PreviousKey(string! key)
{
int i = key.LastIndexOf('/');
if (i < 0)
return null;
return key.Substring(0, i);
}
internal class Rule : IAclRule
{
static int MaxReps = 5;
struct Replacement
{
public int start;
public int end;
public int narc;
}
bool valid;
bool placeholder;
string aclExpression;
Replacement[] reps;
int nreps;
static Rule! placeholderRule = new Rule();
public bool Valid { get { return this.valid; } }
public void SetValid(bool yes) { this.valid = yes; }
private Rule()
{
aclExpression = null;
reps = null;
nreps = 0;
valid = false;
placeholder = true;
}
public Rule(string! aclExpression)
{
this.aclExpression = aclExpression;
reps = null;
nreps = 0;
this.valid = true;
this.placeholder = false;
//this.SetGenerator();
}
public bool Placeholder {
get { return placeholder; }
}
public static Rule! GetPlaceholder() { return placeholderRule; }
public Acl Expand(string! resource)
{
if (aclExpression == null)
return Acl.nullAcl;
if (reps == null)
return new Acl(aclExpression);
int i;
int p = 0;
StringBuilder sb = new StringBuilder();
for (i = 0; i < nreps; i++) {
sb.Append(aclExpression.Substring(p, reps[i].start-p));
sb.Append(NthArc(resource, reps[i].narc));
p = reps[i].end+1;
}
sb.Append(aclExpression.Substring(p));
return new Acl(sb.ToString());
}
private string! NthArc(string! s, int index)
{
int i = 0;
int n = 0;
int j;
for (;;) {
j = s.IndexOf('/', i);
if (j < 0) break;
if (n++ == index) break;
i = j+1;
}
if (n != index)
return "";
if (j < 0)
return s.Substring(i);
return s.Substring(i, j);
}
private void SetGenerator()
{
Replacement[] treps = new Replacement[MaxReps];
int mode = 0;
int start = 0;
int tnreps = 0;
string tacl = aclExpression;
assert tacl != null;
// figure out the replacement set for this generator
for (int i = 0; i < tacl.Length; i++) {
char c = tacl[i];
switch (c) {
case '{':
if (mode == 0) {
mode = 1;
start = i;
}
break;
case '}':
if (mode == 1) {
mode = 0;
if (i > start + 1) {
// get the arc index that we have to replace
if (tnreps == MaxReps)
return; // too many replacements in this pattern
int index = Int32.Parse(tacl.Substring(start, i-start));
treps[tnreps].start = start;
treps[tnreps].end = i;
treps[tnreps].narc = index;
tnreps++;
}
}
break;
default:
if (mode != 0 && (c < '0' || c > '9')) {
mode = 0;
}
break;
}
}
if (nreps > 0) {
reps = treps;
nreps = tnreps;
}
}
}
}
}