ACL9 a complex authorization solution for Web Application
How to use ACL9, to handle all the authorization logic combined with Authlogic authentication services
In most of Internet services developers don't need to build complex authorization solutions, all they need to do is to maintain user access based on simple login password verification. If user is logged on he has the access to all the web page features, if he is logged off, the only thing he can do is to log in. In Ruby On Rails this is fairly simple to achieve by using Authlogic. If you will follow Ryan Bates screencast about Authlogic you will learn how to do it.
In project I was working on, I needed far more sophisticated solution. What I needed was:
- ability to control access based on different user roles
- ability to control access on controller actions level
- ability to change user roles in runtime
- ability to control access on model level
I got used to fact that in Ruby On Rails some one already wrote an API, I can use. All I needed to do was installing ACL9 authorization plug-in:
script/plugin install git://github.com/be9/acl9.git
I will try to show here how helpful the ACL9 API is, by showing small fragments of source code from my project.
First thing you need to do is to let your system catch a special exception, which will be thrown when ever access problem will arise. You can do it by adding one line of code to your main application controller.
rescue_from 'Acl9::AccessDenied', :with => :access_denied
Next you need to define an access_denided action in your application controller. Mine is looking like this:
def access_denied if current_user flash[:error] = "Security problem!" respond_to do |format| format.html {redirect_to :controller => "szws", :action =>"denied"} format.js { render :update do |page| page.redirect_to :controller => "szws", :action =>"denied" end } end else flash[:notice] = "You have to be loged in to see this page" respond_to do |format| format.html {redirect_to login_path} format.js { render :update do |page| page.redirect_to login_path end } end end end
As you can see this action takes care of two things. First if user is logged in and this exception is thrown it will display to user Security problem flash message, and redirect the user to special action in one of the controllers. This action will render a page with more detailed error description. Second if user is not logged in (a current_user object is not available) we will display proper flash message and will redirect him to login page.
As you probably notices thanks to using respond_to code block, my system will be handling properly authorization problems thrown during normal html request and xmlHTTPRequest generated by Ajax call.
What is great in ACL9 is out of the box integration with authentication solution I use. The both services are using current_user object to maintain user login status. You may be interested in getting answer to a question: what to do if you need to check the user access based on login status? Look at the part of my user_session controller:
access_control :debug => true do allow logged_in, :to => [:destroy] allow anonymous, :to => [:new, :create] end
Here you can see a basic access control based only on login status. This small part of code as ACL9 API says will create a before filter which will do user access check before our controller actions are performed. What we can see here is that any logged in user can perform destroy action which is handling login off operation, and any anonymous is able to use new and create, so he access the log in page (the new action) and is able to log in (the create action). There is also one more predefined role called all. If you will use allow all it will always much the truth. You can also use deny instead of allow to ensure that some one is not able to do something. I would recommend everyone to keep :debug => true in development stage, it will easy up finding the problem if something weird will be happening with authorization part of your code.
Lets take a look at some role based authorization. We will start at looking into source code of my user model.
class User < ActiveRecord::Base #This is authentication subject acts_as_authentic do |c| c.validate_email_field = false c.validate_login_field = true #make sure that one workshop wont be able to have two users of the same name c.validations_scope = :workshop_id c.logged_in_timeout=30.minutes; c.maintain_sessions=false c.ignore_blank_passwords=false end #This is authorization subject acts_as_authorization_subject #This is also an authorization object acts_as_authorization_object belongs_to :workshop after_create :make_owner, :assign_security_roles def self.find_user_in_workshop(login) workshop_unique=login[:workshop_unique] username = login[:username] workshop = Workshop.find_by_workshop_unique(workshop_unique) unless workshop.nil? find_by_username_and_workshop_id(username,workshop.id) else return nil end end #Assign new user to standard security grups def assign_security_roles unless self.undeletable self.has_role! :warehouseman self.has_role! :dispatcher end end #Make user owner of his data def make_owner self.has_role!(:owner,self) end end
We will skip all the Authlogic related code and will focus on this lines that are using ACL9 API. First thing you can see is acts_as_authorization_subject, this allows us to assign roles to any user object. Second thing is something I love in ACL9, acts_as_authorization_object, this allows us to assign access rights on model level. Thank to it we can assign access rights to any particular instance of an object. If you look further in my user model source code you will find there examples of usage. Method assign_security_roles shows how to assign an authorization subject to role. You can do it with simple call object.has_role! :role_name. We are making here our user member of dispatcher and warehouseman roles. Method make_owner shows how to assign authorization on model level. It's also a simple call: object.has_role!(:role_name,authorization_object). This way I'm ensuring that user will be owner of his data. This will allow my user to edit his own profile no matter of his security roles membership. As you can see both methods are called in call back after_create this way every created user will be owner of his own data and will be assigned to default security roles.
Now we can take a look how to use roles controlled authorization in controllers. Look at ACL9 code from my users controller.
access_control :debug => true do allow :admin, :manager allow :owner, :of => :user, :to => [:edituser,:update,:destroy,:changepasswd,:dochangepasswd] end
First we allow to perform all the actions in our users controller to admin and manager roles. Then we allow owner of user instance object to perform only listed actions. This part of code let's every user take full control over his own profile. If you need to you can also use except statement instead of to while defining the action list.
If we are preventing our users from doing some tasks, it's better not to display them those tasks. In ACL9 you can do it quite simply. Just look at the menu example below:
As you can see all the role checks are performed by simple object.has_role? role_name call. Authlogic is taking care of creating and mantling current_user instance object, so thanks to both ACL9 and Authlogic I can display a menu depending on user privileges in my system.
Thats how I'm using ACL9 in my application. It's possible that you could find more interesting things in API, so make sure you will read it, before using this great Rails plug-in.
Sources:
GNU Free Documentation License or Creative Commons Share Alike
If you have found something wrong with the information provided above or maybe you just want to speak your mind about it, feel free to leave a comment.
All comments will show up on page after being approved. Sorry for such policy but I want to make sure that my site will be free of abusive or vulgar content. I don't mind being criticized just do it using right words.
Processing a comment.