12/08/2018, 13:02

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

0