zf

zenflows testing
git clone https://s.sonu.ch/~srfsh/zf.git
Log | Files | Refs | Submodules | README | LICENSE

cookie.ex (7864B)


      1 defmodule Plug.Session.COOKIE do
      2   @moduledoc """
      3   Stores the session in a cookie.
      4 
      5   This cookie store is based on `Plug.Crypto.MessageVerifier`
      6   and `Plug.Crypto.MessageEncryptor` which encrypts and signs
      7   each cookie to ensure they can't be read nor tampered with.
      8 
      9   Since this store uses crypto features, it requires you to
     10   set the `:secret_key_base` field in your connection. This
     11   can be easily achieved with a plug:
     12 
     13       plug :put_secret_key_base
     14 
     15       def put_secret_key_base(conn, _) do
     16         put_in conn.secret_key_base, "-- LONG STRING WITH AT LEAST 64 BYTES --"
     17       end
     18 
     19   ## Options
     20 
     21     * `:secret_key_base` - the secret key base to built the cookie
     22       signing/encryption on top of. If one is given on initialization,
     23       the cookie store can precompute all relevant values at compilation
     24       time. Otherwise, the value is taken from `conn.secret_key_base`
     25       and cached.
     26 
     27     * `:encryption_salt` - a salt used with `conn.secret_key_base` to generate
     28       a key for encrypting/decrypting a cookie, can be either a binary or
     29       an MFA returning a binary;
     30 
     31     * `:signing_salt` - a salt used with `conn.secret_key_base` to generate a
     32       key for signing/verifying a cookie, can be either a binary or
     33       an MFA returning a binary;
     34 
     35     * `:key_iterations` - option passed to `Plug.Crypto.KeyGenerator`
     36       when generating the encryption and signing keys. Defaults to 1000;
     37 
     38     * `:key_length` - option passed to `Plug.Crypto.KeyGenerator`
     39       when generating the encryption and signing keys. Defaults to 32;
     40 
     41     * `:key_digest` - option passed to `Plug.Crypto.KeyGenerator`
     42       when generating the encryption and signing keys. Defaults to `:sha256`;
     43 
     44     * `:serializer` - cookie serializer module that defines `encode/1` and
     45       `decode/1` returning an `{:ok, value}` tuple. Defaults to
     46       `:external_term_format`.
     47 
     48     * `:log` - Log level to use when the cookie cannot be decoded.
     49       Defaults to `:debug`, can be set to false to disable it.
     50 
     51     * `:rotating_options` - additional list of options to use when decrypting and
     52       verifying the cookie. These options are used only when the cookie could not
     53       be decoded using primary options and are fetched on init so they cannot be
     54       changed in runtime. Defaults to `[]`.
     55 
     56   ## Examples
     57 
     58       plug Plug.Session, store: :cookie,
     59                          key: "_my_app_session",
     60                          encryption_salt: "cookie store encryption salt",
     61                          signing_salt: "cookie store signing salt",
     62                          key_length: 64,
     63                          log: :debug
     64   """
     65 
     66   require Logger
     67   @behaviour Plug.Session.Store
     68 
     69   alias Plug.Crypto.KeyGenerator
     70   alias Plug.Crypto.MessageVerifier
     71   alias Plug.Crypto.MessageEncryptor
     72 
     73   @impl true
     74   def init(opts) do
     75     build_opts(opts)
     76     |> build_rotating_opts(opts[:rotating_options])
     77     |> Map.delete(:secret_key_base)
     78   end
     79 
     80   @impl true
     81   def get(conn, raw_cookie, opts) do
     82     opts = Map.put(opts, :secret_key_base, conn.secret_key_base)
     83 
     84     [opts | opts.rotating_options]
     85     |> Enum.find_value(:error, &read_raw_cookie(raw_cookie, &1))
     86     |> decode(opts.serializer, opts.log)
     87   end
     88 
     89   @impl true
     90   def put(conn, _sid, term, opts) do
     91     %{serializer: serializer, key_opts: key_opts, signing_salt: signing_salt} = opts
     92     binary = encode(term, serializer)
     93 
     94     case opts do
     95       %{encryption_salt: nil} ->
     96         MessageVerifier.sign(binary, derive(conn.secret_key_base, signing_salt, key_opts))
     97 
     98       %{encryption_salt: encryption_salt} ->
     99         MessageEncryptor.encrypt(
    100           binary,
    101           derive(conn.secret_key_base, encryption_salt, key_opts),
    102           derive(conn.secret_key_base, signing_salt, key_opts)
    103         )
    104     end
    105   end
    106 
    107   @impl true
    108   def delete(_conn, _sid, _opts) do
    109     :ok
    110   end
    111 
    112   defp encode(term, :external_term_format) do
    113     :erlang.term_to_binary(term)
    114   end
    115 
    116   defp encode(term, serializer) do
    117     {:ok, binary} = serializer.encode(term)
    118     binary
    119   end
    120 
    121   defp decode({:ok, binary}, :external_term_format, log) do
    122     {:term,
    123      try do
    124        Plug.Crypto.non_executable_binary_to_term(binary)
    125      rescue
    126        e ->
    127          Logger.log(
    128            log,
    129            "Plug.Session could not decode incoming session cookie. Reason: " <>
    130              Exception.message(e)
    131          )
    132 
    133          %{}
    134      end}
    135   end
    136 
    137   defp decode({:ok, binary}, serializer, _log) do
    138     case serializer.decode(binary) do
    139       {:ok, term} -> {:custom, term}
    140       _ -> {:custom, %{}}
    141     end
    142   end
    143 
    144   defp decode(:error, _serializer, false) do
    145     {nil, %{}}
    146   end
    147 
    148   defp decode(:error, _serializer, log) do
    149     Logger.log(
    150       log,
    151       "Plug.Session could not verify incoming session cookie. " <>
    152         "This may happen when the session settings change or a stale cookie is sent."
    153     )
    154 
    155     {nil, %{}}
    156   end
    157 
    158   defp prederive(secret_key_base, value, key_opts)
    159        when is_binary(secret_key_base) and is_binary(value) do
    160     {:prederived, derive(secret_key_base, value, Keyword.delete(key_opts, :cache))}
    161   end
    162 
    163   defp prederive(_secret_key_base, value, _key_opts) do
    164     value
    165   end
    166 
    167   defp derive(_secret_key_base, {:prederived, value}, _key_opts) do
    168     value
    169   end
    170 
    171   defp derive(secret_key_base, {module, function, args}, key_opts) do
    172     derive(secret_key_base, apply(module, function, args), key_opts)
    173   end
    174 
    175   defp derive(secret_key_base, key, key_opts) do
    176     secret_key_base
    177     |> validate_secret_key_base()
    178     |> KeyGenerator.generate(key, key_opts)
    179   end
    180 
    181   defp validate_secret_key_base(nil),
    182     do: raise(ArgumentError, "cookie store expects conn.secret_key_base to be set")
    183 
    184   defp validate_secret_key_base(secret_key_base) when byte_size(secret_key_base) < 64,
    185     do: raise(ArgumentError, "cookie store expects conn.secret_key_base to be at least 64 bytes")
    186 
    187   defp validate_secret_key_base(secret_key_base), do: secret_key_base
    188 
    189   defp check_signing_salt(opts) do
    190     case opts[:signing_salt] do
    191       nil -> raise ArgumentError, "cookie store expects :signing_salt as option"
    192       salt -> salt
    193     end
    194   end
    195 
    196   defp check_serializer(serializer) when is_atom(serializer), do: serializer
    197 
    198   defp check_serializer(_),
    199     do: raise(ArgumentError, "cookie store expects :serializer option to be a module")
    200 
    201   defp read_raw_cookie(raw_cookie, opts) do
    202     signing_salt = derive(opts.secret_key_base, opts.signing_salt, opts.key_opts)
    203 
    204     case opts do
    205       %{encryption_salt: nil} ->
    206         MessageVerifier.verify(raw_cookie, signing_salt)
    207 
    208       %{encryption_salt: _} ->
    209         encryption_salt = derive(opts.secret_key_base, opts.encryption_salt, opts.key_opts)
    210 
    211         MessageEncryptor.decrypt(raw_cookie, encryption_salt, signing_salt)
    212     end
    213     |> case do
    214       :error -> nil
    215       result -> result
    216     end
    217   end
    218 
    219   defp build_opts(opts) do
    220     encryption_salt = opts[:encryption_salt]
    221     signing_salt = check_signing_salt(opts)
    222 
    223     iterations = Keyword.get(opts, :key_iterations, 1000)
    224     length = Keyword.get(opts, :key_length, 32)
    225     digest = Keyword.get(opts, :key_digest, :sha256)
    226     log = Keyword.get(opts, :log, :debug)
    227     secret_key_base = Keyword.get(opts, :secret_key_base)
    228     key_opts = [iterations: iterations, length: length, digest: digest, cache: Plug.Keys]
    229 
    230     serializer = check_serializer(opts[:serializer] || :external_term_format)
    231 
    232     %{
    233       secret_key_base: secret_key_base,
    234       encryption_salt: prederive(secret_key_base, encryption_salt, key_opts),
    235       signing_salt: prederive(secret_key_base, signing_salt, key_opts),
    236       key_opts: key_opts,
    237       serializer: serializer,
    238       log: log
    239     }
    240   end
    241 
    242   defp build_rotating_opts(opts, rotating_opts) when is_list(rotating_opts) do
    243     Map.put(opts, :rotating_options, Enum.map(rotating_opts, &build_opts/1))
    244   end
    245 
    246   defp build_rotating_opts(opts, _), do: Map.put(opts, :rotating_options, [])
    247 end