declarative_authorization_padrino 0.1.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/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
|
+
|