Load secrets from Hub when deploying an App (#2098)

This commit is contained in:
Alexandre de Souza 2023-07-26 17:39:33 -03:00 committed by GitHub
parent a8ae19b9cf
commit 3ecc0b3653
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
30 changed files with 379 additions and 206 deletions

View file

@ -261,6 +261,10 @@ The following environment variables can be used to configure Livebook on boot:
* LIVEBOOK_TEAMS_OFFLINE_KEY - sets the Livebook Teams public key for creating an offline hub.
Must be set together with LIVEBOOK_TEAMS_NAME and LIVEBOOK_TEAMS_KEY.
* LIVEBOOK_TEAMS_SECRETS - sets the Livebook Teams encrypted secrets for deploying apps with secrets.
This is relevant when deploying apps with offline hub. Must be set together with
LIVEBOOK_TEAMS_NAME, LIVEBOOK_TEAMS_KEY and LIVEBOOK_TEAMS_OFFLINE_KEY.
* LIVEBOOK_TOKEN_ENABLED - controls whether token authentication is enabled.
Enabled by default unless LIVEBOOK_PASSWORD is set. Set it to "false" to
disable it.

View file

@ -217,6 +217,7 @@ defmodule Livebook.Application do
def create_offline_hub() do
name = System.get_env("LIVEBOOK_TEAMS_NAME")
public_key = System.get_env("LIVEBOOK_TEAMS_OFFLINE_KEY")
encrypted_secrets = System.get_env("LIVEBOOK_TEAMS_SECRETS")
if name && public_key do
teams_key =
@ -225,12 +226,43 @@ defmodule Livebook.Application do
"You specified LIVEBOOK_TEAMS_NAME, but LIVEBOOK_TEAMS_KEY is missing."
)
Livebook.Hubs.set_offline_hub(%Livebook.Hubs.Team{
id = "team-#{name}"
secrets =
if encrypted_secrets do
{secret_key, sign_secret} = Livebook.Teams.derive_keys(teams_key)
case Livebook.Teams.decrypt(encrypted_secrets, secret_key, sign_secret) do
{:ok, json} ->
for {name, value} <- Jason.decode!(json),
do: %Livebook.Secrets.Secret{
name: name,
value: value,
hub_id: id
}
:error ->
Livebook.Config.abort!(
"You specified LIVEBOOK_TEAMS_SECRETS, but we couldn't decrypt with the given LIVEBOOK_TEAMS_KEY."
)
end
else
[]
end
Livebook.Hubs.save_hub(%Livebook.Hubs.Team{
id: "team-#{name}",
hub_name: name,
hub_emoji: "💡",
user_id: 0,
org_id: 0,
org_key_id: 0,
session_token: "",
teams_key: teams_key,
org_public_key: public_key
org_public_key: public_key,
offline: %Livebook.Hubs.Team.Offline{
secrets: secrets
}
})
end
end

View file

@ -234,8 +234,7 @@ defmodule Livebook.Apps do
for path <- Path.wildcard(pattern) do
markdown = File.read!(path)
{notebook, %{warnings: warnings, verified_hub_id: verified_hub_id}} =
Livebook.LiveMarkdown.notebook_from_livemd(markdown)
{notebook, warnings} = Livebook.LiveMarkdown.notebook_from_livemd(markdown)
apps_path_hub_id = Livebook.Config.apps_path_hub_id()
@ -245,7 +244,7 @@ defmodule Livebook.Apps do
{:error,
"the deployment settings are missing or invalid. Please configure them under the notebook deploy panel"}
apps_path_hub_id && apps_path_hub_id != verified_hub_id ->
apps_path_hub_id && apps_path_hub_id != notebook.hub_id ->
{:error, "the notebook is not verified to come from hub #{apps_path_hub_id}"}
true ->

View file

@ -6,6 +6,7 @@ defmodule Livebook.Hubs do
alias Livebook.Secrets.Secret
@namespace :hubs
@supervisor Livebook.HubsSupervisor
@type connected_hub :: %{
required(:pid) => pid(),
@ -79,8 +80,8 @@ defmodule Livebook.Hubs do
"""
@spec save_hub(Provider.t()) :: Provider.t()
def save_hub(struct) do
attributes = struct |> Map.from_struct() |> Map.to_list()
:ok = Storage.insert(@namespace, struct.id, attributes)
attributes = Provider.dump(struct)
:ok = Storage.insert(@namespace, struct.id, Map.to_list(attributes))
:ok = connect_hub(struct)
:ok = Broadcasts.hub_changed(struct.id)
@ -164,7 +165,7 @@ defmodule Livebook.Hubs do
end
defp to_struct(%{id: "team-" <> _} = fields) do
Provider.load(%Team{}, fields)
Provider.load(Team.new(), fields)
end
@doc """
@ -185,7 +186,7 @@ defmodule Livebook.Hubs do
defp connect_hub(hub) do
if child_spec = Provider.connection_spec(hub) do
DynamicSupervisor.start_child(Livebook.HubsSupervisor, child_spec)
DynamicSupervisor.start_child(@supervisor, child_spec)
end
:ok
@ -272,25 +273,4 @@ defmodule Livebook.Hubs do
def capability?(hub, capabilities) do
capabilities -- Provider.capabilities(hub) == []
end
@offline_hub_key :livebook_offline_hub
@doc """
Gets the offline hub if the given id matches.
"""
@spec get_offline_hub(String.t()) :: Provider.t() | nil
def get_offline_hub(id) do
case :persistent_term.get(@offline_hub_key, nil) do
%{id: ^id} = hub -> hub
_ -> nil
end
end
@doc """
Sets a offline hub that will be kept only in memory.
"""
@spec set_offline_hub(Provider.t()) :: :ok
def set_offline_hub(hub) do
:persistent_term.put(@offline_hub_key, hub)
end
end

View file

@ -142,17 +142,21 @@ defimpl Livebook.Hubs.Provider, for: Livebook.Hubs.Personal do
:skip
end
def notebook_stamp(hub, notebook_source, metadata) do
token = Livebook.Stamping.aead_encrypt(metadata, notebook_source, hub.secret_key)
def notebook_stamp(personal, notebook_source, metadata) do
token = Livebook.Stamping.aead_encrypt(metadata, notebook_source, personal.secret_key)
stamp = %{"version" => 1, "token" => token}
{:ok, stamp}
end
def verify_notebook_stamp(hub, notebook_source, stamp) do
def verify_notebook_stamp(personal, notebook_source, stamp) do
%{"version" => 1, "token" => token} = stamp
Livebook.Stamping.aead_decrypt(token, notebook_source, hub.secret_key)
Livebook.Stamping.aead_decrypt(token, notebook_source, personal.secret_key)
end
def dump(personal) do
Map.from_struct(personal)
end
end

View file

@ -112,4 +112,10 @@ defprotocol Livebook.Hubs.Provider do
@spec verify_notebook_stamp(t(), iodata(), notebook_stamp()) ::
{:ok, metadata :: map()} | :error
def verify_notebook_stamp(hub, notebook_source, stamp)
@doc """
Transforms hub to the attributes map sent to storage.
"""
@spec dump(t()) :: map()
def dump(hub)
end

View file

@ -4,18 +4,37 @@ defmodule Livebook.Hubs.Team do
use Ecto.Schema
import Ecto.Changeset
defmodule Offline do
@moduledoc false
use Ecto.Schema
alias Livebook.Secrets.Secret
@type t :: %__MODULE__{
secrets: list(Secret.t())
}
@primary_key false
embedded_schema do
field :secrets, {:array, :map}, default: []
end
end
@type t :: %__MODULE__{
id: String.t() | nil,
org_id: pos_integer() | nil,
user_id: pos_integer() | nil,
org_key_id: pos_integer() | nil,
teams_key: String.t() | nil,
org_public_key: String.t() | nil,
session_token: String.t() | nil,
org_id: non_neg_integer(),
user_id: non_neg_integer(),
org_key_id: non_neg_integer(),
teams_key: String.t(),
org_public_key: String.t(),
session_token: String.t(),
hub_name: String.t() | nil,
hub_emoji: String.t() | nil
hub_emoji: String.t() | nil,
offline: Offline.t() | nil
}
@enforce_keys [:user_id, :org_id, :org_key_id, :session_token, :org_public_key, :teams_key]
embedded_schema do
field :org_id, :integer
field :user_id, :integer
@ -25,6 +44,8 @@ defmodule Livebook.Hubs.Team do
field :session_token, :string
field :hub_name, :string
field :hub_emoji, :string
embeds_one :offline, Offline
end
@fields ~w(
@ -38,6 +59,20 @@ defmodule Livebook.Hubs.Team do
hub_emoji
)a
@doc """
Initializes a new Team hub.
"""
def new() do
%__MODULE__{
user_id: nil,
org_id: nil,
org_key_id: nil,
session_token: nil,
org_public_key: nil,
teams_key: nil
}
end
@doc """
Returns an `%Ecto.Changeset{}` for tracking hub changes.
"""
@ -101,7 +136,7 @@ defimpl Livebook.Hubs.Provider, for: Livebook.Hubs.Team do
"Cannot connect to Hub: #{reason}. Will attempt to reconnect automatically..."
end
def notebook_stamp(hub, notebook_source, metadata) do
def notebook_stamp(team, notebook_source, metadata) do
# We apply authenticated encryption using the shared teams key,
# just as for the personal hub, but we additionally sign the token
# with a private organization key stored on the Teams server. We
@ -111,9 +146,9 @@ defimpl Livebook.Hubs.Provider, for: Livebook.Hubs.Team do
# stamp requires access to the shared local key and an authenticated
# request to the Teams server (which ensures team membership).
token = Livebook.Stamping.aead_encrypt(metadata, notebook_source, hub.teams_key)
token = Livebook.Stamping.aead_encrypt(metadata, notebook_source, team.teams_key)
case Livebook.Teams.org_sign(hub, token) do
case Livebook.Teams.org_sign(team, token) do
{:ok, token_signature} ->
stamp = %{"version" => 1, "token" => token, "token_signature" => token_signature}
{:ok, stamp}
@ -123,13 +158,19 @@ defimpl Livebook.Hubs.Provider, for: Livebook.Hubs.Team do
end
end
def verify_notebook_stamp(hub, notebook_source, stamp) do
def verify_notebook_stamp(team, notebook_source, stamp) do
%{"version" => 1, "token" => token, "token_signature" => token_signature} = stamp
if Livebook.Stamping.rsa_verify?(token_signature, token, hub.org_public_key) do
Livebook.Stamping.aead_decrypt(token, notebook_source, hub.teams_key)
if Livebook.Stamping.rsa_verify?(token_signature, token, team.org_public_key) do
Livebook.Stamping.aead_decrypt(token, notebook_source, team.teams_key)
else
:error
end
end
def dump(team) do
team
|> Map.from_struct()
|> Map.delete(:offline)
end
end

View file

@ -66,7 +66,9 @@ defmodule Livebook.Hubs.TeamClient do
## GenServer callbacks
@impl true
def init(%Team{} = team) do
def init(%Team{offline: nil} = team) do
derived_keys = Teams.derive_keys(team.teams_key)
headers = [
{"x-user", to_string(team.user_id)},
{"x-org", to_string(team.org_id)},
@ -74,12 +76,16 @@ defmodule Livebook.Hubs.TeamClient do
{"x-session-token", team.session_token}
]
derived_keys = Teams.derive_keys(team.teams_key)
{:ok, _pid} = Connection.start_link(self(), headers)
{:ok, %__MODULE__{hub: team, derived_keys: derived_keys}}
end
def init(%Team{} = team) do
derived_keys = Teams.derive_keys(team.teams_key)
{:ok, %__MODULE__{hub: team, secrets: team.offline.secrets, derived_keys: derived_keys}}
end
@impl true
def handle_call(:get_connection_error, _caller, state) do
{:reply, state.connection_error, state}
@ -134,7 +140,7 @@ defmodule Livebook.Hubs.TeamClient do
defp build_secret(state, %{name: name, value: value}) do
{secret_key, sign_secret} = state.derived_keys
{:ok, decrypted_value} = Teams.decrypt_secret_value(value, secret_key, sign_secret)
{:ok, decrypted_value} = Teams.decrypt(value, secret_key, sign_secret)
%Livebook.Secrets.Secret{
name: name,
@ -164,15 +170,7 @@ defmodule Livebook.Hubs.TeamClient do
remove_secret(state, secret)
end
defp handle_event(:user_connected, user_connected, %{secrets: []} = state) do
secrets = for secret <- user_connected.secrets, do: build_secret(state, secret)
%{state | secrets: secrets}
end
defp handle_event(:user_connected, user_connected, state) do
secrets = for secret <- user_connected.secrets, do: build_secret(state, secret)
defp handle_event(:user_connected, %{secrets: secrets}, state) do
created_secrets =
Enum.reject(secrets, fn secret ->
Enum.find(state.secrets, &(&1.name == secret.name and &1.value == secret.value))
@ -188,15 +186,15 @@ defmodule Livebook.Hubs.TeamClient do
Enum.find(state.secrets, &(&1.name == secret.name and &1.value != secret.value))
end)
events_by_topic = [
secrets_by_topic = [
secret_deleted: deleted_secrets,
secret_created: created_secrets,
secret_updated: updated_secrets
]
for {topic, events} <- events_by_topic,
event <- events,
for {topic, secrets} <- secrets_by_topic,
secret <- secrets,
reduce: state,
do: (acc -> handle_event(topic, event, acc))
do: (acc -> handle_event(topic, secret, acc))
end
end

View file

@ -98,21 +98,9 @@ defmodule Livebook.LiveMarkdown do
@doc """
Converts the given Markdown document into a notebook data structure.
Returns the notebook struct and info map with the following keys:
* `:warnings` - a list of informative messages/warnings related
to the imported input
* `:verified_hub_id` - the hub id specified in the notebook
provided that the stamp was successfully verified, `nil`
otherwise
Returns the notebook structure and a list of informative messages/warnings
related to the imported input.
"""
@spec notebook_from_livemd(String.t()) ::
{Notebook.t(),
%{
required(:warnings) => list(String.t()),
required(:verified_hub_id) => String.t() | nil
}}
@spec notebook_from_livemd(String.t()) :: {Notebook.t(), list(String.t())}
defdelegate notebook_from_livemd(markdown), to: Livebook.LiveMarkdown.Import
end

View file

@ -10,17 +10,17 @@ defmodule Livebook.LiveMarkdown.Import do
{ast, rewrite_messages} = rewrite_ast(ast)
elements = group_elements(ast)
{stamp_data, elements} = take_stamp_data(elements)
{notebook, stamp_hub_id, build_messages} = build_notebook(elements)
{notebook, build_messages} = build_notebook(elements)
{notebook, postprocess_messages} = postprocess_notebook(notebook)
{notebook, verified_hub_id, metadata_messages} =
postprocess_stamp(notebook, markdown, stamp_data, stamp_hub_id)
{notebook, metadata_messages} =
postprocess_stamp(notebook, markdown, stamp_data)
messages =
earmark_messages ++
rewrite_messages ++ build_messages ++ postprocess_messages ++ metadata_messages
{notebook, %{warnings: messages, verified_hub_id: verified_hub_id}}
{notebook, messages}
end
defp earmark_message_to_string({_severity, line_number, message}) do
@ -329,7 +329,7 @@ defmodule Livebook.LiveMarkdown.Import do
]
end
{attrs, stamp_hub_id, metadata_messages} = notebook_metadata_to_attrs(metadata)
{attrs, metadata_messages} = notebook_metadata_to_attrs(metadata)
messages = messages ++ metadata_messages
# We identify a single leading cell as the setup cell, in any
@ -352,7 +352,7 @@ defmodule Livebook.LiveMarkdown.Import do
|> maybe_put_setup_cell(setup_cell)
|> Map.merge(attrs)
{notebook, stamp_hub_id, messages}
{notebook, messages}
end
defp maybe_put_name(notebook, nil), do: notebook
@ -380,35 +380,34 @@ defmodule Livebook.LiveMarkdown.Import do
defp grab_leading_comments(elems), do: {[], elems}
defp notebook_metadata_to_attrs(metadata) do
Enum.reduce(metadata, {%{}, Livebook.Hubs.Personal.id(), []}, fn
{"persist_outputs", persist_outputs}, {attrs, stamp_hub_id, messages} ->
{Map.put(attrs, :persist_outputs, persist_outputs), stamp_hub_id, messages}
Enum.reduce(metadata, {%{}, []}, fn
{"persist_outputs", persist_outputs}, {attrs, messages} ->
{Map.put(attrs, :persist_outputs, persist_outputs), messages}
{"autosave_interval_s", autosave_interval_s}, {attrs, stamp_hub_id, messages} ->
{Map.put(attrs, :autosave_interval_s, autosave_interval_s), stamp_hub_id, messages}
{"autosave_interval_s", autosave_interval_s}, {attrs, messages} ->
{Map.put(attrs, :autosave_interval_s, autosave_interval_s), messages}
{"default_language", default_language}, {attrs, stamp_hub_id, messages}
{"default_language", default_language}, {attrs, messages}
when default_language in ["elixir", "erlang"] ->
default_language = String.to_atom(default_language)
{Map.put(attrs, :default_language, default_language), stamp_hub_id, messages}
{Map.put(attrs, :default_language, default_language), messages}
{"hub_id", hub_id}, {attrs, stamp_hub_id, messages} ->
{"hub_id", hub_id}, {attrs, messages} ->
cond do
Hubs.hub_exists?(hub_id) -> {Map.put(attrs, :hub_id, hub_id), hub_id, messages}
Hubs.get_offline_hub(hub_id) -> {attrs, hub_id, messages}
true -> {attrs, stamp_hub_id, messages ++ ["ignoring notebook Hub with unknown id"]}
Hubs.hub_exists?(hub_id) -> {Map.put(attrs, :hub_id, hub_id), messages}
true -> {attrs, messages ++ ["ignoring notebook Hub with unknown id"]}
end
{"app_settings", app_settings_metadata}, {attrs, stamp_hub_id, messages} ->
{"app_settings", app_settings_metadata}, {attrs, messages} ->
app_settings =
Map.merge(
Notebook.AppSettings.new(),
app_settings_metadata_to_attrs(app_settings_metadata)
)
{Map.put(attrs, :app_settings, app_settings), stamp_hub_id, messages}
{Map.put(attrs, :app_settings, app_settings), messages}
{"file_entries", file_entry_metadatas}, {attrs, stamp_hub_id, messages}
{"file_entries", file_entry_metadatas}, {attrs, messages}
when is_list(file_entry_metadatas) ->
file_system_by_id =
if Enum.any?(file_entry_metadatas, &(&1["type"] == "file")) do
@ -438,10 +437,10 @@ defmodule Livebook.LiveMarkdown.Import do
|> Map.put(:file_entries, file_entries)
|> Map.put(:quarantine_file_entry_names, quarantine_file_entry_names)
{attrs, stamp_hub_id, messages ++ file_entry_messages}
{attrs, messages ++ file_entry_messages}
_entry, {attrs, stamp_hub_id, messages} ->
{attrs, stamp_hub_id, messages}
_entry, {attrs, messages} ->
{attrs, messages}
end)
end
@ -603,14 +602,10 @@ defmodule Livebook.LiveMarkdown.Import do
defp take_stamp_data([{:stamp, data} | elements]), do: {data, elements}
defp take_stamp_data(elements), do: {nil, elements}
defp postprocess_stamp(notebook, _notebook_source, nil, _), do: {notebook, nil, []}
defp postprocess_stamp(notebook, _notebook_source, nil), do: {notebook, []}
defp postprocess_stamp(notebook, notebook_source, stamp_data, stamp_hub_id) do
{hub, offline?} =
cond do
hub = Hubs.get_offline_hub(stamp_hub_id) -> {hub, true}
hub = Hubs.fetch_hub!(stamp_hub_id) -> {hub, false}
end
defp postprocess_stamp(notebook, notebook_source, stamp_data) do
hub = Hubs.fetch_hub!(notebook.hub_id)
{valid_stamp?, notebook, messages} =
with %{"offset" => offset, "stamp" => stamp} <- stamp_data,
@ -624,12 +619,9 @@ defmodule Livebook.LiveMarkdown.Import do
# We enable teams features for offline hub only if the stamp
# is valid, which ensures it is an existing Teams hub
teams_enabled = is_struct(hub, Livebook.Hubs.Team) and (not offline? or valid_stamp?)
teams_enabled = is_struct(hub, Livebook.Hubs.Team) and (hub.offline == nil or valid_stamp?)
notebook = %{notebook | teams_enabled: teams_enabled}
verified_hub_id = if(valid_stamp?, do: hub.id)
{notebook, verified_hub_id, messages}
{%{notebook | teams_enabled: teams_enabled}, messages}
end
defp safe_binary_slice(binary, start, size)

View file

@ -120,7 +120,7 @@ defmodule Livebook.Notebook.Learn do
markdown = File.read!(path)
# Parse the file to ensure no warnings and read the title.
# However, in the info we keep just the file contents to save on memory.
{notebook, %{warnings: warnings}} = Livebook.LiveMarkdown.notebook_from_livemd(markdown)
{notebook, warnings} = Livebook.LiveMarkdown.notebook_from_livemd(markdown)
if warnings != [] do
items = Enum.map(warnings, &("- " <> &1))
@ -197,8 +197,7 @@ defmodule Livebook.Notebook.Learn do
raise NotFoundError, slug: slug
notebook_info ->
{notebook, %{warnings: []}} =
Livebook.LiveMarkdown.notebook_from_livemd(notebook_info.livemd)
{notebook, []} = Livebook.LiveMarkdown.notebook_from_livemd(notebook_info.livemd)
{notebook, notebook_info.files}
end

View file

@ -158,7 +158,7 @@ defmodule Livebook.Teams do
"""
@spec create_hub!(map()) :: Team.t()
def create_hub!(attrs) do
changeset = Team.change_hub(%Team{}, attrs)
changeset = Team.change_hub(Team.new(), attrs)
team = apply_action!(changeset, :insert)
Hubs.save_hub(team)
@ -187,16 +187,16 @@ defmodule Livebook.Teams do
@doc """
Encrypts the given value with Teams key derived keys.
"""
@spec encrypt_secret_value(String.t(), bitstring(), bitstring()) :: String.t()
def encrypt_secret_value(value, secret, sign_secret) do
@spec encrypt(String.t(), bitstring(), bitstring()) :: String.t()
def encrypt(value, secret, sign_secret) do
Plug.Crypto.MessageEncryptor.encrypt(value, secret, sign_secret)
end
@doc """
Decrypts the given encrypted value with Teams key derived keys.
"""
@spec decrypt_secret_value(String.t(), bitstring(), bitstring()) :: {:ok, String.t()} | :error
def decrypt_secret_value(encrypted_value, secret, sign_secret) do
@spec decrypt(String.t(), bitstring(), bitstring()) :: {:ok, String.t()} | :error
def decrypt(encrypted_value, secret, sign_secret) do
Plug.Crypto.MessageEncryptor.decrypt(encrypted_value, secret, sign_secret)
end

View file

@ -51,7 +51,7 @@ defmodule Livebook.Teams.Requests do
{:ok, map()} | {:error, map() | String.t()} | {:transport_error, String.t()}
def create_secret(team, secret) do
{secret_key, sign_secret} = Teams.derive_keys(team.teams_key)
secret_value = Teams.encrypt_secret_value(secret.value, secret_key, sign_secret)
secret_value = Teams.encrypt(secret.value, secret_key, sign_secret)
headers = auth_headers(team)
params = %{name: secret.name, value: secret_value}
@ -66,7 +66,7 @@ defmodule Livebook.Teams.Requests do
{:ok, map()} | {:error, map() | String.t()} | {:transport_error, String.t()}
def update_secret(team, secret) do
{secret_key, sign_secret} = Teams.derive_keys(team.teams_key)
secret_value = Teams.encrypt_secret_value(secret.value, secret_key, sign_secret)
secret_value = Teams.encrypt(secret.value, secret_key, sign_secret)
headers = auth_headers(team)
params = %{name: secret.name, value: secret_value}

View file

@ -355,9 +355,23 @@ defmodule LivebookWeb.Hub.Edit.TeamComponent do
ENV LIVEBOOK_APPS_PATH_HUB_ID "#{socket.assigns.hub.id}"
ENV LIVEBOOK_TEAMS_NAME "#{socket.assigns.hub.hub_name}"
ENV LIVEBOOK_TEAMS_OFFLINE_KEY "#{socket.assigns.hub.org_public_key}"
ENV LIVEBOOK_TEAMS_SECRETS "#{encrypt_secrets_to_dockerfile(socket)}"
ENV LIVEBOOK_APPS_PATH_WARMUP "manual"
RUN /app/bin/warmup_apps.sh\
""")
end
defp encrypt_secrets_to_dockerfile(socket) do
{secret_key, sign_secret} = Livebook.Teams.derive_keys(socket.assigns.hub.teams_key)
secrets_map =
for %{name: name, value: value} <- socket.assigns.secrets,
into: %{},
do: {name, value}
stringified_secrets = Jason.encode!(secrets_map)
Livebook.Teams.encrypt(stringified_secrets, secret_key, sign_secret)
end
end

View file

@ -249,7 +249,7 @@ defmodule LivebookWeb.OpenLive do
defp file_from_params(_params), do: Livebook.Settings.default_dir()
defp import_source(socket, source, session_opts) do
{notebook, %{warnings: messages}} = Livebook.LiveMarkdown.notebook_from_livemd(source)
{notebook, messages} = Livebook.LiveMarkdown.notebook_from_livemd(source)
socket =
socket

View file

@ -115,7 +115,7 @@ defmodule LivebookWeb.SessionHelpers do
@spec fork_notebook(Socket.t(), FileSystem.File.t()) :: Socket.t()
def fork_notebook(socket, file) do
case import_notebook(file) do
{:ok, {notebook, %{warnings: messages}}} ->
{:ok, {notebook, messages}} ->
notebook = Livebook.Notebook.forked(notebook)
files_dir = Session.files_dir_for_notebook(file)
@ -138,7 +138,7 @@ defmodule LivebookWeb.SessionHelpers do
@spec open_notebook(Socket.t(), FileSystem.File.t()) :: Socket.t()
def open_notebook(socket, file) do
case import_notebook(file) do
{:ok, {notebook, %{warnings: messages}}} ->
{:ok, {notebook, messages}} ->
socket
|> put_import_warnings(messages)
|> create_session(notebook: notebook, file: file, origin: {:file, file})

View file

@ -1845,7 +1845,7 @@ defmodule LivebookWeb.SessionLive do
defp open_notebook(socket, origin, fallback_locations, requested_url) do
case fetch_content_with_fallbacks(origin, fallback_locations) do
{:ok, content} ->
{notebook, %{warnings: messages}} = Livebook.LiveMarkdown.notebook_from_livemd(content)
{notebook, messages} = Livebook.LiveMarkdown.notebook_from_livemd(content)
# If the current session has no file, fork the notebook
fork? = socket.private.data.file == nil

View file

@ -48,6 +48,8 @@ defmodule LivebookWeb.UserPlug do
end
end
defp ensure_user_data(conn) when conn.halted, do: conn
defp ensure_user_data(conn) do
if Map.has_key?(conn.req_cookies, "lb:user_data") do
conn
@ -71,6 +73,8 @@ defmodule LivebookWeb.UserPlug do
# Copies user_data from cookie to session, so that it's
# accessible to LiveViews
defp mirror_user_data_in_session(conn) when conn.halted, do: conn
defp mirror_user_data_in_session(conn) do
user_data = conn.cookies["lb:user_data"] |> Base.decode64!() |> Jason.decode!()
put_session(conn, :user_data, user_data)

View file

@ -100,6 +100,54 @@ defmodule Livebook.AppsTest do
Livebook.App.close(pid)
end
@tag :tmp_dir
test "deploys apps with offline hub secrets", %{tmp_dir: tmp_dir} do
app_path = Path.join(tmp_dir, "app1.livemd")
hub = offline_hub()
File.write!(app_path, """
<!-- livebook:{"app_settings":{"slug":"offline_hub_app2"},"hub_id":"team-org-number-3079"} -->
# App 2
## Section 1
```elixir
IO.puts("hey")
```
<!-- livebook:{"offset":148,"stamp":{"token":"QTEyOEdDTQ.M486X5ISDg_wVwvndrMYJKIkfXa5qAwggh5LzkV41wVOY8SNQvfA4EFx4Tk.RrcteIrzJVrqQdhH.mtmu5KFj.bibmp2rxZoniYX1xY8dTvw","token_signature":"28ucTCoDXxahOIMg7SvdYIoLpGIUSahEa7mchH0jKncKeZH8-w-vOaL1F1uj_94lqQJFkmHWv988__1bPmYCorw7F1wAvAaprt3o2vSitWWmBszuF5JaimkFqOFcK3mc4NHuswQKuBjSL0W_yR-viiwlx6zPNsTpftVKjRI2Cri1PsMeZgahfdR2gy1OEgavzU6J6YWsNQHIMWgt5gwT6fIua1zaV7K8-TA6-6NRgcfG-pSJqRIm-3-vnbH5lkXRCgXCo_S9zWa6Jrcl5AbLObSr5DUueiwac1RobH7jNghCm1F-o1cUk9W-BJRZ7igVMwaYqLaOnKO8ya9CrkIiMg","version":1}} -->
""")
Livebook.Apps.subscribe()
secret = %Livebook.Secrets.Secret{
name: "DB_PASSWORD",
value: "postgres",
hub_id: hub.id
}
put_offline_hub_secret(secret)
assert secret in Livebook.Hubs.get_secrets(hub)
Livebook.Apps.deploy_apps_in_dir(tmp_dir)
assert_receive {:app_created, %{pid: pid, slug: "offline_hub_app2"}}
assert_receive {:app_updated,
%{
slug: "offline_hub_app2",
sessions: [%{app_status: %{execution: :executed}}]
}}
session_id = Livebook.App.get_session_id(pid)
{:ok, session} = Livebook.Sessions.fetch_session(session_id)
assert %{hub_secrets: [^secret]} = Livebook.Session.get_data(session.pid)
Livebook.App.close(pid)
remove_offline_hub_secret(secret)
end
@tag :tmp_dir
test "skips apps with incomplete config and warns", %{tmp_dir: tmp_dir} do
app_path = Path.join(tmp_dir, "app.livemd")

View file

@ -62,7 +62,7 @@ defmodule Livebook.LiveMarkdown.ImportTest do
```
"""
{notebook, %{warnings: []}} = Import.notebook_from_livemd(markdown)
{notebook, []} = Import.notebook_from_livemd(markdown)
# Match only on the relevant fields as some may be generated (ids).
@ -159,7 +159,7 @@ defmodule Livebook.LiveMarkdown.ImportTest do
| Maine | ME | Augusta |
"""
{notebook, %{warnings: []}} = Import.notebook_from_livemd(markdown)
{notebook, []} = Import.notebook_from_livemd(markdown)
assert %Notebook{
name: "My Notebook",
@ -188,7 +188,7 @@ defmodule Livebook.LiveMarkdown.ImportTest do
Some markdown.
"""
{notebook, %{warnings: []}} = Import.notebook_from_livemd(markdown)
{notebook, []} = Import.notebook_from_livemd(markdown)
assert %Notebook{
name: "Untitled notebook",
@ -213,7 +213,7 @@ defmodule Livebook.LiveMarkdown.ImportTest do
###### Tiny heading
"""
{notebook, %{warnings: messages}} = Import.notebook_from_livemd(markdown)
{notebook, messages} = Import.notebook_from_livemd(markdown)
assert %Notebook{
name: "Untitled notebook",
@ -257,7 +257,7 @@ defmodule Livebook.LiveMarkdown.ImportTest do
## # Section
"""
{notebook, %{warnings: []}} = Import.notebook_from_livemd(markdown)
{notebook, []} = Import.notebook_from_livemd(markdown)
assert %Notebook{
name: "My *Notebook*",
@ -278,7 +278,7 @@ defmodule Livebook.LiveMarkdown.ImportTest do
## Actual section
"""
{notebook, %{warnings: []}} = Import.notebook_from_livemd(markdown)
{notebook, []} = Import.notebook_from_livemd(markdown)
assert %Notebook{
name: "My Notebook",
@ -307,7 +307,7 @@ defmodule Livebook.LiveMarkdown.ImportTest do
```
"""
{notebook, %{warnings: []}} = Import.notebook_from_livemd(markdown)
{notebook, []} = Import.notebook_from_livemd(markdown)
assert %Notebook{
name: "Untitled notebook",
@ -337,7 +337,7 @@ defmodule Livebook.LiveMarkdown.ImportTest do
Some markdown.
"""
{notebook, %{warnings: messages}} = Import.notebook_from_livemd(markdown)
{notebook, messages} = Import.notebook_from_livemd(markdown)
assert %Notebook{
name: "My Notebook",
@ -369,7 +369,7 @@ defmodule Livebook.LiveMarkdown.ImportTest do
Some markdown.
"""
{_notebook, %{warnings: messages}} = Import.notebook_from_livemd(markdown)
{_notebook, messages} = Import.notebook_from_livemd(markdown)
assert ["line 3 - closing unclosed backquotes ` at end of input"] == messages
end
@ -395,7 +395,7 @@ defmodule Livebook.LiveMarkdown.ImportTest do
```
"""
{notebook, %{warnings: []}} = Import.notebook_from_livemd(markdown)
{notebook, []} = Import.notebook_from_livemd(markdown)
assert %Notebook{
name: "My Notebook",
@ -451,7 +451,7 @@ defmodule Livebook.LiveMarkdown.ImportTest do
```
"""
{notebook, %{warnings: []}} = Import.notebook_from_livemd(markdown)
{notebook, []} = Import.notebook_from_livemd(markdown)
assert %Notebook{
name: "My Notebook",
@ -499,7 +499,7 @@ defmodule Livebook.LiveMarkdown.ImportTest do
Cell 2
"""
{notebook, %{warnings: []}} = Import.notebook_from_livemd(markdown)
{notebook, []} = Import.notebook_from_livemd(markdown)
assert %Notebook{
name: "My Notebook",
@ -543,7 +543,7 @@ defmodule Livebook.LiveMarkdown.ImportTest do
Cell 1
"""
{notebook, %{warnings: []}} = Import.notebook_from_livemd(markdown)
{notebook, []} = Import.notebook_from_livemd(markdown)
assert %Notebook{
name: "My Notebook",
@ -581,7 +581,7 @@ defmodule Livebook.LiveMarkdown.ImportTest do
Cell 1
"""
{notebook, %{warnings: messages}} = Import.notebook_from_livemd(markdown)
{notebook, messages} = Import.notebook_from_livemd(markdown)
assert %Notebook{
name: "My Notebook",
@ -630,7 +630,7 @@ defmodule Livebook.LiveMarkdown.ImportTest do
```
"""
{notebook, %{warnings: []}} = Import.notebook_from_livemd(markdown)
{notebook, []} = Import.notebook_from_livemd(markdown)
assert %Notebook{
name: "My Notebook",
@ -676,7 +676,7 @@ defmodule Livebook.LiveMarkdown.ImportTest do
```
"""
{notebook, %{warnings: []}} = Import.notebook_from_livemd(markdown)
{notebook, []} = Import.notebook_from_livemd(markdown)
assert %Notebook{
name: "My Notebook",
@ -715,7 +715,7 @@ defmodule Livebook.LiveMarkdown.ImportTest do
# My Notebook
"""
{notebook, %{warnings: []}} = Import.notebook_from_livemd(markdown)
{notebook, []} = Import.notebook_from_livemd(markdown)
assert %Notebook{name: "My Notebook", persist_outputs: true} = notebook
end
@ -728,7 +728,7 @@ defmodule Livebook.LiveMarkdown.ImportTest do
# My Notebook
"""
{notebook, %{warnings: []}} = Import.notebook_from_livemd(markdown)
{notebook, []} = Import.notebook_from_livemd(markdown)
assert %Notebook{name: "My Notebook", autosave_interval_s: 10} = notebook
end
@ -740,7 +740,7 @@ defmodule Livebook.LiveMarkdown.ImportTest do
# My Notebook
"""
{notebook, %{warnings: []}} = Import.notebook_from_livemd(markdown)
{notebook, []} = Import.notebook_from_livemd(markdown)
assert %Notebook{name: "My Notebook", default_language: :erlang} = notebook
end
@ -754,7 +754,7 @@ defmodule Livebook.LiveMarkdown.ImportTest do
# My Notebook
"""
{notebook, %{warnings: []}} = Import.notebook_from_livemd(markdown)
{notebook, []} = Import.notebook_from_livemd(markdown)
assert %Notebook{name: "My Notebook", hub_id: ^hub_id} = notebook
end
@ -766,7 +766,7 @@ defmodule Livebook.LiveMarkdown.ImportTest do
# My Notebook
"""
{notebook, %{warnings: messages}} = Import.notebook_from_livemd(markdown)
{notebook, messages} = Import.notebook_from_livemd(markdown)
assert messages == ["ignoring notebook Hub with unknown id"]
assert notebook.hub_id != "nonexistent"
@ -780,7 +780,7 @@ defmodule Livebook.LiveMarkdown.ImportTest do
# My Notebook
"""
{notebook, %{warnings: []}} = Import.notebook_from_livemd(markdown)
{notebook, []} = Import.notebook_from_livemd(markdown)
assert %Notebook{
name: "My Notebook",
@ -804,7 +804,7 @@ defmodule Livebook.LiveMarkdown.ImportTest do
# My Notebook
"""
{notebook, %{warnings: []}} = Import.notebook_from_livemd(markdown)
{notebook, []} = Import.notebook_from_livemd(markdown)
assert %Notebook{
name: "My Notebook",
@ -823,7 +823,7 @@ defmodule Livebook.LiveMarkdown.ImportTest do
<!-- livebook:{"livebook_object":"cell_input","name":"length","type":"text","value":"100"} -->
"""
{_notebook, %{warnings: messages}} = Import.notebook_from_livemd(markdown)
{_notebook, messages} = Import.notebook_from_livemd(markdown)
assert [
"found an input cell, but those are no longer supported, please use Kino.Input instead"
@ -839,7 +839,7 @@ defmodule Livebook.LiveMarkdown.ImportTest do
<!-- livebook:{"livebook_object":"cell_input","name":"length","reactive":true,"type":"text","value":"100"} -->
"""
{_notebook, %{warnings: messages}} = Import.notebook_from_livemd(markdown)
{_notebook, messages} = Import.notebook_from_livemd(markdown)
assert [
"found an input cell, but those are no longer supported, please use Kino.Input instead." <>
@ -870,7 +870,7 @@ defmodule Livebook.LiveMarkdown.ImportTest do
```
"""
{notebook, %{warnings: []}} = Import.notebook_from_livemd(markdown)
{notebook, []} = Import.notebook_from_livemd(markdown)
assert %Notebook{
name: "My Notebook",
@ -902,7 +902,7 @@ defmodule Livebook.LiveMarkdown.ImportTest do
![](images/dog.jpeg)
"""
{_notebook, %{warnings: messages}} = Import.notebook_from_livemd(markdown)
{_notebook, messages} = Import.notebook_from_livemd(markdown)
assert [
"found Markdown images pointing to the images/ directory. Using this directory has been deprecated, please use notebook files instead"
@ -929,7 +929,7 @@ defmodule Livebook.LiveMarkdown.ImportTest do
```
"""
{notebook, %{warnings: messages}} = Import.notebook_from_livemd(markdown)
{notebook, messages} = Import.notebook_from_livemd(markdown)
assert %Notebook{
name: "My Notebook",
@ -961,7 +961,7 @@ defmodule Livebook.LiveMarkdown.ImportTest do
```
"""
{notebook, %{warnings: messages}} = Import.notebook_from_livemd(markdown)
{notebook, messages} = Import.notebook_from_livemd(markdown)
assert %Notebook{
name: "My Notebook",
@ -1003,7 +1003,7 @@ defmodule Livebook.LiveMarkdown.ImportTest do
```
"""
{notebook, %{warnings: messages}} = Import.notebook_from_livemd(markdown)
{notebook, messages} = Import.notebook_from_livemd(markdown)
assert %Notebook{
name: "My Notebook",
@ -1040,7 +1040,7 @@ defmodule Livebook.LiveMarkdown.ImportTest do
## Section 1
"""
{notebook, %{warnings: []}} = Import.notebook_from_livemd(markdown)
{notebook, []} = Import.notebook_from_livemd(markdown)
assert %Notebook{
name: "My Notebook",
@ -1067,7 +1067,7 @@ defmodule Livebook.LiveMarkdown.ImportTest do
```
"""
{notebook, %{warnings: []}} = Import.notebook_from_livemd(markdown)
{notebook, []} = Import.notebook_from_livemd(markdown)
assert %Notebook{
name: "My Notebook",
@ -1120,7 +1120,7 @@ defmodule Livebook.LiveMarkdown.ImportTest do
<!-- livebook:{"offset":58,"stamp":{"token":"QTEyOEdDTQ.LF8LTeMYrtq8S7wsKMmk2YgOQzMAkEKT2d8fq1Gz3Ot1mydOgEZ1B4hcEZc.Wec6NwBQ584kE661.a_N-5jDiWrjhHha9zxHQ6JJOmxeqgiya3m6YlKt1Na_DPnEfXyLnengaUzQSrf8.ZoD5r6-H87RpTyvFkvEOQw","version":1}} -->
"""
{notebook, %{warnings: []}} = Import.notebook_from_livemd(markdown)
{notebook, []} = Import.notebook_from_livemd(markdown)
assert %Notebook{hub_secret_names: ["DB_PASSWORD"]} = notebook
end
@ -1138,7 +1138,7 @@ defmodule Livebook.LiveMarkdown.ImportTest do
<!-- livebook:{"offset":58,"stamp":{"token":"invalid","version":1}} -->
"""
{notebook, %{warnings: messages}} = Import.notebook_from_livemd(markdown)
{notebook, messages} = Import.notebook_from_livemd(markdown)
assert %Notebook{hub_secret_names: []} = notebook
assert messages == ["failed to verify notebook stamp"]
@ -1159,10 +1159,10 @@ defmodule Livebook.LiveMarkdown.ImportTest do
<!-- livebook:{"offset":111,"stamp":{"token":"QTEyOEdDTQ.VezIVId8jnwawq79Mrxfx-bCeatRPQhg6zRw4gHjfGC_SimS5WSbdETLVnc.IXXzTiDf3JRLiNGt.rtLJ97RxNYLeF_x-2AigBt_y0bYdlh30bnEKUO7FKfJL-9EB6EQOgr1WbdHTkw.rU1OsjOhfU48tE3s0GB6Ag","token_signature":"M_LZP7ssEdLiVqo8_bZYFtJYAzqV5wczQF1mUdaOZeTYoCYXeLA9VuEajMCOwiVgMjfG24Y4PHrnABGUj0bB74xgEMOLtb6Ark42pCJfGUDLXpB2azMnwY6VRXi1nBUTnb_-MajpJVF_MgWGuj0cxyQ4rlfBu2VK1FQn8JGopLKBD7LcICZ9wkLJw3H_lXGTbVxnS6tLcvJZQdLJccTe4edpdF-_uG-d1KqtJva50jZ9vevyxh6zUubl0k5JnA3SUaTpc7AEsqmAMx9qQsoR64P-VobSFTKuuOFrlQsq21OupPCRKmUBU-RKrBvHbc6Fa1eoQigEQom5droj9cASdw","version":1}} -->
"""
{notebook, %{warnings: []}} = Import.notebook_from_livemd(markdown)
{notebook, []} = Import.notebook_from_livemd(markdown)
assert %Notebook{
hub_id: "personal-hub",
hub_id: "team-org-number-3079",
hub_secret_names: ["DB_PASSWORD"],
teams_enabled: true
} = notebook
@ -1183,9 +1183,9 @@ defmodule Livebook.LiveMarkdown.ImportTest do
<!-- livebook:{"offset":58,"stamp":{"token":"invalid","token_signature":"invalid","version":1}} -->
"""
{notebook, %{warnings: messages}} = Import.notebook_from_livemd(markdown)
{notebook, messages} = Import.notebook_from_livemd(markdown)
assert %Notebook{hub_id: "personal-hub", teams_enabled: false} = notebook
assert %Notebook{hub_id: "team-org-number-3079", teams_enabled: true} = notebook
assert messages == ["failed to verify notebook stamp"]
end
@ -1206,7 +1206,7 @@ defmodule Livebook.LiveMarkdown.ImportTest do
<!-- livebook:{"offset":58,"stamp":{"token":"invalid","token_signature":"invalid","version":1}} -->
"""
{notebook, %{warnings: [_]}} = Import.notebook_from_livemd(markdown)
{notebook, [_]} = Import.notebook_from_livemd(markdown)
assert %Notebook{hub_id: ^hub_id, teams_enabled: true} = notebook
end
@ -1220,7 +1220,7 @@ defmodule Livebook.LiveMarkdown.ImportTest do
# My Notebook
"""
{notebook, %{warnings: []}} = Import.notebook_from_livemd(markdown)
{notebook, []} = Import.notebook_from_livemd(markdown)
assert %Notebook{
file_entries: [
@ -1245,7 +1245,7 @@ defmodule Livebook.LiveMarkdown.ImportTest do
# My Notebook
"""
{notebook, %{warnings: messages}} = Import.notebook_from_livemd(markdown)
{notebook, messages} = Import.notebook_from_livemd(markdown)
assert %Notebook{file_entries: []} = notebook
@ -1259,7 +1259,7 @@ defmodule Livebook.LiveMarkdown.ImportTest do
# My Notebook
"""
{notebook, %{warnings: []}} = Import.notebook_from_livemd(markdown)
{notebook, []} = Import.notebook_from_livemd(markdown)
assert %Notebook{
file_entries: [
@ -1327,7 +1327,7 @@ defmodule Livebook.LiveMarkdown.ImportTest do
}
|> Livebook.LiveMarkdown.Export.notebook_to_livemd()
{notebook, %{warnings: []}} = Import.notebook_from_livemd(markdown)
{notebook, []} = Import.notebook_from_livemd(markdown)
assert %Notebook{
file_entries: [

View file

@ -0,0 +1,48 @@
defmodule Livebook.Integration.AppsTest do
use Livebook.TeamsIntegrationCase, async: true
describe "deploy_apps_in_dir/1" do
@tag :tmp_dir
test "deploys apps with hub secrets", %{user: user, node: node, tmp_dir: tmp_dir} do
hub = create_team_hub(user, node)
hub_id = hub.id
app_path = Path.join(tmp_dir, "app.livemd")
secret = insert_secret(name: "DB_PASSWORD", value: "postgres", hub_id: hub.id)
secret_name = secret.name
markdown = """
<!-- livebook:{"app_settings":{"slug":"#{hub_id}"},"hub_id":"#{hub_id}"} -->
# Team Hub
## Fetch Env Var
```elixir
System.fetch_env!("LB_#{secret.name}")
```
"""
{notebook, []} = Livebook.LiveMarkdown.notebook_from_livemd(markdown)
notebook = Map.replace!(notebook, :hub_secret_names, [secret_name])
{source, []} = Livebook.LiveMarkdown.notebook_to_livemd(notebook)
File.write!(app_path, source)
Livebook.Apps.subscribe()
Livebook.Apps.deploy_apps_in_dir(tmp_dir)
assert_receive {:app_created, %{pid: pid, slug: ^hub_id}}
assert_receive {:app_updated,
%{slug: ^hub_id, sessions: [%{app_status: %{execution: :executed}}]}}
session_id = Livebook.App.get_session_id(pid)
{:ok, session} = Livebook.Sessions.fetch_session(session_id)
assert %{notebook: %{hub_secret_names: [^secret_name]}, hub_secrets: [^secret]} =
Livebook.Session.get_data(session.pid)
Livebook.App.close(pid)
end
end
end

View file

@ -3,8 +3,6 @@ defmodule Livebook.Hubs.TeamClientTest do
alias Livebook.Hubs.TeamClient
import Livebook.HubHelpers
@moduletag :capture_log
setup do

View file

@ -7,21 +7,7 @@ defmodule Livebook.Hubs.TeamTest do
describe "stamping" do
test "generates and verifies stamp for a notebook", %{user: user, node: node} do
org = :erpc.call(node, Hub.Integration, :create_org, [])
org_key = :erpc.call(node, Hub.Integration, :create_org_key, [[org: org]])
org_key_pair = :erpc.call(node, Hub.Integration, :create_org_key_pair, [[org: org]])
token = :erpc.call(node, Hub.Integration, :associate_user_with_org, [user, org])
team =
build(:team,
id: "team-#{org.name}",
hub_name: org.name,
user_id: user.id,
org_id: org.id,
org_key_id: org_key.id,
org_public_key: org_key_pair.public_key,
session_token: token
)
team = create_team_hub(user, node)
notebook_source = """
# Team notebook

View file

@ -5,8 +5,6 @@ defmodule Livebook.Teams.ConnectionTest do
alias Livebook.Teams.Connection
import Livebook.HubHelpers
describe "connect" do
test "successfully authenticates the websocket connection", %{user: user, node: node} do
{_, headers} = build_team_headers(user, node)

View file

@ -2,22 +2,21 @@ defmodule LivebookWeb.Integration.Hub.EditLiveTest do
use Livebook.TeamsIntegrationCase, async: true
import Phoenix.LiveViewTest
import Livebook.HubHelpers
import Livebook.TestHelpers
alias Livebook.Hubs
setup %{user: user, node: node} do
Livebook.Hubs.subscribe([:crud, :connection, :secrets])
hub = create_team_hub(user, node)
id = hub.id
assert_receive {:hub_connected, ^id}
{:ok, hub: hub}
end
describe "team" do
setup %{user: user, node: node} do
Livebook.Hubs.subscribe([:crud, :connection, :secrets])
hub = create_team_hub(user, node)
id = hub.id
assert_receive {:hub_connected, ^id}
{:ok, hub: hub}
end
test "updates the hub", %{conn: conn, hub: hub} do
{:ok, view, _html} = live(conn, ~p"/hub/#{hub.id}")

View file

@ -3,7 +3,6 @@ defmodule LivebookWeb.Hub.NewLiveTest do
alias Livebook.Teams.Org
import Livebook.HubHelpers
import Phoenix.LiveViewTest
test "render hub selection cards", %{conn: conn} do

View file

@ -2,7 +2,6 @@ defmodule LivebookWeb.Integration.SessionLiveTest do
use Livebook.TeamsIntegrationCase, async: true
import Phoenix.LiveViewTest
import Livebook.HubHelpers
import Livebook.SessionHelpers
alias Livebook.{Sessions, Session}

View file

@ -25,7 +25,8 @@ defmodule Livebook.Factory do
org_key_id: 1,
org_public_key: Livebook.Utils.random_id(),
teams_key: org.teams_key,
session_token: Livebook.Utils.random_short_id()
session_token: Livebook.Utils.random_short_id(),
offline: nil
}
end

View file

@ -10,7 +10,14 @@ defmodule Livebook.HubHelpers do
teams_key: "A9TarFeAzmX3sDwSPm5JP5qbLPnNpLpzmjVZUCHXwmI",
org_public_key:
"MIIBCgKCAQEA5v_qciaRGOZd5kgCQbhQDgFCnTnIKI5xzN4m4rVtLXMPH7RTA-K6C-e4wy2gn8zulXgSYX4vXDACSjFAG4PlFhXTPgb-v3rFLwbBrUHdaTMTyxRdK52NyNoDpYklQ7FaEU9vr3Z_-cpAQjdADOV1k45GmFe3bo4gImIfUSDYp1rRiEsYcIBt0Wc0S-vQHKSlmfcCexe254_UkvWjLW7KO790bem-PSWcBI_713oRr2mQoxXeeGKd5dSyFsIr5SZXVRWcRK3soQimCXB0ddBSXZ7d2Md3P9Ylo7TcYdBGHlwVIsrmB-P70KPHPYuAVgS9QsIiiMGXPwYVW77xNRTlcwIDAQAB",
hub_name: "org-number-3079"
hub_name: "org-number-3079",
user_id: 0,
org_id: 0,
org_key_id: 0,
session_token: "",
offline: %Livebook.Hubs.Team.Offline{
secrets: []
}
}
def create_team_hub(user, node) do
@ -67,11 +74,38 @@ defmodule Livebook.HubHelpers do
end
def set_offline_hub() do
Livebook.Hubs.set_offline_hub(@offline_hub)
hub = offline_hub()
^hub = Livebook.Hubs.save_hub(hub)
hub
end
def offline_hub(), do: @offline_hub
def put_offline_hub_secret(secret) do
hub = offline_hub()
{:ok, pid} = hub_pid(hub)
{secret_key, sign_secret} = Livebook.Teams.derive_keys(hub.teams_key)
value = Livebook.Teams.encrypt(secret.value, secret_key, sign_secret)
secret_created = LivebookProto.SecretCreated.new(name: secret.name, value: value)
send(pid, {:event, :secret_created, secret_created})
end
def remove_offline_hub_secret(secret) do
hub = offline_hub()
{:ok, pid} = hub_pid(hub)
secret_deleted = LivebookProto.SecretDeleted.new(name: secret.name)
send(pid, {:event, :secret_deleted, secret_deleted})
end
defp hub_pid(hub) do
if pid = GenServer.whereis({:via, Registry, {Livebook.HubsRegistry, hub.id}}) do
{:ok, pid}
end
end
defp hub_element_id(id), do: "#hubs #hub-#{id}"
defp erpc_call(node, fun, args) do

View file

@ -11,6 +11,8 @@ defmodule Livebook.TeamsIntegrationCase do
@moduletag :teams_integration
alias Livebook.TeamsServer
import Livebook.HubHelpers
end
end