Railsチュートリアル10章まとめ。ユーザ編集と認可

Railsチュートリアル 10章を終えました。
10章ではユーザ情報更新画面を作成していきます。
Progate Railsコースでもユーザ情報画面は実装しますが、
ここでは(認証 (authentication) (・・・ユーザーが実行可能な操作を管理すること)も考慮します

railstutorial.jp

ユーザ更新

ユーザ更新画面

ユーザがユーザ情報を編集できるように、ユーザ情報編集画面を作っていきます。

ユーザ情報編集画面
ユーザ情報編集画面

users/id/editにアクセスした際に、ユーザ情報が表示されるように コントローラを設定していきます。

具体的にはユーザテーブルにidを検索条件に引っ張ってきます

  def edit
    @user = User.find(params[:id])
  end

更新

save changeボタンを押下することでユーザ更新できるようにします。

'edit.html.erb'からusers_controller.rbのupdateメソッドをアクセスし、
ユーザ更新成功、失敗時の処理を書いていきます。

app/view/users/edit.html.erb

        <div class="gravatar_edit">
            <%= gravatar_for @user %>
            <a href="http://gravatar.com/emails" target="_blank" rel="noopener">Change</a>
        </div>

app/controllers/users_controller.rb

  def update
    @user = User.find(params[:id])
    if @user.update_attributes(user_params)
      flash[:success] = "Profile updated"
      redirect_to @user
    else
      render 'edit'
    end
  end

ユーザ一覧と削除

次にユーザ一覧画面とその画面からユーザ削除する機能を作っていきます。

ページネーション

とはいえもしユーザ数が数百単位、数千単位になったときに一つの画面に全部表示されたら、
表示がかなり重たくなったり、使い勝手が悪くなったりします
ページネーションを使うことで数人単位で表示するようにします


ページネーションを自分で実装する場合、全ユーザー数を把握して、一ページ毎の表示数から何ページを作成するかを計算し、ページリンクを作成したり、最終ページが全てのユーザの表示数が表示されるとは限らないので・・・

ということを計算しないといけないです


幸いにもRailsにはそういうことを自動でやってくれるwill_paginate gembootstrap-will_paginateが開発されています
なのでこれを導入しながらページネーションを作成していきます

Gemfile

gem 'will_paginate'
gem 'bootstrap-will_paginate'


Gemfileに上記を書き込んだら、$ bundle installでinstallしていきます

管理者権限追加

ユーザ削除機能が全てのユーザに権限があるのは不都合です。
そこでUserテーブルにadminというカラムをbool型で作成します。

$rails generate migration add_admin_to_users admin:boolean

マイグレーションファイルに:boolean, default: falseを追加することによって
adminの初期値にfalseに設定することができます

class AddAdminToUsers < ActiveRecord::Migration[5.0]
  def change
    add_column :users, :admin, :boolean, default: false
  end
end

あとはマイグレを行います $ rails db:migrate

ユーザ一覧

次にユーザ一覧と削除を追加していきます

app/view/users/index.html.erb

<% provide(:title, 'All users') %>
<h1>All users</h1>

<%= will_paginate %>

<ul class="users">
    <%= render @users %>
</ul>

<%= will_paginate %>

ここでwill_paginateで下記のようなページネーションを追加することができます

ページネーション
ページネーション


また、<%= render @users %>は自動的にユーザーのコレクションを列挙し、それぞれのユーザーを_user.html.erbパーシャルで出力します。

ということで出力先のapp/view/users/_user.html.erbを書いていきます

<li>
    <%= gravatar_for user, size: 50 %>
    <%= link_to user.name, user %>
    <% if current_user.admin? && !current_user?(user) %>
    |
    <%= link_to "delete", user, method: :delete,
                                  data: { confirm: "You sure?" } %>
    <% end %>
</li>

ここで削除時のリンクを張っていますが、ログインしているユーザが管理者である場合のみ表示します(詳しくは後述します)


次に一覧と削除のコントローラですpaginateを使うことでページ毎のユーザを取得することができます

app/controllers/users_controller.rb

  def index
    @users = User.paginate(page: params[:page])
  end

  def destroy
    User.find(params[:id]).destroy
    flash[:success] = "User deleted"
    redirect_to users_url
  end

認可

これでユーザ編集とユーザ削除が行えますが、このままではユーザ編集についてはURLを直接叩くことでログインユーザ以外のユーザも編集可能になってしまいます

これを回避するために、ログインしているユーザのみに編集可能し、管理者権限のあるユーザのみにユーザ削除するようにします

ログイン必須にする

ログインしていないユーザーが保護されたページにアクセスしようとした際、ログインページに転送するようにします

before_actionを使用すると、処理の前に実行できるメソッドを定義できます

app/controllers/users_controller.rb

  before_action :logged_in_user, only: [:index, :edit, :update, :destroy]
    # ログイン済みユーザーかどうか確認
    def logged_in_user
      unless logged_in?
        store_location
        flash[:danger] = "Please log in."
        redirect_to login_url
      end
    end

app/helper/SessionsHelper.rb

  # アクセスしようとしたURLを覚えておく
  def store_location
    session[:forwarding_url] = request.original_url if request.get?
  end

編集、一覧、削除、更新を行う前にログイン済みかどうかを確認し、ログインしていない場合、ログイン画面に転送しメッセージを表示します

また、store_locationメソッドではリクエストが送られたURLを覚えておき、ログインしていないユーザーが編集ページにアクセスしようとしていたなら、ユーザーがログインした後にはその編集ページにリダイレクトされるようにします。

正しいユーザーを要求する

このままだと、ログインすれば他のユーザまで編集ができてしまいます
これを回避するために、編集作業の前に、ログインしたユーザが自ユーザかどうかを判定します

app/controllers/users_controller.rb

  before_action :correct_user,   only: [:edit, :update]

        # 正しいユーザーかどうか確認
    def correct_user
      @user = User.find(params[:id])
      redirect_to(root_url) unless current_user?(@user)
    end

app/helper/SessionsHelper.rb

  # 与えられたユーザーがログイン済みユーザーであればtrueを返す
  def current_user?(user)
    user == current_user
  end

これでログイン済みかつ正しいユーザーのみ編集作業や更新作業ができます。

削除時の制限

削除時にもコマンドラインからDELETEリクエストを送ることで全ユーザを削除されてしまうことができるようです

ある程度の腕前を持つ攻撃者なら、コマンドラインでDELETEリクエストを直接発行するという方法でサイトの全ユーザーを削除してしまうことができるでしょう。サイトを正しく防衛するには、destroyアクションにもアクセス制御を行う必要があります。



これを保護するために、削除前に削除者が管理者かどうかを調べます。

  before_action :admin_user,     only: :destroy
    # 管理者かどうか確認
    def admin_user
      redirect_to(root_url) unless current_user.admin?
    end

管理者権限を更新制限

ユーザモデルに管理者属性を追加しましたが
ユーザに管理権限を意図せず勝手に追加することもPATCHコマンドから可能になってしまいます

patch /users/17?admin=1

上記コマンドでid=17のユーザが勝手にadmin権限が付与されますので、
編集してもよい安全な属性だけを更新することにします

app/controllers/users_controller.rb

  def create
    @user = User.new(user_params)
・・・
end

  def update
    @user = User.find(params[:id])
    if @user.update_attributes(user_params)
・・・
end

    def user_params
      params.require(:user).permit(:name, :email, :password,
                                   :password_confirmation)
    end

上記user_paramsメソッドで、管理者以外の属性を編集可能にしています。これにより、任意のユーザーが自分自身にアプリケーションの管理者権限を与えることを防止できます。

最後に

10章のユーザ一覧、編集、削除はprogateのRailsコースでもありましたが、認可については踏み込んだ内容となりました

またそれ以外にも基本的に使いそうな機能も記載がありましたので、何回か見る章になりそうですね

参照

下記のblog様は本当に文章がうまいため、記事にする際にもかなり参考にさせていただいております。
ありがとうございます

www.masalog.site