-1

My rails app has a few cab operators and they have a few cabs associated with them, and they are related as follows:

class Operator < ActiveRecord::Base
    has_many :cabs
end

I have used Devise as my authentication gem. It authenticates users, admins and super admins in my app. I have created separate models for users, admins and super admins (and have not assigned roles to users per se).

I now wish to add the authorization feature to the app, so that an admin (who essentially would be the cab operator in my case) can CRUD only its own cabs. For e.g., an admins belonging to operator# 2 can access only the link: http://localhost:3000/operators/2/cabs and not the link: http://localhost:3000/operators/3/cabs.

My admin model already has an operator_id that associates it to an operator when an admin signs_up. I tried to add the authorization feature through CanCan, but I am unable to configure CanCan to provide restriction such as the one exemplified above.

I also tried to extend my authentication feature in the cabs_controller, as follows:

class CabsController < ApplicationController
  before_action :authenticate_admin!

def index
    if current_admin.operator_id != params[:operator_id]
       redirect_to new_admin_session_path, notice: "Unauthorized access!"
    else
      @operator = Operator.find(params[:operator_id])
      @cabs = Operator.find(params[:operator_id]).cabs
    end
end 

But this redirects me to the root_path even if the operator_id of the current_admin is equal to the params[:operator_id]. How should I proceed?

EDIT: Following is my routes.rb file:

Rails.application.routes.draw do


  devise_for :super_admins
  devise_for :users
resources :operators do
    resources :cabs
  end

scope "operators/:operator_id" do 
    devise_for :admins
  end
end

I have three tables: users, admins and super_admins. I created these coz I wanted my admins to hold operator_ids so that the admins corresponding to an operator can be identified. Also, I wanted the admin sign_in paths to be of the type /operators/:operator_id/admins/sign_in, hence the tweak in the routes file.

Rahul Poddar
  • 343
  • 1
  • 4
  • 12
  • authenticate_admin! is part of authentication gem or your custom function? – Marko Krstic Nov 26 '14 at 08:46
  • Devise provides authenticate_admin! BTW I have defined my routes as: scope "operators/:operator_id" do devise_for :admins end – Rahul Poddar Nov 26 '14 at 08:56
  • Have you considered adding Rollify to your application? It plays well with CanCan and helps with the sort of role management you are doing. Also I recommend using CanCanCan over CanCan as the latter is no longer maintained. – trosborn Nov 26 '14 at 08:59
  • @ThomasO: Can you illustrate through an example how can I use Rollify for this case? – Rahul Poddar Nov 26 '14 at 09:22
  • I can if needed, but I think this tutorial is much more clear than anything I can writeup for you: https://github.com/RolifyCommunity/rolify/wiki/Using-rolify-with-Devise-and-Authority – trosborn Nov 26 '14 at 09:59

1 Answers1

0

Unfortunately, initially I didn't understand that you actually have 3 different tables for users and (super)admins... Not sure that Pundit can help you in this case, but I'll keep the old answer for future visitors. Coming back to your problem, let's try to fix just the unexpected redirect. Routes seems fine, so the problem can be one of this:

  • You're getting redirected because you're currently not logged in as an admin, so you don't pass the :authenticate_admin! before_action.
  • You say "even if the operator_id of the current_admin is equal to the params[:operator_id]", but this condition is probably not true. Can you debug or print somewhere the value of both current_admin.operator_id and params[:operator_id] to see if they're actually equals?

Another interesting thing, is that you have a redirect for new_admin_session_path in your code, but then you say "this redirects me to the root_path". Can you please double check this?

OLD ANSWER

If you want to setup a good authorization-logic layer, I advice you to use pundit. You've probably heard about cancan, but it's not supported anymore... Leave Devise managing only the authentication part and give it a try ;)

PUNDIT EXAMPLE

First of all, follow pundit installation steps to create the app/policies folder and the base ApplicationPolicy class.

Then, in your case, you'll need to create a CabPolicy class in that folder:

class CabPolicy < ApplicationPolicy
  def update?
    user.is_super_admin? or user.cabs.include?(record)
  end
end

This is an example for the update action. The update? function have to return true if the user has the authorisation to update the cab (You'll see later WHICH cab), false otherwise. So, what I'm saying here is "if the user is a super_admin (is_super_admin? is a placeholder function, use your own) is enough to return true, otherwise check if the record (which is the cab your checking) is included in the cabs association of your user". You could also use record.operator_id == record.id, but I'm not sure the association for cab is belongs_to :operator. Keep in mind that in CabPolicy, record is a Cab object, and user is the devise current_user, so implement the check that you prefer.

Next, in your controller, you just need to add a line in your update function:

def update
  @cab = Cab.find(params[:id]) # this will change based on your implementation
  authorize @cab # this will call CabPolicy#update? passing current_user and @cab as user and record
  @cab.update(cab_params)
end

If you want to make things even better, I recommend you to use a before_action

class CabsController < ApplicationController
  before_action :set_cab, only: [:show, :update, :delete]

  def update
    @cab.update(cab_params)
  end

  #def delete and show...

  private
  def set_cab
    @cab = Cab.find(params[:id])
    authorize @cab
  end

And of course, remember to define also show? and delete? methods in your CabPolicy.

iMacTia
  • 671
  • 4
  • 8
  • Can you provide some hint on how to use Pundit in this case? Thanks! – Rahul Poddar Nov 26 '14 at 08:58
  • Yep! Let me change the answer, it is formatted better than a comment – iMacTia Nov 26 '14 at 08:59
  • I followed your advise, but the issue still persists. My Operator 1 Admins are still able to access the link http://localhost:3000/operators/2/cabs. Also, when I try to access the edit action, it shows me this error: unable to find policy CabPolicy for #, highlighting authorize @cab. I am fairly new to rails, can you please be more explicit in your answer? Thanks! EDIT: Please note that I haven't defined separate roles for User. Instead, I have separate models for User, Admin and Super_admins. – Rahul Poddar Nov 26 '14 at 13:10
  • That means you have 3 different tables with nothing in common? Also, can you add your routes.rb file to your answer? I would like to check something – iMacTia Nov 26 '14 at 13:23
  • Please see my Edit in my original post above...thanks! – Rahul Poddar Nov 26 '14 at 13:44
  • I've updated my answer at the top, please take a look. Pundit isn't the best way to go in this case :( – iMacTia Nov 26 '14 at 14:17
  • Why do you need 3 different tables for admins, super admins and user? – trosborn Nov 26 '14 at 17:50