ข้อกำหนดทั่วไปของเว็บแอปพลิเคชันคือความสามารถในการกำหนดบทบาทและการอนุญาตเฉพาะ
เว็บแอปพลิเคชันหลายประเภทแยกความแตกต่างระหว่างผู้ดูแลระบบและผู้ใช้ทั่วไปในการให้การเข้าถึงแบบจำกัด การดำเนินการนี้มักใช้บูลีนแบบง่ายที่กำหนดว่าผู้ใช้เป็นผู้ดูแลระบบหรือไม่ อย่างไรก็ตาม บทบาทและการอนุญาตอาจซับซ้อนกว่านี้มาก
คุณค่าของแอปพลิเคชันของคุณอยู่ที่การจำกัดการเข้าถึงข้อมูลและการดำเนินการบางอย่าง เป็นสิ่งที่คุณไม่อยากยุ่งอย่างแน่นอน ในโพสต์นี้ เราจะอธิบายวิธีการใช้บทบาทและการอนุญาตในแอปพลิเคชัน 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 ล้วนๆ และการทดสอบของคุณจะง่ายและรวดเร็ว