action_access 0.0.3 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +34 -0
- data/CONTRIBUTING.md +19 -14
- data/Gemfile.lock +41 -41
- data/README.md +81 -39
- data/action_access.gemspec +0 -1
- data/lib/action_access/controller_additions.rb +38 -10
- data/lib/action_access/keeper.rb +6 -4
- data/lib/action_access/user_utilities.rb +32 -17
- data/lib/action_access/version.rb +1 -1
- data/test/controllers/articles_controller_test.rb +33 -11
- data/test/controllers/secrets_controller_test.rb +7 -1
- data/test/controllers/static_controller_test.rb +3 -3
- data/test/dummy/app/controllers/application_controller.rb +2 -2
- data/test/dummy/app/controllers/articles_controller.rb +3 -2
- data/test/dummy/app/models/user.rb +2 -2
- data/test/dummy/db/migrate/20140926071026_create_users.rb +1 -1
- data/test/dummy/db/schema.rb +4 -4
- data/test/models/user_test.rb +31 -10
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3018e5d68f21d4a6f66e15a29edc67b654b8f82a
|
|
4
|
+
data.tar.gz: 0cf2e285d1f2116733bb9ec8c5e99862de9b300a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: daa68edbd6d9f3a91e96d20c4e4e957eb7873b637eac6b0639b20d83069a9d2187152d005a059c34e68512c0be3bded5143f91d8b72919d6e4fd6bec0156f141
|
|
7
|
+
data.tar.gz: 4578a0f74cf68ab2b9687a710b36991b950073cddfcaf7b31cdcae37444407cc3f755592c9eb481d38a375b3540f7861d4dbbe91698f73a14a7e4f2db1a965ed
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
Changelog
|
|
2
|
+
=========
|
|
3
|
+
|
|
4
|
+
0.1.0
|
|
5
|
+
-----
|
|
6
|
+
|
|
7
|
+
**Features:**
|
|
8
|
+
|
|
9
|
+
- Support sessions with multiple clearance levels (e.g. users with many roles).
|
|
10
|
+
- Avoid repetition when defining clearance levels with the same permissions.
|
|
11
|
+
|
|
12
|
+
**API:**
|
|
13
|
+
|
|
14
|
+
- Use `current_clearance_levels` (plural) instead of `current_clearance_level`.
|
|
15
|
+
- Use `clearance_levels` (plural) instead of `clearance_level` (model method).
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
0.0.3
|
|
19
|
+
-----
|
|
20
|
+
|
|
21
|
+
- Update dependencies.
|
|
22
|
+
- Support Rails 2.2.2.
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
0.0.2
|
|
26
|
+
-----
|
|
27
|
+
|
|
28
|
+
- Speed up tests with in-memory database.
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
0.0.1
|
|
32
|
+
-----
|
|
33
|
+
|
|
34
|
+
- Initial release.
|
data/CONTRIBUTING.md
CHANGED
|
@@ -1,24 +1,29 @@
|
|
|
1
1
|
## Please read before contributing
|
|
2
2
|
|
|
3
3
|
1. If you have questions about Action Access, use the
|
|
4
|
-
[Mailing List](https://groups.google.com/d/forum/action_access),
|
|
5
|
-
[Gitter's chat](https://gitter.im/matiasgagliano/action_access) or
|
|
6
|
-
[Stack Overflow](https://stackoverflow.com/questions/tagged/action_access).
|
|
7
|
-
**DO NOT** post questions here.
|
|
4
|
+
[Mailing List](https://groups.google.com/d/forum/action_access),
|
|
5
|
+
[Gitter's chat](https://gitter.im/matiasgagliano/action_access) or
|
|
6
|
+
[Stack Overflow](https://stackoverflow.com/questions/tagged/action_access).
|
|
7
|
+
**DO NOT** post questions here.
|
|
8
8
|
|
|
9
9
|
2. If you find a security bug, **DO NOT** submit an issue here.
|
|
10
|
-
Please send an e-mail to [mgag.issues@gmail.com](mailto:mgag.issues@gmail.com)
|
|
11
|
-
instead.
|
|
10
|
+
Please send an e-mail to [mgag.issues@gmail.com](mailto:mgag.issues@gmail.com)
|
|
11
|
+
instead.
|
|
12
12
|
|
|
13
13
|
3. Do a small search on the issues tracker before submitting your issue to
|
|
14
|
-
see if it was already reported or fixed. In case it wasn't, create your
|
|
15
|
-
with a **clear description** of the issue and a **code sample** that
|
|
16
|
-
demonstrates it. Include Rails and Action Access versions, and as much
|
|
17
|
-
information as possible. Tests or a sample application showing how
|
|
18
|
-
expected behavior is not occurring will be much appreciated.
|
|
14
|
+
see if it was already reported or fixed. In case it wasn't, create your
|
|
15
|
+
report with a **clear description** of the issue and a **code sample** that
|
|
16
|
+
demonstrates it. Include Rails and Action Access versions, and as much
|
|
17
|
+
relevant information as possible. Tests or a sample application showing how
|
|
18
|
+
the expected behavior is not occurring will be much appreciated.
|
|
19
19
|
|
|
20
|
-
The more information you give, the easier it becomes to track the issue and
|
|
21
|
-
it. Your goal should be to make it easy for yourself and others to
|
|
22
|
-
the bug and figure out a fix.
|
|
20
|
+
The more information you give, the easier it becomes to track the issue and
|
|
21
|
+
fix it. Your goal should be to make it easy for yourself and others to
|
|
22
|
+
replicate the bug and figure out a fix.
|
|
23
|
+
|
|
24
|
+
4. **ALWAYS** open an issue (issue tracker) before submitting a pull request to
|
|
25
|
+
discuss the changes and the best ways to implement them, it won't be accepted
|
|
26
|
+
otherwise. Describe your proposal, why is it relevant? For which cases are
|
|
27
|
+
those changes or new features needed?
|
|
23
28
|
|
|
24
29
|
Thanks for your time and support!
|
data/Gemfile.lock
CHANGED
|
@@ -1,94 +1,94 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
action_access (0.0
|
|
4
|
+
action_access (0.1.0)
|
|
5
5
|
rails (~> 4.1)
|
|
6
6
|
|
|
7
7
|
GEM
|
|
8
8
|
remote: https://rubygems.org/
|
|
9
9
|
specs:
|
|
10
|
-
actionmailer (4.2.
|
|
11
|
-
actionpack (= 4.2.
|
|
12
|
-
actionview (= 4.2.
|
|
13
|
-
activejob (= 4.2.
|
|
10
|
+
actionmailer (4.2.4)
|
|
11
|
+
actionpack (= 4.2.4)
|
|
12
|
+
actionview (= 4.2.4)
|
|
13
|
+
activejob (= 4.2.4)
|
|
14
14
|
mail (~> 2.5, >= 2.5.4)
|
|
15
15
|
rails-dom-testing (~> 1.0, >= 1.0.5)
|
|
16
|
-
actionpack (4.2.
|
|
17
|
-
actionview (= 4.2.
|
|
18
|
-
activesupport (= 4.2.
|
|
16
|
+
actionpack (4.2.4)
|
|
17
|
+
actionview (= 4.2.4)
|
|
18
|
+
activesupport (= 4.2.4)
|
|
19
19
|
rack (~> 1.6)
|
|
20
20
|
rack-test (~> 0.6.2)
|
|
21
21
|
rails-dom-testing (~> 1.0, >= 1.0.5)
|
|
22
|
-
rails-html-sanitizer (~> 1.0, >= 1.0.
|
|
23
|
-
actionview (4.2.
|
|
24
|
-
activesupport (= 4.2.
|
|
22
|
+
rails-html-sanitizer (~> 1.0, >= 1.0.2)
|
|
23
|
+
actionview (4.2.4)
|
|
24
|
+
activesupport (= 4.2.4)
|
|
25
25
|
builder (~> 3.1)
|
|
26
26
|
erubis (~> 2.7.0)
|
|
27
27
|
rails-dom-testing (~> 1.0, >= 1.0.5)
|
|
28
|
-
rails-html-sanitizer (~> 1.0, >= 1.0.
|
|
29
|
-
activejob (4.2.
|
|
30
|
-
activesupport (= 4.2.
|
|
28
|
+
rails-html-sanitizer (~> 1.0, >= 1.0.2)
|
|
29
|
+
activejob (4.2.4)
|
|
30
|
+
activesupport (= 4.2.4)
|
|
31
31
|
globalid (>= 0.3.0)
|
|
32
|
-
activemodel (4.2.
|
|
33
|
-
activesupport (= 4.2.
|
|
32
|
+
activemodel (4.2.4)
|
|
33
|
+
activesupport (= 4.2.4)
|
|
34
34
|
builder (~> 3.1)
|
|
35
|
-
activerecord (4.2.
|
|
36
|
-
activemodel (= 4.2.
|
|
37
|
-
activesupport (= 4.2.
|
|
35
|
+
activerecord (4.2.4)
|
|
36
|
+
activemodel (= 4.2.4)
|
|
37
|
+
activesupport (= 4.2.4)
|
|
38
38
|
arel (~> 6.0)
|
|
39
|
-
activesupport (4.2.
|
|
39
|
+
activesupport (4.2.4)
|
|
40
40
|
i18n (~> 0.7)
|
|
41
41
|
json (~> 1.7, >= 1.7.7)
|
|
42
42
|
minitest (~> 5.1)
|
|
43
43
|
thread_safe (~> 0.3, >= 0.3.4)
|
|
44
44
|
tzinfo (~> 1.1)
|
|
45
|
-
arel (6.0.
|
|
45
|
+
arel (6.0.3)
|
|
46
46
|
builder (3.2.2)
|
|
47
47
|
erubis (2.7.0)
|
|
48
|
-
globalid (0.3.
|
|
48
|
+
globalid (0.3.6)
|
|
49
49
|
activesupport (>= 4.1.0)
|
|
50
50
|
i18n (0.7.0)
|
|
51
51
|
json (1.8.3)
|
|
52
|
-
loofah (2.0.
|
|
52
|
+
loofah (2.0.3)
|
|
53
53
|
nokogiri (>= 1.5.9)
|
|
54
54
|
mail (2.6.3)
|
|
55
55
|
mime-types (>= 1.16, < 3)
|
|
56
|
-
mime-types (2.6.
|
|
56
|
+
mime-types (2.6.2)
|
|
57
57
|
mini_portile (0.6.2)
|
|
58
|
-
minitest (5.
|
|
58
|
+
minitest (5.8.1)
|
|
59
59
|
nokogiri (1.6.6.2)
|
|
60
60
|
mini_portile (~> 0.6.0)
|
|
61
61
|
rack (1.6.4)
|
|
62
62
|
rack-test (0.6.3)
|
|
63
63
|
rack (>= 1.0)
|
|
64
|
-
rails (4.2.
|
|
65
|
-
actionmailer (= 4.2.
|
|
66
|
-
actionpack (= 4.2.
|
|
67
|
-
actionview (= 4.2.
|
|
68
|
-
activejob (= 4.2.
|
|
69
|
-
activemodel (= 4.2.
|
|
70
|
-
activerecord (= 4.2.
|
|
71
|
-
activesupport (= 4.2.
|
|
64
|
+
rails (4.2.4)
|
|
65
|
+
actionmailer (= 4.2.4)
|
|
66
|
+
actionpack (= 4.2.4)
|
|
67
|
+
actionview (= 4.2.4)
|
|
68
|
+
activejob (= 4.2.4)
|
|
69
|
+
activemodel (= 4.2.4)
|
|
70
|
+
activerecord (= 4.2.4)
|
|
71
|
+
activesupport (= 4.2.4)
|
|
72
72
|
bundler (>= 1.3.0, < 2.0)
|
|
73
|
-
railties (= 4.2.
|
|
73
|
+
railties (= 4.2.4)
|
|
74
74
|
sprockets-rails
|
|
75
75
|
rails-deprecated_sanitizer (1.0.3)
|
|
76
76
|
activesupport (>= 4.2.0.alpha)
|
|
77
|
-
rails-dom-testing (1.0.
|
|
77
|
+
rails-dom-testing (1.0.7)
|
|
78
78
|
activesupport (>= 4.2.0.beta, < 5.0)
|
|
79
79
|
nokogiri (~> 1.6.0)
|
|
80
80
|
rails-deprecated_sanitizer (>= 1.0.1)
|
|
81
81
|
rails-html-sanitizer (1.0.2)
|
|
82
82
|
loofah (~> 2.0)
|
|
83
|
-
railties (4.2.
|
|
84
|
-
actionpack (= 4.2.
|
|
85
|
-
activesupport (= 4.2.
|
|
83
|
+
railties (4.2.4)
|
|
84
|
+
actionpack (= 4.2.4)
|
|
85
|
+
activesupport (= 4.2.4)
|
|
86
86
|
rake (>= 0.8.7)
|
|
87
87
|
thor (>= 0.18.1, < 2.0)
|
|
88
88
|
rake (10.4.2)
|
|
89
|
-
sprockets (3.
|
|
90
|
-
rack (
|
|
91
|
-
sprockets-rails (2.3.
|
|
89
|
+
sprockets (3.4.0)
|
|
90
|
+
rack (> 1, < 3)
|
|
91
|
+
sprockets-rails (2.3.3)
|
|
92
92
|
actionpack (>= 3.0)
|
|
93
93
|
activesupport (>= 3.0)
|
|
94
94
|
sprockets (>= 2.8, < 4.0)
|
data/README.md
CHANGED
|
@@ -6,7 +6,7 @@ Action Access is an access control system for Ruby on Rails. It provides a
|
|
|
6
6
|
modular and easy way to secure applications and handle permissions.
|
|
7
7
|
|
|
8
8
|
It works at controller level focusing on what **actions** are accessible for
|
|
9
|
-
the current user instead of
|
|
9
|
+
the current user instead of messing with models and their attributes.
|
|
10
10
|
|
|
11
11
|
It also provides utilities for thorough control and some useful view helpers.
|
|
12
12
|
|
|
@@ -24,45 +24,62 @@ gem 'action_access'
|
|
|
24
24
|
|
|
25
25
|
## Basic configuration
|
|
26
26
|
|
|
27
|
-
The most important setting is
|
|
28
|
-
user
|
|
27
|
+
The most important setting is how to get the **clearance levels** (roles,
|
|
28
|
+
credentials, user groups, etc.) for the current session, other than that it
|
|
29
|
+
works out of the box.
|
|
29
30
|
|
|
30
31
|
Action Access doesn't require users or authentication at all to function so
|
|
31
32
|
you can get creative with the way you set and identify clearance levels.
|
|
32
33
|
|
|
33
|
-
It only needs a `
|
|
34
|
-
clearance
|
|
35
|
-
|
|
34
|
+
It only needs a `current_clearance_levels` method that returns the
|
|
35
|
+
clearance levels granted for the current request. It can be a single clearance
|
|
36
|
+
level (string or symbol) or a list of them (array), and it doesn't matter if
|
|
37
|
+
they're singular or plural (they'll be singularized).
|
|
36
38
|
|
|
37
39
|
* With `current_user`:
|
|
38
40
|
|
|
39
|
-
The default `
|
|
40
|
-
`current_user.
|
|
41
|
+
The default `current_clearance_levels` method tests if it can get
|
|
42
|
+
`current_user.clearance_levels` and defaults to `:guest` if not.
|
|
41
43
|
|
|
42
44
|
So, if you already have a `current_user` method you just need to add a
|
|
43
|
-
`
|
|
45
|
+
`clearance_levels` method to the user. With a role based authorization you
|
|
44
46
|
may add the following to your `User` model:
|
|
45
47
|
|
|
46
48
|
```ruby
|
|
47
49
|
class User < ActiveRecord::Base
|
|
48
50
|
belongs_to :role
|
|
49
51
|
|
|
50
|
-
def
|
|
52
|
+
def clearance_levels
|
|
53
|
+
# Single role name
|
|
51
54
|
role.name
|
|
52
55
|
end
|
|
53
56
|
end
|
|
54
57
|
```
|
|
55
58
|
|
|
59
|
+
or
|
|
60
|
+
|
|
61
|
+
```ruby
|
|
62
|
+
class User < ActiveRecord::Base
|
|
63
|
+
has_and_belongs_to_many :roles
|
|
64
|
+
|
|
65
|
+
def clearance_levels
|
|
66
|
+
# Array of role names
|
|
67
|
+
roles.pluck(:name)
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
|
|
56
73
|
* No `current_user`:
|
|
57
74
|
|
|
58
|
-
If there's no `current_user` you need to override `
|
|
59
|
-
with whatever logic
|
|
75
|
+
If there's no `current_user` you need to override `current_clearance_levels`
|
|
76
|
+
with whatever logic applies to your application.
|
|
60
77
|
|
|
61
78
|
Continuing with the role based example, you might do something like this:
|
|
62
79
|
|
|
63
80
|
```ruby
|
|
64
81
|
class ApplicationController < ActionController::Base
|
|
65
|
-
def
|
|
82
|
+
def current_clearance_levels
|
|
66
83
|
session[:role] || :guest
|
|
67
84
|
end
|
|
68
85
|
end
|
|
@@ -72,8 +89,8 @@ it doesn't matter if it's singular or plural, it'll be singularized.
|
|
|
72
89
|
## Setting permissions
|
|
73
90
|
|
|
74
91
|
Permissions are set through authorization statements using the **let** class
|
|
75
|
-
method available in every controller.
|
|
76
|
-
|
|
92
|
+
method available in every controller. It takes the clearance levels (plural or
|
|
93
|
+
singular) first and the action or list of actions (array) as the last parameter.
|
|
77
94
|
|
|
78
95
|
As a simple example, to allow administrators (and only administrators in this
|
|
79
96
|
case) to delete articles you'd add the following to `ArticlesController`:
|
|
@@ -94,12 +111,14 @@ This will automatically **lock** the controller and only allow administrators
|
|
|
94
111
|
accessing the destroy action. **Every other request** pointing to the controller
|
|
95
112
|
**will be rejected** and redirected with an alert.
|
|
96
113
|
|
|
114
|
+
|
|
97
115
|
### Real-life example:
|
|
98
116
|
|
|
99
117
|
```ruby
|
|
100
118
|
class ArticlesController < ApplicationController
|
|
101
119
|
let :admins, :all
|
|
102
|
-
let :editors,
|
|
120
|
+
let :editors, :reviewers, [:edit, :update]
|
|
121
|
+
let :editors, :destroy
|
|
103
122
|
let :all, [:index, :show]
|
|
104
123
|
|
|
105
124
|
def index
|
|
@@ -112,21 +131,27 @@ end
|
|
|
112
131
|
|
|
113
132
|
These statements lock the controller and set the following:
|
|
114
133
|
* _Administrators_ (admins) are authorized to access any action.
|
|
115
|
-
* _Editors_ can list, view and
|
|
134
|
+
* _Editors_ can list, view, edit and destroy articles (can't create).
|
|
135
|
+
* _Reviewers_ can list, view and edit articles.
|
|
116
136
|
* _Anyone else_ can **only** list and view articles.
|
|
117
137
|
|
|
118
138
|
This case uses the special keyword `:all`, it means everyone if passed as the
|
|
119
|
-
first argument or every action if passed as the
|
|
139
|
+
first argument or every action if passed as the last one.
|
|
120
140
|
|
|
121
141
|
Again, any unauthorized request will be rejected and redirected with an alert.
|
|
122
142
|
|
|
143
|
+
|
|
123
144
|
### Note about clearance levels
|
|
124
145
|
|
|
125
146
|
Notice that in the previous examples we didn't need to define clearance levels
|
|
126
|
-
or roles anywhere else in the application. With the authorization
|
|
127
|
-
both **define** them and **set their permissions**. The only requirement is
|
|
128
|
-
that the clearance levels from the authorizations match
|
|
129
|
-
`
|
|
147
|
+
or roles anywhere else in the application. With the authorization statements
|
|
148
|
+
you both **define** them and **set their permissions**. The only requirement is
|
|
149
|
+
that the clearance levels from the authorizations match at least one from the
|
|
150
|
+
list returned by `current_clearance_levels`.
|
|
151
|
+
|
|
152
|
+
This makes it easier to embrace modular designs, makes controllers
|
|
153
|
+
self-contained because everything related to a controller is within the
|
|
154
|
+
controller and avoids leaving unnecessary or unused code after refactoring.
|
|
130
155
|
|
|
131
156
|
|
|
132
157
|
## Advanced configuration
|
|
@@ -151,6 +176,7 @@ end
|
|
|
151
176
|
To **unlock** a single controller (to make it "public") add `let :all, :all`,
|
|
152
177
|
this will allow anyone to access any action in the controller.
|
|
153
178
|
|
|
179
|
+
|
|
154
180
|
### Redirection path
|
|
155
181
|
|
|
156
182
|
By default any unauthorized (or not explicitly authorized) access will be
|
|
@@ -162,18 +188,21 @@ very clear `unauthorized_access_redirection_path` method.
|
|
|
162
188
|
```ruby
|
|
163
189
|
class ApplicationController < ActionController::Base
|
|
164
190
|
|
|
165
|
-
def
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
191
|
+
def unauthorized_access_redirection_path
|
|
192
|
+
# Ensure an array of symbols
|
|
193
|
+
clearance_levels = Array(current_user.clearance_levels).map(&:to_sym)
|
|
194
|
+
|
|
195
|
+
# Choose a redirection path
|
|
196
|
+
return admin_root_path if clearance_levels.include?(:admin)
|
|
197
|
+
return user_root_path if clearance_levels.include?(:user)
|
|
198
|
+
root_path
|
|
171
199
|
end
|
|
172
200
|
|
|
173
201
|
# ...
|
|
174
202
|
end
|
|
175
203
|
```
|
|
176
204
|
|
|
205
|
+
|
|
177
206
|
### Alert message
|
|
178
207
|
|
|
179
208
|
Redirections have a default alert message of "Not authorized.". To customize it
|
|
@@ -215,6 +244,7 @@ end
|
|
|
215
244
|
There are better ways to handle this particular case but it serves to outline
|
|
216
245
|
the use of `not_authorized!` inside actions.
|
|
217
246
|
|
|
247
|
+
|
|
218
248
|
### Model additions
|
|
219
249
|
|
|
220
250
|
Action Access is bundled with some model utilities too. By calling
|
|
@@ -233,20 +263,24 @@ of the resource and the namespace (optional). In the end it returns a boolean.
|
|
|
233
263
|
@user.can? :edit, :articles, namespace: :admin
|
|
234
264
|
@user.can? :edit, @admin_article # Admin::Article instance
|
|
235
265
|
@user.can? :edit, Admin::ArticlesController
|
|
236
|
-
# True if the user's clearance
|
|
266
|
+
# True if the user's clearance levels allow her to access 'admin/articles#edit'.
|
|
237
267
|
```
|
|
238
268
|
|
|
239
|
-
|
|
240
|
-
|
|
269
|
+
Just like the default `current_clearance_levels` in controllers, `can?`
|
|
270
|
+
depends on the `clearance_levels` method in the model too.
|
|
271
|
+
|
|
272
|
+
Following up the `User` model from before:
|
|
241
273
|
|
|
242
274
|
```ruby
|
|
243
275
|
class User < ActiveRecord::Base
|
|
244
276
|
add_access_utilities
|
|
245
277
|
|
|
246
|
-
|
|
278
|
+
has_and_belongs_to_many :roles
|
|
247
279
|
|
|
248
|
-
|
|
249
|
-
|
|
280
|
+
# Don't forget this!
|
|
281
|
+
def clearance_levels
|
|
282
|
+
# Array of role names
|
|
283
|
+
roles.pluck(:name)
|
|
250
284
|
end
|
|
251
285
|
end
|
|
252
286
|
```
|
|
@@ -264,12 +298,12 @@ permissions and who decides if a clearance level grants access or not.
|
|
|
264
298
|
|
|
265
299
|
It's available as `keeper` within controllers and views and as
|
|
266
300
|
`ActionAccess::Keeper.instance` anywhere else. You can use it to check
|
|
267
|
-
permissions with the `lets?` method, which takes the clearance level
|
|
268
|
-
first argument and the rest are the same as for `can?`.
|
|
301
|
+
permissions with the `lets?` method, which takes the clearance level (only one)
|
|
302
|
+
as the first argument and the rest are the same as for `can?`.
|
|
269
303
|
|
|
270
304
|
```ruby
|
|
271
|
-
# Filter a list of
|
|
272
|
-
|
|
305
|
+
# Filter a list of roles to only those that allow to edit articles.
|
|
306
|
+
roles.select { |role| keeper.lets? role, :edit, :articles }
|
|
273
307
|
```
|
|
274
308
|
|
|
275
309
|
|
|
@@ -282,4 +316,12 @@ Copyright (c) 2014 Matías A. Gagliano.
|
|
|
282
316
|
|
|
283
317
|
## Contributing
|
|
284
318
|
|
|
285
|
-
|
|
319
|
+
If you have *questions*, found an *issue* or want to submit a *pull request* you
|
|
320
|
+
must read the [CONTRIBUTING file](CONTRIBUTING.md).
|
|
321
|
+
|
|
322
|
+
- **DO NOT** use the issue tracker for **questions** or to require help, there
|
|
323
|
+
are other means for that (see the CONTRIBUTING file).
|
|
324
|
+
|
|
325
|
+
- **ALWAYS** open an issue before submitting a **pull request**, it won't be
|
|
326
|
+
accepted otherwise. Discussing changes beforehand will make your work much
|
|
327
|
+
more relevant.
|
data/action_access.gemspec
CHANGED
|
@@ -16,14 +16,32 @@ module ActionAccess
|
|
|
16
16
|
# Set an access rule for the current controller.
|
|
17
17
|
# It will automatically lock the controller if it wasn't already.
|
|
18
18
|
#
|
|
19
|
+
#
|
|
20
|
+
# == Parameters
|
|
21
|
+
#
|
|
22
|
+
# +clearance_levels+:: single clearance level (string or symbol) or list
|
|
23
|
+
# of them (list of parameters or array), either singular or plural.
|
|
24
|
+
# Accepts the special keyword +:all+ (every clearance level, even none).
|
|
25
|
+
#
|
|
26
|
+
# +permissions+:: controller action (string or symbol) or list of them (array).
|
|
27
|
+
# Accepts the special keyword +:all+ (every action in the controller).
|
|
28
|
+
#
|
|
29
|
+
#
|
|
19
30
|
# == Example:
|
|
20
|
-
# Add the following to ArticlesController to allow admins to edit articles.
|
|
21
|
-
# let :admin, [:edit, :update]
|
|
22
31
|
#
|
|
23
|
-
|
|
32
|
+
# class ArticlesControler < ApplicationController
|
|
33
|
+
# let :admins, :all # admins can do anything
|
|
34
|
+
# let :editors, :reviewers, [:edit, :update] # editors and reviewers can edit articles
|
|
35
|
+
# let :all, [:index, :show] # anyone can view articles
|
|
36
|
+
#
|
|
37
|
+
# # ...
|
|
38
|
+
# end
|
|
39
|
+
#
|
|
40
|
+
def let(*clearance_levels, permissions)
|
|
24
41
|
lock_access unless access_locked?
|
|
25
42
|
keeper = ActionAccess::Keeper.instance
|
|
26
|
-
|
|
43
|
+
clearance_levels = Array(clearance_levels).flatten
|
|
44
|
+
clearance_levels.each { |c| keeper.let c, permissions, self }
|
|
27
45
|
end
|
|
28
46
|
end
|
|
29
47
|
|
|
@@ -41,10 +59,19 @@ module ActionAccess
|
|
|
41
59
|
ActionAccess::Keeper.instance
|
|
42
60
|
end
|
|
43
61
|
|
|
44
|
-
#
|
|
45
|
-
def
|
|
46
|
-
|
|
47
|
-
|
|
62
|
+
# Current user's clearance levels (override to customize).
|
|
63
|
+
def current_clearance_levels
|
|
64
|
+
# Notify deprecation of `current_clearance_level` (singular)
|
|
65
|
+
if defined? current_clearance_level
|
|
66
|
+
ActiveSupport::Deprecation.warn \
|
|
67
|
+
'[Action Access] The use of "current_clearance_level" ' +
|
|
68
|
+
'is going to be deprecated in the next release, rename ' +
|
|
69
|
+
'it to "current_clearance_levels" (plural).'
|
|
70
|
+
return current_clearance_level
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
if defined?(current_user) and current_user.respond_to?(:clearance_levels)
|
|
74
|
+
current_user.clearance_levels
|
|
48
75
|
else
|
|
49
76
|
:guest
|
|
50
77
|
end
|
|
@@ -57,9 +84,10 @@ module ActionAccess
|
|
|
57
84
|
|
|
58
85
|
# Validate access to the current route.
|
|
59
86
|
def validate_access!
|
|
60
|
-
clearance_level = current_clearance_level
|
|
61
87
|
action = self.action_name
|
|
62
|
-
|
|
88
|
+
clearance_levels = Array(current_clearance_levels)
|
|
89
|
+
authorized = clearance_levels.any? { |c| keeper.lets? c, action, self.class }
|
|
90
|
+
not_authorized! unless authorized
|
|
63
91
|
end
|
|
64
92
|
|
|
65
93
|
# Redirect if not authorized.
|
data/lib/action_access/keeper.rb
CHANGED
|
@@ -7,10 +7,10 @@ module ActionAccess
|
|
|
7
7
|
end
|
|
8
8
|
|
|
9
9
|
# Set clearance to perform actions over a resource.
|
|
10
|
-
#
|
|
11
10
|
# Clearance level and resource can be either plural or singular.
|
|
12
11
|
#
|
|
13
12
|
# == Examples:
|
|
13
|
+
#
|
|
14
14
|
# let :user, :show, :profile
|
|
15
15
|
# let :user, :show, @profile
|
|
16
16
|
# let :user, :show, ProfilesController
|
|
@@ -26,15 +26,17 @@ module ActionAccess
|
|
|
26
26
|
actions = Array(actions).map(&:to_sym)
|
|
27
27
|
controller = get_controller_name(resource, options)
|
|
28
28
|
@rules[controller] ||= {}
|
|
29
|
-
@rules[controller][clearance_level]
|
|
29
|
+
@rules[controller][clearance_level] ||= []
|
|
30
|
+
@rules[controller][clearance_level] += actions
|
|
31
|
+
@rules[controller][clearance_level].uniq!
|
|
30
32
|
return nil
|
|
31
33
|
end
|
|
32
34
|
|
|
33
|
-
# Check if a given clearance level allows to perform
|
|
34
|
-
#
|
|
35
|
+
# Check if a given clearance level allows to perform an action on a resource.
|
|
35
36
|
# Clearance level and resource can be either plural or singular.
|
|
36
37
|
#
|
|
37
38
|
# == Examples:
|
|
39
|
+
#
|
|
38
40
|
# lets? :users, :create, :profiles
|
|
39
41
|
# lets? :users, :create, @profile
|
|
40
42
|
# lets? :users, :create, ProfilesController
|
|
@@ -1,39 +1,54 @@
|
|
|
1
1
|
module ActionAccess
|
|
2
2
|
module UserUtilities
|
|
3
3
|
# Check if the user is authorized to perform a given action.
|
|
4
|
-
#
|
|
5
4
|
# Resource can be either plural or singular.
|
|
6
5
|
#
|
|
7
6
|
# == Examples:
|
|
7
|
+
#
|
|
8
8
|
# user.can? :show, :articles
|
|
9
9
|
# user.can? :show, @article
|
|
10
10
|
# user.can? :show, ArticlesController
|
|
11
|
-
# # True if the user's clearance
|
|
11
|
+
# # True if any of the user's clearance levels allows to access 'articles#show'
|
|
12
12
|
#
|
|
13
13
|
# user.can? :edit, :articles, namespace: :admin
|
|
14
14
|
# user.can? :edit, @admin_article
|
|
15
15
|
# user.can? :edit, Admin::ArticlesController
|
|
16
|
-
# # True if the user's clearance
|
|
16
|
+
# # True if any of the user's clearance levels allows to access 'admin/articles#edit'
|
|
17
17
|
#
|
|
18
18
|
def can?(action, resource, options = {})
|
|
19
19
|
keeper = ActionAccess::Keeper.instance
|
|
20
|
-
|
|
20
|
+
clearance_levels = Array(clearance_levels())
|
|
21
|
+
clearance_levels.any? { |c| keeper.lets? c, action, resource, options }
|
|
21
22
|
end
|
|
22
23
|
|
|
23
24
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
25
|
+
# Accessor for the user's clearance levels.
|
|
26
|
+
#
|
|
27
|
+
# Must be *overridden* to set the proper clearance levels.
|
|
28
|
+
#
|
|
29
|
+
# == Examples:
|
|
30
|
+
#
|
|
31
|
+
# # Single clearance level (returns string)
|
|
32
|
+
# def clearance_levels
|
|
33
|
+
# role.name
|
|
34
|
+
# end
|
|
35
|
+
#
|
|
36
|
+
# # Multiple clearance levels (returns array)
|
|
37
|
+
# def clearance_levels
|
|
38
|
+
# roles.pluck(:name)
|
|
39
|
+
# end
|
|
40
|
+
#
|
|
41
|
+
def clearance_levels
|
|
42
|
+
# Notify deprecation of `clearance_level` (singular)
|
|
43
|
+
if defined? clearance_level
|
|
44
|
+
ActiveSupport::Deprecation.warn \
|
|
45
|
+
'[Action Access] The use of "clearance_level" in models ' +
|
|
46
|
+
'is going to be deprecated in the next release, rename ' +
|
|
47
|
+
'it to "clearance_levels" (plural).'
|
|
48
|
+
return clearance_level
|
|
37
49
|
end
|
|
50
|
+
|
|
51
|
+
:guest
|
|
52
|
+
end
|
|
38
53
|
end
|
|
39
54
|
end
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
require 'test_helper'
|
|
2
2
|
|
|
3
3
|
class ArticlesControllerTest < ActionController::TestCase
|
|
4
|
-
test "
|
|
4
|
+
test "accesses with no clearance level get redirected" do
|
|
5
5
|
# Undefined role
|
|
6
6
|
get :new
|
|
7
7
|
assert_redirected_to root_url
|
|
@@ -10,32 +10,54 @@ class ArticlesControllerTest < ActionController::TestCase
|
|
|
10
10
|
assert_redirected_to root_url
|
|
11
11
|
end
|
|
12
12
|
|
|
13
|
-
test "
|
|
14
|
-
# The role doesn't exist (
|
|
15
|
-
get :new, nil, {
|
|
13
|
+
test "accesses with undefined clearance level get redirected" do
|
|
14
|
+
# The role doesn't exist (haven't been defined)
|
|
15
|
+
get :new, nil, {roles: :root}
|
|
16
16
|
assert_redirected_to root_url
|
|
17
17
|
|
|
18
|
-
post :create, nil, {
|
|
18
|
+
post :create, nil, {roles: :root}
|
|
19
19
|
assert_redirected_to root_url
|
|
20
20
|
end
|
|
21
21
|
|
|
22
|
-
test "
|
|
22
|
+
test "unauthorized accesses get redirected" do
|
|
23
23
|
# The roles exist but aren't authorized.
|
|
24
|
-
|
|
24
|
+
post :create, nil, {roles: :editor}
|
|
25
25
|
assert_redirected_to root_url
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
get :edit, {id: 1}, {roles: :user}
|
|
28
28
|
assert_redirected_to root_url
|
|
29
29
|
end
|
|
30
30
|
|
|
31
31
|
test "authorized accesses aren't redirected" do
|
|
32
|
-
get :new, nil, {
|
|
32
|
+
get :new, nil, {roles: :admin}
|
|
33
33
|
assert_response :success
|
|
34
34
|
|
|
35
|
-
get :edit, {id: 1}, {
|
|
35
|
+
get :edit, {id: 1}, {roles: :editor}
|
|
36
|
+
assert_response :success
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
test "many clearance levels can be defined in one statement" do
|
|
40
|
+
get :index, nil, {roles: :editor}
|
|
36
41
|
assert_response :success
|
|
37
42
|
|
|
38
|
-
get :
|
|
43
|
+
get :index, nil, {roles: :cleaner}
|
|
39
44
|
assert_response :success
|
|
45
|
+
|
|
46
|
+
get :index, nil, {roles: :user}
|
|
47
|
+
assert_response :success
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
test "users can have many clearance levels" do
|
|
51
|
+
post :create, nil, {roles: [:editor, :cleaner]}
|
|
52
|
+
assert_redirected_to root_url
|
|
53
|
+
|
|
54
|
+
get :index, nil, {roles: [:editor, :cleaner]}
|
|
55
|
+
assert_response :success
|
|
56
|
+
|
|
57
|
+
get :edit, {id: 1}, {roles: [:editor, :cleaner]}
|
|
58
|
+
assert_response :success
|
|
59
|
+
|
|
60
|
+
delete :destroy, {id: 1}, {roles: [:editor, :cleaner]}
|
|
61
|
+
assert_redirected_to articles_url
|
|
40
62
|
end
|
|
41
63
|
end
|
|
@@ -4,7 +4,13 @@ class SecretsControllerTest < ActionController::TestCase
|
|
|
4
4
|
test "controllers are locked by default" do
|
|
5
5
|
# Test that the "lock_access" call in ApplicationController works properly.
|
|
6
6
|
# There are no access rules in SecretsController but it should be locked anyway.
|
|
7
|
-
get :index, nil, {
|
|
7
|
+
get :index, nil, {roles: :admin}
|
|
8
|
+
assert_redirected_to root_url
|
|
9
|
+
|
|
10
|
+
get :index, nil, {roles: :editor}
|
|
11
|
+
assert_redirected_to root_url
|
|
12
|
+
|
|
13
|
+
get :index
|
|
8
14
|
assert_redirected_to root_url
|
|
9
15
|
end
|
|
10
16
|
end
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
require 'test_helper'
|
|
2
2
|
|
|
3
3
|
class StaticControllerTest < ActionController::TestCase
|
|
4
|
-
# Test that the :all
|
|
4
|
+
# Test that the :all keyword works properly
|
|
5
5
|
test "anyone can do anything" do
|
|
6
|
-
get :home, nil, {
|
|
6
|
+
get :home, nil, {roles: :admin}
|
|
7
7
|
assert_response :success
|
|
8
8
|
|
|
9
|
-
get :home, nil, {
|
|
9
|
+
get :home, nil, {roles: :undefined}
|
|
10
10
|
assert_response :success
|
|
11
11
|
|
|
12
12
|
get :home
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
class ArticlesController < ApplicationController
|
|
2
2
|
let :admin, :all
|
|
3
|
-
let :editor, [:
|
|
4
|
-
let :
|
|
3
|
+
let :editor, [:edit, :update]
|
|
4
|
+
let :cleaner, :destroy
|
|
5
|
+
let :editor, :cleaner, :user, [:index, :show]
|
|
5
6
|
|
|
6
7
|
# GET /articles
|
|
7
8
|
def index
|
data/test/dummy/db/schema.rb
CHANGED
|
@@ -13,11 +13,11 @@
|
|
|
13
13
|
|
|
14
14
|
ActiveRecord::Schema.define(version: 20140926071026) do
|
|
15
15
|
|
|
16
|
-
create_table "users", force:
|
|
16
|
+
create_table "users", force: :cascade do |t|
|
|
17
17
|
t.string "username"
|
|
18
|
-
t.string "
|
|
19
|
-
t.datetime "created_at"
|
|
20
|
-
t.datetime "updated_at"
|
|
18
|
+
t.string "roles"
|
|
19
|
+
t.datetime "created_at", null: false
|
|
20
|
+
t.datetime "updated_at", null: false
|
|
21
21
|
end
|
|
22
22
|
|
|
23
23
|
end
|
data/test/models/user_test.rb
CHANGED
|
@@ -1,22 +1,43 @@
|
|
|
1
1
|
require 'test_helper'
|
|
2
2
|
|
|
3
3
|
class UserTest < ActiveSupport::TestCase
|
|
4
|
-
setup do
|
|
5
|
-
@admin ||= User.where({ username: 'flynn', role: 'admin' }).first_or_create
|
|
6
|
-
@user ||= User.where({ username: 'sam', role: 'user' }).first_or_create
|
|
7
|
-
end
|
|
8
|
-
|
|
9
4
|
test "the model gets extended" do
|
|
10
|
-
assert
|
|
5
|
+
assert user.respond_to? :can?
|
|
11
6
|
end
|
|
12
7
|
|
|
13
8
|
test "it allows to check permissions" do
|
|
14
9
|
# User
|
|
15
|
-
assert
|
|
16
|
-
assert_not
|
|
10
|
+
assert user.can?(:show, :articles)
|
|
11
|
+
assert_not user.can?(:create, :articles)
|
|
17
12
|
|
|
18
13
|
# Admin
|
|
19
|
-
assert
|
|
20
|
-
assert
|
|
14
|
+
assert admin.can?(:show, :articles)
|
|
15
|
+
assert admin.can?(:create, :articles)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
test "users can have more than one role" do
|
|
19
|
+
# Assert the mixed user is both an 'editor' and a 'cleaner'
|
|
20
|
+
assert mixed.can?(:show, :articles) # Editors and cleaners
|
|
21
|
+
assert mixed.can?(:edit, :articles) # Editors only
|
|
22
|
+
assert mixed.can?(:destroy, :articles) # Cleaners only
|
|
23
|
+
|
|
24
|
+
# Assert the mixed user is not an 'admin'
|
|
25
|
+
assert_not mixed.can?(:new, :articles)
|
|
26
|
+
assert_not mixed.can?(:create, :articles)
|
|
21
27
|
end
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
def admin
|
|
33
|
+
@admin ||= User.where({ username: 'flynn', roles: 'admin' }).first_or_create
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def mixed
|
|
37
|
+
@mixed ||= User.where({ username: 'clu', roles: 'editor,cleaner' }).first_or_create
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def user
|
|
41
|
+
@user ||= User.where({ username: 'sam', roles: 'user' }).first_or_create
|
|
42
|
+
end
|
|
22
43
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: action_access
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.0
|
|
4
|
+
version: 0.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Matías A. Gagliano
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2015-06
|
|
11
|
+
date: 2015-10-06 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rails
|
|
@@ -75,6 +75,7 @@ extra_rdoc_files: []
|
|
|
75
75
|
files:
|
|
76
76
|
- ".gitignore"
|
|
77
77
|
- ".travis.yml"
|
|
78
|
+
- CHANGELOG.md
|
|
78
79
|
- CONTRIBUTING.md
|
|
79
80
|
- Gemfile
|
|
80
81
|
- Gemfile.lock
|