PageRenderTime 46ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/README.rdoc

http://github.com/stffn/declarative_authorization
Unknown | 632 lines | 476 code | 156 blank | 0 comment | 0 complexity | 3b8891c9aa14e324e32fde6ea05c5cde MD5 | raw file
  1. = Declarative Authorization
  2. The declarative authorization plugin offers an authorization mechanism inspired
  3. by _RBAC_. The most notable distinction to other authorization plugins is the
  4. declarative approach. That is, authorization rules are not defined
  5. programmatically in between business logic but in an authorization configuration.
  6. With programmatic authorization rules, the developer needs to specify which roles are
  7. allowed to access a specific controller action or a part of a view, which is
  8. not DRY. With a growing application code base roles' permissions often
  9. change and new roles are introduced. Then, at several places of the source code
  10. the changes have to be implemented, possibly leading to omissions and thus hard
  11. to find errors. In these cases, a declarative approach as offered by decl_auth
  12. increases the development and maintenance efficiency.
  13. Plugin features
  14. * Authorization at controller action level
  15. * Authorization helpers for Views
  16. * Authorization at model level
  17. * Authorize CRUD (Create, Read, Update, Delete) activities
  18. * Query rewriting to automatically only fetch authorized records
  19. * DSL for specifying Authorization rules in an authorization configuration
  20. * Support for Rails 4, with backwards compatibility through Rails 2
  21. Requirements
  22. * An authentication mechanism
  23. * User object in Controller#current_user
  24. * (For model security) Setting Authorization.current_user
  25. * User objects need to respond to a method :role_symbols that returns an
  26. array of role symbols
  27. See below for installation instructions.
  28. There is a decl_auth screencast by Ryan Bates, nicely introducing the main concepts:
  29. http://railscasts.com/episodes/188-declarative-authorization
  30. = Quick Start
  31. === Installer
  32. Declarative Authorization comes with an installer to make setup easy.
  33. First, include declarative_authorization in your gemfile.
  34. #! Gemfile
  35. gem 'declarative_authorization'
  36. Next, bundle and install.
  37. $ bundle
  38. $ rails g authorization:install [UserModel=User] [field:type field:type ...] [--create-user --commit --user-belongs-to-role]
  39. This installer will create a Role model, an admin and a user role, and set a
  40. has_and_belongs_to_many relationship between the User model and the Role model.
  41. It will also add a `role_symbols` method to the user model to meet
  42. declarative_authorization's requirements. The default User model is User. You can override this by simply typing the name of a model as above.
  43. You can create the model with the fields provided by using the `--create-user` option.
  44. The `--commit` option will run `rake db:migrate` and `rake db:seed`.
  45. The `--user-belongs-to-role` option will set up a one-to-many relationship between Users and Roles.
  46. That is, each user has a role_id column and can only have one role. Role inheritance can be used
  47. in authorization rules.
  48. Finally, the installer also copies default authorization rules, as below.
  49. === Generate Authorization Rules
  50. To copy a default set of authorization rules which includes CRUD priveleges, run:
  51. $ rails g authorization:rules
  52. This command will copy the following to `config/authorization_rules.rb`. Remember
  53. to implement the requirements of this gem as described in the Installation section
  54. at the end of this README if you do not use the above installer.
  55. authorization do
  56. role :guest do
  57. # add permissions for guests here, e.g.
  58. # has_permission_on :conferences, :to => :read
  59. end
  60. # permissions on other roles, such as
  61. # role :admin do
  62. # has_permission_on :conferences, :to => :manage
  63. # end
  64. # role :user do
  65. # has_permission_on :conferences, :to => [:read, :create]
  66. # has_permission_on :conferences, :to => [:update, :delete] do
  67. # if_attribute :user_id => is {user.id}
  68. # end
  69. # end
  70. # See the readme or GitHub for more examples
  71. end
  72. privileges do
  73. # default privilege hierarchies to facilitate RESTful Rails apps
  74. privilege :manage, :includes => [:create, :read, :update, :delete]
  75. privilege :create, :includes => :new
  76. privilege :read, :includes => [:index, :show]
  77. privilege :update, :includes => :edit
  78. privilege :delete, :includes => :destroy
  79. end
  80. === Controller Authorization
  81. For RESTful controllers, add `filter_resource_access`:
  82. class MyRestfulController < ApplicationController
  83. filter_resource_access
  84. ...
  85. end
  86. For a non-RESTful controller, you can use `filter_access_to`:
  87. class MyOtherController < ApplicationController
  88. filter_access_to :all
  89. # or a group: filter_access_to [:action1, :action2]
  90. ...
  91. end
  92. === View Authorization
  93. Declarative Authorization will use `current_user` to check authorization.
  94. <%= link_to 'Edit Post', edit_post_path(@post) if permitted_to? :update, @post %>
  95. = Authorization Data Model
  96. ----- App domain ----|-------- Authorization conf ---------|------- App domain ------
  97. includes includes
  98. .--. .---.
  99. | v | v
  100. .------. can_play .------. has_permission .------------. requires .----------.
  101. | User |----------->| Role |----------------->| Permission |<-----------| Activity |
  102. '------' * * '------' * * '------------' 1 * '----------'
  103. |
  104. .-------+------.
  105. 1 / | 1 \ *
  106. .-----------. .---------. .-----------.
  107. | Privilege | | Context | | Attribute |
  108. '-----------' '---------' '-----------'
  109. In the application domain, each *User* may be assigned to *Roles* that should
  110. define the users' job in the application, such as _Administrator_. On the
  111. right-hand side of this diagram, application developers specify which *Permissions*
  112. are necessary for users to perform activities, such as calling a controller action,
  113. viewing parts of a View or acting on records in the database. Note that
  114. Permissions consist of an *Privilege* that is to be performed, such as _read_,
  115. and a *Context* in that the Operation takes place, such as _companies_.
  116. In the authorization configuration, Permissions are assigned to Roles and Role
  117. and Permission hierarchies are defined. *Attributes* may be employed to allow
  118. authorization according to dynamic information about the context and the
  119. current user, e.g. "only allow access on employees that belong to the
  120. current user's branch."
  121. = Examples
  122. A fully functional example application can be found at
  123. http://github.com/stffn/decl_auth_demo_app
  124. Details on the demonstrated methods can be found in the API docs, either
  125. generated by yourself or at http://www.tzi.org/~sbartsch/declarative_authorization
  126. == Controller
  127. If authentication is in place, there are two ways to enable user-specific
  128. access control on controller actions. For resource controllers, which more
  129. or less follow the CRUD pattern, +filter_resource_access+ is the simplest
  130. approach. It sets up instance variables in before filters and calls
  131. filter_access_to with the appropriate parameters to protect the CRUD methods.
  132. class EmployeesController < ApplicationController
  133. filter_resource_access
  134. ...
  135. end
  136. See Authorization::AuthorizationInController::ClassMethods for options on
  137. nested resources and custom member and collection actions.
  138. By default, declarative_authorization will enable filter_resource_access compatibility with strong_parameters in Rails 4. If you want to disable this behavior, you can use the `:strong_parameters` option.
  139. class EmployeesController < ApplicationController
  140. filter_resource_access :strong_parameters => false
  141. ...
  142. end
  143. Simalarly, you can use `:strong_parameters => true` if you are using strong_parameters in Rails 3.
  144. If you prefer less magic or your controller has no resemblance with the resource
  145. controllers, directly calling filter_access_to may be the better option. Examples
  146. are given in the following. E.g. the privilege index users is required for
  147. action index. This works as a first default configuration for RESTful
  148. controllers, with these privileges easily handled in the authorization
  149. configuration, which will be described below.
  150. class EmployeesController < ApplicationController
  151. filter_access_to :all
  152. def index
  153. ...
  154. end
  155. ...
  156. end
  157. When custom actions are added to such a controller, it helps to define more
  158. clearly which privileges are the respective requirements. That is when the
  159. filter_access_to call may become more verbose:
  160. class EmployeesController < ApplicationController
  161. filter_access_to :all
  162. # this one would be included in :all, but :read seems to be
  163. # a more suitable privilege than :auto_complete_for_user_name
  164. filter_access_to :auto_complete_for_employee_name, :require => :read
  165. def auto_complete_for_employee_name
  166. ...
  167. end
  168. ...
  169. end
  170. For some actions it might be necessary to check certain attributes of the
  171. object the action is to be acting on. Then, the object needs to be loaded
  172. before the action's access control is evaluated. On the other hand, some actions
  173. might prefer the authorization to ignore specific attribute checks as the object is
  174. unknown at checking time, so attribute checks and thus automatic loading of
  175. objects needs to be enabled explicitly.
  176. class EmployeesController < ApplicationController
  177. filter_access_to :update, :attribute_check => true
  178. def update
  179. # @employee is already loaded from param[:id] because of :attribute_check
  180. end
  181. end
  182. You can provide the needed object through before_filters. This way, you have
  183. full control over the object that the conditions are checked against. Just make
  184. sure, your before_filters occur before any of the filter_access_to calls.
  185. class EmployeesController < ApplicationController
  186. before_filter :new_employee_from_params, :only => :create
  187. before_filter :new_employee, :only => [:index, :new]
  188. filter_access_to :all, :attribute_check => true
  189. def create
  190. @employee.save!
  191. end
  192. protected
  193. def new_employee_from_params
  194. @employee = Employee.new(params[:employee])
  195. end
  196. end
  197. If the access is denied, a +permission_denied+ method is called on the
  198. current_controller, if defined, and the issue is logged.
  199. For further customization of the filters and object loading, have a look at
  200. the complete API documentation of filter_access_to in
  201. Authorization::AuthorizationInController::ClassMethods.
  202. == Views
  203. In views, a simple permitted_to? helper makes showing blocks according to the
  204. current user's privileges easy:
  205. <% permitted_to? :create, :employees do %>
  206. <%= link_to 'New', new_employee_path %>
  207. <% end %>
  208. Only giving a symbol :employees as context prevents any checks of attributes
  209. as there is no object to check against. For example, in case of nested resources
  210. a new object may come in handy:
  211. <% permitted_to? :create, Branch.new(:company => @company) do
  212. # or @company.branches.new
  213. # or even @company.branches %>
  214. <%= link_to 'New', new_company_branch_path(@company) %>
  215. <% end %>
  216. Lists are straight-forward:
  217. <% for employee in @employees %>
  218. <%= link_to 'Edit', edit_employee_path(employee) if permitted_to? :update, employee %>
  219. <% end %>
  220. See also Authorization::AuthorizationHelper.
  221. == Models
  222. There are two distinct features for model security built into this plugin:
  223. authorizing CRUD operations on objects as well as query rewriting to limit
  224. results according to certain privileges.
  225. See also Authorization::AuthorizationInModel.
  226. === Model security for CRUD operations
  227. To activate model security, all it takes is an explicit enabling for each
  228. model that model security should be enforced on, i.e.
  229. class Employee < ActiveRecord::Base
  230. using_access_control
  231. ...
  232. end
  233. Thus,
  234. Employee.create(...)
  235. fails, if the current user is not allowed to :create :employees according
  236. to the authorization rules. For the application to find out about what
  237. happened if an operation is denied, the filters throw
  238. Authorization::NotAuthorized exceptions.
  239. As access control on read are costly, with possibly lots of objects being
  240. loaded at a time in one query, checks on read need to be activated explicitly by
  241. adding the :include_read option.
  242. === Query rewriting through named scopes
  243. When retrieving large sets of records from databases, any authorization needs
  244. to be integrated into the query in order to prevent inefficient filtering
  245. afterwards and to use LIMIT and OFFSET in SQL statements. To keep authorization
  246. rules out of the source code, this plugin offers query rewriting mechanisms
  247. through named scopes. Thus,
  248. Employee.with_permissions_to(:read)
  249. returns all employee records that the current user is authorized to read. In
  250. addition, just like normal named scopes, query rewriting may be chained with
  251. the usual find method:
  252. Employee.with_permissions_to(:read).find(:all, :conditions => ...)
  253. If the current user is completely missing the permissions, an
  254. Authorization::NotAuthorized exception is raised. Through
  255. Model.obligation_conditions, application developers may retrieve
  256. the conditions for manual rewrites.
  257. == Authorization Rules
  258. Authorization rules are defined in config/authorization_rules.rb
  259. (Or redefine rules files path via +Authorization::AUTH_DSL_FILES+). E.g.
  260. authorization do
  261. role :admin do
  262. has_permission_on :employees, :to => [:create, :read, :update, :delete]
  263. end
  264. end
  265. There is a default role :+guest+ that is used if a request is not associated
  266. with any user or with a user without any roles. So, if your application has
  267. public pages, :+guest+ can be used to allow access for users that are not
  268. logged in. All other roles are application defined and need to be associated
  269. with users by the application.
  270. If you need to change the default role, you can do so by adding an initializer
  271. that contains the following statement:
  272. Authorization.default_role = :anonymous
  273. Privileges, such as :create, may be put into hierarchies to simplify
  274. maintenance. So the example above has the same meaning as
  275. authorization do
  276. role :admin do
  277. has_permission_on :employees, :to => :manage
  278. end
  279. end
  280. privileges do
  281. privilege :manage do
  282. includes :create, :read, :update, :delete
  283. end
  284. end
  285. Privilege hierarchies may be context-specific, e.g. applicable to :employees.
  286. privileges do
  287. privilege :manage, :employees, :includes => :increase_salary
  288. end
  289. For more complex use cases, authorizations need to be based on attributes. Note
  290. that you then also need to set :attribute_check => true in controllers for filter_access_to.
  291. E.g. if a branch admin should manage only employees of his branch (see
  292. Authorization::Reader in the API docs for a full list of available operators):
  293. authorization do
  294. role :branch_admin do
  295. has_permission_on :employees do
  296. to :manage
  297. # user refers to the current_user when evaluating
  298. if_attribute :branch => is {user.branch}
  299. end
  300. end
  301. end
  302. To reduce redundancy in has_permission_on blocks, a rule may depend on
  303. permissions on associated objects:
  304. authorization do
  305. role :branch_admin do
  306. has_permission_on :branches, :to => :manage do
  307. if_attribute :managers => contains {user}
  308. end
  309. has_permission_on :employees, :to => :manage do
  310. if_permitted_to :manage, :branch
  311. # instead of
  312. #if_attribute :branch => {:managers => contains {user}}
  313. end
  314. end
  315. end
  316. Lastly, not only privileges may be organized in a hierarchy but roles as well.
  317. Here, project manager inherit the permissions of employees.
  318. role :project_manager do
  319. includes :employee
  320. end
  321. See also Authorization::Reader.
  322. == Testing
  323. declarative_authorization provides a few helpers to ease the testing with
  324. authorization in mind.
  325. In your test_helper.rb, to enable the helpers add
  326. require 'declarative_authorization/maintenance'
  327. class Test::Unit::TestCase
  328. include Authorization::TestHelper
  329. ...
  330. end
  331. For using the test helpers with RSpec, just add the following lines to your
  332. spec_helper.rb (somewhere after require 'spec/rails'):
  333. require 'declarative_authorization/maintenance'
  334. include Authorization::TestHelper
  335. Now, in unit tests, you may deactivate authorization if needed e.g. for test
  336. setup and assume certain identities for tests:
  337. class EmployeeTest < ActiveSupport::TestCase
  338. def test_should_read
  339. without_access_control do
  340. Employee.create(...)
  341. end
  342. assert_nothing_raised do
  343. with_user(admin) do
  344. Employee.find(:first)
  345. end
  346. end
  347. end
  348. end
  349. Or, with RSpec, it would work like this:
  350. describe Employee do
  351. it "should read" do
  352. without_access_control do
  353. Employee.create(...)
  354. end
  355. with_user(admin) do
  356. Employee.find(:first)
  357. end
  358. end
  359. end
  360. In functional tests, get, posts, etc. may be tested in the name of certain users:
  361. get_with admin, :index
  362. post_with admin, :update, :employee => {...}
  363. See Authorization::TestHelper for more information.
  364. = Installation of declarative_authorization
  365. One of three options to install the plugin:
  366. * Install by Gem: Add to your environment.rb in the initializer block:
  367. config.gem "declarative_authorization"
  368. Note: you need gemcutter support in place, i.e. call
  369. gem install gemcutter
  370. gem tumble
  371. And call from your application's root directory
  372. rake gems:install
  373. * Alternativelyi, in Rails 2, to install from github, execute in your application's root directory
  374. cd vendor/plugins && git clone git://github.com/stffn/declarative_authorization.git
  375. Then,
  376. * provide the requirements as noted below,
  377. * create a basic config/authorization_rules.rb--you might want to take the
  378. provided example authorization_rules.dist.rb in the plugin root as a starting
  379. point,
  380. * add +filter_access_to+, +permitted_to+? and model security as needed.
  381. == Providing the Plugin's Requirements
  382. The requirements are
  383. * Rails >= 2.2, including 3 and Ruby >= 1.8.6, including 1.9
  384. * An authentication mechanism
  385. * A user object returned by Controller#current_user
  386. * An array of role symbols returned by User#role_symbols
  387. * (For model security) Setting Authorization.current_user to the request's user
  388. Of the various ways to provide these requirements, here is one way employing
  389. restful_authentication.
  390. * Install restful_authentication
  391. cd vendor/plugins && git clone git://github.com/technoweenie/restful-authentication.git restful_authentication
  392. cd ../.. && ruby script/generate authenticated user sessions
  393. * Move "include AuthenticatedSystem" to ApplicationController
  394. * Add +filter_access_to+ calls as described above.
  395. * If you'd like to use model security, add a before_filter that sets the user
  396. globally to your ApplicationController. This is thread-safe.
  397. before_filter :set_current_user
  398. protected
  399. def set_current_user
  400. Authorization.current_user = current_user
  401. end
  402. * Add roles field to the User model through a :+has_many+ association
  403. (this is just one possible approach; you could just as easily use
  404. :+has_many+ :+through+ or a serialized roles array):
  405. * create a migration for table roles
  406. class CreateRoles < ActiveRecord::Migration
  407. def self.up
  408. create_table "roles" do |t|
  409. t.column :title, :string
  410. t.references :user
  411. end
  412. end
  413. def self.down
  414. drop_table "roles"
  415. end
  416. end
  417. * create a model Role,
  418. class Role < ActiveRecord::Base
  419. belongs_to :user
  420. end
  421. * add +has_many+ :+roles+ to the User model and a roles method that returns the roles
  422. as an Array of Symbols, e.g.
  423. class User < ActiveRecord::Base
  424. has_many :roles
  425. def role_symbols
  426. (roles || []).map {|r| r.title.to_sym}
  427. end
  428. end
  429. * add roles to your User objects using e.g.
  430. user.roles.create(:title => "admin")
  431. Note: If you choose to generate an Account model for restful_authentication
  432. instead of a User model as described above, you have to customize the
  433. examples and create a ApplicationController#current_user method.
  434. == Debugging Authorization
  435. Currently, the main means of debugging authorization decisions is logging and
  436. exceptions. Denied access to actions is logged to +warn+ or +info+, including
  437. some hints about what went wrong.
  438. All bang methods throw exceptions which may be used to retrieve more
  439. information about a denied access than a Boolean value.
  440. == Authorization Development Support
  441. If your authorization rules become more complex, you might be glad to use
  442. the authorization rules browser that comes with declarative_authorization.
  443. It has a syntax-highlighted and a graphical view with filtering of the current
  444. authorization rules.
  445. By default, it will only be available in development mode. To use it, add
  446. the following lines to your authorization_rules.rb for the appropriate role:
  447. has_permission_on :authorization_rules, :to => :read
  448. Then, point your browser to
  449. http://localhost/authorization_rules
  450. The browser needs Rails 2.3 (for Engine support). The graphical view requires
  451. Graphviz (which e.g. can be installed through the graphviz package under Debian
  452. and Ubuntu) and has only been tested under Linux. Note: for Change Support
  453. you'll need to have a User#login method that returns a non-ambiguous user
  454. name for identification.
  455. = Help and Contact
  456. We have an issue tracker[http://github.com/stffn/declarative_authorization/issues]
  457. for bugs and feature requests as well as a
  458. Google Group[http://groups.google.com/group/declarative_authorization] for
  459. discussions on the usage of the plugin. You are very welcome to contribute.
  460. Just fork the git repository and create a new issue, send a pull request or
  461. contact me personally.
  462. Maintained by
  463. Steffen Bartsch
  464. TZI, Universität Bremen, Germany
  465. sbartsch at tzi.org
  466. = Contributors
  467. Thanks to John Joseph Bachir, Dennis BlĂśte, Eike Carls, Damian Caruso, Kai Chen, Erik Dahlstrand,
  468. Jeroen van Dijk, Alexander Dobriakov, Sebastian Dyck, Ari Epstein, Jeremy Friesen,
  469. Tim Harper, John Hawthorn, hollownest, Daniel Kristensen, Jeremy Kleindl, Joel Kociolek,
  470. Benjamin ter Kuile, Brad Langhorst, Brian Langenfeld,
  471. Georg Ledermann, Geoff Longman, Olly Lylo, Mark Mansour, Thomas Maurer, Kevin Moore,
  472. Tyler Pickett, Edward Rudd, Sharagoz,
  473. TJ Singleton, Mike Vincent, Joel Westerberg
  474. = License
  475. Copyright (c) 2008 Steffen Bartsch, TZI, Universität Bremen, Germany
  476. released under the MIT license