44
60

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

[Rails] Deviseを使ってadminユーザーのみがユーザーを登録/編集できるようにする

Last updated at Posted at 2018-07-23

業務システムなど、ユーザー管理を管理者のみ行えるようにしたい場面が度々あると思います。

私の場合はRailsで業務システムを開発しており、上記機能を作成する必要がありました。
Railsにおいて認証システムを開発する際は、Deviseというgemを使用するのが一般的かと思いますので、Deviseを改造して上記のシステム構築を行いました。

なお、今回は以下のgemを導入している想定で話を進めます。

gem 'devise'
gem 'cancancan'
gem 'rolify'

これらのgemの導入方法については、他にもたくさんの素晴らしい記事が公開されています。
今回の主題ではありませんのでスキップします。

また、認証で扱うモデル名はUserとします。

手を入れるソース

Deviseのユーザー登録/編集処理はregistrations_controller.rbで定義されています。

このControllerをオーバーライドしてカスタマイズしていきます。
まずは、オーバーライドするためのファイルを作成します。

$ rails g devise:controllers users
Running via Spring preloader in process 94640
      create  app/controllers/users/confirmations_controller.rb
      create  app/controllers/users/passwords_controller.rb
      create  app/controllers/users/registrations_controller.rb
      create  app/controllers/users/sessions_controller.rb
      create  app/controllers/users/unlocks_controller.rb
      create  app/controllers/users/omniauth_callbacks_controller.rb

今回編集するのはapp/controllers/users/registrations_controller.rbです。
中身を見てみると、以下のようになっています。

registrations_controller.rb
# frozen_string_literal: true

class Users::RegistrationsController < Devise::RegistrationsController
  # before_action :configure_sign_up_params, only: [:create]
  # before_action :configure_account_update_params, only: [:update]

  # GET /resource/sign_up
  # def new
  #   super
  # end

  # POST /resource
  # def create
  #   super
  # end

  # GET /resource/edit
  # def edit
  #   super
  # end

  # PUT /resource
  # def update
  #   super
  # end

  # DELETE /resource
  # def destroy
  #   super
  # end

  # GET /resource/cancel
  # Forces the session data which is usually expired after sign
  # in to be expired now. This is useful if the user wants to
  # cancel oauth signing in/up in the middle of the process,
  # removing all OAuth session data.
  # def cancel
  #   super
  # end

  # protected

  # If you have extra params to permit, append them to the sanitizer.
  # def configure_sign_up_params
  #   devise_parameter_sanitizer.permit(:sign_up, keys: [:attribute])
  # end

  # If you have extra params to permit, append them to the sanitizer.
  # def configure_account_update_params
  #   devise_parameter_sanitizer.permit(:account_update, keys: [:attribute])
  # end

  # The path used after sign up.
  # def after_sign_up_path_for(resource)
  #   super(resource)
  # end

  # The path used after sign up for inactive accounts.
  # def after_inactive_sign_up_path_for(resource)
  #   super(resource)
  # end
end

登録

まずは、サインイン済みでもユーザー登録ができるようにします。

Devise::RegistrationsControllerでは、以下が定義されているため、ユーザーがサインインしている状態で:new:createを行うことができません。

prepend_before_action :require_no_authentication, only: [:new, :create, :cancel]

そこで、オーバーライドして、サインインしていも:new:createにアクセスできるようにしてやります。

.diff
--- a/app/controllers/users/registrations_controller.rb
+++ b/app/controllers/users/registrations_controller.rb
@@ -1,6 +1,7 @@
 # frozen_string_literal: true
 
 class Users::RegistrationsController < Devise::RegistrationsController
+  prepend_before_action :require_no_authentication, only: [:cancel]
   # before_action :configure_sign_up_params, only: [:create]
   # before_action :configure_account_update_params, only: [:update]

次に、ユーザー登録後の自動サインインを回避します。
Devise::ReigistrationsControllerではsign_upという関数の中でsign_inという関数を呼んでいるだけなので、sign_upをオーバーライドします。

.diff
--- a/app/controllers/users/registrations_controller.rb
+++ b/app/controllers/users/registrations_controller.rb
@@ -38,7 +38,7 @@ class Users::RegistrationsController < Devise::RegistrationsController
-  # protected
+  protected
@@ -72,6 +72,10 @@ class Users::RegistrationsController < Devise::RegistrationsController
     devise_parameter_sanitizer.permit(:account_update, keys: [:name])
   end
 
+  def current_user_is_admin?
+    user_signed_in? && current_user.has_role?(:admin)
+  end
+
   # The path used after sign up.
   # def after_sign_up_path_for(resource)
   #   super(resource)
@@ -83,6 +87,8 @@ class Users::RegistrationsController < Devise::RegistrationsController
   # end
 
   def sign_up(resource_name, resource)
-    sign_in(resource_name, resource)
+    if !current_user_is_admin?
+      sign_in(resource_name, resource)
+    end
   end
 end

このままだとどのユーザーでも登録できるようになっているので、権限チェックを行います。

.diff
--- a/app/controllers/users/registrations_controller.rb
+++ b/app/controllers/users/registrations_controller.rb
@@ -3,6 +3,6 @@ class Users::RegistrationsController < Devise::RegistrationsController
   prepend_before_action :set_minimum_password_length, only: [:new, :edit]
+  before_action :creatable?, only: [:new, :create]
 
   # GET /resource/sign_up
   # def new
@@ -129,7 +130,14 @@ class Users::RegistrationsController < Devise::RegistrationsController
     resource.update_without_password(params)
   end
 
+  def creatable?
+    raise CanCan::AccessDenied unless user_signed_in?
+
+    if !current_user_is_admin?
+      raise CanCan::AccessDenied
+    end
+  end
+

編集

編集の場合は登録より複雑です。
まず、routeを定義するところからです。

.diff
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -157,7 +157,10 @@ Rails.application.routes.draw do
   devise_for :users, :controllers => {
     :registrations => 'users/registrations'
   }
-
+  devise_scope :user do
+    get 'users/:id/edit' => 'users/registrations#edit', as: :edit_other_user_registration
+    match 'users/:id', to: 'users/registrations#update', via: [:patch, :put], as: :other_user_registration
+  end
   resources :users, only: [:show, :index]
 
   resources :csv_import, only: :index

続いて、editメソッドを変更します。
Devise::ReigistrationsControllerでは以下の定義がされています。

prepend_before_action :authenticate_scope!, only: [:edit, :destroy]

:authenticate_scope!self.resourceにcurrent_userをぶち込んでしまいますので、:edit:updateは対象外にしておきます。

また、update関数をDevise::ReigistrationsControllerからコピーした後、adminユーザーの場合は現在のパスワードを入力せずとも編集できるようにコードを変更します。

.diff
--- a/app/controllers/users/registrations_controller.rb
+++ b/app/controllers/users/registrations_controller.rb
 class Users::RegistrationsController < Devise::RegistrationsController
   prepend_before_action :require_no_authentication, only: [:cancel]
+  prepend_before_action :authenticate_scope!, only: [:update, :destroy]
@@ -19,17 +19,30 @@ class Users::RegistrationsController < Devise::RegistrationsController
 
   # GET /resource/edit
   def edit
-    render :edit
+    if by_admin_user?(params)
+      self.resource = resource_class.to_adapter.get!(params[:id])
+    else
+      authenticate_scope!
+      super
+    end
   end
 
   # PUT /resource
-  # We need to use a copy of the resource because we don't want to change
-  # the current user in place.
   def update
-    self.resource = resource_class.to_adapter.get!(send(:"current_#{resource_name}").to_key)
+    if by_admin_user?(params)
+      self.resource = resource_class.to_adapter.get!(params[:id])
+    else
+      self.resource = resource_class.to_adapter.get!(send(:"current_#{resource_name}").to_key)
+    end
+
     prev_unconfirmed_email = resource.unconfirmed_email if resource.respond_to?(:unconfirmed_email)
 
-    resource_updated = update_resource(resource, account_update_params)
+    if by_admin_user?(params)
+      resource_updated = update_resource_without_password(resource, account_update_params)
+    else
+      resource_updated = update_resource(resource, account_update_params)
+    end
+
     yield resource if block_given?
     if resource_updated
       if is_flashing_format?
@@ -72,6 +85,10 @@ class Users::RegistrationsController < Devise::RegistrationsController
     devise_parameter_sanitizer.permit(:account_update, keys: [:name])
   end
 
+  def by_admin_user?(params)
+    params[:id].present? && current_user_is_admin?
+  end
+
   def current_user_is_admin?
     defined?(current_user) && current_user.present? && current_user.has_role?(:admin)
   end
@@ -91,4 +108,8 @@ class Users::RegistrationsController < Devise::RegistrationsController
       sign_in(resource_name, resource)
     end
   end
+  
+  def update_resource_without_password(resource, params)
+    resource.update_without_password(params)
+  end
 end

登録時と同じように権限チェックを行います。

.diff
--- a/app/controllers/users/registrations_controller.rb
+++ b/app/controllers/users/registrations_controller.rb
@@ -3,6 +3,6 @@ class Users::RegistrationsController < Devise::RegistrationsController
   prepend_before_action :set_minimum_password_length, only: [:new, :edit]
+  before_action :editable?, only: [:edit, :update]
@@ -129,7 +130,14 @@ class Users::RegistrationsController < Devise::RegistrationsController
     resource.update_without_password(params)
   end
 
-  def writable_check
+  def editable?
+    raise CanCan::AccessDenied unless user_signed_in?
+
+    if !current_user_is_admin?
+      raise CanCan::AccessDenied
+    end
+  end
+

管理者が他のユーザーのパスワードを変更できるようにします。
registrations_controller.rbで利用するようにしたresource.update_without_passwordはデフォルトではパスワードの更新ができないようになっています。

lib/devise/models/database_authenticatable.rb
def update_without_password(params, *options)
  params.delete(:password)
  params.delete(:password_confirmation)

  result = update_attributes(params, *options)
  clean_up_passwords
  result
end

これをUserモデルでオーバーライドします。
update_with_password関数を参考に実装しますが、パスワードチェックは行わないようにします。
ただし、この関数が管理者以外から呼び出されることがあってはいけません。

今回のように呼び出し元で管理者権限を持っているかチェックするようにしましょう。

.diff
--- a/app/models/user.rb
+++ b/app/models/user.rb
+  # deviseのupdate_without_passwordは本来パスワードは変更できない
+  # 管理者しかこの関数を呼ばない前提で、パスワードがなくてもパスワードを変更できるようにオーバーラ
イドする
+  # update_with_password関数を参考にする
+  def update_without_password(params, *options)
+    # current_password = params.delete(:current_password)
+
+    if params[:password].blank?
+      params.delete(:password)
+      params.delete(:password_confirmation) if params[:password_confirmation].blank?
+    end
+
+    # result = if valid_password?(current_password)
+    #   update_attributes(params, *options)
+    # else
+    #   self.assign_attributes(params, *options)
+    #   self.valid?
+    #   self.errors.add(:current_password, current_password.blank? ? :blank : :invalid)
+    #   false
+    # end
+
+    update_attributes(params, *options)
+
+    clean_up_passwords
+    result
+  end

成果物

Viewについてなど、いろいろと説明を省いてしまいましたが、Gistに最終的なソースをアップロードしました。
是非御覧ください。

44
60
6

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
44
60

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?