pundit 0.1.0
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/.gitignore +18 -0
- data/.travis.yml +6 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +260 -0
- data/Rakefile +16 -0
- data/lib/generators/pundit/install/USAGE +2 -0
- data/lib/generators/pundit/install/install_generator.rb +11 -0
- data/lib/generators/pundit/install/templates/application_policy.rb +41 -0
- data/lib/generators/pundit/policy/USAGE +8 -0
- data/lib/generators/pundit/policy/policy_generator.rb +11 -0
- data/lib/generators/pundit/policy/templates/policy.rb +9 -0
- data/lib/pundit.rb +60 -0
- data/lib/pundit/policy_finder.rb +49 -0
- data/lib/pundit/rspec.rb +47 -0
- data/lib/pundit/version.rb +3 -0
- data/pundit.gemspec +25 -0
- data/spec/pundit_spec.rb +181 -0
- metadata +147 -0
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 Jonas Nicklas, Elabs AB
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,260 @@
|
|
1
|
+
# Pundit
|
2
|
+
|
3
|
+
Pundit provides a set of helpers which guide you in leveraging regular Ruby
|
4
|
+
classes and object oriented design patterns to build a simple, robust and
|
5
|
+
scaleable authorization system.
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
``` ruby
|
10
|
+
gem "pundit"
|
11
|
+
```
|
12
|
+
|
13
|
+
Include Pundit in your application controller:
|
14
|
+
|
15
|
+
``` ruby
|
16
|
+
class ApplicationController < ActionController::Base
|
17
|
+
include Pundit
|
18
|
+
protect_from_forgery
|
19
|
+
end
|
20
|
+
```
|
21
|
+
|
22
|
+
Optionally, you can run the generator, which will set up an application policy
|
23
|
+
with some useful defaults for you:
|
24
|
+
|
25
|
+
``` sh
|
26
|
+
rails g pundit:install
|
27
|
+
```
|
28
|
+
|
29
|
+
## Policies
|
30
|
+
|
31
|
+
Pundit is focused around the notion of policy classes. We suggest that you put
|
32
|
+
these classes in `app/policies`. This is a simple example:
|
33
|
+
|
34
|
+
``` ruby
|
35
|
+
class PostPolicy
|
36
|
+
attr_reader :user, :post
|
37
|
+
|
38
|
+
def initialize(user, post)
|
39
|
+
@user = user
|
40
|
+
@post = post
|
41
|
+
end
|
42
|
+
|
43
|
+
def create?
|
44
|
+
user.admin? or not post.published?
|
45
|
+
end
|
46
|
+
end
|
47
|
+
```
|
48
|
+
|
49
|
+
As you can see, this is just a plain Ruby class. As a convenience, we can inherit
|
50
|
+
from Struct:
|
51
|
+
|
52
|
+
``` ruby
|
53
|
+
class PostPolicy < Struct.new(:user, :post)
|
54
|
+
def create?
|
55
|
+
user.admin? or not post.published?
|
56
|
+
end
|
57
|
+
end
|
58
|
+
```
|
59
|
+
|
60
|
+
Pundit makes the following assumptions about this class:
|
61
|
+
|
62
|
+
- The class has the same name as some kind of model class, only suffixed
|
63
|
+
with the word "Policy".
|
64
|
+
- The first argument is a user. In your controller, Pundit will call the
|
65
|
+
`current_user` method to retrieve what to send into this argument
|
66
|
+
- The second argument is some kind of model object, whose authorization
|
67
|
+
you want to check. This does not need to be an ActiveRecord or even
|
68
|
+
an ActiveModel object, it can be anything really.
|
69
|
+
- The class implements some kind of query method, in this case `create?`.
|
70
|
+
Usually, this will map to the name of a particular controller action.
|
71
|
+
|
72
|
+
That's it really.
|
73
|
+
|
74
|
+
Supposing that you have an instance of class `Post`, Pundit now lets you do
|
75
|
+
this in your controller:
|
76
|
+
|
77
|
+
``` ruby
|
78
|
+
def create
|
79
|
+
@post = Post.new(params[:post])
|
80
|
+
authorize @post
|
81
|
+
if @post.save
|
82
|
+
redirect_to @post
|
83
|
+
else
|
84
|
+
render :new
|
85
|
+
end
|
86
|
+
end
|
87
|
+
```
|
88
|
+
|
89
|
+
The authorize method automatically infers that `Post` will have a matching
|
90
|
+
`PostPolicy` class, and instantiates this class, handing in the current user
|
91
|
+
and the given record. It then infers from the action name, that it should call
|
92
|
+
`create?` on this instance of the policy. In this case, you can imagine that
|
93
|
+
`authorize` would have done something like this:
|
94
|
+
|
95
|
+
``` ruby
|
96
|
+
raise "not authorized" unless PostPolicy.new(current_user, @post).create?
|
97
|
+
```
|
98
|
+
|
99
|
+
You can pass a second arguent to `authorize` if the name of the permission you
|
100
|
+
want to check doesn't match the action name. For example:
|
101
|
+
|
102
|
+
``` ruby
|
103
|
+
def publish
|
104
|
+
@post = Post.find(params[:id])
|
105
|
+
authorize @post, :update?
|
106
|
+
@post.publish!
|
107
|
+
redirect_to @post
|
108
|
+
end
|
109
|
+
```
|
110
|
+
|
111
|
+
You can easily get a hold of an instance of the policy through the `policy`
|
112
|
+
method in both the view and controller. This is especially useful for
|
113
|
+
conditionally showing links or buttons in the view:
|
114
|
+
|
115
|
+
``` erb
|
116
|
+
<% if policy(@post).create? %>
|
117
|
+
<%= link_to "New post", new_post_path %>
|
118
|
+
<% end %>
|
119
|
+
```
|
120
|
+
|
121
|
+
## Ensuring policies are used
|
122
|
+
|
123
|
+
Pundit adds a method called `verify_authorized` to your controllers. This
|
124
|
+
method will raise an exception if `authorize` has not yet been called. You
|
125
|
+
should run this method in an `after_filter` to ensure that you haven't
|
126
|
+
forgotten to authorize the action. For example:
|
127
|
+
|
128
|
+
``` ruby
|
129
|
+
class ApplicationController < ActionController::Base
|
130
|
+
after_filter :verify_authorized, :except => :index
|
131
|
+
end
|
132
|
+
```
|
133
|
+
|
134
|
+
## Scopes
|
135
|
+
|
136
|
+
Often, you will want to have some kind of view listing records which a
|
137
|
+
particular user has access to. When using Pundit, you are expected to
|
138
|
+
define a class called a policy scope. It can look something like this:
|
139
|
+
|
140
|
+
``` ruby
|
141
|
+
class PostPolicy < Struct.new(:user, :post)
|
142
|
+
class Scope < Struct.new(:user, :scope)
|
143
|
+
def resolve
|
144
|
+
if user.admin?
|
145
|
+
scope
|
146
|
+
else
|
147
|
+
scope.where(:published => true)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
def create?
|
153
|
+
user.admin? or not post.published?
|
154
|
+
end
|
155
|
+
end
|
156
|
+
```
|
157
|
+
|
158
|
+
Pundit makes the following assumptions about this class:
|
159
|
+
|
160
|
+
- The class has the name `Scope` and is nested under the policy class.
|
161
|
+
- The first argument is a user. In your controller, Pundit will call the
|
162
|
+
`current_user` method to retrieve what to send into this argument.
|
163
|
+
- The second argument is a scope of some kind on which to perform some kind of
|
164
|
+
query. It will usually be an ActiveRecord class or a
|
165
|
+
`ActiveRecord::Relation`, but it could be something else entirely.
|
166
|
+
- Instances of this class respond to the method `resolve`, which should return
|
167
|
+
some kind of result which can be iterated over. For ActiveRecord classes,
|
168
|
+
this would usually be an `ActiveRecord::Relation`.
|
169
|
+
|
170
|
+
You can now use this class from your controller via the `policy_scope` method:
|
171
|
+
|
172
|
+
``` ruby
|
173
|
+
def index
|
174
|
+
@posts = policy_scope(Post)
|
175
|
+
end
|
176
|
+
```
|
177
|
+
|
178
|
+
Just as with your policy, this will automatically infer that you want to use
|
179
|
+
the `PostPolicy::Scope` class, it will instantiate this class and call
|
180
|
+
`resolve` on the instance. In this case it is a shortcut for doing:
|
181
|
+
|
182
|
+
``` ruby
|
183
|
+
def index
|
184
|
+
@posts = PostPolicy::Scope.new(current_user, Post).resolve
|
185
|
+
end
|
186
|
+
```
|
187
|
+
|
188
|
+
You can, and are encouraged to, use this method in views:
|
189
|
+
|
190
|
+
``` erb
|
191
|
+
<% policy_scope(@user.posts).each do |post| %>
|
192
|
+
<p><% link_to @post.title, post_path(post) %></p>
|
193
|
+
<% end %>
|
194
|
+
```
|
195
|
+
|
196
|
+
## Just plain old Ruby
|
197
|
+
|
198
|
+
As you can see, Pundit doesn't do anything you couldn't have easily done
|
199
|
+
yourself. It's a very small library, it just provides a few neat helpers.
|
200
|
+
Together these give you the power of building a well structured, fully working
|
201
|
+
authorization system without using any special DSLs or funky syntax or
|
202
|
+
anything.
|
203
|
+
|
204
|
+
Remember that all of the policy and scope classes are just plain Ruby classes,
|
205
|
+
which means you can use the same mechanisms you always use to DRY things up.
|
206
|
+
Encapsulate a set of permissions into a module and include them in multiple
|
207
|
+
policies. Use `alias_method` to make some permissions behave the same as
|
208
|
+
others. Inherit from a base set of permissions. Use metaprogramming if you
|
209
|
+
really have to.
|
210
|
+
|
211
|
+
## Generator
|
212
|
+
|
213
|
+
Use the supplied generator to generate policies:
|
214
|
+
|
215
|
+
``` sh
|
216
|
+
rails g pundit:policy post
|
217
|
+
```
|
218
|
+
|
219
|
+
## Closed systems
|
220
|
+
|
221
|
+
In many applications, only logged in users are really able to do anything. If
|
222
|
+
you're building such a system, it can be kind of cumbersome to check that the
|
223
|
+
user in a policy isn't `nil` for every single permission.
|
224
|
+
|
225
|
+
We suggest that you define a filter that redirects unauthenticated users to the
|
226
|
+
login page. As a secondary defence, if you've defined an ApplicationPolicy, it
|
227
|
+
might be a good idea to raise an exception if somehow an unauthenticated user
|
228
|
+
got through. This way you can fail more gracefully.
|
229
|
+
|
230
|
+
``` ruby
|
231
|
+
class ApplicationPolicy
|
232
|
+
def initialize(user, record)
|
233
|
+
raise Pundit::NotAuthorized, "must be logged in" unless user
|
234
|
+
@user = user
|
235
|
+
@record = record
|
236
|
+
end
|
237
|
+
end
|
238
|
+
```
|
239
|
+
|
240
|
+
## Manually retrieving policies and scopes
|
241
|
+
|
242
|
+
Sometimes you want to retrieve a policy for a record outside the controller or
|
243
|
+
view. For example when you delegate permissions from one policy to another.
|
244
|
+
|
245
|
+
You can easily retrieve policies and scopes like this:
|
246
|
+
|
247
|
+
``` ruby
|
248
|
+
Pundit.policy!(user, post)
|
249
|
+
Pundit.policy(user, post)
|
250
|
+
|
251
|
+
Pundit.policy_scope!(user, Post)
|
252
|
+
Pundit.policy_scope(user, Post)
|
253
|
+
```
|
254
|
+
|
255
|
+
The bang methods will raise an exception if the policy does not exist, whereas
|
256
|
+
those without the bang will return nil.
|
257
|
+
|
258
|
+
# License
|
259
|
+
|
260
|
+
Licensed under the MIT license, see the separate LICENSE.txt file.
|
data/Rakefile
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rspec/core/rake_task'
|
3
|
+
require 'yard'
|
4
|
+
|
5
|
+
desc "Run all examples"
|
6
|
+
RSpec::Core::RakeTask.new(:spec) do |t|
|
7
|
+
#t.rspec_path = 'bin/rspec'
|
8
|
+
t.rspec_opts = %w[--color]
|
9
|
+
end
|
10
|
+
|
11
|
+
YARD::Rake::YardocTask.new do |t|
|
12
|
+
t.files = ['lib/**/*.rb']
|
13
|
+
#t.options = ['--any', '--extra', '--opts'] # optional
|
14
|
+
end
|
15
|
+
|
16
|
+
task :default => [:spec]
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module Pundit
|
2
|
+
module Generators
|
3
|
+
class InstallGenerator < ::Rails::Generators::Base
|
4
|
+
source_root File.expand_path(File.join(File.dirname(__FILE__), 'templates'))
|
5
|
+
|
6
|
+
def copy_application_policy
|
7
|
+
template 'application_policy.rb', 'app/policies/application_policy.rb'
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
class ApplicationPolicy
|
2
|
+
attr_reader :user, :record
|
3
|
+
|
4
|
+
def initialize(user, record)
|
5
|
+
@user = user
|
6
|
+
@record = record
|
7
|
+
end
|
8
|
+
|
9
|
+
def index?
|
10
|
+
scope.exists?
|
11
|
+
end
|
12
|
+
|
13
|
+
def show?
|
14
|
+
scope.where(:id => record.id).exists?
|
15
|
+
end
|
16
|
+
|
17
|
+
def create?
|
18
|
+
false
|
19
|
+
end
|
20
|
+
|
21
|
+
def new?
|
22
|
+
create?
|
23
|
+
end
|
24
|
+
|
25
|
+
def update?
|
26
|
+
false
|
27
|
+
end
|
28
|
+
|
29
|
+
def edit?
|
30
|
+
update?
|
31
|
+
end
|
32
|
+
|
33
|
+
def destroy?
|
34
|
+
false
|
35
|
+
end
|
36
|
+
|
37
|
+
def scope
|
38
|
+
Pundit.policy_scope!(user, record.class)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module Pundit
|
2
|
+
module Generators
|
3
|
+
class PolicyGenerator < ::Rails::Generators::NamedBase
|
4
|
+
source_root File.expand_path(File.join(File.dirname(__FILE__), 'templates'))
|
5
|
+
|
6
|
+
def create_policy
|
7
|
+
template 'policy.rb', File.join('app/policies', class_path, "#{file_name}_policy.rb")
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
data/lib/pundit.rb
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
require "pundit/version"
|
2
|
+
require "pundit/policy_finder"
|
3
|
+
require "active_support/concern"
|
4
|
+
require "active_support/core_ext/string/inflections"
|
5
|
+
require "active_support/core_ext/object/blank"
|
6
|
+
|
7
|
+
module Pundit
|
8
|
+
class NotAuthorizedError < StandardError; end
|
9
|
+
class NotDefinedError < StandardError; end
|
10
|
+
|
11
|
+
extend ActiveSupport::Concern
|
12
|
+
|
13
|
+
class << self
|
14
|
+
def policy_scope(user, scope)
|
15
|
+
policy = PolicyFinder.new(scope).scope
|
16
|
+
policy.new(user, scope).resolve if policy
|
17
|
+
end
|
18
|
+
|
19
|
+
def policy_scope!(user, scope)
|
20
|
+
PolicyFinder.new(scope).scope!.new(user, scope).resolve
|
21
|
+
end
|
22
|
+
|
23
|
+
def policy(user, record)
|
24
|
+
scope = PolicyFinder.new(record).policy
|
25
|
+
scope.new(user, record) if scope
|
26
|
+
end
|
27
|
+
|
28
|
+
def policy!(user, record)
|
29
|
+
PolicyFinder.new(record).policy!.new(user, record)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
included do
|
34
|
+
if respond_to?(:helper_method)
|
35
|
+
helper_method :policy_scope
|
36
|
+
helper_method :policy
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def verify_authorized
|
41
|
+
raise NotAuthorizedError unless @_policy_authorized
|
42
|
+
end
|
43
|
+
|
44
|
+
def authorize(record, query=nil)
|
45
|
+
query ||= params[:action].to_s + "?"
|
46
|
+
@_policy_authorized = true
|
47
|
+
unless policy(record).public_send(query)
|
48
|
+
raise NotAuthorizedError, "not allowed to #{query} this #{record}"
|
49
|
+
end
|
50
|
+
true
|
51
|
+
end
|
52
|
+
|
53
|
+
def policy_scope(scope)
|
54
|
+
Pundit.policy_scope!(current_user, scope)
|
55
|
+
end
|
56
|
+
|
57
|
+
def policy(record)
|
58
|
+
Pundit.policy!(current_user, record)
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Pundit
|
2
|
+
class PolicyFinder
|
3
|
+
attr_reader :object
|
4
|
+
|
5
|
+
def initialize(object)
|
6
|
+
@object = object
|
7
|
+
end
|
8
|
+
|
9
|
+
def name
|
10
|
+
if object.respond_to?(:model_name)
|
11
|
+
object.model_name.to_s
|
12
|
+
elsif object.class.respond_to?(:model_name)
|
13
|
+
object.class.model_name.to_s
|
14
|
+
elsif object.is_a?(Class)
|
15
|
+
object.to_s
|
16
|
+
else
|
17
|
+
object.class.to_s
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def scope
|
22
|
+
scope_name.constantize
|
23
|
+
rescue NameError
|
24
|
+
nil
|
25
|
+
end
|
26
|
+
|
27
|
+
def policy
|
28
|
+
policy_name.constantize
|
29
|
+
rescue NameError
|
30
|
+
nil
|
31
|
+
end
|
32
|
+
|
33
|
+
def scope!
|
34
|
+
scope or raise NotDefinedError, "unable to find scope #{scope_name} for #{object}"
|
35
|
+
end
|
36
|
+
|
37
|
+
def policy!
|
38
|
+
policy or raise NotDefinedError, "unable to find policy #{policy_name} for #{object}"
|
39
|
+
end
|
40
|
+
|
41
|
+
def scope_name
|
42
|
+
"#{name}Policy::Scope"
|
43
|
+
end
|
44
|
+
|
45
|
+
def policy_name
|
46
|
+
"#{name}Policy"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
data/lib/pundit/rspec.rb
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
module Pundit
|
2
|
+
module RSpec
|
3
|
+
module Matchers
|
4
|
+
extend ::RSpec::Matchers::DSL
|
5
|
+
|
6
|
+
matcher :permit do |user, record|
|
7
|
+
match do |policy|
|
8
|
+
permissions.all? { |permission| policy.new(user, record).public_send(permission) }
|
9
|
+
end
|
10
|
+
|
11
|
+
failure_message_for_should do |policy|
|
12
|
+
"Expected #{policy} to grant #{permissions.to_sentence} on #{record} but it didn't"
|
13
|
+
end
|
14
|
+
|
15
|
+
failure_message_for_should_not do |policy|
|
16
|
+
"Expected #{policy} not to grant #{permissions.to_sentence} on #{record} but it did"
|
17
|
+
end
|
18
|
+
|
19
|
+
def permissions
|
20
|
+
example.metadata[:permissions]
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
module DSL
|
26
|
+
def permissions(*list, &block)
|
27
|
+
describe(list.to_sentence, :permissions => list, :caller => caller) { instance_eval(&block) }
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
module PolicyExampleGroup
|
32
|
+
include Pundit::RSpec::Matchers
|
33
|
+
|
34
|
+
def self.included(base)
|
35
|
+
base.metadata[:type] = :policy
|
36
|
+
base.extend Pundit::RSpec::DSL
|
37
|
+
super
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
RSpec.configure do |config|
|
44
|
+
config.include Pundit::RSpec::PolicyExampleGroup, :type => :policy, :example_group => {
|
45
|
+
:file_path => /spec\/policies/
|
46
|
+
}
|
47
|
+
end
|
data/pundit.gemspec
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'pundit/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = "pundit"
|
8
|
+
gem.version = Pundit::VERSION
|
9
|
+
gem.authors = ["Jonas Nicklas", "Elabs AB"]
|
10
|
+
gem.email = ["jonas.nicklas@gmail.com", "dev@elabs.se"]
|
11
|
+
gem.description = %q{Object oriented authorization for Rails applications}
|
12
|
+
gem.summary = %q{OO authorization for Rails}
|
13
|
+
gem.homepage = "http://github.com/elabs/pundit"
|
14
|
+
|
15
|
+
gem.files = `git ls-files`.split($/)
|
16
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
17
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
18
|
+
gem.require_paths = ["lib"]
|
19
|
+
|
20
|
+
gem.add_dependency "rails", "~>3.0"
|
21
|
+
gem.add_development_dependency "rspec", "~>2.0"
|
22
|
+
gem.add_development_dependency "pry"
|
23
|
+
gem.add_development_dependency "rake"
|
24
|
+
gem.add_development_dependency "yard"
|
25
|
+
end
|
data/spec/pundit_spec.rb
ADDED
@@ -0,0 +1,181 @@
|
|
1
|
+
require "pundit"
|
2
|
+
require "pry"
|
3
|
+
require "active_model/naming"
|
4
|
+
|
5
|
+
class PostPolicy < Struct.new(:user, :post)
|
6
|
+
def update?
|
7
|
+
post.user == user
|
8
|
+
end
|
9
|
+
def destroy?
|
10
|
+
false
|
11
|
+
end
|
12
|
+
def show?
|
13
|
+
true
|
14
|
+
end
|
15
|
+
end
|
16
|
+
class PostPolicy::Scope < Struct.new(:user, :scope)
|
17
|
+
def resolve
|
18
|
+
scope.published
|
19
|
+
end
|
20
|
+
end
|
21
|
+
class Post < Struct.new(:user)
|
22
|
+
def self.published
|
23
|
+
:published
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
class CommentPolicy < Struct.new(:user, :comment); end
|
28
|
+
class CommentPolicy::Scope < Struct.new(:user, :scope)
|
29
|
+
def resolve
|
30
|
+
scope
|
31
|
+
end
|
32
|
+
end
|
33
|
+
class Comment; extend ActiveModel::Naming; end
|
34
|
+
|
35
|
+
class Article; end
|
36
|
+
|
37
|
+
describe Pundit do
|
38
|
+
let(:user) { stub }
|
39
|
+
let(:post) { Post.new(user) }
|
40
|
+
let(:comment) { Comment.new }
|
41
|
+
let(:article) { Article.new }
|
42
|
+
let(:controller) { stub(:current_user => user, :params => { :action => "update" }).tap { |c| c.extend(Pundit) } }
|
43
|
+
|
44
|
+
describe ".policy_scope" do
|
45
|
+
it "returns an instantiated policy scope given a plain model class" do
|
46
|
+
Pundit.policy_scope(user, Post).should == :published
|
47
|
+
end
|
48
|
+
|
49
|
+
it "returns an instantiated policy scope given an active model class" do
|
50
|
+
Pundit.policy_scope(user, Comment).should == Comment
|
51
|
+
end
|
52
|
+
|
53
|
+
it "returns nil if the given policy scope can't be found" do
|
54
|
+
Pundit.policy_scope(user, Article).should be_nil
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
describe ".policy_scope!" do
|
59
|
+
it "returns an instantiated policy scope given a plain model class" do
|
60
|
+
Pundit.policy_scope!(user, Post).should == :published
|
61
|
+
end
|
62
|
+
|
63
|
+
it "returns an instantiated policy scope given an active model class" do
|
64
|
+
Pundit.policy_scope!(user, Comment).should == Comment
|
65
|
+
end
|
66
|
+
|
67
|
+
it "throws an exception if the given policy scope can't be found" do
|
68
|
+
expect { Pundit.policy_scope!(user, Article) }.to raise_error(Pundit::NotDefinedError)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
describe ".policy" do
|
73
|
+
it "returns an instantiated policy given a plain model instance" do
|
74
|
+
policy = Pundit.policy(user, post)
|
75
|
+
policy.user.should == user
|
76
|
+
policy.post.should == post
|
77
|
+
end
|
78
|
+
|
79
|
+
it "returns an instantiated policy given an active model instance" do
|
80
|
+
policy = Pundit.policy(user, comment)
|
81
|
+
policy.user.should == user
|
82
|
+
policy.comment.should == comment
|
83
|
+
end
|
84
|
+
|
85
|
+
it "returns an instantiated policy given a plain model class" do
|
86
|
+
policy = Pundit.policy(user, Post)
|
87
|
+
policy.user.should == user
|
88
|
+
policy.post.should == Post
|
89
|
+
end
|
90
|
+
|
91
|
+
it "returns an instantiated policy given an active model class" do
|
92
|
+
policy = Pundit.policy(user, Comment)
|
93
|
+
policy.user.should == user
|
94
|
+
policy.comment.should == Comment
|
95
|
+
end
|
96
|
+
|
97
|
+
it "returns nil if the given policy can't be found" do
|
98
|
+
Pundit.policy(user, article).should be_nil
|
99
|
+
Pundit.policy(user, Article).should be_nil
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
describe ".policy!" do
|
104
|
+
it "returns an instantiated policy given a plain model instance" do
|
105
|
+
policy = Pundit.policy!(user, post)
|
106
|
+
policy.user.should == user
|
107
|
+
policy.post.should == post
|
108
|
+
end
|
109
|
+
|
110
|
+
it "returns an instantiated policy given an active model instance" do
|
111
|
+
policy = Pundit.policy!(user, comment)
|
112
|
+
policy.user.should == user
|
113
|
+
policy.comment.should == comment
|
114
|
+
end
|
115
|
+
|
116
|
+
it "returns an instantiated policy given a plain model class" do
|
117
|
+
policy = Pundit.policy!(user, Post)
|
118
|
+
policy.user.should == user
|
119
|
+
policy.post.should == Post
|
120
|
+
end
|
121
|
+
|
122
|
+
it "returns an instantiated policy given an active model class" do
|
123
|
+
policy = Pundit.policy!(user, Comment)
|
124
|
+
policy.user.should == user
|
125
|
+
policy.comment.should == Comment
|
126
|
+
end
|
127
|
+
|
128
|
+
it "throws an exception if the given policy can't be found" do
|
129
|
+
expect { Pundit.policy!(user, article) }.to raise_error(Pundit::NotDefinedError)
|
130
|
+
expect { Pundit.policy!(user, Article) }.to raise_error(Pundit::NotDefinedError)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
describe "#verify_authorized" do
|
135
|
+
it "does nothing when authorized" do
|
136
|
+
controller.authorize(post)
|
137
|
+
controller.verify_authorized
|
138
|
+
end
|
139
|
+
|
140
|
+
it "raises an exception when not authorized" do
|
141
|
+
expect { controller.verify_authorized }.to raise_error(Pundit::NotAuthorizedError)
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
describe "#authorize" do
|
146
|
+
it "infers the policy name and authorized based on it" do
|
147
|
+
controller.authorize(post).should be_true
|
148
|
+
end
|
149
|
+
|
150
|
+
it "can be given a different permission to check" do
|
151
|
+
controller.authorize(post, :show?).should be_true
|
152
|
+
expect { controller.authorize(post, :destroy?) }.to raise_error(Pundit::NotAuthorizedError)
|
153
|
+
end
|
154
|
+
|
155
|
+
it "raises an error when the permission check fails" do
|
156
|
+
expect { controller.authorize(Post.new) }.to raise_error(Pundit::NotAuthorizedError)
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
describe ".policy" do
|
161
|
+
it "returns an instantiated policy" do
|
162
|
+
policy = controller.policy(post)
|
163
|
+
policy.user.should == user
|
164
|
+
policy.post.should == post
|
165
|
+
end
|
166
|
+
|
167
|
+
it "throws an exception if the given policy can't be found" do
|
168
|
+
expect { controller.policy(article) }.to raise_error(Pundit::NotDefinedError)
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
describe ".policy_scope" do
|
173
|
+
it "returns an instantiated policy scope" do
|
174
|
+
controller.policy_scope(Post).should == :published
|
175
|
+
end
|
176
|
+
|
177
|
+
it "throws an exception if the given policy can't be found" do
|
178
|
+
expect { controller.policy_scope(Article) }.to raise_error(Pundit::NotDefinedError)
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
metadata
ADDED
@@ -0,0 +1,147 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: pundit
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Jonas Nicklas
|
9
|
+
- Elabs AB
|
10
|
+
autorequire:
|
11
|
+
bindir: bin
|
12
|
+
cert_chain: []
|
13
|
+
date: 2012-11-19 00:00:00.000000000 Z
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: rails
|
17
|
+
requirement: !ruby/object:Gem::Requirement
|
18
|
+
none: false
|
19
|
+
requirements:
|
20
|
+
- - ~>
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: '3.0'
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
none: false
|
27
|
+
requirements:
|
28
|
+
- - ~>
|
29
|
+
- !ruby/object:Gem::Version
|
30
|
+
version: '3.0'
|
31
|
+
- !ruby/object:Gem::Dependency
|
32
|
+
name: rspec
|
33
|
+
requirement: !ruby/object:Gem::Requirement
|
34
|
+
none: false
|
35
|
+
requirements:
|
36
|
+
- - ~>
|
37
|
+
- !ruby/object:Gem::Version
|
38
|
+
version: '2.0'
|
39
|
+
type: :development
|
40
|
+
prerelease: false
|
41
|
+
version_requirements: !ruby/object:Gem::Requirement
|
42
|
+
none: false
|
43
|
+
requirements:
|
44
|
+
- - ~>
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '2.0'
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: pry
|
49
|
+
requirement: !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ! '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
type: :development
|
56
|
+
prerelease: false
|
57
|
+
version_requirements: !ruby/object:Gem::Requirement
|
58
|
+
none: false
|
59
|
+
requirements:
|
60
|
+
- - ! '>='
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: '0'
|
63
|
+
- !ruby/object:Gem::Dependency
|
64
|
+
name: rake
|
65
|
+
requirement: !ruby/object:Gem::Requirement
|
66
|
+
none: false
|
67
|
+
requirements:
|
68
|
+
- - ! '>='
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
version: '0'
|
71
|
+
type: :development
|
72
|
+
prerelease: false
|
73
|
+
version_requirements: !ruby/object:Gem::Requirement
|
74
|
+
none: false
|
75
|
+
requirements:
|
76
|
+
- - ! '>='
|
77
|
+
- !ruby/object:Gem::Version
|
78
|
+
version: '0'
|
79
|
+
- !ruby/object:Gem::Dependency
|
80
|
+
name: yard
|
81
|
+
requirement: !ruby/object:Gem::Requirement
|
82
|
+
none: false
|
83
|
+
requirements:
|
84
|
+
- - ! '>='
|
85
|
+
- !ruby/object:Gem::Version
|
86
|
+
version: '0'
|
87
|
+
type: :development
|
88
|
+
prerelease: false
|
89
|
+
version_requirements: !ruby/object:Gem::Requirement
|
90
|
+
none: false
|
91
|
+
requirements:
|
92
|
+
- - ! '>='
|
93
|
+
- !ruby/object:Gem::Version
|
94
|
+
version: '0'
|
95
|
+
description: Object oriented authorization for Rails applications
|
96
|
+
email:
|
97
|
+
- jonas.nicklas@gmail.com
|
98
|
+
- dev@elabs.se
|
99
|
+
executables: []
|
100
|
+
extensions: []
|
101
|
+
extra_rdoc_files: []
|
102
|
+
files:
|
103
|
+
- .gitignore
|
104
|
+
- .travis.yml
|
105
|
+
- Gemfile
|
106
|
+
- LICENSE.txt
|
107
|
+
- README.md
|
108
|
+
- Rakefile
|
109
|
+
- lib/generators/pundit/install/USAGE
|
110
|
+
- lib/generators/pundit/install/install_generator.rb
|
111
|
+
- lib/generators/pundit/install/templates/application_policy.rb
|
112
|
+
- lib/generators/pundit/policy/USAGE
|
113
|
+
- lib/generators/pundit/policy/policy_generator.rb
|
114
|
+
- lib/generators/pundit/policy/templates/policy.rb
|
115
|
+
- lib/pundit.rb
|
116
|
+
- lib/pundit/policy_finder.rb
|
117
|
+
- lib/pundit/rspec.rb
|
118
|
+
- lib/pundit/version.rb
|
119
|
+
- pundit.gemspec
|
120
|
+
- spec/pundit_spec.rb
|
121
|
+
homepage: http://github.com/elabs/pundit
|
122
|
+
licenses: []
|
123
|
+
post_install_message:
|
124
|
+
rdoc_options: []
|
125
|
+
require_paths:
|
126
|
+
- lib
|
127
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
128
|
+
none: false
|
129
|
+
requirements:
|
130
|
+
- - ! '>='
|
131
|
+
- !ruby/object:Gem::Version
|
132
|
+
version: '0'
|
133
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
134
|
+
none: false
|
135
|
+
requirements:
|
136
|
+
- - ! '>='
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '0'
|
139
|
+
requirements: []
|
140
|
+
rubyforge_project:
|
141
|
+
rubygems_version: 1.8.24
|
142
|
+
signing_key:
|
143
|
+
specification_version: 3
|
144
|
+
summary: OO authorization for Rails
|
145
|
+
test_files:
|
146
|
+
- spec/pundit_spec.rb
|
147
|
+
has_rdoc:
|