active_entry 1.1.0 → 2.0.1
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 +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
|
+
[](https://badge.fury.io/rb/active_entry)
|
9
|
+
[](https://github.com/TFM-Agency/active_entry/actions/workflows/ci-rspec.yml)
|
10
|
+

|
11
|
+
[](https://codeclimate.com/github/TFM-Agency/active_entry/maintainability)
|
12
|
+
[](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
|