Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 85 additions & 0 deletions web-admin/src/features/edit-session/EditSessionViewAs.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
<script lang="ts">
import { useExplore } from "@rilldata/web-common/features/explores/selectors.ts";
import {
useDashboardPolicyCheck,
useRillYamlPolicyCheck,
} from "@rilldata/web-common/features/dashboards/granular-access-policies/useSecurityPolicyCheck.ts";
import { useRuntimeClient } from "@rilldata/web-common/runtime-client/v2";
import { useCanvas } from "@rilldata/web-common/features/canvas/selector.ts";
import ViewAsButton from "@rilldata/web-common/features/dashboards/granular-access-policies/ViewAsButton.svelte";
import { page } from "$app/state";
import { createUpdateEditSessionDevJWT } from "@rilldata/web-admin/features/edit-session/updateEditSessionDevJWT.ts";

let {
deploymentId,
editSessionJwt,
}: {
deploymentId: string;
editSessionJwt: string;
} = $props();

const runtimeClient = useRuntimeClient();

let onExplorePreview = $derived(
!!page.route.id?.startsWith(
"/[organization]/[project]/-/edit/(viz)/explore",
),
);
let onCanvasPreview = $derived(
!!page.route.id?.startsWith(
"/[organization]/[project]/-/edit/(viz)/canvas",
),
);
let dashboardName = $derived(page.params.name ?? "");

// If explore, check explore yaml or its metrics view has security rules
let exploreQuery = $derived(
useExplore(runtimeClient, dashboardName, { enabled: onExplorePreview }),
);
let exploreFilePath = $derived(
$exploreQuery.data?.explore?.meta?.filePaths?.[0] ?? "",
);
let metricsViewFilePath = $derived(
$exploreQuery.data?.metricsView?.meta?.filePaths?.[0] ?? "",
);
let explorePolicyCheck = $derived(
useDashboardPolicyCheck(runtimeClient, exploreFilePath),
);
let metricsPolicyCheck = $derived(
useDashboardPolicyCheck(runtimeClient, metricsViewFilePath),
);

// Else if canvas, check canvas yaml or its metrics views have security rules
let canvasQuery = $derived(
useCanvas(runtimeClient, dashboardName, { enabled: onCanvasPreview }),
);
let canvasFilePath = $derived($canvasQuery.data?.filePath ?? "");
let canvasPolicyCheck = $derived(
useDashboardPolicyCheck(runtimeClient, canvasFilePath),
);
// Check if any metrics view referenced by this canvas has security rules
let referencedMetricsViewsHavePolicy = $derived(
Object.values($canvasQuery.data?.metricsViews ?? {}).some(
(mv) => (mv?.state?.validSpec?.securityRules?.length ?? 0) > 0,
),
);

// Check rill yaml for direct policy
let rillYamlPolicyCheck = $derived(useRillYamlPolicyCheck(runtimeClient));

let hasSecurityPolicy = $derived(
$explorePolicyCheck.data ||
$metricsPolicyCheck.data ||
$canvasPolicyCheck.data ||
$rillYamlPolicyCheck.data ||
referencedMetricsViewsHavePolicy,
);

let devJTWUpdater = $derived(
createUpdateEditSessionDevJWT(deploymentId, editSessionJwt),
);
</script>

{#if hasSecurityPolicy}
<ViewAsButton {devJTWUpdater} />
{/if}
56 changes: 56 additions & 0 deletions web-admin/src/features/edit-session/updateEditSessionDevJWT.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import {
selectedMockUserJWT,
selectedMockUserStore,
} from "@rilldata/web-common/features/dashboards/granular-access-policies/stores";
import type { MockUser } from "@rilldata/web-common/features/dashboards/granular-access-policies/useMockUsers";
import {
invalidateAllMetricsViews,
invalidateCanvasQueries,
} from "@rilldata/web-common/runtime-client/invalidation";
import type { RuntimeClient } from "@rilldata/web-common/runtime-client/v2";
import type { QueryClient } from "@tanstack/svelte-query";
import { adminServiceGetDeployment } from "@rilldata/web-admin/client";
import { getDomain } from "@rilldata/web-admin/features/projects/user-management/selectors.ts";

export function createUpdateEditSessionDevJWT(
deploymentId: string,
editSessionJwt: string,
) {
return async (
queryClient: QueryClient,
client: RuntimeClient,
mockUser: MockUser | null,
) => {
selectedMockUserStore.set(mockUser);

if (mockUser === null) {
selectedMockUserJWT.set(null);
client.updateJwt(editSessionJwt, "user");
} else {
try {
const { name, email, groups, admin, ...customAttributes } = mockUser;

const { accessToken } = await adminServiceGetDeployment(deploymentId, {
attributes: {
email,
...(email ? { domain: getDomain(email) } : {}),
name: name || "Mock User",
admin: !!admin,
groups: groups || [],
...customAttributes,
},
});

if (!accessToken) throw new Error("No JWT returned");

selectedMockUserJWT.set(accessToken);
client.updateJwt(accessToken, "mock");
} catch {
// no-op
}
}

await invalidateAllMetricsViews(queryClient, client.instanceId);
return invalidateCanvasQueries(queryClient, client.instanceId);
};
}
9 changes: 9 additions & 0 deletions web-admin/src/features/projects/header/ProjectHeader.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
import { useReports } from "../../scheduled-reports/selectors";
import SharePersonalFile from "web-admin/src/features/personal-files/SharePersonalFile.svelte";
import VisualizationsBreadcrumbDropdown from "./VisualizationsBreadcrumbDropdown.svelte";
import EditSessionViewAs from "@rilldata/web-admin/features/edit-session/EditSessionViewAs.svelte";

export let organization: string;
export let project: string;
Expand All @@ -65,6 +66,8 @@
export let planDisplayName: string | undefined;
export let organizationLogoUrl: string | undefined;
export let editContext: boolean = false;
export let editSessionDeploymentId: string | undefined = undefined;
export let editSessionJwt: string | undefined = undefined;

const user = createAdminServiceGetCurrentUser();
const runtimeClient = useRuntimeClient();
Expand Down Expand Up @@ -216,6 +219,12 @@

<div class="flex gap-x-2 items-center ml-auto">
{#if editContext}
{#if editSessionDeploymentId && editSessionJwt}
<EditSessionViewAs
deploymentId={editSessionDeploymentId}
{editSessionJwt}
/>
{/if}
{#if $developerChat && !onEditDashboardPreview}
<ChatToggle open={developerChatOpen} actions={developerChatActions} />
{/if}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export function emailIsPublic(email: string) {
return RillPublicEmailDomains.includes(domain);
}

function getDomain(email: string) {
export function getDomain(email: string) {
const domainParts = email.split("@");
return domainParts.length ? domainParts[domainParts.length - 1] : "";
}
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,8 @@
{planDisplayName}
{organizationLogoUrl}
editContext={true}
editSessionDeploymentId={deployment.id}
editSessionJwt={jwt}
/>
<EditSessionTimeoutBanner
usedOn={deployment.usedOn}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,18 @@
import { getFileHref } from "@rilldata/web-common/layout/navigation/editor-routing";
import { escapeHtml } from "@rilldata/web-common/lib/i18n";

let viewAsMenuOpen = false;
let open = false;
let {
devJTWUpdater = updateDevJWT,
}: {
devJTWUpdater?: typeof updateDevJWT;
} = $props();

let viewAsMenuOpen = $state(false);
let open = $state(false);

const client = useRuntimeClient();

$: mockUsers = useMockUsers(client);
let mockUsers = $derived(useMockUsers(client));
</script>

<DropdownMenu.Root bind:open>
Expand All @@ -45,7 +51,7 @@
active={viewAsMenuOpen}
removeTooltipText={m.dashboard_clear_view()}
onRemove={() => {
updateDevJWT(queryClient, client, null);
devJTWUpdater(queryClient, client, null);
}}
>
<div slot="body">
Expand All @@ -68,7 +74,7 @@
{#each $mockUsers.data as user (user?.email)}
<DropdownMenu.Item
onclick={() => {
updateDevJWT(queryClient, client, user);
devJTWUpdater(queryClient, client, user);
}}
class="flex gap-x-2 items-center"
>
Expand Down
Loading