authorization-rails 1.0.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG.txt +170 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +512 -0
- data/README.txt +451 -0
- data/README_developers.txt +43 -0
- data/Rakefile +39 -0
- data/UPGRADE.rdoc +13 -0
- data/VERSION +1 -0
- data/about.yml +8 -0
- data/authorization-rails.gemspec +65 -0
- data/authorization.gemspec +67 -0
- data/doc/authorization_example.gif +0 -0
- data/doc/authorization_example.rb +38 -0
- data/generators/role_model/USAGE +20 -0
- data/generators/role_model/role_model_generator.rb +35 -0
- data/generators/role_model/templates/fixtures.yml +5 -0
- data/generators/role_model/templates/migration.rb +21 -0
- data/generators/role_model/templates/model.rb +8 -0
- data/generators/role_model/templates/role_model.rb +9 -0
- data/generators/role_model/templates/role_user_model.rb +5 -0
- data/generators/role_model/templates/unit_test.rb +10 -0
- data/init.rb +1 -0
- data/install.rb +2 -0
- data/lib/authorization.rb +176 -0
- data/lib/publishare/exceptions.rb +41 -0
- data/lib/publishare/hardwired_roles.rb +82 -0
- data/lib/publishare/identity.rb +119 -0
- data/lib/publishare/object_roles_table.rb +119 -0
- data/lib/publishare/parser.rb +210 -0
- data/tasks/authorization_tasks.rake +4 -0
- metadata +78 -0
@@ -0,0 +1,41 @@
|
|
1
|
+
module Authorization #:nodoc:
|
2
|
+
|
3
|
+
# Base error class for Authorization module
|
4
|
+
class AuthorizationError < StandardError
|
5
|
+
end
|
6
|
+
|
7
|
+
# Raised when the authorization expression is invalid (cannot be parsed)
|
8
|
+
class AuthorizationExpressionInvalid < AuthorizationError
|
9
|
+
end
|
10
|
+
|
11
|
+
# Raised when we can't find the current user
|
12
|
+
class CannotObtainUserObject < AuthorizationError
|
13
|
+
end
|
14
|
+
|
15
|
+
# Raised when an authorization expression contains a model class that doesn't exist
|
16
|
+
class CannotObtainModelClass < AuthorizationError
|
17
|
+
end
|
18
|
+
|
19
|
+
# Raised when an authorization expression contains a model reference that doesn't exist
|
20
|
+
class CannotObtainModelObject < AuthorizationError
|
21
|
+
end
|
22
|
+
|
23
|
+
# Raised when the obtained user object doesn't implement #id
|
24
|
+
class UserDoesntImplementID < AuthorizationError
|
25
|
+
end
|
26
|
+
|
27
|
+
# Raised when the obtained user object doesn't implement #has_role?
|
28
|
+
class UserDoesntImplementRoles < AuthorizationError
|
29
|
+
end
|
30
|
+
|
31
|
+
# Raised when the obtained model doesn't implement #accepts_role?
|
32
|
+
class ModelDoesntImplementRoles < AuthorizationError
|
33
|
+
end
|
34
|
+
|
35
|
+
class CannotSetRoleWhenHardwired < AuthorizationError
|
36
|
+
end
|
37
|
+
|
38
|
+
class CannotSetObjectRoleWhenSimpleRoleTable < AuthorizationError
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/exceptions'
|
2
|
+
|
3
|
+
# In order to use this mixin, you'll need to define roles by overriding the
|
4
|
+
# following functions:
|
5
|
+
#
|
6
|
+
# User#has_role?(role)
|
7
|
+
# Return true or false depending on the roles (strings) passed in.
|
8
|
+
#
|
9
|
+
# Model#accepts_role?(role, user)
|
10
|
+
# Return true or false depending on the roles (strings) this particular user has for
|
11
|
+
# this particular model object.
|
12
|
+
#
|
13
|
+
# See http://www.writertopia.com/developers/authorization
|
14
|
+
|
15
|
+
module Authorization
|
16
|
+
module HardwiredRoles
|
17
|
+
|
18
|
+
module UserExtensions
|
19
|
+
def self.included( recipient )
|
20
|
+
recipient.extend( ClassMethods )
|
21
|
+
end
|
22
|
+
|
23
|
+
module ClassMethods
|
24
|
+
def acts_as_authorized_user
|
25
|
+
include Authorization::HardwiredRoles::UserExtensions::InstanceMethods
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
module InstanceMethods
|
30
|
+
# If roles aren't explicitly defined in user class then return false
|
31
|
+
def has_role?( role, authorizable_object = nil )
|
32
|
+
false
|
33
|
+
end
|
34
|
+
|
35
|
+
def has_role( role, authorizable_object = nil )
|
36
|
+
raise( CannotSetRoleWhenHardwired,
|
37
|
+
"Hardwired mixin: Cannot set user to role #{role}. Don't use #has_role, use code in models."
|
38
|
+
)
|
39
|
+
end
|
40
|
+
|
41
|
+
def has_no_role( role, authorizable_object = nil )
|
42
|
+
raise( CannotSetRoleWhenHardwired,
|
43
|
+
"Hardwired mixin: Cannot remove user role #{role}. Don't use #has_no_role, use code in models."
|
44
|
+
)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
module ModelExtensions
|
50
|
+
def self.included( recipient )
|
51
|
+
recipient.extend( ClassMethods )
|
52
|
+
end
|
53
|
+
|
54
|
+
module ClassMethods
|
55
|
+
def acts_as_authorizable
|
56
|
+
include Authorization::HardwiredRoles::ModelExtensions::InstanceMethods
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
module InstanceMethods
|
61
|
+
def accepts_role?( role, user )
|
62
|
+
return false
|
63
|
+
end
|
64
|
+
|
65
|
+
def accepts_role( role, user )
|
66
|
+
raise( CannotSetRoleWhenHardwired,
|
67
|
+
"Hardwired mixin: Cannot set user to role #{role}. Don't use #accepts_role, use code in models."
|
68
|
+
)
|
69
|
+
end
|
70
|
+
|
71
|
+
def accepts_no_role( role, user )
|
72
|
+
raise( CannotSetRoleWhenHardwired,
|
73
|
+
"Hardwired mixin: Cannot set user to role #{role}. Don't use #accepts_no_role, use code in models."
|
74
|
+
)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
|
@@ -0,0 +1,119 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/exceptions'
|
2
|
+
|
3
|
+
# Provides the appearance of dynamically generated methods on the roles database.
|
4
|
+
#
|
5
|
+
# Examples:
|
6
|
+
# user.is_member? --> Returns true if user has any role of "member"
|
7
|
+
# user.is_member_of? this_workshop --> Returns true/false. Must have authorizable object after query.
|
8
|
+
# user.is_eligible_for [this_award] --> Gives user the role "eligible" for "this_award"
|
9
|
+
# user.is_moderator --> Gives user the general role "moderator" (not tied to any class or object)
|
10
|
+
# user.is_candidate_of_what --> Returns array of objects for which this user is a "candidate" (any type)
|
11
|
+
# user.is_candidate_of_what(Party) --> Returns array of objects for which this user is a "candidate" (only 'Party' type)
|
12
|
+
#
|
13
|
+
# model.has_members --> Returns array of users which have role "member" on that model
|
14
|
+
# model.has_members? --> Returns true/false
|
15
|
+
#
|
16
|
+
module Authorization
|
17
|
+
module Identity
|
18
|
+
|
19
|
+
module UserExtensions
|
20
|
+
module InstanceMethods
|
21
|
+
|
22
|
+
def method_missing( method_sym, *args )
|
23
|
+
method_name = method_sym.to_s
|
24
|
+
authorizable_object = args.empty? ? nil : args[0]
|
25
|
+
|
26
|
+
base_regex = "is_(\\w+)"
|
27
|
+
fancy_regex = base_regex + "_(#{Authorization::Base::VALID_PREPOSITIONS_PATTERN})"
|
28
|
+
is_either_regex = '^((' + fancy_regex + ')|(' + base_regex + '))'
|
29
|
+
base_not_regex = "is_no[t]?_(\\w+)"
|
30
|
+
fancy_not_regex = base_not_regex + "_(#{Authorization::Base::VALID_PREPOSITIONS_PATTERN})"
|
31
|
+
is_not_either_regex = '^((' + fancy_not_regex + ')|(' + base_not_regex + '))'
|
32
|
+
|
33
|
+
if method_name =~ Regexp.new(is_either_regex + '_what$')
|
34
|
+
role_name = $3 || $6
|
35
|
+
has_role_for_objects(role_name, authorizable_object)
|
36
|
+
elsif method_name =~ Regexp.new(is_not_either_regex + '\?$')
|
37
|
+
role_name = $3 || $6
|
38
|
+
not is_role?( role_name, authorizable_object )
|
39
|
+
elsif method_name =~ Regexp.new(is_either_regex + '\?$')
|
40
|
+
role_name = $3 || $6
|
41
|
+
is_role?( role_name, authorizable_object )
|
42
|
+
elsif method_name =~ Regexp.new(is_not_either_regex + '$')
|
43
|
+
role_name = $3 || $6
|
44
|
+
is_no_role( role_name, authorizable_object )
|
45
|
+
elsif method_name =~ Regexp.new(is_either_regex + '$')
|
46
|
+
role_name = $3 || $6
|
47
|
+
is_role( role_name, authorizable_object )
|
48
|
+
else
|
49
|
+
super
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def is_role?( role_name, authorizable_object )
|
56
|
+
if authorizable_object.nil?
|
57
|
+
return self.has_role?(role_name)
|
58
|
+
elsif authorizable_object.respond_to?(:accepts_role?)
|
59
|
+
return self.has_role?(role_name, authorizable_object)
|
60
|
+
end
|
61
|
+
false
|
62
|
+
end
|
63
|
+
|
64
|
+
def is_no_role( role_name, authorizable_object = nil )
|
65
|
+
if authorizable_object.nil?
|
66
|
+
self.has_no_role role_name
|
67
|
+
else
|
68
|
+
self.has_no_role role_name, authorizable_object
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def is_role( role_name, authorizable_object = nil )
|
73
|
+
if authorizable_object.nil?
|
74
|
+
self.has_role role_name
|
75
|
+
else
|
76
|
+
self.has_role role_name, authorizable_object
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def has_role_for_objects(role_name, type)
|
81
|
+
if type.nil?
|
82
|
+
roles = self.roles.find_all_by_name( role_name )
|
83
|
+
else
|
84
|
+
roles = self.roles.find_all_by_authorizable_type_and_name( type.name, role_name )
|
85
|
+
end
|
86
|
+
roles.collect do |role|
|
87
|
+
if role.authorizable_id.nil?
|
88
|
+
role.authorizable_type.nil? ?
|
89
|
+
nil : Module.const_get( role.authorizable_type ) # Returns class
|
90
|
+
else
|
91
|
+
role.authorizable
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
module ModelExtensions
|
99
|
+
module InstanceMethods
|
100
|
+
|
101
|
+
def method_missing( method_sym, *args )
|
102
|
+
method_name = method_sym.to_s
|
103
|
+
if method_name =~ /^has_(\w+)\?$/
|
104
|
+
role_name = $1.singularize
|
105
|
+
self.accepted_roles.find_all_by_name(role_name).any? { |role| role.users.any? }
|
106
|
+
elsif method_name =~ /^has_(\w+)$/
|
107
|
+
role_name = $1.singularize
|
108
|
+
users = self.accepted_roles.find_all_by_name(role_name).collect { |role| role.users }
|
109
|
+
users.flatten.uniq if users
|
110
|
+
else
|
111
|
+
super
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
end
|
119
|
+
end
|
@@ -0,0 +1,119 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/exceptions'
|
2
|
+
require File.dirname(__FILE__) + '/identity'
|
3
|
+
|
4
|
+
module Authorization
|
5
|
+
module ObjectRolesTable
|
6
|
+
|
7
|
+
module UserExtensions
|
8
|
+
def self.included( recipient )
|
9
|
+
recipient.extend( ClassMethods )
|
10
|
+
end
|
11
|
+
|
12
|
+
module ClassMethods
|
13
|
+
def acts_as_authorized_user(roles_relationship_opts = {})
|
14
|
+
has_and_belongs_to_many :roles, roles_relationship_opts
|
15
|
+
include Authorization::ObjectRolesTable::UserExtensions::InstanceMethods
|
16
|
+
include Authorization::Identity::UserExtensions::InstanceMethods # Provides all kinds of dynamic sugar via method_missing
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
module InstanceMethods
|
21
|
+
def has_role?( role_name, authorizable_obj = nil )
|
22
|
+
if authorizable_obj.nil?
|
23
|
+
case role_name
|
24
|
+
when String then self.roles.detect { |role| role.name == role_name } ? true : false
|
25
|
+
when Array then role_name.inject(false) { |memo,role| memo ? memo : has_role?(role) }
|
26
|
+
else false
|
27
|
+
end
|
28
|
+
else
|
29
|
+
role = get_role( role_name, authorizable_obj )
|
30
|
+
role ? self.roles.exists?( role.id ) : false
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def has_role( role_name, authorizable_obj = nil )
|
35
|
+
role = get_role( role_name, authorizable_obj )
|
36
|
+
if role.nil?
|
37
|
+
if authorizable_obj.is_a? Class
|
38
|
+
role = Role.create( :name => role_name, :authorizable_type => authorizable_obj.to_s )
|
39
|
+
elsif authorizable_obj
|
40
|
+
role = Role.create( :name => role_name, :authorizable => authorizable_obj )
|
41
|
+
else
|
42
|
+
role = Role.create( :name => role_name )
|
43
|
+
end
|
44
|
+
end
|
45
|
+
self.roles << role if role and not self.roles.exists?( role.id )
|
46
|
+
end
|
47
|
+
|
48
|
+
def has_no_role( role_name, authorizable_obj = nil )
|
49
|
+
role = get_role( role_name, authorizable_obj )
|
50
|
+
if role
|
51
|
+
self.roles.delete( role )
|
52
|
+
role.destroy if role.users.empty?
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def get_role( role_name, authorizable_obj )
|
59
|
+
if authorizable_obj.is_a? Class
|
60
|
+
Role.find( :first,
|
61
|
+
:conditions => [ 'name = ? and authorizable_type = ? and authorizable_id IS NULL', role_name, authorizable_obj.to_s ] )
|
62
|
+
elsif authorizable_obj
|
63
|
+
Role.find( :first,
|
64
|
+
:conditions => [ 'name = ? and authorizable_type = ? and authorizable_id = ?',
|
65
|
+
role_name, authorizable_obj.class.base_class.to_s, authorizable_obj.id ] )
|
66
|
+
else
|
67
|
+
Role.find( :first,
|
68
|
+
:conditions => [ 'name = ? and authorizable_type IS NULL and authorizable_id IS NULL', role_name ] )
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
module ModelExtensions
|
76
|
+
def self.included( recipient )
|
77
|
+
recipient.extend( ClassMethods )
|
78
|
+
end
|
79
|
+
|
80
|
+
module ClassMethods
|
81
|
+
def acts_as_authorizable
|
82
|
+
has_many :accepted_roles, :as => :authorizable, :class_name => 'Role'
|
83
|
+
|
84
|
+
def accepts_role?( role_name, user )
|
85
|
+
user.has_role? role_name, self
|
86
|
+
end
|
87
|
+
|
88
|
+
def accepts_role( role_name, user )
|
89
|
+
user.has_role role_name, self
|
90
|
+
end
|
91
|
+
|
92
|
+
def accepts_no_role( role_name, user )
|
93
|
+
user.has_no_role role_name, self
|
94
|
+
end
|
95
|
+
|
96
|
+
include Authorization::ObjectRolesTable::ModelExtensions::InstanceMethods
|
97
|
+
include Authorization::Identity::ModelExtensions::InstanceMethods # Provides all kinds of dynamic sugar via method_missing
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
module InstanceMethods
|
102
|
+
# If roles aren't overriden in model then check roles table
|
103
|
+
def accepts_role?( role_name, user )
|
104
|
+
user.has_role? role_name, self
|
105
|
+
end
|
106
|
+
|
107
|
+
def accepts_role( role_name, user )
|
108
|
+
user.has_role role_name, self
|
109
|
+
end
|
110
|
+
|
111
|
+
def accepts_no_role( role_name, user )
|
112
|
+
user.has_no_role role_name, self
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
@@ -0,0 +1,210 @@
|
|
1
|
+
module Authorization
|
2
|
+
module Base
|
3
|
+
|
4
|
+
VALID_PREPOSITIONS = ['of', 'for', 'in', 'on', 'to', 'at', 'by']
|
5
|
+
BOOLEAN_OPS = ['not', 'or', 'and']
|
6
|
+
VALID_PREPOSITIONS_PATTERN = VALID_PREPOSITIONS.join('|')
|
7
|
+
|
8
|
+
module EvalParser
|
9
|
+
# Parses and evaluates an authorization expression and returns <tt>true</tt> or <tt>false</tt>.
|
10
|
+
#
|
11
|
+
# The authorization expression is defined by the following grammar:
|
12
|
+
# <expr> ::= (<expr>) | not <expr> | <term> or <expr> | <term> and <expr> | <term>
|
13
|
+
# <term> ::= <role> | <role> <preposition> <model>
|
14
|
+
# <preposition> ::= of | for | in | on | to | at | by
|
15
|
+
# <model> ::= /:*\w+/
|
16
|
+
# <role> ::= /\w+/ | /'.*'/
|
17
|
+
#
|
18
|
+
# Instead of doing recursive descent parsing (not so fun when we support nested parentheses, etc),
|
19
|
+
# we let Ruby do the work for us by inserting the appropriate permission calls and using eval.
|
20
|
+
# This would not be a good idea if you were getting authorization expressions from the outside,
|
21
|
+
# so in that case (e.g. somehow letting users literally type in permission expressions) you'd
|
22
|
+
# be better off using the recursive descent parser in Module RecursiveDescentParser.
|
23
|
+
#
|
24
|
+
# We search for parts of our authorization evaluation that match <role> or <role> <preposition> <model>
|
25
|
+
# and we ignore anything terminal in our grammar.
|
26
|
+
#
|
27
|
+
# 1) Replace all <role> <preposition> <model> matches.
|
28
|
+
# 2) Replace all <role> matches that aren't one of our other terminals ('not', 'or', 'and', or preposition)
|
29
|
+
# 3) Eval
|
30
|
+
|
31
|
+
def parse_authorization_expression( str )
|
32
|
+
if str =~ /[^A-Za-z0-9_:'\(\)\s]/
|
33
|
+
raise AuthorizationExpressionInvalid, "Invalid authorization expression (#{str})"
|
34
|
+
return false
|
35
|
+
end
|
36
|
+
@replacements = []
|
37
|
+
expr = replace_temporarily_role_of_model( str )
|
38
|
+
expr = replace_role( expr )
|
39
|
+
expr = replace_role_of_model( expr )
|
40
|
+
begin
|
41
|
+
instance_eval( expr )
|
42
|
+
rescue
|
43
|
+
raise AuthorizationExpressionInvalid, "Cannot parse authorization (#{str})"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def replace_temporarily_role_of_model( str )
|
48
|
+
role_regex = '\s*(\'\s*(.+?)\s*\'|(\w+))\s+'
|
49
|
+
model_regex = '\s+(:*\w+)'
|
50
|
+
parse_regex = Regexp.new(role_regex + '(' + VALID_PREPOSITIONS.join('|') + ')' + model_regex)
|
51
|
+
str.gsub(parse_regex) do |match|
|
52
|
+
@replacements.push " process_role_of_model('#{$2 || $3}', '#{$5}') "
|
53
|
+
" <#{@replacements.length-1}> "
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def replace_role( str )
|
58
|
+
role_regex = '\s*(\'\s*(.+?)\s*\'|([A-Za-z]\w*))\s*'
|
59
|
+
parse_regex = Regexp.new(role_regex)
|
60
|
+
str.gsub(parse_regex) do |match|
|
61
|
+
if BOOLEAN_OPS.include?($3)
|
62
|
+
" #{match} "
|
63
|
+
else
|
64
|
+
" process_role('#{$2 || $3}') "
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def replace_role_of_model( str )
|
70
|
+
str.gsub(/<(\d+)>/) do |match|
|
71
|
+
@replacements[$1.to_i]
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def process_role_of_model( role_name, model_name )
|
76
|
+
model = get_model( model_name )
|
77
|
+
raise( ModelDoesntImplementRoles, "Model (#{model_name}) doesn't implement #accepts_role?" ) if not model.respond_to? :accepts_role?
|
78
|
+
model.send( :accepts_role?, role_name, @current_user )
|
79
|
+
end
|
80
|
+
|
81
|
+
def process_role( role_name )
|
82
|
+
return false if @current_user.nil? || @current_user == :false
|
83
|
+
raise( UserDoesntImplementRoles, "User doesn't implement #has_role?" ) if not @current_user.respond_to? :has_role?
|
84
|
+
@current_user.has_role?( role_name )
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
88
|
+
|
89
|
+
# Parses and evaluates an authorization expression and returns <tt>true</tt> or <tt>false</tt>.
|
90
|
+
# This recursive descent parses uses two instance variables:
|
91
|
+
# @stack --> a stack with the top holding the boolean expression resulting from the parsing
|
92
|
+
#
|
93
|
+
# The authorization expression is defined by the following grammar:
|
94
|
+
# <expr> ::= (<expr>) | not <expr> | <term> or <expr> | <term> and <expr> | <term>
|
95
|
+
# <term> ::= <role> | <role> <preposition> <model>
|
96
|
+
# <preposition> ::= of | for | in | on | to | at | by
|
97
|
+
# <model> ::= /:*\w+/
|
98
|
+
# <role> ::= /\w+/ | /'.*'/
|
99
|
+
#
|
100
|
+
# There are really two values we must track:
|
101
|
+
# (1) whether the expression is valid according to the grammar
|
102
|
+
# (2) the evaluated results --> true/false on the permission queries
|
103
|
+
# The first is embedded in the control logic because we want short-circuiting. If an expression
|
104
|
+
# has been parsed and the permission is false, we don't want to try different ways of parsing.
|
105
|
+
# Note that this implementation of a recursive descent parser is meant to be simple
|
106
|
+
# and doesn't allow arbitrary nesting of parentheses. It supports up to 5 levels of nesting.
|
107
|
+
# It also won't handle some types of expressions (A or B) and C, which has to be rewritten as
|
108
|
+
# C and (A or B) so the parenthetical expressions are in the tail.
|
109
|
+
module RecursiveDescentParser
|
110
|
+
|
111
|
+
OPT_PARENTHESES_PATTERN = '(([^()]|\(([^()]|\(([^()]|\(([^()]|\(([^()]|\(([^()])*\))*\))*\))*\))*\))*)'
|
112
|
+
PARENTHESES_PATTERN = '\(' + OPT_PARENTHESES_PATTERN + '\)'
|
113
|
+
NOT_PATTERN = '^\s*not\s+' + OPT_PARENTHESES_PATTERN + '$'
|
114
|
+
AND_PATTERN = '^\s*' + OPT_PARENTHESES_PATTERN + '\s+and\s+' + OPT_PARENTHESES_PATTERN + '\s*$'
|
115
|
+
OR_PATTERN = '^\s*' + OPT_PARENTHESES_PATTERN + '\s+or\s+' + OPT_PARENTHESES_PATTERN + '\s*$'
|
116
|
+
ROLE_PATTERN = '(\'\s*(.+)\s*\'|(\w+))'
|
117
|
+
MODEL_PATTERN = '(:*\w+)'
|
118
|
+
|
119
|
+
PARENTHESES_REGEX = Regexp.new('^\s*' + PARENTHESES_PATTERN + '\s*$')
|
120
|
+
NOT_REGEX = Regexp.new(NOT_PATTERN)
|
121
|
+
AND_REGEX = Regexp.new(AND_PATTERN)
|
122
|
+
OR_REGEX = Regexp.new(OR_PATTERN)
|
123
|
+
ROLE_REGEX = Regexp.new('^\s*' + ROLE_PATTERN + '\s*$')
|
124
|
+
ROLE_OF_MODEL_REGEX = Regexp.new('^\s*' + ROLE_PATTERN + '\s+(' + VALID_PREPOSITIONS_PATTERN + ')\s+' + MODEL_PATTERN + '\s*$')
|
125
|
+
|
126
|
+
def parse_authorization_expression( str )
|
127
|
+
@stack = []
|
128
|
+
raise AuthorizationExpressionInvalid, "Cannot parse authorization (#{str})" if not parse_expr( str )
|
129
|
+
return @stack.pop
|
130
|
+
end
|
131
|
+
|
132
|
+
def parse_expr( str )
|
133
|
+
parse_parenthesis( str ) or
|
134
|
+
parse_not( str ) or
|
135
|
+
parse_or( str ) or
|
136
|
+
parse_and( str ) or
|
137
|
+
parse_term( str )
|
138
|
+
end
|
139
|
+
|
140
|
+
def parse_not( str )
|
141
|
+
if str =~ NOT_REGEX
|
142
|
+
can_parse = parse_expr( $1 )
|
143
|
+
@stack.push( !@stack.pop ) if can_parse
|
144
|
+
end
|
145
|
+
false
|
146
|
+
end
|
147
|
+
|
148
|
+
def parse_or( str )
|
149
|
+
if str =~ OR_REGEX
|
150
|
+
can_parse = parse_expr( $1 ) and parse_expr( $8 )
|
151
|
+
@stack.push( @stack.pop | @stack.pop ) if can_parse
|
152
|
+
return can_parse
|
153
|
+
end
|
154
|
+
false
|
155
|
+
end
|
156
|
+
|
157
|
+
def parse_and( str )
|
158
|
+
if str =~ AND_REGEX
|
159
|
+
can_parse = parse_expr( $1 ) and parse_expr( $8 )
|
160
|
+
@stack.push(@stack.pop & @stack.pop) if can_parse
|
161
|
+
return can_parse
|
162
|
+
end
|
163
|
+
false
|
164
|
+
end
|
165
|
+
|
166
|
+
# Descend down parenthesis (allow up to 5 levels of nesting)
|
167
|
+
def parse_parenthesis( str )
|
168
|
+
str =~ PARENTHESES_REGEX ? parse_expr( $1 ) : false
|
169
|
+
end
|
170
|
+
|
171
|
+
def parse_term( str )
|
172
|
+
parse_role_of_model( str ) or
|
173
|
+
parse_role( str )
|
174
|
+
end
|
175
|
+
|
176
|
+
# Parse <role> of <model>
|
177
|
+
def parse_role_of_model( str )
|
178
|
+
if str =~ ROLE_OF_MODEL_REGEX
|
179
|
+
role_name = $2 || $3
|
180
|
+
model_name = $5
|
181
|
+
model_obj = get_model( model_name )
|
182
|
+
raise( ModelDoesntImplementRoles, "Model (#{model_name}) doesn't implement #accepts_role?" ) if not model_obj.respond_to? :accepts_role?
|
183
|
+
|
184
|
+
has_permission = model_obj.send( :accepts_role?, role_name, @current_user )
|
185
|
+
@stack.push( has_permission )
|
186
|
+
true
|
187
|
+
else
|
188
|
+
false
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
# Parse <role> of the User-like object
|
193
|
+
def parse_role( str )
|
194
|
+
if str =~ ROLE_REGEX
|
195
|
+
role_name = $1
|
196
|
+
if @current_user.nil? || @current_user == :false
|
197
|
+
@stack.push(false)
|
198
|
+
else
|
199
|
+
raise( UserDoesntImplementRoles, "User doesn't implement #has_role?" ) if not @current_user.respond_to? :has_role?
|
200
|
+
@stack.push( @current_user.has_role?(role_name) )
|
201
|
+
end
|
202
|
+
true
|
203
|
+
else
|
204
|
+
false
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|