スポンサーリンク

2015年7月22日

[Elixir+Phoenix]Authorization

Goal

edit、updateアクションが実行される前に認可処理を行うよう実装する。

Dev-Environment

OS: Windows8.1
Erlang: Eshell V6.4, OTP-Version 17.5
Elixir: v1.0.4
Phoenix Framework: v0.13.1
PostgreSQL: postgres (PostgreSQL) 9.4.4

Wait a minute

edit、updateアクションで、認可を行った後に更新を実行するようにします。
ちょっと面倒ですが、必要な処理ですので頑張って実装しましょう。

Index

Authorization
|> The difference of authentication and authorization
|> Sign-in request
|> Correct user?
|> Extra

The difference of authentication and authorization

まず、認可(authorization)とは何でしょう?認証(authentication)とは違うのでしょうか?
ここではざっくりとした説明にとどめます。
認証: 本人を識別するIDなどで間違いなく本人だと見なすこと。一言で言えば本人確認。
認可: 何かを利用したり、アクセスすることに対して許可を与えること。(認証済みであること)
詳しくは以下のサイトを参考にすると分かりやすい説明があります。
ITmedia - 認証と認可の違い

Sign-in request

サインインしていない状態では、
ページが表示されないようにアクセス制御を実装します。
ファイル: web/controllers/user_controller.ex
サインインしているか確認する関数を追加する。
defp signed_in_user?(conn, _) do
  if conn.assigns[:current_user] do
    conn
  else
    conn
    |> put_flash(:info, "Please sign in.")
    |> redirect(to: session_path(conn, :new))
    |> halt
  end
end
Description:
current_userに値がなければサインインしていないので、
サインインページへリダイレクトさせる。
Caution:
halt/1を付けないと以下のようなエラーが発生する。
hexdocs - Plug.Conn.AlreadySentError
halt/1は呼び出すと、
さらに下流プラグへ流れるのを防止でき、
プラグパイプラインを停止させることができる。
hexdocs - Plug v0.13.0 Plug.Conn.halt/1
** (exit) an exception was raised:
    ** (Plug.Conn.AlreadySentError) the response was already sent
        (plug) lib/plug/conn.ex:446: Plug.Conn.resp/3
        (plug) lib/plug/conn.ex:433: Plug.Conn.send_resp/3
        (sample_app) web/controllers/user_controller.ex:1: SampleApp.UserController.phoenix_controller_pipeline/2
        (sample_app) lib/phoenix/router.ex:297: SampleApp.Router.dispatch/2
        (sample_app) lib/phoenix/router.ex:2: SampleApp.Router.call/2
        (sample_app) lib/sample_app/endpoint.ex:1: SampleApp.Endpoint.phoenix_endpoint_pipeline/1
        (sample_app) lib/plug/debugger.ex:90: SampleApp.Endpoint."call (overridable 3)"/2
        (sample_app) lib/phoenix/endpoint/render_errors.ex:34: SampleApp.Endpoint.call/2
ファイル: web/controllers/user_controller.ex
プラグパイプラインに機能プラグを追加する。
plug :signed_in_user? when action in [:show, :edit, :update]
実際に動作が見たければ、
サインインしていない状態で、
直接URLを叩けばリダイレクトされる。

Correct user?

ユーザが正しくない処理を実行できないように、
アクセス制御を実装します。
例えば、
サインインしているユーザをAとします。
他にBと言うユーザがいるとします。
Aがログイン状態であっても、
Bのプロファイルページを参照できては困ります。
ファイル: web/controllers/user_controller.ex
正しいユーザか評価する関数を追加する。
defp correct_user?(conn, _) do
  user = Repo.get(SampleApp.User, String.to_integer(conn.params["id"]))

  if current_user?(user) do
    conn
  else
    conn
    |> put_flash(:info, "Please sign in.")
    |> redirect(to: session_path(conn, :new))
    |> halt
  end
end
defp current_user?(user) do
  user == conn.assigns[:current_user]
end
Description:
paramsにあるidは文字列なので、String.to_integer/1で数値に変換している。
StackOverflow - Convert Elixir string to integer or float
params[“id”]で取得したユーザとセッションに持っているサインインユーザで
ユーザのモデル同士を比較している。
params[“id”]のid値は画面側から指定された値です。
ファイル: web/controllers/user_controller.ex
プラグパイプラインに機能プラグを追加する。
plug :correct_user? when action in [:show, :edit, :update]
サインインした状態で別のユーザプロファイルを参照しようとしてみて下さい。
サインインページへリダイレクトされます。

Extra

ユーザのモデル同士の比較をiexから行ってみました。
iexの起動。
>iex -S mix
iex(1)> alias SampleApp.User
nil
モデルAと
iex(2)> user_a = SampleApp.Repo.get(User, 1)
[debug] SELECT u0."id", u0."name", u0."email", u0."password_digest", u0."inserted_at", u0."updated_at" FROM "users" AS u0 WHERE (u0."id" = $1) [1] OK query=1.0ms
%SampleApp.User{__meta__: %Ecto.Schema.Metadata{source: "users",
  state: :loaded}, email: "huge@huge.com", id: 1,
 inserted_at: #Ecto.DateTime<2015-07-22T07:07:17Z>, name: "huge", password: nil,
 password_digest: "bDA1bThpSk5IUmlCUEFEekx6U0w2Zz09LS1HT1NxUGY2TVRKenFrSjlrWVNmejhBPT0=--5334DAFFB7EAF18D4C85CDCBC4DBC6778BD5F370",
 updated_at: #Ecto.DateTime<2015-07-22T07:59:15Z>}
モデルBを
iex(3)> user_b = SampleApp.Repo.get(User, 1)
[debug] SELECT u0."id", u0."name", u0."email", u0."password_digest", u0."inserted_at", u0."updated_at" FROM "users" AS u0 WHERE (u0."id" = $1) [1] OK query=1.0ms
%SampleApp.User{__meta__: %Ecto.Schema.Metadata{source: "users",
  state: :loaded}, email: "huge@huge.com", id: 1,
 inserted_at: #Ecto.DateTime<2015-07-22T07:07:17Z>, name: "huge", password: nil,
 password_digest: "bDA1bThpSk5IUmlCUEFEekx6U0w2Zz09LS1HT1NxUGY2TVRKenFrSjlrWVNmejhBPT0=--5334DAFFB7EAF18D4C85CDCBC4DBC6778BD5F370",
 updated_at: #Ecto.DateTime<2015-07-22T07:59:15Z>}
比較しています。
(同じものなのでfalseが出たら驚きですが)
iex(4)> user_a == user_b
true
ちょっとしたTips的な?(笑)

Speaking to oneself

ソースコードを見返していたら・・・
サインイン(Sign-in)とログイン(Login)の単語が入り混じってます。
分かり辛くなるから統一しないと・・・後で直しましょう~
具体的には大改修かける時にでも・・・

Bibliography

人気の投稿