Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add permission queue workflow to update space permissions #8378

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
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
18 changes: 11 additions & 7 deletions front/lib/api/assistant/configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -496,13 +496,15 @@ export async function getAgentConfigurations<V extends "light" | "full">({
variant,
limit,
sort,
dangerouslySkipPermissionCheck,
}: {
auth: Authenticator;
agentsGetView: AgentsGetViewType;
agentPrefix?: string;
variant: V;
limit?: number;
sort?: SortStrategyType;
dangerouslySkipPermissionCheck?: boolean;
}): Promise<
V extends "light" ? LightAgentConfigurationType[] : AgentConfigurationType[]
> {
Expand Down Expand Up @@ -564,13 +566,15 @@ export async function getAgentConfigurations<V extends "light" | "full">({

// Filter out agents that the user does not have access to
// user should be in all groups that are in the agent's groupIds
const allowedAgentConfigurations = allAgentConfigurations
.flat()
.filter((a) =>
auth.canRead(
Authenticator.createResourcePermissionsFromGroupIds(a.groupIds)
)
);
const allowedAgentConfigurations = dangerouslySkipPermissionCheck
? allAgentConfigurations
: allAgentConfigurations
.flat()
.filter((a) =>
auth.canRead(
Authenticator.createResourcePermissionsFromGroupIds(a.groupIds)
)
);

return applySortAndLimit(allowedAgentConfigurations.flat());
}
Expand Down
84 changes: 84 additions & 0 deletions front/lib/api/assistant/permissions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import type {
DustAppRunConfigurationType,
ModelId,
PostOrPatchAgentConfigurationRequestBody,
} from "@dust-tt/types";
import { isDustAppRunConfiguration, removeNulls } from "@dust-tt/types";
import { uniq } from "lodash";
import { Op } from "sequelize";

import type { Authenticator } from "@app/lib/auth";
import { AgentConfiguration } from "@app/lib/models/assistant/agent";
import { AppResource } from "@app/lib/resources/app_resource";
import { DataSourceViewResource } from "@app/lib/resources/data_source_view_resource";
import type { GroupResource } from "@app/lib/resources/group_resource";

export async function listAgentConfigurationsForGroups(
auth: Authenticator,
groups: GroupResource[]
) {
return AgentConfiguration.findAll({
attributes: ["sId", "groupIds"],
where: {
workspaceId: auth.getNonNullableWorkspace().id,
status: "active",
groupIds: {
[Op.overlap]: groups.map((g) => g.id),
},
},
});
}

export function getDataSourceViewIdsFromActions(
actions: PostOrPatchAgentConfigurationRequestBody["assistant"]["actions"]
): string[] {
const relevantActions = actions.filter(
(action) =>
action.type === "retrieval_configuration" ||
action.type === "process_configuration" ||
action.type === "tables_query_configuration"
);

return removeNulls(
relevantActions.flatMap((action) => {
if (
action.type === "retrieval_configuration" ||
action.type === "process_configuration"
) {
return action.dataSources.map(
(dataSource) => dataSource.dataSourceViewId
);
} else if (action.type === "tables_query_configuration") {
return action.tables.map((table) => table.dataSourceViewId);
}
return [];
})
);
}

export async function getAgentConfigurationGroupIdsFromActions(
auth: Authenticator,
actions: PostOrPatchAgentConfigurationRequestBody["assistant"]["actions"]
): Promise<number[]> {
const dsViews = await DataSourceViewResource.fetchByIds(
auth,
getDataSourceViewIdsFromActions(actions)
);
const dustApps = await AppResource.fetchByIds(
auth,
actions
.filter((action) => isDustAppRunConfiguration(action))
.map((action) => (action as DustAppRunConfigurationType).appId)
);

// TODO(2024-10-25 flav) Refactor to store a list of ResourcePermission.
const dataSourceViewGroupIds: ModelId[] = dsViews.flatMap((view) =>
view.requestedPermissions().flatMap((rp) => rp.groups.map((g) => g.id))
);

const dustAppGroupIds: ModelId[] = dustApps.flatMap((app) =>
app.requestedPermissions().flatMap((rp) => rp.groups.map((g) => g.id))
);

return uniq([...dataSourceViewGroupIds, ...dustAppGroupIds].flat());
}
4 changes: 4 additions & 0 deletions front/lib/resources/group_resource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -720,6 +720,10 @@ export class GroupResource extends BaseResource<GroupModel> {
return this.kind === "global";
}

isRegular(): boolean {
return this.kind === "regular";
}

// JSON Serialization

toJSON(): GroupType {
Expand Down
28 changes: 22 additions & 6 deletions front/lib/resources/space_resource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import type { ModelStaticSoftDeletable } from "@app/lib/resources/storage/wrappe
import { getResourceIdFromSId, makeSId } from "@app/lib/resources/string_ids";
import type { ResourceFindOptions } from "@app/lib/resources/types";
import { UserResource } from "@app/lib/resources/user_resource";
import { launchUpdateSpacePermissionsWorkflow } from "@app/temporal/permissions_queue/client";

// Attributes are marked as read-only to reflect the stateless nature of our Resource.
// This design will be moved up to BaseResource once we transition away from Sequelize.
Expand Down Expand Up @@ -340,6 +341,7 @@ export class SpaceResource extends BaseResource<SpaceModel> {
const [defaultSpaceGroup] = regularGroups;

const wasRestricted = this.groups.every((g) => !g.isGlobal());
const hasRestrictionChanged = wasRestricted !== isRestricted;

const groupRes = await GroupResource.fetchWorkspaceGlobalGroup(auth);
if (groupRes.isErr()) {
Expand All @@ -356,24 +358,34 @@ export class SpaceResource extends BaseResource<SpaceModel> {
if (memberIds) {
const users = await UserResource.fetchByIds(memberIds);

return defaultSpaceGroup.setMembers(
const setMembersRes = await defaultSpaceGroup.setMembers(
auth,
users.map((u) => u.toJSON())
);
if (setMembersRes.isErr()) {
return setMembersRes;
}
}

return new Ok(undefined);
} else {
// If the space should not be restricted and was restricted before, add the global group.
if (wasRestricted) {
await this.addGroup(globalGroup);
}

// Remove all members.
await defaultSpaceGroup.setMembers(auth, []);
const setMembersRes = await defaultSpaceGroup.setMembers(auth, []);
if (setMembersRes.isErr()) {
return setMembersRes;
}
}

return new Ok(undefined);
// If the restriction has changed, start a workflow to update all associated resource
// permissions.
if (hasRestrictionChanged) {
await launchUpdateSpacePermissionsWorkflow(auth, this);
}

return new Ok(undefined);
}

private async addGroup(group: GroupResource) {
Expand Down Expand Up @@ -537,6 +549,10 @@ export class SpaceResource extends BaseResource<SpaceModel> {
return this.kind === "regular";
}

isRegularAndRestricted() {
return this.isRegular() && !this.groups.some((group) => group.isGlobal());
}

isPublic() {
return this.kind === "public";
}
Expand All @@ -550,7 +566,7 @@ export class SpaceResource extends BaseResource<SpaceModel> {
toJSON(): SpaceType {
return {
groupIds: this.groups.map((group) => group.sId),
isRestricted: !this.groups.some((group) => group.isGlobal()),
isRestricted: this.isRegularAndRestricted(),
kind: this.kind,
name: this.name,
sId: this.sId,
Expand Down
2 changes: 1 addition & 1 deletion front/migrations/20240906_2_backfill_agents_groupIds.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ import _ from "lodash";
import { Sequelize } from "sequelize";

import { getAgentConfiguration } from "@app/lib/api/assistant/configuration";
import { getDataSourceViewIdsFromActions } from "@app/lib/api/assistant/permissions";
import { Authenticator } from "@app/lib/auth";
import { AgentConfiguration } from "@app/lib/models/assistant/agent";
import { Workspace } from "@app/lib/models/workspace";
import { DataSourceViewResource } from "@app/lib/resources/data_source_view_resource";
import type { Logger } from "@app/logger/logger";
import { getDataSourceViewIdsFromActions } from "@app/pages/api/w/[wId]/assistant/agent_configurations";
import { makeScript } from "@app/scripts/helpers";

makeScript({}, async ({ execute }, logger) => {
Expand Down
62 changes: 2 additions & 60 deletions front/pages/api/w/[wId]/assistant/agent_configurations/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import type {
AgentActionConfigurationType,
AgentConfigurationType,
DustAppRunConfigurationType,
LightAgentConfigurationType,
ModelId,
PostOrPatchAgentConfigurationRequestBody,
Result,
WithAPIErrorResponse,
Expand All @@ -12,14 +10,12 @@ import {
assertNever,
Err,
GetAgentConfigurationsQuerySchema,
isDustAppRunConfiguration,
Ok,
PostOrPatchAgentConfigurationRequestBodySchema,
removeNulls,
} from "@dust-tt/types";
import { isLeft } from "fp-ts/lib/Either";
import * as reporter from "io-ts-reporters";
import _, { uniq } from "lodash";
import _ from "lodash";
import type { NextApiRequest, NextApiResponse } from "next";

import { getAgentsUsage } from "@app/lib/api/assistant/agent_usage";
Expand All @@ -29,12 +25,12 @@ import {
getAgentConfigurations,
unsafeHardDeleteAgentConfiguration,
} from "@app/lib/api/assistant/configuration";
import { getAgentConfigurationGroupIdsFromActions } from "@app/lib/api/assistant/permissions";
import { getAgentsRecentAuthors } from "@app/lib/api/assistant/recent_authors";
import { runOnRedis } from "@app/lib/api/redis";
import { withSessionAuthenticationForWorkspace } from "@app/lib/api/wrappers";
import type { Authenticator } from "@app/lib/auth";
import { AppResource } from "@app/lib/resources/app_resource";
import { DataSourceViewResource } from "@app/lib/resources/data_source_view_resource";
import { ServerSideTracking } from "@app/lib/tracking/server";
import { apiError } from "@app/logger/withlogging";

Expand Down Expand Up @@ -450,57 +446,3 @@ export async function createOrUpgradeAgentConfiguration({

return new Ok(agentConfiguration);
}

export function getDataSourceViewIdsFromActions(
actions: PostOrPatchAgentConfigurationRequestBody["assistant"]["actions"]
): string[] {
const relevantActions = actions.filter(
(action) =>
action.type === "retrieval_configuration" ||
action.type === "process_configuration" ||
action.type === "tables_query_configuration"
);

return removeNulls(
relevantActions.flatMap((action) => {
if (
action.type === "retrieval_configuration" ||
action.type === "process_configuration"
) {
return action.dataSources.map(
(dataSource) => dataSource.dataSourceViewId
);
} else if (action.type === "tables_query_configuration") {
return action.tables.map((table) => table.dataSourceViewId);
}
return [];
})
);
}

async function getAgentConfigurationGroupIdsFromActions(
auth: Authenticator,
actions: PostOrPatchAgentConfigurationRequestBody["assistant"]["actions"]
): Promise<number[]> {
const dsViews = await DataSourceViewResource.fetchByIds(
auth,
getDataSourceViewIdsFromActions(actions)
);
const dustApps = await AppResource.fetchByIds(
auth,
actions
.filter((action) => isDustAppRunConfiguration(action))
.map((action) => (action as DustAppRunConfigurationType).appId)
);

// TODO(2024-10-25 flav) Refactor to store a list of ResourcePermission.
const dataSourceViewGroupIds: ModelId[] = dsViews.flatMap((view) =>
view.requestedPermissions().flatMap((rp) => rp.groups.map((g) => g.id))
);

const dustAppGroupIds: ModelId[] = dustApps.flatMap((app) =>
app.requestedPermissions().flatMap((rp) => rp.groups.map((g) => g.id))
);

return uniq([...dataSourceViewGroupIds, ...dustAppGroupIds].flat());
}
3 changes: 3 additions & 0 deletions front/start_worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { runPostUpsertHooksWorker } from "@app/temporal/documents_post_process_h
import { runHardDeleteWorker } from "@app/temporal/hard_delete/worker";
import { runLabsWorker } from "@app/temporal/labs/worker";
import { runMentionsCountWorker } from "@app/temporal/mentions_count_queue/worker";
import { runPermissionsWorker } from "@app/temporal/permissions_queue/worker";
import { runProductionChecksWorker } from "@app/temporal/production_checks/worker";
import { runScrubWorkspaceQueueWorker } from "@app/temporal/scrub_workspace/worker";
import { runUpsertQueueWorker } from "@app/temporal/upsert_queue/worker";
Expand All @@ -20,6 +21,7 @@ type WorkerName =
| "hard_delete"
| "labs"
| "mentions_count"
| "permissions_queue"
| "poke"
| "post_upsert_hooks"
| "production_checks"
Expand All @@ -32,6 +34,7 @@ const workerFunctions: Record<WorkerName, () => Promise<void>> = {
hard_delete: runHardDeleteWorker,
labs: runLabsWorker,
mentions_count: runMentionsCountWorker,
permissions_queue: runPermissionsWorker,
poke: runPokeWorker,
post_upsert_hooks: runPostUpsertHooksWorker,
production_checks: runProductionChecksWorker,
Expand Down
Loading
Loading