module AuthorizationNext::Base::EvalParser

Public Instance Methods

parse_authorization_expression( str ) click to toggle source

Parses and evaluates an authorization expression and returns true or false.

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+/ | /'.*'/

Instead of doing recursive descent parsing (not so fun when we support nested parentheses, etc), we let Ruby do the work for us by inserting the appropriate permission calls and using eval. This would not be a good idea if you were getting authorization expressions from the outside, so in that case (e.g. somehow letting users literally type in permission expressions) you'd be better off using the recursive descent parser in Module RecursiveDescentParser.

We search for parts of our authorization evaluation that match <role> or <role> <preposition> <model> and we ignore anything terminal in our grammar.

1) Replace all <role> <preposition> <model> matches. 2) Replace all <role> matches that aren't one of our other terminals ('not', 'or', 'and', or preposition) 3) Eval

# File lib/authorization_next/publishare/parser.rb, line 31
def parse_authorization_expression( str )
  if str =~ /[^A-Za-z0-9_:'\(\)\s]/
    raise AuthorizationExpressionInvalid, "Invalid authorization expression (#{str})"
    return false
  end
  @replacements = []
  expr = replace_temporarily_role_of_model( str )
  expr = replace_role( expr )
  expr = replace_role_of_model( expr )
  begin
    instance_eval( expr )
  rescue
    raise AuthorizationExpressionInvalid, "Cannot parse authorization (#{str})"
  end
end
process_role( role_name ) click to toggle source
# File lib/authorization_next/publishare/parser.rb, line 81
def process_role( role_name )
  return false if @current_user.nil?
  raise( UserDoesntImplementRoles, "User doesn't implement #has_role?" ) if not @current_user.respond_to? :has_role?
  @current_user.has_role?( role_name )
end
process_role_of_model( role_name, model_name ) click to toggle source
# File lib/authorization_next/publishare/parser.rb, line 75
def process_role_of_model( role_name, model_name )
  model = get_model( model_name )
  raise( ModelDoesntImplementRoles, "Model (#{model_name}) doesn't implement #accepts_role?" ) if not model.respond_to? :accepts_role?
  model.send( :accepts_role?, role_name, @current_user )
end
replace_role( str ) click to toggle source
# File lib/authorization_next/publishare/parser.rb, line 57
def replace_role( str )
  role_regex = '\s*(\'\s*(.+)\s*\'|([A-Za-z]\w*))\s*'
  parse_regex = Regexp.new(role_regex)
  str.gsub(parse_regex) do |match|
    if BOOLEAN_OPS.include?($3)
      " #{match} "
    else
      " process_role('#{$2 || $3}') "
    end
  end
end
replace_role_of_model( str ) click to toggle source
# File lib/authorization_next/publishare/parser.rb, line 69
def replace_role_of_model( str )
  str.gsub(/<(\d+)>/) do |match|
    @replacements[$1.to_i]
  end
end
replace_temporarily_role_of_model( str ) click to toggle source
# File lib/authorization_next/publishare/parser.rb, line 47
def replace_temporarily_role_of_model( str )
  role_regex = '\s*(\'\s*(.+)\s*\'|(\w+))\s+'
  model_regex = '\s+(:*\w+)'
  parse_regex = Regexp.new(role_regex + '(' + VALID_PREPOSITIONS.join('|') + ')' + model_regex)
  str.gsub(parse_regex) do |match|
    @replacements.push " process_role_of_model('#{$2 || $3}', '#{$5}') "
    " <#{@replacements.length-1}> "
  end
end