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,142 @@
|
|
1
|
+
#
|
2
|
+
# = lib/annotation_security/utils.rb
|
3
|
+
#
|
4
|
+
# Provides some methods that are needed at several locations in the plug-in.
|
5
|
+
#
|
6
|
+
|
7
|
+
class AnnotationSecurity::Utils # :nodoc:
|
8
|
+
|
9
|
+
PREFIXES = /\A(may|is|can|has)_/
|
10
|
+
SUFFIXES = /(_(for|in|of|to)|\?)\Z/
|
11
|
+
|
12
|
+
# Removes pre- and suffixes from +method+,
|
13
|
+
# returns +nil+ if no change was made.
|
14
|
+
#
|
15
|
+
def self.method_body(method)
|
16
|
+
body = method.to_s.gsub(PREFIXES,'').gsub(SUFFIXES,'')
|
17
|
+
method.to_s == body ? nil : body
|
18
|
+
end
|
19
|
+
|
20
|
+
# Parses a description string
|
21
|
+
# * +description+ description of a controller action
|
22
|
+
# * +allow_binding+ if false, an exception is raised if the description
|
23
|
+
# contains a variable
|
24
|
+
# Returns right, resource and binding
|
25
|
+
#
|
26
|
+
def self.parse_description(description,allow_binding=false)
|
27
|
+
ActionAnnotation::Utils.parse_description(description,allow_binding)
|
28
|
+
end
|
29
|
+
|
30
|
+
# Parses arguments provided to #apply_policy or #allowed? and returns
|
31
|
+
# [ [:action, :resource_type, resource || nil], ... ]
|
32
|
+
#
|
33
|
+
# See SecurityContext#allowed? for details.
|
34
|
+
#
|
35
|
+
# Each element of the result can be send to a policy using
|
36
|
+
# policy_of_res_type.allowed?(rule, resource)
|
37
|
+
# or
|
38
|
+
# policy_of_res_type.static_policy.allowed?(rule, nil)
|
39
|
+
#
|
40
|
+
# Raises ArgumentError if args could not be parsed.
|
41
|
+
#
|
42
|
+
def self.parse_policy_arguments(args)
|
43
|
+
if args.first.is_a? String
|
44
|
+
hash = AnnotationSecurity::Utils.parse_description(args.first)
|
45
|
+
elsif args.first.is_a? Hash
|
46
|
+
hash = args.first
|
47
|
+
end
|
48
|
+
if hash
|
49
|
+
action = hash.delete(:action) || hash.delete('action')
|
50
|
+
resource = hash.delete(:resource) || hash.delete('resource')
|
51
|
+
unless resource.__is_resource?
|
52
|
+
resource_type = resource
|
53
|
+
resource = nil
|
54
|
+
end
|
55
|
+
resource_type ||= hash.delete(:resource_type)
|
56
|
+
resource_type ||= resource ? resource.resource_type : nil
|
57
|
+
a = [action, resource_type]
|
58
|
+
a << resource if resource
|
59
|
+
args = a + args[1..-1]
|
60
|
+
end
|
61
|
+
|
62
|
+
args << :all_resources unless args.size > 1
|
63
|
+
|
64
|
+
action, resource = args
|
65
|
+
|
66
|
+
if resource.__is_resource?
|
67
|
+
args = [action, resource.resource_type] + args[1..-1]
|
68
|
+
end
|
69
|
+
# if args.size > 2 && args.third == nil
|
70
|
+
# raise ArgumentError, "Did not expect nil as resource"
|
71
|
+
# end
|
72
|
+
args
|
73
|
+
end
|
74
|
+
|
75
|
+
# returns resource type and resource object without action
|
76
|
+
# expects [resource object], [resource type], or both
|
77
|
+
def self.parse_resource_arguments(args)
|
78
|
+
parse_policy_arguments([:r]+args)[1..2]
|
79
|
+
end
|
80
|
+
|
81
|
+
# Returns controller, action, objects and parameters
|
82
|
+
def self.parse_action_args(args)
|
83
|
+
controller = parse_controller(args.first)
|
84
|
+
action = args.second.to_sym
|
85
|
+
|
86
|
+
objects = args.third || []
|
87
|
+
objects = [objects] unless objects.is_a? Array
|
88
|
+
prepare_objects_resources(controller, objects)
|
89
|
+
|
90
|
+
params = args.fourth || {}
|
91
|
+
prepare_params_resources(controller, params)
|
92
|
+
|
93
|
+
objects += params.values
|
94
|
+
|
95
|
+
objects = objects.select { |o| o and o.__is_resource? }
|
96
|
+
return [controller, action, objects, params]
|
97
|
+
end
|
98
|
+
|
99
|
+
# Try to find the controller class from a name.
|
100
|
+
# Looks for [name](s)Controller.
|
101
|
+
#
|
102
|
+
# parse_controller :welcome #=> WelcomeController
|
103
|
+
# parse_controller :user # => UsersController
|
104
|
+
#
|
105
|
+
def self.parse_controller(controller) # :nodoc:
|
106
|
+
begin
|
107
|
+
"#{controller.to_s.camelize}Controller".constantize
|
108
|
+
rescue NameError
|
109
|
+
"#{controller.to_s.pluralize.camelize}Controller".constantize
|
110
|
+
end
|
111
|
+
rescue NameError
|
112
|
+
raise NameError, "Controller '#{controller}' was not found"
|
113
|
+
end
|
114
|
+
|
115
|
+
# if there are non-resources in objects, use the values to get resources
|
116
|
+
# from the controllers default resource type
|
117
|
+
#
|
118
|
+
def self.prepare_objects_resources(controller, objects)
|
119
|
+
res_type = controller.default_resource
|
120
|
+
objects.collect! do |o|
|
121
|
+
if o.__is_resource?
|
122
|
+
o
|
123
|
+
else
|
124
|
+
AnnotationSecurity::ResourceManager.get_resource(res_type, o)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
# if there are non-resources in objects, use the values to get resources
|
130
|
+
# assuming the keys are the resource types (:id is defalut resource)
|
131
|
+
#
|
132
|
+
def self.prepare_params_resources(controller, params)
|
133
|
+
params.each do |k, v|
|
134
|
+
unless v.__is_resource?
|
135
|
+
res_type = k == :id ? controller.default_resource : k
|
136
|
+
v = AnnotationSecurity::ResourceManager.get_resource(res_type, v)
|
137
|
+
params[k] = v
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
#
|
2
|
+
# = lib/annotation_security.rb
|
3
|
+
#
|
4
|
+
# This modul provides the AnnotationSecurity security layer.
|
5
|
+
#
|
6
|
+
|
7
|
+
# = AnnotationSecurity
|
8
|
+
module AnnotationSecurity; end
|
9
|
+
|
10
|
+
# Load annotation security files
|
11
|
+
dir = File.dirname(__FILE__)
|
12
|
+
require dir + '/annotation_security/manager/policy_manager'
|
13
|
+
require dir + '/annotation_security/manager/policy_factory'
|
14
|
+
require dir + '/annotation_security/manager/relation_loader'
|
15
|
+
require dir + '/annotation_security/manager/right_loader'
|
16
|
+
require dir + '/annotation_security/manager/resource_manager'
|
17
|
+
require dir + '/annotation_security/policy/abstract_policy'
|
18
|
+
require dir + '/annotation_security/policy/abstract_static_policy'
|
19
|
+
require dir + '/annotation_security/policy/rule_set'
|
20
|
+
require dir + '/annotation_security/policy/rule'
|
21
|
+
require dir + '/annotation_security/includes/resource'
|
22
|
+
require dir + '/annotation_security/includes/action_controller'
|
23
|
+
require dir + '/annotation_security/includes/active_record'
|
24
|
+
require dir + '/annotation_security/includes/role'
|
25
|
+
require dir + '/annotation_security/includes/user'
|
26
|
+
require dir + '/annotation_security/includes/helper'
|
27
|
+
require dir + '/annotation_security/exceptions'
|
28
|
+
require dir + '/annotation_security/filters'
|
29
|
+
require dir + '/annotation_security/model_observer'
|
30
|
+
require dir + '/annotation_security/user_wrapper'
|
31
|
+
require dir + '/annotation_security/utils'
|
32
|
+
|
33
|
+
require dir + '/security_context'
|
34
|
+
|
35
|
+
module AnnotationSecurity
|
36
|
+
|
37
|
+
# Load the file specified by +fname+.
|
38
|
+
# The file will be reloaded automatically if reset is called.
|
39
|
+
#
|
40
|
+
# See AnnotationSecurity::RightLoader for details.
|
41
|
+
#
|
42
|
+
def self.load_rights(fname, ext = 'yml')
|
43
|
+
# The file is expected to be a yaml file.
|
44
|
+
# However, it is also possible to use a ruby file that uses
|
45
|
+
# AnnotationSecurity.define_rights. In this case, ext should be 'rb'.
|
46
|
+
PolicyManager.add_file(fname, ext)
|
47
|
+
end
|
48
|
+
|
49
|
+
# Load the file specified by +fname+.
|
50
|
+
# The file will be reloaded automatically if reset is called.
|
51
|
+
#
|
52
|
+
# See AnnotationSecurity::RelationLoader for details.
|
53
|
+
#
|
54
|
+
def self.load_relations(fname)
|
55
|
+
PolicyManager.add_file(fname, 'rb')
|
56
|
+
end
|
57
|
+
|
58
|
+
# Defines relations specified in +block+.
|
59
|
+
#
|
60
|
+
# See AnnotationSecurity::RelationLoader for details
|
61
|
+
#
|
62
|
+
def self.define_relations(*resources,&block)
|
63
|
+
RelationLoader.define_relations(*resources,&block)
|
64
|
+
end
|
65
|
+
|
66
|
+
# Defines rights specified in +hash+.
|
67
|
+
#
|
68
|
+
# See AnnotationSecurity::RightLoader for details
|
69
|
+
#
|
70
|
+
def self.define_rights(hash)
|
71
|
+
RightLoader.define_rights(hash)
|
72
|
+
end
|
73
|
+
|
74
|
+
# Reloads all files that were loaded with load_rights or load_relations.
|
75
|
+
#
|
76
|
+
# In development mode, reset is being executed before each request.
|
77
|
+
#
|
78
|
+
def self.reset
|
79
|
+
PolicyManager.reset
|
80
|
+
end
|
81
|
+
|
82
|
+
# Initializes AnnotationSecurity for a Rails application and loads
|
83
|
+
# Rails specific parts of the library.
|
84
|
+
#
|
85
|
+
# This method is called by `init.rb`,
|
86
|
+
# which is run by Rails on startup.
|
87
|
+
#
|
88
|
+
# * +binding+ [Binding] The context of the `init.rb` file.
|
89
|
+
def self.init_rails(binding)
|
90
|
+
puts "Initializing AnnotationSecurity security layer"
|
91
|
+
|
92
|
+
%w{annotation_security/rails extensions/object extensions/action_controller
|
93
|
+
extensions/active_record extensions/filter }.each { |f| require f }
|
94
|
+
|
95
|
+
config = eval("config", binding)
|
96
|
+
AnnotationSecurity::Rails.init!(config)
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
#
|
2
|
+
# = lib/extensions/action_controller.rb
|
3
|
+
#
|
4
|
+
|
5
|
+
module ActionController # :nodoc:
|
6
|
+
|
7
|
+
# Extends ActionController::Base for security.
|
8
|
+
#
|
9
|
+
class Base # :nodoc:
|
10
|
+
|
11
|
+
# Include required security functionality
|
12
|
+
include AnnotationSecurity::ActionController
|
13
|
+
|
14
|
+
alias render_without_security render
|
15
|
+
|
16
|
+
# Before rendering, evaluates the bounded rules of the current action.
|
17
|
+
#
|
18
|
+
def render(*args, &block)
|
19
|
+
SecurityContext.apply_rules_after_action
|
20
|
+
render_without_security(*args, &block)
|
21
|
+
end
|
22
|
+
|
23
|
+
alias redirect_to_without_security redirect_to
|
24
|
+
|
25
|
+
# Before redirecting, evaluates the bounded rules of the current action.
|
26
|
+
#
|
27
|
+
def redirect_to(*args, &block)
|
28
|
+
SecurityContext.apply_rules_after_action
|
29
|
+
redirect_to_without_security(*args, &block)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
#
|
2
|
+
# = lib/extensions/active_record.rb
|
3
|
+
#
|
4
|
+
|
5
|
+
module ActiveRecord # :nodoc:
|
6
|
+
|
7
|
+
# Extends ActiveRecord::Base so that model classes
|
8
|
+
# can be tagged as resources.
|
9
|
+
#
|
10
|
+
# To associate a model class with a resource type, use #resource in the class
|
11
|
+
# definition.
|
12
|
+
#
|
13
|
+
# class MyResource < ActiveRecord::Base
|
14
|
+
# resource :my_resource
|
15
|
+
#
|
16
|
+
# # ...
|
17
|
+
# end
|
18
|
+
#
|
19
|
+
# If you don't pass an argument to #resource, the resource name will be
|
20
|
+
# the underscored class name.
|
21
|
+
#
|
22
|
+
# See AnnotationSecurity::Resource if you want to use non-model classes as resources.
|
23
|
+
#
|
24
|
+
class Base
|
25
|
+
|
26
|
+
# Declares a model class to be a resource.
|
27
|
+
# * +resource_type+ (optional) Symbol of the resource type (like :course)
|
28
|
+
def self.resource(resource_type = nil)
|
29
|
+
include ::AnnotationSecurity::ActiveRecord
|
30
|
+
self.resource_type = resource_type if resource_type
|
31
|
+
self.resource_type
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
@@ -0,0 +1,134 @@
|
|
1
|
+
#
|
2
|
+
# = lib/extensions/filter.rb
|
3
|
+
#
|
4
|
+
# Adds security filters to the Rails filter mechanism.
|
5
|
+
#
|
6
|
+
# Modifies ActionController::Filter::FilterChain. Might not work with other
|
7
|
+
# gems modifying this class.
|
8
|
+
#
|
9
|
+
|
10
|
+
# Extends ActiveRecord::Base and patches ActionController::Filters
|
11
|
+
#
|
12
|
+
# Performs additions to the rails filter chain. It basically adds two
|
13
|
+
# filters which may not be removed:
|
14
|
+
#
|
15
|
+
# 1) Before Fiter to initialize SecurityContext
|
16
|
+
# 2) Around Filter around actions
|
17
|
+
#
|
18
|
+
# The altered filter chain looks like this:
|
19
|
+
#
|
20
|
+
# * AnnotationSecurity::Filters::InitializeSecurity
|
21
|
+
# * ... other before filters
|
22
|
+
# * around filters ...
|
23
|
+
# * AnnotationSecurity::Filters::ApplySecurity
|
24
|
+
# * after filters
|
25
|
+
#
|
26
|
+
module ActionController # :nodoc:
|
27
|
+
module Filters # :nodoc:
|
28
|
+
class FilterChain # :nodoc:
|
29
|
+
def self.new(&block)
|
30
|
+
returning super do |filter_chain|
|
31
|
+
filter_chain.append_filter_to_chain([AnnotationSecurity::Filters::InitializeSecurity], :security, &block)
|
32
|
+
filter_chain.append_filter_to_chain([AnnotationSecurity::Filters::ApplySecurity], :action_security, &block)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def find_filter_append_position(filters, filter_type)
|
39
|
+
# appending an after filter puts it at the end of the call chain
|
40
|
+
# before and around filters go after security filters and
|
41
|
+
# before the first after or action_security filter
|
42
|
+
#
|
43
|
+
return -1 if filter_type == :after
|
44
|
+
|
45
|
+
if filter_type == :security
|
46
|
+
#security filters are first filters in chain
|
47
|
+
each_with_index do |f,i|
|
48
|
+
return i unless f.security?
|
49
|
+
end
|
50
|
+
else
|
51
|
+
each_with_index do |f,i|
|
52
|
+
return i if f.after? or f.action_security?
|
53
|
+
end
|
54
|
+
end
|
55
|
+
return -1
|
56
|
+
end
|
57
|
+
|
58
|
+
def find_filter_prepend_position(filters, filter_type)
|
59
|
+
if filter_type == :after
|
60
|
+
# after filters go before the first after filter in the chain
|
61
|
+
each_with_index do |f,i|
|
62
|
+
return i if f.after?
|
63
|
+
end
|
64
|
+
return -1
|
65
|
+
elsif filter_type == :security
|
66
|
+
return 0
|
67
|
+
else
|
68
|
+
# prepending a before or around filter puts it at the front of the call chain
|
69
|
+
each_with_index do |f,i|
|
70
|
+
return i unless f.security?
|
71
|
+
end
|
72
|
+
end
|
73
|
+
return 0 # Since first filter is security initialization filter
|
74
|
+
end
|
75
|
+
|
76
|
+
def find_or_create_filter(filter, filter_type, options = {})
|
77
|
+
update_filter_in_chain([filter], options)
|
78
|
+
|
79
|
+
if found_filter = find(filter) { |f| f.type == filter_type }
|
80
|
+
found_filter
|
81
|
+
else
|
82
|
+
filter_kind = case
|
83
|
+
when filter.respond_to?(:before) && filter_type == :before
|
84
|
+
:before
|
85
|
+
when filter.respond_to?(:after) && filter_type == :after
|
86
|
+
:after
|
87
|
+
else
|
88
|
+
:filter
|
89
|
+
end
|
90
|
+
|
91
|
+
case filter_type
|
92
|
+
when :before
|
93
|
+
BeforeFilter.new(filter_kind, filter, options)
|
94
|
+
when :after
|
95
|
+
AfterFilter.new(filter_kind, filter, options)
|
96
|
+
when :security
|
97
|
+
SecurityFilter.new(filter_kind, filter, options)
|
98
|
+
when :action_security
|
99
|
+
ActionSecurityFilter.new(filter_kind, filter, options)
|
100
|
+
else
|
101
|
+
AroundFilter.new(filter_kind, filter, options)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
class Filter # :nodoc:
|
108
|
+
|
109
|
+
# override to return true in appropriate subclass
|
110
|
+
def security?
|
111
|
+
false
|
112
|
+
end
|
113
|
+
|
114
|
+
def action_security?
|
115
|
+
false
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
# the customized security filter that sets the current user
|
120
|
+
# and catches security exceptions
|
121
|
+
class SecurityFilter < AroundFilter # :nodoc:
|
122
|
+
def security?
|
123
|
+
true
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
# filter used to activate security for actions
|
128
|
+
class ActionSecurityFilter < AroundFilter # :nodoc:
|
129
|
+
def action_security?
|
130
|
+
true
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|