05 March 2010

ACL9 a complex authorization solution for Web Application

Categories:  RoR  Ruby  Web

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:

View the source of access_denided method
  1. def access_denied
  2. if current_user
  3. flash[:error] = "Security problem!"
  4. respond_to do |format|
  5. format.html {redirect_to :controller => "szws", :action =>"denied"}
  6. format.js {
  7. render :update do |page|
  8. page.redirect_to :controller => "szws", :action =>"denied"
  9. end
  10. }
  11. end
  12. else
  13. flash[:notice] = "You have to be loged in to see this page"
  14. respond_to do |format|
  15. format.html {redirect_to login_path}
  16. format.js {
  17. render :update do |page|
  18. page.redirect_to login_path
  19. end
  20. }
  21. end
  22. end
  23. 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:

  1. access_control :debug => true do
  2. allow logged_in, :to => [:destroy]
  3. allow anonymous, :to => [:new, :create]
  4. 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.

View the source of user model
  1. class User < ActiveRecord::Base
  2. #This is authentication subject
  3. acts_as_authentic do |c|
  4. c.validate_email_field = false
  5. c.validate_login_field = true
  6. #make sure that one workshop wont be able to have two users of the same name
  7. c.validations_scope = :workshop_id
  8. c.logged_in_timeout=30.minutes;
  9. c.maintain_sessions=false
  10. c.ignore_blank_passwords=false
  11. end
  12. #This is authorization subject
  13. acts_as_authorization_subject
  14. #This is also an authorization object
  15. acts_as_authorization_object
  16.  
  17. belongs_to :workshop
  18.  
  19. after_create :make_owner, :assign_security_roles
  20.  
  21. def self.find_user_in_workshop(login)
  22. workshop_unique=login[:workshop_unique]
  23. username = login[:username]
  24. workshop = Workshop.find_by_workshop_unique(workshop_unique)
  25. unless workshop.nil?
  26. find_by_username_and_workshop_id(username,workshop.id)
  27. else
  28. return nil
  29. end
  30. end
  31.  
  32. #Assign new user to standard security grups
  33. def assign_security_roles
  34. unless self.undeletable
  35. self.has_role! :warehouseman
  36. self.has_role! :dispatcher
  37. end
  38. end
  39.  
  40. #Make user owner of his data
  41. def make_owner
  42. self.has_role!(:owner,self)
  43. end
  44.  
  45. 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.

  1. access_control :debug => true do
  2. allow :admin, :manager
  3. allow :owner, :of => :user, :to => [:edituser,:update,:destroy,:changepasswd,:dochangepasswd]
  4. 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:

View the menu example source code

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:




Comments

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.

Leave a comment