0

Context

In my app a user.admin can create multiple hotels.

  • For each of those individual hotels a user.admin can invite one (or more) user.employee or another user.admin.

  • Therefore, there is a many to many relationship between users and hotels.

Isssue

  • When the invited user is a user.admin, everything works like a charm. The invited user.admin can access hotels#show.
  • However when the invited user is a user.employee he/she cannot access hotels#show
  • The assignment of roles seem to work current_user.employee? => true
  • Hotels/show.html.erb is pnlye containing <p>show_page</p>

Error message

message in webbrowser:

localhost redirected you too many times.
Try clearing your cookies.
ERR_TOO_MANY_REDIRECTS

console

Started GET "/" for ::1 at 2019-11-07 09:27:16 +0100
Processing by PagesController#home as HTML
  User Load (0.2ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = $1 ORDER BY "users"."id" ASC LIMIT $2  [["id", 20], ["LIMIT", 1]]
  ↳ app/controllers/pages_controller.rb:6
  Hotel Exists (0.5ms)  SELECT  1 AS one FROM "hotels" INNER JOIN "user_hotels" ON "hotels"."id" = "user_hotels"."hotel_id" WHERE "user_hotels"."user_id" = $1 LIMIT $2  [["user_id", 20], ["LIMIT", 1]]
  ↳ app/controllers/pages_controller.rb:7
  Hotel Load (0.2ms)  SELECT  "hotels".* FROM "hotels" WHERE "hotels"."id" IS NULL LIMIT $1  [["LIMIT", 1]]
  ↳ app/controllers/pages_controller.rb:8
  Hotel Load (0.2ms)  SELECT  "hotels".* FROM "hotels" INNER JOIN "user_hotels" ON "hotels"."id" = "user_hotels"."hotel_id" WHERE "user_hotels"."user_id" = $1 ORDER BY "hotels"."id" DESC LIMIT $2  [["user_id", 20], ["LIMIT", 1]]
  ↳ app/controllers/pages_controller.rb:12
Redirected to http://localhost:3000/hotels/9
Completed 302 Found in 6ms (ActiveRecord: 1.2ms)

Code

routes

Rails.application.routes.draw do

  devise_for :users

  resources :hotels do
devise_for :users, :controllers => { :invitations => 'users/invitations' }
  end
end

application_controller

class ApplicationController < ActionController::Base
  before_action :set_locale
  before_action :configure_permitted_parameters, if: :devise_controller?

  def set_locale
    I18n.locale = params.fetch(:locale, I18n.default_locale).to_sym
  end

  def default_url_options
    { locale: I18n.locale == I18n.default_locale ? nil : I18n.locale }
  end

  protect_from_forgery with: :exception
  before_action :authenticate_user!
  include Pundit

  # Pundit: white-list approach.
  after_action :verify_authorized, except: :index, unless: :skip_pundit?
  after_action :verify_policy_scoped, only: :index, unless: :skip_pundit?

  # Uncomment when you *really understand* Pundit!
  rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized

  private
  def user_not_authorized
    flash[:alert] = "You are not authorized to perform this action."
    redirect_to(root_path)
  end

  def skip_pundit?
    devise_controller? || params[:controller] =~ /(^(rails_)?admin)|(^pages$)/
  end

  def configure_permitted_parameters
    devise_parameter_sanitizer.permit(:invite, keys: [:role, :user_parks_attributes])
  end
end

pages#home

class PagesController < ApplicationController
  skip_before_action :authenticate_user!, :raise => false
  skip_after_action :verify_authorized

  def home
    if !current_user.nil?
      if current_user.hotels.any?
        if Hotel.find_by_id params[:id]
          @hotel = Hotel.find(params[:id])
          redirect_to hotel_path(@hotel)
        else 
          @hotel = current_user.hotels.last
          redirect_to hotel_path(@hotel)
          # redirect_to :controller => 'hotels' , :action => 'show',  :id => @hotel.id
        end
      else
        redirect_to hotels_path
      end
    else
      redirect_to pages_landing_page_path
    end
  end
end

hotel_policy

class HotelPolicy < ApplicationPolicy
  class Scope < Scope
    def resolve
      if user.admin? || user.employee?
        scope.joins(hotel: :user_hotels).where(user_hotels: { user_id: user.id })
      else
        raise Pundit::NotAuthorizedError
      end
    end
  end

  def show?
    user.admin? || user.employee?
  end
end

models

class User < ApplicationRecord
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
  has_many :user_hotels, dependent: :destroy
  has_many :hotels, through: :user_hotels
  accepts_nested_attributes_for :user_hotels
  enum role: [:owner, :admin, :employee]
  after_initialize :set_default_role, :if => :new_record?

  def set_default_role
    self.role ||= :admin
  end

  devise :invitable, :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable, :invitable
end

class UserHotel < ApplicationRecord
  belongs_to :hotel
  belongs_to :user
end

class Hotel < ApplicationRecord
  has_many :user_hotels, dependent: :destroy
  has_many :users, through: :user_hotels
  accepts_nested_attributes_for :users, allow_destroy: true, reject_if: ->(attrs) { attrs['email'].blank? || attrs['role'].blank?}
end

hotels_controller

class HotelsController < ApplicationController
def show
    if Hotel.find_by_id params[:id]
      @hotel = Hotel.find(params[:id])
    else
      @hotel = current_user.hotels.last
    end
    authorize @hotel
    @reservations = @hotel.reservations
  end
end
techquestion
  • 489
  • 5
  • 20
  • 2
    Have you confirmed your enum is working as intended, and the user responds true to `employee?`? If so, what happens when you try visit the show page? Pundit error? – Mark Nov 07 '19 at 08:02
  • Not technically an answer but you could check you Rolify and CanCanCan gems. They provide a nice way to allow certain roles to perform certain crud actions – Int'l Man Of Coding Mystery Nov 07 '19 at 08:22
  • @mark, thanks for your swift response. ```>> current_user.employee? => true```, so that seems to work. I do not get a pundit error, but ```localhost redirected you too many times. Try clearing your cookies. ERR_TOO_MANY_REDIRECTS ```. I will add this + my console log when being directed to show to my question. – techquestion Nov 07 '19 at 08:24
  • 1
    @mike, thanks I will have a look at it! – techquestion Nov 07 '19 at 08:25
  • 1
    Can you post your application_controller please? There's probably a redirect hiding in there somewhere – Mark Nov 07 '19 at 08:42
  • @mark, just added the file. – techquestion Nov 07 '19 at 08:46
  • @mark, thanks so much for hinting at the application_controller! I turned of my Pundit rescue and consequently saw my application_policy was causing the error. Will post the solution below. – techquestion Nov 07 '19 at 08:57
  • 1
    Glad you got there :) – Mark Nov 07 '19 at 09:06

1 Answers1

1

Turns out to be a stupid mistake, the bug was in my application_policy.rb Should have included user.employee in the initialisation.

def initialize(user, record)
    raise Pundit::NotAuthorizedError, "must be logged in with a user account" unless (user.admin? || user.employee?)
    @user = user
    @record = record
  end
techquestion
  • 489
  • 5
  • 20