authoreyes 0.1.1
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.
- checksums.yaml +7 -0
- data/.gitignore +10 -0
- data/.travis.yml +5 -0
- data/Gemfile +9 -0
- data/LICENSE.txt +21 -0
- data/README.md +53 -0
- data/Rakefile +10 -0
- data/authoreyes.gemspec +29 -0
- data/authorization_rules.dist.rb +20 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/authoreyes.rb +7 -0
- data/lib/authoreyes/authorization.rb +80 -0
- data/lib/authoreyes/authorization/anonymous_user.rb +11 -0
- data/lib/authoreyes/authorization/attribute.rb +164 -0
- data/lib/authoreyes/authorization/attribute_with_permission.rb +133 -0
- data/lib/authoreyes/authorization/authorization_rule.rb +89 -0
- data/lib/authoreyes/authorization/authorization_rule_set.rb +58 -0
- data/lib/authoreyes/authorization/engine.rb +296 -0
- data/lib/authoreyes/parser.rb +18 -0
- data/lib/authoreyes/parser/authorization_rules_parser.rb +399 -0
- data/lib/authoreyes/parser/dsl_parser.rb +91 -0
- data/lib/authoreyes/parser/priveleges_reader.rb +59 -0
- data/lib/authoreyes/version.rb +3 -0
- metadata +115 -0
@@ -0,0 +1,133 @@
|
|
1
|
+
module Authoreyes
|
2
|
+
module Authorization
|
3
|
+
# An attribute condition that uses existing rules to decide validation
|
4
|
+
# and create obligations.
|
5
|
+
class AttributeWithPermission < Attribute
|
6
|
+
# E.g. privilege :read, attr_or_hash either :attribute or
|
7
|
+
# { :attribute => :deeper_attribute }
|
8
|
+
def initialize (privilege, attr_or_hash, context = nil)
|
9
|
+
@privilege = privilege
|
10
|
+
@context = context
|
11
|
+
@attr_hash = attr_or_hash
|
12
|
+
end
|
13
|
+
|
14
|
+
def initialize_copy (from)
|
15
|
+
@attr_hash = deep_hash_clone(@attr_hash) if @attr_hash.is_a?(Hash)
|
16
|
+
end
|
17
|
+
|
18
|
+
def validate? (attr_validator, object = nil, hash_or_attr = nil)
|
19
|
+
object ||= attr_validator.object
|
20
|
+
hash_or_attr ||= @attr_hash
|
21
|
+
return false unless object
|
22
|
+
|
23
|
+
case hash_or_attr
|
24
|
+
when Symbol
|
25
|
+
attr_value = object_attribute_value(object, hash_or_attr)
|
26
|
+
case attr_value
|
27
|
+
when nil
|
28
|
+
raise NilAttributeValueError, "Attribute #{hash_or_attr.inspect} is nil in #{object.inspect}."
|
29
|
+
when Enumerable
|
30
|
+
attr_value.any? do |inner_value|
|
31
|
+
attr_validator.engine.permit? @privilege, :object => inner_value, :user => attr_validator.user
|
32
|
+
end
|
33
|
+
else
|
34
|
+
attr_validator.engine.permit? @privilege, :object => attr_value, :user => attr_validator.user
|
35
|
+
end
|
36
|
+
when Hash
|
37
|
+
hash_or_attr.all? do |attr, sub_hash|
|
38
|
+
attr_value = object_attribute_value(object, attr)
|
39
|
+
if attr_value == nil
|
40
|
+
raise NilAttributeValueError, "Attribute #{attr.inspect} is nil in #{object.inspect}."
|
41
|
+
elsif attr_value.is_a?(Enumerable)
|
42
|
+
attr_value.any? do |inner_value|
|
43
|
+
validate?(attr_validator, inner_value, sub_hash)
|
44
|
+
end
|
45
|
+
else
|
46
|
+
validate?(attr_validator, attr_value, sub_hash)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
when NilClass
|
50
|
+
attr_validator.engine.permit? @privilege, :object => object, :user => attr_validator.user
|
51
|
+
else
|
52
|
+
raise AuthorizationError, "Wrong conditions hash format: #{hash_or_attr.inspect}"
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# may return an array of obligations to be OR'ed
|
57
|
+
def obligation (attr_validator, hash_or_attr = nil, path = [])
|
58
|
+
hash_or_attr ||= @attr_hash
|
59
|
+
case hash_or_attr
|
60
|
+
when Symbol
|
61
|
+
@context ||= begin
|
62
|
+
rule_model = attr_validator.context.to_s.classify.constantize
|
63
|
+
context_reflection = self.class.reflection_for_path(rule_model, path + [hash_or_attr])
|
64
|
+
if context_reflection.klass.respond_to?(:decl_auth_context)
|
65
|
+
context_reflection.klass.decl_auth_context
|
66
|
+
else
|
67
|
+
context_reflection.klass.name.tableize.to_sym
|
68
|
+
end
|
69
|
+
rescue # missing model, reflections
|
70
|
+
hash_or_attr.to_s.pluralize.to_sym
|
71
|
+
end
|
72
|
+
|
73
|
+
obligations = attr_validator.engine.obligations(@privilege,
|
74
|
+
:context => @context,
|
75
|
+
:user => attr_validator.user)
|
76
|
+
|
77
|
+
obligations.collect {|obl| {hash_or_attr => obl} }
|
78
|
+
when Hash
|
79
|
+
obligations_array_attrs = []
|
80
|
+
obligations =
|
81
|
+
hash_or_attr.inject({}) do |all, pair|
|
82
|
+
attr, sub_hash = pair
|
83
|
+
all[attr] = obligation(attr_validator, sub_hash, path + [attr])
|
84
|
+
if all[attr].length > 1
|
85
|
+
obligations_array_attrs << attr
|
86
|
+
else
|
87
|
+
all[attr] = all[attr].first
|
88
|
+
end
|
89
|
+
all
|
90
|
+
end
|
91
|
+
obligations = [obligations]
|
92
|
+
obligations_array_attrs.each do |attr|
|
93
|
+
next_array_size = obligations.first[attr].length
|
94
|
+
obligations = obligations.collect do |obls|
|
95
|
+
(0...next_array_size).collect do |idx|
|
96
|
+
obls_wo_array = obls.clone
|
97
|
+
obls_wo_array[attr] = obls_wo_array[attr][idx]
|
98
|
+
obls_wo_array
|
99
|
+
end
|
100
|
+
end.flatten
|
101
|
+
end
|
102
|
+
obligations
|
103
|
+
when NilClass
|
104
|
+
attr_validator.engine.obligations(@privilege,
|
105
|
+
:context => attr_validator.context,
|
106
|
+
:user => attr_validator.user)
|
107
|
+
else
|
108
|
+
raise AuthorizationError, "Wrong conditions hash format: #{hash_or_attr.inspect}"
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def to_long_s
|
113
|
+
"if_permitted_to #{@privilege.inspect}, #{@attr_hash.inspect}"
|
114
|
+
end
|
115
|
+
|
116
|
+
private
|
117
|
+
def self.reflection_for_path (parent_model, path)
|
118
|
+
reflection = path.empty? ? parent_model : begin
|
119
|
+
parent = reflection_for_path(parent_model, path[0..-2])
|
120
|
+
if !parent.respond_to?(:proxy_reflection) and parent.respond_to?(:klass)
|
121
|
+
parent.klass.reflect_on_association(path.last)
|
122
|
+
else
|
123
|
+
parent.reflect_on_association(path.last)
|
124
|
+
end
|
125
|
+
rescue
|
126
|
+
parent.reflect_on_association(path.last)
|
127
|
+
end
|
128
|
+
raise "invalid path #{path.inspect}" if reflection.nil?
|
129
|
+
reflection
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
module Authoreyes
|
2
|
+
module Authorization
|
3
|
+
class AuthorizationRule
|
4
|
+
attr_reader :attributes, :contexts, :role, :privileges, :join_operator,
|
5
|
+
:source_file, :source_line
|
6
|
+
|
7
|
+
def initialize (role, privileges = [], contexts = nil, join_operator = :or,
|
8
|
+
options = {})
|
9
|
+
@role = role
|
10
|
+
@privileges = Set.new(privileges)
|
11
|
+
@contexts = Set.new((contexts && !contexts.is_a?(Array) ? [contexts] : contexts))
|
12
|
+
@join_operator = join_operator
|
13
|
+
@attributes = []
|
14
|
+
@source_file = options[:source_file]
|
15
|
+
@source_line = options[:source_line]
|
16
|
+
end
|
17
|
+
|
18
|
+
def initialize_copy (from)
|
19
|
+
@privileges = @privileges.clone
|
20
|
+
@contexts = @contexts.clone
|
21
|
+
@attributes = @attributes.collect {|attribute| attribute.clone }
|
22
|
+
end
|
23
|
+
|
24
|
+
def append_privileges (privs)
|
25
|
+
@privileges.merge(privs)
|
26
|
+
end
|
27
|
+
|
28
|
+
def append_attribute (attribute)
|
29
|
+
@attributes << attribute
|
30
|
+
end
|
31
|
+
|
32
|
+
def matches? (roles, privs, context = nil)
|
33
|
+
roles = [roles] unless roles.is_a?(Array)
|
34
|
+
@contexts.include?(context) and roles.include?(@role) and
|
35
|
+
not (@privileges & privs).empty?
|
36
|
+
end
|
37
|
+
|
38
|
+
def validate? (attr_validator, skip_attribute = false)
|
39
|
+
skip_attribute or @attributes.empty? or
|
40
|
+
@attributes.send(@join_operator == :and ? :all? : :any?) do |attr|
|
41
|
+
begin
|
42
|
+
attr.validate?(attr_validator)
|
43
|
+
rescue NilAttributeValueError => e
|
44
|
+
nil # Bumping up against a nil attribute value flunks the rule.
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def obligations (attr_validator)
|
50
|
+
exceptions = []
|
51
|
+
obligations = @attributes.collect do |attr|
|
52
|
+
begin
|
53
|
+
attr.obligation(attr_validator)
|
54
|
+
rescue NotAuthorized => e
|
55
|
+
exceptions << e
|
56
|
+
nil
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
if exceptions.length > 0 and (@join_operator == :and or exceptions.length == @attributes.length)
|
61
|
+
raise NotAuthorized, "Missing authorization in collecting obligations: #{exceptions.map(&:to_s) * ", "}"
|
62
|
+
end
|
63
|
+
|
64
|
+
if @join_operator == :and and !obligations.empty?
|
65
|
+
# cross product of OR'ed obligations in arrays
|
66
|
+
arrayed_obligations = obligations.map {|obligation| obligation.is_a?(Hash) ? [obligation] : obligation}
|
67
|
+
merged_obligations = arrayed_obligations.first
|
68
|
+
arrayed_obligations[1..-1].each do |inner_obligations|
|
69
|
+
previous_merged_obligations = merged_obligations
|
70
|
+
merged_obligations = inner_obligations.collect do |inner_obligation|
|
71
|
+
previous_merged_obligations.collect do |merged_obligation|
|
72
|
+
merged_obligation.deep_merge(inner_obligation)
|
73
|
+
end
|
74
|
+
end.flatten
|
75
|
+
end
|
76
|
+
obligations = merged_obligations
|
77
|
+
else
|
78
|
+
obligations = obligations.flatten.compact
|
79
|
+
end
|
80
|
+
obligations.empty? ? [{}] : obligations
|
81
|
+
end
|
82
|
+
|
83
|
+
def to_long_s
|
84
|
+
attributes.collect {|attr| attr.to_long_s } * "; "
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module Authoreyes
|
2
|
+
module Authorization
|
3
|
+
class AuthorizationRuleSet
|
4
|
+
include Enumerable
|
5
|
+
extend Forwardable
|
6
|
+
def_delegators :@rules, :each, :length, :[]
|
7
|
+
|
8
|
+
def initialize(rules = [])
|
9
|
+
@rules = rules.clone
|
10
|
+
reset!
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize_copy(source)
|
14
|
+
@rules = @rules.collect {|rule| rule.clone}
|
15
|
+
reset!
|
16
|
+
end
|
17
|
+
|
18
|
+
def matching(roles, privileges, context)
|
19
|
+
roles = [roles] unless roles.is_a?(Array)
|
20
|
+
rules = cached_auth_rules[context] || []
|
21
|
+
rules.select do |rule|
|
22
|
+
rule.matches? roles, privileges, context
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def delete(rule)
|
27
|
+
@rules.delete rule
|
28
|
+
reset!
|
29
|
+
end
|
30
|
+
|
31
|
+
def << rule
|
32
|
+
@rules << rule
|
33
|
+
reset!
|
34
|
+
end
|
35
|
+
|
36
|
+
def each(&block)
|
37
|
+
@rules.each &block
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
def reset!
|
42
|
+
@cached_auth_rules =nil
|
43
|
+
end
|
44
|
+
|
45
|
+
def cached_auth_rules
|
46
|
+
return @cached_auth_rules if @cached_auth_rules
|
47
|
+
@cached_auth_rules = {}
|
48
|
+
@rules.each do |rule|
|
49
|
+
rule.contexts.each do |context|
|
50
|
+
@cached_auth_rules[context] ||= []
|
51
|
+
@cached_auth_rules[context] << rule
|
52
|
+
end
|
53
|
+
end
|
54
|
+
@cached_auth_rules
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,296 @@
|
|
1
|
+
module Authoreyes
|
2
|
+
module Authorization
|
3
|
+
# Authorization::Engine implements the reference monitor. It may be used
|
4
|
+
# for querying the permission and retrieving obligations under which
|
5
|
+
# a certain privilege is granted for the current user.
|
6
|
+
class Engine
|
7
|
+
extend Forwardable
|
8
|
+
attr_reader :reader
|
9
|
+
|
10
|
+
def_delegators :@reader, :auth_rules_reader, :privileges_reader, :load, :load!
|
11
|
+
def_delegators :auth_rules_reader, :auth_rules, :roles, :omnipotent_roles, :role_hierarchy, :role_titles, :role_descriptions
|
12
|
+
def_delegators :privileges_reader, :privileges, :privilege_hierarchy
|
13
|
+
|
14
|
+
# If +reader+ is not given, a new one is created with the default
|
15
|
+
# authorization configuration of +AUTH_DSL_FILES+. If given, may be either
|
16
|
+
# a Reader object or a path to a configuration file.
|
17
|
+
def initialize(options)
|
18
|
+
options = {
|
19
|
+
reader: nil
|
20
|
+
}.merge(options)
|
21
|
+
#@auth_rules = AuthorizationRuleSet.new reader.auth_rules_reader.auth_rules
|
22
|
+
@reader = ::Authoreyes::Parser::DSLParser.factory(options[:reader] || AUTH_DSL_FILES)
|
23
|
+
end
|
24
|
+
|
25
|
+
def initialize_copy(from) # :nodoc:
|
26
|
+
@reader = from.reader.clone
|
27
|
+
end
|
28
|
+
|
29
|
+
# {[priv, ctx] => [priv, ...]}
|
30
|
+
def rev_priv_hierarchy
|
31
|
+
if @rev_priv_hierarchy.nil?
|
32
|
+
@rev_priv_hierarchy = {}
|
33
|
+
privilege_hierarchy.each do |key, value|
|
34
|
+
value.each do |val|
|
35
|
+
@rev_priv_hierarchy[val] ||= []
|
36
|
+
@rev_priv_hierarchy[val] << key
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
@rev_priv_hierarchy
|
41
|
+
end
|
42
|
+
|
43
|
+
# {[priv, ctx] => [priv, ...]}
|
44
|
+
def rev_role_hierarchy
|
45
|
+
if @rev_role_hierarchy.nil?
|
46
|
+
@rev_role_hierarchy = {}
|
47
|
+
role_hierarchy.each do |higher_role, lower_roles|
|
48
|
+
lower_roles.each do |role|
|
49
|
+
(@rev_role_hierarchy[role] ||= []) << higher_role
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
@rev_role_hierarchy
|
54
|
+
end
|
55
|
+
|
56
|
+
# Returns true if privilege is met by the current user. Raises
|
57
|
+
# AuthorizationError otherwise. +privilege+ may be given with or
|
58
|
+
# without context. In the latter case, the :+context+ option is
|
59
|
+
# required.
|
60
|
+
#
|
61
|
+
# Options:
|
62
|
+
# [:+context+]
|
63
|
+
# The context part of the privilege.
|
64
|
+
# Defaults either to the tableized +class_name+ of the given :+object+, if given.
|
65
|
+
# That is, :+users+ for :+object+ of type User.
|
66
|
+
# Raises AuthorizationUsageError if context is missing and not to be inferred.
|
67
|
+
# [:+object+] An context object to test attribute checks against.
|
68
|
+
# [:+skip_attribute_test+]
|
69
|
+
# Skips those attribute checks in the
|
70
|
+
# authorization rules. Defaults to false.
|
71
|
+
# [:+user+]
|
72
|
+
# The user to check the authorization for.
|
73
|
+
# Defaults to Authorization#current_user.
|
74
|
+
# [:+bang+]
|
75
|
+
# Should NotAuthorized exceptions be raised
|
76
|
+
# Defaults to true.
|
77
|
+
#
|
78
|
+
def permit! (privilege, options = {})
|
79
|
+
return true if Authorization.ignore_access_control
|
80
|
+
options = {
|
81
|
+
:object => nil,
|
82
|
+
:skip_attribute_test => false,
|
83
|
+
:context => nil,
|
84
|
+
:bang => true
|
85
|
+
}.merge(options)
|
86
|
+
|
87
|
+
# Make sure we're handling all privileges as symbols.
|
88
|
+
privilege = privilege.is_a?( Array ) ?
|
89
|
+
privilege.flatten.collect { |priv| priv.to_sym } :
|
90
|
+
privilege.to_sym
|
91
|
+
|
92
|
+
#
|
93
|
+
# If the object responds to :proxy_reflection, we're probably working with
|
94
|
+
# an association proxy. Use 'new' to leverage ActiveRecord's builder
|
95
|
+
# functionality to obtain an object against which we can check permissions.
|
96
|
+
#
|
97
|
+
# Example: permit!( :edit, :object => user.posts )
|
98
|
+
#
|
99
|
+
if Authorization.is_a_association_proxy?(options[:object]) && options[:object].respond_to?(:new)
|
100
|
+
options[:object] = (Rails.version < "3.0" ? options[:object] : options[:object].where(nil)).new
|
101
|
+
end
|
102
|
+
|
103
|
+
options[:context] ||= options[:object] && (
|
104
|
+
options[:object].class.respond_to?(:decl_auth_context) ?
|
105
|
+
options[:object].class.decl_auth_context :
|
106
|
+
options[:object].class.name.tableize.to_sym
|
107
|
+
) rescue NoMethodError
|
108
|
+
|
109
|
+
user, roles, privileges = user_roles_privleges_from_options(privilege, options)
|
110
|
+
|
111
|
+
return true if roles.is_a?(Array) and not (roles & omnipotent_roles).empty?
|
112
|
+
|
113
|
+
# find a authorization rule that matches for at least one of the roles and
|
114
|
+
# at least one of the given privileges
|
115
|
+
attr_validator = AttributeValidator.new(self, user, options[:object], privilege, options[:context])
|
116
|
+
rules = matching_auth_rules(roles, privileges, options[:context])
|
117
|
+
|
118
|
+
# Test each rule in turn to see whether any one of them is satisfied.
|
119
|
+
rules.each do |rule|
|
120
|
+
return true if rule.validate?(attr_validator, options[:skip_attribute_test])
|
121
|
+
end
|
122
|
+
|
123
|
+
if options[:bang]
|
124
|
+
if rules.empty?
|
125
|
+
raise NotAuthorized, "No matching rules found for #{privilege} for #{user.inspect} " +
|
126
|
+
"(roles #{roles.inspect}, privileges #{privileges.inspect}, " +
|
127
|
+
"context #{options[:context].inspect})."
|
128
|
+
else
|
129
|
+
raise AttributeAuthorizationError, "#{privilege} not allowed for #{user.inspect} on #{(options[:object] || options[:context]).inspect}."
|
130
|
+
end
|
131
|
+
else
|
132
|
+
false
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
# Calls permit! but doesn't raise authorization errors. If no exception is
|
137
|
+
# raised, permit? returns true and yields to the optional block.
|
138
|
+
def permit? (privilege, options = {}) # :yields:
|
139
|
+
if permit!(privilege, options.merge(:bang=> false))
|
140
|
+
yield if block_given?
|
141
|
+
true
|
142
|
+
else
|
143
|
+
false
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
# Returns the obligations to be met by the current user for the given
|
148
|
+
# privilege as an array of obligation hashes in form of
|
149
|
+
# [{:object_attribute => obligation_value, ...}, ...]
|
150
|
+
# where +obligation_value+ is either (recursively) another obligation hash
|
151
|
+
# or a value spec, such as
|
152
|
+
# [operator, literal_value]
|
153
|
+
# The obligation hashes in the array should be OR'ed, conditions inside
|
154
|
+
# the hashes AND'ed.
|
155
|
+
#
|
156
|
+
# Example
|
157
|
+
# {:branch => {:company => [:is, 24]}, :active => [:is, true]}
|
158
|
+
#
|
159
|
+
# Options
|
160
|
+
# [:+context+] See permit!
|
161
|
+
# [:+user+] See permit!
|
162
|
+
#
|
163
|
+
def obligations (privilege, options = {})
|
164
|
+
options = {:context => nil}.merge(options)
|
165
|
+
user, roles, privileges = user_roles_privleges_from_options(privilege, options)
|
166
|
+
|
167
|
+
permit!(privilege, :skip_attribute_test => true, :user => user, :context => options[:context])
|
168
|
+
|
169
|
+
return [] if roles.is_a?(Array) and not (roles & omnipotent_roles).empty?
|
170
|
+
|
171
|
+
attr_validator = AttributeValidator.new(self, user, nil, privilege, options[:context])
|
172
|
+
matching_auth_rules(roles, privileges, options[:context]).collect do |rule|
|
173
|
+
rule.obligations(attr_validator)
|
174
|
+
end.flatten
|
175
|
+
end
|
176
|
+
|
177
|
+
# Returns the description for the given role. The description may be
|
178
|
+
# specified with the authorization rules. Returns +nil+ if none was
|
179
|
+
# given.
|
180
|
+
def description_for (role)
|
181
|
+
role_descriptions[role]
|
182
|
+
end
|
183
|
+
|
184
|
+
# Returns the title for the given role. The title may be
|
185
|
+
# specified with the authorization rules. Returns +nil+ if none was
|
186
|
+
# given.
|
187
|
+
def title_for (role)
|
188
|
+
role_titles[role]
|
189
|
+
end
|
190
|
+
|
191
|
+
# Returns the role symbols of the given user.
|
192
|
+
def roles_for (user)
|
193
|
+
user ||= Authorization.current_user
|
194
|
+
raise AuthorizationUsageError, "User object doesn't respond to roles (#{user.inspect})" \
|
195
|
+
if !user.respond_to?(:role_symbols) and !user.respond_to?(:roles)
|
196
|
+
|
197
|
+
Rails.logger.info("The use of user.roles is deprecated. Please add a method " +
|
198
|
+
"role_symbols to your User model.") if defined?(Rails) and Rails.respond_to?(:logger) and !user.respond_to?(:role_symbols)
|
199
|
+
|
200
|
+
roles = user.respond_to?(:role_symbols) ? user.role_symbols : user.roles
|
201
|
+
|
202
|
+
raise AuthorizationUsageError, "User.#{user.respond_to?(:role_symbols) ? 'role_symbols' : 'roles'} " +
|
203
|
+
"doesn't return an Array of Symbols (#{roles.inspect})" \
|
204
|
+
if !roles.is_a?(Array) or (!roles.empty? and !roles[0].is_a?(Symbol))
|
205
|
+
|
206
|
+
(roles.empty? ? [Authorization.default_role] : roles)
|
207
|
+
end
|
208
|
+
|
209
|
+
# Returns the role symbols and inherritted role symbols for the given user
|
210
|
+
def roles_with_hierarchy_for(user)
|
211
|
+
flatten_roles(roles_for(user))
|
212
|
+
end
|
213
|
+
|
214
|
+
def self.development_reload?
|
215
|
+
if Rails.env.development?
|
216
|
+
mod_time = AUTH_DSL_FILES.map { |m| File.mtime(m) rescue Time.at(0) }.flatten.max
|
217
|
+
@@auth_dsl_last_modified ||= mod_time
|
218
|
+
if mod_time > @@auth_dsl_last_modified
|
219
|
+
@@auth_dsl_last_modified = mod_time
|
220
|
+
return true
|
221
|
+
end
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
# Returns an instance of Engine, which is created if there isn't one
|
226
|
+
# yet. If +dsl_file+ is given, it is passed on to Engine.new and
|
227
|
+
# a new instance is always created.
|
228
|
+
def self.instance (dsl_file = nil)
|
229
|
+
if dsl_file or development_reload?
|
230
|
+
@@instance = new(dsl_file)
|
231
|
+
else
|
232
|
+
@@instance ||= new
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
class AttributeValidator # :nodoc:
|
237
|
+
attr_reader :user, :object, :engine, :context, :privilege
|
238
|
+
def initialize (engine, user, object = nil, privilege = nil, context = nil)
|
239
|
+
@engine = engine
|
240
|
+
@user = user
|
241
|
+
@object = object
|
242
|
+
@privilege = privilege
|
243
|
+
@context = context
|
244
|
+
end
|
245
|
+
|
246
|
+
def evaluate (value_block)
|
247
|
+
# TODO cache?
|
248
|
+
instance_eval(&value_block)
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
private
|
253
|
+
def user_roles_privleges_from_options(privilege, options)
|
254
|
+
options = {
|
255
|
+
:user => nil,
|
256
|
+
:context => nil,
|
257
|
+
:user_roles => nil
|
258
|
+
}.merge(options)
|
259
|
+
user = options[:user] || Authorization.current_user
|
260
|
+
privileges = privilege.is_a?(Array) ? privilege : [privilege]
|
261
|
+
|
262
|
+
raise AuthorizationUsageError, "No user object given (#{user.inspect}) or " +
|
263
|
+
"set through Authorization.current_user" unless user
|
264
|
+
|
265
|
+
roles = options[:user_roles] || flatten_roles(roles_for(user))
|
266
|
+
privileges = flatten_privileges privileges, options[:context]
|
267
|
+
[user, roles, privileges]
|
268
|
+
end
|
269
|
+
|
270
|
+
def flatten_roles (roles, flattened_roles = Set.new)
|
271
|
+
# TODO caching?
|
272
|
+
roles.reject {|role| flattened_roles.include?(role)}.each do |role|
|
273
|
+
flattened_roles << role
|
274
|
+
flatten_roles(role_hierarchy[role], flattened_roles) if role_hierarchy[role]
|
275
|
+
end
|
276
|
+
flattened_roles.to_a
|
277
|
+
end
|
278
|
+
|
279
|
+
# Returns the privilege hierarchy flattened for given privileges in context.
|
280
|
+
def flatten_privileges (privileges, context = nil, flattened_privileges = Set.new)
|
281
|
+
# TODO caching?
|
282
|
+
raise AuthorizationUsageError, "No context given or inferable from object" unless context
|
283
|
+
privileges.reject {|priv| flattened_privileges.include?(priv)}.each do |priv|
|
284
|
+
flattened_privileges << priv
|
285
|
+
flatten_privileges(rev_priv_hierarchy[[priv, nil]], context, flattened_privileges) if rev_priv_hierarchy[[priv, nil]]
|
286
|
+
flatten_privileges(rev_priv_hierarchy[[priv, context]], context, flattened_privileges) if rev_priv_hierarchy[[priv, context]]
|
287
|
+
end
|
288
|
+
flattened_privileges.to_a
|
289
|
+
end
|
290
|
+
|
291
|
+
def matching_auth_rules (roles, privileges, context)
|
292
|
+
auth_rules.matching(roles, privileges, context)
|
293
|
+
end
|
294
|
+
end
|
295
|
+
end
|
296
|
+
end
|