Current section

Files

Jump to
plug_hyper_auth lib hyper_auth.ex
Raw

lib/hyper_auth.ex

defmodule HyperAuth do
import Plug.Conn
@moduledoc """
Plug for HTTP AAA using the HTTP Auth framework.
This plug is extensible with the behaviours:
* `HyperAuth.Scheme`
* `HyperAuth.Authenticator`
When a configured scheme is found in the
authorization header it will use that to
process the values of the header (with
access to the connection but the modifications
are ignored), with that values and other generic
values the authenticator will authenticate
the user (without access to connection).
This library support the common schemes then
often you only need extend it with
modules of `HyperAuth.Authenticator`
behaviour (more safe than extend the
schemes).
RFC 2617 http://www.ietf.org/rfc/rfc2617.txt
http://www.webdav.org/specs/rfc2617.html
RFC 7235 https://tools.ietf.org/html/rfc7235
http://www.webdav.org/specs/rfc7235.html
RFC 7616 https://tools.ietf.org/html/rfc7616
+-----+--------+--------+------+
| TLS | PUBLIC | HEADER | AUTH |
+-----+--------+--------+------+-----+------+
| NO | NO | NO | NO | 403 | anon |
| NO | NO | YES | NO | 403 | anon |
| NO | NO | YES | YES | 403 | anon |
| NO | YES | NO | NO | 200 | anon |
| NO | YES | YES | NO | 403 | anon |
| NO | YES | YES | YES | 403 | anon |
| YES | NO | NO | NO | 401 | anon |
| YES | NO | YES | NO | 401 | anon |
| YES | YES | NO | NO | 200 | anon |
| YES | YES | YES | NO | 200 | anon |
| YES | NO | YES | YES | 200 | user |
| YES | YES | YES | YES | 200 | user |
+-----+--------+--------+------+-----+------+
"""
@doc """
Configure the schemes alloweds and the authenticator.
The default schemes are:
* Basic: HyperAuth.Scheme.Basic
* Digest: HyperAuth.Scheme.Digest
"""
def init(opts) do
schemes = Keyword.get(opts, :schemes, %{})
|> Map.put_new("basic", __MODULE__.Scheme.Basic)
|> Map.put_new("digest", __MODULE__.Scheme.Digest)
Keyword.put(opts, :schemes, schemes)
end
def call(conn, opts) do
authorization = conn
|> get_req_header("authorization")
|> List.first
cond do
# Allow public without authorization
opts[:public] && is_nil(authorization) ->
conn
# Forbidden authorization for not public over HTTP
# Auth only allowed over secure connection
conn.scheme != :https && conn.remote_ip != {127, 0, 0, 1} ->
forbidden conn, opts
# Check if not exists authorization
is_nil(authorization) || String.length(authorization) == 0 ->
unauthorized conn, opts
# Limit the authorization size
String.length(authorization) > 4096 ->
header_fields_too_large conn, opts
true ->
# Parse authorization header value
case __MODULE__.HTTP.parse_authorization authorization do
{scheme, tokens, authorization_properties} ->
# Process authorization credentials
credentials = process_authorization conn, scheme, tokens, authorization_properties, opts
if is_map credentials do
# Authenticate
user = authenticate credentials, opts
if is_map user do
# Put the user in the connection
put_private conn, :auth_user, credentials
else
unauthorized conn, opts
end
else
unauthorized conn, opts
end
_ ->
unauthorized conn, opts
end
end
end
defp process_authorization(conn, scheme, tokens, authorization_properties, opts) do
# Call the scheme module
scheme_module = opts[:schemes][scheme]
if is_nil scheme_module do
nil
else
scheme_module.process_authorization conn, tokens, authorization_properties, opts
end
end
defp authenticate(credentials, opts) do
# Call the configured authenticator module
authenticator_module = opts[:authenticator]
if is_nil authenticator_module do
nil
else
# Add common credentials values
nas_identifier = Atom.to_string(node())
credentials = Map.put(credentials, "NAS-Identifier", nas_identifier)
user = authenticator_module.authenticate credentials, opts
# All user map need an UID
if is_nil user["uid"] do
nil
else
user
end
end
end
defp request_authenticate(conn, opts) do
opts[:schemes]
|> Map.values()
|> Enum.map(fn(scheme_module) ->
# Call the scheme module
scheme_module.request_authenticate(conn, opts)
end)
|> Enum.join(" ")
end
defp unauthorized(conn, opts) do
if opts[:public] do
conn
else
authorization_request = request_authenticate conn, opts
conn
|> put_resp_header("www-authenticate", authorization_request)
|> send_resp(:unauthorized, "unauthorized")
|> halt()
end
end
defp forbidden(conn, _opts) do
conn
|> send_resp(:forbidden, "forbidden")
|> halt()
end
defp header_fields_too_large(conn, _opts) do
conn
|> send_resp(:request_header_fields_too_large, "request header fields too large")
|> halt()
end
end