Packages
livebook
0.16.1
0.19.8
0.19.7
0.19.6
0.19.5
0.19.4
0.19.3
0.19.2
0.19.1
0.19.0
0.18.6
0.18.5
0.18.4
0.18.3
0.18.2
0.18.1
0.18.0
0.17.3
0.17.2
0.17.1
0.17.0
0.16.4
0.16.3
0.16.2
0.16.1
0.16.0
0.15.5
0.15.4
0.15.3
0.15.2
0.15.1
0.15.0
0.14.7
0.14.6
0.14.5
0.14.4
0.14.3
0.14.2
0.14.1
0.14.0
0.14.0-rc.1
0.14.0-rc.0
0.13.3
0.13.2
0.13.1
0.13.0
0.12.1
0.12.0
0.11.4
0.11.3
0.11.2
0.11.1
0.11.0
0.10.0
0.9.3
0.9.2
0.9.1
0.9.0
0.8.2
0.8.1
0.8.0
0.7.2
0.7.1
0.7.0
0.6.3
0.6.2
0.6.1
0.6.0
0.5.2
0.5.1
0.5.0
0.4.1
0.4.0
0.3.2
0.3.1
0.3.0
0.2.3
0.2.2
0.2.1
0.2.0
0.1.2
0.1.1
0.1.0
Automate code & data workflows with interactive notebooks
Current section
Files
Jump to
Current section
Files
lib/livebook_web/plugs/auth_plug.ex
defmodule LivebookWeb.AuthPlug do
@behaviour Plug
import Plug.Conn
import Phoenix.Controller
use LivebookWeb, :verified_routes
@impl true
def init(opts), do: opts
@impl true
def call(conn, _opts) do
if authenticated?(conn) do
if not authorized?(conn) do
render_unauthorized(conn)
else
conn
end
else
authenticate(conn)
end
end
@doc """
Stores in the session the secret for the given mode.
"""
@spec store(Plug.Conn.t(), Livebook.Config.authentication_mode(), String.t()) :: Plug.Conn.t()
def store(conn, mode, value) do
conn
|> put_session(key(conn.port, mode), hash(value))
|> configure_session(renew: true)
end
@doc """
Checks if given connection is already authenticated.
"""
@spec authenticated?(Plug.Conn.t()) :: boolean()
def authenticated?(conn) do
authenticated?(get_session(conn), conn.port)
end
defp authenticated?(session, port) do
case authentication(session) do
%{mode: :disabled} ->
true
%{mode: mode, secret: secret} when mode in [:token, :password] ->
secret_hash = session[key(port, mode)]
is_binary(secret_hash) and matches_secret?(secret_hash, secret)
end
end
@doc """
Checks if given connection or session is authorized.
"""
@spec authorized?(Plug.Conn.t()) :: boolean()
def authorized?(%Plug.Conn{} = conn) do
# Note that if the user has access restricted to specific app pages,
# they are not authorized and have no access to any pages guarded
# by this plug.
authenticated?(conn) and
LivebookWeb.UserPlug.build_current_user(
get_session(conn),
conn.assigns.identity_data,
conn.assigns.user_data
).access_type == :full
end
@doc """
Checks if the given session is authorized.
"""
@spec authorized?(map(), non_neg_integer()) :: boolean()
def authorized?(%{} = session, port) do
authenticated?(session, port) and
LivebookWeb.UserPlug.build_current_user(
session,
session["identity_data"],
session["user_data"]
).access_type == :full
end
defp authenticate(conn) do
case authentication(conn) do
%{mode: :password} ->
redirect_to_authenticate(conn)
%{mode: :token, secret: secret} ->
{token, query_params} = Map.pop(conn.query_params, "token")
if is_binary(token) and matches_secret?(hash(token), secret) do
# Redirect to the same path without query params
conn
|> store(:token, token)
|> redirect(to: path_with_query(conn.request_path, query_params))
|> halt()
else
redirect_to_authenticate(conn)
end
end
end
defp matches_secret?(hash, secret) do
Plug.Crypto.secure_compare(hash, hash(secret))
end
defp redirect_to_authenticate(%{path_info: []} = conn) do
path =
if Livebook.Apps.list_apps() != [] or Livebook.Config.apps_path() != nil or
Livebook.Config.teams_auth() != nil do
~p"/apps"
else
~p"/authenticate"
end
conn
|> redirect(to: path)
|> halt()
end
defp redirect_to_authenticate(conn) do
conn
|> then(fn
%{method: "GET"} -> put_session(conn, :redirect_to, current_path(conn))
conn -> conn
end)
|> redirect(to: ~p"/authenticate")
|> halt()
end
defp render_unauthorized(%{path_info: []} = conn) do
conn |> redirect(to: ~p"/apps") |> halt()
end
defp render_unauthorized(conn) do
conn
|> put_status(:unauthorized)
|> put_view(LivebookWeb.ErrorHTML)
|> put_root_layout(false)
|> render("401.html", %{details: "You don't have permission to access this server"})
|> halt()
end
defp path_with_query(path, params) when params == %{}, do: path
defp path_with_query(path, params), do: path <> "?" <> URI.encode_query(params)
defp key(port, mode), do: "#{port}:#{mode}"
defp hash(value), do: :crypto.hash(:sha256, value)
@doc """
Returns the authentication configuration for the given `conn` or
`session`.
This mirrors `Livebook.Config.authentication/0`, except the it can
be overridden in tests, for each connection.
"""
@spec authentication(Plug.Conn.t() | map()) :: Livebook.Config.authentication()
if Mix.env() == :test do
def authentication(%Plug.Conn{} = conn), do: authentication(get_session(conn))
def authentication(%{} = session) do
session["authentication_test_override"] || Livebook.Config.authentication()
end
else
def authentication(_), do: Livebook.Config.authentication()
end
end