Tìm hiểu về gem Pundit
Bài đăng này đã không được cập nhật trong 8 năm
Nếu bạn xây dựng một ứng dụng với nhiều loại user và điều bạn lo lắng nhất chính là phân quyền cho các user của bạn. Hiện tại có rất nhiều giải pháp cho vấn đề trên và một trong số đó là sử dụng gem pundit
Gem pundit
là một thư viện giúp xây dựng một hệ thống hạn chế tài nguyên của một user được phép sử dụng.
Cài đặt#
Đầu tiên, ta thêm dòng sau vào Gemfile
và chạy bundle install
:
gem "pundit"
Bước tiếp theo, ta sẽ thêm Pundit vào trong ApplicationController của ứng dụng:
class ApplicationController < ActionController::Base
[...]
include Pundit
[...]
end
Cuối cùng, ta sẽ generate application policy bằng câu lệnh sau:
rails g pundit:install
Sau khi thực hiện câu lệnh trên, một class ApplicationPolicy sẽ được tạo ra trong thư mục app/policies
như sau:
app/policies/application_policy.rb
class ApplicationPolicy
attr_reader :user, :record
def initialize(user, record)
raise Pundit::NotAuthorizedError, "must be logged in" unless user
@user = user
@record = record
end
def index?
false
end
def show?
scope.where(:id => record.id).exists?
end
def create?
false
end
def new?
create?
end
# [...]
class Scope
# [...]
end
end
Với mỗi class Policy có một số lưu ý về đặt tên như sau:
- Tên class nên là tên của model sau đó đến từ
Policy
. Ví dụ:PostPolicy
sẽ là policy ứng với model Post. - Tham số đần tiên trong phương thức
initialize
phải là một user. Pundit sẽ sử dụng phương thứccurrent_user
để lấy ra nó. Tuy nhiên, nếu ứng dụng không có phương thức này thì ta phải overriding phương thứcpundit_user
. Tham số thứ hai là một đối tượng ActiveModel. - Policy có các phương thức như
create?
hoặcnew?
để kiểm tra quyền truy cập.
Tạo các access rule#
Đầu tiên, ta cần tạo các luật truy cập. Ví dụ như ta có yêu cầu chỉ những user là admin mới xóa được post thì trong PostPolicy
khai báo như sau:
class PostPolicy < ApplicationPolicy
def destroy?
user.admin?
end
end
Để sử dụng luật này thì pundit cung cấp phương thức authorize
. Ví dụ trong PostsController
để kiểm tra quyền xóa Post sẽ như sau:
[...]
def destroy
authorize @post
@post.destroy
redirect_to posts_url, notice: 'Post was successfully destroyed.'
end
[...]
Ngoài ra, phương thức authorize
có tham số thứ hai chỉ đến 1 phương thức tên của luật sử dụng trong policy. Ví dụ:
def publish
authorize @post, :update?
end
Nếu như user không có quyền thực hiện sẽ có ngoại lệ Pundit::NotAuthorizedError
sẽ sinh ra. Chúng ta cần bắt ngoại lệ này và hiện thị thông báo. Ví dụ như sau trong file application_controller :
[...]
rescue_from Pundit::NotAuthorizedError,
with: :user_not_authorized
private
def user_not_authorized
flash[:error] = t "#{policy_name}.#{exception.query}",
scope: "pundit", default: :default
redirect_to(request.referrer || root_path)
end
[...]
Cập nhật file en.yml:
en:
pundit:
default: 'You cannot perform this action.'
post_policy:
destroy?: 'You cannot destroy this post!'
Để kiểm tra quyền trong view ta sử dụng hàm policy. Ví dụ:
<% if policy(post).destroy? %>
[...]
<% end %>
Trong trường hợp user chưa login thì current_user
sẽ nil
. Trong trường hợp này pundit không cần check quyền mà phải đưa ra thông báo yêu cầu login. Để thực hiện việc này, ta có một cách sau. Bổ sung file application_policy.rb như sau:
class ApplicationPolicy
def initialize(user, record)
raise Pundit::NotAuthorizedError, "must be logged in" unless user
@user = user
@record = record
end
end
Enforcing Authorization#
Trong trường hợp bạn muốn kiểm tra quyền đồng thời nhiều action trong controller thì pundit cung cấp phương thức verify_authorized
. Ví dụ:
_posts_controller.rb
before_action :verify_authorized, only: [:destroy]
Nếu như không muốn kiểm tra quyền trong một trường hợp nào đó đặc biệt thì sử dụng phương thức skip_authorization
.
Strong parameters#
Pundit cho phép chúng ta quy định các attribute mà user có thể truy cập thông qua hàm permitted_attributes
. Ví dụ như trong lớp PostPolicy:
[...]
def permitted_attributes_
if user.admin?
[:title, :body, :special]
else
[:title, :body]
end
end
[...]
Như vậy, chỉ admin mới có thể truy cập, sửa attribute :special
.
Trong controller, khi cần lấy các paramater chỉ cần gọi phương thức permitted_attributes
với tham số là một đối tượng model.
Ví dụ:
def create
@post = Post.new
@post.update_attributes(permitted_attributes(@post))
if @post.save
redirect_to @post, notice: 'Post was successfully created.'
else
render :new
end
end
def update
if @post.update(permitted_attributes(@post))
redirect_to @post, notice: 'Post was successfully updated.'
else
render :edit
end
end
[...]
Kết luận#
Trên đây là bài viết tìm hiểu về gem Pundit
. Một thư viên để phân quyền trong rails.
Hy vọng qua đó, chúng ta có thể có thêm một lựa chọn trong việc authorization trong rails ngoài việc sử dụng cancancan
.
Bài viết còn nhiều thiếu sót, hy vọng nhận được nhiều đóng góp ý kiến của mọi người.
Xin chân thành cảm ơn
Tài liệu tham khảo: https://github.com/elabs/pundit#customize-pundit-user
All rights reserved