76 lines
2.8 KiB
Python
76 lines
2.8 KiB
Python
"""
|
|
Session scoping for experimental task identifiers.
|
|
|
|
Task IDs generated by `run_task()` embed an opaque, per-session marker (the
|
|
"session scope") so that the default task handlers can tell which session
|
|
created a task. The default handlers for tasks/get, tasks/result, tasks/list,
|
|
and tasks/cancel only operate on tasks created by the requesting session.
|
|
|
|
Task IDs without a session scope (explicitly provided IDs, IDs created
|
|
directly through a TaskStore, or IDs created in stateless mode) have no known
|
|
creator. They can be used with tasks/get, tasks/result, and tasks/cancel from
|
|
any session - possession of the ID is what grants access - but they are never
|
|
included in tasks/list responses.
|
|
|
|
WARNING: These APIs are experimental and may change without notice.
|
|
"""
|
|
|
|
import re
|
|
from uuid import uuid4
|
|
|
|
__all__ = [
|
|
"new_session_scope",
|
|
"scoped_task_id",
|
|
"session_scope_of",
|
|
"task_in_session_scope",
|
|
"task_listable_in_session_scope",
|
|
]
|
|
|
|
# A scoped task ID has the form "<32 hex chars>:<uuid4>". Both halves must
|
|
# match exactly so that explicitly chosen task IDs are never mistaken for
|
|
# scoped ones. \Z rather than $ so a trailing newline cannot match.
|
|
_SCOPED_TASK_ID = re.compile(
|
|
r"\A(?P<scope>[0-9a-f]{32}):"
|
|
r"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\Z"
|
|
)
|
|
|
|
|
|
def new_session_scope() -> str:
|
|
"""Create a new opaque session scope token."""
|
|
return uuid4().hex
|
|
|
|
|
|
def scoped_task_id(session_scope: str) -> str:
|
|
"""Generate a task ID associated with the given session scope."""
|
|
return f"{session_scope}:{uuid4()}"
|
|
|
|
|
|
def session_scope_of(task_id: str) -> str | None:
|
|
"""Return the session scope embedded in a task ID, or None if it has none."""
|
|
match = _SCOPED_TASK_ID.match(task_id)
|
|
return match.group("scope") if match else None
|
|
|
|
|
|
def task_in_session_scope(task_id: str, session_scope: str | None) -> bool:
|
|
"""Whether a task may be used by a requestor with the given session scope.
|
|
|
|
Used by tasks/get, tasks/result, and tasks/cancel. A task whose ID carries
|
|
no session scope has no known creator, so possession of the ID is what
|
|
grants access to it: it can be used from any session.
|
|
"""
|
|
embedded = session_scope_of(task_id)
|
|
return embedded is None or embedded == session_scope
|
|
|
|
|
|
def task_listable_in_session_scope(task_id: str, session_scope: str | None) -> bool:
|
|
"""Whether a task may be included in a tasks/list response for the given session scope.
|
|
|
|
Used by tasks/list. Listing is stricter than access by ID: a task is only
|
|
listed to the session that created it. Tasks with no session scope are
|
|
never listed because they have no known creator, and requestors with no
|
|
session scope are never shown any tasks because the server cannot tell
|
|
them apart.
|
|
"""
|
|
embedded = session_scope_of(task_id)
|
|
return embedded is not None and embedded == session_scope
|