trust 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- data/MIT-LICENSE +23 -0
- data/README.md +244 -0
- data/Rakefile +37 -0
- data/lib/tasks/trust_tasks.rake +42 -0
- data/lib/trust/active_record.rb +65 -0
- data/lib/trust/authorization.rb +85 -0
- data/lib/trust/controller/properties.rb +134 -0
- data/lib/trust/controller/resource.rb +306 -0
- data/lib/trust/controller.rb +197 -0
- data/lib/trust/exceptions.rb +45 -0
- data/lib/trust/inheritable_attribute.rb +91 -0
- data/lib/trust/permissions.rb +268 -0
- data/lib/trust/test_helper.rb +56 -0
- data/lib/trust/version.rb +27 -0
- data/lib/trust.rb +39 -0
- data/test/dummy/README.rdoc +261 -0
- data/test/dummy/Rakefile +7 -0
- data/test/dummy/app/assets/javascripts/accounts.js +2 -0
- data/test/dummy/app/assets/javascripts/application.js +15 -0
- data/test/dummy/app/assets/javascripts/clients.js +2 -0
- data/test/dummy/app/assets/javascripts/users.js +2 -0
- data/test/dummy/app/assets/stylesheets/accounts.css +4 -0
- data/test/dummy/app/assets/stylesheets/application.css +13 -0
- data/test/dummy/app/assets/stylesheets/clients.css +4 -0
- data/test/dummy/app/assets/stylesheets/scaffold.css +56 -0
- data/test/dummy/app/assets/stylesheets/users.css +4 -0
- data/test/dummy/app/controllers/accounts_controller.rb +100 -0
- data/test/dummy/app/controllers/application_controller.rb +31 -0
- data/test/dummy/app/controllers/clients_controller.rb +107 -0
- data/test/dummy/app/controllers/savings_accounts_controller.rb +27 -0
- data/test/dummy/app/controllers/settlements_controller.rb +26 -0
- data/test/dummy/app/controllers/users_controller.rb +107 -0
- data/test/dummy/app/helpers/accounts_helper.rb +26 -0
- data/test/dummy/app/helpers/application_helper.rb +26 -0
- data/test/dummy/app/helpers/clients_helper.rb +26 -0
- data/test/dummy/app/helpers/users_helper.rb +26 -0
- data/test/dummy/app/models/account/credit.rb +26 -0
- data/test/dummy/app/models/account.rb +35 -0
- data/test/dummy/app/models/client.rb +35 -0
- data/test/dummy/app/models/permissions.rb +68 -0
- data/test/dummy/app/models/savings_account.rb +26 -0
- data/test/dummy/app/models/user.rb +40 -0
- data/test/dummy/app/views/accounts/_form.html.erb +46 -0
- data/test/dummy/app/views/accounts/edit.html.erb +31 -0
- data/test/dummy/app/views/accounts/index.html.erb +48 -0
- data/test/dummy/app/views/accounts/new.html.erb +30 -0
- data/test/dummy/app/views/accounts/show.html.erb +35 -0
- data/test/dummy/app/views/clients/_form.html.erb +46 -0
- data/test/dummy/app/views/clients/edit.html.erb +31 -0
- data/test/dummy/app/views/clients/index.html.erb +48 -0
- data/test/dummy/app/views/clients/new.html.erb +30 -0
- data/test/dummy/app/views/clients/show.html.erb +35 -0
- data/test/dummy/app/views/layouts/application.html.erb +39 -0
- data/test/dummy/app/views/users/_form.html.erb +46 -0
- data/test/dummy/app/views/users/edit.html.erb +31 -0
- data/test/dummy/app/views/users/index.html.erb +48 -0
- data/test/dummy/app/views/users/new.html.erb +30 -0
- data/test/dummy/app/views/users/show.html.erb +35 -0
- data/test/dummy/config/application.rb +56 -0
- data/test/dummy/config/boot.rb +10 -0
- data/test/dummy/config/database.yml +25 -0
- data/test/dummy/config/environment.rb +5 -0
- data/test/dummy/config/environments/development.rb +37 -0
- data/test/dummy/config/environments/production.rb +67 -0
- data/test/dummy/config/environments/test.rb +37 -0
- data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/test/dummy/config/initializers/inflections.rb +15 -0
- data/test/dummy/config/initializers/mime_types.rb +5 -0
- data/test/dummy/config/initializers/secret_token.rb +7 -0
- data/test/dummy/config/initializers/session_store.rb +8 -0
- data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/test/dummy/config/locales/en.yml +5 -0
- data/test/dummy/config/routes.rb +38 -0
- data/test/dummy/config.ru +4 -0
- data/test/dummy/db/migrate/20120522115011_create_accounts.rb +36 -0
- data/test/dummy/db/migrate/20120522130322_create_users.rb +33 -0
- data/test/dummy/db/migrate/20120523144144_create_clients.rb +34 -0
- data/test/dummy/db/schema.rb +38 -0
- data/test/dummy/public/404.html +26 -0
- data/test/dummy/public/422.html +26 -0
- data/test/dummy/public/500.html +25 -0
- data/test/dummy/public/favicon.ico +0 -0
- data/test/dummy/script/rails +6 -0
- data/test/dummy/test/fixtures/accounts.yml +7 -0
- data/test/dummy/test/fixtures/clients.yml +7 -0
- data/test/dummy/test/fixtures/users.yml +7 -0
- data/test/dummy/test/functional/accounts_controller_test.rb +123 -0
- data/test/dummy/test/functional/clients_controller_test.rb +74 -0
- data/test/dummy/test/functional/users_controller_test.rb +74 -0
- data/test/dummy/test/unit/account_test.rb +31 -0
- data/test/dummy/test/unit/client_test.rb +31 -0
- data/test/dummy/test/unit/helpers/accounts_helper_test.rb +28 -0
- data/test/dummy/test/unit/helpers/clients_helper_test.rb +28 -0
- data/test/dummy/test/unit/helpers/users_helper_test.rb +28 -0
- data/test/dummy/test/unit/permissions_test.rb +171 -0
- data/test/dummy/test/unit/user_test.rb +31 -0
- data/test/test_helper.rb +45 -0
- data/test/trust_test.rb +31 -0
- data/test/unit/trust/active_record_test.rb +56 -0
- data/test/unit/trust/authorization_test.rb +108 -0
- data/test/unit/trust/controller/properties_test.rb +132 -0
- data/test/unit/trust/controller/resource_test.rb +251 -0
- data/test/unit/trust/controller_test.rb +160 -0
- data/test/unit/trust/inheritable_attribute_test.rb +65 -0
- data/test/unit/trust/permissions_test.rb +258 -0
- metadata +280 -0
data/MIT-LICENSE
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
Copyright 2012 Bingo Entreprenøren AS
|
2
|
+
Copyright 2012 Teknobingo Scandinavia AS
|
3
|
+
Copyright 2012 Knut I. Stenmark
|
4
|
+
Copyright 2012 Patrick Hanevold
|
5
|
+
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
7
|
+
a copy of this software and associated documentation files (the
|
8
|
+
"Software"), to deal in the Software without restriction, including
|
9
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
10
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
11
|
+
permit persons to whom the Software is furnished to do so, subject to
|
12
|
+
the following conditions:
|
13
|
+
|
14
|
+
The above copyright notice and this permission notice shall be
|
15
|
+
included in all copies or substantial portions of the Software.
|
16
|
+
|
17
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
18
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
19
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
20
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
21
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
22
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
23
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,244 @@
|
|
1
|
+
# Trust
|
2
|
+
|
3
|
+
### Trust is a no-nonsense framework for authorization control - for Ruby On Rails.
|
4
|
+
|
5
|
+
- Why yet another authorization framework you may ask?
|
6
|
+
|
7
|
+
Well, we used [DeclarativeAuthorization](http://github.com/stffn/declarative_authorization) for a while, but got stuck when it comes to name-spaces and inheritance. So, we investigated in the possibilities of using [CanCan](http://github.com/ryanb/cancan) and [CanTango](http://github.com/kristianmandrup/cantango), and found that CanCan could be slow, because all permissions has to be loaded on every request. CanTango has tackled this problem by implementing caching, but the framework is still evolving and seems fairly complex. At the same time, CanTango is role focused and not resource focused.
|
8
|
+
|
9
|
+
### What will you benefit from when using Trust?
|
10
|
+
|
11
|
+
* Resource focused permissions, not role focused
|
12
|
+
* Complete support for inheritance in controllers
|
13
|
+
* Complete support for namespaces, both controllers and models
|
14
|
+
* Complete support for nested resources
|
15
|
+
* Complete support for shortened associations (e.g. if you have models in name spaces that relates to other models in the name space)
|
16
|
+
* Fast permission loading, where no cashing is needed. All permissions are declared on class level, so once loaded, they stay in memory.
|
17
|
+
* Support for inheritance in the permissions model
|
18
|
+
* Natural code evaluation in the permission declarations, i.e. you understand completely what is going on, because the implementation is done the way you implement condifitions in rails for validations and alike.
|
19
|
+
* Automatic loading of instances and parents in controller
|
20
|
+
|
21
|
+
### What is not supported in Trust
|
22
|
+
|
23
|
+
* Loading datasets for the index action. You may use other plugins / gems for doing this, or you may implement your own mechanism.
|
24
|
+
|
25
|
+
### Currently not supported, but may be in the future
|
26
|
+
|
27
|
+
* Support for devise. However you may easily implement this by overriding one method in your controller.
|
28
|
+
* cannot and cannot? expressions.
|
29
|
+
|
30
|
+
# Install and Setup
|
31
|
+
|
32
|
+
Install the gem
|
33
|
+
|
34
|
+
gem install trust
|
35
|
+
|
36
|
+
### Define permissions
|
37
|
+
|
38
|
+
Create the permissions file in your model directory. Example
|
39
|
+
|
40
|
+
``` Ruby
|
41
|
+
module Permissions
|
42
|
+
class Default < Trust::Permissions
|
43
|
+
role :system_admin do
|
44
|
+
can :manage
|
45
|
+
can :audit
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
class Account < Default
|
50
|
+
role :support, can(:manage)
|
51
|
+
role :accountant do
|
52
|
+
can :edit, :show, :if => :associated_with_client?
|
53
|
+
end
|
54
|
+
role :department_manager, :accountant do
|
55
|
+
can :create, :if => lambda { parent }
|
56
|
+
end
|
57
|
+
|
58
|
+
def associated_with_client?
|
59
|
+
parent && parent.is_a?(Client) && parent.operators.find(user.id)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
```
|
64
|
+
|
65
|
+
The following attributes will be accessible in a Permissions class:
|
66
|
+
|
67
|
+
* ```subject``` - the resource that is currently being tested for authorization
|
68
|
+
* ```parent``` - the parent of the authorization when resource is nested
|
69
|
+
* ```user``` - the user accessing the resource
|
70
|
+
* ```klass``` - the resource class
|
71
|
+
* ```action``` - the action that triggered the authorization
|
72
|
+
|
73
|
+
Keep in mind that the permission object will be instanciated to do authorization, and not the class.
|
74
|
+
You can extend the Trust::Permissions with more functionality if needed.
|
75
|
+
|
76
|
+
You can also create aliases for actions. We have defined a predefined set of aliases. See Trust::Permissions.action_aliases.
|
77
|
+
Processing of aliases are done in such way that permissions per action is expanded when the permissions are loaded, so thif you define :update when declaring the permissions, there will be one permission for :update and one for :edit
|
78
|
+
|
79
|
+
|
80
|
+
### Apply access control in controller
|
81
|
+
|
82
|
+
Place ```trustee``` in your controller after the user has been identified. Something like this:
|
83
|
+
|
84
|
+
``` Ruby
|
85
|
+
class AccountsController < ApplicationController
|
86
|
+
login_required
|
87
|
+
trustee
|
88
|
+
end
|
89
|
+
```
|
90
|
+
|
91
|
+
The trustee statement will set up 3 before_filters in your controller:
|
92
|
+
|
93
|
+
``` Ruby
|
94
|
+
before_filter :set_user
|
95
|
+
before_filter :load_resource
|
96
|
+
before_filter :access_control
|
97
|
+
```
|
98
|
+
|
99
|
+
Trust assumes that ```current_user``` is accessible. The user object must respond to the method ```role_symbols``` which should return an array of one or more roles for the user.
|
100
|
+
|
101
|
+
Handling access denied situations in your controller. Implement something like the following in your ApplicationController:
|
102
|
+
|
103
|
+
``` Ruby
|
104
|
+
class ApplicationController < ActionController::Base
|
105
|
+
rescue_from Trust::AccessDenied do |exception|
|
106
|
+
redirect_to root_url, :alert => exception.message
|
107
|
+
# or some other redirect
|
108
|
+
end
|
109
|
+
end
|
110
|
+
```
|
111
|
+
|
112
|
+
### Define associations in your controller
|
113
|
+
|
114
|
+
For nested resources you can easily define the associations using ```belongs_to``` like this:
|
115
|
+
|
116
|
+
``` Ruby
|
117
|
+
class AccountsController < ApplicationController
|
118
|
+
login_required
|
119
|
+
belongs_to :client
|
120
|
+
trustee
|
121
|
+
end
|
122
|
+
```
|
123
|
+
|
124
|
+
You can specify as many associations as you like.
|
125
|
+
|
126
|
+
|
127
|
+
## The can? and permits? method
|
128
|
+
|
129
|
+
The can? method is accessible from controller and views. Here are some coding examples:
|
130
|
+
|
131
|
+
#### In controller or views you will use can?
|
132
|
+
|
133
|
+
``` Ruby
|
134
|
+
can? :edit # does the current user have permission to edit the current resource?
|
135
|
+
# If there is a nested resource, the parent is automatically associated
|
136
|
+
can? :edit, @customer # does the current user have permission to edit the given customer?
|
137
|
+
# Parent is also passed on here.
|
138
|
+
can? :edit, @account, @client # is current user allowed to edit the account associated with the client?
|
139
|
+
```
|
140
|
+
|
141
|
+
#### On ActiveRecord objects you will use permits?
|
142
|
+
|
143
|
+
``` Ruby
|
144
|
+
@customer.permits? :edit # does the current user have permission to edit the given customer?
|
145
|
+
Customer.permits? :create, @client # does the current user have permission to create customers?
|
146
|
+
```
|
147
|
+
|
148
|
+
## Instance variables
|
149
|
+
|
150
|
+
The filter ```load_resource``` will automatically load the instance for the resource in the controller. It will by default use the controller_path to determine the name of the instance variable. Here are a couple of examples:
|
151
|
+
|
152
|
+
``` Ruby
|
153
|
+
UsersController => @user
|
154
|
+
Account::CreditsController => @account_credit
|
155
|
+
```
|
156
|
+
|
157
|
+
If it is a nested resource, it will also instantiate the ```parent``` class, using the namedefined in belongs_to to determine the name. E.g. if you have defined belongs_to :client, it will look for the parameter ```:client_id``` and perform a find like ```Client.find(client_id)```. Finding the resource will be done through the association between the two, such as ```client.accounts.find(id)```.
|
158
|
+
|
159
|
+
You can override the naming by specifying ```model``` like this
|
160
|
+
|
161
|
+
``` Ruby
|
162
|
+
class AccountsController < ApplicationController
|
163
|
+
login_required
|
164
|
+
model :wackount
|
165
|
+
trustee
|
166
|
+
end
|
167
|
+
```
|
168
|
+
|
169
|
+
If you want to override the name with namespacing then
|
170
|
+
|
171
|
+
``` Ruby
|
172
|
+
class Account::CreditsController < ApplicationController
|
173
|
+
login_required
|
174
|
+
model :"account/wreckit"
|
175
|
+
trustee
|
176
|
+
end
|
177
|
+
```
|
178
|
+
|
179
|
+
You can also access the instances in a generic manner if you like. Use following statements:
|
180
|
+
|
181
|
+
``` Ruby
|
182
|
+
resource.instance => accesses the instance variable
|
183
|
+
resource.parent => accesses the parent instance
|
184
|
+
```
|
185
|
+
|
186
|
+
You can even assign these if you like. The resource is also exposed as helper, so you can access it in views.
|
187
|
+
For simplicity we have also exposed an ```instances``` accessor that you can assign when you have a multirecord result,
|
188
|
+
such as for index action.
|
189
|
+
|
190
|
+
## Overriding defaults
|
191
|
+
|
192
|
+
### Overriding resource permits in the controller
|
193
|
+
|
194
|
+
Say you have a controller without a model or do not want to perform access control. You can turn off the featur in your controller
|
195
|
+
|
196
|
+
``` Ruby
|
197
|
+
class ApplicationController < ActionController::Base
|
198
|
+
login_required
|
199
|
+
trustee # By default we want to test for permissions
|
200
|
+
end
|
201
|
+
|
202
|
+
class MyController < ApplicationController
|
203
|
+
trustee :off # turns off all callbacks
|
204
|
+
end
|
205
|
+
```
|
206
|
+
|
207
|
+
#### Alternatives
|
208
|
+
``` Ruby
|
209
|
+
class MyController < ApplicationController
|
210
|
+
set_user :off # turns off set_user callback
|
211
|
+
load_resource :off # do not load resources
|
212
|
+
access_control :off # turn access control off
|
213
|
+
end
|
214
|
+
```
|
215
|
+
|
216
|
+
#### More specifically
|
217
|
+
For all call backs and ```trustee``` you can use ```:only``` and ```:except``` options.
|
218
|
+
Example toggle create action off
|
219
|
+
``` Ruby
|
220
|
+
class MyController < ApplicationController
|
221
|
+
load_resource :except => :create
|
222
|
+
access_control :except => :create
|
223
|
+
end
|
224
|
+
```
|
225
|
+
|
226
|
+
#### Yet another alternative, avoiding resource loading
|
227
|
+
Avoid resource loading on ```show``` action
|
228
|
+
``` Ruby
|
229
|
+
class MyController < ApplicationController
|
230
|
+
actions :except => :show
|
231
|
+
end
|
232
|
+
```
|
233
|
+
|
234
|
+
### Overriding set_user
|
235
|
+
|
236
|
+
If you prefer to use some other user reference than current_user you can override the method ```set_user``` like this in your controller:
|
237
|
+
|
238
|
+
``` Ruby
|
239
|
+
def set_user
|
240
|
+
Trust::Authorization.user = User.current
|
241
|
+
end
|
242
|
+
```
|
243
|
+
|
244
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
#!/usr/bin/env rake
|
2
|
+
begin
|
3
|
+
require 'bundler/setup'
|
4
|
+
rescue LoadError
|
5
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
6
|
+
end
|
7
|
+
begin
|
8
|
+
require 'rdoc/task'
|
9
|
+
rescue LoadError
|
10
|
+
require 'rdoc/rdoc'
|
11
|
+
require 'rake/rdoctask'
|
12
|
+
RDoc::Task = Rake::RDocTask
|
13
|
+
end
|
14
|
+
|
15
|
+
RDoc::Task.new(:rdoc) do |rdoc|
|
16
|
+
rdoc.rdoc_dir = 'rdoc'
|
17
|
+
rdoc.title = 'Trust'
|
18
|
+
rdoc.options << '--line-numbers'
|
19
|
+
rdoc.rdoc_files.include('README.rdoc')
|
20
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
21
|
+
end
|
22
|
+
|
23
|
+
Dir.glob('lib/tasks/*.rake').each { |r| import r }
|
24
|
+
|
25
|
+
Bundler::GemHelper.install_tasks
|
26
|
+
|
27
|
+
require 'rake/testtask'
|
28
|
+
|
29
|
+
Rake::TestTask.new(:test) do |t|
|
30
|
+
t.libs << 'lib'
|
31
|
+
t.libs << 'test'
|
32
|
+
t.pattern = 'test/**/*_test.rb'
|
33
|
+
t.verbose = false
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
task :default => :test
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# Copyright (c) 2012 Bingo Entreprenøren AS
|
2
|
+
# Copyright (c) 2012 Teknobingo Scandinavia AS
|
3
|
+
# Copyright (c) 2012 Knut I. Stenmark
|
4
|
+
# Copyright (c) 2012 Patrick Hanevold
|
5
|
+
#
|
6
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
7
|
+
# a copy of this software and associated documentation files (the
|
8
|
+
# "Software"), to deal in the Software without restriction, including
|
9
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
10
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
11
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
12
|
+
# the following conditions:
|
13
|
+
#
|
14
|
+
# The above copyright notice and this permission notice shall be
|
15
|
+
# included in all copies or substantial portions of the Software.
|
16
|
+
#
|
17
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
18
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
19
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
20
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
21
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
22
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
23
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
24
|
+
|
25
|
+
namespace :db do
|
26
|
+
DELEGATORS = [ :create, :drop, :migrate, :rollback, :setup ]
|
27
|
+
DESCRIPTORS = Hash[*`cd test/dummy; rake -T`.split("\n").select{ |line| line =~ /^rake db:/ }.map{ |line| line.split('#').map{ |str| str.strip } }.flatten]
|
28
|
+
|
29
|
+
DELEGATORS.each do |delegate|
|
30
|
+
desc DESCRIPTORS["rake db:#{delegate}"]
|
31
|
+
task delegate do
|
32
|
+
system "cd test/dummy; rake db:#{delegate}"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
namespace :test do
|
37
|
+
desc 'preparte test database with schema'
|
38
|
+
task :prepare do
|
39
|
+
system "cd test/dummy; rake db:test:prepare"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# Copyright (c) 2012 Bingo Entreprenøren AS
|
2
|
+
# Copyright (c) 2012 Teknobingo Scandinavia AS
|
3
|
+
# Copyright (c) 2012 Knut I. Stenmark
|
4
|
+
# Copyright (c) 2012 Patrick Hanevold
|
5
|
+
#
|
6
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
7
|
+
# a copy of this software and associated documentation files (the
|
8
|
+
# "Software"), to deal in the Software without restriction, including
|
9
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
10
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
11
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
12
|
+
# the following conditions:
|
13
|
+
#
|
14
|
+
# The above copyright notice and this permission notice shall be
|
15
|
+
# included in all copies or substantial portions of the Software.
|
16
|
+
#
|
17
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
18
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
19
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
20
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
21
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
22
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
23
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
24
|
+
|
25
|
+
module Trust
|
26
|
+
module ActiveRecord
|
27
|
+
extend ActiveSupport::Concern
|
28
|
+
# = Trust::ActiveRecord extension
|
29
|
+
# Extends ActiveRecord with the +permits?+ and +ensure_permitted!+ method on class and instances
|
30
|
+
#
|
31
|
+
# ==== Examples
|
32
|
+
#
|
33
|
+
# # If current user is permitted to create customers, create it
|
34
|
+
# if Customer.permits? :create
|
35
|
+
# Customer.create attributes
|
36
|
+
# end
|
37
|
+
#
|
38
|
+
# # If current user is permitted to create accounts for the given customer, create it
|
39
|
+
# if Account.permits? :create, @customer
|
40
|
+
# Account.create attributes
|
41
|
+
# end
|
42
|
+
#
|
43
|
+
# # If current user is permitted to update the given customer, update it
|
44
|
+
# if @customer.permits? :update
|
45
|
+
# @customer.save
|
46
|
+
# end
|
47
|
+
#
|
48
|
+
# # Raise an exception if user is not permitted to update the given customer, else save it
|
49
|
+
# @customer.ensure_permitted! :update
|
50
|
+
# @customer.save
|
51
|
+
|
52
|
+
included do
|
53
|
+
include ClassMethods
|
54
|
+
end
|
55
|
+
|
56
|
+
module ClassMethods
|
57
|
+
def permits?(action, parent = nil)
|
58
|
+
Trust::Authorization.authorized?(action, self, parent)
|
59
|
+
end
|
60
|
+
def ensure_permitted!(action, parent = nil)
|
61
|
+
Trust::Authorization.authorize!(action, self, parent)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
# Copyright (c) 2012 Bingo Entreprenøren AS
|
2
|
+
# Copyright (c) 2012 Teknobingo Scandinavia AS
|
3
|
+
# Copyright (c) 2012 Knut I. Stenmark
|
4
|
+
# Copyright (c) 2012 Patrick Hanevold
|
5
|
+
#
|
6
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
7
|
+
# a copy of this software and associated documentation files (the
|
8
|
+
# "Software"), to deal in the Software without restriction, including
|
9
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
10
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
11
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
12
|
+
# the following conditions:
|
13
|
+
#
|
14
|
+
# The above copyright notice and this permission notice shall be
|
15
|
+
# included in all copies or substantial portions of the Software.
|
16
|
+
#
|
17
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
18
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
19
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
20
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
21
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
22
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
23
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
24
|
+
|
25
|
+
module Trust
|
26
|
+
class Authorization
|
27
|
+
class << self
|
28
|
+
# Returns true if user is authorized to perform +action+ on +object+ or +class+
|
29
|
+
# If +parent+ is given, +parent+ may be tested in the implemented Permissions class
|
30
|
+
# This method is called by the +can?+ method in Trust::Controller, and is normally
|
31
|
+
# not necessary to call directly.
|
32
|
+
def authorized?(action, object_or_class, parent)
|
33
|
+
if object_or_class.is_a? Class
|
34
|
+
klass = object_or_class
|
35
|
+
object = nil
|
36
|
+
else
|
37
|
+
klass = object_or_class.class
|
38
|
+
object = object_or_class
|
39
|
+
end
|
40
|
+
# Identify which class to instanciate and then check authorization
|
41
|
+
auth = authorizing_class(klass)
|
42
|
+
# Rails.logger.debug "Trust: Authorizing class for #{klass.name} is #{auth.name}"
|
43
|
+
auth.new(user, action.to_sym, klass, object, parent).authorized?
|
44
|
+
end
|
45
|
+
|
46
|
+
# Tests if user is authorized to perform +action+ on +object+ or +class+, with the
|
47
|
+
# optional parent and raises Trust::AccessDenied exception if not permitted.
|
48
|
+
# If using this method directly, an optional +message+ can be passed in to
|
49
|
+
# replace the default message used.
|
50
|
+
# This method is used by the +access_control+ method in Trust::Controller
|
51
|
+
def authorize!(action, object_or_class, parent, message = nil)
|
52
|
+
access_denied!(message, action, object_or_class, parent) unless authorized?(action, object_or_class, parent)
|
53
|
+
end
|
54
|
+
|
55
|
+
def access_denied!(message = nil, action = nil, subject = nil, parent = nil) # nodoc
|
56
|
+
raise AccessDenied.new(message, action, subject)
|
57
|
+
end
|
58
|
+
|
59
|
+
# returns the current +user+ being used in the authorization process
|
60
|
+
def user
|
61
|
+
Thread.current["current_user"]
|
62
|
+
end
|
63
|
+
|
64
|
+
# sets the current +user+ to be used in the authorization process.
|
65
|
+
# the +user+ is thread safe.
|
66
|
+
def user=(user)
|
67
|
+
Thread.current["current_user"] = user
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
def authorizing_class(klass) # nodoc
|
72
|
+
auth = nil
|
73
|
+
klass.ancestors.each do |k|
|
74
|
+
break if k == ::ActiveRecord::Base
|
75
|
+
begin
|
76
|
+
auth = "::Permissions::#{k}".constantize
|
77
|
+
break
|
78
|
+
rescue
|
79
|
+
end
|
80
|
+
end
|
81
|
+
auth || ::Permissions::Default
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,134 @@
|
|
1
|
+
# Copyright (c) 2012 Bingo Entreprenøren AS
|
2
|
+
# Copyright (c) 2012 Teknobingo Scandinavia AS
|
3
|
+
# Copyright (c) 2012 Knut I. Stenmark
|
4
|
+
# Copyright (c) 2012 Patrick Hanevold
|
5
|
+
#
|
6
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
7
|
+
# a copy of this software and associated documentation files (the
|
8
|
+
# "Software"), to deal in the Software without restriction, including
|
9
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
10
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
11
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
12
|
+
# the following conditions:
|
13
|
+
#
|
14
|
+
# The above copyright notice and this permission notice shall be
|
15
|
+
# included in all copies or substantial portions of the Software.
|
16
|
+
#
|
17
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
18
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
19
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
20
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
21
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
22
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
23
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
24
|
+
|
25
|
+
module Trust
|
26
|
+
module Controller
|
27
|
+
class Properties
|
28
|
+
delegate :logger, :to => Rails
|
29
|
+
attr_reader :controller
|
30
|
+
attr_accessor :model
|
31
|
+
attr_accessor :associations
|
32
|
+
attr_accessor :new_actions
|
33
|
+
attr_accessor :member_actions
|
34
|
+
attr_accessor :collection_actions
|
35
|
+
|
36
|
+
def initialize(controller, properties) # nodoc
|
37
|
+
@controller = controller
|
38
|
+
@model = controller.controller_path
|
39
|
+
if properties
|
40
|
+
@associations = properties.associations.dup
|
41
|
+
@new_actions = properties.new_actions.dup
|
42
|
+
@member_actions = properties.member_actions.dup
|
43
|
+
@collection_actions = properties.collection_actions.dup
|
44
|
+
else
|
45
|
+
@associations = {}
|
46
|
+
@new_actions = [:new, :create]
|
47
|
+
@member_actions = [:show, :edit, :update, :destroy]
|
48
|
+
@collection_actions = [:index]
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
class << self
|
53
|
+
# returns a controller properties object
|
54
|
+
# ensures controller properties are instantiated in a correct manner and that inheritance is supported
|
55
|
+
def instantiate(controller)
|
56
|
+
new(controller, controller.superclass.instance_variable_get(:@properties))
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# returns or sets the model to be used in a controller
|
61
|
+
# If not set, the controller_path is used
|
62
|
+
# You can override the model to be accessed in a controller by setting the model
|
63
|
+
#
|
64
|
+
# ==== Example
|
65
|
+
#
|
66
|
+
# # You have a controller which inherits from a generic controller and it has not the same name. Below
|
67
|
+
# model :account # will assume that the class to be Account and instance variables to be @account/@accounts
|
68
|
+
#
|
69
|
+
# # name spaced models
|
70
|
+
# model :"customer/account"
|
71
|
+
#
|
72
|
+
def model(name = nil)
|
73
|
+
@model = name if name
|
74
|
+
@model
|
75
|
+
end
|
76
|
+
|
77
|
+
# Returns the class for the model
|
78
|
+
def model_class
|
79
|
+
model.to_s.classify.constantize
|
80
|
+
end
|
81
|
+
|
82
|
+
# Specify associated resources (nested resources)
|
83
|
+
# Example:
|
84
|
+
# belongs_to :lottery
|
85
|
+
# belongs_to :table, :card_game
|
86
|
+
# belongs_to :card_game, :as => :bridge
|
87
|
+
#
|
88
|
+
def belongs_to(*resources)
|
89
|
+
raise ArgumentError, "You must specify at least one resource after belongs_to" unless resources
|
90
|
+
logger.debug "#{@model} belongs_to #{resources.inspect}"
|
91
|
+
options = resources.extract_options!
|
92
|
+
resources.each do |resource|
|
93
|
+
@associations[resource] = options[:as]
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def has_associations?
|
98
|
+
@associations.size > 0
|
99
|
+
end
|
100
|
+
|
101
|
+
# actions(options)
|
102
|
+
# Options
|
103
|
+
# :new => actions # specify new actions - default id :new, :create
|
104
|
+
# :member => actions # specify member actions - default is :show, :edit, :update, :destroy
|
105
|
+
# :collection => actions # specify collection actions - default is :index
|
106
|
+
# :except => actions # removes any standard actions
|
107
|
+
# :only => actions # selects only the standard actions specifiec
|
108
|
+
# :add => {options} # to add options, eg :add => {:new => :confirm}
|
109
|
+
#
|
110
|
+
def actions(options)
|
111
|
+
if add = options[:add]
|
112
|
+
self.new_actions += Array.wrap(add[:new]) if add[:new]
|
113
|
+
self.member_actions += Array.wrap(add[:member]) if add[:member]
|
114
|
+
self.collection_actions += Array.wrap(add[:collection]) if add[:collection]
|
115
|
+
end
|
116
|
+
self.new_actions = Array.wrap(options[:new]) if options[:new]
|
117
|
+
self.member_actions = Array.wrap(options[:member]) if options[:member]
|
118
|
+
self.collection_actions = Array.wrap(options[:collection]) if options[:collection]
|
119
|
+
if options[:only]
|
120
|
+
only = Array.wrap(options[:only])
|
121
|
+
self.new_actions &= only
|
122
|
+
self.member_actions &= only
|
123
|
+
self.collection_actions &= only
|
124
|
+
end
|
125
|
+
if options[:except]
|
126
|
+
except = Array.wrap(options[:except])
|
127
|
+
self.new_actions -= except
|
128
|
+
self.member_actions -= except
|
129
|
+
self.collection_actions -= except
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|