This commit is contained in:
tao.chen
2026-08-11 20:41:15 +08:00
parent e1360c5c38
commit d4013a07a9
+21 -29
View File
@@ -1,19 +1,16 @@
from __future__ import annotations
import re
from typing import Optional
from common.auth.jwt import JwtError, verify_jwt_token
from common.auth.membership import MembershipError, load_active_membership
from common.db.models import Scripts
from fastapi import APIRouter, Depends, HTTPException, Request, Response
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from backend.dependencies import database_session
from backend.runtime_client import RuntimeClientError
from common.auth.jwt import JwtError, verify_jwt_token
from common.auth.membership import MembershipError, load_active_membership
from common.db.models import Scripts
router = APIRouter(tags=["jupyter"])
security = HTTPBearer(auto_error=False)
@@ -25,19 +22,14 @@ def extract_notebook_path(
) -> Optional[str]:
"""Pull the relative notebook path out of the original request URI.
Match ``/jupyter/{workspace_id}/notebooks/*.ipynb`` (the legacy
iframe flow) and ``/jupyter/{workspace_id}/api/contents/*.ipynb``
(the Monaco-based Contents API PUT path). The caller decides
whether to actually enforce the lock — typically only on writes.
Only ``/jupyter/{workspace_id}/notebooks/*.ipynb`` requests are
subject to file-level lock checks; everything else (tree views,
``/api/contents`` and WebSocket upgrades) bypasses the lock.
"""
nb_pattern = rf"^/jupyter/{re.escape(workspace_id)}/notebooks/(.+\.ipynb)"
contents_pattern = (
rf"^/jupyter/{re.escape(workspace_id)}/api/contents/(.+\.ipynb)"
)
for pattern in (nb_pattern, contents_pattern):
match = re.match(pattern, uri)
if match:
return match.group(1)
pattern = rf"^/jupyter/{re.escape(workspace_id)}/notebooks/(.+\.ipynb)"
match = re.match(pattern, uri)
if match:
return match.group(1)
return None
@@ -131,16 +123,16 @@ async def verify_jupyter_access(
await load_active_membership_or_403(session, user_id, workspace_id)
notebook_path = extract_notebook_path(original_uri, workspace_id)
# Lock only on writes — reads (GET) stay open so anyone can still
# preview a locked notebook the way they could via the iframe before.
if notebook_path and request.method in {"PUT", "POST", "PATCH", "DELETE"}:
if await check_notebook_is_locked(
session, workspace_id, notebook_path, user_id,
):
raise HTTPException(
status_code=403,
detail=f"Notebook '{notebook_path}' is currently locked",
)
if notebook_path and await check_notebook_is_locked(
session,
workspace_id,
notebook_path,
user_id,
):
raise HTTPException(
status_code=403,
detail=f"Notebook '{notebook_path}' is currently locked",
)
runtime_client = request.app.state.runtime_client
ws_info = await runtime_client.get_workspace(workspace_id)