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
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
|> Sign-in request
|> Correct user?
|> Extra
The difference of authentication and authorization
まず、認可(authorization)とは何でしょう?認証(authentication)とは違うのでしょうか?
ここではざっくりとした説明にとどめます。
認証: 本人を識別するIDなどで間違いなく本人だと見なすこと。一言で言えば本人確認。
認可: 何かを利用したり、アクセスすることに対して許可を与えること。(認証済みであること)
認証: 本人を識別するIDなどで間違いなく本人だと見なすこと。一言で言えば本人確認。
認可: 何かを利用したり、アクセスすることに対して許可を与えること。(認証済みであること)
詳しくは以下のサイトを参考にすると分かりやすい説明があります。
ITmedia - 認証と認可の違い
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に値がなければサインインしていないので、
サインインページへリダイレクトさせる。
current_userに値がなければサインインしていないので、
サインインページへリダイレクトさせる。
** (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を叩けばリダイレクトされる。
サインインしていない状態で、
直接URLを叩けばリダイレクトされる。
Correct user?
ユーザが正しくない処理を実行できないように、
アクセス制御を実装します。
アクセス制御を実装します。
例えば、
サインインしているユーザをAとします。
他にBと言うユーザがいるとします。
Aがログイン状態であっても、
Bのプロファイルページを参照できては困ります。
サインインしているユーザを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は文字列なので、String.to_integer/1で数値に変換している。
StackOverflow - Convert Elixir string to integer or float
params[“id”]で取得したユーザとセッションに持っているサインインユーザで
ユーザのモデル同士を比較している。
params[“id”]の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が出たら驚きですが)
(同じものなのでfalseが出たら驚きですが)
iex(4)> user_a == user_b
true
ちょっとしたTips的な?(笑)
Speaking to oneself
ソースコードを見返していたら・・・
サインイン(Sign-in)とログイン(Login)の単語が入り混じってます。
サインイン(Sign-in)とログイン(Login)の単語が入り混じってます。
分かり辛くなるから統一しないと・・・後で直しましょう~
具体的には大改修かける時にでも・・・
具体的には大改修かける時にでも・・・