module Authorization::Base::RecursiveDescentParser
Parses and evaluates an authorization expression and returns true
or false
. This recursive descent parses uses two instance variables:
@stack --> a stack with the top holding the boolean expression resulting from the parsing
The authorization expression is defined by the following grammar:
<expr> ::= (<expr>) | not <expr> | <term> or <expr> | <term> and <expr> | <term> <term> ::= <role> | <role> <preposition> <model> <preposition> ::= of | for | in | on | to | at | by <model> ::= /:*\w+/ <role> ::= /\w+/ | /'.*'/
There are really two values we must track: (1) whether the expression is valid according to the grammar (2) the evaluated results –> true/false on the permission queries The first is embedded in the control logic because we want short-circuiting. If an expression has been parsed and the permission is false, we don't want to try different ways of parsing. Note that this implementation of a recursive descent parser is meant to be simple and doesn't allow arbitrary nesting of parentheses. It supports up to 5 levels of nesting. It also won't handle some types of expressions (A or B) and C, which has to be rewritten as C and (A or B) so the parenthetical expressions are in the tail.
Constants
- AND_PATTERN
- AND_REGEX
- MODEL_PATTERN
- NOT_PATTERN
- NOT_REGEX
- OPT_PARENTHESES_PATTERN
- OR_PATTERN
- OR_REGEX
- PARENTHESES_PATTERN
- PARENTHESES_REGEX
- ROLE_OF_MODEL_REGEX
- ROLE_PATTERN
- ROLE_REGEX
Public Instance Methods
# File lib/authorization/publishare/parser.rb, line 157 def parse_and( str ) if str =~ AND_REGEX can_parse = parse_expr( $1 ) and parse_expr( $8 ) @stack.push(@stack.pop & @stack.pop) if can_parse return can_parse end false end
# File lib/authorization/publishare/parser.rb, line 132 def parse_expr( str ) parse_parenthesis( str ) or parse_not( str ) or parse_or( str ) or parse_and( str ) or parse_term( str ) end
# File lib/authorization/publishare/parser.rb, line 140 def parse_not( str ) if str =~ NOT_REGEX can_parse = parse_expr( $1 ) @stack.push( !@stack.pop ) if can_parse end false end
# File lib/authorization/publishare/parser.rb, line 148 def parse_or( str ) if str =~ OR_REGEX can_parse = parse_expr( $1 ) and parse_expr( $8 ) @stack.push( @stack.pop | @stack.pop ) if can_parse return can_parse end false end
Descend down parenthesis (allow up to 5 levels of nesting)
# File lib/authorization/publishare/parser.rb, line 167 def parse_parenthesis( str ) str =~ PARENTHESES_REGEX ? parse_expr( $1 ) : false end
Parse <role> of the User-like object
# File lib/authorization/publishare/parser.rb, line 193 def parse_role( str ) if str =~ ROLE_REGEX role_name = $1 if @current_user.nil? @stack.push(false) else raise( UserDoesntImplementRoles, "User doesn't implement #has_role?" ) if not @current_user.respond_to? :has_role? @stack.push( @current_user.has_role?(role_name) ) end true else false end end
Parse <role> of <model>
# File lib/authorization/publishare/parser.rb, line 177 def parse_role_of_model( str ) if str =~ ROLE_OF_MODEL_REGEX role_name = $2 || $3 model_name = $5 model_obj = get_model( model_name ) raise( ModelDoesntImplementRoles, "Model (#{model_name}) doesn't implement #accepts_role?" ) if not model_obj.respond_to? :accepts_role? has_permission = model_obj.send( :accepts_role?, role_name, @current_user ) @stack.push( has_permission ) true else false end end
# File lib/authorization/publishare/parser.rb, line 171 def parse_term( str ) parse_role_of_model( str ) or parse_role( str ) end