mirror of
https://github.com/hanzoai/insights.git
synced 2026-10-30 16:15:31 +00:00
The tree was a debranded snapshot of upstream taken around 2026-02-20, and upstream has moved about five months since. This replaces it with upstream master, mechanically re-derived: bin/debrand does the rename, and the pipeline and squashed migrations are restored from the shipped tree. Not a merge. The previous import replaced the tree wholesale in a single commit, so there is no shared content history to merge against. Reproducing the rename on the new tree is what actually converges. What survives: .hanzo/workflows, the Dockerfiles and bin/docker* entrypoints, compose files, LLM.md, and insights/migrations. The squashed initial migration is load-bearing — the live database records it by name, so upstream's ~918 individual migrations cannot come back. Nine products are dropped as unreferenced. Twenty-one more that we do not run are kept anyway because the core app still imports them; unpicking those is a refactor and does not belong in a rebase. This base does not build yet, and the reasons are not in the rename: upstream now wants Python 3.13.13 against our 3.12.12 and Django 5.2 against our 4.2, and the dependency manifests still name upstream packages that we republish under our own names. Those are decisions, not renames.
79 lines
2.9 KiB
Python
79 lines
2.9 KiB
Python
from __future__ import annotations
|
|
|
|
from typing import cast
|
|
|
|
from rest_framework import exceptions
|
|
from rest_framework.request import Request
|
|
|
|
from insights.auth import IDJagAccessTokenAuthentication, OAuthAccessTokenAuthentication, PersonalAPIKeyAuthentication
|
|
from insights.rbac.user_access_control import AccessControlLevel, UserAccessControl
|
|
from insights.scopes import APIScopeObject
|
|
|
|
from products.dashboards.backend.models.dashboard_widget import DashboardWidget
|
|
from products.dashboards.backend.widget_catalog import get_widget_product_access_denied_message
|
|
from products.dashboards.backend.widget_registry import WidgetRegistryEntry, get_widget_registry_entry
|
|
|
|
|
|
def _get_request_api_key_scopes(request: Request) -> list[str] | None:
|
|
authenticator = request.successful_authenticator
|
|
if isinstance(authenticator, PersonalAPIKeyAuthentication):
|
|
return list(authenticator.personal_api_key.scopes or [])
|
|
if isinstance(authenticator, OAuthAccessTokenAuthentication):
|
|
token_scope_string = authenticator.access_token.scope
|
|
return token_scope_string.split() if token_scope_string else []
|
|
if isinstance(authenticator, IDJagAccessTokenAuthentication):
|
|
return list(authenticator.scopes)
|
|
return None
|
|
|
|
|
|
def get_widget_api_scope_error(
|
|
registry_entry: WidgetRegistryEntry,
|
|
request: Request,
|
|
) -> str | None:
|
|
key_scopes = _get_request_api_key_scopes(request)
|
|
if key_scopes is None:
|
|
return None
|
|
|
|
if "*" in key_scopes:
|
|
return None
|
|
|
|
required_scopes = registry_entry.get("required_scopes") or []
|
|
for required_scope in required_scopes:
|
|
valid_scopes = [required_scope]
|
|
if required_scope.endswith(":read"):
|
|
valid_scopes.append(required_scope.replace(":read", ":write"))
|
|
if not any(scope in key_scopes for scope in valid_scopes):
|
|
return f"API key missing required scope '{required_scope}'"
|
|
return None
|
|
|
|
|
|
def get_widget_product_access_error(
|
|
registry_entry: WidgetRegistryEntry,
|
|
user_access_control: UserAccessControl,
|
|
*,
|
|
required_level: AccessControlLevel = "viewer",
|
|
) -> str | None:
|
|
required_product_access = registry_entry.get("required_product_access")
|
|
if not required_product_access:
|
|
return None
|
|
|
|
if not user_access_control.check_access_level_for_resource(
|
|
cast(APIScopeObject, required_product_access),
|
|
required_level,
|
|
):
|
|
return get_widget_product_access_denied_message(required_product_access)
|
|
return None
|
|
|
|
|
|
def check_widget_tile_product_access(
|
|
widget: DashboardWidget,
|
|
user_access_control: UserAccessControl,
|
|
) -> None:
|
|
registry_entry = get_widget_registry_entry(widget.widget_type)
|
|
if registry_entry is None:
|
|
raise exceptions.PermissionDenied(f"Unknown widget type: {widget.widget_type}")
|
|
|
|
access_error = get_widget_product_access_error(registry_entry, user_access_control)
|
|
if access_error:
|
|
raise exceptions.PermissionDenied(access_error)
|