trust 0.7.0 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -23,15 +23,15 @@ Well, we used [DeclarativeAuthorization](http://github.com/stffn/declarative_aut
23
23
 
24
24
  * Loading datasets for the index action. You may use other plugins / gems for doing this, or you may implement your own mechanism.
25
25
 
26
- ### Currently not supported, but may be in the future
27
-
28
- * cannot and cannot? expressions.
29
-
30
26
  # Install and Setup
31
27
 
32
28
  Install the gem
33
29
 
34
30
  gem install trust
31
+
32
+ Or, in your Gemfile
33
+
34
+ gem 'trust'
35
35
 
36
36
  ### Define permissions
37
37
 
@@ -150,6 +150,7 @@ Customer.permits? :create, :for => @client, :by => @auditor # same as above
150
150
  #### You can also designate actors in your model so you can test if other people has access to do operations on a subject
151
151
 
152
152
  Include the Actor role in the class
153
+
153
154
  ``` Ruby
154
155
  class Auditor
155
156
  include Trust::Actor
@@ -158,6 +159,7 @@ end
158
159
  ```
159
160
 
160
161
  Now you can test if the auditor has permission to modify customer for client. Three ways of writing it
162
+
161
163
  ``` Ruby
162
164
  @auditor.can? :update, @customer, @client
163
165
  @auditor.can? :update, @customer, :for => @client
@@ -165,6 +167,7 @@ Now you can test if the auditor has permission to modify customer for client. Th
165
167
  ```
166
168
 
167
169
  The above is the same as
170
+
168
171
  ``` Ruby
169
172
  @customer.permits? :update, @client, :by => @auditor
170
173
  ```
@@ -229,6 +232,7 @@ end
229
232
  ```
230
233
 
231
234
  #### Alternatives
235
+
232
236
  ``` Ruby
233
237
  class MyController < ApplicationController
234
238
  set_user :off # turns off set_user callback
@@ -238,6 +242,7 @@ end
238
242
  ```
239
243
 
240
244
  #### More specifically
245
+
241
246
  For all call backs and ```trustee``` you can use ```:only``` and ```:except``` options.
242
247
  Example toggle create action off
243
248
  ``` Ruby
@@ -248,6 +253,7 @@ end
248
253
  ```
249
254
 
250
255
  #### Yet another alternative, avoiding resource loading
256
+
251
257
  Avoid resource loading on ```show``` action
252
258
  ``` Ruby
253
259
  class MyController < ApplicationController
data/lib/trust/actor.rb CHANGED
@@ -25,7 +25,13 @@
25
25
  module Trust
26
26
  # = Trust::Actor extension
27
27
  #
28
- # Include this module if you want to check if an actor can act upon a specific subject
28
+ # Include this module if you want to check if an actor can act upon a specific subject.
29
+ # E.g. if your class is Actor, then include like this:
30
+ #
31
+ # class Actor < ActiveRecord::Base
32
+ # include Trust::Actor
33
+ # ...
34
+ # end
29
35
  #
30
36
  # ==== Examples
31
37
  #
@@ -24,21 +24,22 @@
24
24
 
25
25
  module Trust
26
26
  module Controller
27
+ # = Trust::Controller::Resource
28
+ #
29
+ # Collects information about the current resource and relations.
30
+ # Handles the loading of the resource and its possible parent, i.e. setting the relevant instance variables
31
+ # It assumes the name of the resource is built on the controllers name, but this can be overridden in your
32
+ # controller by setting the +model+
33
+ #
34
+ # Examples:
35
+ #
36
+ # # controller name AccountsController
37
+ # resource.instance # => @account
38
+ #
39
+ # # controller name Customer::AccountsController
40
+ # resource.instance # => @customer_account
41
+ #
27
42
  class Resource
28
- # = Trust::Controller::Resource
29
- # Collects information about the current resource and relations.
30
- # Handles the loading of the resource and its possible parent, i.e. setting the relevant instance variables
31
- # It assumes the name of the resource is built on the controllers name, but this can be overridden in your
32
- # controller by setting the +model+
33
- #
34
- # Examples:
35
- #
36
- # # controller name AccountsController
37
- # resource.instance # => @account
38
- #
39
- # # controller name Customer::AccountsController
40
- # resource.instance # => @customer_account
41
- #
42
43
 
43
44
  delegate :logger, :to => Rails
44
45
  attr_reader :properties, :params, :action
@@ -116,6 +117,14 @@ module Trust
116
117
  info.klass
117
118
  end
118
119
 
120
+ # Returns a collection that can be used for index, new and creation actions
121
+ #
122
+ # See Trust::Controller::ResourceInfo.collection which controls the behavior of this method.
123
+ def collection(instance = nil)
124
+ @info.collection(@parent_info, instance)
125
+ end
126
+
127
+
119
128
  # Loads the resource
120
129
  #
121
130
  # See Trust::Controller::Properties which controls the behavior of this method.
@@ -168,7 +177,10 @@ module Trust
168
177
  parent_info && parent_info.name
169
178
  end
170
179
 
171
-
180
+ # Returns the association name with the parent
181
+ def association_name
182
+ parent_info && info.association_name(parent_info)
183
+ end
172
184
  private
173
185
  def extract_resource_info(model, params) # nodoc
174
186
  ResourceInfo.new(model, params)
@@ -234,7 +246,7 @@ module Trust
234
246
  # = Resource::ResorceInfo
235
247
  #
236
248
  # Resolves the resource in subject
237
- # (see #ResourceInfo)
249
+ # (see #Resource::Info)
238
250
  class Resource::ResourceInfo < Resource::Info
239
251
 
240
252
  def initialize(model, params) #:nodoc:
@@ -265,19 +277,43 @@ module Trust
265
277
  #
266
278
  def relation(associated_resource)
267
279
  if associated_resource && associated_resource.object
268
- name = associated_resource.as || plural_name
269
- associated_resource.object.class.reflect_on_association(name) ?
270
- associated_resource.object.send(name) : associated_resource.object.send(klass.to_s.demodulize.underscore.pluralize)
280
+ associated_resource.object.send(association_name(associated_resource))
271
281
  else
272
282
  klass
273
283
  end
274
284
  end
285
+
286
+ # Returns a collection that can be used for index, new and creation actions.
287
+ #
288
+ # If specifying an instance, returns the full path for that instance. Can be used when not using shallow routes
289
+ #
290
+ # === Example
291
+ #
292
+ # Assumption
293
+ # resource is instance of Lottery::Package #1 (@lottery_package)
294
+ # association is Lottery::Prizes
295
+ # if association is named lottery_prizes, then [@lottery_package, :lottery_prizes] is returned
296
+ # if association is named prizes, then [@lottery_package, :prizes] is returned
297
+ # if you specify an instance, then [@lottery_package, @prize] is returned
298
+ #
299
+ def collection(associated_resource, instance = nil)
300
+ if associated_resource && associated_resource.object
301
+ [associated_resource.object, instance || association_name(associated_resource)]
302
+ else
303
+ klass
304
+ end
305
+ end
306
+
307
+ def association_name(associated_resource) # :nodoc
308
+ name = associated_resource.as || plural_name
309
+ associated_resource.object.class.reflect_on_association(name) ? name : klass.to_s.demodulize.underscore.pluralize
310
+ end
275
311
  end
276
312
 
277
313
  # = Resource::ParentInfo
278
314
  #
279
315
  # Resolves the parent resource in subject
280
- # (see #ResourceInfo)
316
+ # (see #Resource::ResourceInfo)
281
317
  class Resource::ParentInfo < Resource::Info
282
318
  attr_reader :object,:as
283
319
  def initialize(resources, params, request)
@@ -38,9 +38,9 @@ module Trust
38
38
  # == Delegated methods
39
39
  #
40
40
  # The following methods are delegated to properties. See Trust::Controller::Properties for details
41
- # * <tt>belongs_to</tt> - define one or more associations to parents
42
- # * <tt>actions</tt> - acion definitions outside the restful actions
43
- # * <tt>model</tt> - Redefine the model used in the controller (if it's name does not match the
41
+ # * +belongs_to+ - define one or more associations to parents
42
+ # * +actions+ - acion definitions outside the restful actions
43
+ # * +model+ - Redefine the model used in the controller (if it's name does not match the
44
44
  # controller_path)
45
45
  #
46
46
  def properties
@@ -98,9 +98,9 @@ module Trust
98
98
  #
99
99
  # === Arguments:
100
100
  #
101
- # :off - switch callback off
102
- # :only - only include these actions
103
- # :except - except these actions
101
+ # +:off+ - switch callback off
102
+ # +:only+ - only include these actions
103
+ # +:except+ - except these actions
104
104
  def set_user(*args)
105
105
  _filter_setting(:set_user, *args)
106
106
  end
@@ -109,9 +109,9 @@ module Trust
109
109
  #
110
110
  # === Arguments:
111
111
  #
112
- # :off - switch callback off
113
- # :only - only include these actions
114
- # :except - except these actions
112
+ # +:off+ - switch callback off
113
+ # +:only+ - only include these actions
114
+ # +:except+ - except these actions
115
115
  def load_resource(*args)
116
116
  _filter_setting(:load_resource, *args)
117
117
  end
@@ -120,9 +120,9 @@ module Trust
120
120
  #
121
121
  # === Arguments:
122
122
  #
123
- # :off - switch callback off
124
- # :only - only include these actions
125
- # :except - except these actions
123
+ # +:off+ - switch callback off
124
+ # +:only+ - only include these actions
125
+ # +:except+ - except these actions
126
126
  def access_control(*args)
127
127
  _filter_setting(:access_control, *args)
128
128
  end
@@ -144,9 +144,9 @@ module Trust
144
144
  # == Delegated methods
145
145
  #
146
146
  # The following methods are delegated to properties. See Trust::Controller::Properties for details
147
- # * <tt>belongs_to</tt> - define one or more associations to parents
148
- # * <tt>actions</tt> - acion definitions outside the restful actions
149
- # * <tt>model</tt> - Redefine the model used in the controller (if it's name does not match the
147
+ # * +belongs_to+ - define one or more associations to parents
148
+ # * +actions+ - acion definitions outside the restful actions
149
+ # * +model+ - Redefine the model used in the controller (if it's name does not match the
150
150
  # controller_path)
151
151
  #
152
152
  def properties
@@ -196,11 +196,11 @@ module Trust
196
196
  # Also available as a helper in views.
197
197
  #
198
198
  # ==== Examples
199
- # can? :edit # does the current user have permission to edit the current resource?
200
- # # If there is a nested resource, the parent is automatically associated
201
- # can? :edit, @customer # does the current user have permission to edit the given customer?
202
- # # Parent is also passed on here.
203
- # can? :edit, @account, @client # is current user allowed to edit the account associated with the client?
199
+ # +can? :edit+ # does the current user have permission to edit the current resource?
200
+ # # If there is a nested resource, the parent is automatically associated
201
+ # +can? :edit, @customer+ # does the current user have permission to edit the given customer?
202
+ # # Parent is also passed on here.
203
+ # +can? :edit, @account, @client+ # is current user allowed to edit the account associated with the client?
204
204
  def can?(action_name, subject = resource.instance || resource.relation.new, parent = resource.parent)
205
205
  Trust::Authorization.authorized?(action_name, subject, parent)
206
206
  end
@@ -34,8 +34,8 @@ module Trust
34
34
  # ...
35
35
  # end
36
36
  #
37
- # The above is the minimum required definitions that must exist in you file. <tt>Default</tt> will be used if no classes
38
- # match the permissions requested, so the <tt>Default</tt> class definition is mandatory.
37
+ # The above is the minimum required definitions that must exist in you file. +Default+ will be used if no classes
38
+ # match the permissions requested, so the +Default+ class definition is mandatory.
39
39
  #
40
40
  # If you want to separate the permissions into separate files that is ok. Then you shoud place these files in the
41
41
  # /app/model/permissions directory.
@@ -44,10 +44,10 @@ module Trust
44
44
  #
45
45
  # The basic rules is to define classes in the Permissions module that matches your models.
46
46
  # Here are some examples:
47
- # * <tt>Project</tt> should have a matching class <tt>Permissions::Project</tt>
48
- # * <tt>Account</tt> should have a matching class <tt>Permissions::Account</tt>
49
- # * <tt>Account:Credit</tt> may have a matching class <tt>Permissions::Account::Credit</tt>, but if its inheriting from
50
- # <tt>Account</tt> and no special handling is necessary, it is not necessary to create the permissions class.
47
+ # * +Project+ should have a matching class +Permissions::Project+
48
+ # * +Account+ should have a matching class +Permissions::Account+
49
+ # * +Account:Credit+ may have a matching class +Permissions::Account::Credit+, but if its inheriting from
50
+ # +Account+ and no special handling is necessary, it is not necessary to create the permissions class.
51
51
  #
52
52
  # == Using inheritance
53
53
  #
@@ -66,26 +66,26 @@ module Trust
66
66
  #
67
67
  # == Action aliases
68
68
  #
69
- # You can define aliases for actions. You do this by setting the <tt>action_aliases</tt> attribute on Trust::Permissions class
69
+ # You can define aliases for actions. You do this by setting the +action_aliases+ attribute on Trust::Permissions class
70
70
  # Example:
71
71
  # Trust::Permissions.action_aliases = {
72
72
  # read: [:index, :show],
73
73
  # create: [:create, :new]
74
74
  # }
75
75
  #
76
- # Keep in mind that all permissions are expanded upon declaration, so when using the <tt>can?</tt> method you must refer to
76
+ # Keep in mind that all permissions are expanded upon declaration, so when using the +can?+ method you must refer to
77
77
  # the actual action and not the alias. The alias will never give a positive permission.
78
78
  #
79
79
  # == Accessors
80
80
  #
81
81
  # Accessors that can be used when testing permissions:
82
- # * <tt>user</tt> - the user currently logged in
83
- # * <tt>action</tt> - the action request from the controller such as :edit, or the action tested from helper or
84
- # from the object itself when using <tt>ActiveRecord::can?</tt> is being used.
85
- # * <tt>subject</tt> - the object that is being tested for permissions. This may be a an existing object, a new object
82
+ # * +user+ - the user currently logged in
83
+ # * +action+ - the action request from the controller such as :edit, or the action tested from helper or
84
+ # from the object itself when using +ActiveRecord::can?+ is being used.
85
+ # * +subject+ - the object that is being tested for permissions. This may be a an existing object, a new object
86
86
  # (such as for +:create+ and +:new+ action), or nil if no object has been instantiated.
87
- # * <tt>parent</tt> - the parent object if in a nested route, specified by +belongs_to+ in the controller.
88
- # * <tt>klass</tt> - the class of involed in the request. It can be a base class or the real class, depending on
87
+ # * +parent+ - the parent object if in a nested route, specified by +belongs_to+ in the controller.
88
+ # * +klass+ - the class of involed in the request. It can be a base class or the real class, depending on
89
89
  # your controller design.
90
90
  #
91
91
  # == Defining your own accessors or instance methods
@@ -128,11 +128,11 @@ module Trust
128
128
  #
129
129
  # == Parameters:
130
130
  #
131
- # <tt>user</tt> - user object, must respond to role_symbols
132
- # <tt>action</tt> - action, such as :create, :show, etc. Should not be an alias
133
- # <tt>klass</tt> - the class of the subject.
134
- # <tt>subject</tt> - the subject tested for authorization
135
- # <tt>parent</tt> - the parent object, normally declared through belongs_to
131
+ # +user+ - user object, must respond to role_symbols
132
+ # +action+ - action, such as :create, :show, etc. Should not be an alias
133
+ # +klass+ - the class of the subject.
134
+ # +subject+ - the subject tested for authorization
135
+ # +parent+ - the parent object, normally declared through belongs_to
136
136
  #
137
137
  # See {Trust::Authorization} for more details
138
138
  #
@@ -181,7 +181,7 @@ module Trust
181
181
  class << self
182
182
  # Assign permissions to one or more roles.
183
183
  #
184
- # You may call role or roles, they are the same function like <tt>role :admin</tt> or <tt>roles :admin, :accountant</tt>
184
+ # You may call role or roles, they are the same function like +role :admin+ or +roles :admin, :accountant+
185
185
  #
186
186
  # There are two ways to call role, with or without block. If you want to set multiple permissions with different conditons
187
187
  # then you should use a block.
@@ -211,23 +211,32 @@ module Trust
211
211
  @@can_expressions = 0
212
212
  raise RoleAssigmnentMissing
213
213
  end
214
- @perms = []
214
+ @perms = {:can => [], :cannot => []}
215
215
  @in_role_block = true
216
216
  yield
217
217
  @in_role_block = false
218
- perms = @perms
218
+ perms = @perms
219
219
  else
220
220
  if @@can_expressions > 1
221
221
  @@can_expressions = 0
222
222
  raise RoleAssigmnentMissing
223
223
  end
224
- options = roles.extract_options!
225
- raise ArgumentError, "Must have a block or a can expression" unless perms = options[:can]
224
+ perms = roles.extract_options!
225
+ unless perms.size >= 1 && (perms[:can] || perms[:cannot])
226
+ raise ArgumentError, "Must have a block or a can or a cannot expression: #{perms.inspect}"
227
+ end
226
228
  @@can_expressions = 0
227
229
  end
228
230
  roles.flatten.each do |role|
229
231
  self.permissions[role] ||= []
230
- self.permissions[role] += perms
232
+ if perms[:cannot] && perms[:cannot].size > 0
233
+ perms[:cannot].each do |p|
234
+ self.permissions[role].delete_if { |perm| perm[0] == p }
235
+ end
236
+ end
237
+ if perms[:can] && perms[:can].size > 0
238
+ self.permissions[role] += perms[:can]
239
+ end
231
240
  end
232
241
  end
233
242
  alias :roles :role
@@ -237,7 +246,12 @@ module Trust
237
246
  # === Arguments
238
247
  #
239
248
  # action - can be an alias or an actions of some kind
240
- # options - :if/:unless :symbol or proc that will be called to evaluate an expression
249
+ # options - control the behavior of the permission
250
+ #
251
+ # === Options
252
+ # +:if/:unless+ - :symbol or proc that will be called to evaluate an expression
253
+ # +enforce+ - set to true to enforce the permission, delete any previous grants given from parent classes. Most meaningful in
254
+ # combination with +:if+ and +:unless+ options
241
255
  #
242
256
  # === Example
243
257
  #
@@ -254,12 +268,65 @@ module Trust
254
268
  # In the example above a method is used to test data on the actual record when testing for permissions.
255
269
  def can(*args)
256
270
  options = args.extract_options!
271
+ enforce = options.delete(:enforce)
257
272
  p = expand_aliases(args).collect { |action| [action, options] }
258
273
  if @in_role_block
259
- @perms += p
274
+ @perms[:can] += p
275
+ if enforce
276
+ @perms[:cannot] = expand_aliases(args).collect { |action| action }
277
+ end
278
+ else
279
+ @@can_expressions += 1
280
+ perms = {:can => p }
281
+ if enforce
282
+ perms[:cannot] = expand_aliases(args).collect { |action| action }
283
+ end
284
+ return perms
285
+ end
286
+ end
287
+
288
+ # Revokes permissions.
289
+ #
290
+ # Revokes any previous permissions given in parent classes. This cannot be used with conditions. See also +:enforce+ option
291
+ # for +can+
292
+ #
293
+ # +can+ has presedence over +cannot+. In practice this means that in a block; +cannot+ statements are processed before +can+,
294
+ # and any previously permissions granted are deleted.
295
+ # Another way to say this is; if you have +cannot :destroy+ and +can :destroy+, then all inheritied destroys will first be
296
+ # deleted, and then the can destroy will be granted.
297
+ #
298
+ #
299
+ # === Arguments
300
+ #
301
+ # action - actions to be revoked permissions for. Cannot be aliases
302
+ #
303
+ # === Example
304
+ #
305
+ # module Permissions
306
+ # class Account < Trust::Permissions
307
+ # role :admin, :accountant do
308
+ # can :read
309
+ # can :read
310
+ # can :update, :destroy, :unless => :closed?
311
+ # end
312
+ # end
313
+ #
314
+ # class Account::Credit < Account
315
+ # role :accountant do
316
+ # cannot :destroy # revoke permission to destroy
317
+ # end
318
+ # end
319
+ # end
320
+ #
321
+ def cannot(*args)
322
+ options = args.extract_options!
323
+ raise ArgumentError, "No options (#{options.inspect}) are allowed for cannot. It is just meaning less" if options.size > 0
324
+ p = expand_aliases(args).collect { |action| action }
325
+ if @in_role_block
326
+ @perms[:cannot] += p
260
327
  else
261
328
  @@can_expressions += 1
262
- return {:can => p }
329
+ return {:cannot => p }
263
330
  end
264
331
  end
265
332
 
@@ -25,7 +25,7 @@
25
25
  class Trust::ResourceHelper
26
26
  attr_accessor :instance, :parent, :instances
27
27
  attr_accessor :properties, :params, :action, :instance_params
28
- attr_accessor :info, :parent_info, :relation
28
+ attr_accessor :info, :parent_info, :relation, :collection, :association_name
29
29
  class << self
30
30
  attr_accessor :properties
31
31
  end
@@ -42,6 +42,10 @@ class Trust::ResourceHelper
42
42
  Trust::Controller::Resource::Info.var_name(parent.class)
43
43
  end
44
44
 
45
+ def association_name
46
+ @association_name || instance_name.to_s.pluralize.to_sym
47
+ end
48
+
45
49
  def instantiated
46
50
  instances || instance
47
51
  end
data/lib/trust/version.rb CHANGED
@@ -23,5 +23,5 @@
23
23
  # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24
24
 
25
25
  module Trust
26
- VERSION = "0.7.0"
26
+ VERSION = "0.8.0"
27
27
  end