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.
Files changed (62) hide show
  1. data/CHANGELOG +2 -0
  2. data/HOW-TO +261 -0
  3. data/MIT-LICENSE +18 -0
  4. data/README +39 -0
  5. data/Rakefile +56 -0
  6. data/assets/app/helpers/annotation_security_helper.rb +9 -0
  7. data/assets/config/initializers/annotation_security.rb +12 -0
  8. data/assets/config/security/relations.rb +20 -0
  9. data/assets/config/security/rights.yml +16 -0
  10. data/assets/vendor/plugins/annotation_security/init.rb +14 -0
  11. data/bin/annotation_security +8 -0
  12. data/lib/annotation_security/exceptions.rb +125 -0
  13. data/lib/annotation_security/exec.rb +189 -0
  14. data/lib/annotation_security/filters.rb +38 -0
  15. data/lib/annotation_security/includes/action_controller.rb +144 -0
  16. data/lib/annotation_security/includes/active_record.rb +28 -0
  17. data/lib/annotation_security/includes/helper.rb +215 -0
  18. data/lib/annotation_security/includes/resource.rb +85 -0
  19. data/lib/annotation_security/includes/role.rb +31 -0
  20. data/lib/annotation_security/includes/user.rb +27 -0
  21. data/lib/annotation_security/manager/policy_factory.rb +30 -0
  22. data/lib/annotation_security/manager/policy_manager.rb +80 -0
  23. data/lib/annotation_security/manager/relation_loader.rb +273 -0
  24. data/lib/annotation_security/manager/resource_manager.rb +36 -0
  25. data/lib/annotation_security/manager/right_loader.rb +88 -0
  26. data/lib/annotation_security/model_observer.rb +61 -0
  27. data/lib/annotation_security/policy/abstract_policy.rb +345 -0
  28. data/lib/annotation_security/policy/abstract_static_policy.rb +76 -0
  29. data/lib/annotation_security/policy/all_resources_policy.rb +21 -0
  30. data/lib/annotation_security/policy/rule.rb +340 -0
  31. data/lib/annotation_security/policy/rule_set.rb +139 -0
  32. data/lib/annotation_security/rails.rb +39 -0
  33. data/lib/annotation_security/user_wrapper.rb +74 -0
  34. data/lib/annotation_security/utils.rb +142 -0
  35. data/lib/annotation_security.rb +98 -0
  36. data/lib/extensions/action_controller.rb +33 -0
  37. data/lib/extensions/active_record.rb +35 -0
  38. data/lib/extensions/filter.rb +134 -0
  39. data/lib/extensions/object.rb +11 -0
  40. data/lib/security_context.rb +551 -0
  41. data/spec/annotation_security/exceptions_spec.rb +17 -0
  42. data/spec/annotation_security/includes/helper_spec.rb +82 -0
  43. data/spec/annotation_security/manager/policy_manager_spec.rb +15 -0
  44. data/spec/annotation_security/manager/resource_manager_spec.rb +17 -0
  45. data/spec/annotation_security/manager/right_loader_spec.rb +17 -0
  46. data/spec/annotation_security/policy/abstract_policy_spec.rb +17 -0
  47. data/spec/annotation_security/policy/all_resources_policy_spec.rb +24 -0
  48. data/spec/annotation_security/policy/rule_set_spec.rb +112 -0
  49. data/spec/annotation_security/policy/rule_spec.rb +78 -0
  50. data/spec/annotation_security/policy/test_policy_spec.rb +81 -0
  51. data/spec/annotation_security/security_context_spec.rb +78 -0
  52. data/spec/annotation_security/utils_spec.rb +74 -0
  53. data/spec/helper/test_controller.rb +66 -0
  54. data/spec/helper/test_helper.rb +5 -0
  55. data/spec/helper/test_relations.rb +7 -0
  56. data/spec/helper/test_resource.rb +39 -0
  57. data/spec/helper/test_rights.yml +5 -0
  58. data/spec/helper/test_role.rb +22 -0
  59. data/spec/helper/test_user.rb +32 -0
  60. data/spec/rails_stub.rb +38 -0
  61. data/spec/spec_helper.rb +43 -0
  62. 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
@@ -0,0 +1,11 @@
1
+ #
2
+ # = lib/extensions/object.rb
3
+ #
4
+
5
+ class Object # :nodoc:
6
+
7
+ def __is_resource? # :nodoc:
8
+ false
9
+ end
10
+
11
+ end