active_entry 1.1.0 → 2.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +293 -106
- data/app/controllers/concerns/active_entry/concern.rb +57 -0
- data/app/helpers/active_entry/view_helper.rb +20 -0
- data/lib/active_entry.rb +7 -70
- data/lib/active_entry/base.rb +66 -0
- data/lib/active_entry/errors.rb +47 -48
- data/lib/active_entry/generators.rb +4 -0
- data/lib/active_entry/policy_finder.rb +25 -0
- data/lib/active_entry/railtie.rb +6 -6
- data/lib/active_entry/rspec.rb +56 -0
- data/lib/active_entry/version.rb +1 -1
- data/lib/generators/active_entry/install/USAGE +8 -0
- data/lib/generators/active_entry/install/install_generator.rb +9 -0
- data/lib/generators/active_entry/install/templates/application_policy.rb +7 -0
- data/lib/generators/policy/USAGE +8 -0
- data/lib/generators/policy/policy_generator.rb +7 -0
- data/lib/generators/policy/templates/policy.rb +53 -0
- data/lib/generators/rspec/USAGE +8 -0
- data/lib/generators/rspec/policy_generator.rb +11 -0
- data/lib/generators/rspec/templates/policy_spec.rb +5 -0
- metadata +19 -5
- data/lib/active_entry/controller_methods.rb +0 -82
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 68c5d6e98c46a35b4ef4e32f6987bd0c412326aad17db589e45dd671a6d3e1b7
|
4
|
+
data.tar.gz: 48d4b700fdff06bd94ab4fc2b9d34c73142b21a91641be20fd080287f11c16f4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4efc966d6cc59cf94da405cdd583afb3e3954ed73dbb97b9c83876cd04ffe5965e7d67850b903318acbf9af342ea0933c837b65773bf74deb7c8664c0e58d3ff
|
7
|
+
data.tar.gz: ac48a14a4a6b0761b3d35516caa11254c8b637dda53498b6b603d16ba1b800e5d09e44af8383ea9141777907288381648b0b39db033f55db84899e1e671e084f
|
data/README.md
CHANGED
@@ -1,8 +1,74 @@
|
|
1
|
-
|
1
|
+
<p align="center">
|
2
|
+
<a href="https://github.com/TFM-Agency/active_entry">
|
3
|
+
<img src="https://raw.githubusercontent.com/TFM-Agency/active_entry/main/active_entry_logo.svg" alt="Active Entry Logo" width="350px"/>
|
4
|
+
</a>
|
5
|
+
</p>
|
2
6
|
|
3
7
|
# Active Entry - Simple and flexible authentication and authorization
|
8
|
+
[![Gem Version](https://badge.fury.io/rb/active_entry.svg)](https://badge.fury.io/rb/active_entry)
|
9
|
+
[![Ruby](https://github.com/TFM-Agency/active_entry/actions/workflows/ci-rspec.yml/badge.svg)](https://github.com/TFM-Agency/active_entry/actions/workflows/ci-rspec.yml)
|
10
|
+
![Coverage](https://raw.githubusercontent.com/TFM-Agency/active_entry/main/coverage/coverage_badge_total.svg)
|
11
|
+
[![Maintainability](https://api.codeclimate.com/v1/badges/3db0f653be6bdfe0fdac/maintainability)](https://codeclimate.com/github/TFM-Agency/active_entry/maintainability)
|
12
|
+
[![Documentation](https://img.shields.io/badge/docs-rdoc.info-blue.svg)](https://rubydoc.info/github/TFM-Agency/active_entry/main)
|
4
13
|
|
5
|
-
Active Entry is a
|
14
|
+
Active Entry is a secure way to check for authentication and authorization before an action is performed. It's currently only compatible with Rails. But in later versions will ActiveEntry be Framework independent.
|
15
|
+
|
16
|
+
Active Entry works like many other Authorization Systems like [Pundit](https://github.com/varvet/pundit) or [Action Policy](https://github.com/palkan/action_policy) with **Policies**. However in Active Entry it's all about the method calling the auth mechanism. For every method that needs authentication or authorization, a decision maker method counterpart has to be created in the policy of the class.
|
17
|
+
|
18
|
+
## Example
|
19
|
+
|
20
|
+
Let's say we have an Users controller in our application:
|
21
|
+
|
22
|
+
```ruby
|
23
|
+
# app/controllers/users_controller.rb
|
24
|
+
class UsersController < ApplicationController
|
25
|
+
include ActiveEntry::ControllerConcern # Glue for the controller and Active Entry
|
26
|
+
|
27
|
+
def index
|
28
|
+
pass! # The auth happens here
|
29
|
+
load_users
|
30
|
+
end
|
31
|
+
end
|
32
|
+
```
|
33
|
+
|
34
|
+
We have to create the UsersPolicy in order for Active Entry to know who is authenticated and authorized and who not.
|
35
|
+
|
36
|
+
```ruby
|
37
|
+
# app/policies/users_policy.rb
|
38
|
+
module UsersPolicy
|
39
|
+
class Authentication < ActiveEntry::Base::Authentication
|
40
|
+
def index?
|
41
|
+
Current.user_signed_in? # Only signed in users are considered to be authenticated.
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
class Authorization < ActiveEntry::Base::Authorization
|
46
|
+
def index?
|
47
|
+
Current.user.admin? # Only admins are authorized to perform this action
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
```
|
52
|
+
|
53
|
+
Now every time somebody calls the `users#index` endpoint, he or she has to be signed in and an admin. Otherwise `ActiveEntry::NotAuthenticatedError` or `ActiveEntry::NotAuthorizedError` are raised.
|
54
|
+
You can catch them easily in your controller by using Rails' `rescue_from`.
|
55
|
+
|
56
|
+
```ruby
|
57
|
+
class ApplicationController < ActionController::Base
|
58
|
+
rescue_from ActiveEntry::NotAuthenticatedError, with: :not_authenticated
|
59
|
+
rescue_from ActiveEntry::NotAuthorizedError, with: :not_authorized
|
60
|
+
|
61
|
+
def not_authenticated
|
62
|
+
flash[:danger] = "Not authenticated. Please sign in."
|
63
|
+
redirect_to sign_in_path
|
64
|
+
end
|
65
|
+
|
66
|
+
def not_authorized
|
67
|
+
flash[:danger] = "Not authorized."
|
68
|
+
redirect_to root_path
|
69
|
+
end
|
70
|
+
end
|
71
|
+
```
|
6
72
|
|
7
73
|
## Installation
|
8
74
|
Add this line to your application's Gemfile:
|
@@ -11,52 +77,112 @@ Add this line to your application's Gemfile:
|
|
11
77
|
gem 'active_entry'
|
12
78
|
```
|
13
79
|
|
14
|
-
|
80
|
+
Or install it without bundler:
|
15
81
|
```bash
|
82
|
+
$ gem install active_entry
|
83
|
+
```
|
84
|
+
|
85
|
+
Run Bundle:
|
86
|
+
```shell
|
16
87
|
$ bundle
|
17
88
|
```
|
18
89
|
|
19
|
-
|
20
|
-
```
|
21
|
-
$
|
90
|
+
And then install Active Entry:
|
91
|
+
```shell
|
92
|
+
$ rails g active_entry:install
|
22
93
|
```
|
23
94
|
|
95
|
+
This will generate `app/policies/application_policy.rb`.
|
96
|
+
|
24
97
|
## Usage
|
25
|
-
|
26
|
-
|
98
|
+
Active Entry works with Policies. You can generate policies the following way:
|
99
|
+
|
100
|
+
Let's consider the example from above.
|
101
|
+
We have an UsersController and we want a policy for that:
|
102
|
+
|
103
|
+
```shell
|
104
|
+
$ rails g policy Users
|
105
|
+
```
|
106
|
+
|
107
|
+
This generates a policy called `UsersPolicy` and is located in `app/policies/users_policy.rb`.
|
108
|
+
|
109
|
+
The above generator call would generate something like this, but with a few comments to help you get started:
|
110
|
+
|
111
|
+
```ruby
|
112
|
+
module UsersPolicy
|
113
|
+
class Authentication < ActiveEntry::Base::Authentication
|
114
|
+
end
|
115
|
+
|
116
|
+
class Authorization < ActiveEntry::Base::Authorization
|
117
|
+
end
|
118
|
+
end
|
119
|
+
```
|
120
|
+
|
121
|
+
### Verify authentication and authorization
|
122
|
+
You probably want to control authentication and authorization for every controller action you have in your app. As a safeguard to ensure, that auth is performed in every request and the auth call is not forgotten in development, add the `verify_authentication!` and `verify_authorization!` to your `ApplicationController`:
|
27
123
|
|
28
124
|
```ruby
|
29
125
|
class ApplicationController < ActionController::Base
|
30
|
-
|
126
|
+
verify_authentication!
|
127
|
+
verify_authorization!
|
31
128
|
# ...
|
32
129
|
end
|
33
130
|
```
|
131
|
+
This ensures, that you perform auth in all your controllers and raises errors if not.
|
34
132
|
|
35
|
-
|
36
|
-
|
133
|
+
### Perform authentication and authorization
|
134
|
+
in order to do the actual authentication and authorization, you have to use `authenticate!` and `authorize!` or `pass!` as in your actions.
|
37
135
|
|
38
136
|
```ruby
|
39
|
-
class
|
40
|
-
|
137
|
+
class UsersController < ApplicationController
|
138
|
+
def authentication_only_action
|
139
|
+
authenticate!
|
140
|
+
end
|
41
141
|
|
42
|
-
|
142
|
+
def authorization_only_action
|
143
|
+
authorize!
|
144
|
+
end
|
43
145
|
|
44
|
-
def
|
45
|
-
|
146
|
+
def both_authentication_and_authorization_action
|
147
|
+
pass!
|
148
|
+
end
|
149
|
+
end
|
150
|
+
```
|
151
|
+
|
152
|
+
If you try to open a page, Active Entry will raise `ActiveEntry::DecisionMakerMethodNotDefinedError`. This means we have to define the decision makers in our policy.
|
153
|
+
|
154
|
+
```ruby
|
155
|
+
module UsersPolicy
|
156
|
+
class Authentication < ApplicationPolicy::Authentication
|
157
|
+
def authentication_only_action?
|
158
|
+
success # == true | Everybody is allowed
|
159
|
+
end
|
160
|
+
|
161
|
+
def both_authentication_and_authorization_action?
|
162
|
+
success
|
163
|
+
end
|
46
164
|
end
|
47
165
|
|
48
|
-
|
49
|
-
|
50
|
-
|
166
|
+
class Authorization < ApplicationPolicy::Authorization
|
167
|
+
def authorization_only_action?
|
168
|
+
success
|
169
|
+
end
|
170
|
+
|
171
|
+
def both_authentication_and_authorization_action?
|
172
|
+
success
|
173
|
+
end
|
174
|
+
end
|
51
175
|
end
|
52
176
|
```
|
53
177
|
|
54
|
-
|
178
|
+
Every decision maker ends with an `?`. The name has to be the same as the name of the controller action. So `index` is going to be `index?`.
|
55
179
|
|
56
|
-
|
180
|
+
In order for Active Entry to not raise an auth error, the decision makers have to return `true`. In our above example we used `success`, which simply returns `true`.
|
57
181
|
|
58
|
-
|
59
|
-
|
182
|
+
**Note:** It has to be an explicit `true` and not just a truthy value. A string or object return value would raise an auth error.
|
183
|
+
|
184
|
+
### Rescuing from errors
|
185
|
+
Catch the errors in your controllers to redirect the user or show them a message.
|
60
186
|
|
61
187
|
```ruby
|
62
188
|
class ApplicationController < ActionController::Base
|
@@ -81,84 +207,70 @@ end
|
|
81
207
|
|
82
208
|
In this example above, the user will be redirected with a flash message. But you can do whatever you want. For example logging.
|
83
209
|
|
84
|
-
###
|
85
|
-
|
86
|
-
Instead of putting all authentication/authorization logic into `authenticated?` and `authorized?` you can create scoped decision makers:
|
210
|
+
### Authenticate/authorize outside the action
|
211
|
+
You can authenticate and authorize outside the action:
|
87
212
|
|
88
213
|
```ruby
|
89
|
-
class
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
# Do your authentication for the index action only
|
94
|
-
end
|
95
|
-
def index_authorized?
|
96
|
-
# Do your authorization for the index action only
|
97
|
-
end
|
98
|
-
def index
|
99
|
-
# Actual action
|
100
|
-
end
|
214
|
+
class UsersController < ApplicationController
|
215
|
+
authenticate_now!
|
216
|
+
authorize_now!
|
217
|
+
# pass_now! # Does both, authentication and authorization
|
101
218
|
end
|
102
219
|
```
|
103
220
|
|
104
|
-
|
105
|
-
|
106
|
-
**Note:** The scoped authentication/authorization decision maker methods take precendence over the general ones. That means if you have an `index_authenticated?` for your index action defined, the general `authenticated?` gets ignored.
|
107
|
-
|
108
|
-
### Controller helper methods
|
109
|
-
|
110
|
-
Active Entry also has a few helper methods which help you to distinguish between controller actions. You can check if a specific action got called, by adding `_action?` to the action name in your `authenticated?` or `authorized?`.
|
111
|
-
For an action `show` this would be `show_action?`.
|
221
|
+
Access control on class level will ensure that every action performs it.
|
112
222
|
|
113
|
-
**Note:**
|
223
|
+
**Note:** Don't use the class methods if the controller is inherited in other controllers. Best, don't use them at all and use the methods in the actions conciously.
|
114
224
|
|
115
|
-
|
116
|
-
|
117
|
-
* `read_action?` - If the called action just read. Actions: `index`, `show`
|
118
|
-
* `write_action?` - If the called action writes something. Actions: `new`, `create`, `edit`, `update`, `destroy`
|
119
|
-
* `change_action?` - If something will be updated or destroyed. Actions: `edit`, `update`, `destroy`
|
120
|
-
* `create_action?` - If something will be created. Actions: `new`, `create`
|
121
|
-
* `update_action?` - If something will be updated. Actions: `edit`, `update`
|
122
|
-
* `destroy_action?` - If something will be destroyed. Action: `destroy`
|
123
|
-
* `delete_action?` - Alias for `destroy_action?`. Action: `destroy`
|
124
|
-
|
125
|
-
So you can for example do:
|
225
|
+
## Variables
|
226
|
+
You can pass variables to the decision maker.
|
126
227
|
|
127
228
|
```ruby
|
128
|
-
class
|
129
|
-
# ...
|
130
|
-
|
229
|
+
class UsersController < ApplicationController
|
131
230
|
def show
|
231
|
+
@user = User.find params[:id]
|
232
|
+
pass! user: @user
|
132
233
|
end
|
234
|
+
end
|
235
|
+
```
|
133
236
|
|
134
|
-
|
135
|
-
end
|
136
|
-
|
137
|
-
private
|
138
|
-
|
139
|
-
def authorized?
|
140
|
-
return true if read_action? # Everybody is authorized to call read actions
|
237
|
+
You can now access the user object as instance variable in your decision maker.
|
141
238
|
|
142
|
-
|
143
|
-
|
239
|
+
```ruby
|
240
|
+
module Users
|
241
|
+
class Authentication < ApplicationPolicy::Authentication
|
242
|
+
def show?
|
243
|
+
@user # == <User:Instance>
|
144
244
|
end
|
245
|
+
end
|
145
246
|
|
146
|
-
|
147
|
-
|
247
|
+
class Authorization < ApplicationPolicy::Authorization
|
248
|
+
def show?
|
249
|
+
@user # == <User:Instance>
|
148
250
|
end
|
149
251
|
end
|
150
252
|
end
|
151
253
|
```
|
152
254
|
|
153
|
-
|
154
|
-
|
155
|
-
## Pass a custom error hash
|
156
|
-
You can pass an error hash to the exception and use this in your rescue method:
|
255
|
+
## Custom error data
|
256
|
+
If you write something into `@error` in our decision maker, you can access it in your rescue methods in the controller:
|
157
257
|
|
158
258
|
```ruby
|
259
|
+
module UsersPolicy
|
260
|
+
class Authentication < ApplicationPolicy::Authentication
|
261
|
+
def show?
|
262
|
+
@error = { code: 100 }
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
266
|
+
class Authorization < ApplicationPolicy::Authorization
|
267
|
+
def show?
|
268
|
+
@error = { code: 100 }
|
269
|
+
end
|
270
|
+
end
|
271
|
+
end
|
272
|
+
|
159
273
|
class ApplicationController < ActionController::Base
|
160
|
-
before_action :authenticate!, :authorize!
|
161
|
-
|
162
274
|
# ...
|
163
275
|
|
164
276
|
rescue_from ActiveEntry::NotAuthenticatedError, with: :not_authenticated
|
@@ -166,59 +278,134 @@ class ApplicationController < ActionController::Base
|
|
166
278
|
|
167
279
|
private
|
168
280
|
|
169
|
-
def not_authenticated
|
281
|
+
def not_authenticated exception
|
170
282
|
flash[:danger] = "You are not authenticated! Code: #{exception.error[:code]}"
|
171
283
|
redirect_to root_path
|
172
284
|
end
|
173
285
|
|
174
|
-
def not_authorized
|
286
|
+
def not_authorized exception
|
175
287
|
flash[:danger] = "You are not authorized to call this action! Code: #{exception.error[:code]}"
|
176
288
|
redirect_to root_path
|
177
289
|
end
|
290
|
+
end
|
291
|
+
```
|
178
292
|
|
179
|
-
|
180
|
-
error[:code] = "ERROR"
|
293
|
+
But you can pass in whatever you want into your error hash.
|
181
294
|
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
def authorized?(error)
|
186
|
-
error[:code] = "ERROR"
|
295
|
+
## Testing
|
296
|
+
You can easily test your policies in RSpec. Let's start with the generator:
|
187
297
|
|
188
|
-
|
298
|
+
```shell
|
299
|
+
$ rails g rspec:policy Users
|
300
|
+
```
|
189
301
|
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
302
|
+
This will generate a spec for the `UsersPolicy` located in `spec/policies/users_policy_spec.rb`
|
303
|
+
|
304
|
+
```ruby
|
305
|
+
require "rails_helper"
|
306
|
+
|
307
|
+
RSpec.describe UsersPolicy, type: :policy do
|
308
|
+
pending "add some examples to (or delete) #{__FILE__}"
|
194
309
|
end
|
195
310
|
```
|
196
311
|
|
197
|
-
|
198
|
-
The authentication/authorization is done in a before action. These Rails controller before callbacks are done in defined order. If you set an instance variable which is needed in the `authenticated?` or `authorized?` method, you have to call the before action after the other method again.
|
199
|
-
|
200
|
-
For example if you set `@user` in your controller in the `set_user` before action and you want to use the variable in `authorized?` action, you have to add the `authenticate!` or `authorize!` method after the `set_user` again, otherwise `@user` won't be available in `authenticate!` or `authorized?` yet.
|
312
|
+
Now you can easily test every decision maker with the `be_authenticated_for` and `be_authorized_for` matchers.
|
201
313
|
|
202
314
|
```ruby
|
203
|
-
|
204
|
-
|
205
|
-
|
315
|
+
require "rails_helper"
|
316
|
+
|
317
|
+
RSpec.describe UsersPolicy, type: :policy do
|
318
|
+
describe UsersPolicy::Authentication do
|
319
|
+
subject { UsersPolicy::Authentication }
|
320
|
+
|
321
|
+
context "anonymous" do
|
322
|
+
it { is_expected.to_not be_authenticated_for :index }
|
323
|
+
it { is_expected.to be_authenticated_for :new }
|
324
|
+
it { is_expected.to be_authenticated_for :create }
|
325
|
+
it { is_expected.to_not be_authenticated_for :edit }
|
326
|
+
it { is_expected.to_not be_authenticated_for :update }
|
327
|
+
it { is_expected.to_not be_authenticated_for :destroy }
|
328
|
+
it { is_expected.to_not be_authenticated_for :restore }
|
329
|
+
end
|
206
330
|
|
207
|
-
|
331
|
+
context "signed in" do
|
332
|
+
before { Current.user = build :user }
|
333
|
+
|
334
|
+
it { is_expected.to be_authenticated_for :index }
|
335
|
+
it { is_expected.to be_authenticated_for :new }
|
336
|
+
it { is_expected.to be_authenticated_for :create }
|
337
|
+
it { is_expected.to be_authenticated_for :edit }
|
338
|
+
it { is_expected.to be_authenticated_for :update }
|
339
|
+
it { is_expected.to be_authenticated_for :destroy }
|
340
|
+
it { is_expected.to be_authenticated_for :restore }
|
341
|
+
end
|
208
342
|
end
|
209
343
|
|
210
|
-
|
344
|
+
describe UsersPolicy::Authorization do
|
345
|
+
subject { UsersPolicy::Authorization }
|
346
|
+
|
347
|
+
let(:user) { build :user }
|
348
|
+
|
349
|
+
context "anonymous" do
|
350
|
+
it { is_expected.to be_authorized_for :index }
|
351
|
+
it { is_expected.to be_authorized_for :new }
|
352
|
+
it { is_expected.to be_authorized_for :create }
|
353
|
+
it { is_expected.to be_authorized_for :show, user: user }
|
354
|
+
it { is_expected.to_not be_authorized_for :edit, user: user }
|
355
|
+
it { is_expected.to_not be_authorized_for :update, user: user }
|
356
|
+
it { is_expected.to_not be_authorized_for :destroy, user: user }
|
357
|
+
it { is_expected.to_not be_authorized_for :restore, user: user }
|
358
|
+
end
|
211
359
|
|
212
|
-
|
213
|
-
|
360
|
+
context "if @user is Current.user" do
|
361
|
+
before { Current.user = user }
|
362
|
+
|
363
|
+
it { is_expected.to be_authorized_for :show, user: user }
|
364
|
+
it { is_expected.to be_authorized_for :edit, user: user }
|
365
|
+
it { is_expected.to be_authorized_for :update, user: user }
|
366
|
+
it { is_expected.to be_authorized_for :destroy, user: user }
|
367
|
+
it { is_expected.to be_authorized_for :restore, user: user }
|
368
|
+
end
|
369
|
+
|
370
|
+
context "if @user is not Current.user" do
|
371
|
+
before { Current.user = build :user }
|
372
|
+
|
373
|
+
it { is_expected.to be_authorized_for :show, user: user }
|
374
|
+
it { is_expected.to_not be_authorized_for :edit, user: user }
|
375
|
+
it { is_expected.to_not be_authorized_for :update, user: user }
|
376
|
+
it { is_expected.to_not be_authorized_for :destroy, user: user }
|
377
|
+
it { is_expected.to_not be_authorized_for :restore, user: user }
|
378
|
+
end
|
214
379
|
end
|
380
|
+
end
|
381
|
+
```
|
215
382
|
|
216
|
-
|
217
|
-
|
383
|
+
## Differences to Action Policy
|
384
|
+
[Action Policy](https://github.com/palkan/action_policy) is an awesome gem which works pretty similar to Active Entry. But there are some differences:
|
385
|
+
|
386
|
+
### Action Policy expects a performing subject and a target object
|
387
|
+
```ruby
|
388
|
+
class PostPolicy < ApplicationPolicy
|
389
|
+
def update?
|
390
|
+
# `user` is a performing subject,
|
391
|
+
# `record` is a target object (post we want to update)
|
392
|
+
user.admin? || (user.id == record.user_id)
|
218
393
|
end
|
219
394
|
end
|
220
395
|
```
|
221
396
|
|
397
|
+
In Active Entry you can pass in anything you want into the decision maker, which is accessible as instance variables. See Variables.
|
398
|
+
|
399
|
+
One strategy is not better than the other. It's just our preference.
|
400
|
+
|
401
|
+
### Policies in Action Policy are for Resources/Models
|
402
|
+
If you have a `Post` model, you have a `PostPolicy` in Action Policy. In Active Entry you create policies for controllers. So if you have a `PostsController`, you have a `PostsPolicy`.
|
403
|
+
We like to build access control logic around controller endpoints.
|
404
|
+
|
405
|
+
### Action Policy performs only authorization
|
406
|
+
Active Entry does technically also not provide authentication mechanisms. It's just that you place your authentication logic in an authentication decision maker.
|
407
|
+
We like both authentication and authorization logic in the same place but seperated hence `UsersPolicy::Authentication` and `UsersPolicy::Authorization`.
|
408
|
+
|
222
409
|
## Contributing
|
223
410
|
Create pull requests on Github and help us to improve this Gem. There are some guidelines to follow:
|
224
411
|
|
@@ -227,4 +414,4 @@ Create pull requests on Github and help us to improve this Gem. There are some g
|
|
227
414
|
* Document methods that aren't self-explaining (we are using [YARD](http://yardoc.org/))
|
228
415
|
|
229
416
|
## License
|
230
|
-
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
417
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require "active_support/concern"
|
2
|
+
|
3
|
+
module ActiveEntry
|
4
|
+
module ControllerConcern
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
class_methods do
|
8
|
+
# Methods .authenticate_now!, .authorize_now!, and .pass_now!
|
9
|
+
[:authenticate, :authorize, :pass].each do |method_name|
|
10
|
+
define_method "#{method_name}_now!" do
|
11
|
+
before_action do
|
12
|
+
args = {}
|
13
|
+
instance_variables.collect{ |v| v.to_s.remove("@").to_sym }.each do |name|
|
14
|
+
value = instance_variable_get ["@", name].join
|
15
|
+
next if value.nil?
|
16
|
+
args[name] = value
|
17
|
+
end
|
18
|
+
send "#{method_name}!", **args
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def verify_authentication!
|
24
|
+
after_action do
|
25
|
+
raise AuthenticationNotPerformedError.new(self.class, action_name) unless @__authentication_done
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def verify_authorization!
|
30
|
+
after_action do
|
31
|
+
raise AuthorizationNotPerformedError.new(self.class, action_name) unless @__authorization_done
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def pass! **args
|
37
|
+
authenticate! **args
|
38
|
+
authorize! **args
|
39
|
+
end
|
40
|
+
|
41
|
+
def authenticate! **args
|
42
|
+
policy_class::Authentication.pass! action_name, **args unless @__authentication_done
|
43
|
+
@__authentication_done = true
|
44
|
+
end
|
45
|
+
|
46
|
+
def authorize! **args
|
47
|
+
policy_class::Authorization.pass! action_name, **args unless @__authorization_done
|
48
|
+
@__authorization_done = true
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def policy_class
|
54
|
+
PolicyFinder.policy_for self.class.name.remove("Controller")
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module ActiveEntry
|
2
|
+
module ViewHelper
|
3
|
+
def authorized_for? controller_name, action, **args
|
4
|
+
controller_name = controller_name.to_s.camelize.remove "Controller"
|
5
|
+
policy = ActiveEntry::PolicyFinder.policy_for controller_name
|
6
|
+
policy::Authorization.pass? action, **args
|
7
|
+
end
|
8
|
+
|
9
|
+
def link_to_if_authorized name = nil, options = nil, html_options = nil, **args, &block
|
10
|
+
url = url_for options
|
11
|
+
method = options&.is_a?(Hash) && options[:method] ? options[:method].to_s.upcase : "GET"
|
12
|
+
|
13
|
+
recognized_path = Rails.application.routes.recognize_path(url, method: method)
|
14
|
+
|
15
|
+
authorized = authorized_for? recognized_path[:controller], recognized_path[:action], **args
|
16
|
+
|
17
|
+
link_to name, options, html_options, &block if authorized
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
data/lib/active_entry.rb
CHANGED
@@ -1,76 +1,13 @@
|
|
1
1
|
require "active_entry/version"
|
2
|
+
require "active_entry/generators"
|
2
3
|
require "active_entry/errors"
|
3
|
-
require "active_entry/
|
4
|
-
require "active_entry/
|
4
|
+
require "active_entry/base"
|
5
|
+
require "active_entry/policy_finder"
|
5
6
|
|
6
|
-
|
7
|
-
|
8
|
-
def authenticate!
|
9
|
-
general_decision_maker_method_name = :authenticated?
|
10
|
-
scoped_decision_maker_method_name = [action_name, :authenticated?].join("_").to_sym
|
11
|
-
|
12
|
-
general_decision_maker_defined = respond_to? general_decision_maker_method_name, true
|
13
|
-
scoped_decision_maker_defined = respond_to? scoped_decision_maker_method_name, true
|
14
|
-
|
15
|
-
# Check if a scoped decision maker method is defined and use it over
|
16
|
-
# general decision maker method.
|
17
|
-
decision_maker_to_use = scoped_decision_maker_defined ? scoped_decision_maker_method_name : general_decision_maker_method_name
|
18
|
-
|
19
|
-
# Raise an error if the #authenticate? action isn't defined.
|
20
|
-
#
|
21
|
-
# This ensures that you actually do authentication in your controller.
|
22
|
-
if !scoped_decision_maker_defined && !general_decision_maker_defined
|
23
|
-
raise ActiveEntry::AuthenticationNotPerformedError
|
24
|
-
end
|
25
|
-
|
26
|
-
error = {}
|
27
|
-
|
28
|
-
if method(decision_maker_to_use).arity > 0
|
29
|
-
is_authenticated = send decision_maker_to_use, error
|
30
|
-
else
|
31
|
-
is_authenticated = send decision_maker_to_use
|
32
|
-
end
|
33
|
-
|
34
|
-
# If the authenticated? method returns not true
|
35
|
-
# it raises the ActiveEntry::NotAuthenticatedError.
|
36
|
-
#
|
37
|
-
# Use the .rescue_from method from ActionController::Base
|
38
|
-
# to catch the exception and show the user a proper error message.
|
39
|
-
raise ActiveEntry::NotAuthenticatedError.new(error) unless is_authenticated == true
|
40
|
-
end
|
41
|
-
|
42
|
-
# Authorizes the user.
|
43
|
-
def authorize!
|
44
|
-
general_decision_maker_method_name = :authorized?
|
45
|
-
scoped_decision_maker_method_name = [action_name, :authorized?].join("_").to_sym
|
7
|
+
require_relative "../app/controllers/concerns/active_entry/concern" if defined?(ActionController::Base)
|
8
|
+
require 'active_entry/railtie' if defined?(Rails)
|
46
9
|
|
47
|
-
|
48
|
-
scoped_decision_maker_defined = respond_to? scoped_decision_maker_method_name, true
|
10
|
+
require "active_support/inflector"
|
49
11
|
|
50
|
-
|
51
|
-
# general decision maker method.
|
52
|
-
decision_maker_to_use = scoped_decision_maker_defined ? scoped_decision_maker_method_name : general_decision_maker_method_name
|
53
|
-
|
54
|
-
# Raise an error if the #authorize? action isn't defined.
|
55
|
-
#
|
56
|
-
# This ensures that you actually do authorization in your controller.
|
57
|
-
if !scoped_decision_maker_defined && !general_decision_maker_defined
|
58
|
-
raise ActiveEntry::AuthorizationNotPerformedError
|
59
|
-
end
|
60
|
-
|
61
|
-
error = {}
|
62
|
-
|
63
|
-
if method(decision_maker_to_use).arity > 0
|
64
|
-
is_authorized = send(decision_maker_to_use, error)
|
65
|
-
else
|
66
|
-
is_authorized = send(decision_maker_to_use)
|
67
|
-
end
|
68
|
-
|
69
|
-
# If the authorized? method does not return true
|
70
|
-
# it raises the ActiveEntry::NotAuthorizedError
|
71
|
-
#
|
72
|
-
# Use the .rescue_from method from ActionController::Base
|
73
|
-
# to catch the exception and show the user a proper error message.
|
74
|
-
raise ActiveEntry::NotAuthorizedError.new(error) unless is_authorized == true
|
75
|
-
end
|
12
|
+
module ActiveEntry
|
76
13
|
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
module ActiveEntry
|
2
|
+
class Base
|
3
|
+
class Authentication < Base
|
4
|
+
AUTH_ERROR = NotAuthenticatedError
|
5
|
+
|
6
|
+
def self.pass! method_name, **args
|
7
|
+
new(method_name, **args).pass!
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.pass? method_name, **args
|
11
|
+
new(method_name, **args).pass?
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class Authorization < Base
|
16
|
+
AUTH_ERROR = NotAuthorizedError
|
17
|
+
|
18
|
+
def self.pass! method_name, **args
|
19
|
+
new(method_name, **args).pass!
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.pass? method_name, **args
|
23
|
+
new(method_name, **args).pass?
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def initialize method_name, **args
|
28
|
+
@_method_name_to_entrify = method_name
|
29
|
+
@_args = args
|
30
|
+
@_args.each { |name, value| instance_variable_set ["@", name].join, value }
|
31
|
+
end
|
32
|
+
|
33
|
+
class << self
|
34
|
+
def pass! method_name, **args
|
35
|
+
Authentication.pass! method_name, **args
|
36
|
+
Authorization.pass! method_name, **args
|
37
|
+
end
|
38
|
+
|
39
|
+
def pass? method_name, **args
|
40
|
+
Authentication.pass? method_name, **args
|
41
|
+
Authorization.pass? method_name, **args
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
|
46
|
+
def pass!
|
47
|
+
pass? or raise self.class::AUTH_ERROR.new(@error, @_method_name_to_entrify, @_args)
|
48
|
+
end
|
49
|
+
|
50
|
+
def pass?
|
51
|
+
decision_maker_method.call == true
|
52
|
+
end
|
53
|
+
|
54
|
+
def success
|
55
|
+
true
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
def decision_maker_method
|
61
|
+
decision_maker_method_name = [@_method_name_to_entrify, "?"].join
|
62
|
+
raise DecisionMakerMethodNotDefinedError.new(self.class, decision_maker_method_name) unless respond_to?(decision_maker_method_name)
|
63
|
+
method decision_maker_method_name
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
data/lib/active_entry/errors.rb
CHANGED
@@ -1,66 +1,65 @@
|
|
1
|
-
# @author Tobias Feistmantl
|
2
1
|
module ActiveEntry
|
3
|
-
|
4
|
-
# Other, more specific, errors inherit from this one.
|
5
|
-
#
|
6
|
-
# @raise [AuthorizationError]
|
7
|
-
# if something generic is happening.
|
8
|
-
class AuthorizationError < StandardError
|
2
|
+
class Error < StandardError
|
9
3
|
end
|
10
4
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
5
|
+
class AuthError < Error
|
6
|
+
attr_reader :error, :method, :arguments
|
7
|
+
|
8
|
+
def initialize error, method, arguments
|
9
|
+
@error = error
|
10
|
+
@method = method
|
11
|
+
@arguments = arguments
|
12
|
+
@message = "Not authenticated/authorized for method ##{@method}"
|
13
|
+
|
14
|
+
super @message
|
15
|
+
end
|
17
16
|
end
|
18
17
|
|
19
|
-
|
20
|
-
|
21
|
-
# @raise [NotAuthorizedError]
|
22
|
-
# if authorized? isn't returning true.
|
23
|
-
#
|
24
|
-
# @note
|
25
|
-
# Should always be called at the end
|
26
|
-
# of the #authorize! method.
|
27
|
-
class NotAuthorizedError < AuthorizationError
|
28
|
-
attr_reader :error
|
18
|
+
class NotAuthenticatedError < AuthError
|
19
|
+
end
|
29
20
|
|
30
|
-
|
31
|
-
|
21
|
+
class NotAuthorizedError < AuthError
|
22
|
+
end
|
23
|
+
|
24
|
+
class NotPerformedError < Error
|
25
|
+
attr_reader :class_name, :method
|
26
|
+
|
27
|
+
def initialize class_name, method
|
28
|
+
@class_name = class_name
|
29
|
+
@method = method
|
30
|
+
@message = "Auth not performed for #{@class_name}##{@method}."
|
31
|
+
|
32
|
+
super @message
|
32
33
|
end
|
33
34
|
end
|
34
35
|
|
36
|
+
class AuthenticationNotPerformedError < NotPerformedError
|
37
|
+
end
|
35
38
|
|
36
|
-
|
37
|
-
#
|
38
|
-
# @raise [AuthenticationError]
|
39
|
-
# if something generic happens.
|
40
|
-
class AuthenticationError < StandardError
|
39
|
+
class AuthorizationNotPerformedError < NotPerformedError
|
41
40
|
end
|
42
41
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
42
|
+
class NotDefinedError < Error
|
43
|
+
attr_reader :policy_name, :class_name
|
44
|
+
|
45
|
+
def initialize policy_name, class_name
|
46
|
+
@policy_name = policy_name
|
47
|
+
@class_name = class_name
|
48
|
+
@message = "Policy #{policy_name} for class #{@class_name} not defined."
|
49
|
+
|
50
|
+
super @message
|
51
|
+
end
|
49
52
|
end
|
50
53
|
|
51
|
-
|
52
|
-
|
53
|
-
# @raise [NotAuthenticatedError]
|
54
|
-
# if authenticated? isn't returning true.
|
55
|
-
#
|
56
|
-
# @note
|
57
|
-
# Should always be called at the end
|
58
|
-
# of the #authenticate! method.
|
59
|
-
class NotAuthenticatedError < AuthenticationError
|
60
|
-
attr_reader :error
|
54
|
+
class DecisionMakerMethodNotDefinedError < Error
|
55
|
+
attr_reader :policy_name, :decision_maker_method_name
|
61
56
|
|
62
|
-
def initialize
|
63
|
-
@
|
57
|
+
def initialize policy_name, decision_maker_method_name
|
58
|
+
@policy_name = policy_name
|
59
|
+
@decision_maker_method_name = decision_maker_method_name
|
60
|
+
@message = "Decision maker #{policy_name}##{decision_maker_method_name} is not defined."
|
61
|
+
|
62
|
+
super @message
|
64
63
|
end
|
65
64
|
end
|
66
65
|
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module ActiveEntry
|
2
|
+
class PolicyFinder
|
3
|
+
attr_reader :class_name
|
4
|
+
|
5
|
+
def initialize class_name
|
6
|
+
@class_name = class_name
|
7
|
+
end
|
8
|
+
|
9
|
+
class << self
|
10
|
+
def policy_for class_name
|
11
|
+
new(class_name).policy
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def policy
|
16
|
+
policy_class_name.safe_constantize or raise NotDefinedError.new(policy_class_name, @class_name)
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def policy_class_name
|
22
|
+
[@class_name, "Policy"].join
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
data/lib/active_entry/railtie.rb
CHANGED
@@ -1,9 +1,9 @@
|
|
1
|
+
require_relative '../../app/helpers/active_entry/view_helper'
|
2
|
+
|
1
3
|
module ActiveEntry
|
2
|
-
class Railtie <
|
3
|
-
initializer
|
4
|
-
ActiveSupport.on_load
|
5
|
-
::ActionController::Base.include(ActiveEntry)
|
6
|
-
end
|
4
|
+
class Railtie < Rails::Railtie
|
5
|
+
initializer "active_entry.view_helper" do
|
6
|
+
ActiveSupport.on_load(:action_view) { include ActiveEntry::ViewHelper }
|
7
7
|
end
|
8
8
|
end
|
9
|
-
end
|
9
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module ActiveEntry
|
2
|
+
module Rspec
|
3
|
+
module Matchers
|
4
|
+
extend ::RSpec::Matchers::DSL
|
5
|
+
|
6
|
+
matcher :be_authenticated_for do |action, **args|
|
7
|
+
match do |policy|
|
8
|
+
policy.pass? action, **args
|
9
|
+
end
|
10
|
+
|
11
|
+
description do
|
12
|
+
"be authenticated for #{action}"
|
13
|
+
end
|
14
|
+
|
15
|
+
failure_message do |policy|
|
16
|
+
"expected that #{policy} passes authentication for #{action}"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
matcher :be_authorized_for do |action, **args|
|
21
|
+
match do |policy|
|
22
|
+
policy.pass? action, **args
|
23
|
+
end
|
24
|
+
|
25
|
+
description do
|
26
|
+
"be authorized for #{action}"
|
27
|
+
end
|
28
|
+
|
29
|
+
failure_message do |policy|
|
30
|
+
"expected that #{policy} passes authorization for #{action}"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
module DSL
|
36
|
+
end
|
37
|
+
|
38
|
+
module PolicyExampleGroup
|
39
|
+
include ActiveEntry::Rspec::Matchers
|
40
|
+
|
41
|
+
def self.included(base)
|
42
|
+
base.metadata[:type] = :policy
|
43
|
+
base.extend ActiveEntry::Rspec::DSL
|
44
|
+
super
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
RSpec.configure do |config|
|
51
|
+
config.include(
|
52
|
+
ActiveEntry::Rspec::PolicyExampleGroup,
|
53
|
+
type: :policy,
|
54
|
+
file_path: %r{spec/policies}
|
55
|
+
)
|
56
|
+
end
|
data/lib/active_entry/version.rb
CHANGED
@@ -0,0 +1,9 @@
|
|
1
|
+
module ActiveEntry
|
2
|
+
class InstallGenerator < Rails::Generators::Base
|
3
|
+
source_root File.expand_path('templates', __dir__)
|
4
|
+
|
5
|
+
def create_application_policy
|
6
|
+
template "application_policy.rb", File.join("app/policies/application_policy.rb"), skip: true
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module <%= class_name %>Policy
|
2
|
+
class Authentication < ApplicationPolicy::Authentication
|
3
|
+
# It's all about decision makers. In your decision makers you tell
|
4
|
+
# Active Entry when and if somebody is authenticated/authorized.
|
5
|
+
#
|
6
|
+
# You can declare decision makers for any method you want.
|
7
|
+
# Just use the same name as your action and add a ? at the end.
|
8
|
+
|
9
|
+
# def index?
|
10
|
+
# end
|
11
|
+
|
12
|
+
# def new?
|
13
|
+
# end
|
14
|
+
|
15
|
+
# def create?
|
16
|
+
# end
|
17
|
+
|
18
|
+
# def show?
|
19
|
+
# end
|
20
|
+
|
21
|
+
# def edit?
|
22
|
+
# end
|
23
|
+
|
24
|
+
# def update?
|
25
|
+
# end
|
26
|
+
|
27
|
+
# def destroy?
|
28
|
+
# end
|
29
|
+
end
|
30
|
+
|
31
|
+
class Authorization < ApplicationPolicy::Authorization
|
32
|
+
# def index?
|
33
|
+
# end
|
34
|
+
|
35
|
+
# def new?
|
36
|
+
# end
|
37
|
+
|
38
|
+
# def create?
|
39
|
+
# end
|
40
|
+
|
41
|
+
# def show?
|
42
|
+
# end
|
43
|
+
|
44
|
+
# def edit?
|
45
|
+
# end
|
46
|
+
|
47
|
+
# def update?
|
48
|
+
# end
|
49
|
+
|
50
|
+
# def destroy?
|
51
|
+
# end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module Rspec
|
2
|
+
module Generators
|
3
|
+
class PolicyGenerator < Rails::Generators::NamedBase
|
4
|
+
source_root File.expand_path("templates", __dir__)
|
5
|
+
|
6
|
+
def create_policy_spec
|
7
|
+
template "policy_spec.rb", File.join("spec/policies", class_path, "#{file_name}_policy_spec.rb")
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: active_entry
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 2.0.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- TFM Agency GmbH
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2021-
|
12
|
+
date: 2021-04-15 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rails
|
@@ -81,8 +81,8 @@ dependencies:
|
|
81
81
|
- - ">="
|
82
82
|
- !ruby/object:Gem::Version
|
83
83
|
version: '0'
|
84
|
-
description: An easy and flexible access control system.
|
85
|
-
|
84
|
+
description: An easy and flexible access control system. Authentication and authorization
|
85
|
+
before a method/action is executed.
|
86
86
|
email:
|
87
87
|
- hello@tfm.agency
|
88
88
|
executables: []
|
@@ -92,11 +92,25 @@ files:
|
|
92
92
|
- MIT-LICENSE
|
93
93
|
- README.md
|
94
94
|
- Rakefile
|
95
|
+
- app/controllers/concerns/active_entry/concern.rb
|
96
|
+
- app/helpers/active_entry/view_helper.rb
|
95
97
|
- lib/active_entry.rb
|
96
|
-
- lib/active_entry/
|
98
|
+
- lib/active_entry/base.rb
|
97
99
|
- lib/active_entry/errors.rb
|
100
|
+
- lib/active_entry/generators.rb
|
101
|
+
- lib/active_entry/policy_finder.rb
|
98
102
|
- lib/active_entry/railtie.rb
|
103
|
+
- lib/active_entry/rspec.rb
|
99
104
|
- lib/active_entry/version.rb
|
105
|
+
- lib/generators/active_entry/install/USAGE
|
106
|
+
- lib/generators/active_entry/install/install_generator.rb
|
107
|
+
- lib/generators/active_entry/install/templates/application_policy.rb
|
108
|
+
- lib/generators/policy/USAGE
|
109
|
+
- lib/generators/policy/policy_generator.rb
|
110
|
+
- lib/generators/policy/templates/policy.rb
|
111
|
+
- lib/generators/rspec/USAGE
|
112
|
+
- lib/generators/rspec/policy_generator.rb
|
113
|
+
- lib/generators/rspec/templates/policy_spec.rb
|
100
114
|
- lib/tasks/active_entry_tasks.rake
|
101
115
|
homepage: https://github.com/TFM-Agency/active_entry
|
102
116
|
licenses:
|
@@ -1,82 +0,0 @@
|
|
1
|
-
# @author Tobias Feistmantl
|
2
|
-
#
|
3
|
-
# Helper methods for your controller
|
4
|
-
# to identify RESTful actions.
|
5
|
-
module ActiveEntry
|
6
|
-
def method_missing method_name, *args
|
7
|
-
method_name_str = method_name.to_s
|
8
|
-
|
9
|
-
if methods.include?(:action_name) && method_name_str.include?("_action?")
|
10
|
-
method_name_str.slice! "_action?"
|
11
|
-
|
12
|
-
if methods.include? method_name_str.to_sym
|
13
|
-
return method_name_str == action_name
|
14
|
-
end
|
15
|
-
end
|
16
|
-
|
17
|
-
super
|
18
|
-
end
|
19
|
-
|
20
|
-
# @return [Boolean]
|
21
|
-
# True if the called action
|
22
|
-
# is a only-read action.
|
23
|
-
def read_action?
|
24
|
-
action_name == 'index' ||
|
25
|
-
action_name == 'show'
|
26
|
-
end
|
27
|
-
|
28
|
-
# @return [Boolean]
|
29
|
-
# True if the called action
|
30
|
-
# is a write action.
|
31
|
-
def write_action?
|
32
|
-
action_name == 'new' ||
|
33
|
-
action_name == 'create' ||
|
34
|
-
action_name == 'edit' ||
|
35
|
-
action_name == 'update' ||
|
36
|
-
action_name == 'destroy'
|
37
|
-
end
|
38
|
-
|
39
|
-
# @return [Boolean]
|
40
|
-
# True if the called action
|
41
|
-
# is a change action.
|
42
|
-
def change_action?
|
43
|
-
action_name == 'edit' ||
|
44
|
-
action_name == 'update' ||
|
45
|
-
action_name == 'destroy'
|
46
|
-
end
|
47
|
-
|
48
|
-
# @note
|
49
|
-
# Also true for the pseudo
|
50
|
-
# update action `new`.
|
51
|
-
#
|
52
|
-
# @note
|
53
|
-
# Only true for create methods
|
54
|
-
# such as new and create.
|
55
|
-
#
|
56
|
-
# @return [Boolean]
|
57
|
-
# True if the called action
|
58
|
-
# is a create action.
|
59
|
-
def create_action?
|
60
|
-
action_name == 'new' ||
|
61
|
-
action_name == 'create'
|
62
|
-
end
|
63
|
-
|
64
|
-
# @note
|
65
|
-
# Also true for the pseudo
|
66
|
-
# update action `edit`.
|
67
|
-
#
|
68
|
-
# @return [Boolean]
|
69
|
-
# True if the called action
|
70
|
-
# is a update action.
|
71
|
-
def update_action?
|
72
|
-
action_name == 'edit' ||
|
73
|
-
action_name == 'update'
|
74
|
-
end
|
75
|
-
|
76
|
-
# @return [Boolean]
|
77
|
-
# True if it's a destroy action.
|
78
|
-
def destroy_action?
|
79
|
-
action_name == 'destroy'
|
80
|
-
end
|
81
|
-
alias delete_action? destroy_action?
|
82
|
-
end
|