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 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
- Create a **subject class** in your load path
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?(:contributor) || subject_model.has_role?(:author)
62
+ subject_model.has_role?(:author)
38
63
  end
39
64
 
40
- role :editor, :require => :logged_in do
41
- subject_model.has_role?(:editor)
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, :mantainers
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
- scope :api do
98
- allow :staff
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
- # Mantainer
103
- scope :mantainer do
104
- permissions do
105
- allow :mantainers
106
- deny
107
- end
108
- end
91
+ ``` rb
92
+ subject = UserSubject.new(User.first, :scope => :admin)
93
+ ```
109
94
 
110
- # Staff
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
- permissions :for => :guides do
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
- permissions :for => :drafts do
127
- allow :authors, :to => :submit
128
- end
99
+ _ex._
129
100
 
130
- permissions_to_set :published do
131
- allow :editors, :administrators, :mantainers
132
- deny
133
- end
101
+ ``` rb
102
+ subject.logged_in?
103
+ subject.guest?
104
+ subject.own?(Post.first)
105
+ ```
134
106
 
135
- end
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
- Use in controller
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.4
1
+ 0.4.5
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = "checkin"
8
- s.version = "0.4.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-07"
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
  ]
@@ -3,9 +3,9 @@ module Checkin
3
3
  extend ActiveSupport::Concern
4
4
 
5
5
  module ClassMethods
6
- # checkin(:subject => :user, :scope => nil, :skip_authorization => false, :object => :object, rescue_with => lambda {
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, :notice => "Accedi per completare l'operazione"
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
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-07 00:00:00.000000000 Z
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: -3829181454465002286
73
+ hash: -2281754506523548066
72
74
  required_rubygems_version: !ruby/object:Gem::Requirement
73
75
  none: false
74
76
  requirements: