Add Kino.Hub.on_join
josevalim opened this issue · comments
The callback may return either :ok or :forbidden. If the latter is returned, the user is redirected back to the home page. If the callback errors, we should probably force a page reload or send them back to the home page.
I don’t mind taking a stab at this. I have some time this week.
if we returned:
{:ok, _}
for approved
{:forbidden, “” <> reason}
for forbidden
we could allow live books to give the reason if they chose to.
Since the ZTA already does the authentication check, at this level having groups would help be able to make the decisions.
At a high level I am thinking:
- logger info log for audit events
- at least
- load
- Login
- Fail
- at least
- add user groups to user map to allow for group checks
- redirect with flash error message ? Or just go to the default 403 error controller with the override message
add user groups to user map to allow for group checks
Unfortunately there is no unified interface across ZTAs for groups. So I am giving you all JWT fields and you can decide it yourself. The logging also can be totally done by your own code for now. But other than that, we are good to go.
Watch out #405, because that will have a foundation for executing code for each user, which we can use as the foundation here.
Thinking out loud
Starting to look through the code more closely to start building a mental map. I am trying to figure out how to bridge the gaps to create the bridge
call back between kino and livebook.
It looks like the user_info/0
potentially would need to get mapped out. https://github.com/livebook-dev/kino/blob/v0.12.3/lib/kino/hub.ex#L11
To do this it looks like we would need to do io_request
something like:
io_request(:livebook_get_user_info)
This would at least allow :multi_session
types to be able to do checks before rendering the kino if they wanted to.
defp app_info_for_runtime(state) do
case state.data do
%{mode: :app, notebook: %{app_settings: %{multi_session: true}}} ->
info = %{type: :multi_session}
if user = state.started_by do
started_by = user_info(user)
Map.put(info, :started_by, started_by)
else
info
end
%{mode: :app, notebook: %{app_settings: %{multi_session: false}}} ->
%{type: :single_session}
_ ->
%{type: :none}
end
end
So it looks like putting it in the Evaluator.ClientTracker
for when the client joins to call the Kino.Hub.on_join
🤔
on_join
Trying to diget livebook and kino at the same time I am trying to think through how to link between the two. So I appreciate the grace as I am trying to help.
graph LR;
subgraph livebook
subgraph runtime_server_gen_server
runtime_server_g["Livebook.Runtime.ErlDist.RuntimeServer"]-->client_tracker["Livebook.Runtime.Evaluator.ClientTracker"]
uknown
end
client_tracker-->has_callback{Do I have a client on join callback?}-- yes-->send_message
has_callback-- no -->nothing
subgraph kino
callback-->register_callback-->io_request
callback-->check_auth-->register_callback
end
io_request--add to state -->has_callback
send_message-->check_auth-->uknown["TBD??"]-->403
end
It feels complex, and usually that is a sign I don't have a good understanding, so I figured I would take a step back and wait haha.
Hi @spunkedy, thanks for sparking the discussion here. I actually think you are on the right direction and it is even a bit trickier than that, because we need to make sure the page is not rendered until the callback runs, pushing synchronization all the way up to the LiveView.
For this reason, we chose to tackle the problem differently:
-
for multi-session apps, you can use the
Kino.Hub.app_info().started_by
(the payload should already be available if using main on both) -
for single-session apps, the upcoming Kino.LiveFrame (#405) and the upcoming
Kino.Hub.user_info(client_id)
should also provide what is necessary to control access
We should have these in place soon (before the next release), thanks!
hah, glad I didn't go too far down the road :)
Here is a branch if you to try out #426. Note you will need Livebook from main and install Kino pointing to the branch. You can use Kino.Hub.user_info
to get auth information about the user who starts the wizard.
Tried via getting the client id and then getting details as well as the user_info/0
<!-- livebook:{"app_settings":{"access_type":"public","slug":"asdf"}} -->
# Untitled notebook
```elixir
Mix.install([
{:kino, git: "https://github.com/livebook-dev/kino.git", ref: "jv-kino-wizard"}
])
Section
import Kino.Control
import Kino.Shorts
import Kino.Wizard
defmodule MyWizard do
# @behaviour Kino.Wizard
def init(_data, :ok) do
{:ok, %{page: 1, name: nil, address: nil}}
end
defp step_one(%{data: %{name: name}}, state) do
if name == "" do
%{state | name: name}
else
%{state | name: name, page: 2}
end
end
defp step_two(%{data: %{address: address}}, state) do
case address do
"BUMP" <> _ -> %{state | address: address <> "!"}
"" -> %{state | address: ""}
_ -> %{state | address: address, page: 3}
end
end
defp go_back(_, state) do
%{state | page: state.page - 1}
end
def render(%{page: 1} = state) do
{:ok, [user]} = Kino.Bridge.monitor_clients(self()) |> IO.inspect
details = user
|> IO.inspect()
|> Kino.Hub.user_info()
|> IO.inspect
details_via_bridge = user
|> Kino.Bridge.get_user_info()
|> IO.inspect
form(
[name: Kino.Input.text("Name", default: state.name)],
submit: "Step one"
)
|> control(&step_one/2)
Kino.Markdown.new("""
```
Running Details
type: #{inspect(Kino.Bridge.get_app_info())}
details for client_id: #{user}
#{inspect(details)}
details via bridge:
#{inspect(details_via_bridge)}
```
""")
end
def render(%{page: 2} = state) do
Kino.Control.form(
[address: Kino.Input.text("Address", default: state.address)],
submit: "Step two"
)
|> control(&step_two/2)
|> add_go_back()
end
def render(%{page: 3} = state) do
"Well done, #{state.name}. You live in #{state.address}."
|> add_go_back()
end
defp add_go_back(element) do
button =
button("Go back")
|> control(&go_back/2)
grid([element, button])
end
end
Kino.Wizard.new(MyWizard, :ok, "Start Runbook")
@spunkedy you need to change the notebook to use your team workspace:
![image](https://private-user-images.githubusercontent.com/17034772/334683597-12614cbf-e507-4382-97d9-9f87b602382f.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MTkyODM0NTYsIm5iZiI6MTcxOTI4MzE1NiwicGF0aCI6Ii8xNzAzNDc3Mi8zMzQ2ODM1OTctMTI2MTRjYmYtZTUwNy00MzgyLTk3ZDktOWY4N2I2MDIzODJmLnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNDA2MjUlMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjQwNjI1VDAyMzkxNlomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPTc4N2UzODI4NWFlNzg4ZDlkZjg3YmY2YjExMjQ3M2EyY2MyYzIwOGQzYWU0OTMxMzBhMjI5YWQxYjcxNGI0MGEmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0JmFjdG9yX2lkPTAma2V5X2lkPTAmcmVwb19pZD0wIn0.iYzTW8Z2bP6dhsAlv6UNtra554PIdqfbRcWR9caXtmY)
@spunkedy you need to change the notebook to use your team workspace:
![]()
We have no teams setup and unfortunately the deployment pattern of our livebooks is for an offline version where we deploy via docker and pre load the apps.
@spunkedy you can use Teams for offline deployments as well. It makes it easier because you can share configuration, secrets, and other data across your team members, making sure that everyone can deploy the same base version (instead of each of you deploying slightly different ones).
I know we talked earlier around parts of this. Maybe it's time to restart the conversation, will reply to the email.
@spunkedy Here's something you can do to get the user info:
# Untitled notebook
```elixir
Mix.install([
{:kino, github: "livebook-dev/kino"}
])
Section
button = Kino.Control.button("Print user info")
frame = Kino.Frame.new()
Kino.Frame.append(frame, button)
Kino.listen(button, fn event ->
%{origin: origin} = event
{:ok, user_info} = Kino.Workspace.user_info(origin)
Kino.Frame.append(frame, user_info)
end)
frame
We've thinking of other APIs to expose the user_info when the Livebook app is accesed, without the need to require some user interaction like in the example above.