Instantly share code, notes, and snippets.
Last active
October 31, 2023 08:37
-
Star
4
(4)
You must be signed in to star a gist -
Fork
1
(1)
You must be signed in to fork a gist
-
Save meziantou/10603804 to your computer and use it in GitHub Desktop.
Custom `AuthorizeAttribute` that allows boolean operators such as AND, OR, XOR, NOT
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/// <summary> | |
/// [CustomAuthorize(Roles = "A && (!B || C) ^ D")] | |
/// </summary> | |
public class CustomAuthorizeAttribute : AuthorizeAttribute | |
{ | |
/* | |
* Exp -> SubExp '&&' Exp // AND | |
* Exp -> SubExp '||' Exp // OR | |
* Exp -> SubExp '^' Exp // XOR | |
* SubExp -> '(' Exp ')' | |
* SubExp -> '!' Exp // NOT | |
* SubExp -> RoleName | |
* RoleName -> [a-z0-9] | |
*/ | |
abstract class Node | |
{ | |
public abstract bool Eval(IPrincipal principal); | |
public abstract void PrettyPrint(TextWriter writer); | |
public string PrettyPrint() | |
{ | |
using (StringWriter writer = new StringWriter()) | |
{ | |
PrettyPrint(writer); | |
return writer.ToString(); | |
} | |
} | |
} | |
abstract class UnaryNode : Node | |
{ | |
private readonly Node _expression; | |
public Node Expression | |
{ | |
get { return _expression; } | |
} | |
protected UnaryNode(Node expression) | |
{ | |
_expression = expression; | |
} | |
} | |
abstract class BinaryNode : Node | |
{ | |
private readonly Node _leftExpression; | |
private readonly Node _rightExpression; | |
public Node LeftExpression | |
{ | |
get { return _leftExpression; } | |
} | |
public Node RightExpression | |
{ | |
get { return _rightExpression; } | |
} | |
protected BinaryNode(Node leftExpression, Node rightExpression) | |
{ | |
_leftExpression = leftExpression; | |
_rightExpression = rightExpression; | |
} | |
} | |
class AndNode : BinaryNode | |
{ | |
public AndNode(Node leftExpression, Node rightExpression) | |
: base(leftExpression, rightExpression) | |
{ | |
} | |
public override bool Eval(IPrincipal principal) | |
{ | |
return LeftExpression.Eval(principal) && RightExpression.Eval(principal); | |
} | |
public override void PrettyPrint(TextWriter writer) | |
{ | |
writer.Write("("); | |
LeftExpression.PrettyPrint(writer); | |
writer.Write(" && "); | |
RightExpression.PrettyPrint(writer); | |
writer.Write(")"); | |
} | |
} | |
class OrNode : BinaryNode | |
{ | |
public OrNode(Node leftExpression, Node rightExpression) | |
: base(leftExpression, rightExpression) | |
{ | |
} | |
public override bool Eval(IPrincipal principal) | |
{ | |
return LeftExpression.Eval(principal) || RightExpression.Eval(principal); | |
} | |
public override void PrettyPrint(TextWriter writer) | |
{ | |
writer.Write("("); | |
LeftExpression.PrettyPrint(writer); | |
writer.Write(" || "); | |
RightExpression.PrettyPrint(writer); | |
writer.Write(")"); | |
} | |
} | |
class XorNode : BinaryNode | |
{ | |
public XorNode(Node leftExpression, Node rightExpression) | |
: base(leftExpression, rightExpression) | |
{ | |
} | |
public override bool Eval(IPrincipal principal) | |
{ | |
return LeftExpression.Eval(principal) ^ RightExpression.Eval(principal); | |
} | |
public override void PrettyPrint(TextWriter writer) | |
{ | |
writer.Write("("); | |
LeftExpression.PrettyPrint(writer); | |
writer.Write(" ^ "); | |
RightExpression.PrettyPrint(writer); | |
writer.Write(")"); | |
} | |
} | |
class NotNode : UnaryNode | |
{ | |
public NotNode(Node expression) | |
: base(expression) | |
{ | |
} | |
public override bool Eval(IPrincipal principal) | |
{ | |
return !Expression.Eval(principal); | |
} | |
public override void PrettyPrint(TextWriter writer) | |
{ | |
writer.Write("("); | |
writer.Write("!"); | |
Expression.PrettyPrint(writer); | |
writer.Write(")"); | |
} | |
} | |
class RoleNode : Node | |
{ | |
private readonly string _roleName; | |
public string RoleName | |
{ | |
get { return _roleName; } | |
} | |
public RoleNode(string roleName) | |
{ | |
_roleName = roleName; | |
} | |
public override bool Eval(IPrincipal principal) | |
{ | |
return principal.IsInRole(RoleName); | |
} | |
public override void PrettyPrint(TextWriter writer) | |
{ | |
writer.Write("("); | |
writer.Write(RoleName); | |
writer.Write(")"); | |
} | |
} | |
static Node Parse(string text) | |
{ | |
if (text == null) throw new ArgumentNullException("text"); | |
List<string> tokens = new List<string>(); | |
StringBuilder sb = new StringBuilder(); | |
for (int i = 0; i < text.Length; i++) | |
{ | |
char c = text[i]; | |
switch (c) | |
{ | |
case ' ': | |
case '\t': | |
case '\r': | |
case '\n': | |
if (sb.Length == 0) | |
continue; | |
sb.Append(c); | |
break; | |
case '(': | |
case ')': | |
case '^': | |
case '!': | |
if (sb.Length > 0) | |
{ | |
tokens.Add(sb.ToString()); | |
sb.Clear(); | |
} | |
tokens.Add(c.ToString(CultureInfo.InvariantCulture)); | |
break; | |
case '&': | |
case '|': | |
if (sb.Length != 0) | |
{ | |
char prev = sb[sb.Length - 1]; | |
if (c == prev) // && or || | |
{ | |
sb.Remove(sb.Length - 1, 1); // remove last char | |
tokens.Add(sb.ToString().Trim()); | |
sb.Clear(); | |
tokens.Add(c == '&' ? "&&" : "||"); | |
break; | |
} | |
} | |
sb.Append(c); | |
break; | |
default: | |
sb.Append(c); | |
break; | |
} | |
} | |
if (sb.Length > 0) | |
{ | |
tokens.Add(sb.ToString()); | |
} | |
return Parse(tokens.ToArray()); | |
} | |
static Node Parse(string[] tokens) | |
{ | |
int index = 0; | |
return ParseExp(tokens, ref index); | |
} | |
static Node ParseExp(string[] tokens, ref int index) | |
{ | |
Node leftExp = ParseSubExp(tokens, ref index); | |
if (index >= tokens.Length) | |
return leftExp; | |
string token = tokens[index]; | |
if (token == "&&") | |
{ | |
index++; | |
Node rightExp = ParseExp(tokens, ref index); | |
return new AndNode(leftExp, rightExp); | |
} | |
else if (token == "||") | |
{ | |
index++; | |
Node rightExp = ParseExp(tokens, ref index); | |
return new OrNode(leftExp, rightExp); | |
} | |
else if (token == "^") | |
{ | |
index++; | |
Node rightExp = ParseExp(tokens, ref index); | |
return new XorNode(leftExp, rightExp); | |
} | |
else | |
{ | |
throw new Exception("Expected '&&' or '||' or '^' or EOF"); | |
} | |
} | |
static Node ParseSubExp(string[] tokens, ref int index) | |
{ | |
string token = tokens[index]; | |
if (token == "(") | |
{ | |
index++; | |
Node node = ParseExp(tokens, ref index); | |
if (tokens[index] != ")") | |
throw new Exception("Expected ')'"); | |
index++; // Skip ')' | |
return node; | |
} | |
else if (token == "!") | |
{ | |
index++; | |
Node node = ParseExp(tokens, ref index); | |
return new NotNode(node); | |
} | |
else | |
{ | |
index++; | |
return new RoleNode(token); | |
} | |
} | |
private Node _expression; | |
protected override bool AuthorizeCore(HttpContextBase httpContext) | |
{ | |
if (httpContext == null) throw new ArgumentNullException("httpContext"); | |
IPrincipal user = httpContext.User; | |
if (!user.Identity.IsAuthenticated) | |
{ | |
return false; | |
} | |
if (_expression == null) | |
{ | |
_expression = Parse(Roles); | |
} | |
if (_expression != null) | |
{ | |
return _expression.Eval(user); | |
} | |
return true; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Hello @meziantou!
Firstly I want to thank you for your work! It helps me a lot to understand and write my own boolean parser. I translate parts of yours in python for one of my project.
However I find some parts of code that could be improved in my opinion.
SubExp -> '!' Exp
. This means that the not operator will apply to the complete following expression, whereas the main behaviour for boolean parser would be to apply to only a subexpression (a rolename or an expression between parenthesis). I think the rule should beSubExp -> '!'SubExp
and could be corrected in the code by replacing line 305 by:I hope this could be a good contribution to your code :)