12/08/2018, 13:33

Dynamic roles and permissions dùng cancancan gem rails

Trong Rails, cancancan là gem phổ biến nhất dùng cho việc xây dựng chức năng authorization của ứng dụng. Khi sử dụng cancancan thường chúng ta sẽ định nghĩa Role Based Access (RBAC) tới các models trong class Ability. Tuy nhiên, khi muốn thay đổi các roles trong ứng dụng chúng ta cần sửa code trong ...

Trong Rails, cancancan là gem phổ biến nhất dùng cho việc xây dựng chức năng authorization của ứng dụng. Khi sử dụng cancancan thường chúng ta sẽ định nghĩa Role Based Access (RBAC) tới các models trong class Ability. Tuy nhiên, khi muốn thay đổi các roles trong ứng dụng chúng ta cần sửa code trong class Ability, sau đó restart lại ứng dụng để thay đổi đó có hiệu lực. Điều này thật là bất tiện, nhất là với những người quản lý ứng dụng không biết chút gì về coding.

Trong bài viết này, tôi xin giới thiệu với các bạn cách xây dựng tính năng thiết lập roles và permisstions cho ứng dụng mà không phải sửa lại code. Mọi thứ thao tác hoàn toàn trên giao diện người dùng. Nói cách khác chúng ta sẽ đi xây dựng một ứng dụng với dynamic authorization.

solutions_diagram2-03.png

Trong bài viết này, tôi giả sử bạn đã nắm được cách sử dụng cơ bản của các gems: devise, cancancan

Tôi giả sử, chúng ta đang xây dựng một hệ thống có nhiều loại adminstrator mỗi loại lại có những quyền quản lý hệ thống khác nhau. Giả sử chúng ta đã sử dụng gem devise để xây dựng chức năng authentication cho admin của hệ thống. Ở đây tôi giả sử một admin chỉ có một role và một role có thể có nhiều admind. Ngoài các attributes thông thường, admins còn có thêm attribute is_root, type là boolean. Nếu một admin có is_root là true thì admin này có full quyền trong hệ thống.

admin.rb

class Admin < ActiveRecord::Base
  devise …

  belongs_to :role
end
  • Trong terminal, chạy dòng lệnh sau để tạo Role model
rails g model role name permissions:text

Nội dung file role.rb như sau:

class Role < ActiveRecord::Base
   has_many :admins, dependent: :nullify
   serialize :permissions, Hash
end

Bạn có thể thấy đoạn code trên có 2 điểm cần chú ý là:

has_many :admins, dependent: :nullify

dùng để xác định rằng khi một role bị xóa thì trường role_id của các admin liên kết với nó sẽ được thiết lập tới giá trị nil.

serialize :permissions, Hash

Trong mysql không có kiểu dữ liệu là Hash, nhưng chúng ta có thể làm việc với một attribute kiểu text như đang làm việc với một attribute có kiểu dữ liệu là Hash bằng một định nghĩa như trên. Khi đó, để tạo một new record cho roles, thay cho việc đưa vào giá trị text cho permissions field chúng ta sẽ đưa vào một hash. Và tương tự khi chúng ta get giá trị của permission field trong một role record, giá trị trả về là một hash chứ không phải là dạng text. Có điểm đặc biệt là giá trị trong database của permissions field lại là một text

Trong mỗi role, permissons có dạng như sau:

{'admin' => [:read], 'article' => [:read, :create, :update]}

Đây chính là nơi chúng ta hiện thực hóa roles, permissions chúng ta đã lưu trong bảng roles

Nội dung của ability.rb như sau:

class Ability
  include CanCan::Ability

  def initialize user
    return can :manage, :all if user.is_a?(Admin) && user.is_root?
    return unless user.role
    user.role.permissions.each do |subject, actions|
      can actions,
        (klass = subject.classify.safe_constantize) ? klass : subject.to_sym
    end
  end
end
return can :manage, :all if user.is_a?(Admin) && user.is_root?

Xác định rằng nếu current admin là một root admin thì admin đó có full quyền trong hệ thống

Chúng ta cần overwrite lại current_ability trong controller để truyền đúng current_admin tới Ability class
Chúng ta sẽ tạo một AdminBaseController nơi chứa các methods, và attributes phổ biến của các controllers liên quan tới các thao tác của admin. Nội dung của file này như sau:

class Admin::BaseAdminController < ApplicationController
  before_action :authenticate_admin!

  protect_from_forgery

  layout "admin/base"

  rescue_from CanCan::AccessDenied do
    redirect_to admin_root_path, alert: "you don't have permission to access to this page"
  end

  protected
  def current_ability
    @current_ability ||= Ability.new current_admin
  end
end

Đến đây chúng ta đã đủ điều kiện để thực hiện authorization cho hệ thống. Tại mỗi controller mà cần xem xét permission của current admin chúng ta thêm phương thức sau:

load_and_authorize_resource

hoặc

authorize_resource

Với những gì đã thực hiện, chúng ta vẫn chưa có một màn hình để thêm, sửa, xóa các roles, và một màn hình để root admin có thể chọn role cho các admin khác

Hướng giải quyết cho vấn đề này rất đơn giản
Với màn hình thêm sửa xóa role: chúng ta sẽ tạo một RolesController và xây dựng CRUD cho roles. Cần chú ý rằng chỉ root admin mới có quyền thao tác trên màn hình role này.

Với màn hình root admin chọn role cho các admin khác: chúng ta có thể thêm một mục chọn role trong màn hình edit admin mà chỉ khi current admin là root admin mục này mới hiện lên.

Bài viết này tôi đã giới thiệu với các bạn cách xây dựng dynamic roles và permissions cho hệ thống dùng cancancan gem. Những gì tôi giới thiệu là khá đơn giản nhưng nó chính là một hướng đi cho bạn khi muốn tạo một dynamic authorization system, bạn hoàn toàn có thể tùy chỉnh Ability class để phù hợp với hệ thống của bạn.

Hiện tại đã có một số gem giúp bạn cài đặt dynamic authorization cho hệ thống rất đơn giản ví dụ như gem the_role. Nhưng các gem này lại có một khuyết điểm rất lớn là việc custom cho phù hợp với một hệ thống có authorization phức tạp.

Cảm ơn các bạn đã theo dõi bài viết của tôi, hẹn gặp lại trong các bài viết tiếp theo.

0