专家枚举角色

时间:2019-11-07 07:39:32

标签: ruby-on-rails pundit

上下文

在我的应用中,user.admin可以创建多个酒店。

  • 对于每家个人酒店,user.admin可以邀请一个(或多个)user.employee或另一个user.admin。

  • 因此,用户与酒店之间存在多对多的关系。

问题

  • 当受邀用户是user.admin时,一切都像超级按钮一样工作。受邀的user.admin可以访问hotels#show
  • 但是当受邀用户是user.employee时,他/她无法访问hotels#show
  • 角色分配似乎可以current_user.employee? => true
  • Hotels / show.html.erb是包含<p>show_page</p>的pnlye

错误消息

网络浏览器中的消息:

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

控制台

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)

代码

路线

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

模型

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

1 个答案:

答案 0 :(得分:1)

原来是一个愚蠢的错误,该错误在我的application_policy.rb中。初始化时应包含user.employee。

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