2

I am using CanCanCan for authorization purposes. Basically what I want to happen is non-logged in users can access Home#Index, but they need to be logged in for Home#Dashboard.

I have this route:

get 'dashboard', to: 'home#dashboard', as: :dashboard

This is my HomeController:

class HomeController < ApplicationController
  authorize_resource except: [:index]
  skip_authorization_check only: [:index]
  layout 'marketing', only: [:index]

  def index
  end

  def dashboard
  end
end

This is my application_controller.rb:

class ApplicationController < ActionController::Base
  protect_from_forgery with: :exception
  before_action :configure_permitted_parameters, if: :devise_controller?
  check_authorization :unless => :devise_controller?

  rescue_from CanCan::AccessDenied do |exception|
    respond_to do |format|
      format.json { head :forbidden }
      format.html { redirect_back(fallback_location: root_path, flash: { danger: exception.message }) }
    end
  end

  protected

  def configure_permitted_parameters
    devise_parameter_sanitizer.permit(:accept_invitation, keys: [:first_name, :last_name, :phone])
  end
end

However, when I go to /dashboard, this is the error I get:

NameError - uninitialized constant Home:
  activesupport (5.0.0.1) lib/active_support/dependencies.rb:550:in `load_missing_constant'
  activesupport (5.0.0.1) lib/active_support/dependencies.rb:203:in `const_missing'
  activesupport (5.0.0.1) lib/active_support/inflector/methods.rb:268:in `block in constantize'
  activesupport (5.0.0.1) lib/active_support/inflector/methods.rb:266:in `constantize'
  activesupport (5.0.0.1) lib/active_support/core_ext/string/inflections.rb:66:in `constantize'

What could be causing this?

Edit 1

After some debugging, I now realize that it seems to be related to the fact that the action I am authorizing isn't backed by an ActiveRecord resource.

In other words, once I add :dashboard to the exception list, like so:

  authorize_resource except: [:index, :dashboard]
  skip_authorization_check only: [:index, :dashboard]

It works. The page loads like a charm.

So I guess the real issue is, how do I authorize/lock down a resource that is not an ActiveRecord model?

marcamillion
  • 32,933
  • 55
  • 189
  • 380
  • 1
    do you have a typo in the name of the file that defines `HomeController` ?, Did you restart the server ?, I'm just guessing, but it might be something like that. I guess it has more to do with a typo. – fanta Nov 01 '16 at 17:55
  • I just restarted the server and still the same error. Also, there isn't a typo because when I go to `home#index` it works. That's my `unauthenticated root_path` and that works fine. It is just `home#dashboard` that's not. Does it matter that `Home` doesn't correlate to a model in my DB? Will CanCanCan have issues "authorizing a resource" that isn't a db resource? – marcamillion Nov 01 '16 at 18:02
  • I think you're right, it might have to do with not having a model for Home, take a look here https://github.com/CanCanCommunity/cancancan/wiki/Non-RESTful-Controllers, it tells you how to deal with those cases. – fanta Nov 01 '16 at 18:25
  • @fanta That's what I have been reading and I am very close. The issue I am having now is determining whether a user is logged in, in my `ability.rb`. In other words, I have this rule: `can :read, :dashboard if user`, but that obviously doesn't work because every visitor is either a logged in user or a guest, so this always results to `true`. Thoughts? – marcamillion Nov 01 '16 at 18:26
  • that would really depend on how you're handling roles(in case you are), if you're handing roles, then just check if it has a role, if not, then it's a guest user. In case you're not handling roles and just want to know if it is logged in, then(assuming you're setting the guest user just like the first example in https://github.com/CanCanCommunity/cancancan/wiki/defining-abilities), with the line `user ||= User.new # guest user (not logged in)`, change that line to multiline, `unless user`, and there, set `user` and a flag `is_guest` to true, and then use that variable. – fanta Nov 01 '16 at 18:35
  • @fanta I actually just added an answer showing that `roles` implementation I just tried. It works, but is less than ideal. I hear what you are saying re: doing `unless user`, but that just feels hacky. It would be awesome if Devise had some hidden method (or even CanCanCan) that allowed me to check if `user` is currently logged in or is just a `guest`. I can't seem to find info on that anywhere though. – marcamillion Nov 01 '16 at 18:39

1 Answers1

1

It seems the issue stemmed from my trying to authorize_resource for a resource that isn't an ActiveRecord model-backed resource.

So I had to remove authorize_resource from the beginning of the controller and then authorize! each individual action. Then I had to add that rule to the ability.rb.

Now my HomeController looks like this:

class HomeController < ApplicationController
  skip_authorization_check only: [:index]
  layout 'marketing', only: [:index]


  def index
  end

  def dashboard
    authorize! :read, :dashboard
  end
end

Then, in my ability.rb, I have this:

class Ability
  include CanCan::Ability

  def initialize(user)
    user ||= User.new # guest user (not logged in)

    can :manage, User, id: user.id
    can :read, :dashboard if user.has_any_role? :admin, :coach, :player
  end
end

Where I had to hardcode in all of the roles in my db.

What I REALLY wanted to do was just do a check to see if the user is currently logged in, but for w/e reason it kept failing on all user_signed_in? checks.

Ideally I would love to do something like:

can :read, :dashboard if current_user?

Or:

can :read, :dashboard if user.user_signed_in?

But both of those didn't work and gave me variations of the following error:

undefined local variable or method `current_user' for #<Ability:0x007faca14db2c8>

Or:

undefined method `user_signed_in?' for #<User:0x007fac86b2a9c8>

If you have any suggestions on how I can achieve this, please leave a comment or write a new answer and I will accept it. Until then, I guess this will have to do :(

The base of this approach I found in the CanCanCan wiki about managing Non-Restful Controllers.

marcamillion
  • 32,933
  • 55
  • 189
  • 380
  • After searching pages of documentation and about 10 other SO answers, this is the only thing that worked. You, sir, are a legend! – stevec May 01 '21 at 12:37