Tìm hiểu về gem Pundit
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 ...
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.
Đầ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ức current_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ức pundit_user. Tham số thứ hai là một đối tượng ActiveModel.
- Policy có các phương thức như create? hoặc new? để kiểm tra quyền truy cập.
Đầ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
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.
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 [...]
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