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 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
+