Yêu cầu chung của các ứng dụng web là khả năng gán các vai trò và quyền cụ thể.
Nhiều loại ứng dụng web phân biệt giữa quản trị viên và người dùng thông thường trong việc cung cấp quyền truy cập hạn chế. Điều này thường được thực hiện bằng cách sử dụng một boolean đơn giản để xác định xem người dùng có phải là quản trị viên hay không. Tuy nhiên, các vai trò và quyền có thể trở nên phức tạp hơn nhiều.
Giá trị của ứng dụng của bạn nằm ở việc hạn chế quyền truy cập vào một số dữ liệu và hành động nhất định. Đó chắc chắn là điều bạn không muốn làm rối tung lên. Trong bài đăng này, chúng tôi sẽ giải thích cách triển khai các vai trò và quyền trong một ứng dụng Ruby on Rails cơ bản.
Tôi có cần một viên ngọc để quản lý quyền không?
Không, bạn không cần đá quý, đặc biệt nếu ứng dụng của bạn nhỏ và bạn muốn tránh thêm nhiều phụ thuộc vào cơ sở mã của mình. :
-
DeviseDevise là một gem để xác thực và quản lý vai trò, và nó là một giải pháp thực sự phức tạp và mạnh mẽ. Nó được biết đến như một giải pháp xác thực, vì vậy chỉ áp dụng nó cho cơ sở mã của bạn nếu bạn cần một thư viện rất mạnh mẽ.
-
Pundit:Pundit là một viên ngọc sử dụng các đối tượng Ruby đơn giản, và nó có lẽ là viên ngọc chính sách đơn giản nhất mà chúng tôi sẽ đề cập. Sử dụng đơn giản, có mức ủy quyền tối thiểu và tương tự như sử dụng Ruby thuần túy. Với 7,3 nghìn sao trên GitHub, nó hiện là viên ngọc chính sách phổ biến nhất.
-
CanCan:CanCan là một thư viện ủy quyền hạn chế các tài nguyên mà một người dùng nhất định được phép truy cập. Tuy nhiên, CanCan đã bị bỏ rơi trong nhiều năm và chỉ hoạt động với các bản phát hành Rails 3 trở về trước.
-
CanCanCan:CanCanCan là một thư viện ủy quyền khác cho Ruby và Ruby on Rails. Nó là một thay thế cho CanCan và hiện đang được duy trì. Với 4,9k sao trên GitHub, nó ít phổ biến nhất, nhưng nó hoạt động khá tốt và được duy trì tốt.
Tất cả những viên ngọc này đều tuyệt vời, nhưng không quá khó để tự xây dựng quyền trong Ruby đơn giản. Tôi sẽ chỉ cho bạn cách quản lý quyền mà không cần đá quý, bằng cách sử dụng chiến lược có tên là mẫu đối tượng chính sách.
Mẫu đối tượng chính sách
Đối tượng chính sách là một mẫu thiết kế được sử dụng để xử lý các quyền và vai trò. Bạn có thể sử dụng nó mỗi khi bạn phải kiểm tra xem một thứ gì đó hoặc ai đó có được phép thực hiện một hành động hay không. Nó bao gồm các quy tắc nghiệp vụ phức tạp và có thể dễ dàng được thay thế bằng các đối tượng chính sách khác với các quy tắc khác nhau. Tất cả các phụ thuộc bên ngoài được đưa vào đối tượng chính sách, đóng gói logic kiểm tra quyền, dẫn đến một bộ điều khiển và mô hình sạch. Đá quý như Pundit, Cancan và Cancancan thực hiện mô hình này.
Quy tắc đối tượng chính sách thuần túy
- Kết quả trả về phải là một giá trị boolean
- Logic phải đơn giản
- Bên trong phương thức, chúng ta chỉ nên gọi các phương thức trên các đối tượng được truyền
Triển khai
Hãy bắt đầu với quy ước đặt tên; tên tệp có _policy
được áp dụng hậu tố và lớp và chính sách ở cuối. Trong phương thức này, tên luôn kết thúc bằng ?
ký tự (ví dụ:UsersPolicy#allowed?
).
Đây là một số mã ví dụ:
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
Tôi nên sử dụng chúng trong các trường hợp nào?
Khi ứng dụng của bạn có nhiều loại quyền truy cập hạn chế và các hành động bị hạn chế. Ví dụ:các bài đăng có thể được tạo bằng cách sau:
- ít nhất một thẻ,
- một hạn chế mà chỉ quản trị viên và người chỉnh sửa mới có thể tạo chúng và
- một yêu cầu mà người chỉnh sửa cần được xác minh.
Đây là một bộ điều khiển mẫu không có đối tượng chính sách:
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
Bởi vì các kiểm tra điều kiện ở trên dài, xấu và không thể đọc được, nên áp dụng mẫu đối tượng chính sách.
Hãy bắt đầu bằng cách tạo 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
Bộ điều khiển của chúng tôi với đối tượng chính sách trông giống như sau:
class PostsController < ApplicationController
def create
if PostsCreationPolicy.create?(current_user, @post)
# create
end
end
end
Cách sử dụng các đối tượng chính sách trong Rails
Tạo thư mục chính sách bên trong ứng dụng /policies
và đặt tất cả các lớp chính sách của bạn ở đó. Khi bạn cần gọi bộ điều khiển, bạn có thể thực hiện trực tiếp trong một hành động hoặc sử dụng 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
Cách kiểm tra các đối tượng chính sách
Thật đơn giản để kiểm tra hành vi trong bộ điều khiển:
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
Việc thử nghiệm chính sách cũng đơn giản; chúng tôi có rất nhiều phương pháp nhỏ chỉ với một trách nhiệm.
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
Chúng tôi kiểm tra xem đối tượng có được phép tạo trong mọi tình huống hay không.
Kết luận
Khái niệm mẫu chính sách tuy nhỏ nhưng tạo ra kết quả lớn. Khi nói đến thử nghiệm với RSpec, bạn không cần phải sử dụng các bản ghi cơ sở dữ liệu; các chính sách của bạn hoàn toàn là các đối tượng Ruby và việc kiểm tra của bạn sẽ đơn giản và nhanh chóng.