Users, Roles, Rights and Sights
Chad Fowler's Rails Recipes book lays out authorization as the interrogation of the many-to-many connections between `users` and `roles`, and between `roles` and `rights`, a `right` being a named controller-action pair. The many-to-many relationships are established using `roles_users` and `rights_roles` tables in the database. This indirection makes bulk assignment of `rights` easy, simply by assigning `roles` to a `user`, and authorizing using nested detection: {% highlight ruby %} class ApplicationController before_filter :check_authorization def check_authorization unless @operator.roles.detect{|role| role.rights.detect{|right| right.action == action_name && right.controller == self.class.controller_path } } flash[:notice] = "You are not authorized to view the requested page" request.env["HTTP_REFERER"] ? (redirect_to :back) : (redirect_to home_url) end end end {% endhighlight %} Here, `@operator` is the user that is operating the application. This works fine, but the interrogation of `rights` seems a little too removed from the `user`. I prefer asking if a `user` has a particular `right` directly: {% highlight ruby %} class User def has_right?(controller,action) rights = User.find_by_sql [ "select * FROM rights ri, rights_roles rr, roles_users ru where"+ " ru.user_id = ? and ri.controller = ? and ri.action = ?"+ " and rr.role_id = ru.role_id and ri.id = rr.right_id", id, controller, action ] rights.size > 0 end end {% endhighlight %} I also took the liberty of dropping the in-Ruby detection since a single sql query is faster than the multiple smaller queries that detection requires (look at the log files - one query for each role.) So this is a bit faster and is a drop-in replacement for the detection in the original code: {% highlight ruby %} class ApplicationController def check_authorization unless @operator.has_right?(self.class.controller_path,action_name) flash[:notice] = "You are not authorized to view the requested page" request.env["HTTP_REFERER"] ? (redirect_to :back) : (redirect_to home_url) end end end {% endhighlight %} Besides simplifying the authorization, since `has_right?` is a method of a `User`, any `user`'s rights can be simply interrogated, which is useful in setting up rights administration for an application. Formulating the `right`-checking in this way also leads to the notion of `sight`-checking, that is checking if a `user` has the `right` to see something. For instance, when building out a page if there is a question as to whether or not a `user` is allowed to see something, a `sight` can be established that allows it to be seen. The absence of a requested `sight` in the `rights` table for that `user`'s `roles` implies that the `user` should not see the `sight`. `Sights` don't necessarily depend on controller-actions; typically they're just checking to see that the `user` has a `right` with a particular name. The code to check `sights` is a simple as that of checking `rights`: {% highlight ruby %} class User def has_sight?(name) sights = User.find_by_sql [ "select * FROM rights ri, rights_roles rr, roles_users ru where"+ " ru.user_id = ? and ri.name = ?"+ " and rr.role_id = ru.role_id and ri.id = rr.right_id", id, name ] sights.size > 0 end end {% endhighlight %} Checking for `sights` looks for a `Right`'s name, while checking for `rights` looks for a `Right`'s controller and action. Moving the interrogation of `rights` to the `user` and adding the notion of `sights` allows pages to be constructed more simply based on what a particular `user` is allowed to do.