diff --git a/lib/phoenix_html/form_data.ex b/lib/phoenix_html/form_data.ex index 7ceef549..9a277dc4 100644 --- a/lib/phoenix_html/form_data.ex +++ b/lib/phoenix_html/form_data.ex @@ -149,12 +149,11 @@ defimpl Phoenix.HTML.FormData, for: [Plug.Conn, Atom] do def input_value(_conn_or_atom, %{data: data, params: params}, field) when is_atom(field) or is_binary(field) do - case Map.fetch(params, field_to_string(field)) do - {:ok, value} -> - value + key = field_to_string(field) - :error -> - Map.get(data, field) + case params do + %{^key => value} -> value + %{} -> Map.get(data, field) end end diff --git a/lib/phoenix_html/link.ex b/lib/phoenix_html/link.ex index 90f9cf5e..a39392c5 100644 --- a/lib/phoenix_html/link.ex +++ b/lib/phoenix_html/link.ex @@ -65,28 +65,28 @@ defmodule Phoenix.HTML.Link do ### Overriding the default confirm behaviour - `phoenix_html.js` does trigger a custom event `phoenix.link.click` on the + `phoenix_html.js` does trigger a custom event `phoenix.link.click` on the clicked DOM element when a click happened. This allows you to intercept the - event on it's way bubbling up to `window` and do your own custom logic to + event on it's way bubbling up to `window` and do your own custom logic to enhance or replace how the `data-confirm` attribute is handled. - You could for example replace the browsers `confirm()` behavior with a + You could for example replace the browsers `confirm()` behavior with a custom javascript implementation: ```javascript - // listen on document.body, so it's executed before the default of + // listen on document.body, so it's executed before the default of // phoenix_html, which is listening on the window object document.body.addEventListener('phoenix.link.click', function (e) { // Prevent default implementation e.stopPropagation(); - + // Introduce alternative implementation var message = e.target.getAttribute("data-confirm"); if(!message){ return true; } vex.dialog.confirm({ message: message, callback: function (value) { - if (value == false) { e.preventDefault(); } + if (value == false) { e.preventDefault(); } } }) }, false); @@ -105,7 +105,7 @@ defmodule Phoenix.HTML.Link do }, false); ``` - The latter could also be bound to any `click` event, but this way you can be + The latter could also be bound to any `click` event, but this way you can be sure your custom code is only executed when the code of `phoenix_html.js` is run. ## CSRF Protection @@ -231,18 +231,13 @@ defmodule Phoenix.HTML.Link do {[csrf: csrf], opts} {true, opts} -> - {[csrf: csrf_token(to)], opts} + {[csrf: Phoenix.HTML.Tag.csrf_token_value(to)], opts} {false, opts} -> {[], opts} end end - defp csrf_token(to) do - {mod, fun, args} = Application.fetch_env!(:phoenix_html, :csrf_token_reader) - apply(mod, fun, [to | args]) - end - defp pop_required_option!(opts, key, error_message) do {value, opts} = Keyword.pop(opts, key) diff --git a/lib/phoenix_html/tag.ex b/lib/phoenix_html/tag.ex index 447538b1..7d4ab241 100644 --- a/lib/phoenix_html/tag.ex +++ b/lib/phoenix_html/tag.ex @@ -282,7 +282,7 @@ defmodule Phoenix.HTML.Tag do {extra <> ~s'', opts} {true, opts} -> - csrf_token = csrf_token(to) + csrf_token = csrf_token_value(to) {extra <> ~s'', opts} {false, opts} -> @@ -290,7 +290,12 @@ defmodule Phoenix.HTML.Tag do end end - defp csrf_token(to) do + @doc """ + Returns the csrf_token value to be used by forms, meta tags, etc. + + By default, CSRF tokens are generated through `Plug.CSRFProtection`. + """ + def csrf_token_value(to) do {mod, fun, args} = Application.fetch_env!(:phoenix_html, :csrf_token_reader) apply(mod, fun, [to | args]) end @@ -310,7 +315,7 @@ defmodule Phoenix.HTML.Tag do :meta, charset: "UTF-8", name: "csrf-token", - content: csrf_token(%URI{host: nil}), + content: csrf_token_value(%URI{host: nil}), "csrf-param": @csrf_param, "method-param": @method_param ) diff --git a/test/phoenix_html/tag_test.exs b/test/phoenix_html/tag_test.exs index bdbd624f..3537c01e 100644 --- a/test/phoenix_html/tag_test.exs +++ b/test/phoenix_html/tag_test.exs @@ -223,4 +223,25 @@ defmodule Phoenix.HTML.TagTest do assert safe_to_string(csrf_meta_tag()) == ~s() end + + describe "csrf_token_value" do + def custom_csrf(to, extra), do: "#{extra}:#{to}" + + test "with default" do + assert csrf_token_value("/") == Plug.CSRFProtection.get_csrf_token() + end + + @default_reader Application.fetch_env!(:phoenix_html, :csrf_token_reader) + test "with configured MFA" do + Application.put_env( + :phoenix_html, + :csrf_token_reader, + {__MODULE__, :custom_csrf, ["extra"]} + ) + + assert csrf_token_value("/foo") == "extra:/foo" + after + Application.put_env(:phoenix_html, :csrf_token_reader, @default_reader) + end + end end