annotation_security 1.0.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.
- data/CHANGELOG +2 -0
- data/HOW-TO +261 -0
- data/MIT-LICENSE +18 -0
- data/README +39 -0
- data/Rakefile +56 -0
- data/assets/app/helpers/annotation_security_helper.rb +9 -0
- data/assets/config/initializers/annotation_security.rb +12 -0
- data/assets/config/security/relations.rb +20 -0
- data/assets/config/security/rights.yml +16 -0
- data/assets/vendor/plugins/annotation_security/init.rb +14 -0
- data/bin/annotation_security +8 -0
- data/lib/annotation_security/exceptions.rb +125 -0
- data/lib/annotation_security/exec.rb +189 -0
- data/lib/annotation_security/filters.rb +38 -0
- data/lib/annotation_security/includes/action_controller.rb +144 -0
- data/lib/annotation_security/includes/active_record.rb +28 -0
- data/lib/annotation_security/includes/helper.rb +215 -0
- data/lib/annotation_security/includes/resource.rb +85 -0
- data/lib/annotation_security/includes/role.rb +31 -0
- data/lib/annotation_security/includes/user.rb +27 -0
- data/lib/annotation_security/manager/policy_factory.rb +30 -0
- data/lib/annotation_security/manager/policy_manager.rb +80 -0
- data/lib/annotation_security/manager/relation_loader.rb +273 -0
- data/lib/annotation_security/manager/resource_manager.rb +36 -0
- data/lib/annotation_security/manager/right_loader.rb +88 -0
- data/lib/annotation_security/model_observer.rb +61 -0
- data/lib/annotation_security/policy/abstract_policy.rb +345 -0
- data/lib/annotation_security/policy/abstract_static_policy.rb +76 -0
- data/lib/annotation_security/policy/all_resources_policy.rb +21 -0
- data/lib/annotation_security/policy/rule.rb +340 -0
- data/lib/annotation_security/policy/rule_set.rb +139 -0
- data/lib/annotation_security/rails.rb +39 -0
- data/lib/annotation_security/user_wrapper.rb +74 -0
- data/lib/annotation_security/utils.rb +142 -0
- data/lib/annotation_security.rb +98 -0
- data/lib/extensions/action_controller.rb +33 -0
- data/lib/extensions/active_record.rb +35 -0
- data/lib/extensions/filter.rb +134 -0
- data/lib/extensions/object.rb +11 -0
- data/lib/security_context.rb +551 -0
- data/spec/annotation_security/exceptions_spec.rb +17 -0
- data/spec/annotation_security/includes/helper_spec.rb +82 -0
- data/spec/annotation_security/manager/policy_manager_spec.rb +15 -0
- data/spec/annotation_security/manager/resource_manager_spec.rb +17 -0
- data/spec/annotation_security/manager/right_loader_spec.rb +17 -0
- data/spec/annotation_security/policy/abstract_policy_spec.rb +17 -0
- data/spec/annotation_security/policy/all_resources_policy_spec.rb +24 -0
- data/spec/annotation_security/policy/rule_set_spec.rb +112 -0
- data/spec/annotation_security/policy/rule_spec.rb +78 -0
- data/spec/annotation_security/policy/test_policy_spec.rb +81 -0
- data/spec/annotation_security/security_context_spec.rb +78 -0
- data/spec/annotation_security/utils_spec.rb +74 -0
- data/spec/helper/test_controller.rb +66 -0
- data/spec/helper/test_helper.rb +5 -0
- data/spec/helper/test_relations.rb +7 -0
- data/spec/helper/test_resource.rb +39 -0
- data/spec/helper/test_rights.yml +5 -0
- data/spec/helper/test_role.rb +22 -0
- data/spec/helper/test_user.rb +32 -0
- data/spec/rails_stub.rb +38 -0
- data/spec/spec_helper.rb +43 -0
- metadata +157 -0
@@ -0,0 +1,31 @@
|
|
1
|
+
#
|
2
|
+
# = lib/annotation_security/includes/role.rb
|
3
|
+
#
|
4
|
+
|
5
|
+
# = AnnotationSecurity::Role
|
6
|
+
#
|
7
|
+
# This module should be included by all role classes
|
8
|
+
# to enable full support of all features.
|
9
|
+
#
|
10
|
+
# A role class is a domain class that represents user roles
|
11
|
+
# and does not extend the user class. It should have the method #user that
|
12
|
+
# returns the user object it belongs to.
|
13
|
+
#
|
14
|
+
module AnnotationSecurity::Role
|
15
|
+
|
16
|
+
# Returns true if this belongs to the user given as parameter.
|
17
|
+
#
|
18
|
+
# Required to have a common interface with AnnotationSecurity::User.
|
19
|
+
#
|
20
|
+
def is_user?(user)
|
21
|
+
self.user == user
|
22
|
+
end
|
23
|
+
|
24
|
+
# If +obj+ is a UserWrapper, extract the role before comparing
|
25
|
+
#
|
26
|
+
def ==(obj)
|
27
|
+
obj = obj.__role__ if obj.is_a? AnnotationSecurity::UserWrapper
|
28
|
+
super(obj)
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
#
|
2
|
+
# = lib/annotation_security/includes/user.rb
|
3
|
+
#
|
4
|
+
|
5
|
+
# = AnnotationSecurity::User
|
6
|
+
#
|
7
|
+
# This module should be included by the user domain class to
|
8
|
+
# enable full support of all features.
|
9
|
+
#
|
10
|
+
module AnnotationSecurity::User
|
11
|
+
|
12
|
+
# Returns true if this is the user given as parameter.
|
13
|
+
#
|
14
|
+
# Required to have a common interface with AnnotationSecurity::Role.
|
15
|
+
#
|
16
|
+
def is_user?(user)
|
17
|
+
self == user
|
18
|
+
end
|
19
|
+
|
20
|
+
# If +obj+ is a UserWrapper, extract the user before comparing
|
21
|
+
#
|
22
|
+
def ==(obj)
|
23
|
+
obj = obj.__user__ if obj.is_a? AnnotationSecurity::UserWrapper
|
24
|
+
super(obj)
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
#
|
2
|
+
# = lib/annotation_security/manager/policy_factory.rb
|
3
|
+
#
|
4
|
+
|
5
|
+
# = AnnotationSecurity::PolicyFactory
|
6
|
+
# Builds the policy classes.
|
7
|
+
#
|
8
|
+
class AnnotationSecurity::PolicyFactory # :nodoc:
|
9
|
+
|
10
|
+
def initialize(resource_class)
|
11
|
+
@klass = AnnotationSecurity::AbstractPolicy.new_subclass(resource_class)
|
12
|
+
end
|
13
|
+
|
14
|
+
def policy_class
|
15
|
+
@klass
|
16
|
+
end
|
17
|
+
|
18
|
+
def add_rule(symbol,*args,&block)
|
19
|
+
@klass.add_rule(symbol,*args,&block)
|
20
|
+
end
|
21
|
+
|
22
|
+
def create_policy(*args)
|
23
|
+
@klass.new(*args)
|
24
|
+
end
|
25
|
+
|
26
|
+
def reset
|
27
|
+
@klass.reset
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
#
|
2
|
+
# = lib/annotation_security/manager/policy_manager.rb
|
3
|
+
#
|
4
|
+
require 'yaml'
|
5
|
+
|
6
|
+
# = AnnotationSecurity::PolicyManager
|
7
|
+
#
|
8
|
+
# Manages loading and creation of all policy classes.
|
9
|
+
#
|
10
|
+
class AnnotationSecurity::PolicyManager # :nodoc:
|
11
|
+
|
12
|
+
# Get the policy factory for a resource class
|
13
|
+
def self.policy_factory(resource_type) # :nodoc:
|
14
|
+
policy_factories[resource_type.to_sym]
|
15
|
+
end
|
16
|
+
|
17
|
+
# Creates a policy object for a user and a resource type
|
18
|
+
#
|
19
|
+
# ==== Example
|
20
|
+
#
|
21
|
+
# picture = Picture.find_by_id(params[:picture])
|
22
|
+
# policy = PolicyManager.get_policy(:picture,@current_user)
|
23
|
+
# policy.allowed? :show, picture # => true or false
|
24
|
+
#
|
25
|
+
def self.create_policy(resource_type,*args)
|
26
|
+
policy_factory(resource_type).create_policy(*args)
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.policy_class(resource_class) # :nodoc:
|
30
|
+
policy_factory(resource_class).policy_class
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.config_files # :nodoc:
|
34
|
+
@files ||= []
|
35
|
+
end
|
36
|
+
|
37
|
+
# Adds a file that contains security configurations
|
38
|
+
# * +f+ file name
|
39
|
+
# * +ext+ 'yml' or 'rb'
|
40
|
+
def self.add_file(f,ext) # :nodoc:
|
41
|
+
unless config_files.include? [f,ext]
|
42
|
+
config_files.push [f,ext]
|
43
|
+
load_file(f,ext)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.reset
|
48
|
+
policy_factories.each_value(&:reset)
|
49
|
+
config_files.each { |f,ext| load_file(f,ext) }
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def self.load_file(f,ext)
|
55
|
+
fname = get_file_name(f,ext)
|
56
|
+
case ext
|
57
|
+
when 'yml'
|
58
|
+
AnnotationSecurity::RightLoader.define_rights(YAML.load_file(fname))
|
59
|
+
when 'rb'
|
60
|
+
load fname
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
SEARCH_PATH = ['', RAILS_ROOT, RAILS_ROOT + '/config/security/',
|
65
|
+
RAILS_ROOT + '/config/', RAILS_ROOT + '/security/']
|
66
|
+
|
67
|
+
def self.get_file_name(f,ext)
|
68
|
+
SEARCH_PATH.each do |fname1|
|
69
|
+
[f, f+'.'+ext].each do |fname2|
|
70
|
+
return (fname1 + fname2) if File.exist?(fname1 + fname2)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
raise "File not found: '#{f+'.'+ext}'"
|
74
|
+
end
|
75
|
+
|
76
|
+
def self.policy_factories
|
77
|
+
# Create a new factory if it is needed
|
78
|
+
@factories ||= Hash.new { |h,k| h[k] = AnnotationSecurity::PolicyFactory.new(k) }
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,273 @@
|
|
1
|
+
#
|
2
|
+
# = lib/annotation_security/manager/relation_loader.rb
|
3
|
+
#
|
4
|
+
|
5
|
+
# Class responsible for loading the relation definitions for resources.
|
6
|
+
#
|
7
|
+
# == Defining a relation for a resource
|
8
|
+
#
|
9
|
+
# This example defines the owner relation between a picture and a user.
|
10
|
+
# A relation definition is a proc that returns true if the relation exists.
|
11
|
+
# All three examples are equivalent. However, in most cases the first way is
|
12
|
+
# the way you want to use.
|
13
|
+
# AnnotationSecurity.define_relations do
|
14
|
+
# resource :picture do
|
15
|
+
# owner { |user,picture| picture.user == user }
|
16
|
+
# end
|
17
|
+
# end
|
18
|
+
#
|
19
|
+
# If you need only one relation for a resource class, use this example:
|
20
|
+
# AnnotationSecurity.define_relations do
|
21
|
+
# picture.owner { |user,picture| picture.user == user }
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
# If the entire file contains definitions for only one resource class,
|
25
|
+
# you might try this:
|
26
|
+
# AnnotationSecurity.define_relations :picture do
|
27
|
+
# owner { |user,picture| picture.user == user }
|
28
|
+
# end
|
29
|
+
#
|
30
|
+
# === Defining a relation for many resources
|
31
|
+
#
|
32
|
+
# Use +resources+ to define a relation once for more than one resource class.
|
33
|
+
# AnnotationSecurity.define_relations do
|
34
|
+
# resources(:picture, :comment) do
|
35
|
+
# owner { |user,res| res.user == user }
|
36
|
+
# end
|
37
|
+
# end
|
38
|
+
# As for one resource, you can also use
|
39
|
+
# AnnotationSecurity.define_relations do
|
40
|
+
# resources(:picture, :comment).owner { |user,res| res.user == user }
|
41
|
+
# end
|
42
|
+
# or
|
43
|
+
# AnnotationSecurity.define_relations(:picture, :comment) do
|
44
|
+
# owner { |user,res| res.user == user }
|
45
|
+
# end
|
46
|
+
#
|
47
|
+
# It is also possible to define relations for all resources:
|
48
|
+
# AnnotationSecurity.define_relations do
|
49
|
+
# all_resources do
|
50
|
+
# related { owner or friend_of_owner }
|
51
|
+
# end
|
52
|
+
# end
|
53
|
+
# or
|
54
|
+
# AnnotationSecurity.define_relations do
|
55
|
+
# all_resources.related { owner or friend_of_owner }
|
56
|
+
# end
|
57
|
+
#
|
58
|
+
# Notice that +owner+ and +friend_of_owner+ are relations that can be defined
|
59
|
+
# individually for each resource. The 2 parameters +user+ and +resource_object+
|
60
|
+
# dont need to be specified if they are not used.
|
61
|
+
#
|
62
|
+
# == Details on defining a relation
|
63
|
+
#
|
64
|
+
# As you have seen, the default way to define a relation is using a proc,
|
65
|
+
# like
|
66
|
+
# owner { |user,picture| picture.user == user }
|
67
|
+
# related { owner or friend_of_owner }
|
68
|
+
#
|
69
|
+
# If the condition is simple and uses only other relations,
|
70
|
+
# it also can be specified by a string:
|
71
|
+
# related 'if owner or friend_of_owner'
|
72
|
+
#
|
73
|
+
# === Implicit conditions
|
74
|
+
# Besides a string or a proc, a rule definition can contain a list of flags
|
75
|
+
# and an options-hash.
|
76
|
+
#
|
77
|
+
# ==== The :is option
|
78
|
+
#
|
79
|
+
# A Relation to which the <tt>:is => symbol</tt> option is passed as a parameter
|
80
|
+
# only exists if the relation exists and <tt>is_symbol?</tt> invoked on the
|
81
|
+
# current user evaluates to true.
|
82
|
+
#
|
83
|
+
# Let the user class have a method <tt>is_super_user?</tt>, which returns true
|
84
|
+
# or false, depending on wheter the user is a super user. This method can be
|
85
|
+
# used for defining a relation +super_owner+, that is true if the user is the
|
86
|
+
# owner and a super user.
|
87
|
+
#
|
88
|
+
# owner { |user,picture| picture.user == user }
|
89
|
+
# super_owner(:is => :super_user) "if owner"
|
90
|
+
#
|
91
|
+
# super_user(:system, :is => :super_user)
|
92
|
+
#
|
93
|
+
# ==== The :as option
|
94
|
+
#
|
95
|
+
# For a relation to which the <tt>:as => symbol</tt> option is passed as a
|
96
|
+
# parameter the current user is replaced by the invocation of
|
97
|
+
# <tt>current_credential.as_[symbol]</tt>. The method invocation may return +nil+
|
98
|
+
# indicating that the transformation failed. In this case the relation for
|
99
|
+
# which <tt>:as => ..</tt> was specified does not exist.
|
100
|
+
#
|
101
|
+
# ==== :require_credential
|
102
|
+
# By default, a relation requires a user to be executed. Therefore, rights will
|
103
|
+
# always fail if the user is nil. To enable rights like 'unless logged_in', the
|
104
|
+
# :require_credential option can be set to false.
|
105
|
+
# logged_in(:system, :require_credential => false) { |user| not user.nil? }
|
106
|
+
#
|
107
|
+
# === Evaluation time
|
108
|
+
# While most relations are between the user and a resource object, some are
|
109
|
+
# beween the user and an entire class of objects. This means that no instance of
|
110
|
+
# a resource is required to tell whether the user has that relation or not.
|
111
|
+
#
|
112
|
+
# ==== The :resource flag
|
113
|
+
# This flag is set by default. It is set for relations that need a resource.
|
114
|
+
#
|
115
|
+
# owner { |user,picture| picture.user == user }
|
116
|
+
# # is short for
|
117
|
+
# # owner(:resource) { |user,picture| picture.user == user }
|
118
|
+
#
|
119
|
+
# ==== The :system flag
|
120
|
+
# You can use the :system flag to denote that a relation does not
|
121
|
+
# require a resource object.
|
122
|
+
#
|
123
|
+
# all_resources do
|
124
|
+
# super_user(:system, :is => :super_user)
|
125
|
+
# end
|
126
|
+
#
|
127
|
+
# It is possible to define system relations only for certain resources, and they
|
128
|
+
# do not conflict with resource relations.
|
129
|
+
#
|
130
|
+
# resource :present do
|
131
|
+
# receiver(:system) { |user| user.was_good? }
|
132
|
+
# receiver { |user,present| present.receiver == user }
|
133
|
+
# end
|
134
|
+
#
|
135
|
+
# The advantage of system relations is that they improve the rights evaluation.
|
136
|
+
# Consider the right
|
137
|
+
# present:
|
138
|
+
# receive: if receiver
|
139
|
+
#
|
140
|
+
# If an action is invoked requiring the receive-present right,
|
141
|
+
# AnnotationSecurity will evaluate the system relation before even entering the
|
142
|
+
# action, thus improving the fail fast behavior and avoiding unnecessary
|
143
|
+
# operations.
|
144
|
+
#
|
145
|
+
# Once a present object is observed during the action, the resource relation
|
146
|
+
# will be evaluated as well.
|
147
|
+
#
|
148
|
+
# ==== The :pretest flag
|
149
|
+
#
|
150
|
+
# Using the :pretest flag, it is possible to define both resource and system
|
151
|
+
# relations in one block.
|
152
|
+
#
|
153
|
+
# resource :present do
|
154
|
+
# receiver(:pretest) do |user, present|
|
155
|
+
# if present
|
156
|
+
# present.receiver == user
|
157
|
+
# else
|
158
|
+
# user.was_good?
|
159
|
+
# end
|
160
|
+
# end
|
161
|
+
# end
|
162
|
+
#
|
163
|
+
# This can be helpfull if your relation depends on other relations, where a
|
164
|
+
# resource and a system version is available.
|
165
|
+
#
|
166
|
+
# all_resources do
|
167
|
+
# responsible(:pretest) { lecturer or corrector }
|
168
|
+
# lecturer(:system, :as => :lecturer)
|
169
|
+
# corrector(:system, :as => :corrector)
|
170
|
+
# end
|
171
|
+
#
|
172
|
+
# resource :course do
|
173
|
+
# lecturer(:as => :lecturer) { |lecturer, course| course.lecturers.include? lecturer }
|
174
|
+
# corrector(:as => :corrector) { |corrector, course| course.correctors.include? corrector }
|
175
|
+
# end
|
176
|
+
# # For other resources, lecturer and corrector are defined differently
|
177
|
+
#
|
178
|
+
# === Defining relations as strings
|
179
|
+
#
|
180
|
+
# Instead of a block, a string can be used to define the relation.
|
181
|
+
# responsible :pretest, "if lecturer or corrector"
|
182
|
+
#
|
183
|
+
# The string syntax provides more simplifications, like referring to relations
|
184
|
+
# of other resources.
|
185
|
+
#
|
186
|
+
# This example will evaluate the course-correction relation for the course
|
187
|
+
# property of an assignment resource.
|
188
|
+
# resource :assignment do
|
189
|
+
# corrector "if course.corrector: course"
|
190
|
+
# end
|
191
|
+
#
|
192
|
+
# As the course class includes AnnotationSecurity::Resource, the resource type
|
193
|
+
# is not explicitly needed.
|
194
|
+
# resource :assignment_result do
|
195
|
+
# corrector "if corrector: assignment.course"
|
196
|
+
# end
|
197
|
+
#
|
198
|
+
#
|
199
|
+
class AnnotationSecurity::RelationLoader
|
200
|
+
|
201
|
+
# Load relations of the +block+
|
202
|
+
# * +resources+ (optional) list of resources
|
203
|
+
# * +block+ block with relation definitions
|
204
|
+
def self.define_relations(*resources, &block)
|
205
|
+
if resources.blank?
|
206
|
+
class_eval(&block)
|
207
|
+
else
|
208
|
+
resources(*resources,&block)
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
#
|
213
|
+
def self.method_missing(symbol,*args,&block) #:nodoc:
|
214
|
+
return super unless args.empty?
|
215
|
+
resources(symbol,&block)
|
216
|
+
end
|
217
|
+
|
218
|
+
# Defines relations for a resource
|
219
|
+
# * +block+ (optional) proc with relation definitions
|
220
|
+
def self.resource(resource,&block)
|
221
|
+
resources(resource,&block)
|
222
|
+
end
|
223
|
+
|
224
|
+
# Defines relations for a list of resources
|
225
|
+
# * +block+ (optional) proc with relation definitions
|
226
|
+
def self.resources(*resources,&block)
|
227
|
+
new(resources,&block)
|
228
|
+
end
|
229
|
+
|
230
|
+
# Defines relations for all resources
|
231
|
+
# * +block+ (optional) proc with relation definitions
|
232
|
+
def self.all_resources(&block)
|
233
|
+
resources(:all_resources,&block)
|
234
|
+
end
|
235
|
+
|
236
|
+
## ===========================================================================
|
237
|
+
## Instance
|
238
|
+
|
239
|
+
# An instance of RelationLoader is responsible for loading the relations
|
240
|
+
# for a set of resources.
|
241
|
+
def initialize(resources,&block) #:nodoc:
|
242
|
+
@factories = get_factories_for(resources)
|
243
|
+
instance_eval(&block) if block
|
244
|
+
end
|
245
|
+
|
246
|
+
# if a method is missing this will be a new relation for the resource class
|
247
|
+
def method_missing(symbol,*args,&block) #:nodoc:
|
248
|
+
define_relation(symbol,*args,&block)
|
249
|
+
end
|
250
|
+
|
251
|
+
# Defines a new relation for the current resources. However, instead of using
|
252
|
+
# define_relation(:relation_name,args) { |user,res| some_condition }
|
253
|
+
# it is recommended to use
|
254
|
+
# relation_name(args) { |user,res| some_condition }
|
255
|
+
#
|
256
|
+
# ==== Parameters
|
257
|
+
# * +symbol+ name of the relation
|
258
|
+
# * +args+ additonal arguments, see AnnotationSecurity::Rule for details
|
259
|
+
# * +block+ (optional) The condition can be passed either as string or as proc
|
260
|
+
#
|
261
|
+
def define_relation(symbol,*args,&block)
|
262
|
+
@factories.each do |factory|
|
263
|
+
factory.add_rule(symbol,*args,&block)
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
private
|
268
|
+
|
269
|
+
def get_factories_for(resources)
|
270
|
+
resources.collect{ |res| AnnotationSecurity::PolicyManager.policy_factory(res) }
|
271
|
+
end
|
272
|
+
|
273
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
#
|
2
|
+
# = lib/annotation_security/manager/resource_manager.rb
|
3
|
+
#
|
4
|
+
|
5
|
+
# Needed to find resource objects when only their id is known
|
6
|
+
#
|
7
|
+
class AnnotationSecurity::ResourceManager # :nodoc:
|
8
|
+
|
9
|
+
@classes = {}
|
10
|
+
|
11
|
+
def self.add_resource_class(res_type,klass)
|
12
|
+
@classes.delete_if { |k,v| v == klass }
|
13
|
+
@classes[res_type] = klass
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.get_resource_class(res_type)
|
17
|
+
@classes[res_type]
|
18
|
+
end
|
19
|
+
|
20
|
+
unless RAILS_ENV == 'production'
|
21
|
+
def self.get_resource_class(res_type)
|
22
|
+
c = @classes[res_type]
|
23
|
+
unless c
|
24
|
+
res_type.to_s.camelize.constantize # load the class
|
25
|
+
c = @classes[res_type]
|
26
|
+
end
|
27
|
+
c
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# Call get_resource of the class that is registered for +res_type+
|
32
|
+
def self.get_resource(res_type,object)
|
33
|
+
c = get_resource_class(res_type)
|
34
|
+
c ? c.get_resource(object) : object
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
#
|
2
|
+
# = lib/annotation_security/manager/right_loader.rb
|
3
|
+
#
|
4
|
+
|
5
|
+
# = AnnotationSecurity::RightLoader
|
6
|
+
# Contains the right loader class, which is responsible for loading
|
7
|
+
# right definitions for resources. Load rights from a yaml file or a hash.
|
8
|
+
#
|
9
|
+
# == Example YAML
|
10
|
+
#
|
11
|
+
# The file <tt>config/security/rights.yml</tt> inside a rails app
|
12
|
+
# might look like this:
|
13
|
+
# picture:
|
14
|
+
# # a user may show a picture if he fulfils the 'related'-relation
|
15
|
+
# show: if related
|
16
|
+
# comment:
|
17
|
+
# # you have to be logged in to view comments
|
18
|
+
# show: if logged_in
|
19
|
+
# user:
|
20
|
+
# # like in ruby, 'unless' is equivalent to 'if not'
|
21
|
+
# register: unless logged_in
|
22
|
+
# delete: if administrator # comments are also possible behind a line
|
23
|
+
# user_content:
|
24
|
+
# # all rights of 'user_content' are defined for 'picture' and 'comment' too
|
25
|
+
# applies_to: picture, comment
|
26
|
+
# create: if logged_in
|
27
|
+
# edit: if owner
|
28
|
+
# delete: if owner or administrator
|
29
|
+
# The file can be loaded via <code>AnnotationSecurity#load_rights('rights')</code>.
|
30
|
+
#
|
31
|
+
# A right's condition can use the keywords +if+, +unless+, +and+, +or+ and
|
32
|
+
# +not+, brackets, other rights and all of the resource's relations
|
33
|
+
# (see AnnotationSecurity::RelationLoader). For better readability you may
|
34
|
+
# add the prefixes +may+, +is+, +can+ or +has+,
|
35
|
+
# or append one of the suffixes +for+, +in+, +of+ or +to+.
|
36
|
+
#
|
37
|
+
# user_content:
|
38
|
+
# edit: if is_owner_of
|
39
|
+
# delete: if may_edit or is_administrator
|
40
|
+
#
|
41
|
+
# However, it is recommended to use this feature sparingly.
|
42
|
+
#
|
43
|
+
class AnnotationSecurity::RightLoader
|
44
|
+
|
45
|
+
# Goes through all resources of +hash+ and load the defined rights.
|
46
|
+
#
|
47
|
+
def self.define_rights(hash) # :nodoc:
|
48
|
+
if hash
|
49
|
+
hash.each_pair do |resource_class, rights|
|
50
|
+
new(resource_class).define_rights(rights)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# An instance of RightLoader is responsible for loading the rights of a
|
56
|
+
# resource class.
|
57
|
+
#
|
58
|
+
def initialize(resource) # :nodoc:
|
59
|
+
@factory = AnnotationSecurity::PolicyManager.policy_factory(resource)
|
60
|
+
end
|
61
|
+
|
62
|
+
# Goes through all rights in +hash+ and creates rules for all policies these
|
63
|
+
# rights apply to.
|
64
|
+
#
|
65
|
+
def define_rights(hash) # :nodoc:
|
66
|
+
factories = extract_applies_to(hash) << @factory
|
67
|
+
hash.each_pair do |right,condition|
|
68
|
+
# Important: set the :right-flag to activate automatic detection of
|
69
|
+
# the other flags (static,dynamic,require_user)
|
70
|
+
factories.each { |f| f.add_rule(right,:right,condition) }
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
private
|
75
|
+
|
76
|
+
# Looks for the key 'applies_to', which is no right but a command to apply
|
77
|
+
# all rights of the current resource class to the resource classes listed
|
78
|
+
# in the value.
|
79
|
+
#
|
80
|
+
def extract_applies_to(hash)
|
81
|
+
applies_to = hash.delete('applies_to') || hash.delete(:applies_to)
|
82
|
+
return [] if applies_to.blank?
|
83
|
+
applies_to = [applies_to] if applies_to.is_a? String
|
84
|
+
applies_to.collect{ |r| r.split(',') }.flatten.
|
85
|
+
collect{ |r| AnnotationSecurity::PolicyManager.policy_factory(r.strip) }
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
#
|
2
|
+
# = lib/annotation_security/model_observer.rb
|
3
|
+
#
|
4
|
+
# Contains SecurityObserver which implements constraint checking for model
|
5
|
+
# classes.
|
6
|
+
#
|
7
|
+
|
8
|
+
module AnnotationSecurity
|
9
|
+
|
10
|
+
# Observes changes in models and applies security policy to them
|
11
|
+
#
|
12
|
+
class ModelObserver < ::ActiveRecord::Observer # :nodoc:
|
13
|
+
|
14
|
+
# Sets the observed model classes
|
15
|
+
#
|
16
|
+
observe # will be set automatically. However, observe must not be removed
|
17
|
+
|
18
|
+
def before_validation_on_create(record)
|
19
|
+
SecurityContext.observe record
|
20
|
+
end
|
21
|
+
|
22
|
+
def before_validation_on_update(record)
|
23
|
+
SecurityContext.observe record
|
24
|
+
end
|
25
|
+
|
26
|
+
# after_find is removed in favour of after_initialize
|
27
|
+
|
28
|
+
def after_initialize(record)
|
29
|
+
if record.new_record?
|
30
|
+
# The record is new
|
31
|
+
else
|
32
|
+
# The record came out of database
|
33
|
+
SecurityContext.observe record
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def before_destroy(record)
|
38
|
+
SecurityContext.observe record
|
39
|
+
end
|
40
|
+
|
41
|
+
# Re-register on classes you are observing
|
42
|
+
# See http://riotprojects.com/2009/1/18/active-record-observers-in-gems-plugins
|
43
|
+
#
|
44
|
+
def reload_model_observer
|
45
|
+
observed_classes.each do |klass|
|
46
|
+
add_observer!(klass.name.constantize)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
protected
|
51
|
+
|
52
|
+
def add_observer!(klass)
|
53
|
+
klass.delete_observer(self)
|
54
|
+
super
|
55
|
+
|
56
|
+
if respond_to?(:after_initialize) && !klass.method_defined?(:after_initialize)
|
57
|
+
klass.class_eval 'def after_initialize() end'
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|