mirror of
https://github.com/langgenius/dify.git
synced 2026-09-05 00:31:19 +08:00
fix(knowledge-fs): propagate preview capability to crawl import
This commit is contained in:
parent
8be760ac12
commit
90dd7fa894
@ -1,6 +1,6 @@
|
||||
{
|
||||
"schemaVersion": 5,
|
||||
"subtreeTree": "84cd26e3b3345f1d1718097bceaec6e18f99dc92",
|
||||
"subtreeTree": "33e1204f278037d1dc25556691038acb0534329e",
|
||||
"openapiSha256": "eba6f0e32eb27fd68ac20021c46b5f647217005fdb85522e7e4de6f0a1afc9a8",
|
||||
"capabilityV2AuthManifestSha256": "e322a2fa779d1f40b95c54c1021cffecaec77abbd7b34899573dcdf4ff353109",
|
||||
"capabilityV2AuthTestVectorSha256": "ae0de37b1ff05c40f905cf17a7b410d8971acacf64db07d5ee3d6fecfa559ce3",
|
||||
|
||||
@ -0,0 +1,75 @@
|
||||
import { OpenAPIHono } from "@hono/zod-openapi";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
|
||||
import type { KnowledgeGatewayEnv } from "./gateway-openapi-contracts";
|
||||
import { registerNamespaceSourcePreviewHandlers } from "./namespace-source-preview-handlers";
|
||||
|
||||
const knowledgeSpaceId = "11111111-1111-4111-8111-111111111111";
|
||||
const sourceId = "22222222-2222-4222-8222-222222222222";
|
||||
const previewJobId = "33333333-3333-4333-8333-333333333333";
|
||||
const workflowId = "44444444-4444-4444-8444-444444444444";
|
||||
|
||||
describe("namespace website source preview handlers", () => {
|
||||
it("forwards the admitted capability principal to the import workflow", async () => {
|
||||
const consume = vi.fn(async () => workflowId);
|
||||
const app = new OpenAPIHono<KnowledgeGatewayEnv>();
|
||||
app.use("*", async (context, next) => {
|
||||
context.set("subject", {
|
||||
scopes: [],
|
||||
subjectId: "account-1",
|
||||
tenantId: "tenant-1",
|
||||
});
|
||||
context.set("callerKind", "interactive");
|
||||
context.set("capabilityV2Grant", {
|
||||
contentScopeIds: ["tenant:tenant-1", `source:${sourceId}`],
|
||||
grantId: "capability-grant-1",
|
||||
} as never);
|
||||
await next();
|
||||
});
|
||||
registerNamespaceSourcePreviewHandlers({
|
||||
app,
|
||||
service: { consume } as never,
|
||||
});
|
||||
|
||||
const response = await app.request(
|
||||
`/knowledge-spaces/${knowledgeSpaceId}/sources/${sourceId}/namespace-preview-import`,
|
||||
{
|
||||
body: JSON.stringify({
|
||||
configurationFingerprint: "f".repeat(64),
|
||||
pageIds: ["page-1"],
|
||||
previewJobId,
|
||||
}),
|
||||
headers: {
|
||||
"content-type": "application/json",
|
||||
"idempotency-key": "preview-import-1",
|
||||
},
|
||||
method: "POST",
|
||||
},
|
||||
);
|
||||
|
||||
expect(response.status).toBe(202);
|
||||
await expect(response.json()).resolves.toEqual({ workflowId });
|
||||
expect(consume).toHaveBeenCalledWith(
|
||||
{
|
||||
callerKind: "interactive",
|
||||
capability: {
|
||||
contentScopeIds: ["tenant:tenant-1", `source:${sourceId}`],
|
||||
grantId: "capability-grant-1",
|
||||
},
|
||||
subject: {
|
||||
scopes: [],
|
||||
subjectId: "account-1",
|
||||
tenantId: "tenant-1",
|
||||
},
|
||||
},
|
||||
{
|
||||
configurationFingerprint: "f".repeat(64),
|
||||
idempotencyKey: "preview-import-1",
|
||||
jobId: previewJobId,
|
||||
knowledgeSpaceId,
|
||||
pageIds: ["page-1"],
|
||||
sourceId,
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
@ -96,7 +96,7 @@ export function registerNamespaceSourcePreviewHandlers(input: {
|
||||
const headers = context.req.valid("header");
|
||||
return context.json(
|
||||
{
|
||||
workflowId: await input.service.consume(context.get("subject"), {
|
||||
workflowId: await input.service.consume(workflowPrincipal(context), {
|
||||
jobId: body.previewJobId,
|
||||
pageIds: body.pageIds,
|
||||
configurationFingerprint: body.configurationFingerprint,
|
||||
@ -112,6 +112,25 @@ export function registerNamespaceSourcePreviewHandlers(input: {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function workflowPrincipal(context: Pick<LooseOpenApiContext, "get">) {
|
||||
const apiKey = context.get("authenticatedApiKey");
|
||||
const capabilityGrant = context.get("capabilityV2Grant");
|
||||
return {
|
||||
...(apiKey ? { apiKey } : {}),
|
||||
...(capabilityGrant
|
||||
? {
|
||||
capability: {
|
||||
contentScopeIds: capabilityGrant.contentScopeIds,
|
||||
grantId: capabilityGrant.grantId,
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
callerKind: context.get("callerKind") ?? "interactive",
|
||||
subject: context.get("subject"),
|
||||
} as const;
|
||||
}
|
||||
|
||||
function failure(context: LooseOpenApiContext, error: unknown) {
|
||||
if (error instanceof NamespaceSourcePreviewError) {
|
||||
const status = error.code.includes("NOT_FOUND")
|
||||
|
||||
@ -64,18 +64,34 @@ describe("namespace website source preview", () => {
|
||||
expect(page).toMatchObject({ sourceUrl: "https://example.com/a" });
|
||||
if (!page) throw new Error("Expected a preview page");
|
||||
|
||||
const workflowId = await service.consume(subject, {
|
||||
jobId: job.id,
|
||||
pageIds: [page.pageId],
|
||||
configurationFingerprint: "f".repeat(64),
|
||||
knowledgeSpaceId: "11111111-1111-4111-8111-111111111111",
|
||||
sourceId: "33333333-3333-4333-8333-333333333333",
|
||||
idempotencyKey: "request:crawl-import",
|
||||
});
|
||||
const workflowId = await service.consume(
|
||||
{
|
||||
callerKind: "interactive",
|
||||
capability: {
|
||||
contentScopeIds: ["tenant:tenant", "source:preview"],
|
||||
grantId: "preview-grant",
|
||||
},
|
||||
subject,
|
||||
},
|
||||
{
|
||||
jobId: job.id,
|
||||
pageIds: [page.pageId],
|
||||
configurationFingerprint: "f".repeat(64),
|
||||
knowledgeSpaceId: "11111111-1111-4111-8111-111111111111",
|
||||
sourceId: "33333333-3333-4333-8333-333333333333",
|
||||
idempotencyKey: "request:crawl-import",
|
||||
},
|
||||
);
|
||||
|
||||
expect(workflowId).toBe("22222222-2222-4222-8222-222222222222");
|
||||
expect(createCrawlImport).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
callerKind: "interactive",
|
||||
capability: {
|
||||
contentScopeIds: ["tenant:tenant", "source:preview"],
|
||||
grantId: "preview-grant",
|
||||
},
|
||||
subject,
|
||||
sourceUrls: ["https://example.com/a"],
|
||||
pageReferences: [
|
||||
expect.objectContaining({
|
||||
|
||||
@ -14,7 +14,10 @@ import {
|
||||
jsonInsertPlaceholder,
|
||||
quoteDatabaseIdentifier,
|
||||
} from "./database-sql-utils";
|
||||
import type { SourceProductWorkflowService } from "./source-product-workflow";
|
||||
import type {
|
||||
SourceProductWorkflowService,
|
||||
SourceWorkflowPrincipal,
|
||||
} from "./source-product-workflow";
|
||||
import type { SourceRepository } from "./source-repository";
|
||||
import type { WebsiteCrawlConnector } from "./website-crawl-connector";
|
||||
|
||||
@ -558,7 +561,7 @@ export function createNamespaceSourcePreviewService(input: {
|
||||
return Boolean(job || cleanupJob);
|
||||
},
|
||||
consume: async (
|
||||
subject: AuthSubject,
|
||||
principal: SourceWorkflowPrincipal,
|
||||
request: {
|
||||
jobId: string;
|
||||
pageIds: readonly string[];
|
||||
@ -568,6 +571,7 @@ export function createNamespaceSourcePreviewService(input: {
|
||||
idempotencyKey: string;
|
||||
},
|
||||
) => {
|
||||
const { subject } = principal;
|
||||
const job = await requireJob(subject, request.jobId);
|
||||
if (job.importWorkflowId) return job.importWorkflowId;
|
||||
if (job.expiresAt <= now().toISOString())
|
||||
@ -607,8 +611,7 @@ export function createNamespaceSourcePreviewService(input: {
|
||||
selected.push(page);
|
||||
}
|
||||
const workflow = await input.workflows.createCrawlImport({
|
||||
subject,
|
||||
callerKind: "interactive",
|
||||
...principal,
|
||||
knowledgeSpaceId: request.knowledgeSpaceId,
|
||||
sourceId: request.sourceId,
|
||||
idempotencyKey: request.idempotencyKey,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user