kakuyonの日記

Webアプリやセキュリティに関する事を書いていけたらと考えています。

自己紹介

概要

こんにちは!kakuyonです。
おそらく今流行りのWebエンジニアとして生きる事を夢見てる人です。

これからはWebに関わる技術を色々と勉強していこうと考えてます。

連絡先

Twitterか、以下のメールアドレスへお願いします。
laplace128@gmail.com


こんな感じです。何か感じたことをどんどん書いていこうと考えていますので、よろしくお願いします!

HerokuがRailsのアセットパイプラインを読み取ってくれない問題

経緯

Rails Tutorialの5章終了! Herokuへデプロイ!
最後にいざ動作確認、といったところで、
f:id:kakuyon:20180715022725p:plain
何故かエラー…
rails testは問題無く通過できたのに…


修正作業へ

とりあえずログを見ろと言われてるので、heroku logsで確認

2018-07-14T17:21:49.698143+00:00 app[web.1]: F, [2018-07-14T17:21:49.698072 #4] FATAL -- : [7859d62e-523d-48d0-97fa-4ea4ce8bac18]
2018-07-14T17:21:49.698250+00:00 app[web.1]: F, [2018-07-14T17:21:49.698175 #4] FATAL -- : [7859d62e-523d-48d0-97fa-4ea4ce8bac18] ActionView::Template::Error (The asset "rails.png" is not present in the asset pipeline.):
2018-07-14T17:21:49.698483+00:00 app[web.1]: F, [2018-07-14T17:21:49.698416 #4] FATAL -- : [7859d62e-523d-48d0-97fa-4ea4ce8bac18] 10: <%= link_to "Sign up now!", signup_path, class: "btn btn-lg btn-primary" %>

一部抜粋

よく見てみると、not present in the asset pipelineとか言われてますね。
この時は、なんとなく画像が認識されていないんだろうなーくらいに捉えてました。

画像を利用していないページにアクセスすると、表示はされるものの、
スタイルシートが反映されていない事に気づき、ここでようやくアセットパイプラインが認識されていないと判断。


対策法を調べてみると、デプロイする直前に

rake assets:precompile RAILS_ENV=production

↑これをコンソール上で実行することで、アセットパイプラインが利用可能になるとか。

後は、config/environments/production.rb内の
config.assets.compileをfalseからtrueに変更、これでアセットパイプラインを利用するようになる


この後デプロイ、アクセスすると正常に色々読み込んでくれるようになりました。
これ、初めてこういったフレームワークを使う人なら絶対悩まされると思うんですけど、
Rails Tutorialでは特に触れられていないんですよね…
途中でなにか設定を間違えたのでしょうか?

とりあえず以上です。

printとputsとpの違いとは (Ruby)

メモ書きです。
printとputsとpの違いに関して書いておきます。

それぞれの特徴

print、puts、p
これらは、引数として渡されたオブジェクトを標準出力するメソッドです。
出力の仕方に少しだけ違いがあります。

printメソッド

オブジェクトをそのまま出力させます。

print "foo"
print "123"
=> foo123
putsメソッド

オブジェクトを出力&最後に改行を加えます。

puts "foo"
puts "123"
=> foo
=> 123
pメソッド

オブジェクトを分かりやすい形で表示するメソッドです。
数値はそのまま表示、文字列はダブルクォートで挟んだ状態で表示されます。
putsメソッドと同様、最後に改行を加えます。

p "foo"
p "123"
=> "foo"
=> "123"

putsとpの違い

※printは、改行を行わないputsと考えればいいだけなのでスルー

pメソッドは文字列の出力方法が異なります。
つまり、出力したものが文字列なのかどうかを判別できます。

# pメソッドなら文字列の識別が可能
p 1
p "1"
=> 1
=> "1"

# putsメソッドは無理
puts 1
puts "1"
=> 1
=> 1

デバッグに活躍するやつですね。
あと、pメソッドは「\n」のようなエスケープ文字もそのまま出力します。

puts "foo\nbar"
=> foo
=> bar

p "foo\nbar"
=> "foo\nbar"


いじょう

「秘密の質問」の危険性と対策について考えてみる

パスワードを初期化する時などに、本人確認の代わりとして用いられる「秘密の質問」は相当な曲者だと思ってます。
最近、アカウント登録を行うシステムを作っていた際に、この仕組みについて感じたことを書いていきます。

秘密の質問のダメなところ

例えば「登録したパスワードの内容を忘れてしまった!」という人がいた場合に、
秘密の質問に答えられたら再設定できる、というシステムがあるとする。
そうすると、こういった問題点が出てくることは無いでしょうか?

・質問の答えが予測されやすい

もしここで質問を設定する際に、

「初めて訪れた海外の国は?」

という質問を再設定に利用するとしたらどうでしょう?

例え全く知らない人間のことでも、ある程度答えに見当がつきませんか?
とりあえず韓国とか台湾とか入力したらあっさり通るかもしれない、
というか実際にこんな感じでアカウントを弄られてた人も身近に居ました。

とにかく、質問に対する答えがある程度予測できてしまう点は、大きなデメリットだと思います。
「母親の旧姓」や「ペットの名前」も、文字の種類や文字数は大体把握できてしまいます。

・質問の答えは忘れやすい

秘密の質問は通常のパスワードと違って、毎回入力するわけではありません。
大抵はパスワードを忘れた時のように、本人確認が困難となった場合にのみ、使われるものです。

そもそも秘密の質問の内容自体、滅多なことでは見ないでしょう。
ついでに全角や半角の区別も完璧に覚えておく必要があります。
パスワードを忘れた人が、果たしてそんなものを覚えていられるでしょうか?

ならばどう対策するべきなのか

よく使われている手段として、次のものがあります。

・見当もつかない言葉を答えに設定する

確かに、これなら見当がつけられない分、危険性は減ると思います。
実際、設定するならこうするべきでしょう。
ただ、これによって更に質問の答えを忘れる可能性が上がると思います。
なにより、こうなるともはや秘密の質問が質問として役割を果たしていません。パスワード初期化用のパスワードを登録してるようなものです。

・いっそのこと初めから第二のパスワードとして設定する

中途半端に脆弱な、忘れやすい言葉を設定するくらいなら、いっそのこと本格的に強力なパスワードを設定するのもいいと思います。
これなら他人に秘密の質問を突破されることはまず無くなるでしょうが、結局答えを忘れる危険は残ったままです。


簡単な答えを設定すると他人にバレやすく、
難しい答えだと自分が忘れやすい…

うまいこと、自分が見たらすぐ答えを思い出せて、他人からは見られても分からないような、そんな質問に設定できればいいのですが…

☆そもそもこんな方法で本人確認を行わない

思考放棄してるようなものかもしれませんが、実際この仕組みを利用していくのは結構無理があるのではないかと思います。

現在はメールアドレスによる認証が一般的ですが、今後はメール認証 + 生体認証の2要素認証が主流になっていくのではないかなぁと思っています。
もしかしたらメール認証は無くなるかもしれませんが、所持情報 + 生体情報の組み合わせでの認証は確実に利用されるでしょう。

まとめ

利用者側は、他人に予測されにくい + 自分が忘れることのないワードをなるべく設定するように心掛けておき、
提供者側は、この脆弱な認証方式を少しでも早く淘汰するのが最適解ではないでしょうか。

Pythonで配列のコピー作る時に用心すべきこと

自身への戒めとして書いときます



Pythonで変数にリストを代入しようとすると、
変数にはリストへの参照が渡されるようになっています。

>>> hoge = ['a', 'b', 'c']
>>> fuga = hoge
>>> fuga[1] = 'change'
>>> print(hoge)
['a', 'change', 'c']


片方だけの値を変えたい場合、リストへの参照ではなく、
リストのコピーを渡す必要があります。いわゆる値渡しです。

>>> hoge = ['a', 'b', 'c']
>>> fuga = list(hoge)  # あるいはhoge[:]
>>> fuga[1] = 'change'
>>> print(hoge)
['a', 'b', 'c']


list()の引数にコピーしたいリストを与える、
もしくはスライスを利用するのもok



本題

しかし、二次元配列だとこうはいきません。いけませんでした

>>> hoge = [['a', 'b', 'c'], ['d', 'e', 'f']]
>>> fuga = list(hoge)
>>> fuga[0][1] = 'change'
>>> print(hoge)
[['a', 'change', 'c'], ['d', 'e', 'f']]


list()を使おうが使うまいがこうなります。
hoge[:][:]とかも無意味です。
一次元目まではコピーしてくれますが、
二次元目以降は参照渡しになるらしいです(この数え方合ってる?)



対策

copyモジュールというものを使用します。
このモジュールに存在するdeepcopy()というメソッドで解決できます

>>> import copy
>>> hoge = [['a', 'b', 'c'], ['d', 'e', 'f']]
>>> fuga = copy.deepcopy(hoge)
>>> fuga[0][1] = 'change'
>>> print(hoge)
[['a', 'b', 'c'], ['d', 'e', 'f']]


モジュールに頼りたくないなら、内包表記でしょうか

>>> hoge = [['a', 'b', 'c'], ['d', 'e', 'f']]
>>> fuga = [x[:] for x in hoge]
>>> fuga[0][1] = 'change'
>>> print(hoge)
[['a', 'b', 'c'], ['d', 'e', 'f']]


二次元配列でしか試していませんが、これは多次元配列であれば
同じことが言えると思います。



Pythonでは基本参照渡し、
二次元配列の値渡しはdeepcopy()を使う
この事を忘れると数時間が水の泡になったりするので気を付けましょう




終わり

CODE QUESTの「毒沼ノ試練」を一応解いてみた

Pythonの学習をある程度進めてきたので、練習として何かやってみようと思い、
半年ほど前に話題になっていた、CODE QUESTに挑戦してみました。

目次

CODE QUESTとは

GeekOutさんが提供されている、エンジニア向けのゲーム(?)です。

最近は、第二弾としてクイズ形式で遊べるものも用意されたみたいです。
以前、このゲームに挑戦した時は解法が分からず惨敗したので、
リベンジがてら「毒沼ノ試練」に挑戦してみました。

毒沼ノ試練

f:id:kakuyon:20180619021901p:plain

人力で10分くらい試してみましたが、ギリギリ行けそうで行けなかったので、
とりあえず、どこかで見かけてうろ覚えだったアルゴリズムを用いて解くことにしました。

とりあえず書いたコード
from copy import deepcopy

data = [[-1,1,1,-1,-1,1,1,-1,-1,1],
        [1,"S",1,"L",-1,-1,-1,-1,-1,-1],
        [-1,-1,-1,1,1,-1,1,-1,1,1],
        [1,"L",-1,-1,1,-1,1,-1,1,-1],
        [-1,1,1,-1,-1,-1,-1,"L",1,-1],
        [1,-1,1,-1,-1,1,-1,1,-1,1],
        [-1,"L",-1,-1,-1,1,-1,-1,-1,1],
        [-1,1,-1,1,1,-1,1,-1,-1,-1],
        [-1,1,-1,-1,-1,1,1,-1,"G",1],
        [-1,-1,1,1,-1,-1,-1,1,-1,-1]]
y = 1
x = 1
ans = []

def search_route(field, y, x, hp=36, route=''):
	# ゴール地点へ到達したら終了
	if field[y][x] == 'G':
		if hp >= 50: ans.append(route)
		return

        # HP増減処理
	if field[y][x] == 1: hp += 1
	if field[y][x] == -1: hp -= 1
	if hp <= 0: return 

	field[y][x] = 'L'

        # 移動可能なマスを探索する
	if y - 1 >= 0 and field[y-1][x] != 'L':
		search_route(deepcopy(field), y-1, x, hp, route + 'U')
	if y + 1 <= 9 and field[y+1][x] != 'L':
		search_route(deepcopy(field), y+1, x, hp, route + 'D')
	if x + 1 <= 9 and field[y][x+1] != 'L':
		search_route(deepcopy(field), y, x+1, hp, route + 'R')
	if x - 1 >= 0 and field[y][x-1] != 'L':
		search_route(deepcopy(field), y, x-1, hp, route + 'L')
	return

search_route(deepcopy(data), y, x)
print(ans)

data内にマップの情報が入っています。
文字列Sはスタート地点、Gはゴール地点、Lが死神です。

スタート地点から上下左右、移動可能なマスへ再帰的に関数を呼び出しています。
マップからはみ出しておらず、死神の居ないマスが移動可能なマスです。

一度来た道は引き返せないように、死神を設置しながら移動するようにして、
後は現在の居場所に応じて体力を増減させたりする‥‥大体こんな感じです。

実行すると固まります。
一日くらい放置してたら解は出るかもしれませんが、暇なので色々調べてると、
枝刈り法というものがあるっぽい事に気づいたので、早速試してみました。

枝刈り法を用いたコード
from copy import deepcopy

data = [[-1,1,1,-1,-1,1,1,-1,-1,1],
        [1,"S",1,"L",-1,-1,-1,-1,-1,-1],
        [-1,-1,-1,1,1,-1,1,-1,1,1],
        [1,"L",-1,-1,1,-1,1,-1,1,-1],
        [-1,1,1,-1,-1,-1,-1,"L",1,-1],
        [1,-1,1,-1,-1,1,-1,1,-1,1],
        [-1,"L",-1,-1,-1,1,-1,-1,-1,1],
        [-1,1,-1,1,1,-1,1,-1,-1,-1],
        [-1,1,-1,-1,-1,1,1,-1,"G",1],
        [-1,-1,1,1,-1,-1,-1,1,-1,-1]]
y = 1
x = 1
ans = []

def search_route(field, y, x, hp=36, route='', cnt=0):
	# ゴール地点へ到達したら終了
	if field[y][x] == 'G':
		if hp >= 50: ans.append(route)
		return

        # HP増減処理
	if field[y][x] == 1: hp += 1
	if field[y][x] == -1: hp -= 1
	if hp <= 0: return

        # 見込み以上のHPが無い場合は終了
	if hp <= 32 + cnt // 3: return        

	field[y][x] = 'L'

        # 移動可能なマスを探索する
	if y - 1 >= 0 and field[y-1][x] != 'L':
		search_route(deepcopy(field), y-1, x, hp, route + 'U', cnt+1)
	if y + 1 <= 9 and field[y+1][x] != 'L':
		search_route(deepcopy(field), y+1, x, hp, route + 'D', cnt+1)
	if x + 1 <= 9 and field[y][x+1] != 'L':
		search_route(deepcopy(field), y, x+1, hp, route + 'R', cnt+1)
	if x - 1 >= 0 and field[y][x-1] != 'L':
		search_route(deepcopy(field), y, x-1, hp, route + 'L', cnt+1)
	return

search_route(deepcopy(data), y, x)
print(ans)
if hp <= 32 + cnt // 3: return

この部分で枝刈りを行っています。
移動量に比例して求めるHPの量を増やしていくことで無駄な探索を削れている…はずです。

実行結果
['RDRRDRRUUULLLLLLDDDDRRDDDLDDRRUURDRRUULURRRUUURDDDDDDL',
 'RDRRDRRUUULLLLLLDDDDRRDDDLDDRRUURDRRULUURRRUUURDDDDDDL',
 'RDRRDRRUUULLLLLLDDDDRRDDDLDDRRUURRDRUULURRRUUURDDDDDDL',
 'RDRRDRRUUULLLLLLDDDDRRDDDLDRDRUURDRRUULURRRUUURDDDDDDL',
 'RDRRDRRUUULLLLLLDDDDRRDDDLDRDRUURDRRULUURRRUUURDDDDDDL',
 'RDRRDRRUUULLLLLLDDDDRRDDDLDRDRUURRDRUULURRRUUURDDDDDDL']
[Finished in 1754.8s]

実行時間1754.8sは置いておいて、とりあえず結果が出力されましたので、これに沿って移動してみると!
f:id:kakuyon:20180619034352p:plain

というわけで一応クリアできました。
どう見ても実行時間が普通ではないので、どこかに改善の余地があるのでしょうが、よく分からない&疲れたので、これで一旦終了ということにしておきます。
改善案が見つかったら更新するかもしれないししないかもしれません。