LoginSignup
16
18

More than 5 years have passed since last update.

ビットフィールドでユーザの権限管理

Posted at

権限管理用のgemもいくつかあるみたいですが、今回は機能を自作してみました。

Rubyならビットフィールドを使わずともハッシュで事足りるんでしょうけど、
こっちのほうがちょっとスッキリするかも…?気持ち的にも。

目指すところ

特定の権限を持つユーザーにだけリンクを表示したい場合

app/views/foo/bar.html.erb
<% if current_user.permission?('hoge') %>
    <%= link_to 'link_to_hoge', hoge_path %>
<% end %>

特定の権限を持つユーザーだけアクセスできるようにしたい場合
(権限を持たなければ403エラー(Forbidden)にする)

app/controllers/hoge_controller.rb
class HogeController < ApplicationController
  include CheckPermission

  before_action :authenticate_user!
  before_action -> { check_role_permission!('hoge')  }

  .....

end

それから…

  • ユーザーの種類(ロール)ごとに付加データを持たせたい
    • フォームでの表示名
    • DB保存用のロールID
    • ...
  • 権限の変更を簡単にできるようにしたい
  • 権限の種類が増えてきてもスッキリ書きたい
  • ビット演算を使いたい!

ビットフィールドまわりの説明

ロールは、各権限の有無を表す数値を持つ。(0b***はRubyの2進数表記)

  • 管理者:0b1111_1111_1111_1111
  • 一般 :0b0010_1101_0001_0000
  • ...

各ビットは、権限の有無を表している。
0ビット目はユーザー情報閲覧権限、1ビット目はユーザー情報編集権限、
2ビット目は顧客データの閲覧権限…など。

指定したビットが立っているか(権限を持つか)調べたい場合は、
そのビットだけ1になっている値との&をとって0かどうかを調べればよい。
(&をとると、両方のビットが1のときのみ1,それ以外のビットは0になる。)

たとえば顧客データの閲覧権限(2ビット目)を持つか調べたい場合は、

  0b1111_1111_1111_1111
& 0b0000_0000_0000_0100
-----------------------
  0b0000_0000_0000_0100 (!= 0)
  0b0010_1101_0001_0000
& 0b0000_0000_0000_0100
-----------------------
  0b0000_0000_0000_0000 (== 0)

といった具合。

作ってみる

用意するGem

plataformatec/devise(認証機能)
binarylogic/settingslogic(定数管理)

権限設定を書く

settingslogicの設定ファイルに。
一旦形を作っておけば後からどんどん追加、変更ができる。
桁数が多くなってきたら4ビットぐらいで区切ると見やすい。

<<はシフト演算子で、指定した数だけビットを右にシフトしてくれる。
たとえば、1<<2 => 0b0000_0001 << 2 => 0b0000_0100

config/application.yml
# 権限設定
permission:
  #reserved:        <%= 1 << 7  %> # 予約
  announcement:     <%= 1 << 6  %> # お知らせ記事投稿
  item_write:       <%= 1 << 5  %> # 商品情報編集
  item_read:        <%= 1 << 4  %> # 商品情報閲覧

  customer_write:   <%= 1 << 3  %> # 顧客情報編集
  customer_read:    <%= 1 << 2  %> # 顧客情報閲覧
  user_write:       <%= 1 << 1  %> # ユーザー情報編集
  user_read:        <%= 1 << 0  %> # ユーザー情報閲覧

# ロール設定
role:
  admin:
    name:       管理者                # ロール表示名
    id:         10                   # ロールID
    permission: <%= 0b0111_1111 %>   # 権限設定値
  normal:
    name:       一般ユーザー
    id:         20
    permission: <%= 0b0101_0000 %>

ユーザーテーブルにロールIDを持たせる

db/migrate/2014**********_add_user_to_role_id.rb
class AddUserToRoleID < ActiveRecord::Migration
  def change
    add_column :users, :role_id, :integer
  end
end

権限チェックできるようにする

ついでに付加データも取れるようにしたり。

app/models/user.rb
  # ロールの権限をチェックする
  def permission?(key)
    role = __role_data
    permission = role.present? ? role[1]['permission'] : 0
    return (permission & Settings.permission[key]) != 0    # 上で説明したビット演算!
  end
  # ロール名を取得する
  def role_name
    role = __role_data
    role.present? ? role[1]['name'] : nil
  end
  # ロールIDを収得する
  def role_id
    role = __role_data
    role.present? ? role[1]['id'] : -1
  end

  private
  # ロールのデータを取得する
  def __role_data
    Settings.role.detect { |_, val| val['id'] == self.role_id }
  end

最初に書いたこれができるようになった!

app/views/foo/bar.html.erb
<% if current_user.permission?('hoge') %>
    <%= link_to 'link_to_hoge', hoge_path %>
<% end %>

権限によるアクセス制限ができるようにする

いろんなcontrollerファイルで使うと思うので、concernsにモジュールとして作成する。

app/controllers/concerns/check_permission.rb
module CheckPermission
  extend ActiveSupport::Concern

    # 権限があるかをチェック
    def check_role_permission!(key)
      render file: 'public/403.html', status: :forbidden unless current_user.permission?(key)
    end

end

最初に書いたこれができるようになった!

app/controllers/hoge_controller.rb
class HogeController < ApplicationController
  include CheckPermission

  before_action :authenticate_user!
  before_action -> { check_role_permission!('hoge')  }

  .....

end

ちなみに、特定のアクションのみ制限したいときは、

  before_action -> { check_role_permission!('hoge')  }, only: [:new, :edit]

といった感じで書ける。

参考

権限管理用のgemについて
rails 4 対応の認可の gem
deviseについて
Rails4 にて Devise でユーザー登録・ログイン認証・認可の機能を追加
settingslogicについて
Railsで定数を一元管理する(settingslogic)
concernsについて
ActiveSupport::Concern でハッピーなモジュールライフを送る

16
18
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
16
18