mirror of
https://github.com/livebook-dev/livebook.git
synced 2025-10-10 05:25:57 +08:00
Handle authorization groups in real-time (#2998)
This commit is contained in:
parent
7fa44a3966
commit
73c0f1b45c
24 changed files with 950 additions and 91 deletions
|
@ -7,6 +7,7 @@ defmodule Livebook.Apps do
|
||||||
require Logger
|
require Logger
|
||||||
|
|
||||||
alias Livebook.App
|
alias Livebook.App
|
||||||
|
alias Livebook.Apps
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Returns app process pid for the given slug.
|
Returns app process pid for the given slug.
|
||||||
|
@ -79,13 +80,10 @@ defmodule Livebook.Apps do
|
||||||
@spec authorized?(App.t(), Livebook.Users.User.t()) :: boolean()
|
@spec authorized?(App.t(), Livebook.Users.User.t()) :: boolean()
|
||||||
def authorized?(app, user)
|
def authorized?(app, user)
|
||||||
|
|
||||||
def authorized?(%{app_spec: %Livebook.Apps.TeamsAppSpec{}}, %{restricted_apps_groups: []}),
|
def authorized?(_app, %{access_type: :full}), do: true
|
||||||
do: false
|
|
||||||
|
|
||||||
def authorized?(_app, %{restricted_apps_groups: nil}), do: true
|
def authorized?(%{slug: slug, app_spec: %Apps.TeamsAppSpec{hub_id: id}}, user) do
|
||||||
|
Livebook.Hubs.TeamClient.user_app_access?(id, user.groups, slug)
|
||||||
def authorized?(%{slug: slug, app_spec: %Livebook.Apps.TeamsAppSpec{hub_id: id}}, user) do
|
|
||||||
Livebook.Hubs.TeamClient.user_app_access?(id, user.restricted_apps_groups, slug)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
|
|
|
@ -296,27 +296,40 @@ defmodule Livebook.Hubs.TeamClient do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_call({:check_full_access, groups}, _caller, %{deployment_group_id: id} = state) do
|
def handle_call({:check_full_access, groups}, _caller, state) do
|
||||||
|
if id = state.deployment_group_id do
|
||||||
case fetch_deployment_group(id, state) do
|
case fetch_deployment_group(id, state) do
|
||||||
{:ok, deployment_group} ->
|
{:ok, deployment_group} ->
|
||||||
{:reply, authorized_group?(deployment_group.authorization_groups, groups), state}
|
{:reply,
|
||||||
|
not deployment_group.teams_auth or
|
||||||
|
not deployment_group.groups_auth or
|
||||||
|
authorized_group?(deployment_group.authorization_groups, groups), state}
|
||||||
|
|
||||||
_ ->
|
_ ->
|
||||||
{:reply, false, state}
|
{:reply, false, state}
|
||||||
end
|
end
|
||||||
|
else
|
||||||
|
{:reply, true, state}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_call({:check_app_access, groups, slug}, _caller, %{deployment_group_id: id} = state) do
|
def handle_call({:check_app_access, groups, slug}, _caller, state) do
|
||||||
|
if id = state.deployment_group_id do
|
||||||
with {:ok, deployment_group} <- fetch_deployment_group(id, state),
|
with {:ok, deployment_group} <- fetch_deployment_group(id, state),
|
||||||
{:ok, app_deployment} <- fetch_app_deployment_from_slug(slug, state) do
|
{:ok, app_deployment} <- fetch_app_deployment_from_slug(slug, state) do
|
||||||
app_access? =
|
app_access? =
|
||||||
authorized_group?(deployment_group.authorization_groups, groups) or
|
not deployment_group.teams_auth or
|
||||||
authorized_group?(app_deployment.authorization_groups, groups)
|
not deployment_group.groups_auth or
|
||||||
|
(authorized_group?(deployment_group.authorization_groups, groups) or
|
||||||
|
authorized_group?(app_deployment.authorization_groups, groups))
|
||||||
|
|
||||||
{:reply, app_access?, state}
|
{:reply, app_access?, state}
|
||||||
else
|
else
|
||||||
_ -> {:reply, false, state}
|
_ -> {:reply, false, state}
|
||||||
end
|
end
|
||||||
|
else
|
||||||
|
{:reply, true, state}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
|
@ -492,6 +505,7 @@ defmodule Livebook.Hubs.TeamClient do
|
||||||
clustering: nullify(deployment_group.clustering),
|
clustering: nullify(deployment_group.clustering),
|
||||||
url: nullify(deployment_group.url),
|
url: nullify(deployment_group.url),
|
||||||
teams_auth: deployment_group.teams_auth,
|
teams_auth: deployment_group.teams_auth,
|
||||||
|
groups_auth: deployment_group.groups_auth,
|
||||||
authorization_groups: authorization_groups
|
authorization_groups: authorization_groups
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
@ -531,6 +545,7 @@ defmodule Livebook.Hubs.TeamClient do
|
||||||
clustering: atomize(deployment_group_updated.clustering),
|
clustering: atomize(deployment_group_updated.clustering),
|
||||||
url: nullify(deployment_group_updated.url),
|
url: nullify(deployment_group_updated.url),
|
||||||
teams_auth: deployment_group_updated.teams_auth,
|
teams_auth: deployment_group_updated.teams_auth,
|
||||||
|
groups_auth: deployment_group_updated.groups_auth,
|
||||||
authorization_groups: authorization_groups
|
authorization_groups: authorization_groups
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
@ -659,7 +674,6 @@ defmodule Livebook.Hubs.TeamClient do
|
||||||
|
|
||||||
defp handle_event(:deployment_group_created, %Teams.DeploymentGroup{} = deployment_group, state) do
|
defp handle_event(:deployment_group_created, %Teams.DeploymentGroup{} = deployment_group, state) do
|
||||||
Teams.Broadcasts.deployment_group_created(deployment_group)
|
Teams.Broadcasts.deployment_group_created(deployment_group)
|
||||||
|
|
||||||
put_deployment_group(state, deployment_group)
|
put_deployment_group(state, deployment_group)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -673,6 +687,16 @@ defmodule Livebook.Hubs.TeamClient do
|
||||||
|
|
||||||
defp handle_event(:deployment_group_updated, %Teams.DeploymentGroup{} = deployment_group, state) do
|
defp handle_event(:deployment_group_updated, %Teams.DeploymentGroup{} = deployment_group, state) do
|
||||||
Teams.Broadcasts.deployment_group_updated(deployment_group)
|
Teams.Broadcasts.deployment_group_updated(deployment_group)
|
||||||
|
|
||||||
|
with {:ok, current_deployment_group} <- fetch_deployment_group(deployment_group.id, state) do
|
||||||
|
if state.deployment_group_id == deployment_group.id and
|
||||||
|
(current_deployment_group.authorization_groups != deployment_group.authorization_groups or
|
||||||
|
current_deployment_group.groups_auth != deployment_group.groups_auth or
|
||||||
|
current_deployment_group.teams_auth != deployment_group.teams_auth) do
|
||||||
|
Teams.Broadcasts.server_authorization_updated(deployment_group)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
put_deployment_group(state, deployment_group)
|
put_deployment_group(state, deployment_group)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,7 @@ defmodule Livebook.Teams.Broadcasts do
|
||||||
@app_deployments_topic "teams:app_deployments"
|
@app_deployments_topic "teams:app_deployments"
|
||||||
@clients_topic "teams:clients"
|
@clients_topic "teams:clients"
|
||||||
@deployment_groups_topic "teams:deployment_groups"
|
@deployment_groups_topic "teams:deployment_groups"
|
||||||
|
@app_server_topic "teams:app_server"
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Subscribes to one or more subtopics in `"teams"`.
|
Subscribes to one or more subtopics in `"teams"`.
|
||||||
|
@ -31,9 +32,13 @@ defmodule Livebook.Teams.Broadcasts do
|
||||||
Topic `#{@deployment_groups_topic}`:
|
Topic `#{@deployment_groups_topic}`:
|
||||||
|
|
||||||
* `{:deployment_group_created, DeploymentGroup.t()}`
|
* `{:deployment_group_created, DeploymentGroup.t()}`
|
||||||
* `{:deployment_group_update, DeploymentGroup.t()}`
|
* `{:deployment_group_updated, DeploymentGroup.t()}`
|
||||||
* `{:deployment_group_deleted, DeploymentGroup.t()}`
|
* `{:deployment_group_deleted, DeploymentGroup.t()}`
|
||||||
|
|
||||||
|
Topic `#{@app_server_topic}`:
|
||||||
|
|
||||||
|
* `{:server_authorization_updated, DeploymentGroup.t()}`
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@spec subscribe(atom() | list(atom())) :: :ok | {:error, term()}
|
@spec subscribe(atom() | list(atom())) :: :ok | {:error, term()}
|
||||||
def subscribe(topics) when is_list(topics) do
|
def subscribe(topics) when is_list(topics) do
|
||||||
|
@ -132,6 +137,14 @@ defmodule Livebook.Teams.Broadcasts do
|
||||||
broadcast(@agents_topic, {:agent_left, agent})
|
broadcast(@agents_topic, {:agent_left, agent})
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Broadcasts under `#{@app_server_topic}` topic when hub received a updated deployment group that changed which groups have access to the server.
|
||||||
|
"""
|
||||||
|
@spec server_authorization_updated(Teams.DeploymentGroup.t()) :: broadcast()
|
||||||
|
def server_authorization_updated(%Teams.DeploymentGroup{} = deployment_group) do
|
||||||
|
broadcast(@app_server_topic, {:server_authorization_updated, deployment_group})
|
||||||
|
end
|
||||||
|
|
||||||
defp broadcast(topic, message) do
|
defp broadcast(topic, message) do
|
||||||
Phoenix.PubSub.broadcast(Livebook.PubSub, topic, message)
|
Phoenix.PubSub.broadcast(Livebook.PubSub, topic, message)
|
||||||
end
|
end
|
||||||
|
|
|
@ -13,6 +13,7 @@ defmodule Livebook.Teams.DeploymentGroup do
|
||||||
clustering: :auto | :dns | nil,
|
clustering: :auto | :dns | nil,
|
||||||
hub_id: String.t() | nil,
|
hub_id: String.t() | nil,
|
||||||
teams_auth: boolean(),
|
teams_auth: boolean(),
|
||||||
|
groups_auth: boolean(),
|
||||||
authorization_groups: Ecto.Schema.embeds_many(Teams.AuthorizationGroup.t()),
|
authorization_groups: Ecto.Schema.embeds_many(Teams.AuthorizationGroup.t()),
|
||||||
secrets: Ecto.Schema.has_many(Secrets.Secret.t()),
|
secrets: Ecto.Schema.has_many(Secrets.Secret.t()),
|
||||||
agent_keys: Ecto.Schema.has_many(Teams.AgentKey.t()),
|
agent_keys: Ecto.Schema.has_many(Teams.AgentKey.t()),
|
||||||
|
@ -27,6 +28,7 @@ defmodule Livebook.Teams.DeploymentGroup do
|
||||||
field :clustering, Ecto.Enum, values: [:auto, :dns]
|
field :clustering, Ecto.Enum, values: [:auto, :dns]
|
||||||
field :url, :string
|
field :url, :string
|
||||||
field :teams_auth, :boolean, default: true
|
field :teams_auth, :boolean, default: true
|
||||||
|
field :groups_auth, :boolean, default: false
|
||||||
|
|
||||||
has_many :secrets, Secrets.Secret
|
has_many :secrets, Secrets.Secret
|
||||||
has_many :agent_keys, Teams.AgentKey
|
has_many :agent_keys, Teams.AgentKey
|
||||||
|
@ -36,7 +38,7 @@ defmodule Livebook.Teams.DeploymentGroup do
|
||||||
|
|
||||||
def changeset(deployment_group, attrs \\ %{}) do
|
def changeset(deployment_group, attrs \\ %{}) do
|
||||||
deployment_group
|
deployment_group
|
||||||
|> cast(attrs, [:id, :name, :mode, :hub_id, :clustering, :url, :teams_auth])
|
|> cast(attrs, [:id, :name, :mode, :hub_id, :clustering, :url])
|
||||||
|> validate_required([:name, :mode])
|
|> validate_required([:name, :mode])
|
||||||
|> update_change(:url, fn url ->
|
|> update_change(:url, fn url ->
|
||||||
if url do
|
if url do
|
||||||
|
|
|
@ -172,8 +172,7 @@ defmodule Livebook.Teams.Requests do
|
||||||
name: deployment_group.name,
|
name: deployment_group.name,
|
||||||
mode: deployment_group.mode,
|
mode: deployment_group.mode,
|
||||||
clustering: deployment_group.clustering,
|
clustering: deployment_group.clustering,
|
||||||
url: deployment_group.url,
|
url: deployment_group.url
|
||||||
teams_auth: deployment_group.teams_auth
|
|
||||||
}
|
}
|
||||||
|
|
||||||
post("/api/v1/org/deployment-groups", params, team)
|
post("/api/v1/org/deployment-groups", params, team)
|
||||||
|
|
|
@ -12,12 +12,15 @@ defmodule Livebook.Users.User do
|
||||||
|
|
||||||
alias Livebook.Utils
|
alias Livebook.Utils
|
||||||
|
|
||||||
|
@type access_type :: :full | :apps
|
||||||
|
|
||||||
@type t :: %__MODULE__{
|
@type t :: %__MODULE__{
|
||||||
id: id(),
|
id: id(),
|
||||||
name: String.t() | nil,
|
name: String.t() | nil,
|
||||||
email: String.t() | nil,
|
email: String.t() | nil,
|
||||||
avatar_url: String.t() | nil,
|
avatar_url: String.t() | nil,
|
||||||
restricted_apps_groups: list(map()) | nil,
|
access_type: access_type(),
|
||||||
|
groups: list(map()) | nil,
|
||||||
payload: map() | nil,
|
payload: map() | nil,
|
||||||
hex_color: hex_color()
|
hex_color: hex_color()
|
||||||
}
|
}
|
||||||
|
@ -29,7 +32,8 @@ defmodule Livebook.Users.User do
|
||||||
field :name, :string
|
field :name, :string
|
||||||
field :email, :string
|
field :email, :string
|
||||||
field :avatar_url, :string
|
field :avatar_url, :string
|
||||||
field :restricted_apps_groups, {:array, :map}
|
field :access_type, Ecto.Enum, values: ~w[full apps]a, default: :full
|
||||||
|
field :groups, {:array, :map}, default: []
|
||||||
field :payload, :map
|
field :payload, :map
|
||||||
field :hex_color, Livebook.EctoTypes.HexColor
|
field :hex_color, Livebook.EctoTypes.HexColor
|
||||||
end
|
end
|
||||||
|
@ -44,15 +48,17 @@ defmodule Livebook.Users.User do
|
||||||
name: nil,
|
name: nil,
|
||||||
email: nil,
|
email: nil,
|
||||||
avatar_url: nil,
|
avatar_url: nil,
|
||||||
restricted_apps_groups: nil,
|
access_type: :full,
|
||||||
|
groups: [],
|
||||||
payload: nil,
|
payload: nil,
|
||||||
hex_color: Livebook.EctoTypes.HexColor.random()
|
hex_color: Livebook.EctoTypes.HexColor.random()
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@doc false
|
||||||
def changeset(user, attrs \\ %{}) do
|
def changeset(user, attrs \\ %{}) do
|
||||||
user
|
user
|
||||||
|> cast(attrs, [:name, :email, :avatar_url, :restricted_apps_groups, :hex_color, :payload])
|
|> cast(attrs, [:name, :email, :avatar_url, :access_type, :groups, :hex_color, :payload])
|
||||||
|> validate_required([:hex_color])
|
|> validate_required([:hex_color])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -58,6 +58,9 @@ defmodule Livebook.ZTA do
|
||||||
* `:id` - a string that uniquely identifies the user
|
* `:id` - a string that uniquely identifies the user
|
||||||
* `:name` - the user name
|
* `:name` - the user name
|
||||||
* `:email` - the user email
|
* `:email` - the user email
|
||||||
|
* `:avatar_url` - the user avatar
|
||||||
|
* `:access_type` - the user access type
|
||||||
|
* `:groups` - the user groups
|
||||||
* `:payload` - the provider payload
|
* `:payload` - the provider payload
|
||||||
|
|
||||||
Note that none of the keys are required. The metadata returned depends
|
Note that none of the keys are required. The metadata returned depends
|
||||||
|
@ -67,6 +70,9 @@ defmodule Livebook.ZTA do
|
||||||
optional(:id) => String.t(),
|
optional(:id) => String.t(),
|
||||||
optional(:name) => String.t(),
|
optional(:name) => String.t(),
|
||||||
optional(:email) => String.t(),
|
optional(:email) => String.t(),
|
||||||
|
optional(:avatar_url) => String.t() | nil,
|
||||||
|
optional(:access_type) => Livebook.Users.User.access_type(),
|
||||||
|
optional(:groups) => list(map()),
|
||||||
optional(:payload) => map()
|
optional(:payload) => map()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -159,6 +159,15 @@ defmodule Livebook.ZTA.LivebookTeams do
|
||||||
|
|
||||||
defp get_user_info(team, access_token) do
|
defp get_user_info(team, access_token) do
|
||||||
with {:ok, payload} <- Teams.Requests.get_user_info(team, access_token) do
|
with {:ok, payload} <- Teams.Requests.get_user_info(team, access_token) do
|
||||||
|
{:ok, build_metadata(team.id, payload)}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Returns the user metadata from given payload.
|
||||||
|
"""
|
||||||
|
@spec build_metadata(String.t(), map()) :: Livebook.ZTA.metadata()
|
||||||
|
def build_metadata(hub_id, payload) do
|
||||||
%{
|
%{
|
||||||
"id" => id,
|
"id" => id,
|
||||||
"name" => name,
|
"name" => name,
|
||||||
|
@ -167,23 +176,19 @@ defmodule Livebook.ZTA.LivebookTeams do
|
||||||
"avatar_url" => avatar_url
|
"avatar_url" => avatar_url
|
||||||
} = payload
|
} = payload
|
||||||
|
|
||||||
restricted_apps_groups =
|
access_type =
|
||||||
if Livebook.Hubs.TeamClient.user_full_access?(team.id, groups) do
|
if Livebook.Hubs.TeamClient.user_full_access?(hub_id, groups),
|
||||||
nil
|
do: :full,
|
||||||
else
|
else: :apps
|
||||||
groups
|
|
||||||
end
|
|
||||||
|
|
||||||
metadata = %{
|
%{
|
||||||
id: id,
|
id: id,
|
||||||
name: name,
|
name: name,
|
||||||
avatar_url: avatar_url,
|
avatar_url: avatar_url,
|
||||||
restricted_apps_groups: restricted_apps_groups,
|
access_type: access_type,
|
||||||
|
groups: groups,
|
||||||
email: email,
|
email: email,
|
||||||
payload: payload
|
payload: payload
|
||||||
}
|
}
|
||||||
|
|
||||||
{:ok, metadata}
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -12,8 +12,13 @@ defmodule LivebookWeb.AppLive do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def mount(_params, _session, socket) when not socket.assigns.app_authorized? do
|
def mount(%{"slug" => slug}, _session, socket) when not socket.assigns.app_authorized? do
|
||||||
{:ok, socket, layout: false}
|
if connected?(socket) do
|
||||||
|
Livebook.Teams.Broadcasts.subscribe(:app_deployments)
|
||||||
|
end
|
||||||
|
|
||||||
|
{:ok, app} = Livebook.Apps.fetch_app(slug)
|
||||||
|
{:ok, assign(socket, app: app), layout: false}
|
||||||
end
|
end
|
||||||
|
|
||||||
def mount(%{"slug" => slug}, _session, socket) do
|
def mount(%{"slug" => slug}, _session, socket) do
|
||||||
|
@ -22,6 +27,7 @@ defmodule LivebookWeb.AppLive do
|
||||||
|
|
||||||
if connected?(socket) do
|
if connected?(socket) do
|
||||||
Livebook.App.subscribe(slug)
|
Livebook.App.subscribe(slug)
|
||||||
|
Livebook.Teams.Broadcasts.subscribe(:app_deployments)
|
||||||
end
|
end
|
||||||
|
|
||||||
{:ok, assign(socket, app: app)}
|
{:ok, assign(socket, app: app)}
|
||||||
|
@ -122,6 +128,18 @@ defmodule LivebookWeb.AppLive do
|
||||||
{:noreply, assign(socket, :app, app)}
|
{:noreply, assign(socket, :app, app)}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def handle_info(
|
||||||
|
{:app_deployment_updated, %{slug: slug}},
|
||||||
|
%{assigns: %{app: %{slug: slug} = app}} = socket
|
||||||
|
) do
|
||||||
|
if socket.assigns.app_authorized? and
|
||||||
|
Livebook.Apps.authorized?(app, socket.assigns.current_user) do
|
||||||
|
{:noreply, socket}
|
||||||
|
else
|
||||||
|
{:noreply, redirect(socket, to: ~p"/apps/#{slug}")}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def handle_info(_message, socket), do: {:noreply, socket}
|
def handle_info(_message, socket), do: {:noreply, socket}
|
||||||
|
|
||||||
defp active_sessions(sessions) do
|
defp active_sessions(sessions) do
|
||||||
|
|
|
@ -24,8 +24,13 @@ defmodule LivebookWeb.AppSessionLive do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def mount(_params, _session, socket) when not socket.assigns.app_authorized? do
|
def mount(%{"slug" => slug, "id" => session_id}, _session, socket)
|
||||||
{:ok, socket, layout: false}
|
when not socket.assigns.app_authorized? do
|
||||||
|
if connected?(socket) do
|
||||||
|
Livebook.Teams.Broadcasts.subscribe(:app_deployments)
|
||||||
|
end
|
||||||
|
|
||||||
|
{:ok, assign(socket, slug: slug, session: %{id: session_id}), layout: false}
|
||||||
end
|
end
|
||||||
|
|
||||||
def mount(%{"slug" => slug, "id" => session_id}, _session, socket) do
|
def mount(%{"slug" => slug, "id" => session_id}, _session, socket) do
|
||||||
|
@ -388,7 +393,8 @@ defmodule LivebookWeb.AppSessionLive do
|
||||||
# should't have access.
|
# should't have access.
|
||||||
{:ok, app} = Livebook.Apps.fetch_app(slug)
|
{:ok, app} = Livebook.Apps.fetch_app(slug)
|
||||||
|
|
||||||
if Livebook.Apps.authorized?(app, socket.assigns.current_user) do
|
if socket.assigns.app_authorized? and
|
||||||
|
Livebook.Apps.authorized?(app, socket.assigns.current_user) do
|
||||||
{:noreply, socket}
|
{:noreply, socket}
|
||||||
else
|
else
|
||||||
{:noreply, redirect(socket, to: ~p"/apps/#{slug}/sessions/#{socket.assigns.session.id}")}
|
{:noreply, redirect(socket, to: ~p"/apps/#{slug}/sessions/#{socket.assigns.session.id}")}
|
||||||
|
|
|
@ -4,7 +4,7 @@ defmodule LivebookWeb.AppsLive do
|
||||||
@impl true
|
@impl true
|
||||||
def mount(_params, _session, socket) do
|
def mount(_params, _session, socket) do
|
||||||
if connected?(socket) do
|
if connected?(socket) do
|
||||||
Livebook.Teams.Broadcasts.subscribe(:app_deployments)
|
Livebook.Teams.Broadcasts.subscribe(:app_server)
|
||||||
Livebook.Apps.subscribe()
|
Livebook.Apps.subscribe()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -101,7 +101,7 @@ defmodule LivebookWeb.AppsLive do
|
||||||
{:noreply, update(socket, :apps, &LivebookWeb.AppComponents.update_app_list(&1, event))}
|
{:noreply, update(socket, :apps, &LivebookWeb.AppComponents.update_app_list(&1, event))}
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_info({:app_deployment_updated, _app_deployment}, socket) do
|
def handle_info({:server_authorization_updated, _}, socket) do
|
||||||
apps = Livebook.Apps.list_authorized_apps(socket.assigns.current_user)
|
apps = Livebook.Apps.list_authorized_apps(socket.assigns.current_user)
|
||||||
{:noreply, assign(socket, :apps, apps)}
|
{:noreply, assign(socket, :apps, apps)}
|
||||||
end
|
end
|
||||||
|
|
|
@ -5,6 +5,7 @@ defmodule LivebookWeb.AuthHook do
|
||||||
|
|
||||||
def on_mount(:default, _params, session, socket) do
|
def on_mount(:default, _params, session, socket) do
|
||||||
uri = get_connect_info(socket, :uri)
|
uri = get_connect_info(socket, :uri)
|
||||||
|
socket = attach_hook(socket, :authorization_subscription, :handle_info, &handle_info/2)
|
||||||
|
|
||||||
if LivebookWeb.AuthPlug.authorized?(session || %{}, uri.port) do
|
if LivebookWeb.AuthPlug.authorized?(session || %{}, uri.port) do
|
||||||
{:cont, socket}
|
{:cont, socket}
|
||||||
|
@ -12,4 +13,19 @@ defmodule LivebookWeb.AuthHook do
|
||||||
{:halt, redirect(socket, to: ~p"/")}
|
{:halt, redirect(socket, to: ~p"/")}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp handle_info({:server_authorization_updated, %{hub_id: hub_id}}, socket) do
|
||||||
|
# We already updated the current user, so we just need to force the redirection.
|
||||||
|
# But, for apps, we redirect directly from the app session
|
||||||
|
current_user = socket.assigns.current_user
|
||||||
|
|
||||||
|
if current_user.access_type == :full and
|
||||||
|
Livebook.Hubs.TeamClient.user_full_access?(hub_id, current_user.groups) do
|
||||||
|
{:halt, socket}
|
||||||
|
else
|
||||||
|
{:halt, redirect(socket, to: ~p"/")}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp handle_info(_message, socket), do: {:cont, socket}
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
defmodule LivebookWeb.UserHook do
|
defmodule LivebookWeb.UserHook do
|
||||||
|
use LivebookWeb, :verified_routes
|
||||||
|
|
||||||
import Phoenix.Component
|
import Phoenix.Component
|
||||||
import Phoenix.LiveView
|
import Phoenix.LiveView
|
||||||
|
|
||||||
|
@ -13,10 +15,12 @@ defmodule LivebookWeb.UserHook do
|
||||||
user_data = connect_params["user_data"] || session["user_data"]
|
user_data = connect_params["user_data"] || session["user_data"]
|
||||||
LivebookWeb.UserPlug.build_current_user(session, identity_data, user_data)
|
LivebookWeb.UserPlug.build_current_user(session, identity_data, user_data)
|
||||||
end)
|
end)
|
||||||
|> attach_hook(:current_user_subscription, :handle_info, &info/2)
|
|> attach_hook(:current_user_subscription, :handle_info, &handle_info/2)
|
||||||
|
|> attach_hook(:server_authorization_subscription, :handle_info, &handle_info/2)
|
||||||
|
|
||||||
if connected?(socket) do
|
if connected?(socket) do
|
||||||
Livebook.Users.subscribe(socket.assigns.current_user.id)
|
Livebook.Users.subscribe(socket.assigns.current_user.id)
|
||||||
|
Livebook.Teams.Broadcasts.subscribe(:app_server)
|
||||||
end
|
end
|
||||||
|
|
||||||
Logger.metadata(Livebook.Utils.logger_users_metadata([socket.assigns.current_user]))
|
Logger.metadata(Livebook.Utils.logger_users_metadata([socket.assigns.current_user]))
|
||||||
|
@ -24,12 +28,25 @@ defmodule LivebookWeb.UserHook do
|
||||||
{:cont, socket}
|
{:cont, socket}
|
||||||
end
|
end
|
||||||
|
|
||||||
defp info(
|
defp handle_info(
|
||||||
{:user_change, %{id: id} = user},
|
{:user_change, %{id: id} = user},
|
||||||
%{assigns: %{current_user: %{id: id}}} = socket
|
%{assigns: %{current_user: %{id: id}}} = socket
|
||||||
) do
|
) do
|
||||||
{:halt, assign(socket, :current_user, user)}
|
{:halt, assign(socket, :current_user, user)}
|
||||||
end
|
end
|
||||||
|
|
||||||
defp info(_message, socket), do: {:cont, socket}
|
defp handle_info({:server_authorization_updated, deployment_group}, socket) do
|
||||||
|
# Since we checks if the updated deployment group we received belongs
|
||||||
|
# to the current app server, we don't need to check here.
|
||||||
|
current_user = socket.assigns.current_user
|
||||||
|
hub_id = deployment_group.hub_id
|
||||||
|
metadata = Livebook.ZTA.LivebookTeams.build_metadata(hub_id, current_user.payload)
|
||||||
|
|
||||||
|
case Livebook.Users.update_user(current_user, metadata) do
|
||||||
|
{:ok, user} -> {:cont, assign(socket, :current_user, user)}
|
||||||
|
_otherwise -> {:cont, socket}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp handle_info(_message, socket), do: {:cont, socket}
|
||||||
end
|
end
|
||||||
|
|
|
@ -64,7 +64,7 @@ defmodule LivebookWeb.AuthPlug do
|
||||||
get_session(conn),
|
get_session(conn),
|
||||||
conn.assigns.identity_data,
|
conn.assigns.identity_data,
|
||||||
conn.assigns.user_data
|
conn.assigns.user_data
|
||||||
).restricted_apps_groups == nil
|
).access_type == :full
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
|
@ -77,7 +77,7 @@ defmodule LivebookWeb.AuthPlug do
|
||||||
session,
|
session,
|
||||||
session["identity_data"],
|
session["identity_data"],
|
||||||
session["user_data"]
|
session["user_data"]
|
||||||
).restricted_apps_groups == nil
|
).access_type == :full
|
||||||
end
|
end
|
||||||
|
|
||||||
defp authenticate(conn) do
|
defp authenticate(conn) do
|
||||||
|
@ -136,6 +136,7 @@ defmodule LivebookWeb.AuthPlug do
|
||||||
conn
|
conn
|
||||||
|> put_status(:unauthorized)
|
|> put_status(:unauthorized)
|
||||||
|> put_view(LivebookWeb.ErrorHTML)
|
|> put_view(LivebookWeb.ErrorHTML)
|
||||||
|
|> put_root_layout(false)
|
||||||
|> render("401.html", %{details: "You don't have permission to access this server"})
|
|> render("401.html", %{details: "You don't have permission to access this server"})
|
||||||
|> halt()
|
|> halt()
|
||||||
end
|
end
|
||||||
|
|
|
@ -34,7 +34,7 @@ defmodule LivebookWeb.UserPlug do
|
||||||
end
|
end
|
||||||
|
|
||||||
defp ensure_user_identity(conn) do
|
defp ensure_user_identity(conn) do
|
||||||
{_type, module, _key} = Livebook.Config.identity_provider()
|
{_type, module, _key} = identity_provider(conn)
|
||||||
{conn, identity_data} = module.authenticate(LivebookWeb.ZTA, conn, [])
|
{conn, identity_data} = module.authenticate(LivebookWeb.ZTA, conn, [])
|
||||||
|
|
||||||
cond do
|
cond do
|
||||||
|
@ -128,4 +128,22 @@ defmodule LivebookWeb.UserPlug do
|
||||||
"user_data" => conn.assigns.user_data
|
"user_data" => conn.assigns.user_data
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Returns the identity provider configuration for the given `conn` or
|
||||||
|
`session`.
|
||||||
|
|
||||||
|
This mirrors `Livebook.Config.identity_provider/0`, except the it can
|
||||||
|
be overridden in tests, for each connection.
|
||||||
|
"""
|
||||||
|
@spec identity_provider(Plug.Conn.t() | map()) :: {atom(), module, binary}
|
||||||
|
if Mix.env() == :test do
|
||||||
|
def identity_provider(%Plug.Conn{} = conn), do: identity_provider(get_session(conn))
|
||||||
|
|
||||||
|
def identity_provider(%{} = session) do
|
||||||
|
session["identity_provider_test_override"] || Livebook.Config.identity_provider()
|
||||||
|
end
|
||||||
|
else
|
||||||
|
def identity_provider(_), do: Livebook.Config.identity_provider()
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -22,4 +22,6 @@ defmodule LivebookProto.DeploymentGroup do
|
||||||
repeated: true,
|
repeated: true,
|
||||||
type: LivebookProto.AuthorizationGroup,
|
type: LivebookProto.AuthorizationGroup,
|
||||||
json_name: "authorizationGroups"
|
json_name: "authorizationGroups"
|
||||||
|
|
||||||
|
field :groups_auth, 13, type: :bool, json_name: "groupsAuth"
|
||||||
end
|
end
|
||||||
|
|
|
@ -21,4 +21,6 @@ defmodule LivebookProto.DeploymentGroupUpdated do
|
||||||
repeated: true,
|
repeated: true,
|
||||||
type: LivebookProto.AuthorizationGroup,
|
type: LivebookProto.AuthorizationGroup,
|
||||||
json_name: "authorizationGroups"
|
json_name: "authorizationGroups"
|
||||||
|
|
||||||
|
field :groups_auth, 12, type: :bool, json_name: "groupsAuth"
|
||||||
end
|
end
|
||||||
|
|
|
@ -67,6 +67,7 @@ message DeploymentGroup {
|
||||||
repeated EnvironmentVariable environment_variables = 10;
|
repeated EnvironmentVariable environment_variables = 10;
|
||||||
bool teams_auth = 11;
|
bool teams_auth = 11;
|
||||||
repeated AuthorizationGroup authorization_groups = 12;
|
repeated AuthorizationGroup authorization_groups = 12;
|
||||||
|
bool groups_auth = 13;
|
||||||
}
|
}
|
||||||
|
|
||||||
message DeploymentGroupCreated {
|
message DeploymentGroupCreated {
|
||||||
|
@ -93,6 +94,7 @@ message DeploymentGroupUpdated {
|
||||||
repeated EnvironmentVariable environment_variables = 9;
|
repeated EnvironmentVariable environment_variables = 9;
|
||||||
bool teams_auth = 10;
|
bool teams_auth = 10;
|
||||||
repeated AuthorizationGroup authorization_groups = 11;
|
repeated AuthorizationGroup authorization_groups = 11;
|
||||||
|
bool groups_auth = 12;
|
||||||
}
|
}
|
||||||
|
|
||||||
message DeploymentGroupDeleted {
|
message DeploymentGroupDeleted {
|
||||||
|
|
|
@ -4,6 +4,7 @@ defmodule LivebookWeb.Integration.AdminLiveTest do
|
||||||
|
|
||||||
import Phoenix.LiveViewTest
|
import Phoenix.LiveViewTest
|
||||||
|
|
||||||
|
describe "topbar" do
|
||||||
setup %{teams_auth: teams_auth} do
|
setup %{teams_auth: teams_auth} do
|
||||||
Application.put_env(:livebook, :teams_auth, teams_auth)
|
Application.put_env(:livebook, :teams_auth, teams_auth)
|
||||||
on_exit(fn -> Application.delete_env(:livebook, :teams_auth) end)
|
on_exit(fn -> Application.delete_env(:livebook, :teams_auth) end)
|
||||||
|
@ -28,4 +29,182 @@ defmodule LivebookWeb.Integration.AdminLiveTest do
|
||||||
"You are running an offline Workspace for deployment. You cannot modify its settings."
|
"You are running an offline Workspace for deployment. You cannot modify its settings."
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "authorization" do
|
||||||
|
setup %{conn: conn, node: node} do
|
||||||
|
Livebook.Teams.Broadcasts.subscribe([:agents, :app_server])
|
||||||
|
Livebook.Apps.subscribe()
|
||||||
|
|
||||||
|
{_agent_key, org, deployment_group, team} = create_agent_team_hub(node)
|
||||||
|
|
||||||
|
# we wait until the agent_connected is received by livebook
|
||||||
|
hub_id = team.id
|
||||||
|
deployment_group_id = to_string(deployment_group.id)
|
||||||
|
org_id = to_string(org.id)
|
||||||
|
|
||||||
|
assert_receive {:agent_joined,
|
||||||
|
%{
|
||||||
|
hub_id: ^hub_id,
|
||||||
|
org_id: ^org_id,
|
||||||
|
deployment_group_id: ^deployment_group_id
|
||||||
|
}}
|
||||||
|
|
||||||
|
start_supervised!(
|
||||||
|
{Livebook.ZTA.LivebookTeams, name: LivebookWeb.ZTA, identity_key: team.id}
|
||||||
|
)
|
||||||
|
|
||||||
|
{conn, code} = authenticate_user_on_teams(conn, node, team)
|
||||||
|
|
||||||
|
{:ok, conn: conn, code: code, deployment_group: deployment_group, org: org, team: team}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "renders unauthorized admin page if user doesn't have full access",
|
||||||
|
%{conn: conn, node: node, code: code} = context do
|
||||||
|
erpc_call(node, :toggle_groups_authorization, [context.deployment_group])
|
||||||
|
oidc_provider = erpc_call(node, :create_oidc_provider, [context.org])
|
||||||
|
|
||||||
|
authorization_group =
|
||||||
|
erpc_call(node, :create_authorization_group, [
|
||||||
|
%{
|
||||||
|
group_name: "marketing",
|
||||||
|
access_type: :apps,
|
||||||
|
prefixes: ["dev-"],
|
||||||
|
oidc_provider: oidc_provider,
|
||||||
|
deployment_group: context.deployment_group
|
||||||
|
}
|
||||||
|
])
|
||||||
|
|
||||||
|
erpc_call(node, :update_user_info_groups, [
|
||||||
|
code,
|
||||||
|
[
|
||||||
|
%{
|
||||||
|
"provider_id" => to_string(oidc_provider.id),
|
||||||
|
"group_name" => authorization_group.group_name
|
||||||
|
}
|
||||||
|
]
|
||||||
|
])
|
||||||
|
|
||||||
|
assert conn
|
||||||
|
|> get(~p"/settings")
|
||||||
|
|> html_response(401) =~ "Not authorized"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "shows admin page if user have full access",
|
||||||
|
%{conn: conn, node: node, code: code} = context do
|
||||||
|
erpc_call(node, :toggle_groups_authorization, [context.deployment_group])
|
||||||
|
oidc_provider = erpc_call(node, :create_oidc_provider, [context.org])
|
||||||
|
|
||||||
|
authorization_group =
|
||||||
|
erpc_call(node, :create_authorization_group, [
|
||||||
|
%{
|
||||||
|
group_name: "marketing",
|
||||||
|
access_type: :app_server,
|
||||||
|
oidc_provider: oidc_provider,
|
||||||
|
deployment_group: context.deployment_group
|
||||||
|
}
|
||||||
|
])
|
||||||
|
|
||||||
|
erpc_call(node, :update_user_info_groups, [
|
||||||
|
code,
|
||||||
|
[
|
||||||
|
%{
|
||||||
|
"provider_id" => to_string(oidc_provider.id),
|
||||||
|
"group_name" => authorization_group.group_name
|
||||||
|
}
|
||||||
|
]
|
||||||
|
])
|
||||||
|
|
||||||
|
{:ok, _view, html} = live(conn, ~p"/settings")
|
||||||
|
assert html =~ "System settings"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "renders unauthorized if loses the access in real-time",
|
||||||
|
%{conn: conn, node: node, code: code} = context do
|
||||||
|
{:ok, deployment_group} =
|
||||||
|
erpc_call(node, :toggle_groups_authorization, [context.deployment_group])
|
||||||
|
|
||||||
|
oidc_provider = erpc_call(node, :create_oidc_provider, [context.org])
|
||||||
|
|
||||||
|
authorization_group =
|
||||||
|
erpc_call(node, :create_authorization_group, [
|
||||||
|
%{
|
||||||
|
group_name: "marketing",
|
||||||
|
access_type: :app_server,
|
||||||
|
oidc_provider: oidc_provider,
|
||||||
|
deployment_group: deployment_group
|
||||||
|
}
|
||||||
|
])
|
||||||
|
|
||||||
|
erpc_call(node, :update_user_info_groups, [
|
||||||
|
code,
|
||||||
|
[
|
||||||
|
%{
|
||||||
|
"provider_id" => to_string(oidc_provider.id),
|
||||||
|
"group_name" => authorization_group.group_name
|
||||||
|
}
|
||||||
|
]
|
||||||
|
])
|
||||||
|
|
||||||
|
{:ok, view, _html} = live(conn, ~p"/settings")
|
||||||
|
assert render(view) =~ "System settings"
|
||||||
|
|
||||||
|
erpc_call(node, :update_authorization_group, [
|
||||||
|
authorization_group,
|
||||||
|
%{access_type: :apps, prefixes: ["ops-"]}
|
||||||
|
])
|
||||||
|
|
||||||
|
id = to_string(deployment_group.id)
|
||||||
|
assert_receive {:server_authorization_updated, %{id: ^id}}
|
||||||
|
|
||||||
|
# If you lose access to the app server, we will redirect to "/"
|
||||||
|
assert_redirect view, ~p"/"
|
||||||
|
|
||||||
|
# And it will redirect to "/apps"
|
||||||
|
{:ok, view, _html} = live(conn, ~p"/apps")
|
||||||
|
assert render(view) =~ "No apps running."
|
||||||
|
end
|
||||||
|
|
||||||
|
test "shows admin page if authentication is disabled",
|
||||||
|
%{conn: conn, node: node, code: code} = context do
|
||||||
|
{:ok, deployment_group} =
|
||||||
|
erpc_call(node, :toggle_groups_authorization, [context.deployment_group])
|
||||||
|
|
||||||
|
oidc_provider = erpc_call(node, :create_oidc_provider, [context.org])
|
||||||
|
|
||||||
|
authorization_group =
|
||||||
|
erpc_call(node, :create_authorization_group, [
|
||||||
|
%{
|
||||||
|
group_name: "marketing",
|
||||||
|
access_type: :apps,
|
||||||
|
prefixes: ["ops-"],
|
||||||
|
oidc_provider: oidc_provider,
|
||||||
|
deployment_group: deployment_group
|
||||||
|
}
|
||||||
|
])
|
||||||
|
|
||||||
|
erpc_call(node, :update_user_info_groups, [
|
||||||
|
code,
|
||||||
|
[
|
||||||
|
%{
|
||||||
|
"provider_id" => to_string(oidc_provider.id),
|
||||||
|
"group_name" => authorization_group.group_name
|
||||||
|
}
|
||||||
|
]
|
||||||
|
])
|
||||||
|
|
||||||
|
assert conn
|
||||||
|
|> get(~p"/settings")
|
||||||
|
|> html_response(401) =~ "Not authorized"
|
||||||
|
|
||||||
|
{:ok, %{teams_auth: false} = deployment_group} =
|
||||||
|
erpc_call(node, :toggle_teams_authentication, [deployment_group])
|
||||||
|
|
||||||
|
id = to_string(deployment_group.id)
|
||||||
|
assert_receive {:server_authorization_updated, %{id: ^id, teams_auth: false}}
|
||||||
|
|
||||||
|
{:ok, view, _html} = live(conn, ~p"/settings")
|
||||||
|
assert render(view) =~ "System settings"
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
253
test/livebook_teams/web/app_session_live_test.exs
Normal file
253
test/livebook_teams/web/app_session_live_test.exs
Normal file
|
@ -0,0 +1,253 @@
|
||||||
|
defmodule LivebookWeb.Integration.AppSessionLiveTest do
|
||||||
|
use Livebook.TeamsIntegrationCase, async: false
|
||||||
|
|
||||||
|
import Phoenix.LiveViewTest
|
||||||
|
|
||||||
|
describe "authorized apps" do
|
||||||
|
setup %{conn: conn, node: node} do
|
||||||
|
Livebook.Teams.Broadcasts.subscribe([:agents, :app_deployments, :app_server])
|
||||||
|
Livebook.Apps.subscribe()
|
||||||
|
|
||||||
|
{_agent_key, org, deployment_group, team} = create_agent_team_hub(node)
|
||||||
|
|
||||||
|
# we wait until the agent_connected is received by livebook
|
||||||
|
hub_id = team.id
|
||||||
|
deployment_group_id = to_string(deployment_group.id)
|
||||||
|
org_id = to_string(org.id)
|
||||||
|
|
||||||
|
assert_receive {:agent_joined,
|
||||||
|
%{
|
||||||
|
hub_id: ^hub_id,
|
||||||
|
org_id: ^org_id,
|
||||||
|
deployment_group_id: ^deployment_group_id
|
||||||
|
}}
|
||||||
|
|
||||||
|
start_supervised!(
|
||||||
|
{Livebook.ZTA.LivebookTeams, name: LivebookWeb.ZTA, identity_key: team.id}
|
||||||
|
)
|
||||||
|
|
||||||
|
{conn, code} = authenticate_user_on_teams(conn, node, team)
|
||||||
|
|
||||||
|
{:ok, conn: conn, code: code, deployment_group: deployment_group, org: org, team: team}
|
||||||
|
end
|
||||||
|
|
||||||
|
@tag :tmp_dir
|
||||||
|
test "shows app if user doesn't have full access",
|
||||||
|
%{conn: conn, node: node, code: code, tmp_dir: tmp_dir} = context do
|
||||||
|
erpc_call(node, :toggle_groups_authorization, [context.deployment_group])
|
||||||
|
oidc_provider = erpc_call(node, :create_oidc_provider, [context.org])
|
||||||
|
|
||||||
|
authorization_group =
|
||||||
|
erpc_call(node, :create_authorization_group, [
|
||||||
|
%{
|
||||||
|
group_name: "marketing",
|
||||||
|
access_type: :apps,
|
||||||
|
prefixes: ["dev-"],
|
||||||
|
oidc_provider: oidc_provider,
|
||||||
|
deployment_group: context.deployment_group
|
||||||
|
}
|
||||||
|
])
|
||||||
|
|
||||||
|
erpc_call(node, :update_user_info_groups, [
|
||||||
|
code,
|
||||||
|
[
|
||||||
|
%{
|
||||||
|
"provider_id" => to_string(oidc_provider.id),
|
||||||
|
"group_name" => authorization_group.group_name
|
||||||
|
}
|
||||||
|
]
|
||||||
|
])
|
||||||
|
|
||||||
|
slug = "dev-app"
|
||||||
|
pid = deploy_app(slug, context.team, context.org, context.deployment_group, tmp_dir, node)
|
||||||
|
session_id = Livebook.App.get_session_id(pid, user: Livebook.Users.User.new())
|
||||||
|
|
||||||
|
{:ok, _view, html} = live(conn, ~p"/apps/#{slug}/sessions/#{session_id}")
|
||||||
|
assert html =~ "LivebookApp:#{slug}"
|
||||||
|
end
|
||||||
|
|
||||||
|
@tag :tmp_dir
|
||||||
|
test "shows app if user have full access",
|
||||||
|
%{conn: conn, node: node, code: code, tmp_dir: tmp_dir} = context do
|
||||||
|
erpc_call(node, :toggle_groups_authorization, [context.deployment_group])
|
||||||
|
oidc_provider = erpc_call(node, :create_oidc_provider, [context.org])
|
||||||
|
|
||||||
|
authorization_group =
|
||||||
|
erpc_call(node, :create_authorization_group, [
|
||||||
|
%{
|
||||||
|
group_name: "marketing",
|
||||||
|
access_type: :app_server,
|
||||||
|
oidc_provider: oidc_provider,
|
||||||
|
deployment_group: context.deployment_group
|
||||||
|
}
|
||||||
|
])
|
||||||
|
|
||||||
|
erpc_call(node, :update_user_info_groups, [
|
||||||
|
code,
|
||||||
|
[
|
||||||
|
%{
|
||||||
|
"provider_id" => to_string(oidc_provider.id),
|
||||||
|
"group_name" => authorization_group.group_name
|
||||||
|
}
|
||||||
|
]
|
||||||
|
])
|
||||||
|
|
||||||
|
slugs = ~w(mkt-app sales-app opt-app)
|
||||||
|
|
||||||
|
for slug <- slugs do
|
||||||
|
pid = deploy_app(slug, context.team, context.org, context.deployment_group, tmp_dir, node)
|
||||||
|
session_id = Livebook.App.get_session_id(pid, user: Livebook.Users.User.new())
|
||||||
|
|
||||||
|
{:ok, _view, html} = live(conn, ~p"/apps/#{slug}/sessions/#{session_id}")
|
||||||
|
assert html =~ "LivebookApp:#{slug}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@tag :tmp_dir
|
||||||
|
test "renders unauthorized if loses the access in real-time",
|
||||||
|
%{conn: conn, node: node, code: code, tmp_dir: tmp_dir} = context do
|
||||||
|
{:ok, deployment_group} =
|
||||||
|
erpc_call(node, :toggle_groups_authorization, [context.deployment_group])
|
||||||
|
|
||||||
|
oidc_provider = erpc_call(node, :create_oidc_provider, [context.org])
|
||||||
|
|
||||||
|
authorization_group =
|
||||||
|
erpc_call(node, :create_authorization_group, [
|
||||||
|
%{
|
||||||
|
group_name: "marketing",
|
||||||
|
access_type: :apps,
|
||||||
|
prefixes: ["mkt-"],
|
||||||
|
oidc_provider: oidc_provider,
|
||||||
|
deployment_group: deployment_group
|
||||||
|
}
|
||||||
|
])
|
||||||
|
|
||||||
|
erpc_call(node, :update_user_info_groups, [
|
||||||
|
code,
|
||||||
|
[
|
||||||
|
%{
|
||||||
|
"provider_id" => to_string(oidc_provider.id),
|
||||||
|
"group_name" => authorization_group.group_name
|
||||||
|
}
|
||||||
|
]
|
||||||
|
])
|
||||||
|
|
||||||
|
slug = "mkt-analytics"
|
||||||
|
pid = deploy_app(slug, context.team, context.org, context.deployment_group, tmp_dir, node)
|
||||||
|
session_id = Livebook.App.get_session_id(pid, user: Livebook.Users.User.new())
|
||||||
|
path = ~p"/apps/#{slug}/sessions/#{session_id}"
|
||||||
|
|
||||||
|
{:ok, view, _html} = live(conn, path)
|
||||||
|
assert render(view) =~ "LivebookApp:#{slug}"
|
||||||
|
|
||||||
|
erpc_call(node, :update_authorization_group, [authorization_group, %{prefixes: ["ops-"]}])
|
||||||
|
|
||||||
|
id = to_string(deployment_group.id)
|
||||||
|
|
||||||
|
assert_receive {:server_authorization_updated, %{id: ^id}}
|
||||||
|
assert_receive {:app_deployment_updated, %{slug: ^slug}}
|
||||||
|
assert_redirect view, path
|
||||||
|
|
||||||
|
{:ok, view, _html} = live(conn, path)
|
||||||
|
assert render(view) =~ "Not authorized"
|
||||||
|
end
|
||||||
|
|
||||||
|
@tag :tmp_dir
|
||||||
|
test "shows app if disable the authentication in real-time",
|
||||||
|
%{conn: conn, node: node, code: code, tmp_dir: tmp_dir} = context do
|
||||||
|
{:ok, deployment_group} =
|
||||||
|
erpc_call(node, :toggle_groups_authorization, [context.deployment_group])
|
||||||
|
|
||||||
|
oidc_provider = erpc_call(node, :create_oidc_provider, [context.org])
|
||||||
|
|
||||||
|
authorization_group =
|
||||||
|
erpc_call(node, :create_authorization_group, [
|
||||||
|
%{
|
||||||
|
group_name: "marketing",
|
||||||
|
access_type: :apps,
|
||||||
|
prefixes: ["mkt-"],
|
||||||
|
oidc_provider: oidc_provider,
|
||||||
|
deployment_group: deployment_group
|
||||||
|
}
|
||||||
|
])
|
||||||
|
|
||||||
|
erpc_call(node, :update_user_info_groups, [
|
||||||
|
code,
|
||||||
|
[
|
||||||
|
%{
|
||||||
|
"provider_id" => to_string(oidc_provider.id),
|
||||||
|
"group_name" => authorization_group.group_name
|
||||||
|
}
|
||||||
|
]
|
||||||
|
])
|
||||||
|
|
||||||
|
slug = "analytics-app"
|
||||||
|
pid = deploy_app(slug, context.team, context.org, context.deployment_group, tmp_dir, node)
|
||||||
|
session_id = Livebook.App.get_session_id(pid, user: Livebook.Users.User.new())
|
||||||
|
path = ~p"/apps/#{slug}/sessions/#{session_id}"
|
||||||
|
|
||||||
|
{:ok, view, _html} = live(conn, path)
|
||||||
|
assert render(view) =~ "Not authorized"
|
||||||
|
|
||||||
|
{:ok, %{teams_auth: false} = deployment_group} =
|
||||||
|
erpc_call(node, :toggle_teams_authentication, [deployment_group])
|
||||||
|
|
||||||
|
id = to_string(deployment_group.id)
|
||||||
|
assert_receive {:server_authorization_updated, %{id: ^id, teams_auth: false}}
|
||||||
|
assert_redirect view, path
|
||||||
|
|
||||||
|
{:ok, view, _html} = live(conn, path)
|
||||||
|
assert render(view) =~ "LivebookApp:#{slug}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp deploy_app(slug, team, org, deployment_group, tmp_dir, node) do
|
||||||
|
source = """
|
||||||
|
<!-- livebook:{"app_settings":{"access_type":"public","slug":"#{slug}"},"hub_id":"#{team.id}","deployment_group_id":"#{deployment_group.id}"} -->
|
||||||
|
|
||||||
|
# LivebookApp:#{slug}
|
||||||
|
|
||||||
|
```elixir
|
||||||
|
```
|
||||||
|
"""
|
||||||
|
|
||||||
|
{notebook, %{warnings: []}} = Livebook.LiveMarkdown.notebook_from_livemd(source)
|
||||||
|
|
||||||
|
files_dir = Livebook.FileSystem.File.local(tmp_dir)
|
||||||
|
|
||||||
|
{:ok, %Livebook.Teams.AppDeployment{file: zip_content} = app_deployment} =
|
||||||
|
Livebook.Teams.AppDeployment.new(notebook, files_dir)
|
||||||
|
|
||||||
|
secret_key = Livebook.Teams.derive_key(team.teams_key)
|
||||||
|
encrypted_content = Livebook.Teams.encrypt(zip_content, secret_key)
|
||||||
|
|
||||||
|
app_deployment_id =
|
||||||
|
erpc_call(node, :upload_app_deployment, [
|
||||||
|
org,
|
||||||
|
deployment_group,
|
||||||
|
app_deployment,
|
||||||
|
encrypted_content,
|
||||||
|
# broadcast?
|
||||||
|
true
|
||||||
|
]).id
|
||||||
|
|
||||||
|
app_deployment_id = to_string(app_deployment_id)
|
||||||
|
assert_receive {:app_deployment_started, %{id: ^app_deployment_id}}
|
||||||
|
|
||||||
|
assert_receive {:app_created, %{pid: pid, slug: ^slug}}
|
||||||
|
|
||||||
|
assert_receive {:app_updated,
|
||||||
|
%{
|
||||||
|
slug: ^slug,
|
||||||
|
sessions: [%{app_status: %{execution: :executed, lifecycle: :active}}]
|
||||||
|
}}
|
||||||
|
|
||||||
|
on_exit(fn ->
|
||||||
|
if Process.alive?(pid) do
|
||||||
|
Livebook.App.close(pid)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
pid
|
||||||
|
end
|
||||||
|
end
|
260
test/livebook_teams/web/apps_live_test.exs
Normal file
260
test/livebook_teams/web/apps_live_test.exs
Normal file
|
@ -0,0 +1,260 @@
|
||||||
|
defmodule LivebookWeb.Integration.AppsLiveTest do
|
||||||
|
use Livebook.TeamsIntegrationCase, async: false
|
||||||
|
|
||||||
|
describe "authorized apps" do
|
||||||
|
setup %{conn: conn, node: node} do
|
||||||
|
Livebook.Teams.Broadcasts.subscribe([:agents, :app_deployments, :app_server])
|
||||||
|
Livebook.Apps.subscribe()
|
||||||
|
|
||||||
|
{_agent_key, org, deployment_group, team} = create_agent_team_hub(node)
|
||||||
|
|
||||||
|
# we wait until the agent_connected is received by livebook
|
||||||
|
hub_id = team.id
|
||||||
|
deployment_group_id = to_string(deployment_group.id)
|
||||||
|
org_id = to_string(org.id)
|
||||||
|
|
||||||
|
assert_receive {:agent_joined,
|
||||||
|
%{
|
||||||
|
hub_id: ^hub_id,
|
||||||
|
org_id: ^org_id,
|
||||||
|
deployment_group_id: ^deployment_group_id
|
||||||
|
}}
|
||||||
|
|
||||||
|
start_supervised!(
|
||||||
|
{Livebook.ZTA.LivebookTeams, name: LivebookWeb.ZTA, identity_key: team.id}
|
||||||
|
)
|
||||||
|
|
||||||
|
{conn, code} = authenticate_user_on_teams(conn, node, team)
|
||||||
|
|
||||||
|
{:ok, conn: conn, code: code, deployment_group: deployment_group, org: org, team: team}
|
||||||
|
end
|
||||||
|
|
||||||
|
@tag :tmp_dir
|
||||||
|
test "shows one app if user doesn't have full access",
|
||||||
|
%{conn: conn, node: node, code: code, tmp_dir: tmp_dir} = context do
|
||||||
|
erpc_call(node, :toggle_groups_authorization, [context.deployment_group])
|
||||||
|
oidc_provider = erpc_call(node, :create_oidc_provider, [context.org])
|
||||||
|
|
||||||
|
authorization_group =
|
||||||
|
erpc_call(node, :create_authorization_group, [
|
||||||
|
%{
|
||||||
|
group_name: "marketing",
|
||||||
|
access_type: :apps,
|
||||||
|
prefixes: ["dev-"],
|
||||||
|
oidc_provider: oidc_provider,
|
||||||
|
deployment_group: context.deployment_group
|
||||||
|
}
|
||||||
|
])
|
||||||
|
|
||||||
|
erpc_call(node, :update_user_info_groups, [
|
||||||
|
code,
|
||||||
|
[
|
||||||
|
%{
|
||||||
|
"provider_id" => to_string(oidc_provider.id),
|
||||||
|
"group_name" => authorization_group.group_name
|
||||||
|
}
|
||||||
|
]
|
||||||
|
])
|
||||||
|
|
||||||
|
slug = "dev-app"
|
||||||
|
|
||||||
|
deploy_app(slug, context.team, context.org, context.deployment_group, tmp_dir, node)
|
||||||
|
|
||||||
|
html =
|
||||||
|
conn
|
||||||
|
|> get(~p"/apps")
|
||||||
|
|> html_response(200)
|
||||||
|
|
||||||
|
refute html =~ "No apps running."
|
||||||
|
assert html =~ slug
|
||||||
|
end
|
||||||
|
|
||||||
|
@tag :tmp_dir
|
||||||
|
test "shows all apps if user have full access",
|
||||||
|
%{conn: conn, node: node, code: code, tmp_dir: tmp_dir} = context do
|
||||||
|
erpc_call(node, :toggle_groups_authorization, [context.deployment_group])
|
||||||
|
oidc_provider = erpc_call(node, :create_oidc_provider, [context.org])
|
||||||
|
|
||||||
|
authorization_group =
|
||||||
|
erpc_call(node, :create_authorization_group, [
|
||||||
|
%{
|
||||||
|
group_name: "marketing",
|
||||||
|
access_type: :app_server,
|
||||||
|
oidc_provider: oidc_provider,
|
||||||
|
deployment_group: context.deployment_group
|
||||||
|
}
|
||||||
|
])
|
||||||
|
|
||||||
|
erpc_call(node, :update_user_info_groups, [
|
||||||
|
code,
|
||||||
|
[
|
||||||
|
%{
|
||||||
|
"provider_id" => to_string(oidc_provider.id),
|
||||||
|
"group_name" => authorization_group.group_name
|
||||||
|
}
|
||||||
|
]
|
||||||
|
])
|
||||||
|
|
||||||
|
slugs = ~w(mkt-app sales-app opt-app)
|
||||||
|
|
||||||
|
for slug <- slugs do
|
||||||
|
deploy_app(slug, context.team, context.org, context.deployment_group, tmp_dir, node)
|
||||||
|
end
|
||||||
|
|
||||||
|
html =
|
||||||
|
conn
|
||||||
|
|> get(~p"/apps")
|
||||||
|
|> html_response(200)
|
||||||
|
|
||||||
|
refute html =~ "No apps running."
|
||||||
|
|
||||||
|
for slug <- slugs do
|
||||||
|
assert html =~ slug
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@tag :tmp_dir
|
||||||
|
test "updates the apps list in real-time",
|
||||||
|
%{conn: conn, node: node, code: code, tmp_dir: tmp_dir} = context do
|
||||||
|
{:ok, deployment_group} =
|
||||||
|
erpc_call(node, :toggle_groups_authorization, [context.deployment_group])
|
||||||
|
|
||||||
|
oidc_provider = erpc_call(node, :create_oidc_provider, [context.org])
|
||||||
|
|
||||||
|
authorization_group =
|
||||||
|
erpc_call(node, :create_authorization_group, [
|
||||||
|
%{
|
||||||
|
group_name: "marketing",
|
||||||
|
access_type: :apps,
|
||||||
|
prefixes: ["mkt-"],
|
||||||
|
oidc_provider: oidc_provider,
|
||||||
|
deployment_group: deployment_group
|
||||||
|
}
|
||||||
|
])
|
||||||
|
|
||||||
|
erpc_call(node, :update_user_info_groups, [
|
||||||
|
code,
|
||||||
|
[
|
||||||
|
%{
|
||||||
|
"provider_id" => to_string(oidc_provider.id),
|
||||||
|
"group_name" => authorization_group.group_name
|
||||||
|
}
|
||||||
|
]
|
||||||
|
])
|
||||||
|
|
||||||
|
slug = "marketing-app"
|
||||||
|
|
||||||
|
deploy_app(slug, context.team, context.org, context.deployment_group, tmp_dir, node)
|
||||||
|
|
||||||
|
assert conn
|
||||||
|
|> get(~p"/apps")
|
||||||
|
|> html_response(200) =~ "No apps running."
|
||||||
|
|
||||||
|
{:ok, %{groups_auth: false} = deployment_group} =
|
||||||
|
erpc_call(node, :toggle_groups_authorization, [deployment_group])
|
||||||
|
|
||||||
|
id = to_string(deployment_group.id)
|
||||||
|
assert_receive {:server_authorization_updated, %{id: ^id, groups_auth: false}}
|
||||||
|
|
||||||
|
assert conn
|
||||||
|
|> get(~p"/apps")
|
||||||
|
|> html_response(200) =~ slug
|
||||||
|
end
|
||||||
|
|
||||||
|
@tag :tmp_dir
|
||||||
|
test "shows all apps if disable the authentication in real-time",
|
||||||
|
%{conn: conn, node: node, code: code, tmp_dir: tmp_dir} = context do
|
||||||
|
{:ok, deployment_group} =
|
||||||
|
erpc_call(node, :toggle_groups_authorization, [context.deployment_group])
|
||||||
|
|
||||||
|
oidc_provider = erpc_call(node, :create_oidc_provider, [context.org])
|
||||||
|
|
||||||
|
authorization_group =
|
||||||
|
erpc_call(node, :create_authorization_group, [
|
||||||
|
%{
|
||||||
|
group_name: "marketing",
|
||||||
|
access_type: :apps,
|
||||||
|
prefixes: ["mkt-"],
|
||||||
|
oidc_provider: oidc_provider,
|
||||||
|
deployment_group: deployment_group
|
||||||
|
}
|
||||||
|
])
|
||||||
|
|
||||||
|
erpc_call(node, :update_user_info_groups, [
|
||||||
|
code,
|
||||||
|
[
|
||||||
|
%{
|
||||||
|
"provider_id" => to_string(oidc_provider.id),
|
||||||
|
"group_name" => authorization_group.group_name
|
||||||
|
}
|
||||||
|
]
|
||||||
|
])
|
||||||
|
|
||||||
|
slug = "marketing-app"
|
||||||
|
|
||||||
|
deploy_app(slug, context.team, context.org, context.deployment_group, tmp_dir, node)
|
||||||
|
|
||||||
|
assert conn
|
||||||
|
|> get(~p"/apps")
|
||||||
|
|> html_response(200) =~ "No apps running."
|
||||||
|
|
||||||
|
{:ok, %{teams_auth: false} = deployment_group} =
|
||||||
|
erpc_call(node, :toggle_teams_authentication, [deployment_group])
|
||||||
|
|
||||||
|
id = to_string(deployment_group.id)
|
||||||
|
assert_receive {:server_authorization_updated, %{id: ^id, teams_auth: false}}
|
||||||
|
|
||||||
|
assert conn
|
||||||
|
|> get(~p"/apps")
|
||||||
|
|> html_response(200) =~ slug
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp deploy_app(slug, team, org, deployment_group, tmp_dir, node) do
|
||||||
|
source = """
|
||||||
|
<!-- livebook:{"app_settings":{"access_type":"public","slug":"#{slug}"},"hub_id":"#{team.id}","deployment_group_id":"#{deployment_group.id}"} -->
|
||||||
|
|
||||||
|
# LivebookApp:#{slug}
|
||||||
|
|
||||||
|
```elixir
|
||||||
|
```
|
||||||
|
"""
|
||||||
|
|
||||||
|
{notebook, %{warnings: []}} = Livebook.LiveMarkdown.notebook_from_livemd(source)
|
||||||
|
|
||||||
|
files_dir = Livebook.FileSystem.File.local(tmp_dir)
|
||||||
|
|
||||||
|
{:ok, %Livebook.Teams.AppDeployment{file: zip_content} = app_deployment} =
|
||||||
|
Livebook.Teams.AppDeployment.new(notebook, files_dir)
|
||||||
|
|
||||||
|
secret_key = Livebook.Teams.derive_key(team.teams_key)
|
||||||
|
encrypted_content = Livebook.Teams.encrypt(zip_content, secret_key)
|
||||||
|
|
||||||
|
app_deployment_id =
|
||||||
|
erpc_call(node, :upload_app_deployment, [
|
||||||
|
org,
|
||||||
|
deployment_group,
|
||||||
|
app_deployment,
|
||||||
|
encrypted_content,
|
||||||
|
# broadcast?
|
||||||
|
true
|
||||||
|
]).id
|
||||||
|
|
||||||
|
app_deployment_id = to_string(app_deployment_id)
|
||||||
|
assert_receive {:app_deployment_started, %{id: ^app_deployment_id}}
|
||||||
|
|
||||||
|
assert_receive {:app_created, %{pid: pid, slug: ^slug}}
|
||||||
|
|
||||||
|
assert_receive {:app_updated,
|
||||||
|
%{
|
||||||
|
slug: ^slug,
|
||||||
|
sessions: [%{app_status: %{execution: :executed, lifecycle: :active}}]
|
||||||
|
}}
|
||||||
|
|
||||||
|
on_exit(fn ->
|
||||||
|
if Process.alive?(pid) do
|
||||||
|
Livebook.App.close(pid)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
end
|
|
@ -73,7 +73,7 @@ defmodule Livebook.ZTA.LivebookTeamsTest do
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
|
|
||||||
{conn, code, %{restricted_apps_groups: []}} = authenticate_user(conn, node, test)
|
{conn, code, %{groups: [], access_type: :apps}} = authenticate_user(conn, node, test)
|
||||||
session = get_session(conn)
|
session = get_session(conn)
|
||||||
|
|
||||||
conn =
|
conn =
|
||||||
|
@ -88,7 +88,7 @@ defmodule Livebook.ZTA.LivebookTeamsTest do
|
||||||
# Get the user with updated groups
|
# Get the user with updated groups
|
||||||
erpc_call(node, :update_user_info_groups, [code, [group]])
|
erpc_call(node, :update_user_info_groups, [code, [group]])
|
||||||
|
|
||||||
assert {%{halted: false}, %{restricted_apps_groups: nil}} =
|
assert {%{halted: false}, %{groups: [^group], access_type: :full}} =
|
||||||
LivebookTeams.authenticate(test, conn, [])
|
LivebookTeams.authenticate(test, conn, [])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -163,7 +163,7 @@ defmodule Livebook.ZTA.LivebookTeamsTest do
|
||||||
}}
|
}}
|
||||||
|
|
||||||
# Now we need to check if the current user has access to this app
|
# Now we need to check if the current user has access to this app
|
||||||
{conn, code, %{restricted_apps_groups: []}} = authenticate_user(conn, node, test)
|
{conn, code, %{groups: [], access_type: :apps}} = authenticate_user(conn, node, test)
|
||||||
session = get_session(conn)
|
session = get_session(conn)
|
||||||
|
|
||||||
group = %{
|
group = %{
|
||||||
|
@ -199,7 +199,7 @@ defmodule Livebook.ZTA.LivebookTeamsTest do
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
|
|
||||||
{conn, code, %{restricted_apps_groups: []}} = authenticate_user(conn, node, test)
|
{conn, code, %{groups: [], access_type: :apps}} = authenticate_user(conn, node, test)
|
||||||
|
|
||||||
group = %{
|
group = %{
|
||||||
"provider_id" => to_string(oidc_provider.id),
|
"provider_id" => to_string(oidc_provider.id),
|
||||||
|
@ -212,7 +212,7 @@ defmodule Livebook.ZTA.LivebookTeamsTest do
|
||||||
build_conn(:get, ~p"/settings")
|
build_conn(:get, ~p"/settings")
|
||||||
|> init_test_session(get_session(conn))
|
|> init_test_session(get_session(conn))
|
||||||
|
|
||||||
assert {_conn, %{restricted_apps_groups: [^group]}} =
|
assert {_conn, %{groups: [^group], access_type: :apps}} =
|
||||||
LivebookTeams.authenticate(test, conn, [])
|
LivebookTeams.authenticate(test, conn, [])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -30,6 +30,12 @@ defmodule LivebookWeb.ConnCase do
|
||||||
end
|
end
|
||||||
|
|
||||||
def with_authentication(conn, authentication) do
|
def with_authentication(conn, authentication) do
|
||||||
Plug.Test.init_test_session(conn, authentication_test_override: authentication)
|
Plug.Test.init_test_session(conn, %{authentication_test_override: authentication})
|
||||||
|
end
|
||||||
|
|
||||||
|
def with_authorization(conn, id) do
|
||||||
|
Plug.Test.init_test_session(conn, %{
|
||||||
|
identity_provider_test_override: {:zta, Livebook.ZTA.LivebookTeams, id}
|
||||||
|
})
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,8 +1,11 @@
|
||||||
defmodule Livebook.TeamsIntegrationCase do
|
defmodule Livebook.TeamsIntegrationCase do
|
||||||
use ExUnit.CaseTemplate
|
use ExUnit.CaseTemplate
|
||||||
|
|
||||||
|
import Phoenix.ConnTest
|
||||||
alias Livebook.TeamsServer
|
alias Livebook.TeamsServer
|
||||||
|
|
||||||
|
@endpoint LivebookWeb.Endpoint
|
||||||
|
|
||||||
using do
|
using do
|
||||||
quote do
|
quote do
|
||||||
use Livebook.DataCase
|
use Livebook.DataCase
|
||||||
|
@ -13,6 +16,7 @@ defmodule Livebook.TeamsIntegrationCase do
|
||||||
alias Livebook.TeamsServer
|
alias Livebook.TeamsServer
|
||||||
|
|
||||||
import Livebook.HubHelpers
|
import Livebook.HubHelpers
|
||||||
|
import Livebook.TeamsIntegrationCase
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -31,4 +35,26 @@ defmodule Livebook.TeamsIntegrationCase do
|
||||||
|
|
||||||
{:ok, node: node, token: token, user: user}
|
{:ok, node: node, token: token, user: user}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def authenticate_user_on_teams(conn, node, team) do
|
||||||
|
response =
|
||||||
|
conn
|
||||||
|
|> LivebookWeb.ConnCase.with_authorization(team.id)
|
||||||
|
|> get("/")
|
||||||
|
|> html_response(200)
|
||||||
|
|
||||||
|
[_, location] = Regex.run(~r/URL\("(.*?)"\)/, response)
|
||||||
|
uri = URI.parse(location)
|
||||||
|
%{"code" => code} = URI.decode_query(uri.query)
|
||||||
|
|
||||||
|
Livebook.HubHelpers.erpc_call(node, :allow_auth_request, [code])
|
||||||
|
|
||||||
|
session =
|
||||||
|
conn
|
||||||
|
|> LivebookWeb.ConnCase.with_authorization(team.id)
|
||||||
|
|> get("/", %{teams_identity: "", code: code})
|
||||||
|
|> Plug.Conn.get_session()
|
||||||
|
|
||||||
|
{Plug.Test.init_test_session(conn, session), code}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Add table
Reference in a new issue