authorizer
DESCRIPTION:
Authorizer is a gem for Ruby (in conjunction with Rails 2.3) that does authorization for you on a per-object basis. What makes this gem different from e.g. declarative_authorization and cancan is they define one role for the entire application. With Authorizer, you define roles for different users on every Rails object.
Let’s use a Dropbox analogy.
With Dropbox, you can choose which folder you want to share. For instance:
Al has a home folder with these subfolders in it:
- Music (shared with Bob)
- Pictures (shared with Casper and Bob)
- News (shared with no-one)
This causes Al to have all 3 folders in his Dropbox. Bob has 2 and Casper has only 1 folder called Pictures.
In other words, a user has access to a subset of the entire collection of folders. Bob has access to 2 of Al’s folders, namely Music and Pictures. But he doesn’t even see the News folder, nor can he download files from it.
Bob’s access to the two folders is both read and write, so let’s call that role “admin”. Al is the owner of all 3 folders and has a role called “owner”. This leads to the following Roles table:
folder_name user_name role
Music Al owner
Bob admin
Pictures Al owner
Bob admin
Casper admin
News Al owner
Now if we would allow Bob to also access the News folder but only read from it, we could add the role called “reader” to the table:
folder_name user_name role
News Bob reader
This is exactly what Authorizer does for your Rails application.
PROBLEMS:
In development mode, classes are lazy loaded. This will cause Authorizer to not find all subclasses of your class the second time you load any page with your development mode server. In development mode, subclasses are loaded ONLY when they are included, so a given class could not have any children at a given time even though you might have defined some! AFAIK there are two possible solutions: 1) reload the subclasses at runtime (workaround) or 2) persist the object hierarchy (comes with synchronisation problems).
For development mode, use this workaround:
class LegalEntity
def self.my_subclasses
[ "Company", "Foundation", "Person" ]
end
if Rails.env.development?
LegalEntity.my_subclasses.each do |sc|
require_dependency File.join("app", "models", "#{sc.downcase}.rb")
end
end
end
In other words, when using Authorizer with Single Table Inheritance (STI), you must include the above code snippet in any class that has subclasses and is subject to authorization.
More material on this subject: code.alexreisner.com/articles/single-table-inheritance-in-rails.html sean-carley.blogspot.com/2006/05/when-rails-needs-clue-single-table.html
The ‘lazy loading of subclasses’ only pertains to development mode, not to production and test.
SYNOPSIS:
Authorize a user on an object
Authorizer::Base.authorize_user( :object => object )
=> true/false
If you want to know if the current user is authorized on an object, use:
Authorizer::Base.user_is_authorized?( :object => object )
=> true/false
Authorizer::Base.authorize!( :object => object, :message => "You are not authorized to access this resource." ) # message is optional
=> returns true or raises Authorizer::UserNotAuthorized
Remove authorization from an object
Authorizer::Base.remove_authorization( :object => object )
=> true/false
Find all objects that the current user is authorized to use
Authorizer::Base.find("Post", :all, { :conditions => { :name => "my_post" } }) # [ #<Post id: 1>, #<Post id: 2> ]
Authorizer::Base.find("Post", :first) #<Post id: 1>
Authorizer::Base.find("Post", [ 1, 6 ]) # [ #<Post id: 1>, #<Post id: 6> ]
If you are using inherited_resources, you can also use these filters in your controller class:
# own created objects so you can access them after creation
after_filter :own_created_object, :only => :create
# authorize entire controller
before_filter :authorize, :only => [ :show, :edit, :update, :destroy ]
This obviously works out of the box with resource-oriented controllers, but with anything different you’ll have to make your own choices.
If you’re just getting started with Authorizer but you already have a running app, you can have one user own all objects with this method:
Authorizer::Admin.create_brand_new_object_roles(:user => User.first)
This method will guess what objects to use by checking for descendants of ActiveRecord::Base.
If you just want to do this for the Post and Category classes, use:
Authorizer::Admin.create_brand_new_object_roles(:user => User.first, :objects => [ "Post", "Category" ])
Authorizer uses ActiveRecord observers to make sure it doesn’t make any mess, for instance, when a user is deleted, all of his authorization objects are deleted as well. Should you want more control over this garbage collection process, or if you are a cleanfreak, use this to get rid of any stale authorization objects lying around in your database: (protip: embed into rake task!)
Authorizer::Admin.
REQUIREMENTS:
- Ruby (this gem was tested with 1.8.7)
- Rails 2.3 (tested with 2.3.11 and 2.3.12 and 2.3.14)
- An authentication mechanism such as Authlogic for authentication (tested with authlogic 2.1.6)
Optional:
- inherited_resources if you want to use the controller filters supplied with this gem. Otherwise, you'll have to check for authorization yourself.
INSTALL:
Step 1. sudo gem install authorizer
Step 2. add “authorizer” to your Gemfile (I hope you’ve stopped using config.gem already even if you are on Rails 2.3?)
Step 3. generate a migration for authorization objects:
script/generate migration CreateObjectRoles
Paste this code into the newly generated file:
def self.up
create_table :object_roles do |t|
t.string :klazz_name
t.integer :object_reference
t.references :user
t.string :role
t.
end
end
def self.down
drop_table :object_roles
end
Step 4. run “rake db:migrate” to migrate your database
Step 5. Add these lines to your app/controllers/application_controller.rb:
require 'authorizer/exceptions'
rescue_from Authorizer::UserNotAuthorized do |exception|
# Show a static 403 page upon authorization failure
render :file => "#{Rails.root}/public/403.html", :status => 403
end
Step 6. Download the file public/403.html from authorizer_project and copy it to your own app.
That’s it!
DEVELOPERS:
Reviews, patches and bug tickets are welcome! There’s still some features that need to be implemented:
- (Multiple) roles for users on an object
- Rails 3 support!
authorizer_project is a sample project that shows the usage of the Authorizer gem. It also contains all unit and functional tests for your testing pleasure.
LICENSE:
(The MIT License)
Copyright © 2011 Commander Johnson <[email protected]>
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the ‘Software’), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED ‘AS IS’, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.