【问题标题】:Authorize Users to perform various CRUD actions for each controller without using Pundit; Ruby on Rails授权用户在不使用 Pundit 的情况下为每个控制器执行各种 CRUD 操作; Ruby on Rails
【发布时间】:2020-12-23 13:26:51
【问题描述】:

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

  1. 用户可以选择每个控制器可以执行的操作; 例如:用户 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

登录/注销功能有效,没有问题。

我首先在主应用程序控制器中实现 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 的用户可以编辑每个用户的访问权限,如果这有意义的话。

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

【问题讨论】:

  • 你不必使用 gem,但你可以看到 gem 是如何解决他们的问题的,github.com/CanCanCommunity/cancancan
  • 无论如何我都会从权威人士开始。 Pundit 只是简单的 OOP,如果您从头开始重新发明轮子,您可能会学到更多。从学习的角度来看,CanCanCan 实际上更糟糕,因为它的 DSL 往往会妨碍您。

标签: ruby-on-rails ruby session authorization


【解决方案1】:

授权系统的基础是异常类:

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

当您的应用程序引发错误时,它会捕捉到救援:

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

  private
  def deny_access
    # see https://stackoverflow.com/questions/3297048/403-forbidden-vs-401-unauthorized-http-responses
    redirect_to '/somewhere', status: :forbidden
  end
end

这避免了在整个控制器中重复逻辑,同时您仍然可以覆盖子类中的 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

在这个非常典型的场景中,任何人都可以创建事物,但用户只能编辑他们创建的事物,除非他们是管理员。随着复杂程度的增加,将这样的所有内容“内联”到您的控制器中很快就会变得笨拙——这就是 Pundit 和 CanCanCan 等 gem 将其提取到单独的层中的原因。

创建一个权限可由应用程序的用户编辑的系统在概念化和实现方面都困难了几个数量级,如果您是授权(或 Rails)新手,这确实超出了您应该尝试的范围。您需要创建一个单独的表来保存权限:

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)

【讨论】:

    猜你喜欢
    • 2013-10-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-11-06
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多