Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I specify an ability for a route created by a Rails engine?

I am using Trestle Admin, this is my route:

trestle_path        /admin         Trestle::Engine

When a user that is not an admin visits the /admin route, I would like CanCanCan to handle it like it handles all of the other unauthorized requests in my app.

The issue though is that I can't figure out how to specify that ability in my ability.rb or I can't figure out where to add an authorize statement to.

When I visit /admin in my app, this is what my log looks like:

Started GET "/admin" for ::1 at 2019-03-31 01:10:01 -0500
Processing by Trestle::DashboardController#index as HTML
Redirected to http://localhost:3000/admin/login
Filter chain halted as :require_authenticated_user rendered or redirected
Completed 302 Found in 13ms (ActiveRecord: 0.0ms)

So all that happens is that it redirects to /admin/login which is how the Trestle engine handles it.

But I would like for CanCanCan to hijack that and handle it like it handles all other unauthorized requests throughout my application via the rule in my application_controller.rb:

rescue_from CanCan::AccessDenied do |exception|
  respond_to do |format|
    format.json { head :forbidden, content_type: 'text/html' }
    format.html { redirect_to main_app.root_url, alert: exception.message }
    format.js   { head :forbidden, content_type: 'text/html' }
  end
end

But, given that it isn't a Model or a controller I defined, I am not sure what to specify in my ability.rb.

I have tried the following, all to no avail:

  if user.has_role? :admin
    can :manage, :all
  else
    cannot :read, :trestle
  end

or:

  if user.has_role? :admin
    can :manage, :all
  else
    cannot :read, :admin
  end

Is it possible for me to do what I am trying to do?

like image 747
marcamillion Avatar asked Mar 31 '19 06:03

marcamillion


1 Answers

You can use a hack with route constraints:

class CanCanConstraint
  def initialize(action, resource)
    @resource, @action = resource, action
  end

  def matches?(request)
    # this will differ depending on your auth solution, for devise/warden:
    current_user = request.env['warden'].user        
    current_user && Ability.new(current_user).can?(@action, @resource) || false
  end
end

mount Trestle::Engine, at: '/admin', constraints: CanCanConstraint.new(:edit, :trestle)
match "/admin/*glob", to: "some_controller_when#trestle_not_authorized"

This way /admin only leads to trestle if user is authorized and has permission, otherwise first route will not match and you can handle request any way you like, for example with catch-all second route.

Depending on how you mount trestle - you may need to disable it's automount (config.automount = false in corresponding initializer)

like image 138
Vasfed Avatar answered Oct 06 '22 08:10

Vasfed