Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Kino.Hub.on_join #410

Closed
josevalim opened this issue Mar 28, 2024 · 13 comments
Closed

Add Kino.Hub.on_join #410

josevalim opened this issue Mar 28, 2024 · 13 comments

Comments

@josevalim
Copy link
Contributor

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.

@spunkedy
Copy link

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
  • 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

@josevalim
Copy link
Contributor Author

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.

@spunkedy
Copy link

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

Loading

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.

@josevalim
Copy link
Contributor Author

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:

  1. 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)

  2. for single-session apps, the upcoming Kino.LiveFrame (Add 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!

@josevalim josevalim closed this as not planned Won't fix, can't repro, duplicate, stale Apr 2, 2024
@spunkedy
Copy link

spunkedy commented Apr 2, 2024

hah, glad I didn't go too far down the road :)

@josevalim
Copy link
Contributor Author

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.

@spunkedy
Copy link

spunkedy commented May 12, 2024 via email

@spunkedy
Copy link

Tried via getting the client id and then getting details as well as the user_info/0

image

image

<!-- 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")

@jonatanklosko
Copy link
Member

@spunkedy you need to change the notebook to use your team workspace:

image

@spunkedy
Copy link

@spunkedy you need to change the notebook to use your team workspace:

image

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.

@josevalim
Copy link
Contributor Author

@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).

@spunkedy
Copy link

@josevalim

I know we talked earlier around parts of this. Maybe it's time to restart the conversation, will reply to the email.

@hugobarauna
Copy link
Member

@spunkedy Here's something you can do to get the user info:

CleanShot 2024-05-29 at 11 28 20


# 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants