Module: Constellation::Authorization::Base::RecursiveDescentParser
- Defined in:
- lib/constellation/authorization/parser.rb
Overview
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.
Constant Summary collapse
- OPT_PARENTHESES_PATTERN =
'(([^()]|\(([^()]|\(([^()]|\(([^()]|\(([^()]|\(([^()])*\))*\))*\))*\))*\))*)'
- PARENTHESES_PATTERN =
'\(' + OPT_PARENTHESES_PATTERN + '\)'
- NOT_PATTERN =
'^\s*not\s+' + OPT_PARENTHESES_PATTERN + '$'
- AND_PATTERN =
'^\s*' + OPT_PARENTHESES_PATTERN + '\s+and\s+' + OPT_PARENTHESES_PATTERN + '\s*$'
- OR_PATTERN =
'^\s*' + OPT_PARENTHESES_PATTERN + '\s+or\s+' + OPT_PARENTHESES_PATTERN + '\s*$'
- ROLE_PATTERN =
'(\'\s*(.+)\s*\'|(\w+))'
- MODEL_PATTERN =
'(:*\w+)'
- PARENTHESES_REGEX =
Regexp.new('^\s*' + PARENTHESES_PATTERN + '\s*$')
- NOT_REGEX =
Regexp.new(NOT_PATTERN)
- AND_REGEX =
Regexp.new(AND_PATTERN)
- OR_REGEX =
Regexp.new(OR_PATTERN)
- ROLE_REGEX =
Regexp.new('^\s*' + ROLE_PATTERN + '\s*$')
- ROLE_OF_MODEL_REGEX =
Regexp.new('^\s*' + ROLE_PATTERN + '\s+(' + VALID_PREPOSITIONS_PATTERN + ')\s+' + MODEL_PATTERN + '\s*$')
Instance Method Summary collapse
- #parse_and(str) ⇒ Object
- #parse_authorization_expression(str) ⇒ Object
- #parse_expr(str) ⇒ Object
- #parse_not(str) ⇒ Object
- #parse_or(str) ⇒ Object
-
#parse_parenthesis(str) ⇒ Object
Descend down parenthesis (allow up to 5 levels of nesting).
-
#parse_role(str) ⇒ Object
Parse <role> of the User-like object.
-
#parse_role_of_model(str) ⇒ Object
Parse <role> of <model>.
- #parse_term(str) ⇒ Object
Instance Method Details
#parse_and(str) ⇒ Object
158 159 160 161 162 163 164 165 |
# File 'lib/constellation/authorization/parser.rb', line 158 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) ⇒ Object
127 128 129 130 131 |
# File 'lib/constellation/authorization/parser.rb', line 127 def ( str ) @stack = [] raise AuthorizationExpressionInvalid, "Cannot parse authorization (#{str})" if not parse_expr( str ) return @stack.pop end |
#parse_expr(str) ⇒ Object
133 134 135 136 137 138 139 |
# File 'lib/constellation/authorization/parser.rb', line 133 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) ⇒ Object
141 142 143 144 145 146 147 |
# File 'lib/constellation/authorization/parser.rb', line 141 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) ⇒ Object
149 150 151 152 153 154 155 156 |
# File 'lib/constellation/authorization/parser.rb', line 149 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) ⇒ Object
Descend down parenthesis (allow up to 5 levels of nesting)
168 169 170 |
# File 'lib/constellation/authorization/parser.rb', line 168 def parse_parenthesis( str ) str =~ PARENTHESES_REGEX ? parse_expr( $1 ) : false end |
#parse_role(str) ⇒ Object
Parse <role> of the User-like object
194 195 196 197 198 199 200 201 202 203 204 205 206 207 |
# File 'lib/constellation/authorization/parser.rb', line 194 def parse_role( str ) if str =~ ROLE_REGEX role_name = $1 if @current_user.nil? || @current_user == :false @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) ⇒ Object
Parse <role> of <model>
178 179 180 181 182 183 184 185 186 187 188 189 190 191 |
# File 'lib/constellation/authorization/parser.rb', line 178 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? = model_obj.send( :accepts_role?, role_name, @current_user ) @stack.push( ) true else false end end |
#parse_term(str) ⇒ Object
172 173 174 175 |
# File 'lib/constellation/authorization/parser.rb', line 172 def parse_term( str ) parse_role_of_model( str ) or parse_role( str ) end |