forked from stffn/declarative_authorization
/
README
320 lines (248 loc) · 11.9 KB
/
README
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
= Declarative Authorization
The declarative authorization plugin offers an authorization mechanism inspired
by _RBAC_. The most notable distinction to existing authorization plugins is the
declarative authorization approach. That is, authorization rules are not
programmatically in between business logic but in an authorization configuration.
Currently, Rails authorization plugins only provide for programmatic
authorization rules. That is, the developer needs to specify which roles are
allowed to access a specific controller action or a part of a view, which is
not DRY. With a growing application code base and functions, as it happens
especially in agile development processes, it may be decided to introduce new
roles. Then, at several places of the source code the new group needs to be
added, possibly leading to omissions and thus hard to test errors. Another
aspect are changing authorization requirements in development or
even after taking the application into production. Then, privileges of
certain roles need to be easily adjusted when the original assumptions
concerning access control prove unrealistic. In these situations, a
declarative approach as offered by this plugin increases the development
and maintenance efficiency.
Plugin features
* Authorization at controller action level
* Authorization helpers for Views
* Authorization at model level
* Authorize CRUD (Create, Read, Update, Delete) activities
* Query rewriting to automatically only fetch authorized records
* DSL for specifying Authorization rules in an authorization configuration
Requirements
* An authentication mechanism
* User object in Controller#current_user
* User object needs to respond to a method :roles, which should return an
array of role symbols
See below for installation instructions.
= Authorization Data Model
----- App domain ----|-------- Authorization conf ---------|------- App domain ------
includes includes
.--. .---.
| v | v
.------. can_play .------. has_permission .------------. requires .----------.
| User |----------->| Role |----------------->| Permission |<-----------| Activity |
'------' * * '------' * * '------------' 1 * '----------'
|
.-------+------.
1 / | 1 \ *
.-----------. .---------. .-----------.
| Privilege | | Context | | Attribute |
'-----------' '---------' '-----------'
In the application domain, each *User* may be assigned to *Roles* that should
define the users' job in the application, such as _Administrator_. On the
right-hand side of this diagram, application developers specify which *Permissions*
are necessary for users to perform activities, such as calling a controller action,
viewing parts of a View or acting on records in the database. Note that
Permissions consist of an *Privilege* that is to be performed, such as _read_,
and a *Context* in that the Operation takes place, such as _companies_.
In the authorization configuration, Permissions are assigned to Roles and Role
and Permission hierarchies are defined. *Attributes* may be employed to allow
authorization according to dynamic information about the context and the
current user, e.g. "only allow access on employees that belong to the
current user's branch."
= Examples
== Controller
If authentication is in place, enabling user-specific access control may be
as simple as one call to filter_access_to :all which simply requires the
according privileges for present actions. E.g. the privilege index_users is
required for action index. This works as a first default configuration
for RESTful controllers, with these privileges easily handled in the
authorization configuration, which will be described below.
class EmployeeController < ApplicationController
filter_access_to :all
def index
...
end
...
end
When custom actions are added to such a controller, it helps to define more
clearly which privileges are the respective requirements. That is when the
filter_access_to call may become more verbose:
class EmployeeController < ApplicationController
filter_access_to :all
# this one would be included in :all, but :read seems to be
# a more suitable privilege than :auto_complete_for_user_name
filter_access_to :auto_complete_for_employee_name, :require => :read
def auto_complete_for_employee_name
...
end
...
end
For some actions it might be necessary to check certain attributes of the
object the action is to be acting on. Then, the object needs to be loaded
before the action's access control is evaluated. On the other hand, some actions
might prefer the authorization to ignore specific attribute checks as the object is
unknown at checking time, so attribute checks and thus automatic loading of
objects needs to be enabled explicitly.
class EmployeeController < ApplicationController
...
filter_access_to :update, :attribute_check => true
def update
# @employee is already loaded from param[:id]
@employee ||= Employee.find(param[:id])
...
end
...
end
If the access is denied, a +permission_denied+ method is called on the
current_controller, if defined and the issue is logged.
For further customization of the filters and object loading, have a look at
the complete API documentation of filter_access_to in
Authorization::AuthorizationInController::ClassMethods.
== Views
In views, a simple permitted_to? helper makes showing blocks according to the
current user's privileges easy:
<% permitted_to?(:create, :employees) do %>
<%= link_to 'New', new_employee_path %>
<% end %>
...
<% for employee in @employees %>
...
<%= link_to 'Edit', edit_employee_path(company) if permitted_to?(:update, employee) %>
<% end %>
See also Authorization::AuthorizationHelper.
== Models
There are two destinct features for model security built into this plugin:
authorizing CRUD operations on objects as well as query rewriting to limit
results according to certain privileges.
See also Authorization::AuthorizationInModel.
=== Model security for CRUD opterations
To activate model security, all it takes is an explicit enabling for each
model that model security should be enforced on, i.e.
class Employee < ActiveRecord::Base
using_access_control
...
end
Thus,
Employee.create(...)
fails, if the current user is not allowed to :create :employees according
to the authorization rules. For the application to find out about what
happened if an operation is denied, the filters throw
Authorization::NotAuthorized exceptions.
As access control on read are costly, with possibly lots of objects being
loaded at a time in one query, checks on read need to be actived explicitly by
adding the :include_read option.
=== Query rewriting using named scopes
When retrieving large sets of records from databases, any authorization needs
to be integrated into the query in order to prevent inefficient filtering
afterwards and to use LIMIT and OFFSET in SQL statements. To keep authorization
rules out of the source code, this plugin offers query rewriting mechanisms
through named scopes. Thus,
Employee.with_permissions_to(:read)
returns all employee records that the current user is authorized to read. In
addition, just like normal named scopes, query rewriting may be chained with
the usual find method:
Employee.with_permissions_to(:read).find(:all, :conditions => ...)
If the current user is completely missing the permissions, an
Authorization::NotAuthorized exception is raised. Through
Model.obligation_conditions, application developers may retrieve
the conditions for manual rewrites.
== Authorization Rules
Authorization rules are defined in config/authorization_rules.rb. E.g.
authorization do
role :admin do
has_permission_on :employees, :to => [:create, :read, :update, :delete]
end
end
There is a default role :+guest+ that is used if a request is not associated
with any user or with a user without any roles. So, if your application has
public pages, :+guest+ can be used to allow access for users that are not
logged in. All other roles are application defined and need to be associated
with users by the application.
Privileges, such as :create, may be put into hierarchies to simplify
maintenance. So the example above has the same meaning as
authorization do
role :admin do
has_permission_on :employees, :to => :manage
end
end
privileges do
privilege :manage do
includes :create, :read, :update, :delete
end
end
Privilege hierarchies may be context-specific, e.g. applicable to :employees.
privileges do
privilege :manage, :employees, :includes => :increase_salary
end
For more complex use cases, authorizations need to be based on attributes. E.g.
if a branch admin should manage only employees of his branch:
authorization do
role :branch_admin do
has_permission.on :employees do
to :manage
# user refers to the current_user when evaluating
if_attribute :branch => is {user.branch}
end
end
end
Lastly, not only privileges may be organized in a hierarchy but roles as well.
Here, project manager inherit the permissions of employees.
role :project_manager do
includes :employee
end
See also Authorization::Reader.
= Installation of declarative_authorization and Usage
To install simply execute in your applications root directory
cd vendor/plugins && git clone git://github.com/stffn/declarative_authorization.git
Then,
* provide the requirements as noted below,
* create a basic config/authorization_rules.rb--you might want to take the
provided example authorization_rules.dist.rb in the plugin root as a starting
point,
* add +filter_access_to+, +permitted_to+? and model security as needed.
== Providing the Plugin's Requirements
The requirements are
* An authentication mechanism
* A user object returned by controller.current_user
* An array of role symbols returned by user.roles
Of the various ways to provide these requirements, here is one way employing
restful_authentication:
* Install restful_authentication
cd vendor/plugins && git clone git://github.com/technoweenie/restful-authentication.git restful_authentication
ruby script/generate authenticated user sessions
* Move "include AuthenticatedSystem" to ApplicationController
* Add +before_filter+ :+login_required+ to controllers where a user should be
forced to log in; if parts of your application are public,
+filter_access_to+ calls as described above are sufficient.
* If you'd like to use model security, add a before_filter that sets the user
globally to your ApplicationController. Note, this is by no means thread-safe.
before_filter :set_current_user
protected
def set_current_user
Authorization.current_user = current_user
end
* Add roles field to User model: a migration and table for roles,
+has_many+ :+roles+ in User model and a roles method that returns the roles
as an Array of Symbols, e.g.
def roles
(super || []).map {|r| r.title.to_sym}
end
== Debugging Authorization
Currently, the main means of debugging authorization decisions is logging and
exceptions. Denied access to actions is logged to +warn+ or +info+, including
some hints about what went wrong.
All bang methods throw exceptions which may be used to retrieve more
information about a denied access than a Boolean value.
== Contact
Steffen Bartsch
TZI, Universität Bremen, Germany
sbartsch at tzi.org
== Licence
Copyright (c) 2008 Steffen Bartsch, TZI, Universität Bremen, Germany
released under the MIT license