trust 0.7.0 → 0.8.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.
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