Computer >> คอมพิวเตอร์ >  >> การเขียนโปรแกรม >> Ruby

คู่มือฉบับสมบูรณ์ในการจัดการสิทธิ์ผู้ใช้ในแอพ Rails

ข้อกำหนดทั่วไปของเว็บแอปพลิเคชันคือความสามารถในการกำหนดบทบาทและการอนุญาตเฉพาะ

เว็บแอปพลิเคชันหลายประเภทแยกความแตกต่างระหว่างผู้ดูแลระบบและผู้ใช้ทั่วไปในการให้การเข้าถึงแบบจำกัด การดำเนินการนี้มักใช้บูลีนแบบง่ายที่กำหนดว่าผู้ใช้เป็นผู้ดูแลระบบหรือไม่ อย่างไรก็ตาม บทบาทและการอนุญาตอาจซับซ้อนกว่านี้มาก

คุณค่าของแอปพลิเคชันของคุณอยู่ที่การจำกัดการเข้าถึงข้อมูลและการดำเนินการบางอย่าง เป็นสิ่งที่คุณไม่อยากยุ่งอย่างแน่นอน ในโพสต์นี้ เราจะอธิบายวิธีการใช้บทบาทและการอนุญาตในแอปพลิเคชัน Ruby on Rails พื้นฐาน

ฉันต้องใช้อัญมณีเพื่อจัดการการอนุญาตหรือไม่

ไม่ คุณไม่จำเป็นต้องมี gem โดยเฉพาะอย่างยิ่งหากแอปพลิเคชันของคุณมีขนาดเล็ก และคุณต้องการหลีกเลี่ยงการเพิ่มการพึ่งพาให้กับฐานโค้ดของคุณมากขึ้น อย่างไรก็ตาม หากคุณกำลังมองหาทางเลือกอื่น นี่คืออัญมณียอดนิยมที่เกี่ยวข้องกับบทบาทและการอนุญาต :

  • DeviseDevise เป็นอัญมณีสำหรับการรับรองความถูกต้องและการจัดการบทบาท และเป็นโซลูชันที่ซับซ้อนและมีประสิทธิภาพจริงๆ ด้วยดาว 21.7k บน GitHub เป็น repo ที่ได้รับความนิยมมากที่สุดในโพสต์นี้ แต่ทำมากกว่าการจัดการบทบาท เรียกว่าโซลูชันการตรวจสอบสิทธิ์ ดังนั้นให้ใช้กับ codebase ของคุณเท่านั้นหากคุณต้องการไลบรารีที่มีประสิทธิภาพมาก

  • Pundit:Pundit เป็นอัญมณีที่ใช้วัตถุ Ruby ธรรมดาและอาจเป็นอัญมณีนโยบายที่ง่ายที่สุดที่เราจะกล่าวถึง ใช้งานง่าย มีการอนุญาตน้อยที่สุด และคล้ายกับการใช้ Ruby ล้วนๆ ด้วยจำนวนดาว 7.3k บน GitHub ปัจจุบันเป็นอัญมณีด้านนโยบายที่ได้รับความนิยมมากที่สุด

  • CanCan:CanCan เป็นไลบรารีการอนุญาตที่จำกัดทรัพยากรที่ผู้ใช้กำหนดสามารถเข้าถึงได้ อย่างไรก็ตาม CanCan ถูกละทิ้งมาหลายปีแล้วและใช้งานได้กับ Rails 3 และรุ่นก่อนหน้าเท่านั้น

  • CanCanCan:CanCanCan เป็นไลบรารีการอนุญาตอื่นสำหรับ Ruby และ Ruby on Rails เป็นทางเลือกแทน CanCan และกำลังอยู่ในระหว่างการบำรุงรักษา ด้วย 4.9k ดาวบน GitHub มันจึงได้รับความนิยมน้อยที่สุด แต่ใช้งานได้ดีและได้รับการดูแลอย่างดี

อัญมณีทั้งหมดเหล่านี้ยอดเยี่ยม แต่ก็ไม่ยากเกินไปที่จะสร้างการอนุญาตด้วยตัวคุณเองใน Ruby ธรรมดา ฉันจะแสดงวิธีจัดการการอนุญาตโดยไม่ต้องใช้อัญมณี โดยใช้กลยุทธ์ที่เรียกว่ารูปแบบวัตถุนโยบาย

รูปแบบออบเจ็กต์นโยบาย

Policy Object เป็นรูปแบบการออกแบบที่ใช้ในการจัดการกับการอนุญาตและบทบาท คุณสามารถใช้ได้ทุกครั้งที่ต้องตรวจสอบว่าบางสิ่งหรือบางคนได้รับอนุญาตให้ดำเนินการใดๆ หรือไม่ มันสรุปกฎธุรกิจที่ซับซ้อนและแทนที่ได้อย่างง่ายดายด้วยออบเจ็กต์นโยบายอื่นที่มีกฎต่างกัน การพึ่งพาภายนอกทั้งหมดจะถูกฉีดเข้าไปในอ็อบเจ็กต์นโยบาย ห่อหุ้มลอจิกการตรวจสอบสิทธิ์ ซึ่งส่งผลให้ตัวควบคุมและโมเดลสะอาด อัญมณีอย่าง Pundit, Cancan และ Cancancan นำรูปแบบนี้ไปใช้

กฎออบเจ็กต์นโยบายบริสุทธิ์

  • ผลตอบแทนต้องเป็นค่าบูลีน
  • ตรรกะต้องเรียบง่าย
  • ภายในเมธอด เราควรเรียกเมธอดบนอ็อบเจ็กต์ที่ส่งเท่านั้น

การนำไปใช้

เริ่มต้นด้วยหลักการตั้งชื่อ ชื่อไฟล์มี _policy คำต่อท้ายที่ใช้และคลาสและนโยบายในตอนท้าย ในวิธีนี้ ชื่อจะลงท้ายด้วย ? เสมอ อักขระ (เช่น UsersPolicy#allowed? )

นี่คือตัวอย่างโค้ดบางส่วน:

class UsersPolicy
  def initialize(user)
    @user = user
  end

  def allowed?
    admin? || editor?
  end

  def editor?
    @user.where(editor: true)
  end

  def admin?
    @user.where(admin: true)
  end
end

ฉันควรใช้ในสถานการณ์ใด

เมื่อแอปของคุณมีการเข้าถึงแบบจำกัดและการดำเนินการที่จำกัดมากกว่าหนึ่งประเภท ตัวอย่างเช่น สามารถสร้างโพสต์ได้ดังนี้:

  • อย่างน้อยหนึ่งแท็ก
  • ข้อจำกัดที่ผู้ดูแลระบบและผู้แก้ไขเท่านั้นที่สามารถสร้างได้ และ
  • ข้อกำหนดที่ผู้แก้ไขต้องได้รับการยืนยัน

นี่คือตัวอย่างคอนโทรลเลอร์ที่ไม่มีอ็อบเจ็กต์นโยบาย:

class PostsController < ApplicationController
  def create
    if @post.tag_ids.size > 0
    && (current_user.role == ‘admin’
    || (current_user.role == ‘editor’ && current_user.verified_email))
      # create
    end
  end
end

เนื่องจากการตรวจสอบเงื่อนไขข้างต้นยาว น่าเกลียด และอ่านไม่ได้ จึงควรใช้รูปแบบออบเจ็กต์นโยบาย

เริ่มต้นด้วยการสร้าง PostsCreationPolicy .

class PostsCreationPolicy
  attr_reader :user, :post

  def initialize(user, post)
    @user = user
    @post = post
  end

  def self.create?(user, post)
    new(user, post).create?
  end

  def create?
    with_tags? && author_is_allowed?
  end

  private

  def with_tags?
    post.tag_ids.size > 0
  end

  def author_is_allowed?
    is_admin? || editor_is_verified?
  end

  def is_admin?
    user.role == ‘admin’
  end

  def editor_is_verified?
    user.role == ‘editor` && user.verified_email
  end
end

ผู้ควบคุมของเราที่มีอ็อบเจ็กต์นโยบายมีลักษณะดังนี้:

class PostsController < ApplicationController
  def create
    if PostsCreationPolicy.create?(current_user, @post)
      # create
    end
  end
end

วิธีใช้ออบเจ็กต์นโยบายใน Rails

สร้างไดเร็กทอรีนโยบายภายในแอป /policies และวางคลาสนโยบายทั้งหมดของคุณไว้ที่นั่น เมื่อคุณต้องการเรียกตัวควบคุม คุณสามารถทำได้โดยตรงในการดำเนินการหรือใช้ before_action :

class PostsController < ApplicationController
  before_action :authorized?, only: [:edit, :create, :update, :destroy]

  def authorized?
    unless ::PostsCreationPolicy.create?(current_user, @post)
      render :file => "public/404.html", :status => :unauthorized
    end
  end
end

วิธีทดสอบออบเจ็กต์นโยบาย

การทดสอบพฤติกรรมในคอนโทรลเลอร์ทำได้ง่าย:

require 'rails_helper'

RSpec.describe "/posts", type: :request do
  describe "when user is not allowed" do
    let(:user_not_allowed) { create(:user, admin: false, editor: false) }
    let(:tag) { create(:tag) }
    let(:valid_attributes) { attributes_for(:post, tag_id: tag.id) }

    before do
      sign_in user_not_allowed
    end

    describe "GET /index" do
      it "return code 401" do
        diet = Post.create! valid_attributes
        get edit_post_url(post)
        expect(response).to have_http_status(401)
      end
    end
  end
end

การทดสอบนโยบายก็ง่ายเช่นกัน เรามีวิธีการเล็ก ๆ มากมายที่มีหน้าที่เดียวเท่านั้น

require 'rails_helper'

RSpec.describe PostsCreationPolicy do
  describe "when user is not allowed" do
    let(:user) { create(:user, editor: false, admin: false) }
    let(:user_editor) { create(:user, editor: true, email: verified) }
    let(:tag) { create(:tag) }
    let(:post) { create(:post, tag_id: tag.id) }

    describe ".create?" do
      context "when user is allowed" do
        it "creates a new post" do
          expect(described_class.create?(user_editor, post)).to eq(true)
        end
      end

      context "when user is not allowed" do
        it "does not create a new post" do
          expected(described_class.create?(user, post)).to eq(false)
        end
      end
    end

    # ...more test cases
  end
end

เราทดสอบว่าวัตถุนั้นได้รับอนุญาตให้สร้างขึ้นในทุกสถานการณ์หรือไม่

บทสรุป

แนวคิดรูปแบบนโยบายมีขนาดเล็ก แต่ให้ผลลัพธ์ที่ยิ่งใหญ่ พิจารณาใช้วัตถุนโยบายทุกครั้งที่คุณต้องจัดการกับการอนุญาตที่เรียบง่ายหรือซับซ้อน เมื่อพูดถึงการทดสอบด้วย RSpec คุณไม่จำเป็นต้องใช้บันทึกฐานข้อมูล นโยบายของคุณเป็นวัตถุ Ruby ล้วนๆ และการทดสอบของคุณจะง่ายและรวดเร็ว