Skip to content

Instantly share code, notes, and snippets.

@PaulStovell
Created March 27, 2012 11:58
Show Gist options
  • Save PaulStovell/2215304 to your computer and use it in GitHub Desktop.
Save PaulStovell/2215304 to your computer and use it in GitHub Desktop.
A permissions model
using System;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
namespace Auth
{
[TestFixture]
public class RuleSetFixture
{
private RuleSet rules;
[Test]
public void ShouldAllowAdminsToDeployToProduction()
{
Rules(
Allow.UsersIn("Admins").To("deploy-release").For("environments/production")
);
IsGranted(WhenIAm.In("Power Users", "Admins").AndIWantTo("deploy-release").Regarding("environments/production"));
}
[Test]
public void ShouldAllowAnyoneToDeployToUatButOnlyAdminsToProduction()
{
Rules(
Allow.UsersIn("Admins").To("deploy-release").For("environments/production"),
Allow.UsersIn("Everyone").To("deploy-release").For("environments/uat"));
IsGranted(WhenIAm.In("Everyone").AndIWantTo("deploy-release").Regarding("environments/uat"));
IsDenied(WhenIAm.In("Everyone").AndIWantTo("deploy-release").Regarding("environments/production"));
IsGranted(WhenIAm.In("Everyone", "Admins").AndIWantTo("deploy-release").Regarding("environments/uat"));
IsGranted(WhenIAm.In("Everyone", "Admins").AndIWantTo("deploy-release").Regarding("environments/production"));
}
[Test]
public void ShouldOnlyAllowTeamMembersToDeploy()
{
Rules(
Allow.UsersIn("Releasers").To("deploy-release").For("environments/production"),
Deny.UsersIn("TeamA").To("deploy-release").For("project/1").And("environments/production"),
Allow.UsersIn("TeamA").To("deploy-release").For("project/1"),
Allow.UsersIn("TeamB").To("deploy-release").For("environments/uat"));
IsDenied(WhenIAm.In("TeamA").AndIWantTo("deploy-release").Regarding("environments/production"));
IsGranted(WhenIAm.In("Releasers").AndIWantTo("deploy-release").Regarding("environments/production"));
IsGranted(WhenIAm.In("TeamA", "Releasers").AndIWantTo("deploy-release").Regarding("environments/production"));
IsGranted(WhenIAm.In("TeamA").AndIWantTo("deploy-release").Regarding("project/1"));
IsGranted(WhenIAm.In("TeamA").AndIWantTo("deploy-release").Regarding("project/1", "environments/uat"));
IsDenied(WhenIAm.In("TeamA").AndIWantTo("deploy-release").Regarding("project/1", "environments/production"));
IsDenied(WhenIAm.In("TeamB").AndIWantTo("deploy-release").Regarding("project/1"));
}
[Test]
public void ShouldApplyRulesBasedOnOrder()
{
Rules(
Deny.UsersIn("TeamA").To("deploy-release").For("project/1").And("environments/production"),
Allow.UsersIn("Releasers").To("deploy-release").For("environments/production"));
// This user is in both groups, but since the 'deny' rule is hit first, it is denied
IsDenied(WhenIAm.In("TeamA", "Releasers").AndIWantTo("deploy-release").Regarding("environments/production", "project/1"));
Rules(
Allow.UsersIn("Releasers").To("deploy-release").For("environments/production"),
Deny.UsersIn("TeamA").To("deploy-release").For("project/1").And("environments/production"));
// With the rules in a different order, he now has access
IsGranted(WhenIAm.In("TeamA", "Releasers").AndIWantTo("deploy-release").Regarding("environments/production"));
}
[Test]
public void ShouldDenyWhenNoRulesApply()
{
Rules(
Allow.UsersIn("Releasers").To("deploy-release").For("environments/production"));
IsDenied(WhenIAm.In("Releasers").AndIWantTo("do-something-else").Regarding("environments/production", "project/1"));
IsDenied(WhenIAm.In("Releasers").AndIWantTo("deploy-release").Regarding("environments/uat"));
IsDenied(WhenIAm.In("Unrecognised").AndIWantTo("deploy-release").Regarding("anything"));
}
protected void Rules(params RuleBuilder[] ruleBuilders)
{
rules = new RuleSet(ruleBuilders.Select(r => r.Build()).ToArray());
}
protected void IsGranted(AuthorizationRequest request)
{
Assert.IsTrue(rules.IsAllowed(request));
}
protected void IsDenied(AuthorizationRequest request)
{
Assert.IsFalse(rules.IsAllowed(request));
}
}
public static class Allow
{
public static PartialRuleBuilder UsersIn(string group)
{
return new PartialRuleBuilder(true, group);
}
}
public static class Deny
{
public static PartialRuleBuilder UsersIn(string group)
{
return new PartialRuleBuilder(false, group);
}
}
public class PartialRuleBuilder
{
private readonly bool allow;
private readonly string groupName;
public PartialRuleBuilder(bool allow, string groupName)
{
this.allow = allow;
this.groupName = groupName;
}
public RuleBuilder To(string operation)
{
return new RuleBuilder(allow, groupName, operation);
}
}
public class RuleBuilder
{
private readonly bool allow;
private readonly string groupName;
private readonly string operation;
private readonly List<string> documentIds = new List<string>();
public RuleBuilder(bool allow, string groupName, string operation)
{
this.allow = allow;
this.groupName = groupName;
this.operation = operation;
}
public RuleBuilder For(string documentId)
{
documentIds.Add(documentId);
return this;
}
public RuleBuilder And(string documentId)
{
documentIds.Add(documentId);
return this;
}
public Rule Build()
{
return new Rule(allow, groupName, operation, documentIds.ToArray());
}
}
public class RuleSet
{
private readonly Rule[] rules;
public RuleSet(params Rule[] rules)
{
this.rules = rules;
}
public bool IsAllowed(AuthorizationRequest request)
{
foreach (var rule in rules.Where(rule => rule.Operation == request.Operation && request.Groups.Contains(rule.GroupId)))
{
var notMet = rule.DocumentIds.Except(request.Documents).ToList();
if (notMet.Count == 0)
{
return rule.Allow;
}
}
return false;
}
}
public static class WhenIAm
{
public static AuthorizationRequest In(params string[] groups)
{
return new AuthorizationRequest(groups);
}
}
public class AuthorizationRequest
{
private readonly string[] groups;
private string operation;
private string[] documents;
public AuthorizationRequest(string[] groups)
{
this.groups = groups;
}
public string[] Groups
{
get { return groups; }
}
public string Operation
{
get { return operation; }
}
public string[] Documents
{
get { return documents; }
}
public AuthorizationRequest AndIWantTo(string operation)
{
this.operation = operation;
return this;
}
public AuthorizationRequest Regarding(params string[] documents)
{
this.documents = documents;
return this;
}
}
public class Rule
{
private readonly bool allow;
private readonly string groupId;
private readonly string operation;
private readonly string[] documentIds;
public Rule(bool allow, string groupId, string operation, string[] documentIds)
{
this.allow = allow;
this.groupId = groupId;
this.operation = operation;
this.documentIds = documentIds;
}
public bool Allow
{
get { return allow; }
}
public string GroupId
{
get { return groupId; }
}
public string Operation
{
get { return operation; }
}
public string[] DocumentIds
{
get { return documentIds; }
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment