module AuthorizationNext::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

parse_and( str ) click to toggle source
# File lib/authorization_next/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
parse_authorization_expression( str ) click to toggle source
# File lib/authorization_next/publishare/parser.rb, line 126
def parse_authorization_expression( str )
  @stack = []       
  raise AuthorizationExpressionInvalid, "Cannot parse authorization (#{str})" if not parse_expr( str )
  return @stack.pop
end
parse_expr( str ) click to toggle source
# File lib/authorization_next/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
parse_not( str ) click to toggle source
# File lib/authorization_next/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
parse_or( str ) click to toggle source
# File lib/authorization_next/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
parse_parenthesis( str ) click to toggle source

Descend down parenthesis (allow up to 5 levels of nesting)

# File lib/authorization_next/publishare/parser.rb, line 167
def parse_parenthesis( str )
  str =~ PARENTHESES_REGEX ? parse_expr( $1 ) : false
end
parse_role( str ) click to toggle source

Parse <role> of the User-like object

# File lib/authorization_next/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( str ) click to toggle source

Parse <role> of <model>

# File lib/authorization_next/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
parse_term( str ) click to toggle source
# File lib/authorization_next/publishare/parser.rb, line 171
def parse_term( str )
  parse_role_of_model( str ) or
  parse_role( str )
end