permitters 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ MmY3NGJmNTczNzExZjRkYjQ3MDRiZWJiNGM3NDhjOWI5ZTYyZThlZQ==
5
+ data.tar.gz: !binary |-
6
+ NmZmYTk1MTExNDg2YmQ4OGQyM2I5YjE2OTUzNjA2MGVhZjRlMGY4YQ==
7
+ !binary "U0hBNTEy":
8
+ metadata.gz: !binary |-
9
+ NmQ5ZGYxY2I4NTcwZDY1OTBkOTg0M2VmYzIwYWM4OTlhZjBjMjJiNGU1YzU4
10
+ ZmU5OTY5MTVhNTFjZGMwZGFhN2MyNWFhN2Y5ODQ4ZGIzY2I2ZWM5ZmRjMDNj
11
+ NzUxYTQzNWZiNjRmYTBmZjdiZGViMTdlMmY3NzI1YzJiZThmMDE=
12
+ data.tar.gz: !binary |-
13
+ OTcxMzUxMjBmNGFiNDg4MzliNDhmMTg1MzA2MTg5NTIzNjhkNWVmMzk2MTRk
14
+ NzUxZTczN2M5MTFlMTE0MzQwNGZmOGZlNDZhM2MwMjBiZjczNmVlZDUzMTgx
15
+ NGE2NGI1ZDRjZmVmMjg1YWVkMDQzNDIyNDNjOWI2NWM4NDkyMzA=
data/README.md ADDED
@@ -0,0 +1,296 @@
1
+ [![Build Status](https://secure.travis-ci.org/permitters/permitters.png?branch=master)](http://travis-ci.org/permitters/permitters) [![Gem Version](https://badge.fury.io/rb/permitters.png)](http://badge.fury.io/rb/permitters)
2
+ # Permitters
3
+
4
+ Permitters are an object-oriented way of defining what request parameters are permitted using [Strong Parameters][strong_parameters]. It is to Strong Parameters what ActiveModel::Serializers are to as_json/to_json.
5
+
6
+ Permitters also allow additional parameter authorization using an authorizing class, such as CanCan's Ability class or a custom authorizer.
7
+
8
+ The original version of the Permitters framework that this was based on was provided in a [post][post] by Adam Hawkins.
9
+
10
+ ### Installation
11
+
12
+ In your Rails app's `Gemfile`:
13
+
14
+ ```ruby
15
+ gem 'permitters', '~> 0.0.1'
16
+ ```
17
+
18
+ Then:
19
+
20
+ ```
21
+ bundle install
22
+ ```
23
+
24
+ #### Strong Parameters
25
+
26
+ If you are using Rails 4.x, Strong Parameters is included and everything should be setup, so you can skip this section.
27
+
28
+ If you are using Rails 3.x, you'll need [Strong Parameters][strong_parameters]:
29
+
30
+ ```ruby
31
+ gem 'strong_parameters'
32
+ ```
33
+
34
+ Also in Rails 3.x, you probably will want to change this to false in config:
35
+
36
+ ```ruby
37
+ config.active_record.whitelist_attributes = false
38
+ ```
39
+
40
+ and either put this in each model you want to use Strong Parameters with:
41
+
42
+ ```ruby
43
+ include ActiveModel::ForbiddenAttributesProtection
44
+ ```
45
+
46
+ Or if you'd rather use Strong Parameters with all models, just put this at the bottom of your `config/environment.rb` or an initializer:
47
+
48
+ ```ruby
49
+ ActiveRecord::Base.send :include, ActiveModel::ForbiddenAttributesProtection
50
+ ```
51
+
52
+ ### Usage
53
+
54
+ First, either put this in each controller you want to use Strong Parameters with:
55
+
56
+ ```ruby
57
+ include ActionController::Permittance
58
+ ```
59
+
60
+ Or if you'd rather use Permitters with all controllers, just put this at the bottom of your `config/environment.rb` or an initializer:
61
+
62
+ ```ruby
63
+ ActionController::Base.send :include, ActionController::Permittance
64
+ ```
65
+
66
+ Then in your controller:
67
+
68
+ ```ruby
69
+ def create
70
+ @deal = Deal.new permitted_params
71
+ # ...
72
+ end
73
+ ```
74
+
75
+ Next, add a permitter for each controller that uses `ActionController::Permittance` in `/app/permitters/`.
76
+
77
+ For `/app/controllers/deals_controller.rb`, you would add a `/app/permitters/deal_permitter.rb`:
78
+
79
+ ```ruby
80
+ class DealPermitter < ActionController::Permitter
81
+ # No premissions required to set this
82
+ permit :name, :description, :close_by, :state
83
+
84
+ # can pass `:authorize` with a permission:
85
+ # This line allows user_id if the user can read the user specified
86
+ # by the user_id. This only happens if it's present
87
+ permit :user_id, :authorize => :read
88
+
89
+ # same thing but automatically handles arrays of ids as well.
90
+ # This line allows the attachment_ids if the user can manage all
91
+ # the specified attachments
92
+ permit :attachment_ids, :authorize => :manage
93
+
94
+ # same thing as before but scopes this it to the
95
+ # hash inside the line_items_attributes array
96
+ #
97
+ # line_items_attributes is permitted if every item in the array
98
+ # is allowed.
99
+ #
100
+ # This also only allows line items if the user can manage the parent
101
+ scope :line_items_attributes, :manage => true do |line_item|
102
+ # So you cannot manipulate line items outside the parent
103
+ line_item.permit :id, :authorize => :manage
104
+
105
+ line_item.permit :name, :quantity, :price, :currency, :notes
106
+ line_item.permit :product_id, :authorize => :read
107
+ end
108
+ end
109
+ ```
110
+
111
+ When you call `permitted_params`, this happens:
112
+
113
+ ```ruby
114
+ params.require(:deal).permit(:name, :description, :close_by, :state, :line_items_attributes => [:id, :name, :quantity, :price, :currency, :notes, product_id])
115
+ ```
116
+
117
+ If the controller is namespaced, the permitter should have the same namespace, e.g. `A:B:DealsController` defined in `app/controllers/a/b/deals_controller.rb` requires `A:B:DealPermitter` defined in `/app/a/b/permitters/deal_permitter.rb`.
118
+
119
+ If you need to override the argument(s) to pass into the require, use `resource` in the permitter:
120
+
121
+ ```ruby
122
+ class DealPermitter < ActionController::Permitter
123
+ resource :deal
124
+ # ...
125
+ end
126
+ ```
127
+
128
+ To specify a different Permitter to use with a Controller, either provide a `permitter_class` method:
129
+
130
+ ```ruby
131
+ def permitter_class
132
+ PersonPermitter
133
+ end
134
+ ```
135
+
136
+ Or to specify the permitter, use `permitted_params_using(PermitterClass)`, e.g.:
137
+
138
+ ```ruby
139
+ def make_cotton_candy
140
+ @cotton_candy = CottonCandy.new(permitted_params_using(A::B::CottonCandyPermitter))
141
+ # ...
142
+ end
143
+ ```
144
+
145
+ ### Authorizers
146
+
147
+ Permitters also allow additional parameter authorization using CanCan or a custom authorizer.
148
+
149
+ The authorizer class must implement `initialize(user)` and `authorize!(permission, record)` (like CanCan's Ability class).
150
+
151
+ The controller class can implement a method to return an instance of the authorizer class.
152
+
153
+ So, now let's add the `authorize!`:
154
+
155
+ ```ruby
156
+ def create
157
+ authorize! :create, Deal
158
+ @deal = Deal.new permitted_params
159
+ # ...
160
+ end
161
+ ```
162
+
163
+ When you call `authorize!(:some_permission, YourModelClass)` method, that method will raise an error if `current_user` doesn't have the appropriate permissions for those attributes for which `:authorize` is specified.
164
+
165
+ #### Adding a Custom Authorizer
166
+
167
+ To set this up, you'll need to add one of these into your app config:
168
+
169
+ ```ruby
170
+ config.action_controller.authorizer = 'SomeAuthorizer'
171
+ ```
172
+
173
+ or:
174
+
175
+ ```ruby
176
+ config.action_controller.current_authorizer_method = 'current_authorizer'
177
+ ```
178
+
179
+ If neither is specified, then if the controller calls `authorize!(permission, record)`, nothing happens.
180
+
181
+ ##### Without a Controller Method
182
+
183
+ Put this into your app config:
184
+
185
+ ```ruby
186
+ config.action_controller.authorizer = 'SomeAuthorizer'
187
+ ```
188
+
189
+ Create a class `lib/some_authorizer.rb` that has an `initialize(user)` and `authorize!(permission, record)` methods:
190
+
191
+ ```ruby
192
+ class SomeAuthorizer
193
+
194
+ def initialize(user)
195
+ @user = user
196
+ end
197
+
198
+ def authorize!(permission, record)
199
+ raise "You must login to create deals" if permission == :create && record == Deal && @user.name == 'guest'
200
+ end
201
+ end
202
+ ```
203
+
204
+ ##### With a Controller Method
205
+
206
+ Put this into your app config:
207
+
208
+ ```ruby
209
+ config.action_controller.current_authorizer_method = 'current_authorizer'
210
+ ```
211
+
212
+ and in your controller or ApplicationController return an instance of an authorizer from that method:
213
+
214
+ ```ruby
215
+ def current_authorizer
216
+ @current_ability ||= ::SomeAuthorizer.new
217
+ end
218
+ ```
219
+
220
+ Create a class `lib/some_authorizer.rb` that raises an error from `authorize!(permission, record)` if unauthorized:
221
+
222
+ ```ruby
223
+ class SomeAuthorizer
224
+ def authorize!(permission, record)
225
+ raise "Deals can only be created from 8-5pm" if permission == :create && record == Deal && (Time.new.hour < 8 || Time.new.hour >= 17)
226
+ end
227
+ end
228
+ ```
229
+
230
+ ##### CanCan
231
+
232
+ [CanCan][cancan] is able to integrate with the Permitters framework as an authorizer. To use CanCan, put this into your app config:
233
+
234
+ ```ruby
235
+ config.action_controller.authorizer = 'Ability'
236
+ config.action_controller.current_authorizer_method = 'current_ability'
237
+ ```
238
+
239
+ (Note: You could just define either. If you don't set current_authorizer_method, it will just try creating an instance of the authorizer using the current user. If neither are specified, nothing will happen when `authorize!(permission, record)` is called.)
240
+
241
+ CanCan can integrate with [Authlogic][authlogic], [Devise][devise], etc. to return a proper logged-in user, or you can return it however you wish from the `current_user` method in your controller. Just to provide a simple example, we'll pretend the user was logged-in and return a new User instance (which means you will need a User model):
242
+
243
+ ```ruby
244
+ class ApplicationController < ActionController::Base
245
+ protect_from_forgery
246
+
247
+ def current_user
248
+ User.new
249
+ end
250
+ end
251
+ ```
252
+
253
+ Again for simplicity, we'll write an "allow-everything" Ability in `app/models/ability.rb`:
254
+
255
+ ```ruby
256
+ class Ability
257
+ include CanCan::Ability
258
+
259
+ def initialize(user)
260
+ can :manage, :all
261
+ end
262
+ end
263
+ ```
264
+
265
+ Then in each model you want to use CanCan with, add this into the class:
266
+
267
+ ```ruby
268
+ include CanCan::ModelAdditions
269
+ ```
270
+
271
+ If you'd rather use CanCan with all models, just put this at the bottom of your `config/environment.rb` or an initializer:
272
+
273
+ ```ruby
274
+ ActiveRecord::Base.send :include, CanCan::ModelAdditions
275
+ ```
276
+
277
+ ### Release Notes
278
+
279
+ See the [changelog][changelog].
280
+
281
+ ### Contributors
282
+
283
+ * Adam Hawkins (https://github.com/twinturbo)
284
+ * Gary Weaver (https://github.com/garysweaver)
285
+
286
+ ### License
287
+
288
+ Copyright (c) 2013 Adam Hawkins and Gary S. Weaver, released under the [MIT license][lic].
289
+
290
+ [post]: http://broadcastingadam.com/2012/07/parameter_authorization_in_rails_apis/
291
+ [cancan]: https://github.com/ryanb/cancan
292
+ [strong_parameters]: https://github.com/rails/strong_parameters
293
+ [authlogic]: https://github.com/binarylogic/authlogic
294
+ [devise]: https://github.com/plataformatec/devise
295
+ [changelog]: https://github.com/permitters/permitters/blob/master/CHANGELOG.md
296
+ [lic]: http://github.com/permitters/permitters/blob/master/LICENSE
data/Rakefile ADDED
@@ -0,0 +1,18 @@
1
+ require 'bundler/setup'
2
+ require 'bundler/gem_tasks'
3
+ require 'appraisal'
4
+
5
+ require 'rspec/core/rake_task'
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ task :default do |t|
9
+ if ENV['BUNDLE_GEMFILE'] =~ /gemfiles/
10
+ exec 'rake spec'
11
+ else
12
+ exec 'rake appraise'
13
+ end
14
+ end
15
+
16
+ task :appraise => ['appraisal:install'] do |t|
17
+ exec 'rake appraisal'
18
+ end
@@ -0,0 +1,41 @@
1
+ module ActionController
2
+ module Permittance
3
+ extend ActiveSupport::Concern
4
+
5
+ def permitted_params
6
+ get_permitted_params_using(permitter)
7
+ end
8
+
9
+ def permitted_params_using(pclass)
10
+ get_permitted_params_using(permitter(pclass))
11
+ end
12
+
13
+ # Returns a new instance of the permitter by initializing it with params, current_user, current_ability.
14
+ def permitter(pclass = permitter_class)
15
+ pinstance = (@permitter_class_to_permitter ||= {})[pclass]
16
+ return pinstance if pinstance
17
+ current_authorizer_method = ActionController::Permitter.current_authorizer_method ? ActionController::Permitter.current_authorizer_method.to_sym : nil
18
+ @permitter_class_to_permitter[pclass] = pclass.new(params, current_user, current_authorizer_method && defined?(current_authorizer_method) ? __send__(current_authorizer_method) : nil)
19
+ end
20
+
21
+ # Returns the permitter class corresponding to the controller by matching everything in the controller class name
22
+ # other than "Controller" and singularizing the part after any namespace before tacking on Permitter to the name.
23
+ #
24
+ # e.g. if self.class.name is "A:B:StatusesController", it would return A::B::StatusPermitter
25
+ def permitter_class
26
+ # Permitters should be in the same namespace as the controller, like ActiveModel::Serializers are in the same namespace as the model.
27
+ name = self.class.name
28
+ # in Rails 3.2+ could do:
29
+ # namespace = name.deconstantize; "#{namespace}#{namespace.blank? ? '' : '::'}#{name.demodulize.chomp('Controller').singularize}Permitter".constantize
30
+ # Rails < 3.2
31
+ last_index = name.rindex('::')
32
+ "#{last_index ? "#{name[0...(last_index || 0)]}::" : ''}#{(last_index ? name[(last_index+2)..-1] : name).chomp('Controller').singularize}Permitter".constantize
33
+ end
34
+
35
+ private
36
+
37
+ def get_permitted_params_using(pinstance)
38
+ (@permitter_to_permitted_params ||= {})[pinstance] ||= pinstance.permitted_params
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,101 @@
1
+ module ActionController
2
+ class PermitterAttribute < Struct.new(:name, :options); end
3
+ class Permitter
4
+
5
+ # Application-wide configuration
6
+ cattr_accessor :authorizer, :instance_accessor => false
7
+ cattr_accessor :current_authorizer_method, :instance_accessor => false
8
+
9
+ class << self
10
+ # When Permitter is inherited, it sets the resource (the symbol for params.require(some_sym)) to the unnamespaced model class that corresponds
11
+ # to the Permitter's classname, e.g. by default A::B::ApplesController will use A::B::ApplePermitter which will do params.permit(:apple).
12
+ # To change this value, use the `resource` class method.
13
+ def inherited(subclass)
14
+ subclass.class_eval do
15
+ class_attribute :permitted_attributes
16
+ class_attribute :resource_name
17
+
18
+ self.permitted_attributes = []
19
+ name = self.name
20
+
21
+ # in Rails 3.2+ could do:
22
+ # name.demodulize.chomp('Permitter').underscore.to_sym
23
+ # Rails < 3.2
24
+ last_index = name.rindex('::')
25
+ self.resource_name = (last_index ? name[(last_index+2)..-1] : name).chomp('Permitter').underscore.to_sym
26
+ end
27
+ end
28
+
29
+ def permit(*args)
30
+ options = args.extract_options!
31
+
32
+ args.each do |name|
33
+ self.permitted_attributes += [ActionController::PermitterAttribute.new(name, options)]
34
+ end
35
+ end
36
+
37
+ def scope(name)
38
+ with_options :scope => name do |nested|
39
+ yield nested
40
+ end
41
+ end
42
+
43
+ def resource(name)
44
+ self.resource_name = name
45
+ end
46
+ end
47
+
48
+ def initialize(params, user, authorizer = nil)
49
+ @params, @user, @authorizer = params, user, authorizer
50
+ end
51
+
52
+ def permitted_params
53
+ scopes = {}
54
+ unscoped_attributes = []
55
+
56
+ permitted_attributes.each do |attribute|
57
+ scope_name = attribute.options[:scope]
58
+ (scope_name ? (scopes[scope_name] ||= []) : unscoped_attributes) << attribute.name
59
+ end
60
+
61
+ # class_attribute creates an instance method called resource_name, which we'll allow overriding of in the permitter definition, if desired for some odd reason.
62
+ @filtered_params ||= params.require(resource_name).permit(*unscoped_attributes, scopes)
63
+
64
+ permitted_attributes.select {|a| a.options[:authorize]}.each do |attribute|
65
+ scope_name = attribute.options[:scope]
66
+ values = scope_name ? Array.wrap(@filtered_params[scope_name]).collect {|hash| hash[attribute.name]}.compact : Array.wrap(@filtered_params[attribute.name])
67
+ klass = (attribute.options[:as].try(:to_s) || attribute.name.to_s.split(/(.+)_ids?/)[1]).classify.constantize
68
+
69
+ values.each do |record_id|
70
+ record = klass.find record_id
71
+ permission = attribute.options[:authorize].to_sym || :read
72
+ authorize! permission, record
73
+ end
74
+ end
75
+
76
+ @filtered_params
77
+ end
78
+
79
+ def authorize!(*args, &block)
80
+ # implementing here is clearer than doing a delegate :authorize!, :to => :authorizer, imo.
81
+ authorizer ? authorizer.__send__(:authorize!, *args, &block) : nil
82
+ end
83
+
84
+
85
+ private
86
+
87
+ def params
88
+ @params
89
+ end
90
+
91
+ def user
92
+ @user
93
+ end
94
+
95
+ def authorizer
96
+ # e.g. if ActionController::Permitter.authorizer = Ability
97
+ # then this returns Ability.new(user)
98
+ @authorizer ||= (ActionController::Permitter.authorizer.is_a?(String) ? ActionController::Permitter.authorizer.constantize : ActionController::Permitter.authorizer).try(:new, user)
99
+ end
100
+ end
101
+ end
data/lib/permitters.rb ADDED
@@ -0,0 +1,4 @@
1
+ require 'permitters/version'
2
+ require 'action_controller/permittance'
3
+ require 'action_controller/permitter'
4
+ require 'permitters/railtie' if defined? ::Rails::Railtie
@@ -0,0 +1,15 @@
1
+ require 'rails/railtie'
2
+
3
+ module Permitters
4
+ class Railtie < ::Rails::Railtie
5
+ initializer "permitters.config", :before => "action_controller.set_configs" do |app|
6
+ ActionController::Permitter.authorizer = app.config.action_controller.delete(:authorizer)
7
+ ActionController::Permitter.current_authorizer_method = app.config.action_controller.delete(:current_authorizer_method)
8
+ end
9
+
10
+ initializer "permitters.set_autoload_path", after: :load_config_initializers do
11
+ permitters_path = "#{Rails.root}/app/permitters"
12
+ ActiveSupport::Dependencies.autoload_paths << permitters_path unless ActiveSupport::Dependencies.autoload_paths.include?(permitters_path)
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,3 @@
1
+ module Permitters
2
+ VERSION = '0.0.1'
3
+ end
metadata ADDED
@@ -0,0 +1,55 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: permitters
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Adam Hawkins
8
+ - Gary S. Weaver
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-04-27 00:00:00.000000000 Z
13
+ dependencies: []
14
+ description: Permitters are an object-oriented way of defining what request parameters
15
+ are permitted. using Strong Parameters. It is to Strong Parameters what ActiveModel::Serializers
16
+ are to as_json/to_json, but supports CanCan and similar authorization frameworks.
17
+ email:
18
+ - me@broadcastingadam.com
19
+ - garysweaver@gmail.com
20
+ executables: []
21
+ extensions: []
22
+ extra_rdoc_files: []
23
+ files:
24
+ - lib/action_controller/permittance.rb
25
+ - lib/action_controller/permitter.rb
26
+ - lib/permitters/railtie.rb
27
+ - lib/permitters/version.rb
28
+ - lib/permitters.rb
29
+ - Rakefile
30
+ - README.md
31
+ homepage: https://github.com/permitters/permitters
32
+ licenses:
33
+ - MIT
34
+ metadata: {}
35
+ post_install_message:
36
+ rdoc_options: []
37
+ require_paths:
38
+ - lib
39
+ required_ruby_version: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ! '>='
42
+ - !ruby/object:Gem::Version
43
+ version: '0'
44
+ required_rubygems_version: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ! '>='
47
+ - !ruby/object:Gem::Version
48
+ version: '0'
49
+ requirements: []
50
+ rubyforge_project:
51
+ rubygems_version: 2.0.3
52
+ signing_key:
53
+ specification_version: 4
54
+ summary: Object-oriented parameter authorization with Strong Parameters
55
+ test_files: []