| 1 | = Authorization plugin |
| 2 | |
| 3 | See the following wiki page for the latest version of this documentation: |
| 4 | |
| 5 | http://code.google.com/p/rails-authorization-plugin/w/list |
| 6 | |
| 7 | This plugin provides a flexible way to add authorization to Rails. |
| 8 | |
| 9 | The authorization process decides whether a user is allowed access to some |
| 10 | feature. It is distinct from the authentication process, which tries to |
| 11 | confirm a user is authentic, not an imposter. There are many authentication |
| 12 | systems available for Rails, e.g., acts_as_authenticated and LoginEngine. This |
| 13 | authorization system will play nicely with them as long as some simple |
| 14 | requirements are met: |
| 15 | |
| 16 | 1. User objects are available that implement a has_role?(role, |
| 17 | authorizable_object = nil) method. This requirement can be easily |
| 18 | handled by using acts_as_authorized_user in the User-like class. |
| 19 | |
| 20 | 2. If you want to use "role of model" authorization expressions, like "owner of |
| 21 | resource" or "eligible for :award", then your models with roles must |
| 22 | implement an accepts_role?(role, user) method. This requirement can |
| 23 | be handled by using acts_as_authorizable in the model class. |
| 24 | |
| 25 | The authorization plugin provides the following: |
| 26 | |
| 27 | * A simple way of checking authorization at either the class or instance method |
| 28 | level using #permit and #permit? |
| 29 | |
| 30 | * Authorization using roles for the entire application, a model class, or an |
| 31 | instance of a model (i.e., a particular object). |
| 32 | |
| 33 | * Some english-like dynamic methods that draw on the defined roles. You will be |
| 34 | able to use methods like "user.is_fan_of angelina" or "angelina.has_fans?", |
| 35 | where a 'fan' is only defined in the roles table. |
| 36 | |
| 37 | * Pick-and-choose a mixin for your desired level of database complexity. For |
| 38 | all the features, you will want to use "object roles table" (see below) |
| 39 | |
| 40 | |
| 41 | == Installation |
| 42 | |
| 43 | Installation of the Authorization plugin is quick and easy. |
| 44 | |
| 45 | === Step 1 |
| 46 | |
| 47 | Open a terminal and change directory to the root of your |
| 48 | Ruby on Rails application referred to here as 'RAILS_ROOT'. You |
| 49 | can choose to install the plugin in the standard recommended way, |
| 50 | or as a Git sub-module. |
| 51 | |
| 52 | === Step 2 |
| 53 | |
| 54 | ==== Standard install, recommended |
| 55 | |
| 56 | Run the following command in your RAILS_ROOT: |
| 57 | |
| 58 | ./script/plugin install http://rails-authorization-plugin.googlecode.com/svn/trunk/authorization |
| 59 | |
| 60 | This will install the latest version of the plugin from SVN trunk |
| 61 | into your RAILS_ROOT/vendor/plugins/authorization directory. |
| 62 | |
| 63 | ==== Alternative install using Git sub-module, for users of git |
| 64 | |
| 65 | The source code for this plugin is maintained in a Git SCM |
| 66 | repository (The code in the SVN repository here at Google |
| 67 | Code is a read-only mirror). The Git repository will always |
| 68 | have the latest version of the code. |
| 69 | |
| 70 | You can install the plugin using Git sub-modules (which |
| 71 | are akin to using SVN externals). Installing this way allows |
| 72 | you to update the plugin code later if needed (but note that |
| 73 | it will not update any generated code created earlier by this |
| 74 | plugin such as migrations, you will need to update that manually). |
| 75 | Also note that if you are deploying your code using Capistrano |
| 76 | this method may cause issues if you are not careful (e.g. the code |
| 77 | will be deployed but the sub-modules will not be updated or |
| 78 | installed at all). |
| 79 | |
| 80 | From your RAILS_ROOT directory run: |
| 81 | |
| 82 | git submodule add git://github.com/DocSavage/rails-authorization-plugin.git vendor/plugins/authorization |
| 83 | |
| 84 | You should be able to update this plugin in the future with |
| 85 | the simple command (again from RAILS_ROOT): |
| 86 | |
| 87 | git submodule update |
| 88 | |
| 89 | ==== Alternative manual install |
| 90 | |
| 91 | If you like to install the old school manual way, feel free to download a copy of the plugin code from: |
| 92 | |
| 93 | http://github.com/docsavage/rails-authorization-plugin/tarball/master |
| 94 | |
| 95 | Once downloaded, you can unpack that file in your RAILS_ROOT/vendor/plugins directory. |
| 96 | |
| 97 | |
| 98 | == Configuration |
| 99 | |
| 100 | These instructions will show you how to do the initial configuration |
| 101 | of the plugin. |
| 102 | |
| 103 | === Choose a Mixin Type |
| 104 | |
| 105 | ==== Hardwired Roles |
| 106 | |
| 107 | This is the simplest way to use the plugin and requires no database. |
| 108 | Roles are assumed to be coded into the Model classes using the |
| 109 | <tt>has_role?(role, obj = nil)</tt> method. This method is however more |
| 110 | limited in the functionality available to you. |
| 111 | |
| 112 | ==== Object Roles (Recommended, DB Required) |
| 113 | |
| 114 | The Object Roles Table mixin provides full support for authorization |
| 115 | expressions within a database by add a polymorphic field to the |
| 116 | Role table. Because roles have polymorphic associations to an |
| 117 | authorizable object, we can assign a user to a role for any model |
| 118 | instance. So you could declare user X to be a moderator for workshop Y, |
| 119 | or you could make user A be the owner of resource B. |
| 120 | |
| 121 | The identity module adds a number of dynamic methods that use defined |
| 122 | roles. The user-like model gets methods like `user.is_moderator_of |
| 123 | group (sets user to "moderator" of group`), user.is_moderator? (returns |
| 124 | true/false if user has some role "moderator"), and group.has_moderators |
| 125 | (returns an array of users that have role "moderator" for the group). If |
| 126 | you prefer not to have these dynamic methods available, you can simply |
| 127 | comment out the inclusion of the identity module within object_roles_table.rb. |
| 128 | |
| 129 | === Initial Configuration Instructions |
| 130 | |
| 131 | Choose one of the installation types identified above and make sure your |
| 132 | application provides a current_user method or something that returns the |
| 133 | current user object (resful_authentication provides this out of the box). |
| 134 | |
| 135 | At the top of your RAILS_ROOT/config/environment.rb file add something |
| 136 | like the following (customized for your controllers and actions of course): |
| 137 | |
| 138 | ... |
| 139 | |
| 140 | # Authorization plugin for role based access control |
| 141 | # You can override default authorization system constants here. |
| 142 | |
| 143 | # Can be 'object roles' or 'hardwired' |
| 144 | AUTHORIZATION_MIXIN = "object roles" |
| 145 | |
| 146 | # NOTE : If you use modular controllers like '/admin/products' be sure |
| 147 | # to redirect to something like '/sessions' controller (with a leading slash) |
| 148 | # as shown in the example below or you will not get redirected properly |
| 149 | # |
| 150 | # This can be set to a hash or to an explicit path like '/login' |
| 151 | # |
| 152 | LOGIN_REQUIRED_REDIRECTION = { :controller => '/sessions', :action => 'new' } |
| 153 | PERMISSION_DENIED_REDIRECTION = { :controller => '/home', :action => 'index' } |
| 154 | |
| 155 | # The method your auth scheme uses to store the location to redirect back to |
| 156 | STORE_LOCATION_METHOD = :store_location |
| 157 | |
| 158 | # standard rails config below here |
| 159 | Rails::Initializer.run do |config| |
| 160 | |
| 161 | ... |
| 162 | |
| 163 | * Set the AUTHORIZATION_MIXIN constant to object roles or hardwired. (See init.rb in this plugin for how the role support is mixed in.) |
| 164 | * Set the LOGIN_REQUIRED_REDIRECTION to match the path or a hash with the :controller and :action for your applications login page. |
| 165 | * Set the PERMISSION_DENIED_REDIRECTION to match the path or a hash with the :controller and :action for your applications permission denied page. |
| 166 | * Set the STORE_LOCATION_METHOD to the method your application uses for storing the current URL that the user should return to after authentication (e.g. store_location). |
| 167 | * See the PLUGIN_DIR\lib\authorization.rb file for the default values of LOGIN_REQUIRED_REDIRECTION, PERMISSION_DENIED_REDIRECTION and STORE_LOCATION_METHOD. |
| 168 | |
| 169 | |
| 170 | === Create the database tables |
| 171 | |
| 172 | If you plan to use the object roles method you will need to setup a few |
| 173 | database tables. We have provided a database migration file |
| 174 | (Rails 2.0+ compatible) that will make this process easy for you. |
| 175 | If you plan to use the hardwired mixin, no extra database tables |
| 176 | are required. and you can skip to the next step. |
| 177 | |
| 178 | Run the following command from your RAILS_ROOT (Note : The generator |
| 179 | takes a model name as its argument, which at this time must be 'Role'.): |
| 180 | |
| 181 | ./script/generate role_model Role |
| 182 | |
| 183 | This will create: |
| 184 | |
| 185 | Model: RAILS_ROOT/app/models/role.rb |
| 186 | Test: RAILS_ROOT/test/unit/role_test.rb |
| 187 | Fixtures: RAILS_ROOT/test/fixtures/roles.yml |
| 188 | Migration: RAILS_ROOT/db/migrate/###_add_role.rb |
| 189 | |
| 190 | And now you will need to run a database migration from your RAILS_ROOT: |
| 191 | |
| 192 | rake db:migrate |
| 193 | |
| 194 | === Jumpstarting with a mixin |
| 195 | |
| 196 | Now we need to add the methods needed by each of your models that will |
| 197 | participate in role based authorization. Typically these models fall into |
| 198 | two categories, the User model, and all other models that will have |
| 199 | roles available for use. |
| 200 | |
| 201 | For a typical installation you would add both mixins to your User model. |
| 202 | |
| 203 | class User < ActiveRecord::Base |
| 204 | |
| 205 | # Authorization plugin |
| 206 | acts_as_authorized_user |
| 207 | acts_as_authorizable |
| 208 | |
| 209 | ... |
| 210 | |
| 211 | Then in each additional model that you want to be able to restrict based |
| 212 | on role you would add just the acts_as_authorizable mixin like this: |
| 213 | |
| 214 | class Event < ActiveRecord::Base |
| 215 | |
| 216 | acts_as_authorizable |
| 217 | |
| 218 | ... |
| 219 | |
| 220 | You are done with the configuration! |
| 221 | |
| 222 | |
| 223 | == The Specifics |
| 224 | |
| 225 | === permit and permit? |
| 226 | |
| 227 | permit and permit? take an authorization expression and a hash of options that |
| 228 | typically includes any objects that need to be queried: |
| 229 | |
| 230 | permit <authorization expression> [, options hash ] |
| 231 | permit? <authorization expression> [, options hash ] |
| 232 | |
| 233 | The difference between permit and permit? is redirection. permit is a |
| 234 | declarative statement and redirects by default. It can also be used as a class |
| 235 | or an instance method, gating the access to an entire controller in a |
| 236 | before_filter fashion. |
| 237 | |
| 238 | permit? is only an instance method, can be used within expressions, does not |
| 239 | redirect by default. |
| 240 | |
| 241 | The authorization expression is a boolean expression made up of permitted |
| 242 | roles, prepositions, and authorizable models. Examples include "admin" (User |
| 243 | model assumed), "moderator of :workshop" (looks at options hash and then |
| 244 | @workshop), "'top salesman' at :company" (multiword roles delimited by single |
| 245 | quotes), or "scheduled for Exam" (queries class method of Exam). |
| 246 | |
| 247 | Note that we can use several permitted prepositions ('of', 'for', 'in', 'on', |
| 248 | 'to', 'at', 'by'). In the discussion below, we assume you use the "of" |
| 249 | preposition. You can modify the permitted prepositions by changing the constant |
| 250 | in Authorization::Base::Parser. |
| 251 | |
| 252 | * If a specified role has no "of <model>" designation, we assume it is a user |
| 253 | role (i.e., the model is the user-like object). |
| 254 | |
| 255 | * If an "of model" designation is given but no "model" key/value is supplied in |
| 256 | the hash, we check if an instance variable @model if it's available. |
| 257 | |
| 258 | * If the model is capitalized, we assume it's a class and query |
| 259 | <tt>Model#self.accepts_role?</tt> (the class method) for the |
| 260 | permission. (Currently only available in ObjectRolesTable mixin.) |
| 261 | |
| 262 | For each role, a query is sent to the appropriate model object. |
| 263 | |
| 264 | The grammar for the authorization expression is: |
| 265 | |
| 266 | <expr> ::= (<expr>) | not <expr> | <term> or <expr> | <term> and <expr> | <term> |
| 267 | <term> ::= <role> | <role> <preposition> <model> |
| 268 | <preposition> ::= of | for | in | on | to | at | by |
| 269 | <model> ::= /:*\w+/ |
| 270 | <role> ::= /\w+/ | /'.*'/ |
| 271 | |
| 272 | Parentheses should be used to clarify permissions. Note that you may prefix the |
| 273 | model with an optional ":" -- the first versions of Authorization plugin made |
| 274 | this mandatory but it's now optional since the mandatory preposition makes |
| 275 | models unambiguous. |
| 276 | |
| 277 | ==== Options |
| 278 | |
| 279 | <tt>:allow_guests => false</tt>. We can allow permission processing without a |
| 280 | current user object. The default is <tt>false</tt>. |
| 281 | |
| 282 | <tt>:user</tt> => A <tt>user</tt> object. |
| 283 | |
| 284 | <tt>:get_user_method => method</tt> that will return a <tt>user</tt> |
| 285 | object. Default is <tt>#current_user</tt>, which is the how |
| 286 | <tt>acts_as_authenticated</tt> works. |
| 287 | |
| 288 | <tt>:only => [ :method1, :method2 ]</tt>. Array of methods to apply permit (not |
| 289 | valid when used in instance methods) |
| 290 | |
| 291 | <tt>:except => [ :method1, :method2 ]</tt>. Array of methods that won't have |
| 292 | permission checking (not valid when used in instance methods) |
| 293 | |
| 294 | <tt>:redirect => bool</tt>. default is <tt>true</tt>. If <tt>false</tt>, permit |
| 295 | will not redirect to denied page. |
| 296 | |
| 297 | <tt>:login_required_redirection => path or hash</tt> where user will be |
| 298 | redirected if not logged in (default is "{ :controller => 'session', :action => |
| 299 | 'new' }") |
| 300 | |
| 301 | <tt>:login_required_message => 'my message'</tt> (default is 'Login is required |
| 302 | to access the requested page.') |
| 303 | |
| 304 | <tt>:permission_denied_redirection => path or hash</tt> where user will be |
| 305 | redirected if logged in but not authorized (default is '') |
| 306 | |
| 307 | <tt>:permission_denied_message => 'my message</tt> (default is 'Permission |
| 308 | denied. You cannot access the requested page.') |
| 309 | |
| 310 | === Setting and getting the roles |
| 311 | |
| 312 | Roles are set by #has_role and #accepts_role methods that are mixed into the |
| 313 | User-like object and the authorizable models. User objects can set roles and |
| 314 | optionally an object scope for that role: |
| 315 | |
| 316 | user.has_role 'site_admin' |
| 317 | user.has_role 'moderator', group |
| 318 | user.has_no_role 'site_admin' |
| 319 | user.has_no_role 'moderator', group |
| 320 | user.has_role 'member', Group |
| 321 | |
| 322 | Note that the last method sets role "member" on a class "Group". Roles can be |
| 323 | set with three scopes: entire application (no class or object specified), a |
| 324 | model class, or an instance of a model (i.e., a model object). |
| 325 | |
| 326 | Models set roles for specific users: |
| 327 | |
| 328 | a_model.accepts_role 'moderator', user |
| 329 | a_model.accepts_no_role 'moderator', user |
| 330 | Model.accepts_role 'class moderator', user |
| 331 | |
| 332 | The method language has been chosen to aid memory of the argument order. A user |
| 333 | has a role "foo", so the role string immediately follows has_role. Similarly, a |
| 334 | model accepts a role "foo", so the role string immediately follows |
| 335 | accepts_role. Then we append the scope. |
| 336 | |
| 337 | Sometimes the user-like object might be an authorizable object as well, for example, when you |
| 338 | allow 'friend' roles for users. In this case, the user-like object can be declared to be |
| 339 | <tt>acts_as_authorizable</tt> as well as <tt>acts_as_authorized_user</tt>. |
| 340 | |
| 341 | Role queries follow the same pattern as the setting of roles: |
| 342 | |
| 343 | user.has_role? 'moderator' |
| 344 | user.has_role? 'moderator', group |
| 345 | user.has_role? 'member', Group |
| 346 | |
| 347 | a_model.accepts_role? 'moderator', user |
| 348 | Model.accepts_role? 'moderator', user |
| 349 | |
| 350 | When a user is queried without specifying either a model class or object, it |
| 351 | returns true if the user has *any* matching role. For example, |
| 352 | <tt>user.has_role? 'moderator'</tt> returns true if the user is 'moderator' of |
| 353 | a class, a model object, or just a generic 'moderator'. Note that if you say |
| 354 | <tt>user.has_role 'moderator'</tt>, the user does not become 'moderator' for |
| 355 | all classes and model objects; the user simply has a generic role 'moderator'. |
| 356 | |
| 357 | ==== Dynamic methods through the Identity mixin |
| 358 | |
| 359 | The Object Roles Table version includes some dynamic methods that use the roles |
| 360 | table. For example, if you have roles like "eligible", "moderator", and |
| 361 | "owner", you'll be able to use the following: |
| 362 | |
| 363 | user.is_eligible_for_what --> returns array of authorizable objects for which user has role "eligible" |
| 364 | user.is_moderator_of? group --> returns true/false |
| 365 | user.is_moderator_of group --> sets user to have role "moderator" for object group. |
| 366 | user.is_administrator --> sets user to have role "administrator" not really tied to any object. |
| 367 | |
| 368 | Models get has_* methods: |
| 369 | |
| 370 | group.has_moderators --> returns array of users with role "moderator" on that group |
| 371 | group.has_moderators? --> returns true/false |
| 372 | |
| 373 | Allowed prepositions are optional in the above dynamic methods. They are simply |
| 374 | syntactic sugar. For example, the following are equivalent: |
| 375 | |
| 376 | user.is_member_of group |
| 377 | user.is_member_for group |
| 378 | user.is_member group |
| 379 | |
| 380 | Allowed prepositions are required in the authorization expressions because they |
| 381 | are used to distinguish "role" and "role of :model" and "role of Model". |
| 382 | |
| 383 | If you prefer not to pollute your namespace with these dynamic methods, do not |
| 384 | include the Identity module in <tt>object_roles_table.rb</tt>. |
| 385 | |
| 386 | === Pattern of use |
| 387 | |
| 388 | We expect the application to provide the following methods: |
| 389 | |
| 390 | ==== #current_user |
| 391 | |
| 392 | Returns some user object, like an instance of my favorite class, |
| 393 | <tt>UserFromMars</tt>. A <tt>user</tt> object, from the Authorization |
| 394 | viewpoint, is simply an object that provides a <tt>has_role?</tt> method. |
| 395 | |
| 396 | Note that duck typing means we don't care what else the <tt>UserFromMars</tt> |
| 397 | might be doing. We only care that we can get an id from whatever it is, and we |
| 398 | can check if a given role string is associated with it. By using |
| 399 | <tt>acts_as_authorized_user</tt>, we inject what we need into the user object. |
| 400 | |
| 401 | If you use an authorization expression "admin of :foo", we check permission by |
| 402 | asking <tt>foo</tt> if it <tt>accepts_role?('admin', user)</tt>. So for each |
| 403 | model that is used in an expression, we assume that it provides the |
| 404 | <tt>accepts_role?(role, user)</tt> method. |
| 405 | |
| 406 | Note that <tt>user</tt> can be <tt>nil</tt> if <tt>:allow_guests => true</tt>. |
| 407 | |
| 408 | ==== #store_location (optional) |
| 409 | |
| 410 | This method will be called if authorization fails and the user is about to be |
| 411 | redirected to the login action. This allows the application to return to the |
| 412 | desired page after login. If the application doesn't provide this method, the |
| 413 | method will not be called. |
| 414 | |
| 415 | The name of the method for storing a location can be modified by changing the |
| 416 | constant STORE_LOCATION_METHOD in environment.rb. Also, the default login and |
| 417 | permission denied pages are defined by the constants LOGIN_REQUIRED_REDIRECTION |
| 418 | and PERMISSION_DENIED_REDIRECTION in authorization.rb and can be overriden in |
| 419 | your environment.rb. |
| 420 | |
| 421 | === Conventions |
| 422 | |
| 423 | Roles specified without the "of model" designation: |
| 424 | |
| 425 | 1. We see if there is a <tt>current_user</tt> method available that will return |
| 426 | a user object. This method can be overridden with the <tt>:user</tt> hash. |
| 427 | |
| 428 | 2. Once a user object is determined, we pass the role to |
| 429 | <tt>user.has_role?</tt> and expect a true return value if the user has the |
| 430 | given role. |
| 431 | |
| 432 | Roles specified with "of model" designation: |
| 433 | |
| 434 | 1. We attempt to query an object in the options hash that has a matching |
| 435 | key. Example: <tt>permit "knight for justice", :justice => |
| 436 | @abstract_idea</tt> |
| 437 | |
| 438 | 2. If there is no object with a matching key, we see if there's a matching |
| 439 | instance variable. Example: @meeting defined before we use <tt>permit |
| 440 | "moderator of meeting"</tt> |
| 441 | |
| 442 | 3. Once the model object is determined, we pass the role and user (determined |
| 443 | in the manner above) to <tt>model.accepts_role?</tt> |
| 444 | |
| 445 | === More information |
| 446 | |
| 447 | Information on this plugin and other development can be found at |
| 448 | the project home page: |
| 449 | |
| 450 | http://code.google.com/p/rails-authorization-plugin/ |