kant 0.0.2
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.
- checksums.yaml +7 -0
- data/.gitignore +14 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +406 -0
- data/Rakefile +2 -0
- data/kant.gemspec +27 -0
- data/lib/kant/access_denied.rb +4 -0
- data/lib/kant/all.rb +4 -0
- data/lib/kant/all_access.rb +14 -0
- data/lib/kant/controller_mixin.rb +45 -0
- data/lib/kant/no_access.rb +14 -0
- data/lib/kant/policy_access.rb +69 -0
- data/lib/kant/resolvers/active_record.rb +30 -0
- data/lib/kant/rspec/matchers.rb +29 -0
- data/lib/kant/version.rb +3 -0
- data/lib/kant.rb +5 -0
- data/spec/kant/all_access_spec.rb +20 -0
- data/spec/kant/integration_spec.rb +6 -0
- data/spec/kant/no_access_spec.rb +21 -0
- data/spec/kant/policy_access_spec.rb +155 -0
- data/spec/spec_helper.rb +87 -0
- metadata +155 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 8aeeefb188062978e189623f81d217b7988ed158
|
4
|
+
data.tar.gz: b658a15f162e5a88a2f576394ae12398102084aa
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: c034e6d3971a058076c5eed08aefad7adc4620d51a09e59dc790c19bcff21cfc0762b70d8fbbc2b8f769722df251714684a9fd3ebcf6f174fc2cbcbe5de5d15d
|
7
|
+
data.tar.gz: d4c6c7e75190b3064383d344900f36ba6d8164ba971b2113af64ddde0365272c7b31ac78155c601afb7a88be3d082e9c5ce525eef2c463bf36d1de00d9654003
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2015 Mark Przepiora
|
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,406 @@
|
|
1
|
+
# Kant v0.0.2
|
2
|
+
|
3
|
+
Kant is a tiny authorization library for your Ruby (especially Rails and/or
|
4
|
+
ActiveRecord) projects.
|
5
|
+
|
6
|
+
## Overview
|
7
|
+
|
8
|
+
### What Kant does NOT do:
|
9
|
+
|
10
|
+
- Add a scope to every single ActiveRecord model in your application.
|
11
|
+
- Add any magic methods to your controllers that fetch your data for you, or
|
12
|
+
make any assumptions about how you want to do this.
|
13
|
+
- Force you to redefine your authorization logic on every single request.
|
14
|
+
- Depend on Rails/ActiveRecord--but if you do use these, there's a tiny bit of
|
15
|
+
magic available to you if you want to use it.
|
16
|
+
|
17
|
+
### What Kant does:
|
18
|
+
|
19
|
+
- Very little.
|
20
|
+
- Allows you to pick and choose how much of it you want to use.
|
21
|
+
- Defines a simple interface (two methods) that your `AccessControl` class
|
22
|
+
should implement.
|
23
|
+
- Provides two simple access control classes (`NoAccess` and `AllAccess`) you
|
24
|
+
might want to use for unauthenticated users and admins respectively.
|
25
|
+
- For typical use cases, Kant gives you a `PolicyAccess` class which allows you
|
26
|
+
to split up your authorization logic into various `FooPolicy` classes, one
|
27
|
+
for each of your models. This class uses a minimal amount of magic to work
|
28
|
+
with `ActiveRecord` models out of the box, but you can extend it easily to
|
29
|
+
use any other ORM.
|
30
|
+
- Provides a module you can include in your Rails controllers which gives you
|
31
|
+
an interface very similar to the one you might be used to if you are
|
32
|
+
currently using CanCanCan.
|
33
|
+
|
34
|
+
### Kant's philosophy
|
35
|
+
|
36
|
+
There are two broad classes of actions you probably need to authorize in your
|
37
|
+
application:
|
38
|
+
|
39
|
+
1. Single-record access: namely, can a user perform an action on a given
|
40
|
+
record? (Yes or No).
|
41
|
+
2. Record index access of some sort: namely, give me a list of the records the
|
42
|
+
user is allowed to access.
|
43
|
+
|
44
|
+
An `AccessControl` class (you might know this as an `Ability` class in CanCan)
|
45
|
+
is any plain-old Ruby class that is instantiated with a single argument (your
|
46
|
+
user object), and implements two methods: (1) `can?(action, object)`, which
|
47
|
+
returns true or false for any action and object (typically a symbol and a
|
48
|
+
record), and (2) `accessible(action, scope)` which takes an action and a scope
|
49
|
+
of some kind (typically an ActiveRecord scope) and returns a new scope of which
|
50
|
+
records the user is allowed to access.
|
51
|
+
|
52
|
+
Everything else in Kant is either a very simple API on top of this interface to
|
53
|
+
make your life easier when using it in a typical Rails app, or an
|
54
|
+
implementation of an access control class which might work well in a typical
|
55
|
+
Rails app. However, you can use as little or as much of Kant as you want, and
|
56
|
+
it certainly doesn't require Rails at all.
|
57
|
+
|
58
|
+
## Installation
|
59
|
+
|
60
|
+
Add this line to your application's Gemfile:
|
61
|
+
|
62
|
+
```ruby
|
63
|
+
gem 'kant', require: false
|
64
|
+
```
|
65
|
+
|
66
|
+
And then execute:
|
67
|
+
|
68
|
+
$ bundle
|
69
|
+
|
70
|
+
## Usage
|
71
|
+
|
72
|
+
### Controller Mixin
|
73
|
+
|
74
|
+
Kant provides a `Kant::ControllerMixin` module that you can include in your
|
75
|
+
ApplicationController if you wish. It adds a couple of methods:
|
76
|
+
|
77
|
+
- A `current_access_control` method, which simply returns
|
78
|
+
`AccessControl.new(current_user)` (a class which are you are expected to
|
79
|
+
provide). You can override this if you need to, for example, choose which
|
80
|
+
access control class to use based on the current user's role. See the source
|
81
|
+
code for an example.
|
82
|
+
- It delegtes `can?(...)` and `accessible(...)` to `current_access_control`.
|
83
|
+
- It adds an `authorize!(...)` method, which delegates to `can?(...)`, being a
|
84
|
+
no-op if `can?` returns true, but raising a `Kant::AccessDenied` exception if
|
85
|
+
`can?` returns false.
|
86
|
+
|
87
|
+
Be sure to require what you need! Example:
|
88
|
+
|
89
|
+
```ruby
|
90
|
+
require 'kant/controller_mixin'
|
91
|
+
|
92
|
+
class ApplicationController < ActionController::Base
|
93
|
+
include Kant::ControllerMixin
|
94
|
+
|
95
|
+
# ...
|
96
|
+
end
|
97
|
+
```
|
98
|
+
|
99
|
+
### Basic All or Nothing Access Controls
|
100
|
+
|
101
|
+
If you are using ActiveRecord, Kant provides `Kant::AllAccess` and
|
102
|
+
`Kant::NoAccess` which you can use for your admins and unauthenticated users
|
103
|
+
respectively.
|
104
|
+
|
105
|
+
`AllAccess` returns true for every `can?` query, and returns `scope.all` for
|
106
|
+
every `accessible` query. In other words, everything is permitted.
|
107
|
+
|
108
|
+
`NoAccess` returns false for every `can?` query, and returns `scope.none` for
|
109
|
+
every `accessible` query, denying all access.
|
110
|
+
|
111
|
+
### Policy-based Access Controls
|
112
|
+
|
113
|
+
While you could implement your own access control class from scratch, you
|
114
|
+
probably just have a typical Rails app in which you just want to implement
|
115
|
+
per-model, and per-action authorization logic. For this, there's
|
116
|
+
`Kant::PolicyAccess`.
|
117
|
+
|
118
|
+
This is the only part of Kant that contains magic, and assumes you are using
|
119
|
+
ActiveRecord, but we'll explain the magic entirely with an example:
|
120
|
+
|
121
|
+
When a `PolicyAccess` is queried with `can?(:update, foo)`, where `foo` is an
|
122
|
+
instance of a class named `Foo`, it will look for a class/module named
|
123
|
+
`FooPolicy`, and a class method on it called `can_update?(record, user)`. It
|
124
|
+
simply delegates to this function.
|
125
|
+
|
126
|
+
For example,
|
127
|
+
|
128
|
+
```ruby
|
129
|
+
module FooPolicy
|
130
|
+
def self.can_update?(foo, user)
|
131
|
+
foo.owner_id == user.id
|
132
|
+
end
|
133
|
+
end
|
134
|
+
```
|
135
|
+
|
136
|
+
If either `FooPolicy` does not exist, or if `can_update?` is undefined, `can?`
|
137
|
+
will simply return false.
|
138
|
+
|
139
|
+
When a `PolicyAccess` is queried with `accessible(:read, Foo)` where `Foo` is
|
140
|
+
an ActiveRecord model class (or a scope of that model), Kant will again look
|
141
|
+
for `FooPolicy`, this time with an instance method called
|
142
|
+
`readable(scope, user)`, which will be called with `readable(Foo, user)`.
|
143
|
+
|
144
|
+
If either the class or method doesn't exist, `Foo.none` will be returned.
|
145
|
+
|
146
|
+
For example,
|
147
|
+
|
148
|
+
```ruby
|
149
|
+
module FooPolicy
|
150
|
+
def self.readable(foos, user)
|
151
|
+
foos.where(owner_id: user.id)
|
152
|
+
end
|
153
|
+
end
|
154
|
+
```
|
155
|
+
|
156
|
+
There is one added bonus to keep in mind. If your policy class implements
|
157
|
+
`readable` but not `can_read?`, and if `PolicyAccess` is queried with
|
158
|
+
`can?(:read, foo)`, then it will return,
|
159
|
+
|
160
|
+
```ruby
|
161
|
+
FooPolicy.readable(Foo, user).where(id: foo.id).any?
|
162
|
+
```
|
163
|
+
|
164
|
+
Therefore, you can typically implement just a scope policy for an action and
|
165
|
+
let the single-object policy be generated automatically. You may want to
|
166
|
+
implement (in this example) `can_read?(...)` anyway, since you can avoid an
|
167
|
+
extra SQL query by simply comparing `foo.owner_id == user.id`. However, in
|
168
|
+
real-world apps, authorizing an action often requires executing a query of some
|
169
|
+
sort anyway, so it's your choice.
|
170
|
+
|
171
|
+
Since scope and object policies are just methods, you can alias them. No magic
|
172
|
+
required. For example,
|
173
|
+
|
174
|
+
```ruby
|
175
|
+
module FooPolicy
|
176
|
+
def self.can_read?(foo, user)
|
177
|
+
foo.owner_id == user.id
|
178
|
+
end
|
179
|
+
|
180
|
+
class << self
|
181
|
+
alias_method :can_update?, :can_read?
|
182
|
+
alias_method :can_destroy?, :can_read?
|
183
|
+
end
|
184
|
+
end
|
185
|
+
```
|
186
|
+
|
187
|
+
## Okay Practices
|
188
|
+
|
189
|
+
### Controller Params
|
190
|
+
|
191
|
+
This isn't enforced by Kant at all, but you could define your allowed
|
192
|
+
controller params inside your policy classes. For example:
|
193
|
+
|
194
|
+
```ruby
|
195
|
+
module FooPolicy
|
196
|
+
# ...
|
197
|
+
|
198
|
+
def self.create_params(params)
|
199
|
+
params.require(:foo).permit(:name)
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
class FoosController < ApplicationController
|
204
|
+
def create_params
|
205
|
+
FooPolicy.create_params(params)
|
206
|
+
end
|
207
|
+
end
|
208
|
+
```
|
209
|
+
|
210
|
+
### Create and Update Policies
|
211
|
+
|
212
|
+
For defining your `can_create?` and `can_update?` policies, it's probably a
|
213
|
+
good idea to perform the following order of actions in your controllers:
|
214
|
+
|
215
|
+
```ruby
|
216
|
+
foo.assign_attributes(update_params)
|
217
|
+
authorize! :update, foo
|
218
|
+
foo.save!
|
219
|
+
```
|
220
|
+
|
221
|
+
This way, in your `can_update?` policy you can check foo's `#changes`, etc.,
|
222
|
+
methods to see what was changed in case a user might only be allowed to modify
|
223
|
+
some fields but not others, or only make certain kinds of changes.
|
224
|
+
|
225
|
+
### DRYing Up Your Controllers
|
226
|
+
|
227
|
+
You might be worried about missing out on CanCan's magical controller methods
|
228
|
+
that fetch and authorize your records for you.
|
229
|
+
|
230
|
+
But why not instead just define a method like this in `ApplicationController`?
|
231
|
+
|
232
|
+
```ruby
|
233
|
+
def find_and_authorize(model_class, id, action)
|
234
|
+
record = model_class.find(id)
|
235
|
+
authorize! action, record
|
236
|
+
record
|
237
|
+
end
|
238
|
+
```
|
239
|
+
|
240
|
+
Now you can just,
|
241
|
+
|
242
|
+
```ruby
|
243
|
+
foo = find_and_authorize(Foo, params[:id], :read)
|
244
|
+
```
|
245
|
+
|
246
|
+
You could define something analogous for updating and creating records,
|
247
|
+
|
248
|
+
```ruby
|
249
|
+
def authorize_and_create(model_class, params)
|
250
|
+
record = model_class.new(params)
|
251
|
+
authorize! :create, record
|
252
|
+
[record.save, record]
|
253
|
+
end
|
254
|
+
```
|
255
|
+
|
256
|
+
And in your actions use,
|
257
|
+
|
258
|
+
```ruby
|
259
|
+
success, foo = authorize_and_create(Foo, create_params)
|
260
|
+
|
261
|
+
if success
|
262
|
+
# ...
|
263
|
+
else
|
264
|
+
# ...
|
265
|
+
end
|
266
|
+
```
|
267
|
+
|
268
|
+
## Complete-ish Example
|
269
|
+
|
270
|
+
```ruby
|
271
|
+
# config/application.rb
|
272
|
+
# ...
|
273
|
+
config.autoload_paths += %W(#{config.root}/authorization)
|
274
|
+
# ...
|
275
|
+
|
276
|
+
# app/controllers/application_controller.rb
|
277
|
+
require 'kant/all'
|
278
|
+
|
279
|
+
class ApplicationController < ActionController::Base
|
280
|
+
include Kant::ControllerMixin
|
281
|
+
|
282
|
+
def current_access_control
|
283
|
+
if !current_user
|
284
|
+
Kant::NoAccess.new(nil)
|
285
|
+
elsif current_user.admin?
|
286
|
+
Kant::AllAccess.new(nil)
|
287
|
+
else
|
288
|
+
Kant::PolicyAccess.new(current_user, policies_module: Policies)
|
289
|
+
end
|
290
|
+
end
|
291
|
+
end
|
292
|
+
|
293
|
+
# app/authorization/policies/foo_policy.rb
|
294
|
+
module Policies
|
295
|
+
module FooPolicy
|
296
|
+
def self.readable(foos, user)
|
297
|
+
foos.where(user_id: user.id)
|
298
|
+
end
|
299
|
+
end
|
300
|
+
end
|
301
|
+
|
302
|
+
# app/controllers/foos_controller.rb
|
303
|
+
class FoosController < ApplicationController
|
304
|
+
def index
|
305
|
+
foos = accessible(:read, Foo)
|
306
|
+
render json: foos
|
307
|
+
end
|
308
|
+
|
309
|
+
def show
|
310
|
+
foo = Foo.find(params[:id])
|
311
|
+
authorize! :read, foo
|
312
|
+
render json: foo
|
313
|
+
end
|
314
|
+
end
|
315
|
+
```
|
316
|
+
|
317
|
+
## RSpec
|
318
|
+
|
319
|
+
Kant has RSpec matchers if you want to use them. Example:
|
320
|
+
|
321
|
+
```ruby
|
322
|
+
# spec_helper.rb
|
323
|
+
require 'kant/rspec/matchers'
|
324
|
+
|
325
|
+
# foo_spec.rb
|
326
|
+
describe Foo
|
327
|
+
it "bars" do
|
328
|
+
# ... some setup ...
|
329
|
+
access_control = AccessControl.new(user)
|
330
|
+
expect(access_control).to be_able_to(:read, foo)
|
331
|
+
end
|
332
|
+
end
|
333
|
+
```
|
334
|
+
|
335
|
+
## But is it good?
|
336
|
+
|
337
|
+
It's small, simple, and it works. I use it in production. So maybe?
|
338
|
+
|
339
|
+
## Why should I use this instead of CanCan?
|
340
|
+
|
341
|
+
First of all, this library is tiny.
|
342
|
+
|
343
|
+
```bash
|
344
|
+
$ cat lib/kant/**/*.rb | wc -l
|
345
|
+
212
|
346
|
+
```
|
347
|
+
|
348
|
+
And this includes the RSpec matcher definition which you won't even use in
|
349
|
+
production. Excluding that, Kant is about 170 lines of code.
|
350
|
+
|
351
|
+
Compare this to CanCanCan:
|
352
|
+
|
353
|
+
```bash
|
354
|
+
$ cat lib/**/*.rb | wc -l
|
355
|
+
1849
|
356
|
+
```
|
357
|
+
|
358
|
+
Also, it's fast:
|
359
|
+
|
360
|
+
**Warning: Do not trust these benchmarks. Perform your own measurements on your
|
361
|
+
own application.**
|
362
|
+
|
363
|
+
A problem with CanCan as your application grows is that it expects you to
|
364
|
+
define *all* of your abilities upon initialization, even if the request only
|
365
|
+
checks a single one. This is fine if you only have a couple of models, or if
|
366
|
+
your scopes are simple, but if your application has a couple dozen models, this
|
367
|
+
can translate into a significant overhead.
|
368
|
+
|
369
|
+
Here is a simple benchmark I executed on production when we still used CanCan:
|
370
|
+
|
371
|
+
```ruby
|
372
|
+
require 'benchmark'
|
373
|
+
u = User.first
|
374
|
+
|
375
|
+
puts Benchmark.measure {
|
376
|
+
10000.times { Ability.new(u) }
|
377
|
+
}
|
378
|
+
# => 48.510000 0.990000 49.500000 ( 70.699697)
|
379
|
+
```
|
380
|
+
|
381
|
+
That's almost 50 seconds of CPU time simply to instantiate 10,000 objects.
|
382
|
+
|
383
|
+
Here is the benchmark from the application after it was refactored to use Kant:
|
384
|
+
|
385
|
+
```ruby
|
386
|
+
require 'benchmark'
|
387
|
+
u = User.first
|
388
|
+
|
389
|
+
puts Benchmark.measure {
|
390
|
+
10000.times { AccessControl.new(u) }
|
391
|
+
}
|
392
|
+
# => 0.060000 0.010000 0.070000 ( 0.169156)
|
393
|
+
```
|
394
|
+
|
395
|
+
That's a *slight* difference. For us, this translates into a savings of roughly
|
396
|
+
50 seconds of CPU time for every 10,000 requests.
|
397
|
+
|
398
|
+
**Perform your own benchmarks.**
|
399
|
+
|
400
|
+
## Contributing
|
401
|
+
|
402
|
+
1. Fork it ( https://github.com/markprzepiora/kant/fork )
|
403
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
404
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
405
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
406
|
+
5. Create a new Pull Request
|
data/Rakefile
ADDED
data/kant.gemspec
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'kant/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "kant"
|
8
|
+
spec.version = Kant::VERSION
|
9
|
+
spec.authors = ["Mark Przepiora"]
|
10
|
+
spec.email = ["mark.przepiora@gmail.com"]
|
11
|
+
spec.summary = %q{A tiny, non-magical authorization library}
|
12
|
+
spec.description = %q{Kant is a tiny authorization library for your Ruby (especially Rails and/or ActiveRecord) projects.}
|
13
|
+
spec.homepage = "https://github.com/markprzepiora/kant"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.7"
|
22
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
23
|
+
spec.add_development_dependency "rspec", "~> 3.1.0"
|
24
|
+
spec.add_development_dependency "with_model", "~> 1.2.1"
|
25
|
+
spec.add_development_dependency "sqlite3", "~> 1.3.10"
|
26
|
+
spec.add_development_dependency "activerecord", ">= 3.2"
|
27
|
+
end
|
data/lib/kant/all.rb
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'kant/access_denied'
|
2
|
+
|
3
|
+
module Kant
|
4
|
+
module ControllerMixin
|
5
|
+
# By default, Kant expects an AccessControl class to exist. Override this
|
6
|
+
# method if you need more complicated logic here. A typical implementation
|
7
|
+
# might be:
|
8
|
+
#
|
9
|
+
# def current_access_control
|
10
|
+
# @current_access_control ||=
|
11
|
+
# if !current_user
|
12
|
+
# Kant::NoAccess.new(nil)
|
13
|
+
# elsif current_user.admin?
|
14
|
+
# Kant::AllAccess.new(current_user)
|
15
|
+
# else
|
16
|
+
# AccessControl.new(current_user)
|
17
|
+
# end
|
18
|
+
# end
|
19
|
+
def current_access_control
|
20
|
+
@current_access_control ||= AccessControl.new(current_user)
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
# Delegates to current_access_control
|
26
|
+
def can?(*args)
|
27
|
+
current_access_control.can?(*args)
|
28
|
+
end
|
29
|
+
|
30
|
+
# Delegates to current_access_control
|
31
|
+
def accessible(*args)
|
32
|
+
current_access_control.accessible(*args)
|
33
|
+
end
|
34
|
+
|
35
|
+
# If `can?(action, object)` is true, then this is a no-op. If on the other
|
36
|
+
# hand that value is false, this raises a Kant::AccessDenied exception.
|
37
|
+
def authorize!(action, object)
|
38
|
+
if can?(action, object)
|
39
|
+
true
|
40
|
+
else
|
41
|
+
raise Kant::AccessDenied, "You are not permitted to #{action} this record."
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'kant/resolvers/active_record'
|
2
|
+
|
3
|
+
module Kant
|
4
|
+
class PolicyAccess
|
5
|
+
include Kant::Resolvers::ActiveRecord
|
6
|
+
|
7
|
+
attr_accessor :user
|
8
|
+
|
9
|
+
def initialize(user, policies_module: nil)
|
10
|
+
@user = user
|
11
|
+
@policies_module = policies_module || Kernel
|
12
|
+
end
|
13
|
+
|
14
|
+
# Delegates to an appropriate Policy module. For example,
|
15
|
+
#
|
16
|
+
# Ability.new(user).can?(:read, Foo.first)
|
17
|
+
#
|
18
|
+
# will return
|
19
|
+
#
|
20
|
+
# FooPolicy.can_read?(Foo.first, user)
|
21
|
+
def can?(action, object)
|
22
|
+
method_eh = "can_#{action}?"
|
23
|
+
abilities = resolve_object(object)
|
24
|
+
_scope_method = scope_method(abilities, action)
|
25
|
+
model_class = object.class
|
26
|
+
|
27
|
+
if abilities.respond_to?(method_eh)
|
28
|
+
abilities.send(method_eh, object, user)
|
29
|
+
elsif _scope_method && object.id
|
30
|
+
abilities.send(_scope_method, model_class, user).where(id: object.id).any?
|
31
|
+
else
|
32
|
+
false
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# Example:
|
37
|
+
#
|
38
|
+
# ability.accessible(:read, Content)
|
39
|
+
# # => a Content scope
|
40
|
+
def accessible(action, scope)
|
41
|
+
abilities = resolve_scope(scope)
|
42
|
+
_scope_method = scope_method(abilities, action)
|
43
|
+
|
44
|
+
if _scope_method
|
45
|
+
abilities.send(_scope_method, scope, user)
|
46
|
+
else
|
47
|
+
scope.none
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def scope_method(abilities, action)
|
54
|
+
regular_name = "#{action}able"
|
55
|
+
alternate_name = begin
|
56
|
+
first, *rest = action.to_s.split('_')
|
57
|
+
"#{first}able_#{rest.join('_')}"
|
58
|
+
end
|
59
|
+
|
60
|
+
if abilities.respond_to?(regular_name)
|
61
|
+
regular_name
|
62
|
+
elsif abilities.respond_to?(alternate_name)
|
63
|
+
alternate_name
|
64
|
+
else
|
65
|
+
nil
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Kant
|
2
|
+
module Resolvers
|
3
|
+
module ActiveRecord
|
4
|
+
private
|
5
|
+
|
6
|
+
attr_reader :policies_module
|
7
|
+
|
8
|
+
def resolve_object(object_or_class)
|
9
|
+
klass =
|
10
|
+
if object_or_class.is_a?(Class)
|
11
|
+
object_or_class
|
12
|
+
else
|
13
|
+
object_or_class.class
|
14
|
+
end
|
15
|
+
|
16
|
+
resolve(klass.name)
|
17
|
+
end
|
18
|
+
|
19
|
+
def resolve_scope(scope)
|
20
|
+
resolve(scope.all.model.name)
|
21
|
+
end
|
22
|
+
|
23
|
+
def resolve(name)
|
24
|
+
policies_module.const_get("#{name}Policy")
|
25
|
+
rescue NameError
|
26
|
+
nil
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# These matchers are taken directly from CanCanCan:
|
2
|
+
# https://github.com/CanCanCommunity/cancancan/blob/develop/lib/cancan/matchers.rb
|
3
|
+
|
4
|
+
rspec_module = defined?(RSpec::Core) ? 'RSpec' : 'Spec' # for RSpec 1 compatability
|
5
|
+
|
6
|
+
if rspec_module == 'RSpec'
|
7
|
+
require 'rspec/core'
|
8
|
+
require 'rspec/expectations'
|
9
|
+
end
|
10
|
+
|
11
|
+
Kernel.const_get(rspec_module)::Matchers.define :be_able_to do |*args|
|
12
|
+
match do |ability|
|
13
|
+
ability.can?(*args)
|
14
|
+
end
|
15
|
+
|
16
|
+
# Check that RSpec is < 2.99
|
17
|
+
if !respond_to?(:failure_message) && respond_to?(:failure_message_for_should)
|
18
|
+
alias :failure_message :failure_message_for_should
|
19
|
+
alias :failure_message_when_negated :failure_message_for_should_not
|
20
|
+
end
|
21
|
+
|
22
|
+
failure_message do |ability|
|
23
|
+
"expected to be able to #{args.map(&:inspect).join(" ")}"
|
24
|
+
end
|
25
|
+
|
26
|
+
failure_message_when_negated do |ability|
|
27
|
+
"expected not to be able to #{args.map(&:inspect).join(" ")}"
|
28
|
+
end
|
29
|
+
end
|
data/lib/kant/version.rb
ADDED
data/lib/kant.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'kant/all_access'
|
3
|
+
|
4
|
+
describe Kant::AllAccess do
|
5
|
+
let(:user) { double("user") }
|
6
|
+
subject(:all_access) { Kant::AllAccess.new(user) }
|
7
|
+
|
8
|
+
it "it can do anything" do
|
9
|
+
foo = double("foo")
|
10
|
+
|
11
|
+
expect(all_access).to be_able_to(:bar, foo)
|
12
|
+
end
|
13
|
+
|
14
|
+
it "it has access to everything" do
|
15
|
+
scope = double("scope")
|
16
|
+
expect(scope).to receive(:all).and_return(scope)
|
17
|
+
|
18
|
+
expect(all_access.accessible(:foo, scope)).to eq(scope)
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'kant/no_access'
|
3
|
+
|
4
|
+
describe Kant::NoAccess do
|
5
|
+
let(:user) { double("user") }
|
6
|
+
subject(:no_access) { Kant::NoAccess.new(user) }
|
7
|
+
|
8
|
+
it "it can't do anything" do
|
9
|
+
foo = double("foo")
|
10
|
+
|
11
|
+
expect(no_access).not_to be_able_to(:bar, foo)
|
12
|
+
end
|
13
|
+
|
14
|
+
it "it has access to nothing" do
|
15
|
+
scope = double("scope")
|
16
|
+
none = double("none")
|
17
|
+
expect(scope).to receive(:none).and_return(none)
|
18
|
+
|
19
|
+
expect(no_access.accessible(:foo, scope)).to eq(none)
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,155 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'kant/policy_access'
|
3
|
+
|
4
|
+
describe Kant::PolicyAccess do
|
5
|
+
setup_models
|
6
|
+
|
7
|
+
describe "#can?" do
|
8
|
+
let(:user) { User.create!(email: 'foo@bar.com') }
|
9
|
+
subject(:policy_access) { Kant::PolicyAccess.new(user) }
|
10
|
+
|
11
|
+
it "uses FooPolicy to authorize Foos" do
|
12
|
+
todo = Todo.create
|
13
|
+
bell = double("bell")
|
14
|
+
expect(bell).to receive(:ring).with(todo, user).and_return(nil)
|
15
|
+
|
16
|
+
# module TodoPolicy
|
17
|
+
# def self.can_tickle?(todo, user)
|
18
|
+
# bell.ring(todo, user)
|
19
|
+
# "foo"
|
20
|
+
# end
|
21
|
+
# end
|
22
|
+
todo_policy = Class.new do
|
23
|
+
define_singleton_method(:can_tickle?) do |todo, user|
|
24
|
+
bell.ring(todo, user)
|
25
|
+
"foo"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
stub_const("TodoPolicy", todo_policy)
|
30
|
+
|
31
|
+
expect(policy_access.can?(:tickle, todo)).to eq("foo")
|
32
|
+
expect(policy_access.can?(:read, todo)).to eq(false)
|
33
|
+
end
|
34
|
+
|
35
|
+
it "uses FooPolicy to authorize Foo itself" do
|
36
|
+
bell = double("bell")
|
37
|
+
expect(bell).to receive(:ring).with(Todo, user).and_return(nil)
|
38
|
+
|
39
|
+
# module TodoPolicy
|
40
|
+
# def self.can_tickle?(todo, user)
|
41
|
+
# bell.ring(todo, user)
|
42
|
+
# "foo"
|
43
|
+
# end
|
44
|
+
# end
|
45
|
+
todo_policy = Class.new do
|
46
|
+
define_singleton_method(:can_tickle?) do |todo, user|
|
47
|
+
bell.ring(todo, user)
|
48
|
+
"foo"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
stub_const("TodoPolicy", todo_policy)
|
53
|
+
|
54
|
+
expect(policy_access.can?(:tickle, Todo)).to eq("foo")
|
55
|
+
end
|
56
|
+
|
57
|
+
it "returns false for an undefined action" do
|
58
|
+
todo = Todo.create
|
59
|
+
stub_const("TodoPolicy", Class.new)
|
60
|
+
|
61
|
+
expect(policy_access.can?(:tickle, todo)).to eq(false)
|
62
|
+
end
|
63
|
+
|
64
|
+
it "returns false if FooPolicy does not exist" do
|
65
|
+
todo = Todo.create
|
66
|
+
expect(policy_access.can?(:tickle, todo)).to eq(false)
|
67
|
+
end
|
68
|
+
|
69
|
+
it "uses an *able scope method if no can_*? method exists" do
|
70
|
+
# module TodoPolicy
|
71
|
+
# def self.readable(todos, user)
|
72
|
+
# todos.where(completed: true)
|
73
|
+
# end
|
74
|
+
# end
|
75
|
+
todo_policy = Class.new do
|
76
|
+
define_singleton_method(:readable) do |todos, user|
|
77
|
+
todos.where(completed: true)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
stub_const("TodoPolicy", todo_policy)
|
82
|
+
|
83
|
+
complete_todo = Todo.create!(completed: true)
|
84
|
+
incomplete_todo = Todo.create!(completed: false)
|
85
|
+
|
86
|
+
expect(policy_access).to be_able_to(:read, complete_todo)
|
87
|
+
expect(policy_access).not_to be_able_to(:read, incomplete_todo)
|
88
|
+
|
89
|
+
expect(policy_access).not_to be_able_to(:tickle, complete_todo)
|
90
|
+
expect(policy_access).not_to be_able_to(:tickle, incomplete_todo)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
describe "#accessible" do
|
95
|
+
let(:user) { User.create!(email: 'foo@bar.com') }
|
96
|
+
subject(:policy_access) { Kant::PolicyAccess.new(user) }
|
97
|
+
|
98
|
+
it "delegates to a Policy module" do
|
99
|
+
# module TodoPolicy
|
100
|
+
# def self.readable(todos, user)
|
101
|
+
# todos.where(completed: true)
|
102
|
+
# end
|
103
|
+
# end
|
104
|
+
todo_policy = Class.new do
|
105
|
+
define_singleton_method(:readable) do |todos, user|
|
106
|
+
todos.where(completed: true)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
stub_const("TodoPolicy", todo_policy)
|
111
|
+
|
112
|
+
complete_todo = Todo.create!(completed: true)
|
113
|
+
incomplete_todo = Todo.create!(completed: false)
|
114
|
+
|
115
|
+
expect(policy_access.accessible(:read, Todo)).to eq([complete_todo])
|
116
|
+
expect(policy_access.accessible(:read, Todo.where(completed: false))).to eq([])
|
117
|
+
end
|
118
|
+
|
119
|
+
it "returns an empty scope when no scope method is defined" do
|
120
|
+
stub_const("TodoPolicy", Class.new)
|
121
|
+
|
122
|
+
complete_todo = Todo.create!(completed: true)
|
123
|
+
incomplete_todo = Todo.create!(completed: false)
|
124
|
+
|
125
|
+
expect(policy_access.accessible(:read, Todo)).to eq(Todo.none)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
describe "the policies_module option in initializer" do
|
130
|
+
let(:user) { User.create!(email: 'foo@bar.com') }
|
131
|
+
|
132
|
+
it "can be specified to namespace policies" do
|
133
|
+
# module Policies::TodoPolicy
|
134
|
+
# def self.readable(todos, user)
|
135
|
+
# todos.where(completed: true)
|
136
|
+
# end
|
137
|
+
# end
|
138
|
+
todo_policy = Class.new do
|
139
|
+
define_singleton_method(:readable) do |todos, user|
|
140
|
+
todos.where(completed: true)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
stub_const("Policies", Module.new)
|
145
|
+
stub_const("Policies::TodoPolicy", todo_policy)
|
146
|
+
|
147
|
+
policy_access = Kant::PolicyAccess.new(user, policies_module: Policies)
|
148
|
+
|
149
|
+
complete_todo = Todo.create!(completed: true)
|
150
|
+
incomplete_todo = Todo.create!(completed: false)
|
151
|
+
|
152
|
+
expect(policy_access.accessible(:read, Todo)).to eq([complete_todo])
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler/setup'
|
3
|
+
|
4
|
+
require 'active_record'
|
5
|
+
require 'sqlite3'
|
6
|
+
require 'with_model'
|
7
|
+
require 'kant/rspec/matchers'
|
8
|
+
require 'kant'
|
9
|
+
|
10
|
+
ActiveRecord::Base.establish_connection adapter: "sqlite3", database: ":memory:"
|
11
|
+
|
12
|
+
RSpec.configure do |config|
|
13
|
+
config.extend WithModel
|
14
|
+
end
|
15
|
+
|
16
|
+
def setup_models
|
17
|
+
with_model :User do
|
18
|
+
table do |t|
|
19
|
+
t.string "email"
|
20
|
+
end
|
21
|
+
|
22
|
+
model do
|
23
|
+
has_many :projects_as_owner,
|
24
|
+
:class_name => 'Project',
|
25
|
+
:foreign_key => :owner_id,
|
26
|
+
:inverse_of => :owner
|
27
|
+
|
28
|
+
has_many :authored_todos,
|
29
|
+
:foreign_key => :author_id,
|
30
|
+
:inverse_of => :author,
|
31
|
+
:class_name => 'Todo'
|
32
|
+
|
33
|
+
has_and_belongs_to_many :projects_as_developer,
|
34
|
+
:class_name => 'Project',
|
35
|
+
:join_table => 'project_developers'
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
with_model :Project do
|
40
|
+
table do |t|
|
41
|
+
t.string "name"
|
42
|
+
t.integer "owner_id"
|
43
|
+
t.datetime "created_at", :null => false
|
44
|
+
t.datetime "updated_at", :null => false
|
45
|
+
end
|
46
|
+
|
47
|
+
model do
|
48
|
+
belongs_to :owner,
|
49
|
+
:class_name => 'User',
|
50
|
+
:inverse_of => :projects_as_owner
|
51
|
+
|
52
|
+
has_many :todos,
|
53
|
+
:inverse_of => :project
|
54
|
+
|
55
|
+
has_and_belongs_to_many :developers,
|
56
|
+
:class_name => 'User',
|
57
|
+
:join_table => 'project_developers'
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
with_table "project_developers", :id => false do |t|
|
62
|
+
t.integer "user_id", :null => false
|
63
|
+
t.integer "project_id", :null => false
|
64
|
+
end
|
65
|
+
|
66
|
+
with_model :Todo do
|
67
|
+
table do |t|
|
68
|
+
t.string "name"
|
69
|
+
t.string "type"
|
70
|
+
t.boolean "completed"
|
71
|
+
t.integer "suggested_by_id"
|
72
|
+
t.integer "author_id"
|
73
|
+
t.integer "project_id"
|
74
|
+
t.datetime "created_at", :null => false
|
75
|
+
t.datetime "updated_at", :null => false
|
76
|
+
end
|
77
|
+
|
78
|
+
model do
|
79
|
+
belongs_to :project,
|
80
|
+
:inverse_of => :todos
|
81
|
+
|
82
|
+
belongs_to :author,
|
83
|
+
:inverse_of => :authored_todos,
|
84
|
+
:class_name => 'User'
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
metadata
ADDED
@@ -0,0 +1,155 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: kant
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Mark Przepiora
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-01-07 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.7'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.7'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 3.1.0
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 3.1.0
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: with_model
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 1.2.1
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 1.2.1
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: sqlite3
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: 1.3.10
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 1.3.10
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: activerecord
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '3.2'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '3.2'
|
97
|
+
description: Kant is a tiny authorization library for your Ruby (especially Rails
|
98
|
+
and/or ActiveRecord) projects.
|
99
|
+
email:
|
100
|
+
- mark.przepiora@gmail.com
|
101
|
+
executables: []
|
102
|
+
extensions: []
|
103
|
+
extra_rdoc_files: []
|
104
|
+
files:
|
105
|
+
- ".gitignore"
|
106
|
+
- Gemfile
|
107
|
+
- LICENSE.txt
|
108
|
+
- README.md
|
109
|
+
- Rakefile
|
110
|
+
- kant.gemspec
|
111
|
+
- lib/kant.rb
|
112
|
+
- lib/kant/access_denied.rb
|
113
|
+
- lib/kant/all.rb
|
114
|
+
- lib/kant/all_access.rb
|
115
|
+
- lib/kant/controller_mixin.rb
|
116
|
+
- lib/kant/no_access.rb
|
117
|
+
- lib/kant/policy_access.rb
|
118
|
+
- lib/kant/resolvers/active_record.rb
|
119
|
+
- lib/kant/rspec/matchers.rb
|
120
|
+
- lib/kant/version.rb
|
121
|
+
- spec/kant/all_access_spec.rb
|
122
|
+
- spec/kant/integration_spec.rb
|
123
|
+
- spec/kant/no_access_spec.rb
|
124
|
+
- spec/kant/policy_access_spec.rb
|
125
|
+
- spec/spec_helper.rb
|
126
|
+
homepage: https://github.com/markprzepiora/kant
|
127
|
+
licenses:
|
128
|
+
- MIT
|
129
|
+
metadata: {}
|
130
|
+
post_install_message:
|
131
|
+
rdoc_options: []
|
132
|
+
require_paths:
|
133
|
+
- lib
|
134
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - ">="
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '0'
|
139
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
140
|
+
requirements:
|
141
|
+
- - ">="
|
142
|
+
- !ruby/object:Gem::Version
|
143
|
+
version: '0'
|
144
|
+
requirements: []
|
145
|
+
rubyforge_project:
|
146
|
+
rubygems_version: 2.2.2
|
147
|
+
signing_key:
|
148
|
+
specification_version: 4
|
149
|
+
summary: A tiny, non-magical authorization library
|
150
|
+
test_files:
|
151
|
+
- spec/kant/all_access_spec.rb
|
152
|
+
- spec/kant/integration_spec.rb
|
153
|
+
- spec/kant/no_access_spec.rb
|
154
|
+
- spec/kant/policy_access_spec.rb
|
155
|
+
- spec/spec_helper.rb
|