Packages

A full featured, configurable authentication and user management system for Phoenix.

Current section

Files

Jump to
coherence lib coherence plugs authentication ip_address.ex
Raw

lib/coherence/plugs/authentication/ip_address.ex

defmodule Coherence.Authentication.IpAddress do
@moduledoc """
Implements ip address based authentication. To use add
plug Coherence.Authentication.IpAddress, allow: ~w(127.0.0.1 192.168.1.200)
to your pipeline.
IP addresses can be specified in a list as either IP or IP/subnet_mask, where subnet_mask
can be an integer or dot format.
If you would like access to the current user you must set each authorized IP address like:
Coherence.CredentialStore.Server.put_credentials({127.0.0.1}, %{role: :admin})
or use a custom store like:
defmodule MyProject.Store do
def get_user_data(ip) do
Repo.one from u in User, where: u.ip_address == ^id
end
end
plug Coherence.Authentication.IpAddress, allow: ~w(127.0.0.1 192.168.1.0/24), store: &MyProject.Store/1
## IP Format Examples:
allow: ~w(127.0.0.1 192.169.1.0/255.255.255.0)
allow: ~w(127.0.0.1 192.169.1.0/24)
deny: ~w(10.10.0.0/16)
## Options
* `:allow` - list of allowed IPs
* `:deny` - list of denied IPs
* `:error` - error to be displayed if the IP is not allowed
* `:store` - the user_data store
* `:assign_key` - the assigns key to store the user_data
"""
@behaviour Plug
import Bitwise
import Plug.Conn
import Coherence.Authentication.Utils
alias Coherence.Authentication.Utils
alias Coherence.Messages
require Logger
@dialyzer [ {:nowarn_function, call: 2} ]
@type t :: Ecto.Schema.t() | map()
@type conn :: Plug.Conn.t()
@doc """
Add the credentials for a `token`. `user_data` can be any term but must not be `nil`.
"""
@spec add_credentials(String.t(), t, module) :: t
def add_credentials(ip, user_data, store \\ Coherence.CredentialStore.Server) do
store.put_credentials(ip, user_data)
end
@doc """
Remove the credentials for a `token`.
"""
@spec remove_credentials(String.t(), module) :: t
def remove_credentials(ip, store \\ Coherence.CredentialStore.Server) do
store.delete_credentials(ip)
end
@spec init(keyword()) :: map()
def init(opts) do
%{
allow: Keyword.get(opts, :allow, []),
deny: Keyword.get(opts, :deny, []),
error: Keyword.get(opts, :error, Messages.backend().unauthorized_ip_address()),
store: Keyword.get(opts, :store, Coherence.CredentialStore.Server),
assign_key: Keyword.get(opts, :assign_key, :current_user)
}
end
@spec call(conn, keyword()) :: conn
def call(conn, opts) do
ip = conn |> Plug.Conn.get_peer_data() |> Map.get(:address)
conn
|> verify_ip(ip, opts)
|> fetch_user_data(opts)
|> assert_ip(opts)
end
defp verify_ip(conn, ip, %{allow: allow, deny: deny}),
do: {conn, ip, in?(ip, allow) && !in?(ip, deny)}
defp fetch_user_data({conn, ip, true}, %{store: store}),
do: {conn, true, store.get_user_data(ip)}
defp fetch_user_data({conn, _ip, valid?}, _), do: {conn, valid?, nil}
defp assert_ip({conn, true, nil}, _), do: conn
defp assert_ip({conn, true, user_data}, %{assign_key: assign_key}),
do: assign(conn, assign_key, user_data)
defp assert_ip({conn, _, _}, %{error: error}), do: halt_with_error(conn, error)
defp in?(ip, list) do
Enum.any?(list, &matches?(String.split(&1, "/"), ip))
end
defp matches?([item], ip), do: Utils.to_string(ip) == item
defp matches?([item, subnet], ip), do: in_subnet?(to_tuple(item), ip, subnet)
defp subnet(string) when is_binary(string) do
if String.contains?(string, ".") do
string |> to_tuple |> subnet
else
string |> String.to_integer() |> subnet
end
end
defp subnet(num) when is_integer(num) do
Enum.reduce(0..31, 0, &if(&1 < num, do: (&2 ||| 1) <<< 1, else: &2 <<< 1)) >>> 1
end
defp subnet(tuple) when is_tuple(tuple), do: to_integer(tuple)
defp in_subnet?(source_ip, target_ip, subnet) do
to_integer(source_ip) == (to_integer(target_ip) &&& subnet(subnet))
end
defp to_integer({a, b, c, d}) do
a <<< 24 ||| b <<< 16 ||| c <<< 8 ||| d
end
defp to_tuple(string) when is_binary(string) do
string
|> String.split(".")
|> Enum.map(&String.to_integer/1)
|> List.to_tuple()
end
end