管理者権限の付与方法
手順
1.gemのインストール
gemfileに記述しbundle install
gem 'devise'
gem 'cancancan'
gem 'rails_admin'
2.deviseのセットアップ
deviseのインストールと、userモデルの作成
rails g devise:install
rails g devise user
rails db:migrate
http://<host>:3000/users/sign_up
にアクセスして、メールアドレス等入力して登録。
3.cancanのセットアップ
rails g cancan:ability
4. rails_adminのセットアップ
rails g rails_admin:install
install時、パスを聞かれるが、今回はデフォルトの/adminで表示させるので、そのままEnter。
5.rails_admin.rb を編集(コメントアウトを外す)
# == Devise ==
config.authenticate_with do
warden.authenticate! scope: :user
end
config.current_user_method(&:current_user)
# == Cancan ==
config.authorize_with :cancan
6.adminの判定の追加
- rails_adminでの管理画面に入れるユーザーを管理者のみに限定する設定
- userテーブルにadminを追加し、このフラグがtrueのユーザーのみ管理者として扱う
- 出来上がったマイグレーションファイルを編集(falseにすることで自動的に管理者設定されない。)
rails g migration AddAdminToUser admin:boolean
# == マイグレーションファイル
class AddAdminToUsers < ActiveRecord::Migration[5.2]
def change
add_column :users, :admin, :boolean, default: false
end
end
rails db:migrate
- 次に先ほど登録したユーザーを管理者に設定。
- 既にあるユーザーに権限をつけることも可
- コントローラーやビューでadminがtrueの場合とfailseの場合で分岐して、各処理を書く
# 既存のユーザーへの管理者権限の付与
rails c
[1] pry(main)> user = User.find(1) //idが1のレコードを取得(今作成したuserのid)
[2] pry(main)> user.admin = true
[3] pry(main)> user.save
- 最後にcancancanの権限設定(管理者権限を簡単に定義しておくことのできるgem)
app/models/ability.rb
class Ability
include CanCan::Ability
def initialize(user)
if user.try(:admin?) # userがnillだった場合(ログインしていなかった場合)にエラーになるのを防ぐため
can :access, :rails_admin # 管理者ページへのアクセスを許可
can :manage, :all # 全ての権限を付与
end
end
end
- ログイン済みで、adminがtrueのユーザーは、全てのモデルのCRUDが行えて(can :manage, :all)、rails_adminの管理画面にアクセス権限(can :access, :rails_admin)があるという設定。
⭐️controllerでの判断(一例)
下記のように記載することで、indexにはadminカラムがtrueのユーザーのみがリンク可能。
adminカラムがfalseのユーザーは/に指定したページへとリダイレクトされる。
class UsersController < ApplicationController
before_action user_admin, only: [:index]
def index
@users = User.all
end
private
def user_admin
@users = User.all
if current_user.admin == false
redirect_to root_path
else
render action: "index"
end
end
end
ユーザーのログイン状態でルーティング先を変更
- ログイン状態であればリクエストのコンテンツを表示
- ログイン状態でなければログインページ、もしくはサインアップのページに飛ぶ
/app/controllers/application_controller.rb
before_action :authenticate_user! #protect_from_forgery with: :exceptionより後におく。
- アプリ全体の仕様にする場合にはapplicationに記載。
- 実装の確認のために、ログアウトのリンクを仕込んでおく。
/app/views/layouts/application.html.erb
# 以下を適当なところに追記
<% if user_signed_in? %>
<p><%= link_to "ログアウト", destroy_user_session_path, method: :delete %></p>
<% end %>
- ログイン時はリクエストしたページへ通し、ログオフ時はログイン画面へ。
メール認証
- サインアップしたユーザーアカウントは、一旦仮登録としてメールを飛ばし、返信を持って本登録させる。
1.Userモデルにconfirmableを追加。
- 各種認証時に「確認作業」が定義できるようになる。Eメール認証はそのやり方のうちの一つ(くらい)。
/app/models/user.rb
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable, :confirmable #最後のやつを追加
- Userテーブルに確認にまつわる項目を追加しなければいけないのでマイグレーションファイルを作成。
rails g migration add_confirmable_to_devise
マイグレーションファイルを次のように編集。
/db/migrate/yyyymmddxxxxxx_add_confirmable_to_devise.rb
class AddConfirmableToDevise < ActiveRecord::Migration[5.1]
# Note: You can't use change, as User.update_all will fail in the down migration
def up
add_column :users, :confirmation_token, :string
add_column :users, :confirmed_at, :datetime
add_column :users, :confirmation_sent_at, :datetime
add_column :users, :unconfirmed_email, :string # Only if using reconfirmable
add_index :users, :confirmation_token, unique: true
# User.reset_column_information # Need for some types of updates, but not for update_all.
# To avoid a short time window between running the migration and updating all existing
# users as confirmed, do the following
User.all.update_all confirmed_at: DateTime.now
# All existing user accounts should be able to log in after this.
end
def down
remove_columns :users, :confirmation_token, :confirmed_at, :confirmation_sent_at
remove_columns :users, :unconfirmed_email # Only if using reconfirmable
end
end
rake db:migrate
- Eメール送信のための設定完了。
- SMTPサーバの設定が必要。githubにアップしたくはないので、letter-opener_webを使用。
- gemのインストール。
- SMTP(Simple Mail Transfer Protocol)サーバとは、メールの送信元と宛先の間で、メールを送受信または中継することを主な目的としたアプリケーション
- gemのインストール。
Gemfile
group :development do #開発環境のみ
gem 'letter_opener' # 追加
gem 'letter_opener_web' # 追加
end
bundle install --path vender/bundle
設定ファイルを編集
config.action_mailer.perform_caching = true # falseをtrueに修正
config.action_mailer.default_url_options = { host: 'localhost:3000' } # 追加
config.action_mailer.delivery_method = :letter_opener_web # 追加
以下をルーティング設定に追加。
/config/routes.rb
if Rails.env.development? #開発環境の場合
mount LetterOpenerWeb::Engine, at: "/letter_opener"
end
- localhost:3000/letter_openerを開くと送信されたメールをブラウザで確認できる。
cancancanについて
- 権限があるかはcan?メソッドで判定することが出来る。
※具体例
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new # ログインしていない場合は、からユーザーを用意し判定に用いる
# default parmission
cannot :buy, Product
if user.sys_admin?
can :manage, :all
end
if user.product_manager?
can :manage, Stockpile, owner: user # 自分がオーナーの倉庫には全権限を持つ
can :read, Stockpile # そうでなくても、読み取り権限を持つ
# 自分の倉庫にある製品に対してすべての権限を持つ
can :manage, Product, stockpile: {owner: user}
# ただし、新規登録、削除はできない
cannot [:create, :destroy], Product
end
if user.customer?
# 複数のモデルに権限を付与できる
can :read, [Stockpile, Product]
# 独自権限も作れる
can :buy, Procuct, stockpile: nil # 倉庫から出されている製品を買える
end
end
end
read:参照
create:登録
update:更新
destroy:削除
manage:すべて
基本の権限には上記のものがあります。
「manage / すべて」というのは「read+crete+update+destroy」
というわけではない点に注意。
上の例では「buy / 購入」という権限を独自に作られているが、
manage権限ではbuyも可能名ため、デフォルトの権限付与で、製品の購入を禁じ、
もしuser.customer? = true
ならば購入可能になるように加工。
※あるモデルのある権限にcanとcannotが重なって付与された場合、最後に適応されたほうが有効になる。
# current_userがproduct_managerの場合
can? :manage, Stockpile #=> true
can? :read, Stockpile #=> true
can? :update, Stockpile #=> true
# 1. あるモデルに権限Xを持っているか判定する
@stockpile = Stockpile.find_by(owner: user)
can? :manage, @stockpile #=> true
# 2. あるレコードに権限Xを持っているか判定する
@stockpile_2 = Stockpile.where("owner_id != ?", user.id).first
can? :manage, @stockpile_2 #=> false
can? :read, @stockpile_2 #=> true
can? :update, @stockpile_2 #=> false
# 3. あるモデルから権限を持っているレコードを取得する
Stockpile.accessible_by(current_ability).to_sql
#=> SELECT "stockpiles".*
# FROM "stockpiles"
# WHERE "stockpiles"."owner_id" = 1
ということが出来ます。
3の方法では、index権限での検索がデフォルトのようですが、
Stockpile.accessible_by(current_ability, :destroy)
Stockpile.accessible_by(current_ability, :my_permission)
権限のAND条件
# 自身が登録した食べ物に参照権限がある
can :show, Product, type: 'food', created_by: user.id
# 一年前〜半年前に登録された食べ物の削除権限がある
can :destroy, Product, type: 'food', created_at: (1.years.ago..6.months.ago)
# どう検索されるか確認
Stockpile.accessible_by(current_ability, :show)
# WHERE "stockpiles"."type" = 'food' AND "stockpiles"."created_by" = 1
Stockpile.accessible_by(current_ability, :destroy)
# WHERE "stockpiles"."type" = 'food' AND "stockpiles"."created_at" BETWEEN '2014-12-1' AND '2014-6-1'
権限のOR条件
# 食べ物、または飲み物の閲覧権限がある
can :read, Product, type: 'food'
can :read, Product, type: 'drink'
# どう検索されるか確認
Stockpile.accessible_by(current_ability)
# WHERE "stockpiles"."type" = 'food' OR "stockpiles"."type" = 'drink'
deviseで定義されているモジュールについて
モジュール名 | 内容 |
---|---|
DatabaseAuthenticatable | データベースに保存されたパスワードが正しいか検証をします。同時にパスワードの暗号化も行います。 |
|
|Recoverable|パスワードをリセットします。|
|Trackable|ログインした回数、最終ログイン日時、前回ログイン日時、最終ログインIP、前回ログインIPを保存する。|
|Confirmable|新規登録時にメール認証機能をつけます。|
|Lockable|ログインに何度も失敗すると、アカウントをロックします。何回失敗するとロックするかはこのLockable内で指定します。|
以上がマイグレーションファイルに設定されており、初期状態だとコメントアウトされているものもあるので、
使いたい場合はコメントアウトを外す必要がある。
その他に用意されているモジュール
- マイグレーションファイルに記述されている以外のモジュール
モジュール名 | 内容 |
---|---|
Timeoutable | ログインしたままの状態で一定時間経つと自動でログアウトさせる。デフォルトは30分。 |
- 有効期限を設定する場合は
config/initializers/devise.rb
に下記を追記。
# 1ヶ月と指定
config.timeout_in = 1.month
次にrailsの有効期限を設定します。
config/initializers/session_store.rb
を下記のように編集します。
TechReviewSite::Application.config.session_store :cookie_store, key: '自分のアプリの設定_session', expire_after: 1.month'
こうすることにより1ヶ月はログインセッションが保持され、
1ヶ月以内にアクセスすればさらに1ヶ月セッション時間が保持される。
モジュール名 | 内容 |
---|---|
Validatable | Emailやパスワードのバリデーションを追加します。 |
Omniauthable | twitterやFacebookなどのSNS認証を追加します。 |
モジュールの使い方
app/models/users.rb
で下記のように指定します。
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
deviseのビューについて
投稿フォームのデザインはdeviseで用意されています。
ただシンプルなので自分のアプリに合ったデザインに変更するのが良いでしょう。
その際は下記のコマンドを入力します。
rails g devise:views
各フォルダがどのビューに対応しているか確認してみましょう。
フォルダ名 | ファイル名 | 内容 |
---|---|---|
confirmations | new | パスワード再発行時のフォーム |
mailer | 後述 | 送信されるメールの内容(下記に詳細) |
passwords | edit | パスワード編集フォーム |
passwords | new | パスワードを忘れた時のフォーム |
registrations | edit | ユーザー情報編集フォーム |
registrations | new | 新規登録のフォーム |
sessions | new | ログインフォーム |
shared | _link | 各フォームに表示されるリンクの部分テンプレート |
unlocks | new | アカウントロック通知メールを送信するフォーム |
Devise Securityについて
deviseでは過去に使用されたパスワードの制限や多重ログイン禁止などの
エンタープライズ用のセキュリティ要件までは取り扱っていない。
よりセキュアなRailsアプリを構築するために使用される。
追加できる機能
名前 | 内容 |
---|---|
:password_expirable | 設定された時間が過ぎるとパスワードが自動的に失効。 |
:secure_validatable | Devise本家の:validatableよりも、さらに強いEmail、パスワード検証。 |
:password_archivable | 過去に使われたパスワードをold_passwordsテーブルに格納し、履歴確認。 |
:session_limitable | いちアカウントが一度に1つのセッションしか使えないことを保証する。(=多重ログインの禁止) |
:expirable | アクティブでない状態がXX日間続いたユーザアカウントを失効させる。 |
:security_questionable | 秘密の質問機能を可能に。 |
:captcha support | さらに追加的な機能として、sign_up、sign_in、recover、unlockに対してcaptcha を導入することができる。 |
:paranoid_verification | ユーザがアプリを使うための識別コードを管理者が発行できる |
インストール
Gemfile
にdevise-security
を追加してbundle install
gem 'devise-security'
Config設定
次にジェネレータを実行します。
rails generate devise_security:install
-
config/initializers/devise-security.rb
に設定ファイルが作成されるので、
それを適宜欲しい機能に応じて修正する。
config/initializers/devise-security.rb
Devise.setup do |config|
# パスワード有効期限(例. 3ヶ月)
# config.expire_password_after = 3.months
# パスワードは英文字(大文字・小文字)と数字、記号を最低1字ずつ含む
# config.password_complexity = { digit: 1, lower: 1, symbol: 1, upper: 1 }
# アーカイブする旧パスワードの数
# config.password_archiving_count = 5
# 古いパスワードを拒否する?(true, false, count)
# config.deny_old_passwords = true
# recoverフォームにcaptchaを導入する?
# config.captcha_for_recover = true
# sign upフォームにcaptchaを導入する?
# config.captcha_for_sign_up = true
# sign inフォームにcaptchaを導入する?
# config.captcha_for_sign_in = true
# unlockフォームにcaptchaを導入する?
# config.captcha_for_unlock = true
# recoverフォームに秘密の質問を導入する?
# captcha_for_recoverが自動的にtrueになります。
# config.security_question_for_recover = false
# unlockフォームに秘密の質問を導入する?
# captcha_for_unlockが自動的にtrueになります。
# config.security_question_for_unlock = false
# confirmationフォームに秘密の質問を導入する?
# captcha_for_confirmationが自動的にtrueになります。
# config.security_question_for_confirmation = false
# ==> Configuration for :expirable
# 最後のアクティブから失効になるまでの時間
# config.expire_after = 90.days
end
DBスキーマの修正
各機能に応じてDBスキーマを修正します。
詳細はDevise SecurityのREADMEを見てください。
Session limitable
の例
def change
add_column :resources, :unique_session_id, :string, limit: 20
end
リソースに機能を付与
devise :database_authenticatable, ..., :session_limitable
※herokuにアップする場合
herokuへのプッシュ後、
heroku rake db:migrate
heroku run rails c
> user = User.find(1)
> user.update_attribute(:admin_flg, true)
参照URL
https://qiita.com/umanoda/items/679419ce30d1996628ed
https://qiita.com/NZTK/items/7868b2897ff1c46ee338
https://qiita.com/shun-kusano/items/1a65040aae3e0db72294
https://qiita.com/7sgg_m/items/178e0d90fc55c5716474
https://pikawaka.com/rails/devise#devise%E3%81%AE%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB%E6%96%B9%E6%B3%95
https://qiita.com/tsukakei/items/7c4701270cacedcf5ab7
https://github.com/devise-security/devise-security