Adding a Controller without corresponding model while using cancancan - ruby-on-rails

I've added a controller collaborators to manage a particular type of join association between Users and Companies. The issue is that whenever I load anything from collaborators, I get the error
uninitialized constant Collaborator
From my understanding, this is because there is no model Collaborator and I am using cancancanfor authorization. From the old cancan (note not cancancan) documentation, I've been able to gather that controllers that don't have a corresponding model need to have a model manually authorized for them something like: load_and_authorize_resource :the_model, :parent => false.
This seems to work if I disable load_and_authorize_resource in my application.rb controller.
SO my quesestion is: what is the best way to authorize controllers that don't have corresponding models with cancancan? Can I continue to load_and_authorize_resource in my application controller?
Many thanks in advance.

This LINK will help.
From the link, I quote,
class ToolsController < ApplicationController
authorize_resource :class => false
def show
# automatically calls authorize!(:show, :tool)
end
end
And in your ability.rb:
class Ability
include CanCan::Ability
def initialize(user)
can :show, :tool
end
end

Related

Adding a new scope to Devise

My goal is to create a scope which can only be accessed by users with a certain role, in this case "editor". Or rather by admins and editors.
I'm using devise. Here's a part of my attempt:
scope "/editor" do
devise_for :editors
# ...other routes
end
When running it via "rails server":
/home/user123/.gem/ruby/2.4.1/gems/activesupport-5.1.3/lib/active_support/inflector/methods.rb:269:in `const_get': uninitialized constant Editor (NameError)
How should I fix it?
I think you're misunderstanding use of the scope argument. If you want to restrict access in a certain view you can do the following. Assume you have a video streaming service where a regular user can only access the index and show actions/views, yet an admin can see the rest (new edit etc...).
You make a concern (google DRY) at app/controllers/concerns/ and name it admin.rb
module IsAdmin
extend ActiveSupport::Concern
def admin?
return redirect_to videos_new, alert: "Access Denied" unless current_user.admin?
end
end
Now in your videos_controller.rb include the concern we just made and tell the controller in which views you want to use the admin? method.
Class VideosController < Applicationcontroller
include IsAdmin
before_action :admin?, only: [:new, :edit]
end

How to add this specific authorization feature to my rails app?

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.
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.

How to configure Devise+Cancan correctly for guest users

In a Rails 3.2 app I'm using Devise + CanCan. The app previously restricted access to only logged in users. I'm in the process of adding a Guest user/ability that will be able to read certain sections of the site.
I'm having trouble understanding the "correct" way to set this up, specifically what combination of before_filter :authenticate! and load_and_authorize_resource is needed in controllers.
While working on this I've stripped the ability class to a minimum.
#Ability.rb
class Ability
include CanCan::Ability
def initialize(user_or_admin)
user_or_admin ||= User.new
can :manage, :all
end
end
In a model-less/ static page Home controller
#home_controller.rb
class HomeController < ApplicationController
load_and_authorize_resource
def index
...some stuff
end
end
and
#application_controller.rb
class ApplicationController < ActionController::Base
protect_from_forgery
before_filter :authenticate!
...more stuff
end
With this set up, un-logged-in users are redirected to Devise sign in page.
If I remove before_filter :authenticate! from the application controller I get an error uninitialized constant Home from activesupport-3.2.11/lib/active_support/inflector/methods.rb
If I remove load_and_authorize_resource from the home controller, this error goes away.
This is ok with my simplified testing Ability class, but as I start adding roles and abilities back in I will need to have CanCan handling the Home controller, i.e., will need load_and_authorize_resource to be called.
Can anyone help me understand why this error occurs when before_filter :authenticate! is removed, and point me towards any info that explain the "correct" way to set up Devise+Cancan for guest users. The info I've found thus far only explains how to set up the Ability class, not how to configure Devise.
The problem is that there is no resource to authorize. Therefore, you need only call authorize_resource not load_and_authorize_resource. See authorizing controller actions in the cancan documentation for further information.
Update: You must also specify the class as false: authorize_resource class: false.
Then your home controller will look like this:
class HomeController < ActionController::Base
authorize_resource class: false
def show
# automatically calls authorize!(:show, :home)
end
end
This information is in the Non-RESTful controllers section. Sorry about that.

CanCan - How to setup a modeless Controller?

I have a User Model, with a admin boolean field. I then have an Admin Controller which I want CanCan On.
How do I add CanCan to the Admin Controller as there is not Admin Model?
I've tried:
class AdminController < ApplicationController
load_and_authorize_resource :user
load_and_authorize_resource :admin, :through => :user
class Ability
include CanCan::Ability
def initialize
....
can :manage, Admin if current_user.admin == TRUE
....
But that errors with "Access denied on nil"
Ideas? Thanks
Are you sure you have access to the 'current_user' method in your ability class? I wouldn't think you would have.
For what I remember when using CanCan you could have this in your Ability class:
def initialize(user)
user ||= User.new #guest user.
...
end
And the user would be the current user, I expect. And I guess that should work.
You can also take a look at this page: https://github.com/ryanb/cancan/wiki/Non-RESTful-Controllers of the CanCan wiki. It's not exactly the same issue, but as it covers a special case (Non-RESTful-Controllers) it might give you some hints about how to do what you want to do.
Cheers

Admin Authorization with CanCan

A have a bunch of controllers with the Admin namespace. I want to restrict access to these unless the user is an admin. Is there a way to do this using CanCan without having to call unauthorized! in every method of every controller?
Add an application controller to your namespace and a before filter to it.
class ApplicationController < ActionController::Base
end
class Admin::ApplicationController < ApplicationController
# these goes in your namespace admin folder
before_filter :check_authorized
def check_authorized
redirect_to root_path unless can? :admin, :all
end
end
class SomeadminController < Admin::ApplicationController
def some_action
# do_stuff
end
end
The Admin Namespaces wiki page for CanCan lists out several solutions to this problem.
As #mark suggested, have a base controller for admins which checks authorization for every action.
You may not need to use CanCan at all for this if all you require is to check that users have an admin
flag.
For handling admins differently from each other (as opposed to differently from regular users only),
consider a separate AdminAbility class (this is a little off-topic, but could prove relevant).
now rails_admin has full support with Cancan, you can find it in its official website, there is a wiki page for this topic:
Rails Admin's authorization with CanCan:

Resources