permitter 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (77) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.rdoc +302 -0
  4. data/Rakefile +32 -0
  5. data/lib/generators/permitter/permission/USAGE +5 -0
  6. data/lib/generators/permitter/permission/permission_generator.rb +16 -0
  7. data/lib/generators/permitter/permission/templates/permission.rb +22 -0
  8. data/lib/generators/permitter/permission/templates/permission_spec.rb +17 -0
  9. data/lib/permitter.rb +4 -0
  10. data/lib/permitter/controller_additions.rb +43 -0
  11. data/lib/permitter/exceptions.rb +17 -0
  12. data/lib/permitter/matchers.rb +11 -0
  13. data/lib/permitter/model_additions.rb +30 -0
  14. data/lib/permitter/permission.rb +72 -0
  15. data/lib/permitter/version.rb +3 -0
  16. data/spec/README.rdoc +21 -0
  17. data/spec/controllers/projects_controller_spec.rb +243 -0
  18. data/spec/controllers/users_controller_spec.rb +77 -0
  19. data/spec/dummy/Rakefile +6 -0
  20. data/spec/dummy/app/assets/javascripts/application.js +13 -0
  21. data/spec/dummy/app/assets/stylesheets/application.css +13 -0
  22. data/spec/dummy/app/controllers/application_controller.rb +19 -0
  23. data/spec/dummy/app/controllers/projects_controller.rb +49 -0
  24. data/spec/dummy/app/controllers/users_controller.rb +17 -0
  25. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  26. data/spec/dummy/app/models/permission.rb +26 -0
  27. data/spec/dummy/app/models/project.rb +3 -0
  28. data/spec/dummy/app/models/user.rb +3 -0
  29. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  30. data/spec/dummy/app/views/projects/edit.html.erb +0 -0
  31. data/spec/dummy/app/views/projects/index.html.erb +0 -0
  32. data/spec/dummy/app/views/projects/new.html.erb +0 -0
  33. data/spec/dummy/app/views/projects/show.html.erb +0 -0
  34. data/spec/dummy/app/views/users/index.html.erb +0 -0
  35. data/spec/dummy/app/views/users/show.html.erb +0 -0
  36. data/spec/dummy/bin/bundle +3 -0
  37. data/spec/dummy/bin/rails +4 -0
  38. data/spec/dummy/bin/rake +4 -0
  39. data/spec/dummy/config.ru +4 -0
  40. data/spec/dummy/config/application.rb +23 -0
  41. data/spec/dummy/config/boot.rb +5 -0
  42. data/spec/dummy/config/database.yml +25 -0
  43. data/spec/dummy/config/environment.rb +5 -0
  44. data/spec/dummy/config/environments/development.rb +29 -0
  45. data/spec/dummy/config/environments/production.rb +80 -0
  46. data/spec/dummy/config/environments/test.rb +36 -0
  47. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  48. data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  49. data/spec/dummy/config/initializers/inflections.rb +16 -0
  50. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  51. data/spec/dummy/config/initializers/secret_token.rb +12 -0
  52. data/spec/dummy/config/initializers/session_store.rb +3 -0
  53. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  54. data/spec/dummy/config/locales/en.yml +23 -0
  55. data/spec/dummy/config/routes.rb +59 -0
  56. data/spec/dummy/db/development.sqlite3 +0 -0
  57. data/spec/dummy/db/migrate/20131205175023_create_projects.rb +10 -0
  58. data/spec/dummy/db/migrate/20131205175100_create_users.rb +10 -0
  59. data/spec/dummy/db/migrate/20131210152022_add_columns_to_projects.rb +5 -0
  60. data/spec/dummy/db/schema.rb +31 -0
  61. data/spec/dummy/db/test.sqlite3 +0 -0
  62. data/spec/dummy/log/development.log +825 -0
  63. data/spec/dummy/log/test.log +44662 -0
  64. data/spec/dummy/public/404.html +58 -0
  65. data/spec/dummy/public/422.html +58 -0
  66. data/spec/dummy/public/500.html +57 -0
  67. data/spec/dummy/public/favicon.ico +0 -0
  68. data/spec/factories/project.rb +6 -0
  69. data/spec/factories/user.rb +10 -0
  70. data/spec/models/permission_spec.rb +72 -0
  71. data/spec/permitter/controller_additions_spec.rb +44 -0
  72. data/spec/permitter/exceptions_spec.rb +36 -0
  73. data/spec/permitter/matchers_spec.rb +9 -0
  74. data/spec/permitter/model_additions_spec.rb +138 -0
  75. data/spec/permitter/permission_spec.rb +84 -0
  76. data/spec/spec_helper.rb +32 -0
  77. metadata +278 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: c1982649b776d20be5bb9f18c484aba7fa562a8c
4
+ data.tar.gz: c0ec0c06e6b7bdb1acc07d149b902dde3d980090
5
+ SHA512:
6
+ metadata.gz: 21c03aa7c540916d8fda1ce1f8a2004202071f35539cebf41d765c4d7fa7cf4eae3d3fee090ecc8ace59ff548316a0f75e24f49fd5f0fd148573f5631e15944d
7
+ data.tar.gz: 0f9f654b5c0baa97179cfb2c6e97de6972b942ef0d412b7ec74e0207fb085c169035b824e943cc395e94d7db80c8ba39ba7c8cc8b1dd06ed1e7e97180492438e
@@ -0,0 +1,20 @@
1
+ Copyright 2013 Matthew Erhard
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.
@@ -0,0 +1,302 @@
1
+ = Permitter
2
+ {<img src="https://travis-ci.org/merhard/permitter.png?branch=master" alt="Build Status" />}[https://travis-ci.org/merhard/permitter] {<img src="https://codeclimate.com/github/merhard/permitter.png" />}[https://codeclimate.com/github/merhard/permitter]
3
+
4
+ Here are some instructions for setting up Permitter. Try this out and provide feedback in the {issue tracker}[https://github.com/merhard/permitter/issues].
5
+
6
+
7
+ == Setup
8
+
9
+ Permitter expects your controllers to have a +current_user+ method. Add some authentication for this (such as Devise[https://github.com/plataformatec/devise]).
10
+
11
+ To install Permitter, add it to your +Gemfile+ and run the +bundle+ command.
12
+
13
+ gem "permitter", git: "git://github.com/merhard/permitter.git"
14
+
15
+ Next generate a Permission class, this is where your permissions will be defined.
16
+
17
+ rails g permitter:permission
18
+
19
+ Add authorization by calling +authorize_user!+ in a +before_action+ in any controller (or the +ApplicationController+ to authorize the whole app).
20
+
21
+ class ApplicationController < ActionController::Base
22
+ before_action :authorize_user!
23
+ end
24
+
25
+ This will add an authorization check locking down every action in the controller. If you try visiting a page without granting the user access, a <tt>Permitter::Unauthorized</tt> exception will be raised. You can catch this exception and modify its behavior.
26
+
27
+ class ApplicationController < ActionController::Base
28
+ before_action :authorize_user!
29
+
30
+ rescue_from Permitter::Unauthorized do |exception|
31
+ # your code here
32
+ end
33
+ end
34
+
35
+
36
+ == Defining Abilities
37
+
38
+ You grant access to controller actions through the +Permission+ class which was generated above. The +current_user+ is passed in allowing you to define permissions based on user attributes. For example:
39
+
40
+ class Permission
41
+ include Permitter::Permission
42
+
43
+ def initialize(user)
44
+ if user
45
+ allow_all
46
+ else
47
+ allow_action [:sessions, :registrations], [:new, :create]
48
+ allow_action :projects, :index
49
+ end
50
+ end
51
+ end
52
+
53
+ Here, if there is a +current_user+ (user signed in), he will be able to perform any action on any controller. If +current_user+ is +nil+ (user not signed in), the visitor can only access the new and create actions of the +SessionsController+ and the +RegistrationsController+ as well as the +#index+ action of the +ProjectsController+.
54
+
55
+ The first argument to +allow_action+ is the controller name being permitted. The second argument is the action they can perform in that controller.
56
+
57
+ As shown above, pass an array to either of these will grant permission on each item in the array. Controller names and actions can be represented as symbols or strings.
58
+
59
+ You can check permissions in any controller or view using the +allowed_action?+ method.
60
+
61
+ <% if allowed_action? :projects, :create %>
62
+ <%= link_to "New Project", new_project_path %>
63
+ <% end %>
64
+
65
+ Here the link will only show up if the user can create projects.
66
+
67
+
68
+ == Resource Conditions
69
+
70
+ If you need to change authorization based on a model's attributes, you can do so by passing a block as the last argument to +allow_action+. For example, if you want to only allow a user to edit projects which he/she owns, first:
71
+
72
+ class Permission
73
+ include Permitter::Permission
74
+
75
+ def initialize(user)
76
+ if user
77
+
78
+ allow_action :projects, [:edit, :update] do |project|
79
+ project.user_id == user.id
80
+ end
81
+
82
+ end
83
+ end
84
+ end
85
+
86
+
87
+ Then, create a +current_resource+ method in that controller:
88
+
89
+ class ProjectsController < ApplicationController
90
+
91
+ private
92
+
93
+ def current_resource
94
+ @project ||= Project.find(params[:id]) if params[:id]
95
+ end
96
+ end
97
+
98
+
99
+ You can still check permissions using the +allowed_action?+ method. Just pass in the resource.
100
+
101
+ <% if allowed_action? :projects, :update, @project %>
102
+ <%= link_to "Edit Project", edit_project_path %>
103
+ <% end %>
104
+
105
+ Here, it will only show the edit link if the +user_id+ of the project matches the +current_user.id+.
106
+
107
+
108
+ == Resource Attributes
109
+
110
+ Rails 4 moved mass assignment to the controller level with +strong_parameters+. +Permitter+ fully supports Rails 4 mass assignment. If mass assignment in your app requires no user specific logic, it may not be necessarry to use Permitter for mass assignment sanitation. In this case, just follow normal Rails 4 methods for strong parameter mass assignment.
111
+
112
+ If your app does require user specific mass assignment logic, +Permitter+ supplies an +allow_param+ method to be used along-side +allow_action+ in your +Permission+ class.
113
+
114
+ For example, suppose a user should only be able to set the title (and no other attributes) of projects they own. Just:
115
+
116
+ class Permission
117
+ include Permitter::Permission
118
+
119
+ def initialize(user)
120
+ allow_action :project, [:index, :show]
121
+
122
+ if user
123
+ allow_action :projects, [:new, :create]
124
+
125
+ allow_action :projects, [:edit, :update] do |project|
126
+ project.user_id == user.id
127
+ end
128
+
129
+ allow_param :project, :title
130
+
131
+ allow_all if user.admin?
132
+
133
+ end
134
+ end
135
+ end
136
+
137
+
138
+ The +allow_param+ method takes a resource title (or array of resource titles) and an attribute (or array of attributes) for that resource to be permitted via strong parameters. +Permitter+ will modify +params+ for that resource (here, <tt>params[:project]</tt>) to only include the whitelisted attributes, removing all others. +Permitter+ flags the remaining +params+ of that resource permitted per strong parameters. This allows mass assignment in the controller to follow the old Rails 3.2 syntax while using the more secure methodology of strong paramters.
139
+
140
+ class ProjectsController < ApplicationController
141
+
142
+ ...
143
+
144
+ def create
145
+ @project = Project.create(params[:project])
146
+ end
147
+
148
+ def update
149
+ @project = Project.find(params[:id])
150
+ @project.update(params[:project])
151
+ end
152
+
153
+ ...
154
+
155
+ end
156
+
157
+
158
+ You can check permissions using the +allowed_param?+ method.
159
+
160
+ <% if allowed_param? :project, :title %>
161
+ <div class="field">
162
+ <%= f.label :title %><br />
163
+ <%= f.text_field :title %>
164
+ </div>
165
+ <% end %>
166
+
167
+ Here, it will only show the title label and text field if the user is allowed to modify the title attribute of +Project+.
168
+
169
+
170
+ == Permission Scoping
171
+
172
+ Sometimes you may want to scope the relation used in the +#index+ action of the controller. +Permitter+ allows you to do this in your +Permission+ class without the need to repeat yourself.
173
+
174
+ For example:
175
+
176
+ class Permission
177
+ include Permitter::Permission
178
+
179
+ def initialize(user)
180
+ allow_action :projects, :index
181
+
182
+ if user
183
+ allow_action :projects, :show do |project|
184
+ project.user_id == user.id
185
+ end
186
+ end
187
+ end
188
+ end
189
+
190
+ class ProjectController < ApplicationController
191
+
192
+ def index
193
+ @projects = Project.permitted_by(current_permissions)
194
+ end
195
+
196
+ end
197
+
198
+ The <tt>@projects</tt> variable will now be scoped to projects available to the +#show+ action.
199
+
200
+
201
+ If the +#show+ action does not match the scoping needed, any action can be used (even a custom one if none match).
202
+
203
+ class Permission
204
+ include Permitter::Permission
205
+
206
+ def initialize(user)
207
+ allow_action :projects, :index
208
+
209
+ if user
210
+ allow_action :projects, :show do |project|
211
+ project.user_id == user.id
212
+ end
213
+
214
+ allow_action :projects, :custom_action do |project|
215
+ # your scope here
216
+ end
217
+ end
218
+ end
219
+ end
220
+
221
+ Then:
222
+
223
+ class ProjectController < ApplicationController
224
+
225
+ def index
226
+ @projects = Project.permitted_by(current_permissions, :custom_action)
227
+ end
228
+
229
+ end
230
+
231
+
232
+
233
+ When writing scopes via an association, a custom action must be used with a block requiring no arguments.
234
+
235
+ class Permission
236
+ include Permitter::Permission
237
+
238
+ def initialize(user)
239
+ allow_action :comments, :index
240
+
241
+ allow_action :comments, :permitted do
242
+ article.published == true
243
+ end
244
+ end
245
+ end
246
+
247
+ class CommentsController < ApplicationController
248
+ def index
249
+ @comments = Comment.joins(:article).permitted_by(@permissions, :permitted)
250
+ #alternatively: Comment.joins{article}.permitted_by(@permissions, :permitted)
251
+ end
252
+ end
253
+
254
+
255
+ class Comment < ActiveRecord::Base
256
+ # t.integer :article_id
257
+
258
+ belongs_to :article
259
+ end
260
+
261
+ class Article < ActiveRecord::Base
262
+ # integer :category_id
263
+ # boolean :published
264
+
265
+ belongs_to :category
266
+ has_many :comments
267
+ end
268
+
269
+ class Category < ActiveRecord::Base
270
+ # boolean :visible
271
+
272
+ has_many :articles
273
+ end
274
+
275
+ Nested joins are also supported:
276
+
277
+ class Permission
278
+ include Permitter::Permission
279
+
280
+ def initialize(user)
281
+ allow_action :comments, :index
282
+
283
+ allow_action :comments, :permitted do
284
+ article.category.visible == true
285
+ end
286
+ end
287
+ end
288
+
289
+ class CommentsController < ApplicationController
290
+ def index
291
+ @comments = Comment.joins{article.category}.permitted_by(@permissions, :permitted)
292
+ end
293
+ end
294
+
295
+
296
+ Permitter accomplishes this using the squeel[https://github.com/activerecord-hackery/squeel] gem. See the squeel docs for any query related questions.
297
+
298
+
299
+
300
+ == Special Thanks
301
+
302
+ Permitter was inspired by cancan[https://github.com/ryanb/cancan/] and Railscasts[http://railscasts.com/episodes/385-authorization-from-scratch-part-1/].
@@ -0,0 +1,32 @@
1
+ require 'rubygems'
2
+
3
+ begin
4
+ require 'bundler/setup'
5
+ rescue LoadError
6
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
7
+ end
8
+
9
+ require 'rake'
10
+
11
+
12
+ require 'rdoc/task'
13
+
14
+ RDoc::Task.new(:rdoc) do |rdoc|
15
+ rdoc.rdoc_dir = 'rdoc'
16
+ rdoc.title = 'Permitter'
17
+ rdoc.options << '--line-numbers'
18
+ rdoc.rdoc_files.include('README.rdoc')
19
+ rdoc.rdoc_files.include('lib/**/*.rb')
20
+ end
21
+
22
+
23
+ require 'rspec/core/rake_task'
24
+
25
+ RSpec::Core::RakeTask.new('spec')
26
+
27
+ # If you want to make this the default task
28
+ task :default => :spec
29
+
30
+
31
+
32
+ Bundler::GemHelper.install_tasks
@@ -0,0 +1,5 @@
1
+ Description:
2
+ The permitter:permission generator creates a Permission class in the models
3
+ directory. You can move this file anywhere you want as long as it
4
+ is in the load path. A spec file is also generated if a spec
5
+ directory exists.
@@ -0,0 +1,16 @@
1
+ module Permitter
2
+ module Generators
3
+ class PermissionGenerator < Rails::Generators::Base
4
+
5
+ source_root File.expand_path("../templates", __FILE__)
6
+
7
+ def generate_permission
8
+ copy_file "permission.rb", "app/models/permission.rb"
9
+ if File.exist?(File.join(destination_root, "spec"))
10
+ copy_file "permission_spec.rb", "spec/models/permission_spec.rb"
11
+ end
12
+ end
13
+
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,22 @@
1
+ class Permission
2
+ include Permitter::Permission
3
+
4
+ def initialize(user)
5
+ # Define permissions for the passed in (current) user. For example:
6
+ #
7
+ # if user
8
+ # allow_all
9
+ # else
10
+ # allow_action [:sessions, :registrations], [:new, :create]
11
+ # allow_action :sessions, :destroy
12
+ # end
13
+ #
14
+ # Here if there is a user he will be able to perform any action on any controller.
15
+ # If someone is not logged in he can only access the registrations and sessions controllers.
16
+ #
17
+ # The first argument to `allow_action` is the controller name being permitted. The second
18
+ # argument is the action they can perform in that controller. Passing an array to either of
19
+ # these will grant permission on each item in the array.
20
+ #
21
+ end
22
+ end
@@ -0,0 +1,17 @@
1
+ require "spec_helper"
2
+ require "permitter/matchers"
3
+
4
+ describe Permission do
5
+ describe "as guest" do
6
+
7
+ subject { Permission.new(nil) }
8
+
9
+ it "allows new sessions" do
10
+ # Define what a guest is allowed to do
11
+ # should allow_action(:sessions, :new)
12
+ # should allow_action(:sessions, :create)
13
+ # should_not allow_action(:sessions, :destroy)
14
+ end
15
+
16
+ end
17
+ end
@@ -0,0 +1,4 @@
1
+ require 'permitter/controller_additions'
2
+ require 'permitter/exceptions'
3
+ require 'permitter/model_additions'
4
+ require 'permitter/permission'
@@ -0,0 +1,43 @@
1
+ require 'active_support/concern'
2
+
3
+ module Permitter
4
+ module ControllerAdditions
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+
9
+ delegate :allowed_action?, to: :current_permissions
10
+ helper_method :allowed_action?
11
+
12
+ delegate :allowed_param?, to: :current_permissions
13
+ helper_method :allowed_param?
14
+
15
+
16
+ private
17
+
18
+ def authorize_user!
19
+ if current_permissions.allowed_action?(params[:controller], params[:action], current_resource)
20
+ current_permissions.permit_params!(params)
21
+ else
22
+ raise Permitter::Unauthorized
23
+ end
24
+ end
25
+
26
+ def current_permissions
27
+ @current_permissions ||= ::Permission.new(current_user)
28
+ end
29
+
30
+ def current_resource
31
+ nil
32
+ end
33
+
34
+ end
35
+
36
+ end
37
+ end
38
+
39
+ if defined? ActionController::Base
40
+ ActionController::Base.class_eval do
41
+ include Permitter::ControllerAdditions
42
+ end
43
+ end