授权用户在不使用 Pundit 的情况下为每个控制器执行各种 CRUD 操作; Ruby 在 Rails

Authorize Users to perform various CRUD actions for each controller without using Pundit; Ruby on Rails

我目前正在 Rails 上使用 Ruby 构建一个简单的网络应用程序,允许登录用户对 User 模型执行 CRUD 操作。我想添加一个函数,其中:

  1. 用户可以select每个控制器可以执行哪些操作; 例如:用户 A 可以在控制器 A 中执行操作 a 和 b,而用户 B 只能在控制器 A 中执行操作 B。这些可以通过视图进行编辑。
  2. 只有授权用户才能获得其他用户的编辑授权权限。例如,如果用户 A 获得授权,则它可以更改用户 B 可以执行的操作,但未授权的用户 B 将无法更改自己或任何人的可执行操作。

我已经为我的用户控制器设置了视图和模型

class UsersController < ApplicationController
  skip_before_action :already_logged_in?
  skip_before_action :not_authorized, only: [:index, :show]

  def index
    @users = User.all
  end
  
  def new
    @user = User.new
  end
  
  def create
    @user = User.new(user_params)
    if @user.save
      redirect_to users_path
    else
      render :new
    end
  end

  def show
    set_user
  end

  def edit
    set_user
  end
  
  def update
    if set_user.update(user_params)
      redirect_to user_path(set_user)
    else
      render :edit
    end
  end

  def destroy
    if current_user.id == set_user.id
      set_user.destroy
      session[:user_id] = nil
      redirect_to root_path
    else
      set_user.destroy
      redirect_to users_path
    end
  end
  
  private

  def user_params
    params.require(:user).permit(:email, :password)
  end

  def set_user
    @user = User.find(params[:id])
  end
end

My sessions controller:
class SessionsController < ApplicationController
  skip_before_action :login?, except: [:destroy]
  skip_before_action :already_logged_in?, only: [:destroy]
  skip_before_action :not_authorized
  
  def new
  end
  
  def create
    user = User.find_by(email: params[:email])
    if user && user.authenticate(params[:password])
      session[:user_id] = user.id
      redirect_to user_path(user.id), notice: 'You are now successfully logged in.'
    else
      flash.now[:alert] = 'Email or Password is Invalid'
      render :new
    end
  end

  def destroy
    session[:user_id] = nil
    redirect_to root_path, notice: 'You have successfully logged out'
  end
end

login/logout 功能有效,没有问题。

我首先在主应用程序控制器中实现了一个 not_authorized 方法,如果用户角色不等于 1,该方法默认会阻止用户访问相应的操作。

  def not_authorized
    return if current_user.nil?
    
    redirect_to users_path, notice: 'Not Authorized' unless current_user.role == 1
  end  

问题是我想让它可编辑。因此,如果有意义,角色 = 1 的用户可以编辑每个用户的访问权限。

我将如何进一步开发它?我也不想使用宝石,因为这样做的唯一目的是让我学习。 任何见解表示赞赏。谢谢!

授权系统的基础是个例外class:

# app/errors/authorization_error.rb
class AuthorizationError < StandardError; end

以及当您的应用程序引发错误时将捕获的救援:

class ApplicationController < ActionController::Base
  rescue_from 'AuthorizationError', with: :deny_access

  private
  def deny_access
    # see 
    redirect_to '/somewhere', status: :forbidden
  end
end

这避免了在整个控制器中重复逻辑,同时您仍然可以覆盖子classes 中的 deny_access 方法来自定义它。

然后您将在您的控制器中执行授权检查:

class ThingsController
  before_action :authorize!, only: [:update, :edit, :destroy]

  def create
     @thing = current_user.things.new(thing_params)
     if @thing.save
       redirect_to :thing
     else
       render :new
     end
  end

  # ...

  private
  def authorize!
    @thing.find(params[:id])
    raise AuthorizationError unless @thing.user == current_user || current_user.admin?
  end
end

在这种非常典型的情况下,任何人都可以创建 Thing,但用户只能编辑他们创建的 thing,除非他们是管理员.随着复杂程度的增加,将诸如此类的所有内容“内联”到您的控制器中很快就会变得笨拙混乱 - 这就是为什么像 Pundit 和 CanCanCan 这样的宝石将其提取到一个单独的层中。

创建一个权限为应用程序用户 editable 的系统在概念化和实施上都困难了好几个数量级,如果您不熟悉授权,这确实超出了您应该尝试的范围(或 Rails)。您需要创建一个单独的 table 来持有权限:

class User < ApplicationRecord
  has_many :privileges
end
class Privilege < ApplicationRecord
  belongs_to :thing
  belongs_to :user
end
class ThingsController
  before_action :authorize!, only: [:update, :edit, :destroy]
  # ...

  private
  def authorize!
    @thing.find(params[:id])
    raise AuthorizationError unless owner? || admin? || privileged?
  end

  def owner?
    @thing.user == current_user
  end

  def admin?
    current_user.admin?
  end

  def privileged?
    current_user.privileges.where(
      thing: @thing,
      name: params[:action]
    )
  end
end

这真是入门Role-based access control system (RBAC).