セキュリティ系の勉強・その他開発メモとか雑談. Twitter, ブログカテゴリ一覧
本ブログはあくまでセキュリティに関する情報共有の一環として作成したものであり,公開されているシステム等に許可なく実行するなど、違法な行為を助長するものではありません.

ruby チュートリアル10章 メモ

form_for(@user)の判定

form_forを利用すると自動で入力フォームとそのsubmit先の設定等を設定し表示できるようにしてくれる。一般にcreateアクションとeditアクション用のフォームは同じform_for(@user)で生成される。このアクションの違いは@user内のnew_record?論理値メソッドを使用して判定している。その結果trueならPOSTfalseならPATCHをリクエストに使用する。

$ rails console
>> User.new.new_record?
=> true
>> User.first.new_record?
=> false

認証と認可

認証(authentication)はユーザを識別すること、認可(authorization)はそのユーザが実行可能な操作を管理すること。

unless

if文がtrueの際の処理を書くなら、unless文はfalseの際の処理を記述する。

before_action

すでに定義されているアクション実行前に作動する。登録してあるユーザのみ実行させるなどの認証をここで指定できる。only:を使用すると指定したアクションでのみ、before_actionが作動するようになる。

リダイレクトの発生タイミング

チュートリアルでは下のようなコードが出る。これは別ページでセッションに移動先を保持させておいて、このメソッドを使用してリダイレクトを発生させる。(引数は無視) この場合、リダイレクトの発生タイミングは最終行のコードが実行されてからになる為、リダイレクトも発生するがセッション削除も行われる。リダイレクトの発生タイミングは最終行のコードの実行後か明示的にreturnを呼ばれた時になる。

#app/helpers/sessions_helper.rb
  def redirect_back_or(default)
    redirect_to(session[:forwarding_url] || default)
    session.delete(:forwarding_url)
  end


request.get?

送られてきたリクエストがGETかどうか判定できる。チュートリアルでは下のように使用して、GETリクエストの時だけ、URLをセッションに退避させておき、必要になった時にリダイレクトで飛ばせるようにしている。UPDATEPATCH等が飛んできた時にそれを退避してしまわないよう、GETのみに絞っている。

#app/helpers/sessions_helper.rb
session[:forwarding_url] = request.original_url if request.get?


ページネーション(pagination)

ユーザ一覧を1ページにずらっと並べると見にくいので、それを10人ずつ表示したりすること。チュートリアルではgem 'will_paginate'を使用する。
導入後、下のように確認が可能。

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

デフォルトでは30人ずつ取り出され、pageはそのインデックス番号を指定。nilが届いた場合は、1ページ目が自動で指定される。


パーシャルの利用

下のコードはhtmlタグをパーシャルを利用して上手く表示する例。

#app/views/users/index.html.erb
#before
<ul class="users">
  <% @users.each do |user| %>
    <li>
  <%= gravatar_for user, size: 50 %>
  <%= link_to user.name, user %>
</li>
  <% end %>
</ul>

#after
<ul class="users">
  <% @users.each do |user| %>
    <%= render user %>
  <% end %>
</ul>

肝心のパーシャルの部分は、_user.html.erbファイルを自動で探しに行くのでそこに定義してやる。

<li>
  <%= gravatar_for user, size: 50 %>
  <%= link_to user.name, user %>
</li>


この時、下のようにすれば更にeachの部分も省略可能になる。

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

RailsはこれをUserオブジェクトのリストであると認識、それぞれをパーシャルとして書き出してくれる。すごい。

【Ruby】チュートリアルのform_forをform_withで書き換え (おまけ:capybaraでのテスト)

Ruby5.1前まではフォームを生成してくれるメソッドとして、form_forform_tagがあった。特にRubyチュートリアルでは2018/11/18現在、form_forを利用した方法を紹介しています。が、5.1以降はform_with推奨とのことなので、書き換えようと思いました。

form_with

APIドキュメントの翻訳版はこちらで読む事ができます。

techracho.bpsinc.jp


基準となるモデルがある場合

チュートリアルでは7.2.1で登場してます。Userモデルを作るフォームです。

<%= form_for(@user) do |f| %>
      <%= f.label :name %>
      <%= f.text_field :name %>

      <%= f.label :email %>
      <%= f.email_field :email %>

      <%= f.label :password %>
      <%= f.password_field :password %>

      <%= f.label :password_confirmation, "Confirmation" %>
      <%= f.password_field :password_confirmation %>

      <%= f.submit "Create my account", class: "btn btn-primary" %>
    <% end %>


form_withを使用すると下のようにかけます。基本的に変わりませんが、model:でUserモデルを指定してあげます。チュートリアルの方を見ればわかりますが、コントローラの方で@user = User.newやってます。

<%=form_with model: @user do |f|%>
  <%= render 'shared/error_messages', object: @user%>

  <%=f.label :name%>
  <%=f.text_field :name,class:'form-control'%>

  <%=f.label :email%>
  <%= f.email_field :email,class:'form-control'%>

  <%=f.label :password%>
  <%= f.password_field :password,class:'form-control'%>

  <%=f.label :password_confirmation %>
  <%=f.password_field :password_confirmation,class:'form-control'%>

  <%=f.submit yield(:button_text),class:'btn btn-primary'%>
<%end%>


基準となるモデルがない場合

一方チュートリアル8.1.2では、上のような誰のフォームであるかをモデルで表わせず、sessionという言葉でまとめています。

<%= form_for(:session, url: login_path) do |f| %>

      <%= f.label :email %>
      <%= f.email_field :email, class: 'form-control' %>

      <%= f.label :password %>
      <%= f.password_field :password, class: 'form-control' %>

      <%= f.submit "Log in", class: "btn btn-primary" %>
    <% end %>


これは下のようにして書き換えられます。

    <%=form_with scope: :session, url:login_path do |f|%>

      <%=f.label :email%>
      <%=f.email_field :email, class:'form-control', id:'email'%>

      <%=f.label :password%>
      <%=f.password_field :password, class: 'form-control',id: 'password'%>

      <%=f.submit "Log in", class: "btn btn-primary"%>
    <%end%>


capybaraでのテスト

form_forでは、生成されるinputタグにid属性が付与されていました。一方form_withではid属性はオプションでつくようになっています。上の書き換えの例では、id属性にemailpasswordを指定しています。これはcapybaraでfill_inメソッドを使用してテストするためです。(他にもテスト方法はありますが、、)

#form_for
  <input class="form-control" id="session_email" name="session[email]" type="email" />
#form_with(id指定なし)
<input class="form-control" type="email" name="session[email]">
#form_with(id指定あり)
<input class="form-control" id="email" type="email" name="session[email]">


fill_inメソッドはidを参照するようで、id属性がないとエラーが起きます。変更前は以下のようにテストコードを書いていました。

#capybaraを使用しフォームに値を入れる
fill_in "Email",        with: "入力する値"

実はこれidがemailだと反応しません。_emailEmailが含まれていれば反応します。 なので、どっちも小文字でemailを指定するのが一番まるくおさまる気がします。もしくはid属性にEmailを指定してやればテストコードはそのままで大丈夫かもしれません。

ruby チュートリアル9章 メモ

SecureRandom.urlsafe_base64

Ruby標準ライブラリのメソッド。長さ22文字の文字列を生成してくれる。base64文字列なので64の22乘通りあるため、衝突の可能性も限りなく低い。

アクセサ

インスタンス変数のゲッターとセッターのこと。rubyのクラスのインスタンス変数にはアクセサが無いとアクセスすることができない。attr_accessorを使用することで自分で書くことなく利用できるようになる。

model(class)内でのselfの意味

チュートリアルでは、変数にselfをつける事でそのインスタンス内で共有が可能な変数を生成している。selfがない場合、ローカル変数になるとの記述があったので、おそらく定義したメソッドのスコープ内でのみ使用可能なものになってしまうはず。 ちなみにメソッドはクラスメソッド内でselfを使用するとクラスを指し、インスタンスメソッド内でselfはそのインスタンスになる。クラスメソッドはmodelクラスのレコードの更新や検索をする用に実装する。

class Article < ActiveRecord::Base
  def hoge       #インスタンスメソッド
  end

  def self.hoge  #クラスメソッド
  end

  def pdf       #インスタンスメソッド
    self.hoge        #インスタンスメソッドのhogeが呼ばれる
    hoge             #インスタンスメソッドのhogeが呼ばれる
    self.class.hoge  #こうするとクラスメソッドのhogeを呼べる
  end

  def self.pdf  #クラスメソッド
    self.hoge        #クラスメソッドのhogeが呼ばれる
    hoge             #クラスメソッドのhogeが呼ばれる
  end
end
# https://qiita.com/suzuki_koya/items/1553c405beeb73f83bbc


cookiesメソッド

1つのvalueとオプションのexpires(有効期限)から成っている。下のようにpermanentを使用すると有効期限が20年に設定される(よく20年設定が使われていた為)

cookies.permanent[:remember_token] = remember_token


それに伴いユーザIDの保存方法も変更する必要がある。signedを使用すると署名付きcookieを使うこと宣言し、permanentではcookieと同じ方法で永続化している。

#変更前
cookies[:user_id] = user.id

#変更後
cookies.permanent.signed[:user_id] = user.id

ユーザIDを取得する際はcookies.signed[:user_id]とする。

remember_me機能

remember_me機能をチェックの有無でクッキーがブラウザに保存されるかが決まる。これだけでこの機能が実現できているのは、セッションにはチェックに関わらず、変数にユーザ情報を保持させているから。一周回って仕組みが把握しにくくなってきた。

ログアウト時のcookieの削除

ログアウト処理にてcookieの値が正常に削除されたかをテストするには下のようにする。responseの中身のcookiesを覗かないと値が削除されていないので注意。

expect(response.cookies['user_id'].nil?).to eq false


インスタンス変数のテスト

インスタンス変数へはassignsを利用することでアクセスが可能になる。チュートリアルではこれを用いることで今までテストされていないかったインスタンス変数をテストした。

#@user内のremember_tokenへアクセスしている
expect(response.cookies['remember_token']).to eq assigns(:user).remember_token

ruby チュートリアル8章 メモ

セッションとクッキー

混同しがち(自分だけかも、)HTTPはステートレスなプロトコルなので、セッションのような物を実装するには、状態を保存するものが別に必要なる。それの一つがクッキー。クッキーはユーザのブラウザ内に保存される小さなテキスト。一方セッションはそもそも何かを継続的に行なっているものを指すらしく、接続し続けることもその中の一つと言う解釈でしょうか。

ログインページ

チュートリアルではこれから生成するセッションのコントローラがそのままログインページ系を司どるように作るそうです。言葉にするとわかりにくいので下にそれっぽいルーティングを示します。loginと言うコントローラを作るのではなく、セッションを開始終了のためのページがログイン/ログアウトってイメージですね。なお、セッションコントローラの処理はこれだけでよいので、generateで無駄なアクションを作ったり、resourcesで無駄にルーティングを作るのは避けます。

#config/route.rb
  get    '/login',   to: 'sessions#new'
  post   '/login',   to: 'sessions#create'
  delete '/logout',  to: 'sessions#destroy'


renderメソッドとアクション

下のようにflashに値を格納した後にrenderを呼ぶと、flashは予期しない挙動をとる。と言うのも、rendernewを呼ぶとそれはアクションとは認められないため必然的にflashの生存時間が1アクション分伸びてしまう。

#app/controllers/sessions_controller.rb
 def create
    if ~~
      # ユーザーログイン後にユーザー情報のページにリダイレクトする
    else
      flash[:danger] = 'Invalid email/password combination'
      render 'new'
    end
  end


flash.nowメソッドを利用することでこれを回避することができる。

 flash.now[:danger] = 'Invalid email/password combination'


findメソッドとfind_byメソッド

User.find(session[:user_id])でユーザ検索を行った場合、ユーザIDが存在していない状況(まだログインしていない、session[:user_id]の値がnilになる状態)だと、例外が発生する。一方User.find_by(id: session[:user_id])は、例外を発生せずnilを返す。

fixtureとFactorybot

チュートリアルではMinitest利用なのでfixtureを利用しているが、RSpecを使用していならFactorybotでカバーできる。個人的には下のようにしてチュートリアルのテストコードを実装しています。

#spec/controllers/user_controller_spec.rb
let(:user){build(:user)}
    let(:post_create){
      post( :create, params: {
        user: {
          name:   user.name,
          email:  user.email,
          password: user.password,
          password_confirmation: user.password_confirmation 
        }
    })}

    it 'ユーザが新規に生成される' do
      expect{post_create}.to change{User.count}.by(1)
    end

t-pot 観察 #6 明け方からアクセス増加

こんにちは。目立つログが残っていたので、メモしておきます。まず、今月のここまでのハニポ全体の記録をみてみます。
f:id:thinline196:20181111140554p:plain

今の所今月一のアクセス数です。では中身を見てみます。上のグラフと形が似てしまっていますが、今日(14時まで)の記録です。
f:id:thinline196:20181111140714p:plain

Cowrieが検知してますが、どうやら一度にあのアクセス量を受けていたわけでは無いようです。では国別とポート別に分けてみます。
f:id:thinline196:20181111140903p:plain
f:id:thinline196:20181111141018p:plain

詳細

中国からは211.143.198[.]237から16731程度のアクセス、アメリカからは1162.252.106[.]245から8300程度のアクセスで、どちらもsshへ向いてましたが時間帯は被っておらず。ロシアからはhttpへのアクセスですがこれもピークは他のアクセスとは被ってはおらず128.0.31[.]26から2000程度のアクセスでした。現地時間的にはアメリカとロシアからは夕方頃、中国は明け方頃ですね 。


3つの国からのアクセスは特に珍しくもなかったのですが、時間帯が近いのは珍しいと思います。(少なくとも私の環境だと) ここ最近だとMiraiが怪しいのかと思いますが、httpは確かmiraiの守備範囲ではなかった気がするので(IoTでは無い)、仮にmiraiのbotnetが動き出していたのなら、ロシアのは別攻撃ですね。そうすると中国とアメリカからのピーク時間が微妙に近いのもなんとなく腑に落ちるかもしれない?