know_it_all 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +11 -0
- data/.travis.yml +4 -0
- data/CODE_OF_CONDUCT.md +13 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +379 -0
- data/Rakefile +10 -0
- data/bin/console +14 -0
- data/bin/setup +7 -0
- data/know_it_all.gemspec +29 -0
- data/lib/know_it_all/authorizer.rb +33 -0
- data/lib/know_it_all/base.rb +14 -0
- data/lib/know_it_all/version.rb +3 -0
- data/lib/know_it_all.rb +67 -0
- metadata +142 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 4550ab98d97e3e526452bbf3e1b7e8dd22bbe8ed
|
4
|
+
data.tar.gz: 707163abb17202e76a11f7fb7edf4b17438bd57d
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: c4a86f9030e144126720a63363418073bd8837db9b2ab2e42426d25a7c459cf83b034bc6f62a44940680058ae990f559f5ea7991b35bf23b0acc50c71b308919
|
7
|
+
data.tar.gz: 3be9fcdaa02b1de4ddbcdc752e8f0639a0656c0c73edf9d5420302e0cde436a517429f6cd78d14af458ec1e32dca0ceefd97527373b1fac39dd23714526529f5
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/CODE_OF_CONDUCT.md
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
# Contributor Code of Conduct
|
2
|
+
|
3
|
+
As contributors and maintainers of this project, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities.
|
4
|
+
|
5
|
+
We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, or religion.
|
6
|
+
|
7
|
+
Examples of unacceptable behavior by participants include the use of sexual language or imagery, derogatory comments or personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct.
|
8
|
+
|
9
|
+
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed from the project team.
|
10
|
+
|
11
|
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers.
|
12
|
+
|
13
|
+
This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.0.0, available at [http://contributor-covenant.org/version/1/0/0/](http://contributor-covenant.org/version/1/0/0/)
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2016 mrodrigues
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,379 @@
|
|
1
|
+
> Well, actually...
|
2
|
+
|
3
|
+
# KnowItAll
|
4
|
+
|
5
|
+
KnowItAll is a small, object-oriented approach to authorization. It knows everything about your application!
|
6
|
+
|
7
|
+
More of an architectural pattern for API-focused authorization than properly a dependency, and heavily inspired by [Pundit](https://github.com/elabs/pundit), this gem simply provides a small set of helpers that make applying the pattern easier.
|
8
|
+
|
9
|
+
If your application needs to validate pre-requisites before performing certain actions, at the same time providing helpful error messages for the API's clients, all that while using regular magic-less Ruby and object oriented design patterns, KnowItAll is your friend.
|
10
|
+
|
11
|
+
## Why?
|
12
|
+
|
13
|
+
The assumption made is that each action has its own requirements based on the current context. Some may be related to the current user's permissions in the system, others with the parameters sent, and others yet may even have nothing to do with any input received. Let's say you're building the API for a food delivery app. To be able to checkout, you need to validate the following requirements:
|
14
|
+
|
15
|
+
- The user must be signed in;
|
16
|
+
- The user must have a registered address;
|
17
|
+
- The registered address must be within a determined radius;
|
18
|
+
- The cart must contain at least $10 in items;
|
19
|
+
- The chosen items must be available for delivery;
|
20
|
+
- The store must be open.
|
21
|
+
|
22
|
+
It'd be very helpful for a developer consuming this API if, in case of failure, the API returned an appropriate error message explaining exactly what when wrong, instead of an empty `403 Forbidden`. Performing this manually is easy, but quickly polutes the action's code:
|
23
|
+
|
24
|
+
```ruby
|
25
|
+
class OrdersController < ApplicationController
|
26
|
+
def create
|
27
|
+
return error("User must be signed in") unless current_user
|
28
|
+
return error("User must have a registered address") unless current_user.address
|
29
|
+
return error("Registered address is outside the range") unless address_in_range?(current_user.address)
|
30
|
+
return error("Cart must contain at least $10 in items") unless cart_has_minimum?(cart)
|
31
|
+
return error("Some of the items are not available") unless items_available?(cart.items)
|
32
|
+
return error("The store is closed") unless store.open?
|
33
|
+
|
34
|
+
# Here finally starts what the action actually does
|
35
|
+
order = Order.create(order_params)
|
36
|
+
if order.save
|
37
|
+
render json: order, status: :created
|
38
|
+
else
|
39
|
+
render json: { errors: order.errors }, status: :unprocessable_entity
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def error(message)
|
46
|
+
render json: { error: message }, status: :forbidden
|
47
|
+
end
|
48
|
+
end
|
49
|
+
```
|
50
|
+
|
51
|
+
It's much more readable, as well as easier to test and extend, if all of those requirement tests were contained in a proper class:
|
52
|
+
|
53
|
+
```ruby
|
54
|
+
class OrdersController < ApplicationController
|
55
|
+
def create
|
56
|
+
policy = OrdersPolicies::Create.new(current_user, cart, store)
|
57
|
+
return render json: { errors: policy.errors } unless policy.authorize?
|
58
|
+
|
59
|
+
order = Order.create(order_params)
|
60
|
+
if order.save
|
61
|
+
render json: order, status: :created
|
62
|
+
else
|
63
|
+
render json: { errors: order.errors }, status: :unprocessable_entity
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
```
|
68
|
+
|
69
|
+
That's exactly the architectural pattern encouraged by this gem. By including a small set of helpers, it makes it extremely simple to perform complex validations and provide helpful feedback through the API.
|
70
|
+
|
71
|
+
## Installation
|
72
|
+
|
73
|
+
Add this line to your application's Gemfile:
|
74
|
+
|
75
|
+
```ruby
|
76
|
+
gem 'know_it_all'
|
77
|
+
```
|
78
|
+
|
79
|
+
And then execute:
|
80
|
+
|
81
|
+
$ bundle
|
82
|
+
|
83
|
+
Or install it yourself as:
|
84
|
+
|
85
|
+
$ gem install know_it_all
|
86
|
+
|
87
|
+
## Usage
|
88
|
+
|
89
|
+
There are two steps to using this gem: creating and using policies:
|
90
|
+
|
91
|
+
### Creating policies
|
92
|
+
|
93
|
+
A policy is simply a class obeys some rules:
|
94
|
+
|
95
|
+
* It is initialized with the same arguments that are passed to the `authorize`, `authorize!` and `authorize?` methods in the controller;
|
96
|
+
* It responds to a method `errors`;
|
97
|
+
* Calling `errors` returns an object that responds to the method `empty?` and is serializable. It's usually an array, but it could easily be an `ActiveModel::Errors`.
|
98
|
+
|
99
|
+
Here's an example:
|
100
|
+
|
101
|
+
```ruby
|
102
|
+
module OrdersPolicies
|
103
|
+
class Create
|
104
|
+
def initialize(current_user, cart, store)
|
105
|
+
@current_user = current_user
|
106
|
+
@cart = cart
|
107
|
+
@store = store
|
108
|
+
end
|
109
|
+
|
110
|
+
def errors
|
111
|
+
@errors = []
|
112
|
+
@errors << "User must be signed in" unless @current_user
|
113
|
+
@errors << "User must have a registered address" unless @current_user.address
|
114
|
+
@errors << "Registered address is outside the range" unless address_in_range?(current_user.address)
|
115
|
+
@errors << "Cart must contain at least $10 in items" unless cart_has_minimum?(@cart)
|
116
|
+
@errors << "Some of the items are not available" unless items_available?(@cart.items)
|
117
|
+
@errors << "The store is closed" unless @store.open?
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
```
|
122
|
+
|
123
|
+
Using `ActiveModel::Validations`:
|
124
|
+
|
125
|
+
```ruby
|
126
|
+
module OrdersPolicies
|
127
|
+
class Create
|
128
|
+
includes ActiveModel::Validations
|
129
|
+
|
130
|
+
validates_presence_of :current_user, :address
|
131
|
+
validate :address_in_range
|
132
|
+
validate :cart_has_minimum
|
133
|
+
validate :items_are_available
|
134
|
+
validate :store_is_open
|
135
|
+
|
136
|
+
def initialize(current_user, cart, store)
|
137
|
+
@current_user = current_user
|
138
|
+
@cart = cart
|
139
|
+
@store = store
|
140
|
+
|
141
|
+
run_validations! # Populates the `ActiveModel::Errors`
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
```
|
146
|
+
|
147
|
+
#### Naming convention
|
148
|
+
|
149
|
+
The convention `KnowItAll` uses for defining the name of the constant containing the appropriate policy is the following:
|
150
|
+
|
151
|
+
* Based on the `controller_path` method on the controller, it builds a module name by appending the `Policies` suffix: `"orders"` becomes `"OrdersPolicies"` and `"admin/dashboard_panel"` becomes `"Admin::DashboardPanelPolicies"`.
|
152
|
+
* Based on the `action_name` method on the controller, it builds a class name: `"index"` becomes `"Index"`, `"increase_inventory"` becomes `"IncreaseInventory"`.
|
153
|
+
* By appending the class name to the module name, it tries to find that constant: with `controller_path == "orders"` and `action_name == "Index"`, it looks for a `OrdersPolicies::Index` constant.
|
154
|
+
|
155
|
+
For more details about how the module and class names are converted, please check the [`ActiveSupport::Inflector#camelize`](http://api.rubyonrails.org/classes/ActiveSupport/Inflector.html#method-i-camelize) method.
|
156
|
+
|
157
|
+
#### Helper class
|
158
|
+
|
159
|
+
If you don't want to write your own policy from the scratch, I've also provided a minimalistic base policy:
|
160
|
+
|
161
|
+
```ruby
|
162
|
+
module OrdersPolicies
|
163
|
+
class Create < KnowItAll::Base
|
164
|
+
assert :user_signed_in?, "User must be signed in"
|
165
|
+
assert :address_present?, "User must have a registered address"
|
166
|
+
assert :address_in_range?, "Registered address is outside the range"
|
167
|
+
assert :cart_has_minimum?, "Cart must contain at least $10 in items"
|
168
|
+
assert :items_available?, "Some of the items are not available"
|
169
|
+
assert :store_open?, "The store is closed"
|
170
|
+
|
171
|
+
def initialize(current_user, cart, store)
|
172
|
+
@current_user = current_user
|
173
|
+
@cart = cart
|
174
|
+
@store = store
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
```
|
179
|
+
|
180
|
+
The class method `assert` expects a `Symbol` representing the name of a predicate and a `String` containing the error message in case the predicate fails. The default `errors` method returns an array containing the messages for all the assertions that didn't pass.
|
181
|
+
|
182
|
+
### Using policies
|
183
|
+
|
184
|
+
The simplest approach is to include the `KnowItAll` module in the controller you want to perform the validation. For this example, let's make the helpers available to all controllers by including it in the `ApplicationController`:
|
185
|
+
|
186
|
+
```ruby
|
187
|
+
class ApplicationController < ActionController::Base
|
188
|
+
include KnowItAll
|
189
|
+
end
|
190
|
+
```
|
191
|
+
|
192
|
+
After that, we can use the helpers in any controller that inherits from `ApplicationController`:
|
193
|
+
|
194
|
+
```ruby
|
195
|
+
class OrdersController < ApplicationController
|
196
|
+
def create
|
197
|
+
authorize! current_user, cart, store
|
198
|
+
|
199
|
+
order = Order.create(order_params)
|
200
|
+
if order.save
|
201
|
+
render json: order, status: :created
|
202
|
+
else
|
203
|
+
render json: { errors: order.errors }, status: :unprocessable_entity
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
207
|
+
```
|
208
|
+
|
209
|
+
#### What happens when not authorized
|
210
|
+
|
211
|
+
The `authorize` method raises a `KnowItAll::NotAuthorized` exception in case the authorization has failed, and contains the instance of the policy used to perform the validation:
|
212
|
+
|
213
|
+
```ruby
|
214
|
+
class ApplicationController < ActionController::Base
|
215
|
+
include KnowItAll
|
216
|
+
rescue_from KnowItAll::NotAuthorized do |exception|
|
217
|
+
render json: { errors: exception.policy.errors }, status: :forbidden
|
218
|
+
end
|
219
|
+
end
|
220
|
+
```
|
221
|
+
|
222
|
+
This pattern is so common that I've wrote a method that does exactly that:
|
223
|
+
|
224
|
+
```ruby
|
225
|
+
class ApplicationController < ActionController::Base
|
226
|
+
include KnowItAll
|
227
|
+
rescue_from KnowItAll::NotAuthorized, with: :render_not_authorized
|
228
|
+
end
|
229
|
+
```
|
230
|
+
|
231
|
+
Alternatively, you can use the bangless form of the authorization method (`authorize`), which doesn't raise an exception and return an array of errors:
|
232
|
+
|
233
|
+
```ruby
|
234
|
+
class OrdersController < ApplicationController
|
235
|
+
def create
|
236
|
+
errors = authorize current_user, cart, store
|
237
|
+
if errors.empty?
|
238
|
+
order = Order.create(order_params)
|
239
|
+
if order.save
|
240
|
+
render json: order, status: :created
|
241
|
+
else
|
242
|
+
render json: { errors: order.errors }, status: :unprocessable_entity
|
243
|
+
end
|
244
|
+
else
|
245
|
+
return render json: { errors: errors }, status: :forbidden
|
246
|
+
end
|
247
|
+
end
|
248
|
+
end
|
249
|
+
```
|
250
|
+
|
251
|
+
#### Querying authorizations in the view
|
252
|
+
|
253
|
+
You can use the predicate `authorize?` to make decisions based on future authorizations in your views. First you need to make the method available as a helper:
|
254
|
+
|
255
|
+
```ruby
|
256
|
+
class ApplicationController < ActionController::Base
|
257
|
+
include KnowItAll
|
258
|
+
helper_method :authorize?
|
259
|
+
end
|
260
|
+
```
|
261
|
+
|
262
|
+
Then use it in your views, passing the appropriate overrides (more about that here):
|
263
|
+
|
264
|
+
```erb
|
265
|
+
<%= form_for @order do |f| %>
|
266
|
+
<!-- Form fields -->
|
267
|
+
|
268
|
+
<%= f.button "Place order", disabled: authorize?(
|
269
|
+
@current_user,
|
270
|
+
@cart,
|
271
|
+
@store,
|
272
|
+
controller_path: "orders",
|
273
|
+
action_name: "create"
|
274
|
+
) %>
|
275
|
+
<% end %>
|
276
|
+
```
|
277
|
+
|
278
|
+
#### Avoiding conflicts in the controller
|
279
|
+
|
280
|
+
It's possible that you're already using methods with the same names as the ones in the `KnowItAll` module: `authorize`, `authorized?`, `authorize!`, `policy`, `policy_class`, `policy_name`, `render_not_authorized` or `verify_authorized`. In that case, the solution is to include the module in another class, and use it as a collaborator. The only methods `KnowItAll` needs to find the correct policies are `controller_path` and `action_name`:
|
281
|
+
|
282
|
+
```ruby
|
283
|
+
class Authorizer
|
284
|
+
include KnowItAll
|
285
|
+
attr_reader :controller_path, :action_name
|
286
|
+
|
287
|
+
def initialize(controller)
|
288
|
+
@controller_path = controller.controller_path
|
289
|
+
@action_name = controller.action_name
|
290
|
+
end
|
291
|
+
end
|
292
|
+
|
293
|
+
class ApplicationController < ActionController::Base
|
294
|
+
protected
|
295
|
+
|
296
|
+
def authorizer
|
297
|
+
Authorizer.new(self)
|
298
|
+
end
|
299
|
+
end
|
300
|
+
|
301
|
+
class OrdersController < ApplicationController
|
302
|
+
def create
|
303
|
+
authorizer.authorize! current_user, cart, store
|
304
|
+
|
305
|
+
# Action's code here
|
306
|
+
end
|
307
|
+
end
|
308
|
+
```
|
309
|
+
|
310
|
+
In that case, I've made available a `KnowItAll::Authorizer` class that does exactly that:
|
311
|
+
|
312
|
+
```ruby
|
313
|
+
class ApplicationController < ActionController::Base
|
314
|
+
protected
|
315
|
+
|
316
|
+
def authorizer
|
317
|
+
KnowItAll::Authorizer.new(self)
|
318
|
+
end
|
319
|
+
end
|
320
|
+
```
|
321
|
+
|
322
|
+
#### Overrides
|
323
|
+
|
324
|
+
It's possible to override any of the methods `KnowItAll` uses to define the appropriate policy. You can do that in the controller:
|
325
|
+
|
326
|
+
```ruby
|
327
|
+
class OrdersController < ApplicationController
|
328
|
+
def create
|
329
|
+
authorize! current_user, cart, store
|
330
|
+
|
331
|
+
# Action's code here
|
332
|
+
end
|
333
|
+
|
334
|
+
def policy_name
|
335
|
+
"OrdersPolicies::Checkout"
|
336
|
+
end
|
337
|
+
end
|
338
|
+
```
|
339
|
+
|
340
|
+
Or when calling the `authorize`, `authorize?` or `authorize!` methods:
|
341
|
+
|
342
|
+
```ruby
|
343
|
+
class OrdersController < ApplicationController
|
344
|
+
def create
|
345
|
+
authorize! current_user, cart, store, policy_name: "OrdersPolicies::Checkout"
|
346
|
+
|
347
|
+
# Action's code here
|
348
|
+
end
|
349
|
+
end
|
350
|
+
```
|
351
|
+
|
352
|
+
The available overrides are: `controller_path`, `action_name`, `policy_name`, `policy_class` and `policy` (instance of the policy).
|
353
|
+
|
354
|
+
## Enforcing authorization checks
|
355
|
+
|
356
|
+
While developing a simple feature, it's easy to forget to perform an authorization check. It's helpful during development to know when you forget it, so I've provided a `verify_authorized` method that raises a `KnowItAll::AuthorizationNotPerformedError` when there were no calls to any one of the authorization methods: `authorize`, `authorize?` or `authorize!`:
|
357
|
+
|
358
|
+
```ruby
|
359
|
+
class ApplicationController < ActionController::Base
|
360
|
+
include KnowItAll
|
361
|
+
after_action :verify_authorized
|
362
|
+
end
|
363
|
+
```
|
364
|
+
|
365
|
+
## Development
|
366
|
+
|
367
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
368
|
+
|
369
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
370
|
+
|
371
|
+
## Contributing
|
372
|
+
|
373
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/know_it_all. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](contributor-covenant.org) code of conduct.
|
374
|
+
|
375
|
+
|
376
|
+
## License
|
377
|
+
|
378
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
379
|
+
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "know_it_all"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start
|
data/bin/setup
ADDED
data/know_it_all.gemspec
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'know_it_all/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "know_it_all"
|
8
|
+
spec.version = KnowItAll::VERSION
|
9
|
+
spec.authors = ["mrodrigues"]
|
10
|
+
spec.email = ["mrodrigues.uff@gmail.com"]
|
11
|
+
|
12
|
+
spec.summary = %q{OO authorization for APIs}
|
13
|
+
spec.description = %q{Minimalistic authorization focused on APIs}
|
14
|
+
spec.homepage = "https://github.com/mrodrigues/know_it_all"
|
15
|
+
spec.license = "MIT"
|
16
|
+
|
17
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
18
|
+
spec.bindir = "exe"
|
19
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
20
|
+
spec.require_paths = ["lib"]
|
21
|
+
|
22
|
+
spec.add_dependency "activesupport"
|
23
|
+
|
24
|
+
spec.add_development_dependency "bundler", "~> 1.10"
|
25
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
26
|
+
spec.add_development_dependency "minitest"
|
27
|
+
spec.add_development_dependency "minitest-reporters"
|
28
|
+
spec.add_development_dependency "pry"
|
29
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module KnowItAll
|
2
|
+
class Authorizer
|
3
|
+
include KnowItAll
|
4
|
+
|
5
|
+
def initialize(controller)
|
6
|
+
self.controller = controller
|
7
|
+
end
|
8
|
+
|
9
|
+
def controller_path
|
10
|
+
controller.controller_path
|
11
|
+
end
|
12
|
+
|
13
|
+
def action_name
|
14
|
+
controller.action_name
|
15
|
+
end
|
16
|
+
|
17
|
+
def policy(*args)
|
18
|
+
controller.respond_to?(:policy) && controller.policy(*args) || super
|
19
|
+
end
|
20
|
+
|
21
|
+
def policy_class(*args)
|
22
|
+
controller.respond_to?(:policy_class) && controller.policy_class(*args) || super
|
23
|
+
end
|
24
|
+
|
25
|
+
def policy_name(*args)
|
26
|
+
controller.respond_to?(:policy_name) && controller.policy_name(*args) || super
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
attr_accessor :controller
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module KnowItAll
|
2
|
+
class Base
|
3
|
+
def self.assert(method_name, message)
|
4
|
+
@@validations ||= {}
|
5
|
+
@@validations[method_name] = message
|
6
|
+
end
|
7
|
+
|
8
|
+
def errors
|
9
|
+
@@validations.each
|
10
|
+
.select { |method_name, _| !self.send(method_name) }
|
11
|
+
.map { |_, message| message }
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
data/lib/know_it_all.rb
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
require "know_it_all/version"
|
2
|
+
require "know_it_all/base"
|
3
|
+
require "know_it_all/authorizer"
|
4
|
+
|
5
|
+
module KnowItAll
|
6
|
+
SUFFIX = "Policies"
|
7
|
+
|
8
|
+
def authorize?(*args)
|
9
|
+
authorize(*args).empty?
|
10
|
+
end
|
11
|
+
|
12
|
+
def authorize(*args,
|
13
|
+
controller_path: self.controller_path,
|
14
|
+
action_name: self.action_name,
|
15
|
+
policy_name: self.policy_name(
|
16
|
+
controller_path: controller_path,
|
17
|
+
action_name: action_name
|
18
|
+
),
|
19
|
+
policy_class: self.policy_class(policy_name: policy_name),
|
20
|
+
policy: self.policy(*args, policy_class: policy_class)
|
21
|
+
)
|
22
|
+
@_authorization_performed = true
|
23
|
+
policy.errors
|
24
|
+
end
|
25
|
+
|
26
|
+
def authorize!(*args)
|
27
|
+
raise NotAuthorized.new(policy(*args)) unless authorize?(*args)
|
28
|
+
end
|
29
|
+
|
30
|
+
def policy(*args, policy_class: self.policy_class)
|
31
|
+
policy_class.new(*args)
|
32
|
+
end
|
33
|
+
|
34
|
+
def policy_class(policy_name: self.policy_name)
|
35
|
+
@policy_class ||= policy_name.constantize
|
36
|
+
end
|
37
|
+
|
38
|
+
def policy_name(
|
39
|
+
controller_path: self.controller_path,
|
40
|
+
action_name: self.action_name
|
41
|
+
)
|
42
|
+
"#{controller_path.to_s.camelize}#{SUFFIX}::#{action_name.to_s.camelize}"
|
43
|
+
end
|
44
|
+
|
45
|
+
def render_not_authorized(exception)
|
46
|
+
render(
|
47
|
+
json: {
|
48
|
+
errors: exception.policy.errors
|
49
|
+
},
|
50
|
+
status: :forbidden
|
51
|
+
)
|
52
|
+
end
|
53
|
+
|
54
|
+
def verify_authorized
|
55
|
+
raise AuthorizationNotPerformedError unless @_authorization_performed
|
56
|
+
end
|
57
|
+
|
58
|
+
class NotAuthorized < StandardError
|
59
|
+
attr_accessor :policy
|
60
|
+
|
61
|
+
def initialize(policy)
|
62
|
+
self.policy = policy
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
class AuthorizationNotPerformedError < StandardError; end
|
67
|
+
end
|
metadata
ADDED
@@ -0,0 +1,142 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: know_it_all
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- mrodrigues
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-06-29 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: activesupport
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: bundler
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.10'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.10'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '10.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '10.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: minitest
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: minitest-reporters
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: pry
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
description: Minimalistic authorization focused on APIs
|
98
|
+
email:
|
99
|
+
- mrodrigues.uff@gmail.com
|
100
|
+
executables: []
|
101
|
+
extensions: []
|
102
|
+
extra_rdoc_files: []
|
103
|
+
files:
|
104
|
+
- ".gitignore"
|
105
|
+
- ".travis.yml"
|
106
|
+
- CODE_OF_CONDUCT.md
|
107
|
+
- Gemfile
|
108
|
+
- LICENSE.txt
|
109
|
+
- README.md
|
110
|
+
- Rakefile
|
111
|
+
- bin/console
|
112
|
+
- bin/setup
|
113
|
+
- know_it_all.gemspec
|
114
|
+
- lib/know_it_all.rb
|
115
|
+
- lib/know_it_all/authorizer.rb
|
116
|
+
- lib/know_it_all/base.rb
|
117
|
+
- lib/know_it_all/version.rb
|
118
|
+
homepage: https://github.com/mrodrigues/know_it_all
|
119
|
+
licenses:
|
120
|
+
- MIT
|
121
|
+
metadata: {}
|
122
|
+
post_install_message:
|
123
|
+
rdoc_options: []
|
124
|
+
require_paths:
|
125
|
+
- lib
|
126
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
127
|
+
requirements:
|
128
|
+
- - ">="
|
129
|
+
- !ruby/object:Gem::Version
|
130
|
+
version: '0'
|
131
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
132
|
+
requirements:
|
133
|
+
- - ">="
|
134
|
+
- !ruby/object:Gem::Version
|
135
|
+
version: '0'
|
136
|
+
requirements: []
|
137
|
+
rubyforge_project:
|
138
|
+
rubygems_version: 2.4.8
|
139
|
+
signing_key:
|
140
|
+
specification_version: 4
|
141
|
+
summary: OO authorization for APIs
|
142
|
+
test_files: []
|