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 +10 -4
- data/lib/trust/actor.rb +7 -1
- data/lib/trust/controller/resource.rb +56 -20
- data/lib/trust/controller.rb +20 -20
- data/lib/trust/permissions.rb +95 -28
- data/lib/trust/test_helper.rb +5 -1
- data/lib/trust/version.rb +1 -1
- data/test/dummy/log/test.log +11445 -0
- data/test/unit/trust/controller/resource_test.rb +21 -6
- data/test/unit/trust/permissions_test.rb +51 -0
- metadata +4 -4
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 #
|
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
|
-
|
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)
|
data/lib/trust/controller.rb
CHANGED
@@ -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
|
-
# *
|
42
|
-
# *
|
43
|
-
# *
|
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
|
-
#
|
102
|
-
#
|
103
|
-
#
|
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
|
-
#
|
113
|
-
#
|
114
|
-
#
|
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
|
-
#
|
124
|
-
#
|
125
|
-
#
|
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
|
-
# *
|
148
|
-
# *
|
149
|
-
# *
|
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
|
-
#
|
201
|
-
# can? :edit, @customer # does the current user have permission to edit the given customer?
|
202
|
-
#
|
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
|
data/lib/trust/permissions.rb
CHANGED
@@ -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.
|
38
|
-
# match the permissions requested, so the
|
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
|
-
# *
|
48
|
-
# *
|
49
|
-
# *
|
50
|
-
#
|
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
|
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
|
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
|
-
# *
|
83
|
-
# *
|
84
|
-
# from the object itself when using
|
85
|
-
# *
|
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
|
-
# *
|
88
|
-
# *
|
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
|
-
#
|
132
|
-
#
|
133
|
-
#
|
134
|
-
#
|
135
|
-
#
|
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
|
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
|
-
|
225
|
-
|
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
|
-
|
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 -
|
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 {:
|
329
|
+
return {:cannot => p }
|
263
330
|
end
|
264
331
|
end
|
265
332
|
|
data/lib/trust/test_helper.rb
CHANGED
@@ -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