CanCan Route Constraints
2nd November 2013
I use Sidekiq to handle the background jobs which is working great, but I noticed that some of them seemed to be failing! I had no way of seeing which jobs where failing or why because I handn’t mounted the WebUI anywhere. The issue I had was securing it, I can’t just rely on the fact that a random user wont know that it is at
/sidekiq, especially since it lets you perform some job control.
The only solution is to secure access to it, which I can only do within the routes. I also wanted to use the Authorization system I already had in place instead of adding something extra in (like HTTP basic etc…).
I use CanCan which is a great solution, I just need to get it working in the router.
The class I wrote to do this is actually pretty simple:
class CanCanConstraint def initialize(action, resource) @action = action @resource = resource end def matches?(request) if request.session['user_id'].present? current_user = User.find(request.session['user_id']) ability = Ability.new(current_user) return ability.can?(@action, @resource) else return false end end end
For my uses I mounted sidekiq like this:
mount Sidekiq::Web, at: '/sidekiq', constraints: CanCanConstraint.new(:manage, :sidekiq)
So how does it work?
The router lets you pass a class to the route constraints option, the only requirement being that this class/instance of a class has to respond to
matches?. So my
CanCanConstraint class is re-usable as it lets you specify which permission in CanCan you want to check the user has, in this case I check if the user can manage sidekiq (this is the same as using
can? :manage, :sidekiq in a view).
matches? gets called it first checks if the session variable of
user_id exists (assuming that guests would not be given access) and if it doesn’t return false. If it does exist it finds that user in the same way that
ApplicationController#current_user does and then passes that user to
Ability.new which is exactly what CanCan does (which is why you write your code in initialize). This then gives you the
can? method same as in views. can? returns true or false based on the users assigned permissions, if you meet the requirements and it returns true the router will allow that route to exist for you.
This means that for anyone not given manage permissions on sidekiq the route simply wont exist, you will get a 404 instead of a 403.