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,189 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
require 'fileutils'
|
3
|
+
|
4
|
+
# = lib/annotation_security/exec.rb
|
5
|
+
# This file is borrowed heavily from HAML
|
6
|
+
#
|
7
|
+
|
8
|
+
module AnnotationSecurity
|
9
|
+
module Exec # :nodoc:
|
10
|
+
|
11
|
+
# An abstract class that encapsulates the executable
|
12
|
+
# code for all executables.
|
13
|
+
class Generic # :nodoc:
|
14
|
+
|
15
|
+
# Parsed options
|
16
|
+
def options
|
17
|
+
@options ||= {}
|
18
|
+
end
|
19
|
+
|
20
|
+
# @param args [Array<String>] The command-line arguments
|
21
|
+
def initialize(args)
|
22
|
+
@args = args
|
23
|
+
end
|
24
|
+
|
25
|
+
# Parses the command-line arguments and runs the executable.
|
26
|
+
# Calls `Kernel#exit` at the end, so it never returns.
|
27
|
+
def parse!
|
28
|
+
begin
|
29
|
+
@opts = OptionParser.new(&method(:set_opts))
|
30
|
+
@opts.parse!(@args)
|
31
|
+
|
32
|
+
process_result
|
33
|
+
|
34
|
+
options
|
35
|
+
rescue Exception => e
|
36
|
+
raise e if e.is_a?(SystemExit) || options[:trace]
|
37
|
+
|
38
|
+
$stderr.puts e.message
|
39
|
+
exit 1
|
40
|
+
end
|
41
|
+
exit 0
|
42
|
+
end
|
43
|
+
|
44
|
+
# @return [String] A description of the executable
|
45
|
+
def to_s
|
46
|
+
@opts.to_s
|
47
|
+
end
|
48
|
+
|
49
|
+
protected
|
50
|
+
|
51
|
+
# Tells optparse how to parse the arguments
|
52
|
+
# available for all executables.
|
53
|
+
#
|
54
|
+
# This is meant to be overridden by subclasses
|
55
|
+
# so they can add their own options.
|
56
|
+
#
|
57
|
+
# @param opts [OptionParser]
|
58
|
+
def set_opts(opts)
|
59
|
+
opts.on("--force", "Force command execution, override assets without asking") do
|
60
|
+
options[:force] = true
|
61
|
+
end
|
62
|
+
|
63
|
+
opts.on("--trace", "Shows full stack trace in case of errors") do
|
64
|
+
options[:trace] = true
|
65
|
+
end
|
66
|
+
|
67
|
+
opts.on_tail("-?", "-h", "--help", "Show this message") do
|
68
|
+
puts opts
|
69
|
+
exit
|
70
|
+
end
|
71
|
+
|
72
|
+
opts.on_tail("-v", "--version", "Print version") do
|
73
|
+
puts("AnnotationSecurity 0.01")
|
74
|
+
exit
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# Processes the options set by the command-line arguments.
|
79
|
+
#
|
80
|
+
# This is meant to be overridden by subclasses
|
81
|
+
# so they can run their respective programs.
|
82
|
+
def process_result; end
|
83
|
+
end
|
84
|
+
|
85
|
+
# An abstrac class that encapsulates the code
|
86
|
+
# specific to executables.
|
87
|
+
class RailsInstaller < Generic # :nodoc:
|
88
|
+
|
89
|
+
ASSET_FILES = %w{
|
90
|
+
config/initializers/annotation_security.rb
|
91
|
+
config/security/relations.rb
|
92
|
+
config/security/rights.yml
|
93
|
+
app/helpers/annotation_security_helper.rb
|
94
|
+
vendor/plugins/annotation_security/init.rb
|
95
|
+
}
|
96
|
+
|
97
|
+
# @param args [Array<String>] The command-line arguments
|
98
|
+
def initialize(args)
|
99
|
+
super
|
100
|
+
@name = "annosec"
|
101
|
+
end
|
102
|
+
|
103
|
+
protected
|
104
|
+
|
105
|
+
# Tells optparse how to parse the arguments.
|
106
|
+
#
|
107
|
+
# This is meant to be overridden by subclasses
|
108
|
+
# so they can add their own options.
|
109
|
+
#
|
110
|
+
# @param opts [OptionParser]
|
111
|
+
def set_opts(opts)
|
112
|
+
opts.banner = <<END
|
113
|
+
Usage: #{@name.downcase} [options]
|
114
|
+
|
115
|
+
Description:
|
116
|
+
Installs the AnnotationSecurity layer into a rails app
|
117
|
+
|
118
|
+
Options:
|
119
|
+
END
|
120
|
+
|
121
|
+
opts.on('--rails RAILS_DIR', "Install AnnotationSecurity layer from the Gem to a Rails project") do |dir|
|
122
|
+
options[:rails_dir] = dir
|
123
|
+
end
|
124
|
+
|
125
|
+
super
|
126
|
+
end
|
127
|
+
|
128
|
+
# Processes the options set by the command-line arguments.
|
129
|
+
# In particular, sets `@options[:for_engine][:filename]` to the input filename
|
130
|
+
# and requires the appropriate file.
|
131
|
+
#
|
132
|
+
# This is meant to be overridden by subclasses
|
133
|
+
# so they can run their respective programs.
|
134
|
+
def process_result
|
135
|
+
|
136
|
+
unless options[:rails_dir]
|
137
|
+
puts @opts
|
138
|
+
exit
|
139
|
+
end
|
140
|
+
|
141
|
+
options[:cur_dir] = File.dirname(__FILE__)
|
142
|
+
options[:assets_dir] = File.join(options[:cur_dir], '..', '..', 'assets')
|
143
|
+
|
144
|
+
assert_exists('config')
|
145
|
+
assert_exists('vendor')
|
146
|
+
assert_exists('app/helpers')
|
147
|
+
|
148
|
+
ASSET_FILES.each { |f| install_file(f) }
|
149
|
+
|
150
|
+
puts "AnnotationSecurity plugin added to #{options[:rails_dir]}"
|
151
|
+
exit
|
152
|
+
end
|
153
|
+
|
154
|
+
private
|
155
|
+
|
156
|
+
def assert_exists(dir)
|
157
|
+
dir = File.join(options[:rails_dir], dir)
|
158
|
+
unless File.exists?(dir)
|
159
|
+
if options[:force]
|
160
|
+
puts "Creating #{dir}"
|
161
|
+
FileUtils.mkdir_p dir
|
162
|
+
else
|
163
|
+
puts "Directory #{dir} does not exist"
|
164
|
+
exit
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
def install_file(f)
|
170
|
+
orign = File.join(options[:assets_dir], f)
|
171
|
+
dest = File.join(options[:rails_dir], f)
|
172
|
+
|
173
|
+
if File.exists?(dest) && !options[:force]
|
174
|
+
print "File #{dest} already exists, overwrite [y/N]? "
|
175
|
+
return if gets !~ /y/i
|
176
|
+
end
|
177
|
+
|
178
|
+
dir = File.dirname(dest)
|
179
|
+
unless File.exists?(dir)
|
180
|
+
puts "Creating #{dir}"
|
181
|
+
FileUtils.mkdir_p(dir)
|
182
|
+
end
|
183
|
+
|
184
|
+
FileUtils.install(orign, dest)
|
185
|
+
puts "Installed #{dest}"
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
#
|
2
|
+
# = lib/annotation_security/filters.rb
|
3
|
+
#
|
4
|
+
|
5
|
+
require "active_record"
|
6
|
+
|
7
|
+
module AnnotationSecurity # :nodoc:
|
8
|
+
|
9
|
+
# Contains filters of the security layer which filter current requests,
|
10
|
+
# set up security context and apply security rules.
|
11
|
+
module Filters
|
12
|
+
# This filter is a before filter and is executed as the first filter in the
|
13
|
+
# filter chain. It initializes the security layer.
|
14
|
+
class InitializeSecurity
|
15
|
+
|
16
|
+
# Initialize current security context depending on logged_in user
|
17
|
+
def self.filter(controller)
|
18
|
+
SecurityContext.initialize(controller)
|
19
|
+
yield
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# This filter is an around filter and is executed as the last filter before
|
24
|
+
# execution of action. It applies the security mechanisms.
|
25
|
+
class ApplySecurity
|
26
|
+
# Applies security policies based on current user.
|
27
|
+
def self.filter(controller)
|
28
|
+
::ActiveRecord::Base.transaction do
|
29
|
+
rules = controller.class.descriptions_of(controller.action_name)
|
30
|
+
SecurityContext.current.eval_with_security(rules){ yield }
|
31
|
+
end
|
32
|
+
rescue AnnotationSecurity::SecurityError
|
33
|
+
SecurityContext.security_exception = $!
|
34
|
+
raise $!
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,144 @@
|
|
1
|
+
#
|
2
|
+
# = lib/annotation_security/includes/action_controller.rb
|
3
|
+
#
|
4
|
+
|
5
|
+
# Provides security extensions for rails controllers.
|
6
|
+
# Is included in ActionController::Base.
|
7
|
+
#
|
8
|
+
# See AnnotationSecurity::ActionController::ClassMethods.
|
9
|
+
#
|
10
|
+
module AnnotationSecurity::ActionController
|
11
|
+
|
12
|
+
def self.included(base) # :nodoc:
|
13
|
+
base.extend(ClassMethods)
|
14
|
+
base.send :include, InstanceMethods
|
15
|
+
end
|
16
|
+
|
17
|
+
# Provides security extensions for rails controllers on the class side.
|
18
|
+
#
|
19
|
+
module ClassMethods
|
20
|
+
|
21
|
+
# Filters are not affected by the security settings of the action.
|
22
|
+
# If you want security checkings in your filters, activate them with
|
23
|
+
# +apply_security+.
|
24
|
+
#
|
25
|
+
# apply_security :get_user
|
26
|
+
#
|
27
|
+
# private
|
28
|
+
#
|
29
|
+
# desc "shows a user"
|
30
|
+
# def get_user
|
31
|
+
# @user = User.find params[:id]
|
32
|
+
# end
|
33
|
+
#
|
34
|
+
# You can use +apply_security+ to secure any methods, not only filters.
|
35
|
+
# Notice that these rules are *not* taken into account when evaluating
|
36
|
+
# AnnotationSecurity::Helper#link_to_if_allowed and similar methods.
|
37
|
+
#
|
38
|
+
def apply_security(*symbols)
|
39
|
+
symbols.each { |s| pending_security_wrappers << s.to_sym }
|
40
|
+
end
|
41
|
+
|
42
|
+
# Filters are not affected by the security settings of the action.
|
43
|
+
# If you want the security settings of the action applied to your filter,
|
44
|
+
# use this method. It can be combined with #apply_security
|
45
|
+
def apply_action_security(*symbols)
|
46
|
+
symbols.each { |s| pending_action_security_wrappers << s.to_sym }
|
47
|
+
end
|
48
|
+
|
49
|
+
# AnnotationSecurity is using the +method_added+ callback. If this method
|
50
|
+
# is overwritten without calling +super+, +apply_security+ will not work.
|
51
|
+
#
|
52
|
+
def method_added(method)
|
53
|
+
super(method)
|
54
|
+
if pending_security_wrappers.delete method
|
55
|
+
build_security_wrapper(method)
|
56
|
+
end
|
57
|
+
if pending_action_security_wrappers.delete method
|
58
|
+
build_action_security_wrapper(method)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# If no resource type is provided in a description, the default resource
|
63
|
+
# will be used. Once set the value cannot be changed.
|
64
|
+
#
|
65
|
+
# This is still experimental. You should not use it unless you have a
|
66
|
+
# reason. It might be usefull for inheritance.
|
67
|
+
#
|
68
|
+
def default_resource(value=nil)
|
69
|
+
@default_resource ||= value || compute_default_resource
|
70
|
+
end
|
71
|
+
|
72
|
+
# Creates a new security filter.
|
73
|
+
#
|
74
|
+
# Security filters are around filters that are evaluated before the first
|
75
|
+
# before filter. Use security filters to set the credentials and to react
|
76
|
+
# to security violations.
|
77
|
+
# class ApplicationController < ActionController::Base
|
78
|
+
#
|
79
|
+
# security_filter :security_filter
|
80
|
+
#
|
81
|
+
# private
|
82
|
+
#
|
83
|
+
# def security_filter
|
84
|
+
# SecurityContext.current_credential = session[:user]
|
85
|
+
# yield
|
86
|
+
# rescue SecurityViolationError
|
87
|
+
# if SecurityContext.is? :logged_in
|
88
|
+
# render :template => "welcome/not_allowed"
|
89
|
+
# else
|
90
|
+
# render :template => "welcome/please_login"
|
91
|
+
# end
|
92
|
+
# end
|
93
|
+
#
|
94
|
+
# See SecurityContext#current_credential= and SecurityViolationError.
|
95
|
+
#
|
96
|
+
def security_filter(symbol, &block)
|
97
|
+
filter_chain.append_filter_to_chain([symbol], :security, &block)
|
98
|
+
end
|
99
|
+
|
100
|
+
private
|
101
|
+
|
102
|
+
def pending_security_wrappers
|
103
|
+
@pending_security_wrappers ||= []
|
104
|
+
end
|
105
|
+
|
106
|
+
def pending_action_security_wrappers
|
107
|
+
@pending_action_security_wrappers ||= []
|
108
|
+
end
|
109
|
+
|
110
|
+
def build_security_wrapper(method)
|
111
|
+
no_security = "#{method}_without_security".to_sym
|
112
|
+
class_eval %{
|
113
|
+
alias :#{no_security} :#{method}
|
114
|
+
def #{method}(*args, &proc)
|
115
|
+
rules = self.class.descriptions_of(:#{method})
|
116
|
+
SecurityContext.current.send_with_security(rules, self, :#{no_security}, *args, &proc)
|
117
|
+
end
|
118
|
+
}
|
119
|
+
end
|
120
|
+
|
121
|
+
def build_action_security_wrapper(method)
|
122
|
+
no_security = "#{method}_without_action_security".to_sym
|
123
|
+
class_eval %{
|
124
|
+
alias :#{no_security} :#{method}
|
125
|
+
def #{method}(*args, &proc)
|
126
|
+
rules = self.class.descriptions_of(action_name)
|
127
|
+
SecurityContext.current.send_with_security(rules, self, :#{no_security}, *args, &proc)
|
128
|
+
end
|
129
|
+
}
|
130
|
+
end
|
131
|
+
|
132
|
+
def compute_default_resource
|
133
|
+
name.first(-"Controller".length).singularize.underscore.to_sym
|
134
|
+
end
|
135
|
+
|
136
|
+
end
|
137
|
+
|
138
|
+
module InstanceMethods # :nodoc:
|
139
|
+
|
140
|
+
def security_exception=(ex)
|
141
|
+
@security_exception = ex
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
#
|
2
|
+
# = lib/annotation_security/includes/active_record.rb
|
3
|
+
#
|
4
|
+
|
5
|
+
# = AnnotationSecurity::ActiveRecord
|
6
|
+
#
|
7
|
+
# Included by model classes if they are used as resources.
|
8
|
+
# Includes AnnotationSecurity::Resource and sets up the model observer.
|
9
|
+
#
|
10
|
+
module AnnotationSecurity::ActiveRecord # :nodoc:
|
11
|
+
|
12
|
+
def self.included(base)
|
13
|
+
base.class_eval do
|
14
|
+
include ::AnnotationSecurity::Resource
|
15
|
+
end
|
16
|
+
base.extend(ClassMethods)
|
17
|
+
AnnotationSecurity::ModelObserver.observe base.name.underscore.to_sym
|
18
|
+
AnnotationSecurity::ModelObserver.instance.reload_model_observer
|
19
|
+
end
|
20
|
+
|
21
|
+
module ClassMethods # :nodoc:
|
22
|
+
def get_resource(object)
|
23
|
+
return object if object.is_a? self
|
24
|
+
# Object.const_get(name) needed because of a bug in Rails
|
25
|
+
Object.const_get(name).find(object)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,215 @@
|
|
1
|
+
#
|
2
|
+
# = lib/annotation_security/includes/helper.rb
|
3
|
+
#
|
4
|
+
|
5
|
+
# = AnnotationSecurity::Helper
|
6
|
+
#
|
7
|
+
# This module adds some useful helper methods to your templates.
|
8
|
+
#
|
9
|
+
module AnnotationSecurity::Helper
|
10
|
+
|
11
|
+
# Returns true if the operation defined by +policy_args+ is allowed.
|
12
|
+
#
|
13
|
+
# The following calls to #allowed? are possible:
|
14
|
+
#
|
15
|
+
# allowed? :show, :resource, @resource
|
16
|
+
# # => true if the current user has the right to show @resource,
|
17
|
+
# # which belongs to the :resource resource-class
|
18
|
+
#
|
19
|
+
# In case of model objects or other classes which implement a #resource_type
|
20
|
+
# method the the second argument may be ommited
|
21
|
+
#
|
22
|
+
# allowed? :show, @resource
|
23
|
+
# # equivalent to the above call if @resource.resource_type == :resource
|
24
|
+
#
|
25
|
+
# A policy description used as a controller annotation may also be used
|
26
|
+
# to check a right
|
27
|
+
#
|
28
|
+
# allowed? "show resource", @resource
|
29
|
+
# # => true if the current user has the right "show resource" for @resource
|
30
|
+
#
|
31
|
+
# A policy may also be applied without an object representing the context:
|
32
|
+
#
|
33
|
+
# allowed? :show, :resource
|
34
|
+
# # => true if the current may show resources.
|
35
|
+
#
|
36
|
+
# This will only check system and pretest rules. The result +true+ does not
|
37
|
+
# mean that the user may show all resources. However, a +false+ indicates
|
38
|
+
# that the user is not allowed to show any resources.
|
39
|
+
#
|
40
|
+
# If the resource class is omitted as well, only rules defined for all
|
41
|
+
# resources can be tested. See RelationLoader#all_resources for details.
|
42
|
+
#
|
43
|
+
# allowed? :administrate
|
44
|
+
# # => true if the user is allowed to administrate all resources.
|
45
|
+
#
|
46
|
+
# See SecurityContext#allowed?.
|
47
|
+
#
|
48
|
+
def allowed?(*args)
|
49
|
+
SecurityContext.allowed?(*args)
|
50
|
+
end
|
51
|
+
|
52
|
+
alias a? allowed?
|
53
|
+
|
54
|
+
# Equivalent to allowed?; is? is provided for better readability.
|
55
|
+
#
|
56
|
+
# allowed? :logged_in
|
57
|
+
# vs
|
58
|
+
# is? :logged_in
|
59
|
+
#
|
60
|
+
def is?(*args)
|
61
|
+
SecurityContext.is?(*args)
|
62
|
+
end
|
63
|
+
|
64
|
+
# Checks whether the user is allowed to access the action.
|
65
|
+
#
|
66
|
+
# Expects arguments like #link_to_if_allowed, just without name and block.
|
67
|
+
#
|
68
|
+
# Returns true if the action is allowed.
|
69
|
+
#
|
70
|
+
def action_allowed?(options, objects=nil, params=nil, html_options=nil)
|
71
|
+
|
72
|
+
options, objects, params, html_options =
|
73
|
+
parse_allow_action_args(options, objects, params, html_options)
|
74
|
+
|
75
|
+
controller = params.delete :controller
|
76
|
+
action = params.delete :action
|
77
|
+
SecurityContext.allow_action?(controller, action, objects, params)
|
78
|
+
end
|
79
|
+
|
80
|
+
# Returns a link tag with the specified name to the specified resource if
|
81
|
+
# the user is allowed to access it. See #link_to_unless and
|
82
|
+
# SecurityContext#action_allowed? for more documentation.
|
83
|
+
#
|
84
|
+
# There are two ways of using #link_to_if_allowed
|
85
|
+
#
|
86
|
+
# === As #link_to with alternative
|
87
|
+
# (or as #link_to_unless without explicit condition)
|
88
|
+
# link_to_if_allowed(name, options={}, html_options=nil) { 'alternative' }
|
89
|
+
# +options+ either is a hash, like
|
90
|
+
# { :controller => :comments, :action => edit, :id => @comment }
|
91
|
+
# a string, like
|
92
|
+
# "comments/1/edit"
|
93
|
+
# or
|
94
|
+
# edit_comment_path(@comment)
|
95
|
+
# or a single resource object.
|
96
|
+
#
|
97
|
+
# Notice that when providing a string, controller, action and parameters will
|
98
|
+
# be parsed. After that, the resource types of the parameters are *guessed*,
|
99
|
+
# the resources are retrieved and the rules of the action are evaluated.
|
100
|
+
#
|
101
|
+
# The block will be evaluated if the action is not allowed,
|
102
|
+
# like in #link_to_unless.
|
103
|
+
#
|
104
|
+
# === As #link_to with alternative and explicit objects
|
105
|
+
# link_to_if_allowed(name, options={}, objects=[], params={}, html_options=nil) { 'alternative' }
|
106
|
+
# In this case, controller and action will be derived from +options+ unless
|
107
|
+
# they are specified in +params+.
|
108
|
+
# All items in +objects+ and all remaining items in +params+ will be used
|
109
|
+
# for evaluating the rules of the action.
|
110
|
+
#
|
111
|
+
# If you want to specify +html_options+, provide at least an empty hash
|
112
|
+
# for +params+.
|
113
|
+
#
|
114
|
+
# Unlike to #link_to, you can also provide a symbol as +options+ value.
|
115
|
+
# In this case, the target url will be determined by sending symbol as
|
116
|
+
# message, providing +objects+ and +params+ as arguments, e.g.
|
117
|
+
# link_to_if_allowed("Show comment", :comment_path, [@article, @comment], {:details => true})
|
118
|
+
# will call
|
119
|
+
# comment_path(@article, @comment, {:details => true})
|
120
|
+
#
|
121
|
+
# === Examples
|
122
|
+
# <%= link_to_if_allowed("Show", @course) { } %>
|
123
|
+
# <%= link_to_if_allowed("New", new_course_path) { "You may not create a new course." } %>
|
124
|
+
#
|
125
|
+
# These two are equivalent, however, the second approach is more efficient:
|
126
|
+
# <%= link_to_if_allowed("Edit", edit_course_path(@course)) { } %>
|
127
|
+
# <%= link_to_if_allowed("Edit", :edit_course_path, @course) { } %>
|
128
|
+
#
|
129
|
+
# The HTML-options are taken into account when choosing the action.
|
130
|
+
# <%= link_to_if_allowed("Delete", @course, {:method => :delete}) { } %>
|
131
|
+
#
|
132
|
+
# You can also define all values explicitly
|
133
|
+
# <%= link_to_if_allowed("Edit comment", "articles/1/comments/5/edit", [@comment], {:article => @comment.article, :action => :edit, :controller => :comments}) { } %>
|
134
|
+
#
|
135
|
+
# === Parameters
|
136
|
+
# - +name+ Text of the link
|
137
|
+
# - +options+
|
138
|
+
# - +objects+
|
139
|
+
# - +params+
|
140
|
+
# - +html_options+
|
141
|
+
#
|
142
|
+
def link_to_if_allowed(name, options, objects=nil, params=nil, html_options=nil, &block)
|
143
|
+
|
144
|
+
options, objects, params, html_options =
|
145
|
+
parse_allow_action_args(options, objects, params, html_options)
|
146
|
+
|
147
|
+
controller = params.delete :controller
|
148
|
+
action = params.delete :action
|
149
|
+
allowed = SecurityContext.allow_action?(controller, action, objects, params)
|
150
|
+
|
151
|
+
link_to_if(allowed, name, options, html_options, &block)
|
152
|
+
end
|
153
|
+
|
154
|
+
alias link_if_a link_to_if_allowed
|
155
|
+
|
156
|
+
private
|
157
|
+
|
158
|
+
def parse_allow_action_args(*args)
|
159
|
+
if args.second && !(args.second.is_a? Hash)
|
160
|
+
# objects and params are specified
|
161
|
+
options, objects, params, html_options = args
|
162
|
+
objects = [objects] unless objects.is_a? Array
|
163
|
+
params ||= {}
|
164
|
+
html_options ||= {}
|
165
|
+
if options.is_a? Symbol
|
166
|
+
# options is a symbol, send the message to get the link path
|
167
|
+
path_args = objects + [params]
|
168
|
+
options = send(options, *path_args)
|
169
|
+
end
|
170
|
+
else
|
171
|
+
# retrieve objects and params from options
|
172
|
+
options = args.first
|
173
|
+
html_options = args.second || {}
|
174
|
+
objects = [] # everything will be in the params
|
175
|
+
if options.is_a? Hash
|
176
|
+
params = options.dup
|
177
|
+
else
|
178
|
+
params = parse_action_params(options, html_options)
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
unless params[:controller] && params[:action]
|
183
|
+
# if controller and action are not given, parse from options
|
184
|
+
params = parse_controller_action(options, params, html_options)
|
185
|
+
end
|
186
|
+
|
187
|
+
[options, objects, params, html_options]
|
188
|
+
end
|
189
|
+
|
190
|
+
# uses options and html_options to retrieve controller and action,
|
191
|
+
# adds these values to params hash
|
192
|
+
def parse_controller_action(options, params, html_options)
|
193
|
+
path_info = get_path_info(options, html_options)
|
194
|
+
params[:controller] ||= path_info[:controller]
|
195
|
+
params[:action] ||= path_info[:action]
|
196
|
+
params
|
197
|
+
end
|
198
|
+
|
199
|
+
# uses options and html_options to retrieve controller, action
|
200
|
+
# and params
|
201
|
+
def parse_action_params(options, html_options)
|
202
|
+
get_path_info(options, html_options)
|
203
|
+
end
|
204
|
+
|
205
|
+
def get_path_info(options, html_options)
|
206
|
+
if options.is_a? String
|
207
|
+
path = options
|
208
|
+
else
|
209
|
+
path = url_for(options)
|
210
|
+
end
|
211
|
+
env = { :method => (html_options[:method] || :get ) }
|
212
|
+
ActionController::Routing::Routes.recognize_path(path, env)
|
213
|
+
end
|
214
|
+
|
215
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
#
|
2
|
+
# = lib/annotation_security/includes/resource.rb
|
3
|
+
#
|
4
|
+
|
5
|
+
# Must be included by all classes that are resource classes and do not extend
|
6
|
+
# ActiveRecord::Base.
|
7
|
+
#
|
8
|
+
# class MailDispatcher
|
9
|
+
# include AnnotationSecurity::Resource
|
10
|
+
# resource_type = :email
|
11
|
+
# ...
|
12
|
+
#
|
13
|
+
# See AnnotationSecurity::Resource::ClassMethods.
|
14
|
+
#
|
15
|
+
module AnnotationSecurity::Resource
|
16
|
+
|
17
|
+
def self.included(base) # :nodoc:
|
18
|
+
base.extend(ClassMethods)
|
19
|
+
base.class_eval do
|
20
|
+
include InstanceMethods
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# Provides class side methods for resource classes.
|
25
|
+
module ClassMethods
|
26
|
+
|
27
|
+
# Registers the class as a resource.
|
28
|
+
#
|
29
|
+
def resource_type=(symbol)
|
30
|
+
@resource_type = symbol
|
31
|
+
AnnotationSecurity::ResourceManager.add_resource_class(symbol,self)
|
32
|
+
symbol
|
33
|
+
end
|
34
|
+
|
35
|
+
def resource_type # :nodoc:
|
36
|
+
@resource_type || (self.resource_type = name.underscore.to_sym)
|
37
|
+
end
|
38
|
+
|
39
|
+
def policy_for(user,obj=nil) # :nodoc:
|
40
|
+
policy_factory.create_policy(user,obj)
|
41
|
+
end
|
42
|
+
|
43
|
+
# If required, overwrite this method to return a resource object identified
|
44
|
+
# by the argument.
|
45
|
+
#
|
46
|
+
# This might be necessary if you change the to_param method of an
|
47
|
+
# ActiveRecord class.
|
48
|
+
#
|
49
|
+
# class Course < ActiveRecord::Base
|
50
|
+
# ...
|
51
|
+
# # each course has a unique name --> make better urls
|
52
|
+
# def to_param
|
53
|
+
# name
|
54
|
+
# end
|
55
|
+
#
|
56
|
+
# def self.get_resource(name)
|
57
|
+
# find_by_name(name)
|
58
|
+
# end
|
59
|
+
#
|
60
|
+
def get_resource(arg)
|
61
|
+
raise NoMethodError, "#{self} does not implement #get_resource"
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
def policy_factory # :nodoc:
|
67
|
+
@policy_factory ||= AnnotationSecurity::PolicyManager.policy_factory(resource_type)
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
|
72
|
+
module InstanceMethods # :nodoc:
|
73
|
+
def resource_type
|
74
|
+
self.class.resource_type
|
75
|
+
end
|
76
|
+
|
77
|
+
def __is_resource?
|
78
|
+
true
|
79
|
+
end
|
80
|
+
|
81
|
+
def policy_for(user)
|
82
|
+
self.class.policy_for(user,self)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|