17. admin 権限の設定

前回までにデザインのないテストは受入テストで実施できたが、実際のデザインはブラウザで確認する必要がある。しかしながら LDAP 接続できない場所では、ブラウザで確認することはできない。そこで、他のユーザになりすませるローカルログインする admin でアクセスする。ここでは、kyoumu ユーザ権限を付けるテストを行う。

feature の記述

以下のように spec/acceptance/features/admin_privilege.feature を記述する。

# language: ja
機能: admin ユーザの権限取得
  シナリオ: admin のトップページから教務室の権限を得る。その後年度変更に飛び、権限を確認する。
    前提 管理者になる
    かつ 教務室アカウントを作成する
    ならば 管理者トップページと表示されていること
    もし "[教務室]"リンクをクリックする
    かつ "[年度変更]"リンクをクリックする
    ならば 年度一覧と表示されていること

まず、「管理者になる」step と「教務室アカウントを作成する」 step を spec/acceptance/steps/login_steps.rb に追加する。

step %(管理者になる) do
  login_as(FactoryGirl.create(:admin), scope: :admin)
  visit root_path
end

step %(:jnameアカウントを作成する) do |jname|
  FactoryGirl.create(USERS.assoc(jname).last)
end

この結果 Guard は以下のようになる。Welcome#index に留まっており、管理者ページに飛んでいない。

     Failure/Error: ならば 管理者トップページと表示されていること
       expected to find text "管理者トップページ" in "Welcome#index Find me in app/views/welcome/index.html.haml ログアウト"

spec/controllers/welcome_controller_spec.rb にテストを追加する。

    context "for :admin" do
      it do
        login_admin_as :admin
        get :index
        expect(response).to redirect_to(admins_path)
      end
    end

「login_admin_as」がないと言われるので、spec/rails_helper.rb にメソッドを追加する。

def login_admin_as(fg_key)
  admin = FactoryGirl.create(fg_key)
  sign_in :admin, admin
  admin
end 

今度は admins_path がないと言われるので、controller を作成する。

$ bin/rails g controller admins
      create  app/controllers/admins_controller.rb
      invoke  haml
      create    app/views/admins
      invoke  rspec
      create    spec/controllers/admins_controller_spec.rb
      invoke  assets
      invoke    coffee
      invoke    scss

また、config/routes.rb に admins の routing を追加する。ここではトップページ用の index と権限付与用の update のみを設定する。

  resources :admins, only: [ :index, :update ]

この結果、以下のようなエラーとなる。

  2) WelcomeController GET index for :admin should redirect to "/admins"
     Failure/Error: expect(response).to redirect_to(admins_path)
       Expected response to be a <redirect>, but was <200>

app/controllers/welcome_controller.rb を修正して、リダイレクトするように変更する。

  def index
    if user_signed_in?
      current_user.set_privileges(user_session)
      redirect_to teacher_kyoumus_path if is_kyoumu?
    elsif admin_signed_in?
      redirect_to admins_path
    end          
  end

controller のテストは通過したが、feature のテストが残っている。

     Failure/Error: 前提 管理者になる
     AbstractController::ActionNotFound:
       The action 'index' could not be found for AdminsController

まず、spec/controllers/admins_controller_spec.rb を記述する。admin でログインしていない場合には、root_path にリダイレクトすることを確認する。

require 'rails_helper'

RSpec.describe AdminsController, :type => :controller do

  describe "GET index" do
    it "returns http success when admin is logining" do
      login_admin_as :admin
      get :index     
      expect(response).to have_http_status(:success)
    end

    it "redirect_to root_path when admin is not logining" do
      get :index
      expect(response).to redirect_to(root_path)
    end
  end
end

このテストを通過させるために、app/controllers/admins_controller.rb に index を作成する。

  before_action :admin_login_required

  def index
  end

admin_login_required がないと言われるので、app/controllers/application_controller.rb に記述する。ここでは、権限がないときにメッセージを出して root_path にリダイレクトする redirect_to_root_path_with_message を作成し、これを呼び出すことにする。

  # @param [Boolean] flag 権限がある時に true
  # @return [Boolean] flag が true の場合に true を返す
  # @note flag が false の時は root_path にリダイレクト
  def redirect_to_root_path_with_message(flag)
    unless flag
      flash[:alert] = 'このページを表示する権限がありません。'
      redirect_to root_path
    end
    true
  end

  # @note admin でログインしていない場合には root_path にリダイレクト
  # @return [Boolean] flag が true の場合に true を返す
  def admin_login_required
    redirect_to_root_path_with_message(admin_signed_in?)
  end

さらに、app/views/admins/index.html.haml を作成する。教員用と共用のどちらのサーバも admin はログインできるので、サーバごとに選択できる権限を区別する。特殊アカウントと職員は教職員のみ、学生は共用のみ、教員はどちらでもログインできるようにする予定であるので、サーバごとに表示を切り替えている。

- @is_kyouin_server = Rails.application.config.IsKyouinServer
%h1 管理者トップページ (#{ @is_kyouin_server ? "教職員" : “共用” }サーバ)
%p 権限の選択
- if @is_kyouin_server
  %h2 特殊アカウント
  %table
    %tr
      %th{colspan: User::SpecialAccounts.count} 
    %tr
      - User::SpecialAccounts.each do |account, str|
        %td= link_to lh(str), admin_path(current_admin, privilege: account), method: :put
- else
  %h2 学生
-if @is_kyouin_server
%h2 教員  
-if @is_kyouin_server
  %h2 職員

教員用サーバかどうかは Rails.application.config.IsKyouinServer で判断する。これは、config/application.rb で設定し、サーバ起動時に確定する。旧システムと同様 RAILS_ROOT に .kyouin_server というファイルがあった場合には、教職員専用サーバであるとする。

    config.IsKyouinServer = File.exists?('.kyouin_server')

.kyouin_server はリポジトリに入れないので、.gitignore に登録しておく。また、テストのために .kyouin_server を作っておく。

touch .kyouin_server

特殊アカウントは User モデルの定数として app/models/user.rb に用意しておく(後で増やす予定)。

  # 特殊アカウント一覧
  SpecialAccounts = [
    %w( skyoumu 教務室 ),
  ]

リンク文字列に [] を付与する lh メソッドを app/helpers/applicatoin_helper.rb に追記する。

module ApplicationHelper
  # リンク文字列を作成
  def lh(str)
    "[#{str}]"
  end
end

Guard は以下のようになる。

     Failure/Error: もし "[教務室]"リンクをクリックする
     AbstractController::ActionNotFound:
       The action 'update' could not be found for AdminsController

実装を記述する前に spec/controllers/admins_controller_spec.rb にテストを追加する。update では、権限になりたいユーザの name を admin_session[:mode] にセットする。

  describe "PUT update" do
    context "for user skyoumu" do
      before do
        admin = login_admin_as :admin
        skyoumu = FactoryGirl.create(:user_skyoumu)
        put :update, id: admin.id, privilege: 'skyoumu'
      end

      it("controller.admin_session['user'] should eq skyoumu") { expect(controller.admin_session['user']).to eq('skyoumu') }
      it { expect(response).to redirect_to(root_path) }                                          
    end
  end

app/controllers/admins_controller.rb の実装は以下のようになる。

  def update
    admin_session['user'] = params[:privilege]
    redirect_to root_path
  end

admin_session[:user] に値がセットされているときは、admins_path ではなく、権限に従ったリダイレクトされるように変更する。実装をする前に spec/controllers/welcome_controller_spec.rb を修正する。’user’ に正しくない値(‘unknown’)が入っている場合のテストも忘れないようにしておく。

    context "for :admin" do
      context "without admin_session['user']" do
        it do
          login_admin_as :admin
          get :index
          expect(response).to redirect_to(admins_path)
        end
      end

      context "admin_session['user'] is 'unknown'" do
        it do
          login_admin_as :admin
          controller.admin_session['user'] = 'unknown'
          get :index
          expect(response).to redirect_to(admins_path)
        end
      end

      context "admin_session['user'] is 'skyoumu'" do
        it do
          login_admin_as :admin
          controller.admin_session['user'] = 'skyoumu'
          user = FactoryGirl.create(:user_skyoumu)
          get :index
          expect(response).to redirect_to(teacher_kyoumus_path)
        end
      end
    end

app/controllers/welcome_controller.rb は以下のようになる。当初、admin_session.delete('user') を忘れ、正しくないユーザの場合に、無限ループになっていてハマった。

  def index
    if user_signed_in?
      current_user.set_privileges(user_session)
      redirect_to teacher_kyoumus_path if is_kyoumu?
    elsif admin_signed_in?
      p admin_session
      if name = admin_session['user']
        user = User.name_value_is(name).first
        if user # find user
          user.set_privileges(admin_session)
          if is_kyoumu?
            redirect_to teacher_kyoumus_path
          else
            admin_session.delete('user')
            redirect_to admins_path
          end
        else # Can't find user
          admin_session.delete('user')
          redirect_to admins_path
        end
      else
        redirect_to admins_path
      end
    end
  end

今日はここまで。

written by iHatenaSync