Skip to content

Add internal vs hackathon Jupyter spaces - #88

Open
kirillmitrofanov wants to merge 2 commits into
mainfrom
feat/two-jupyter-spaces
Open

Add internal vs hackathon Jupyter spaces#88
kirillmitrofanov wants to merge 2 commits into
mainfrom
feat/two-jupyter-spaces

Conversation

@kirillmitrofanov

Copy link
Copy Markdown
Collaborator

Summary

  • Two shared JupyterLabs (internal / hackathon) with separate host trees (codes/ vs codes-hackathon/). Isolation between groups is filesystem-level; hiding other private project folders inside a Lab is visual only (kernel/terminal still see the mount).
  • App/API tenants (nw-internal / nw-hackathon Keycloak groups) so GUI lists do not leak the other tenant’s public projects or nodes.
  • Node governance private → submitted → approved → public, node-reviewers group, audit log, tenant-scoped palette.
  • Frontend Jupyter URLs and Run-via-Jupyter use the caller’s tenant Hub user (no hardcoded user1 / password).

Operator runbook: deployment/JUPYTER_TWO_SPACES.md.

Not in this PR: live Hub recreate, production DB migrate, copying catalog nodes into the hackathon tree. Those are an explicit cutover.

Test plan

  • pytest test_tenants.py test_node_governance.py test_jupyter_listing_filter.py test_visibility.py (already passing in an ephemeral container)
  • After cutover (separate OK): guest Keycloak user sees only hackathon projects; Lab ls has no internal UUIDs
  • Internal user: app hides hackathon tenant; Lab file browser omits others’ private UUIDs; terminal ls still sees them (expected)
  • Approve a node in one tenant; it does not appear in the other tenant’s palette
  • No extra public Docker ports

Split shared Labs by group (hard isolation between trees; visual hide of others' private project dirs inside a Lab) and keep GUI/API lists on the same tenant boundary.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces two-tenant isolation (internal vs hackathon) across the GUI/API and JupyterHub, so projects/nodes are scoped to the caller’s tenant and the frontend opens Jupyter using a tenant-derived Hub user + viewer token (instead of hardcoded user1). It also adds a tenant-scoped node governance pipeline (private → submitted → approved/public) with reviewer permissions and auditing.

Changes:

  • Add tenant scoping for workflow projects (DB tenant column, tenant-filtered querysets/permissions, tenant-aware filesystem roots).
  • Add Jupyter “viewer token” session APIs + a JupyterLab contents filter extension to visually hide other users’ private project directories in the shared Lab, and update the frontend to open tenant-correct Jupyter URLs.
  • Add tenant-scoped node governance (status, reviewer group, endpoints, audit log) and expose status/actions in the frontend palette.

Reviewed changes

Copilot reviewed 47 out of 49 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
gui/workflow_frontend/src/views/home/type.ts Adds Tenant type and optional tenant field on Project.
gui/workflow_frontend/src/views/home/homeView.tsx Switches Jupyter URL construction to openJupyterTree() (tenant/session-driven).
gui/workflow_frontend/src/views/home/components/projectSelector.tsx Switches Jupyter open behavior to openJupyterTree() and standardizes project path usage.
gui/workflow_frontend/src/views/home/components/nodeDetailModal.tsx Switches to tenant-based Jupyter open helper (but currently leaves dead/unused code).
gui/workflow_frontend/src/views/home/components/jupyterModal.tsx Uses backend session + viewer token to build the Jupyter tree URL for iframe/new-tab.
gui/workflow_frontend/src/views/home/components/calculationNode.tsx Uses openJupyterTree() for node file navigation in Jupyter.
gui/workflow_frontend/src/views/box/boxView.tsx Adds node governance actions/status badges and uses tenant-based Jupyter open helper.
gui/workflow_frontend/src/hooks/useUploadedNodes.ts Extends uploaded-node response/types with tenant/reviewer/governance fields.
gui/workflow_frontend/src/api/jupyterTenant.ts New API module: caches Jupyter session, builds tree URLs, adds viewer token query param.
gui/workflow_backend/env.template Documents fallback Jupyter execution user configuration.
gui/workflow_backend/django-project/tests/test_tenants.py New tests for tenant isolation and viewer-token visible-path allowlisting.
gui/workflow_backend/django-project/tests/test_node_governance.py New tests for tenant-scoped node governance and reviewer permissions.
gui/workflow_backend/django-project/tests/test_jupyter_listing_filter.py New pure unit tests for the Jupyter contents filter logic.
gui/workflow_backend/django-project/neuroworkflow/PRIVACY_NOTICE.md Adds user-facing explanation that the Jupyter hide is visual-only within a tenant space.
gui/workflow_backend/django-project/neuroworkflow/jupyterhub_config.py Adds two-space volume mapping + tenant env + enables contents filter extension; tightens sudo default.
gui/workflow_backend/django-project/neuroworkflow/jupyter_tenant_filter.py New Jupyter server extension: filters directory listing and blocks contents GET outside allowlist.
gui/workflow_backend/django-project/neuroworkflow/jupyter_server_config.py Enables the Jupyter server extension in both Labs.
gui/workflow_backend/django-project/codes-hackathon/README.md Documents hackathon Lab filesystem tree and cutover expectations.
gui/workflow_backend/django-project/codes-hackathon/projects/.gitkeep Ensures hackathon projects directory exists in repo.
gui/workflow_backend/django-project/codes-hackathon/nodes/.gitkeep Ensures hackathon nodes directory exists in repo.
gui/workflow_backend/django-project/app/workflow/views.py Enforces tenant-scoped querysets and uses tenant Hub user for Jupyter URLs / execution.
gui/workflow_backend/django-project/app/workflow/viewer_tokens.py Adds signed short-lived viewer tokens that encode user+tenant+hub user.
gui/workflow_backend/django-project/app/workflow/urls.py Adds Jupyter session and visible-paths endpoints.
gui/workflow_backend/django-project/app/workflow/serializers.py Exposes tenant on projects (read-only).
gui/workflow_backend/django-project/app/workflow/permissions.py Adds tenant isolation checks + a tenant-scoped project queryset helper.
gui/workflow_backend/django-project/app/workflow/path_utils.py Makes project/nodes filesystem roots tenant-aware; propagates tenant through path guards.
gui/workflow_backend/django-project/app/workflow/models.py Adds tenant column to FlowProject.
gui/workflow_backend/django-project/app/workflow/migrations/0005_flowproject_tenant.py Migration for FlowProject tenant + ensures tenant/reviewer groups exist.
gui/workflow_backend/django-project/app/workflow/jupyter_views.py New backend APIs for session+visible paths, supporting viewer token + Keycloak auth.
gui/workflow_backend/django-project/app/workflow/jupyter_execution_service.py Changes default execution user to internal and maps legacy user1internal.
gui/workflow_backend/django-project/app/workflow/jupyter_auth.py Adds DRF authentication backend for “Viewer ” signed viewer tokens.
gui/workflow_backend/django-project/app/workflow/execution/remote_slurm_executor.py Copies tenant-correct project/nodes directories when staging remote runs.
gui/workflow_backend/django-project/app/workflow/execution/local_executor.py Resolves project directory via project model/tenant-aware helpers where possible.
gui/workflow_backend/django-project/app/tenants.py New tenant model (groups → tenant), hub user mapping, reviewer checks, login sync from Keycloak claims.
gui/workflow_backend/django-project/app/box/views.py Makes uploads tenant-scoped; adds node governance endpoints + tenant/reviewer info in list response.
gui/workflow_backend/django-project/app/box/urls.py Registers node governance/reviewer/audit endpoints.
gui/workflow_backend/django-project/app/box/services/python_file_service.py Makes node duplicate checks tenant-scoped and initializes governance/audit fields.
gui/workflow_backend/django-project/app/box/serializers.py Exposes governance fields (tenant/status/review_comment).
gui/workflow_backend/django-project/app/box/models.py Adds tenant + status + review metadata to nodes; adds NodeAuditLog model; adjusts uniqueness constraint.
gui/workflow_backend/django-project/app/box/migrations/0006_pythonfile_tenant_governance.py Migration for node tenant/governance fields + NodeAuditLog + backfill rules.
gui/workflow_backend/django-project/app/box/governance.py New governance logic with reviewer rules and audit logging.
gui/workflow_backend/django-project/app/auth/authViews.py Adds tenant/hub user/reviewer flags + notice to user profile response.
gui/workflow_backend/django-project/app/auth/authentication.py Syncs user tenant group membership from Keycloak token claims at login.
gui/README.md Documents JupyterHub allowed users env var.
gui/env.template Adds two-space JupyterHub env documentation.
gui/docker-compose.yml Mounts hackathon code tree; sets default execution user; exposes JupyterHub limits/env.
gui/docker-compose.prod.yml Updates allowed users default + sets default resource limits for Hub in prod.
gui/.env.example Documents new two-space JupyterHub env knobs.
deployment/JUPYTER_TWO_SPACES.md Adds operator runbook for two shared Labs, cutover steps, and tenant/governance behavior.
Suppressed comments (1)

gui/workflow_backend/django-project/app/box/services/python_file_service.py:57

  • When an identical file hash already exists, this code overwrites that existing PythonFile record and reassigns uploaded_by. This lets a different user (or a user upload matching catalog content) take over/replace someone else’s node by uploading identical content, which is a cross-user data integrity/security issue.
        if existing_file is not None:
            python_file = PythonFile.objects.get(id=existing_file.id)
            python_file.name = name
            python_file.description = description or ""
            python_file.category = category

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread gui/workflow_frontend/src/views/home/components/calculationNode.tsx Outdated
Comment thread gui/workflow_frontend/src/views/box/boxView.tsx
Comment thread gui/workflow_frontend/src/views/home/components/nodeDetailModal.tsx Outdated

@IzBrain67 IzBrain67 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @kirillmitrofanov, thanks a lot for this PR — the two-space design is a big step forward, and I really appreciate how honestly the visual-only nature of the in-Lab hiding is documented (the PRIVACY_NOTICE is a nice touch).

I ran an automated review over the diff (Claude Code) and am sharing the results for reference — please treat them as input rather than change requests, and feel free to push back on anything that doesn't match your intent or the actual runtime setup. The ones that map to changed lines are inline comments; a few relate to code outside the diff, so I'm listing those here:

Notes on code outside the diff

  • app/box/views.py:328 (code-save PUT / parameter update) — _can_modify_python_file only checks ownership, so it seems an owner can rewrite an approved/PUBLIC node's content after review: the status stays PUBLIC, the shared tenant nodes tree is rewritten via _update_nodes_folder_file, and no re-review or audit entry happens. A status downgrade to private (or reviewer-only edits) on content change might be worth considering so the governance pipeline isn't one-shot.
  • app/box/views.py:402 (bulk copy) and :557 (copy-by-filename) — the copied PythonFile doesn't set tenant/status, so a hackathon user's copy defaults to tenant="internal" and never appears in their list (and can't be managed afterwards — GET/PUT/DELETE 404). The duplicate-name checks around :506/:535 also look tenant-unscoped, so internal file names can block or rename hackathon copies.
  • app/box/views.py:1478 (NodeCategoryListView) and :1540 (category color update) — these still use Path(settings.MEDIA_ROOT) rather than nodes_root(tenant), so hackathon requests read and write the internal codes/nodes tree (the sibling UploadedNodesView was converted; these two seem to have been missed).

There are also a handful of smaller cleanup-level notes (dead code, duplicated exception boilerplate, a few lines over black's 88-char limit) — happy to share those separately if useful.

None of this is meant to block — take whatever is useful. Thanks again!

Comment thread gui/workflow_backend/django-project/neuroworkflow/jupyter_tenant_filter.py Outdated
Comment thread gui/workflow_backend/django-project/neuroworkflow/jupyter_tenant_filter.py Outdated
Comment thread gui/workflow_backend/django-project/neuroworkflow/jupyter_tenant_filter.py Outdated
Comment thread gui/workflow_backend/django-project/app/tenants.py Outdated
Comment thread gui/workflow_backend/django-project/app/box/governance.py
Comment thread gui/workflow_backend/django-project/app/workflow/jupyter_views.py Outdated
Comment thread gui/workflow_backend/django-project/app/auth/authentication.py
Comment thread gui/workflow_backend/django-project/app/box/services/python_file_service.py Outdated
…vernance exits.

Copy the viewer token from the Lab URL onto a cookie, wrap async contents get, and stop stealing /checkpoints. Tighten claim matching, keep owners able to see their own rows, and block self-approve plus hash-based ownership takeover.
@kirillmitrofanov

Copy link
Copy Markdown
Collaborator Author

Addressed the Copilot and @IzBrain67 review threads in 0944089.

Lab filter: copy nw_viewer from the tree URL onto a cookie, await async contents get, do not register a catch-all /api/contents handler (notebook checkpoints), and cap the allow-list cache. Tenant claims use exact group names. Owners still see their own rows after a tenant change; reviewers see APPROVED and cannot approve their own uploads. Identical-hash re-upload no longer steals ownership; hackathon node bytes go to codes-hackathon/nodes/.

Tests: test_tenants, test_node_governance, test_jupyter_listing_filter (26 passed). This is on the PR branch only — the live snnbuilder Hub/frontend were not restarted.

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

Successfully merging this pull request may close these issues.

3 participants