diff --git a/lib/livebook/hubs/team_client.ex b/lib/livebook/hubs/team_client.ex
index 60630b7d8..7dc9e14da 100644
--- a/lib/livebook/hubs/team_client.ex
+++ b/lib/livebook/hubs/team_client.ex
@@ -153,7 +153,7 @@ defmodule Livebook.Hubs.TeamClient do
@doc """
Returns if the given user groups has access to given app.
"""
- @spec user_app_access?(String.t(), Livebook.Users.User.t(), String.t()) :: boolean()
+ @spec user_app_access?(String.t(), list(map()), String.t()) :: boolean()
def user_app_access?(id, groups, slug) do
GenServer.call(registry_name(id), {:check_app_access, groups, slug})
end
diff --git a/lib/livebook_web/live/app_session_live.ex b/lib/livebook_web/live/app_session_live.ex
index f9a5f61fe..9439c78af 100644
--- a/lib/livebook_web/live/app_session_live.ex
+++ b/lib/livebook_web/live/app_session_live.ex
@@ -8,11 +8,28 @@ defmodule LivebookWeb.AppSessionLive do
alias Livebook.Notebook.Cell
@impl true
+ def mount(%{"slug" => slug} = params, _session, socket)
+ when not socket.assigns.app_authenticated? do
+ if connected?(socket) do
+ to =
+ if id = params["id"] do
+ ~p"/apps/#{slug}/authenticate?id=#{id}"
+ else
+ ~p"/apps/#{slug}/authenticate"
+ end
- def mount(%{"slug" => slug, "id" => session_id}, _session, socket)
- when socket.assigns.app_authenticated? and socket.assigns.app_authorized? do
+ {:ok, push_navigate(socket, to: to)}
+ else
+ {:ok, socket}
+ end
+ end
+
+ def mount(_params, _session, socket) when not socket.assigns.app_authorized? do
+ {:ok, socket, layout: false}
+ end
+
+ def mount(%{"slug" => slug, "id" => session_id}, _session, socket) do
{:ok, app} = Livebook.Apps.fetch_app(slug)
-
app_session = Enum.find(app.sessions, &(&1.id == session_id))
if app_session && app_session.app_status.lifecycle == :active do
@@ -54,26 +71,6 @@ defmodule LivebookWeb.AppSessionLive do
end
end
- def mount(_params, _session, socket)
- when socket.assigns.app_authenticated? and not socket.assigns.app_authorized? do
- {:ok, socket, layout: false}
- end
-
- def mount(%{"slug" => slug} = params, _session, socket) do
- if connected?(socket) do
- to =
- if id = params["id"] do
- ~p"/apps/#{slug}/authenticate?id=#{id}"
- else
- ~p"/apps/#{slug}/authenticate"
- end
-
- {:ok, push_navigate(socket, to: to)}
- else
- {:ok, socket}
- end
- end
-
# Puts the given assigns in `socket.private`,
# to ensure they are not used for rendering.
defp assign_private(socket, assigns) do
@@ -116,7 +113,7 @@ defmodule LivebookWeb.AppSessionLive do
<.remix_icon icon="arrow-down-s-line" />
- <.menu_item :if={@livebook_authenticated?}>
+ <.menu_item :if={@livebook_authorized?}>
<.link navigate={~p"/"} role="menuitem">
<.remix_icon icon="home-6-line" />
Home
@@ -143,7 +140,7 @@ defmodule LivebookWeb.AppSessionLive do
View source
- <.menu_item :if={@livebook_authenticated?}>
+ <.menu_item :if={@livebook_authorized?}>
<.link patch={~p"/sessions/#{@session.id}"} role="menuitem">
<.remix_icon icon="terminal-line" />
Debug
@@ -183,7 +180,7 @@ defmodule LivebookWeb.AppSessionLive do
<.link
- :if={@livebook_authenticated?}
+ :if={@livebook_authorized?}
navigate={~p"/sessions/#{@session.id}" <> "#cell-#{@data_view.errored_cell_id}"}
>
<.remix_icon icon="terminal-line" />
@@ -383,19 +380,16 @@ defmodule LivebookWeb.AppSessionLive do
{:noreply, redirect_on_closed(socket)}
end
- def handle_info(
- {:app_deployment_updated, %{slug: slug, hub_id: hub_id}},
- %{assigns: %{slug: slug}} = socket
- ) do
+ def handle_info({:app_deployment_updated, %{slug: slug}}, %{assigns: %{slug: slug}} = socket) do
# We force the redirection in case of
# the current user loses access to this app.
# With this strategy, we guarantee that unauthorized users
# won't be able to keep reading the app which they
# should't have access.
- groups = socket.assigns.current_user.restricted_apps_groups
+ {:ok, app} = Livebook.Apps.fetch_app(slug)
- if Livebook.Hubs.TeamClient.user_app_access?(hub_id, groups, slug) do
+ if Livebook.Apps.authorized?(app, socket.assigns.current_user) do
{:noreply, socket}
else
{:noreply, redirect(socket, to: ~p"/apps/#{slug}/sessions/#{socket.assigns.session.id}")}
diff --git a/lib/livebook_web/live/hooks/app_auth_hook.ex b/lib/livebook_web/live/hooks/app_auth_hook.ex
index 5a53d28bc..ffdb61977 100644
--- a/lib/livebook_web/live/hooks/app_auth_hook.ex
+++ b/lib/livebook_web/live/hooks/app_auth_hook.ex
@@ -38,7 +38,7 @@ defmodule LivebookWeb.AppAuthHook do
# For public apps (or in case the user has full access) it is
# set to `true` on both dead and live render
#
- # * `:livebook_authenticated?` - if the user has full Livebook
+ # * `:livebook_authorized?` - if the user has full Livebook
# access
#
# * `:app_settings` - the current app settings
@@ -49,40 +49,26 @@ defmodule LivebookWeb.AppAuthHook do
LivebookWeb.SessionHelpers.subscribe_to_logout()
end
- user =
- LivebookWeb.UserPlug.build_current_user(
- session,
- session["identity_data"],
- session["user_data"]
- )
-
- livebook_authenticated? = livebook_authenticated?(session, user, socket)
+ livebook_authorized? = livebook_authorized?(session, socket)
socket =
socket
- |> assign(livebook_authenticated?: livebook_authenticated?)
+ |> assign(livebook_authorized?: livebook_authorized?)
|> attach_hook(:logout, :handle_info, &handle_info/2)
|> attach_hook(:logout, :handle_event, &handle_event/3)
with {:ok, app} <- Livebook.Apps.fetch_app(slug),
{:ok, app_settings} <- Livebook.Apps.fetch_settings(slug) do
- app_authorized? =
- case app.app_spec do
- %Livebook.Apps.TeamsAppSpec{hub_id: hub_id} ->
- Livebook.Hubs.TeamClient.user_app_access?(hub_id, user.restricted_apps_groups, slug)
-
- _ ->
- true
- end
-
app_authenticated? =
- app_settings.access_type == :public or
- (livebook_authenticated? or has_valid_token?(socket, app_settings))
+ case app_settings.access_type do
+ :public -> true
+ :protected -> livebook_authorized? or has_valid_token?(socket, app_settings)
+ end
{:cont,
assign(socket,
app_authenticated?: app_authenticated?,
- app_authorized?: app_authorized?,
+ app_authorized?: app_authorized?(session, app),
app_settings: app_settings
)}
else
@@ -95,11 +81,22 @@ defmodule LivebookWeb.AppAuthHook do
{:cont, socket}
end
- defp livebook_authenticated?(session, user, socket) do
+ defp livebook_authorized?(session, socket) do
uri = get_connect_info(socket, :uri)
LivebookWeb.AuthPlug.authenticated?(session, uri.port) and
- user.restricted_apps_groups == nil
+ LivebookWeb.AuthPlug.authorized?(session)
+ end
+
+ defp app_authorized?(session, app) do
+ user =
+ LivebookWeb.UserPlug.build_current_user(
+ session,
+ session["identity_data"],
+ session["user_data"]
+ )
+
+ Livebook.Apps.authorized?(app, user)
end
defp handle_info(:logout, socket) do
diff --git a/lib/livebook_web/plugs/auth_plug.ex b/lib/livebook_web/plugs/auth_plug.ex
index a77036715..a1fbfd71c 100644
--- a/lib/livebook_web/plugs/auth_plug.ex
+++ b/lib/livebook_web/plugs/auth_plug.ex
@@ -12,17 +12,12 @@ defmodule LivebookWeb.AuthPlug do
@impl true
def call(conn, _opts) do
if authenticated?(conn) do
- user =
- LivebookWeb.UserPlug.build_current_user(
- get_session(conn),
- conn.assigns.identity_data,
- conn.assigns.user_data
- )
-
- if user.restricted_apps_groups == nil do
- conn
- else
+ if not authorized?(conn) do
+ # User has access only to specific app pages, so they do not have
+ # access to any pages guarded by this plug.
render_unauthorized(conn)
+ else
+ conn
end
else
authenticate(conn)
@@ -62,6 +57,26 @@ defmodule LivebookWeb.AuthPlug do
end
end
+ @doc """
+ Checks if given connection or session is authorized.
+ """
+ @spec authorized?(Plug.Conn.t() | map()) :: boolean()
+ def authorized?(%Plug.Conn{} = conn) do
+ LivebookWeb.UserPlug.build_current_user(
+ get_session(conn),
+ conn.assigns.identity_data,
+ conn.assigns.user_data
+ ).restricted_apps_groups == nil
+ end
+
+ def authorized?(%{} = session) do
+ LivebookWeb.UserPlug.build_current_user(
+ session,
+ session["identity_data"],
+ session["user_data"]
+ ).restricted_apps_groups == nil
+ end
+
defp authenticate(conn) do
case authentication(conn) do
%{mode: :password} ->