declarative_authorization_padrino 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/MIT-LICENSE +20 -0
- data/README.rdoc +27 -0
- data/Rakefile +22 -0
- data/lib/declarative_authorization_padrino.rb +22 -0
- data/lib/declarative_authorization_padrino/authorization.rb +27 -0
- data/lib/declarative_authorization_padrino/in_model.rb +57 -0
- data/lib/declarative_authorization_padrino/obligation_scope.rb +341 -0
- data/lib/declarative_authorization_padrino/padrino.rb +28 -0
- metadata +71 -0
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2008 [name of plugin creator]
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
= Declarative Authorization wrapper for Padrino
|
2
|
+
|
3
|
+
Read https://github.com/stffn/declarative_authorization for everything else but how to secure your controllers.
|
4
|
+
|
5
|
+
== Securing your controllers
|
6
|
+
|
7
|
+
Although it's not the best approach and it has to be improved, it'll do the job for now. Suggestions accepted :)
|
8
|
+
|
9
|
+
Register the module in your app/app.rb
|
10
|
+
|
11
|
+
register Authorization::Padrino
|
12
|
+
|
13
|
+
For each action on your controller you'd like to protect add something like this:
|
14
|
+
|
15
|
+
:protect => [{:action => :read, :resource => :users, :forbidden => "Custom 403 forbidden message"}]
|
16
|
+
|
17
|
+
Note that :forbidden is optional.
|
18
|
+
|
19
|
+
get '/', :provides => :json, :protect => [{:action => :read, :resource => :users}] do
|
20
|
+
User.with_permissions_to.all.to_json(:include => :roles)
|
21
|
+
end
|
22
|
+
|
23
|
+
And that's pretty much it :)
|
24
|
+
|
25
|
+
== Demo app
|
26
|
+
|
27
|
+
https://github.com/dariocravero/declarative_authorization_padrino_demo_app
|
data/Rakefile
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
|
3
|
+
require "bundler"
|
4
|
+
Bundler.setup
|
5
|
+
|
6
|
+
require 'rake'
|
7
|
+
require 'rake/gempackagetask'
|
8
|
+
|
9
|
+
gemspec = eval(File.read('declarative_authorization_padrino.gemspec'))
|
10
|
+
Rake::GemPackageTask.new(gemspec) do |pkg|
|
11
|
+
pkg.gem_spec = gemspec
|
12
|
+
end
|
13
|
+
|
14
|
+
desc "build the gem and release it to rubygems.org"
|
15
|
+
task :release => :gem do
|
16
|
+
puts "Tagging #{gemspec.version}..."
|
17
|
+
system "git tag -a #{gemspec.version} -m 'Tagging #{gemspec.version}'"
|
18
|
+
puts "Pushing to Github..."
|
19
|
+
system "git push --tags"
|
20
|
+
puts "Pushing to rubygems.org..."
|
21
|
+
system "gem push pkg/#{gemspec.name}-#{gemspec.version}.gem"
|
22
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require "active_support/core_ext/module/delegation" # for some funny reason I need this here! ActiveRecord might not be pulling it from its dependencies?
|
2
|
+
|
3
|
+
require File.join("declarative_authorization_padrino", "authorization")
|
4
|
+
|
5
|
+
# Stub Rails so we don't have to change almost everything in obligation_scope
|
6
|
+
module Authorization
|
7
|
+
module Rails
|
8
|
+
def self.version
|
9
|
+
"3"
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
require "declarative_authorization/helper"
|
15
|
+
require File.join("declarative_authorization_padrino", "in_model")
|
16
|
+
require "declarative_authorization/in_controller"
|
17
|
+
require "declarative_authorization/in_model"
|
18
|
+
require "declarative_authorization/obligation_scope"
|
19
|
+
|
20
|
+
ActiveRecord::Base.send :include, Authorization::AuthorizationInModel
|
21
|
+
|
22
|
+
require File.join("declarative_authorization_padrino", "padrino")
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Authorization
|
2
|
+
AUTH_DSL_FILES = [Pathname.new(PADRINO_ROOT || '').join("config", "authorization_rules.rb").to_s] unless defined? AUTH_DSL_FILES
|
3
|
+
|
4
|
+
def self.activate_authorization_rules_browser? # :nodoc:
|
5
|
+
::Padrino.env == :development
|
6
|
+
end
|
7
|
+
|
8
|
+
class Engine
|
9
|
+
# Returns the role symbols of the given user.
|
10
|
+
def roles_for (user)
|
11
|
+
user ||= Authorization.current_user
|
12
|
+
raise AuthorizationUsageError, "User object doesn't respond to roles (#{user.inspect})" \
|
13
|
+
if !user.respond_to?(:role_symbols) and !user.respond_to?(:roles)
|
14
|
+
|
15
|
+
::Padrino.logger.info("The use of user.roles is deprecated. Please add a method " +
|
16
|
+
"role_symbols to your User model.") if defined?(::Padrino) and ::Padrino.respond_to?(:logger) and !user.respond_to?(:role_symbols)
|
17
|
+
|
18
|
+
roles = user.respond_to?(:role_symbols) ? user.role_symbols : user.roles
|
19
|
+
|
20
|
+
raise AuthorizationUsageError, "User.#{user.respond_to?(:role_symbols) ? 'role_symbols' : 'roles'} " +
|
21
|
+
"doesn't return an Array of Symbols (#{roles.inspect})" \
|
22
|
+
if !roles.is_a?(Array) or (!roles.empty? and !roles[0].is_a?(Symbol))
|
23
|
+
|
24
|
+
(roles.empty? ? [Authorization.default_role] : roles)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# Authorization::AuthorizationInModel
|
2
|
+
module Authorization
|
3
|
+
module AuthorizationInModel
|
4
|
+
def self.included(base) # :nodoc:
|
5
|
+
base.module_eval do
|
6
|
+
# Activates model security for the current model. Then, CRUD operations
|
7
|
+
# are checked against the authorization of the current user. The
|
8
|
+
# privileges are :+create+, :+read+, :+update+ and :+delete+ in the
|
9
|
+
# context of the model. By default, :+read+ is not checked because of
|
10
|
+
# performance impacts, especially with large result sets.
|
11
|
+
#
|
12
|
+
# class User < ActiveRecord::Base
|
13
|
+
# using_access_control
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
# If an operation is not permitted, a Authorization::AuthorizationError
|
17
|
+
# is raised.
|
18
|
+
#
|
19
|
+
# To activate model security on all models, call using_access_control
|
20
|
+
# on ActiveRecord::Base
|
21
|
+
# ActiveRecord::Base.using_access_control
|
22
|
+
#
|
23
|
+
# Available options
|
24
|
+
# [:+context+] Specify context different from the models table name.
|
25
|
+
# [:+include_read+] Also check for :+read+ privilege after find.
|
26
|
+
#
|
27
|
+
def self.using_access_control (options = {})
|
28
|
+
options = {
|
29
|
+
:context => nil,
|
30
|
+
:include_read => false
|
31
|
+
}.merge(options)
|
32
|
+
|
33
|
+
class_eval do
|
34
|
+
[:create, :update, [:destroy, :delete]].each do |action, privilege|
|
35
|
+
send(:"before_#{action}") do |object|
|
36
|
+
Authorization::Engine.instance.permit!(privilege || action,
|
37
|
+
:object => object, :context => options[:context])
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
if options[:include_read]
|
42
|
+
# after_find is only called if after_find is implemented
|
43
|
+
after_find do |object|
|
44
|
+
Authorization::Engine.instance.permit!(:read, :object => object,
|
45
|
+
:context => options[:context])
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.using_access_control?
|
50
|
+
true
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,341 @@
|
|
1
|
+
module Authorization
|
2
|
+
# The +ObligationScope+ class parses any number of obligations into joins and conditions.
|
3
|
+
#
|
4
|
+
# In +ObligationScope+ parlance, "association paths" are one-dimensional arrays in which each
|
5
|
+
# element represents an attribute or association (or "step"), and "leads" to the next step in the
|
6
|
+
# association path.
|
7
|
+
#
|
8
|
+
# Suppose we have this path defined in the context of model Foo:
|
9
|
+
# +{ :bar => { :baz => { :foo => { :attr => is { user } } } } }+
|
10
|
+
#
|
11
|
+
# To parse this path, +ObligationScope+ evaluates each step in the context of the preceding step.
|
12
|
+
# The first step is evaluated in the context of the parent scope, the second step is evaluated in
|
13
|
+
# the context of the first, and so forth. Every time we encounter a step representing an
|
14
|
+
# association, we make note of the fact by storing the path (up to that point), assigning it a
|
15
|
+
# table alias intended to match the one that will eventually be chosen by ActiveRecord when
|
16
|
+
# executing the +find+ method on the scope.
|
17
|
+
#
|
18
|
+
# +@table_aliases = {
|
19
|
+
# [] => 'foos',
|
20
|
+
# [:bar] => 'bars',
|
21
|
+
# [:bar, :baz] => 'bazzes',
|
22
|
+
# [:bar, :baz, :foo] => 'foos_bazzes' # Alias avoids collisions with 'foos' (already used)
|
23
|
+
# }+
|
24
|
+
#
|
25
|
+
# At the "end" of each path, we expect to find a comparison operation of some kind, generally
|
26
|
+
# comparing an attribute of the most recent association with some other value (such as an ID,
|
27
|
+
# constant, or array of values). When we encounter a step representing a comparison, we make
|
28
|
+
# note of the fact by storing the path (up to that point) and the comparison operation together.
|
29
|
+
# (Note that individual obligations' conditions are kept separate, to allow their conditions to
|
30
|
+
# be OR'ed together in the generated scope options.)
|
31
|
+
#
|
32
|
+
# +@obligation_conditions[<obligation>][[:bar, :baz, :foo]] = [
|
33
|
+
# [ :attr, :is, <user.id> ]
|
34
|
+
# ]+
|
35
|
+
#
|
36
|
+
# TODO update doc for Relations:
|
37
|
+
# After successfully parsing an obligation, all of the stored paths and conditions are converted
|
38
|
+
# into scope options (stored in +proxy_options+ as +:joins+ and +:conditions+). The resulting
|
39
|
+
# scope may then be used to find all scoped objects for which at least one of the parsed
|
40
|
+
# obligations is fully met.
|
41
|
+
#
|
42
|
+
# +@proxy_options[:joins] = { :bar => { :baz => :foo } }
|
43
|
+
# @proxy_options[:conditions] = [ 'foos_bazzes.attr = :foos_bazzes__id_0', { :foos_bazzes__id_0 => 1 } ]+
|
44
|
+
#
|
45
|
+
class ObligationScope < ActiveRecord::Relation
|
46
|
+
def initialize (model, options)
|
47
|
+
@finder_options = {}
|
48
|
+
super(model, model.table_name)
|
49
|
+
end
|
50
|
+
|
51
|
+
def scope
|
52
|
+
self.klass.scoped(@finder_options)
|
53
|
+
end
|
54
|
+
|
55
|
+
# Consumes the given obligation, converting it into scope join and condition options.
|
56
|
+
def parse!( obligation )
|
57
|
+
@current_obligation = obligation
|
58
|
+
@join_table_joins = Set.new
|
59
|
+
obligation_conditions[@current_obligation] ||= {}
|
60
|
+
follow_path( obligation )
|
61
|
+
|
62
|
+
rebuild_condition_options!
|
63
|
+
rebuild_join_options!
|
64
|
+
end
|
65
|
+
|
66
|
+
protected
|
67
|
+
|
68
|
+
# Parses the next step in the association path. If it's an association, we advance down the
|
69
|
+
# path. Otherwise, it's an attribute, and we need to evaluate it as a comparison operation.
|
70
|
+
def follow_path( steps, past_steps = [] )
|
71
|
+
if steps.is_a?( Hash )
|
72
|
+
steps.each do |step, next_steps|
|
73
|
+
path_to_this_point = [past_steps, step].flatten
|
74
|
+
reflection = reflection_for( path_to_this_point ) rescue nil
|
75
|
+
if reflection
|
76
|
+
follow_path( next_steps, path_to_this_point )
|
77
|
+
else
|
78
|
+
follow_comparison( next_steps, past_steps, step )
|
79
|
+
end
|
80
|
+
end
|
81
|
+
elsif steps.is_a?( Array ) && steps.length == 2
|
82
|
+
if reflection_for( past_steps )
|
83
|
+
follow_comparison( steps, past_steps, :id )
|
84
|
+
else
|
85
|
+
follow_comparison( steps, past_steps[0..-2], past_steps[-1] )
|
86
|
+
end
|
87
|
+
else
|
88
|
+
raise "invalid obligation path #{[past_steps, steps].inspect}"
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def top_level_model
|
93
|
+
self.klass
|
94
|
+
end
|
95
|
+
|
96
|
+
def finder_options
|
97
|
+
@finder_options
|
98
|
+
end
|
99
|
+
|
100
|
+
# At the end of every association path, we expect to see a comparison of some kind; for
|
101
|
+
# example, +:attr => [ :is, :value ]+.
|
102
|
+
#
|
103
|
+
# This method parses the comparison and creates an obligation condition from it.
|
104
|
+
def follow_comparison( steps, past_steps, attribute )
|
105
|
+
operator = steps[0]
|
106
|
+
value = steps[1..-1]
|
107
|
+
value = value[0] if value.length == 1
|
108
|
+
|
109
|
+
add_obligation_condition_for( past_steps, [attribute, operator, value] )
|
110
|
+
end
|
111
|
+
|
112
|
+
# Adds the given expression to the current obligation's indicated path's conditions.
|
113
|
+
#
|
114
|
+
# Condition expressions must follow the format +[ <attribute>, <operator>, <value> ]+.
|
115
|
+
def add_obligation_condition_for( path, expression )
|
116
|
+
raise "invalid expression #{expression.inspect}" unless expression.is_a?( Array ) && expression.length == 3
|
117
|
+
add_obligation_join_for( path )
|
118
|
+
obligation_conditions[@current_obligation] ||= {}
|
119
|
+
( obligation_conditions[@current_obligation][path] ||= Set.new ) << expression
|
120
|
+
end
|
121
|
+
|
122
|
+
# Adds the given path to the list of obligation joins, if we haven't seen it before.
|
123
|
+
def add_obligation_join_for( path )
|
124
|
+
map_reflection_for( path ) if reflections[path].nil?
|
125
|
+
end
|
126
|
+
|
127
|
+
# Returns the model associated with the given path.
|
128
|
+
def model_for (path)
|
129
|
+
reflection = reflection_for(path)
|
130
|
+
|
131
|
+
if reflection.respond_to?(:proxy_reflection)
|
132
|
+
reflection.proxy_reflection.klass
|
133
|
+
elsif reflection.respond_to?(:klass)
|
134
|
+
reflection.klass
|
135
|
+
else
|
136
|
+
reflection
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
# Returns the reflection corresponding to the given path.
|
141
|
+
def reflection_for(path, for_join_table_only = false)
|
142
|
+
@join_table_joins << path if for_join_table_only and !reflections[path]
|
143
|
+
reflections[path] ||= map_reflection_for( path )
|
144
|
+
end
|
145
|
+
|
146
|
+
# Returns a proper table alias for the given path. This alias may be used in SQL statements.
|
147
|
+
def table_alias_for( path )
|
148
|
+
table_aliases[path] ||= map_table_alias_for( path )
|
149
|
+
end
|
150
|
+
|
151
|
+
# Attempts to map a reflection for the given path. Raises if already defined.
|
152
|
+
def map_reflection_for( path )
|
153
|
+
raise "reflection for #{path.inspect} already exists" unless reflections[path].nil?
|
154
|
+
|
155
|
+
reflection = path.empty? ? top_level_model : begin
|
156
|
+
parent = reflection_for( path[0..-2] )
|
157
|
+
if !parent.respond_to?(:proxy_reflection) and parent.respond_to?(:klass)
|
158
|
+
parent.klass.reflect_on_association( path.last )
|
159
|
+
else
|
160
|
+
parent.reflect_on_association( path.last )
|
161
|
+
end
|
162
|
+
rescue
|
163
|
+
parent.reflect_on_association( path.last )
|
164
|
+
end
|
165
|
+
raise "invalid path #{path.inspect}" if reflection.nil?
|
166
|
+
|
167
|
+
reflections[path] = reflection
|
168
|
+
map_table_alias_for( path ) # Claim a table alias for the path.
|
169
|
+
|
170
|
+
# Claim alias for join table
|
171
|
+
# TODO change how this is checked
|
172
|
+
if !reflection.respond_to?(:proxy_reflection) and !reflection.respond_to?(:proxy_scope) and reflection.is_a?(ActiveRecord::Reflection::ThroughReflection)
|
173
|
+
join_table_path = path[0..-2] + [reflection.options[:through]]
|
174
|
+
reflection_for(join_table_path, true)
|
175
|
+
end
|
176
|
+
|
177
|
+
reflection
|
178
|
+
end
|
179
|
+
|
180
|
+
# Attempts to map a table alias for the given path. Raises if already defined.
|
181
|
+
def map_table_alias_for( path )
|
182
|
+
return "table alias for #{path.inspect} already exists" unless table_aliases[path].nil?
|
183
|
+
|
184
|
+
reflection = reflection_for( path )
|
185
|
+
table_alias = reflection.table_name
|
186
|
+
if table_aliases.values.include?( table_alias )
|
187
|
+
max_length = reflection.active_record.connection.table_alias_length
|
188
|
+
# Rails seems to pluralize reflection names
|
189
|
+
table_alias = "#{reflection.name.to_s.pluralize}_#{reflection.active_record.table_name}".to(max_length-1)
|
190
|
+
end
|
191
|
+
while table_aliases.values.include?( table_alias )
|
192
|
+
if table_alias =~ /\w(_\d+?)$/
|
193
|
+
table_index = $1.succ
|
194
|
+
table_alias = "#{table_alias[0..-(table_index.length+1)]}_#{table_index}"
|
195
|
+
else
|
196
|
+
table_alias = "#{table_alias[0..(max_length-3)]}_2"
|
197
|
+
end
|
198
|
+
end
|
199
|
+
table_aliases[path] = table_alias
|
200
|
+
end
|
201
|
+
|
202
|
+
# Returns a hash mapping obligations to zero or more condition path sets.
|
203
|
+
def obligation_conditions
|
204
|
+
@obligation_conditions ||= {}
|
205
|
+
end
|
206
|
+
|
207
|
+
# Returns a hash mapping paths to reflections.
|
208
|
+
def reflections
|
209
|
+
# lets try to get the order of joins right
|
210
|
+
@reflections ||= ActiveSupport::OrderedHash.new
|
211
|
+
end
|
212
|
+
|
213
|
+
# Returns a hash mapping paths to proper table aliases to use in SQL statements.
|
214
|
+
def table_aliases
|
215
|
+
@table_aliases ||= {}
|
216
|
+
end
|
217
|
+
|
218
|
+
# Parses all of the defined obligation conditions and defines the scope's :conditions option.
|
219
|
+
def rebuild_condition_options!
|
220
|
+
conds = []
|
221
|
+
binds = {}
|
222
|
+
used_paths = Set.new
|
223
|
+
delete_paths = Set.new
|
224
|
+
obligation_conditions.each_with_index do |array, obligation_index|
|
225
|
+
obligation, conditions = array
|
226
|
+
obligation_conds = []
|
227
|
+
conditions.each do |path, expressions|
|
228
|
+
model = model_for( path )
|
229
|
+
table_alias = table_alias_for(path)
|
230
|
+
parent_model = (path.length > 1 ? model_for(path[0..-2]) : top_level_model)
|
231
|
+
expressions.each do |expression|
|
232
|
+
attribute, operator, value = expression
|
233
|
+
# prevent unnecessary joins:
|
234
|
+
if attribute == :id and operator == :is and parent_model.columns_hash["#{path.last}_id"]
|
235
|
+
attribute_name = :"#{path.last}_id"
|
236
|
+
attribute_table_alias = table_alias_for(path[0..-2])
|
237
|
+
used_paths << path[0..-2]
|
238
|
+
delete_paths << path
|
239
|
+
else
|
240
|
+
attribute_name = model.columns_hash["#{attribute}_id"] && :"#{attribute}_id" ||
|
241
|
+
model.columns_hash[attribute.to_s] && attribute ||
|
242
|
+
:id
|
243
|
+
attribute_table_alias = table_alias
|
244
|
+
used_paths << path
|
245
|
+
end
|
246
|
+
bindvar = "#{attribute_table_alias}__#{attribute_name}_#{obligation_index}".to_sym
|
247
|
+
|
248
|
+
sql_attribute = "#{parent_model.connection.quote_table_name(attribute_table_alias)}." +
|
249
|
+
"#{parent_model.connection.quote_table_name(attribute_name)}"
|
250
|
+
if value.nil? and [:is, :is_not].include?(operator)
|
251
|
+
obligation_conds << "#{sql_attribute} IS #{[:contains, :is].include?(operator) ? '' : 'NOT '}NULL"
|
252
|
+
else
|
253
|
+
attribute_operator = case operator
|
254
|
+
when :contains, :is then "= :#{bindvar}"
|
255
|
+
when :does_not_contain, :is_not then "<> :#{bindvar}"
|
256
|
+
when :is_in, :intersects_with then "IN (:#{bindvar})"
|
257
|
+
when :is_not_in then "NOT IN (:#{bindvar})"
|
258
|
+
when :lt then "< :#{bindvar}"
|
259
|
+
when :lte then "<= :#{bindvar}"
|
260
|
+
when :gt then "> :#{bindvar}"
|
261
|
+
when :gte then ">= :#{bindvar}"
|
262
|
+
else raise AuthorizationUsageError, "Unknown operator: #{operator}"
|
263
|
+
end
|
264
|
+
obligation_conds << "#{sql_attribute} #{attribute_operator}"
|
265
|
+
binds[bindvar] = attribute_value(value)
|
266
|
+
end
|
267
|
+
end
|
268
|
+
end
|
269
|
+
obligation_conds << "1=1" if obligation_conds.empty?
|
270
|
+
conds << "(#{obligation_conds.join(' AND ')})"
|
271
|
+
end
|
272
|
+
(delete_paths - used_paths).each {|path| reflections.delete(path)}
|
273
|
+
|
274
|
+
finder_options[:conditions] = [ conds.join( " OR " ), binds ]
|
275
|
+
end
|
276
|
+
|
277
|
+
def attribute_value (value)
|
278
|
+
value.class.respond_to?(:descends_from_active_record?) && value.class.descends_from_active_record? && value.id ||
|
279
|
+
value.is_a?(Array) && value[0].class.respond_to?(:descends_from_active_record?) && value[0].class.descends_from_active_record? && value.map( &:id ) ||
|
280
|
+
value
|
281
|
+
end
|
282
|
+
|
283
|
+
# Parses all of the defined obligation joins and defines the scope's :joins or :includes option.
|
284
|
+
# TODO: Support non-linear association paths. Right now, we just break down the longest path parsed.
|
285
|
+
def rebuild_join_options!
|
286
|
+
joins = (finder_options[:joins] || []) + (finder_options[:includes] || [])
|
287
|
+
|
288
|
+
reflections.keys.each do |path|
|
289
|
+
next if path.empty? or @join_table_joins.include?(path)
|
290
|
+
|
291
|
+
existing_join = joins.find do |join|
|
292
|
+
existing_path = join_to_path(join)
|
293
|
+
min_length = [existing_path.length, path.length].min
|
294
|
+
existing_path.first(min_length) == path.first(min_length)
|
295
|
+
end
|
296
|
+
|
297
|
+
if existing_join
|
298
|
+
if join_to_path(existing_join).length < path.length
|
299
|
+
joins[joins.index(existing_join)] = path_to_join(path)
|
300
|
+
end
|
301
|
+
else
|
302
|
+
joins << path_to_join(path)
|
303
|
+
end
|
304
|
+
end
|
305
|
+
|
306
|
+
case obligation_conditions.length
|
307
|
+
when 0 then
|
308
|
+
# No obligation conditions means we don't have to mess with joins or includes at all.
|
309
|
+
when 1 then
|
310
|
+
finder_options[:joins] = joins
|
311
|
+
finder_options.delete( :include )
|
312
|
+
else
|
313
|
+
finder_options.delete( :joins )
|
314
|
+
finder_options[:include] = joins
|
315
|
+
end
|
316
|
+
end
|
317
|
+
|
318
|
+
def path_to_join (path)
|
319
|
+
case path.length
|
320
|
+
when 0 then nil
|
321
|
+
when 1 then path[0]
|
322
|
+
else
|
323
|
+
hash = { path[-2] => path[-1] }
|
324
|
+
path[0..-3].reverse.each do |elem|
|
325
|
+
hash = { elem => hash }
|
326
|
+
end
|
327
|
+
hash
|
328
|
+
end
|
329
|
+
end
|
330
|
+
|
331
|
+
def join_to_path (join)
|
332
|
+
case join
|
333
|
+
when Symbol
|
334
|
+
[join]
|
335
|
+
when Hash
|
336
|
+
[join.keys.first] + join_to_path(join[join.keys.first])
|
337
|
+
end
|
338
|
+
end
|
339
|
+
end
|
340
|
+
end
|
341
|
+
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Authorization
|
2
|
+
module Padrino
|
3
|
+
def self.registered(app)
|
4
|
+
app.extend(Protect)
|
5
|
+
app.helpers Authorization::AuthorizationInController
|
6
|
+
end
|
7
|
+
|
8
|
+
module Protect
|
9
|
+
def protect(*args)
|
10
|
+
condition {
|
11
|
+
unless permitted_to? args[0][:action], args[0][:resource]
|
12
|
+
halt 403, args[0][:forbidden] || "Can't access this"
|
13
|
+
end
|
14
|
+
}
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
# Include this module in your helpers if you don't have an auth system that provides them :)
|
19
|
+
module CurrentUser
|
20
|
+
def current_user
|
21
|
+
Authorization.current_user
|
22
|
+
end
|
23
|
+
def current_user=(user)
|
24
|
+
Authorization.current_user=user
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
metadata
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: declarative_authorization_padrino
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease:
|
5
|
+
version: 0.1.1
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Dario Javier Cravero
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
|
13
|
+
date: 2011-05-11 00:00:00 Z
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: declarative_authorization
|
17
|
+
prerelease: false
|
18
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
19
|
+
none: false
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 0.5.2
|
24
|
+
type: :runtime
|
25
|
+
version_requirements: *id001
|
26
|
+
description:
|
27
|
+
email: dario@qinnova.com.ar
|
28
|
+
executables: []
|
29
|
+
|
30
|
+
extensions: []
|
31
|
+
|
32
|
+
extra_rdoc_files:
|
33
|
+
- README.rdoc
|
34
|
+
files:
|
35
|
+
- MIT-LICENSE
|
36
|
+
- README.rdoc
|
37
|
+
- Rakefile
|
38
|
+
- lib/declarative_authorization_padrino.rb
|
39
|
+
- lib/declarative_authorization_padrino/authorization.rb
|
40
|
+
- lib/declarative_authorization_padrino/in_model.rb
|
41
|
+
- lib/declarative_authorization_padrino/obligation_scope.rb
|
42
|
+
- lib/declarative_authorization_padrino/padrino.rb
|
43
|
+
homepage: http://github.com/dariocravero/declarative_authorization_padrino
|
44
|
+
licenses: []
|
45
|
+
|
46
|
+
post_install_message:
|
47
|
+
rdoc_options: []
|
48
|
+
|
49
|
+
require_paths:
|
50
|
+
- lib
|
51
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
52
|
+
none: false
|
53
|
+
requirements:
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: 1.8.6
|
57
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
58
|
+
none: false
|
59
|
+
requirements:
|
60
|
+
- - ">="
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: "0"
|
63
|
+
requirements: []
|
64
|
+
|
65
|
+
rubyforge_project:
|
66
|
+
rubygems_version: 1.8.1
|
67
|
+
signing_key:
|
68
|
+
specification_version: 3
|
69
|
+
summary: declarative_authorization_padrino is a Padrino's wrapper around declarative_authorization's Rails plugin for maintainable authorization based on readable authorization rules.
|
70
|
+
test_files: []
|
71
|
+
|