action_access 0.0.3 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 8064bbe60e63e09b2aff74ea089abdf60d3bfaf9
4
- data.tar.gz: 4e4568f7bf4292c9e83c314b6dc21ce2db04cdd1
3
+ metadata.gz: 3018e5d68f21d4a6f66e15a29edc67b654b8f82a
4
+ data.tar.gz: 0cf2e285d1f2116733bb9ec8c5e99862de9b300a
5
5
  SHA512:
6
- metadata.gz: f1ee3d030dafa6cff846430ec1ac922c2dea3ced3082eb6f07087df49c2e13c6068d1cda14d1120c8c9892f116ae55cb2e078a2b3359bbe1aad2ca14fd32829f
7
- data.tar.gz: a889877401c7eb09577c8b29cc8387f36b404c5fc8a0a13d2f1e7ab0e1bcc1fd0c386bb8cc8a0dedb6d234c9cf6111867b36b06c3c5a3d5c8f4bce8a78d7ebf7
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 report
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 relevant
17
- information as possible. Tests or a sample application showing how the
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 fix
21
- it. Your goal should be to make it easy for yourself and others to replicate
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.3)
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.2)
11
- actionpack (= 4.2.2)
12
- actionview (= 4.2.2)
13
- activejob (= 4.2.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.2)
17
- actionview (= 4.2.2)
18
- activesupport (= 4.2.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.1)
23
- actionview (4.2.2)
24
- activesupport (= 4.2.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.1)
29
- activejob (4.2.2)
30
- activesupport (= 4.2.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.2)
33
- activesupport (= 4.2.2)
32
+ activemodel (4.2.4)
33
+ activesupport (= 4.2.4)
34
34
  builder (~> 3.1)
35
- activerecord (4.2.2)
36
- activemodel (= 4.2.2)
37
- activesupport (= 4.2.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.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.0)
45
+ arel (6.0.3)
46
46
  builder (3.2.2)
47
47
  erubis (2.7.0)
48
- globalid (0.3.5)
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.2)
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.1)
56
+ mime-types (2.6.2)
57
57
  mini_portile (0.6.2)
58
- minitest (5.7.0)
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.2)
65
- actionmailer (= 4.2.2)
66
- actionpack (= 4.2.2)
67
- actionview (= 4.2.2)
68
- activejob (= 4.2.2)
69
- activemodel (= 4.2.2)
70
- activerecord (= 4.2.2)
71
- activesupport (= 4.2.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.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.6)
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.2)
84
- actionpack (= 4.2.2)
85
- activesupport (= 4.2.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.2.0)
90
- rack (~> 1.0)
91
- sprockets-rails (2.3.1)
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 handling models and their attributes.
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 the way to get the **clearance level** (role,
28
- user group, etc.), other than that it works out of the box.
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 `current_clearance_level` method that returns the proper
34
- clearance level for the current request. It can be a string or symbol and
35
- it doesn't matter if it's singular or plural, it'll be singularized.
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 `current_clearance_level` method tests if it can get
40
- `current_user.clearance_level` and defaults to `:guest` if not.
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
- `clearance_level` method to the user. With a role based authorization you
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 clearance_level
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 `current_clearance_level`
59
- with whatever logic that applies to your application.
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 current_clearance_level
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. The first parameter is the clearance
76
- level (plural or singular) and the second is the action or list of actions.
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, [:index, :show, :edit, :update]
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 edit articles.
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 second one.
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 statement you
127
- both **define** them and **set their permissions**. The only requirement is
128
- that the clearance levels from the authorizations match the one returned by
129
- `current_clearance_level`.
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 unathorized_access_redirection_path
166
- case current_user.clearance_level.to_sym
167
- when :admin then admin_root_path
168
- when :user then user_root_path
169
- else root_path
170
- end
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 level allows her to access 'admin/articles#edit'.
266
+ # True if the user's clearance levels allow her to access 'admin/articles#edit'.
237
267
  ```
238
268
 
239
- `can?` depends on a `clearance_level` method in the model so don't forget it.
240
- Continuing with the `User` model from before:
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
- belongs_to :role
278
+ has_and_belongs_to_many :roles
247
279
 
248
- def clearance_level
249
- role.name
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 as the
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 users to only those allowed to edit articles.
272
- @users.select { |user| keeper.lets? user.role.name, :edit, :articles }
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
- See [CONTRIBUTING.md](CONTRIBUTING.md).
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.
@@ -23,4 +23,3 @@ Gem::Specification.new do |s|
23
23
  s.add_development_dependency 'bundler', '~> 1.6'
24
24
  s.add_development_dependency 'rake', '~> 10.0'
25
25
  end
26
-
@@ -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
- def let(clearance_level, permissions)
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
- keeper.let clearance_level, permissions, self
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
- # Clearance level of the current user (override to customize).
45
- def current_clearance_level
46
- if defined? current_user and current_user.respond_to?(:clearance_level)
47
- current_user.clearance_level.to_s.to_sym
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
- not_authorized! unless keeper.lets? clearance_level, action, self.class
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.
@@ -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] = actions
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 certain action on a resource.
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 level allows to access 'articles#show'
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 level allows to access 'admin/articles#edit'
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
- keeper.lets? clearance_level, action, resource, options
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
- private
25
-
26
- # Accessor for the user's clearance level.
27
- #
28
- # Must be overridden to set the proper clearance level.
29
- #
30
- # == Example:
31
- # def clearance_level
32
- # role.name
33
- # end
34
- #
35
- def clearance_level
36
- :guest
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,3 +1,3 @@
1
1
  module ActionAccess
2
- VERSION = '0.0.3'
2
+ VERSION = '0.1.0'
3
3
  end
@@ -1,7 +1,7 @@
1
1
  require 'test_helper'
2
2
 
3
3
  class ArticlesControllerTest < ActionController::TestCase
4
- test "any access with no clearance level gets redirected" do
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 "any access with undefined clearance level gets redirected" do
14
- # The role doesn't exist (undefined)
15
- get :new, nil, {role: :super}
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, {role: :super}
18
+ post :create, nil, {roles: :root}
19
19
  assert_redirected_to root_url
20
20
  end
21
21
 
22
- test "any unauthorized access gets redirected" do
22
+ test "unauthorized accesses get redirected" do
23
23
  # The roles exist but aren't authorized.
24
- get :new, nil, {role: :user}
24
+ post :create, nil, {roles: :editor}
25
25
  assert_redirected_to root_url
26
26
 
27
- post :create, nil, {role: :editor}
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, {role: :admin} # Admins can create articles
32
+ get :new, nil, {roles: :admin}
33
33
  assert_response :success
34
34
 
35
- get :edit, {id: 1}, {role: :editor} # Editors can edit articles
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 :show, {id: 1}, {role: :user} # Users can view articles
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, {role: :admin}
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 key works properly
4
+ # Test that the :all keyword works properly
5
5
  test "anyone can do anything" do
6
- get :home, nil, {role: :admin}
6
+ get :home, nil, {roles: :admin}
7
7
  assert_response :success
8
8
 
9
- get :home, nil, {role: :undefined}
9
+ get :home, nil, {roles: :undefined}
10
10
  assert_response :success
11
11
 
12
12
  get :home
@@ -9,7 +9,7 @@ class ApplicationController < ActionController::Base
9
9
 
10
10
  private
11
11
 
12
- def current_clearance_level
13
- session[:role] || :guest
12
+ def current_clearance_levels
13
+ session[:roles] || :guest
14
14
  end
15
15
  end
@@ -1,7 +1,8 @@
1
1
  class ArticlesController < ApplicationController
2
2
  let :admin, :all
3
- let :editor, [:index, :show, :edit, :update]
4
- let :user, [:index, :show]
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
@@ -1,7 +1,7 @@
1
1
  class User < ActiveRecord::Base
2
2
  add_access_utilities
3
3
 
4
- def clearance_level
5
- role
4
+ def clearance_levels
5
+ roles.split(',')
6
6
  end
7
7
  end
@@ -2,7 +2,7 @@ class CreateUsers < ActiveRecord::Migration
2
2
  def change
3
3
  create_table :users do |t|
4
4
  t.string :username
5
- t.string :role
5
+ t.string :roles
6
6
 
7
7
  t.timestamps null: false
8
8
  end
@@ -13,11 +13,11 @@
13
13
 
14
14
  ActiveRecord::Schema.define(version: 20140926071026) do
15
15
 
16
- create_table "users", force: true do |t|
16
+ create_table "users", force: :cascade do |t|
17
17
  t.string "username"
18
- t.string "role"
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
@@ -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 @user.respond_to? :can?
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 @user.can?(:show, :articles)
16
- assert_not @user.can?(:create, :articles)
10
+ assert user.can?(:show, :articles)
11
+ assert_not user.can?(:create, :articles)
17
12
 
18
13
  # Admin
19
- assert @admin.can?(:create, :articles)
20
- assert @admin.can?(:edit, :articles)
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.3
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-22 00:00:00.000000000 Z
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