checkin 0.4.4 → 0.4.5
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +180 -82
- data/VERSION +1 -1
- data/checkin.gemspec +4 -2
- data/lib/checkin/filters.rb +2 -2
- data/lib/generators/checkin/subject_generator.rb +12 -0
- data/lib/generators/checkin/templates/subject.rb +32 -0
- metadata +5 -3
data/README.md
CHANGED
@@ -1,6 +1,19 @@
|
|
1
1
|
# Checkin
|
2
2
|
|
3
|
-
**Checkin** is an authorization gem for Ruby on Rails
|
3
|
+
**Checkin** is an authorization gem for Ruby on Rails.
|
4
|
+
|
5
|
+
## Features
|
6
|
+
|
7
|
+
* Handy [DSL](http://en.wikipedia.org/wiki/Domain-specific_language) to define roles and permissions with a declarative approach
|
8
|
+
* Check authorization for CRUD operations automatically
|
9
|
+
* Standard way to rescue from Authorization errors
|
10
|
+
* Authorization subject decoupled from model (compatible with any autentication system)
|
11
|
+
* Role-based authorization decoupled from role system (compatible with any role system)
|
12
|
+
* Decorator for `current_user` and other subject objects
|
13
|
+
* Scoped authorization rules
|
14
|
+
* Cascading authorization rules
|
15
|
+
* Simple: even complex authorization behaviour is understandable at glimpse and easily predictable
|
16
|
+
* Support for controller based mass assignment protection (*Coming Soon* – already implemented but still not tested and not documented)
|
4
17
|
|
5
18
|
## Installation
|
6
19
|
|
@@ -11,10 +24,26 @@ Put this in your Gemfile
|
|
11
24
|
and update your bundle
|
12
25
|
|
13
26
|
bundle install
|
27
|
+
|
28
|
+
|
29
|
+
## Generate a `*Subject` class
|
30
|
+
|
31
|
+
```
|
32
|
+
rails g checkin:subject SUBJECTNAME
|
33
|
+
```
|
34
|
+
|
35
|
+
_eg._
|
36
|
+
|
37
|
+
```
|
38
|
+
rails g checkin:subject user
|
39
|
+
```
|
14
40
|
|
15
41
|
## Usage
|
16
42
|
|
17
|
-
|
43
|
+
### Define a subject class
|
44
|
+
|
45
|
+
Create a **subject class** in your load path (you can use the `checkin:subject` generator as described above). A subject class is a decorator
|
46
|
+
around the role and authentication system in which you can define *roles* and *permissions* for a given subject with a simple [DSL](http://en.wikipedia.org/wiki/Domain-specific_language).
|
18
47
|
|
19
48
|
_ex._
|
20
49
|
|
@@ -29,114 +58,121 @@ class UserSubject < Checkin::Subject
|
|
29
58
|
!!subject_model
|
30
59
|
end
|
31
60
|
|
32
|
-
role :owner, :require => [:logged_in, :author], :method => :own do |object|
|
33
|
-
object && object.respond_to?(:author) && ( subject_model == object.author )
|
34
|
-
end
|
35
|
-
|
36
61
|
role :author, :require => :logged_in do
|
37
|
-
subject_model.has_role?(:
|
62
|
+
subject_model.has_role?(:author)
|
38
63
|
end
|
39
64
|
|
40
|
-
role :
|
41
|
-
|
65
|
+
role :owner, :require => [:author], :method => :own do |object|
|
66
|
+
object && object.respond_to?(:author) && ( subject_model == object.author )
|
42
67
|
end
|
43
68
|
|
44
69
|
role :administrator, :require => :logged_in, :alias => :admin do
|
45
70
|
subject_model.has_role?(:administrator)
|
46
71
|
end
|
47
72
|
|
48
|
-
role :mantainer, :require => :logged_in do
|
49
|
-
subject_model.has_role?(:mantainer)
|
50
|
-
end
|
51
|
-
|
52
|
-
role :staff, :require => :logged_in do
|
53
|
-
subject_model.has_role?(:administrator) ||
|
54
|
-
subject_model.has_role?(:mantainer) ||
|
55
|
-
subject_model.has_role?(:editor) ||
|
56
|
-
subject_model.has_role?(:author) ||
|
57
|
-
subject_model.has_role?(:contributor) ||
|
58
|
-
subject_model.has_role?(:photographer)
|
59
|
-
end
|
60
|
-
|
61
|
-
role :tester, :require => :logged_in do
|
62
|
-
subject_model.has_role?(:tester)
|
63
|
-
end
|
64
|
-
|
65
|
-
role :nexta_supervisor, :require => :logged_in do
|
66
|
-
subject_model.has_role?(:nexta)
|
67
|
-
end
|
68
|
-
|
69
|
-
role :self, :require => :logged_in do |object|
|
70
|
-
object && object.is_a?(User) && subject_model == object
|
71
|
-
end
|
72
|
-
|
73
73
|
#
|
74
74
|
# Permissions
|
75
75
|
#
|
76
76
|
|
77
|
-
scope :site do
|
78
|
-
permissions :for => [:messages, :replies] do
|
79
|
-
allow :logged_in
|
80
|
-
allow :guests, :to => [:show, :index]
|
81
|
-
deny :guests
|
82
|
-
end
|
83
|
-
permissions :for => :network do
|
84
|
-
allow [:administrators, :nexta_supervisors], :to => [:see]
|
85
|
-
deny
|
86
|
-
end
|
87
|
-
end
|
88
|
-
|
89
|
-
# Admin
|
90
77
|
scope :admin do
|
91
78
|
permissions do
|
92
|
-
allow :administrators
|
79
|
+
allow :administrators
|
80
|
+
allow :owners, :to => [:edit, :update]
|
93
81
|
deny
|
94
82
|
end
|
95
83
|
end
|
84
|
+
|
85
|
+
end
|
86
|
+
```
|
96
87
|
|
97
|
-
|
98
|
-
|
99
|
-
deny
|
100
|
-
end
|
88
|
+
Now you can instantiate a `UserSubject` to decorate any subject model. **NOTE**: you won't need to do this directly since the `checkin` method
|
89
|
+
will do this for you (see the section below).
|
101
90
|
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
allow :mantainers
|
106
|
-
deny
|
107
|
-
end
|
108
|
-
end
|
91
|
+
``` rb
|
92
|
+
subject = UserSubject.new(User.first, :scope => :admin)
|
93
|
+
```
|
109
94
|
|
110
|
-
|
111
|
-
scope :staff do
|
112
|
-
permissions do
|
113
|
-
allow :administrators, :mantainers
|
114
|
-
allow :editors, :to => [:edit, :update]
|
115
|
-
allow :authors, :to => [:new, :create]
|
116
|
-
allow :owners, :to => [:edit, :update]
|
117
|
-
deny
|
118
|
-
end
|
95
|
+
#### Checking role membership
|
119
96
|
|
120
|
-
|
121
|
-
allow :administrators, :mantainers
|
122
|
-
allow :logged_in, :to => [:index, :show]
|
123
|
-
deny
|
124
|
-
end
|
97
|
+
According to the definitions included in the subject class the subject decorator will provide some methods to check role memberships.
|
125
98
|
|
126
|
-
|
127
|
-
allow :authors, :to => :submit
|
128
|
-
end
|
99
|
+
_ex._
|
129
100
|
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
101
|
+
``` rb
|
102
|
+
subject.logged_in?
|
103
|
+
subject.guest?
|
104
|
+
subject.own?(Post.first)
|
105
|
+
```
|
134
106
|
|
135
|
-
|
107
|
+
#### Checking permissions
|
108
|
+
|
109
|
+
similarly the decorator allows you to check the permissions of the subject
|
110
|
+
|
111
|
+
``` rb
|
112
|
+
subject.can_edit?(Post.first)
|
113
|
+
```
|
114
|
+
|
115
|
+
### Subject DSL reference
|
116
|
+
|
117
|
+
#### Defining roles
|
118
|
+
|
119
|
+
A role can be defined through the method `role`.
|
120
|
+
|
121
|
+
* `role(name, options = {}, &block)`<br/>
|
122
|
+
Define a role named `name` and specify how to test the membership of a subject to this role with the `block` procedure. A role can be relative also to an object. _eg._ `owner` depends on `object.author`. **NOTE** keep in mind that roles are tested also in situations where `subject` and `object` are `nil`.
|
123
|
+
<br/><br/>
|
124
|
+
**Options**
|
125
|
+
* `:require` : Specify other memberships that are required to belong to this role
|
126
|
+
* `:alias` _or_ `:aliases` : Specify aliases that can be used in permissions to refer this role. _eg_ `connected` for `logged_in`
|
127
|
+
* `:method` : Specify the method that will be defined in the decorator to check the membership to this role. _eg_ `own?` for `owner`
|
128
|
+
|
129
|
+
|
130
|
+
#### Defining permissions
|
131
|
+
|
132
|
+
To define a permission you can use the `permissions` block. Inside the permission block you can call `allow` to define a rule that if satisfied immediately allow the subject to perform the related action and `deny` to define a rule that if satisfied immediately deny the subject to perform the related action.
|
133
|
+
|
134
|
+
Permissions can be both _scoped_ or _global_. To define a scope you can use the `scope` method. You can also define permissions for a specific resource or for a set of resources through passing a `:for` option to the `permissions` method.
|
135
|
+
|
136
|
+
Permissions blocks are evaluated in the same order they are written skipping them for scopes and resources that are not affected.
|
137
|
+
|
138
|
+
_eg._
|
139
|
+
``` rb
|
140
|
+
permissions :for => :comments do
|
141
|
+
allow :administrators, :mantainers
|
142
|
+
allow :logged_in, :to => [:create]
|
143
|
+
deny
|
136
144
|
end
|
137
145
|
```
|
138
146
|
|
139
|
-
|
147
|
+
Inside a permissions block `allow` and `deny` rules are checked consecutively. The checking process will immediately return if any rule is matched and the subject is allowed or denied according to the kind of the rule. If no rule are matched then the subject is allowed.
|
148
|
+
|
149
|
+
##### References
|
150
|
+
|
151
|
+
* `permissions(options = {}, &block)`<br/>
|
152
|
+
Define a permissions block<br/><br/>
|
153
|
+
**Options**<br/>
|
154
|
+
* `:for` : specify a resource or a set of resources for which these permissions are applied. If no `:for` option is specified the block is applied to any resource.
|
155
|
+
* `allow(*roles, options = {})`<br/>
|
156
|
+
Allow a subject that has a mebership to `roles` to perform an action specified by the `:to` option. If no `roles` are specified this rule **any role** is allowed. Similarly if no `:to` option is specified **any action** can be performed.<br/><br/>
|
157
|
+
**Options**<br/>
|
158
|
+
* `:to` : specify an action or a set of actions that the subject is allowed to perform if belonging to `*roles`.
|
159
|
+
* `deny(*roles, options = {})`<br/>
|
160
|
+
Deny a subject that has a mebership to `roles` to perform an action specified by the `:to` option. If no `roles` are specified this rule **no role** is allowed. Similarly if no `:to` option is specified **no action** can be performed.
|
161
|
+
<br/><br/>
|
162
|
+
**Options**
|
163
|
+
* `:to` : specify an action or a set of actions that the subject is allowed to perform if belonging to `*roles`.
|
164
|
+
* `scope(name)`<br/>
|
165
|
+
Defines a scope in which the permissions are applied
|
166
|
+
|
167
|
+
## Integration with the application
|
168
|
+
|
169
|
+
The `checkin` method define a before filter to a controller that is responsible to instantiate a subject decorator to the current subject model, to instantiate the current resource for crud actions that has one, and then to check permissions for the *current subject* to perform the *current action* on the *current resource* in the *current scope*.
|
170
|
+
|
171
|
+
If the check fails a `Checkin::AccessDenied` exception is raised.
|
172
|
+
|
173
|
+
The `checkin` method defines also some other methods available in controller and views to access the subject decorator and the resource object, by default `#subject` and `#object` (see the reference section below).
|
174
|
+
|
175
|
+
**NOTE** The resource object is instantiated and checked only if a `params[:id]` is present for the current action.
|
140
176
|
|
141
177
|
_ex._
|
142
178
|
|
@@ -159,8 +195,70 @@ class ApplicationController < ActionController::Base
|
|
159
195
|
end
|
160
196
|
```
|
161
197
|
|
198
|
+
##### References
|
199
|
+
|
200
|
+
* `checkin(options = {})`<br/>
|
201
|
+
Define the checkin before filter in the controller in which is invoked
|
202
|
+
**Options**
|
203
|
+
* `:scope` : Specify the current scope
|
204
|
+
* `:subject` : Specify the [underscored](http://api.rubyonrails.org/classes/ActiveSupport/Inflector.html#method-i-underscore) subject class name. Default to :user_subject
|
205
|
+
* `:as` : Specify the method name for the generated accessor to the subject decorator, default to `:subject`
|
206
|
+
* `:subject_model` : Specify the method that return the current subject model, default to `:current_user`
|
207
|
+
* `:object` : Specify a method accessible in the controller scope that returns the current resource, if none is specified such method is automatically generated by the `checkin` method
|
208
|
+
* `:find_object` : Specify a way to fetch the resource object passing a `String` or `Symbol` corresponding to a method name or a `Proc` that returns the resoruce. Default to `Proc.new { model_class.find(params[:id]) }`
|
209
|
+
* `:skip_authorization` : Specify to skip the authorization process
|
210
|
+
* `:rescue_with` : Specify a procedure that is invoked to rescue from `Checkin::AccessDenied` exceptions
|
211
|
+
|
212
|
+
_eg._
|
213
|
+
|
214
|
+
``` rb
|
215
|
+
checkin(:subject => :user_subject, :scope => nil, :skip_authorization => false, :object => :object, rescue_with => lambda {
|
216
|
+
if subject.guest?
|
217
|
+
redirect_to new_user_session_path
|
218
|
+
else
|
219
|
+
render :text => "Not Authorized", :status => 403
|
220
|
+
end
|
221
|
+
})
|
222
|
+
```
|
223
|
+
|
224
|
+
|
225
|
+
### Helpers
|
226
|
+
|
227
|
+
The `subject` and `object` methods (or what they are configured to be named) are also invokable from views. Since the `#subject` method returns the subject decorator it's safe to call test methods on it even if no subject is logged in. So you can replace `current_user.try(:administrator?)` with `subject.administrator?`. Also thanks to the dependencies expressed with the role definition DSL you can easily test complex situation with a single method. _eg._
|
228
|
+
|
229
|
+
``` rb
|
230
|
+
# user_subject.rb
|
231
|
+
role :owner, :require => :author, :method => :own do |object|
|
232
|
+
object && object.respond_to?(:author) && ( subject_model == object.author )
|
233
|
+
end
|
234
|
+
```
|
235
|
+
|
236
|
+
`subject.own?(Post.new)` is the same of `current_user && current_user.has_role?(:author) && object && object.respond_to?(:author) && current_user == object.author`
|
237
|
+
|
238
|
+
|
239
|
+
## Permission to change attributes (in-controller mass assignment protection)
|
240
|
+
|
241
|
+
**NOTE** This feature is implemented but not tested yet (please help me to do that if you wish!)
|
242
|
+
|
243
|
+
You can define permission to set attributes through the method `permissions_to_set`
|
244
|
+
|
245
|
+
``` rb
|
246
|
+
# user_subject
|
247
|
+
permissions_to_set :published do
|
248
|
+
allow :editors, :administrators
|
249
|
+
deny
|
250
|
+
end
|
251
|
+
```
|
252
|
+
|
253
|
+
``` rb
|
254
|
+
editor.allowed_to_set?(:published, :on => post)
|
255
|
+
```
|
256
|
+
|
257
|
+
Denied params will be automatically removed from `params` hash in the `checkin` before filter
|
162
258
|
|
259
|
+
## Explaining and logging
|
163
260
|
|
261
|
+
The subject decorator has two methods `explain!` and `stop_explaining!` to enable/disable the logging of the details of the authorization process. By default `explain!` is automatically called in the development runtime.
|
164
262
|
|
165
263
|
---
|
166
264
|
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.4.
|
1
|
+
0.4.5
|
data/checkin.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = "checkin"
|
8
|
-
s.version = "0.4.
|
8
|
+
s.version = "0.4.5"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["mcasimir"]
|
12
|
-
s.date = "2012-05-
|
12
|
+
s.date = "2012-05-25"
|
13
13
|
s.description = "Checkin is an authorization gem for Ruby on Rails"
|
14
14
|
s.email = "maurizio.cas@gmail.com"
|
15
15
|
s.extra_rdoc_files = [
|
@@ -33,6 +33,8 @@ Gem::Specification.new do |s|
|
|
33
33
|
"lib/checkin/role.rb",
|
34
34
|
"lib/checkin/rule.rb",
|
35
35
|
"lib/checkin/subject.rb",
|
36
|
+
"lib/generators/checkin/subject_generator.rb",
|
37
|
+
"lib/generators/checkin/templates/subject.rb",
|
36
38
|
"test/helper.rb",
|
37
39
|
"test/test_checkin.rb"
|
38
40
|
]
|
data/lib/checkin/filters.rb
CHANGED
@@ -3,9 +3,9 @@ module Checkin
|
|
3
3
|
extend ActiveSupport::Concern
|
4
4
|
|
5
5
|
module ClassMethods
|
6
|
-
# checkin(:subject => :
|
6
|
+
# checkin(:subject => :user_subject, :scope => nil, :skip_authorization => false, :object => :object, rescue_with => lambda {
|
7
7
|
# if subject.guest?
|
8
|
-
# redirect_to new_user_session_path
|
8
|
+
# redirect_to new_user_session_path
|
9
9
|
# else
|
10
10
|
# render :text => "Not Authorized", :status => 403
|
11
11
|
# end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module Checkin
|
2
|
+
module Generators
|
3
|
+
class SubjectGenerator < Rails::Generators::NamedBase
|
4
|
+
source_root File.expand_path('../templates', __FILE__)
|
5
|
+
|
6
|
+
def generate_subject
|
7
|
+
template 'subject.rb', File.join('app/models', class_path, "#{file_name}_subject.rb")
|
8
|
+
end
|
9
|
+
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
class <%= class_name %>Subject < Checkin::Subject
|
2
|
+
|
3
|
+
role :guest, :alias => :anonymous do
|
4
|
+
!subject_model
|
5
|
+
end
|
6
|
+
|
7
|
+
role :logged_in, :alias => [:connected] do
|
8
|
+
!!subject_model
|
9
|
+
end
|
10
|
+
|
11
|
+
# role :owner, :require => [:logged_in, :author], :method => :own do |object|
|
12
|
+
# object && object.respond_to?(:author) && ( subject_model == object.author )
|
13
|
+
# end
|
14
|
+
#
|
15
|
+
# role :administrator, :require => :logged_in, :alias => :admin do
|
16
|
+
# subject_model.has_role?(:administrator)
|
17
|
+
# end
|
18
|
+
|
19
|
+
#
|
20
|
+
# Permissions
|
21
|
+
#
|
22
|
+
|
23
|
+
# Admin
|
24
|
+
|
25
|
+
# scope :admin do
|
26
|
+
# permissions do
|
27
|
+
# allow :administrators
|
28
|
+
# deny
|
29
|
+
# end
|
30
|
+
# end
|
31
|
+
|
32
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: checkin
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.4.
|
4
|
+
version: 0.4.5
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-05-
|
12
|
+
date: 2012-05-25 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: jeweler
|
@@ -51,6 +51,8 @@ files:
|
|
51
51
|
- lib/checkin/role.rb
|
52
52
|
- lib/checkin/rule.rb
|
53
53
|
- lib/checkin/subject.rb
|
54
|
+
- lib/generators/checkin/subject_generator.rb
|
55
|
+
- lib/generators/checkin/templates/subject.rb
|
54
56
|
- test/helper.rb
|
55
57
|
- test/test_checkin.rb
|
56
58
|
homepage: http://github.com/mcasimir/checkin
|
@@ -68,7 +70,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
68
70
|
version: '0'
|
69
71
|
segments:
|
70
72
|
- 0
|
71
|
-
hash: -
|
73
|
+
hash: -2281754506523548066
|
72
74
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
73
75
|
none: false
|
74
76
|
requirements:
|