mirror of
https://github.com/langgenius/dify.git
synced 2026-08-29 03:45:08 +08:00
fix: allow OAuth trigger builder verification without credentials (#39199)
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
@@ -54,6 +54,10 @@ class TriggerSubscriptionBuilderCreatePayload(BaseModel):
|
||||
|
||||
|
||||
class TriggerSubscriptionBuilderVerifyPayload(BaseModel):
|
||||
credentials: dict[str, Any] | None = None
|
||||
|
||||
|
||||
class TriggerSubscriptionVerifyPayload(BaseModel):
|
||||
credentials: dict[str, Any]
|
||||
|
||||
|
||||
@@ -120,6 +124,7 @@ register_schema_models(
|
||||
TriggerSubscriptionBuilderCreatePayload,
|
||||
TriggerSubscriptionBuilderVerifyPayload,
|
||||
TriggerSubscriptionBuilderUpdatePayload,
|
||||
TriggerSubscriptionVerifyPayload,
|
||||
TriggerOAuthClientPayload,
|
||||
)
|
||||
register_response_schema_models(
|
||||
@@ -812,7 +817,7 @@ class TriggerOAuthClientManageApi(Resource):
|
||||
"/workspaces/current/trigger-provider/<path:provider>/subscriptions/verify/<path:subscription_id>",
|
||||
)
|
||||
class TriggerSubscriptionVerifyApi(Resource):
|
||||
@console_ns.expect(console_ns.models[TriggerSubscriptionBuilderVerifyPayload.__name__])
|
||||
@console_ns.expect(console_ns.models[TriggerSubscriptionVerifyPayload.__name__])
|
||||
@console_ns.response(
|
||||
200,
|
||||
"Trigger subscription verified successfully",
|
||||
@@ -825,10 +830,10 @@ class TriggerSubscriptionVerifyApi(Resource):
|
||||
@account_initialization_required
|
||||
@with_current_user
|
||||
@with_current_tenant_id
|
||||
@model_validate(TriggerSubscriptionBuilderVerifyPayload)
|
||||
@model_validate(TriggerSubscriptionVerifyPayload)
|
||||
def post(
|
||||
self,
|
||||
req_data: TriggerSubscriptionBuilderVerifyPayload,
|
||||
req_data: TriggerSubscriptionVerifyPayload,
|
||||
tenant_id: str,
|
||||
user: Account,
|
||||
provider: str,
|
||||
|
||||
@@ -12868,7 +12868,7 @@ Import a Skill zip package from multipart form field `file`.
|
||||
|
||||
| Required | Schema |
|
||||
| -------- | ------ |
|
||||
| Yes | **application/json**: [TriggerSubscriptionBuilderVerifyPayload](#triggersubscriptionbuilderverifypayload)<br> |
|
||||
| Yes | **application/json**: [TriggerSubscriptionVerifyPayload](#triggersubscriptionverifypayload)<br> |
|
||||
|
||||
#### Responses
|
||||
|
||||
@@ -23453,6 +23453,12 @@ The identity of the trigger provider
|
||||
|
||||
#### TriggerSubscriptionBuilderVerifyPayload
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
| ---- | ---- | ----------- | -------- |
|
||||
| credentials | object | | No |
|
||||
|
||||
#### TriggerSubscriptionVerifyPayload
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
| ---- | ---- | ----------- | -------- |
|
||||
| credentials | object | | Yes |
|
||||
|
||||
@@ -7,10 +7,15 @@ from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
from flask import Flask
|
||||
from pydantic import ValidationError
|
||||
from sqlalchemy.engine import Engine
|
||||
from werkzeug.exceptions import BadRequest
|
||||
|
||||
from controllers.console.workspace.trigger_providers import TriggerSubscriptionDeleteApi
|
||||
from controllers.console.workspace.trigger_providers import (
|
||||
TriggerSubscriptionBuilderVerifyPayload,
|
||||
TriggerSubscriptionDeleteApi,
|
||||
TriggerSubscriptionVerifyPayload,
|
||||
)
|
||||
from models.engine import db
|
||||
|
||||
trigger_provider_module = import_module("controllers.console.workspace.trigger_providers")
|
||||
@@ -59,3 +64,14 @@ def test_delete_subscription_translates_value_error(flask_sqlite_engine: Engine)
|
||||
pytest.raises(BadRequest, match="bad"),
|
||||
):
|
||||
method(api, "t1", "sub1")
|
||||
|
||||
|
||||
def test_builder_verify_payload_allows_credentials_to_be_omitted() -> None:
|
||||
payload = TriggerSubscriptionBuilderVerifyPayload.model_validate({})
|
||||
|
||||
assert payload.credentials is None
|
||||
|
||||
|
||||
def test_subscription_verify_payload_requires_credentials() -> None:
|
||||
with pytest.raises(ValidationError):
|
||||
TriggerSubscriptionVerifyPayload.model_validate({})
|
||||
|
||||
@@ -1191,9 +1191,9 @@ export type SubscriptionBuilderApiEntity = {
|
||||
}
|
||||
|
||||
export type TriggerSubscriptionBuilderVerifyPayload = {
|
||||
credentials: {
|
||||
credentials?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
} | null
|
||||
}
|
||||
|
||||
export type TriggerVerificationResponse = {
|
||||
@@ -1212,6 +1212,12 @@ export type TriggerOAuthAuthorizeResponse = {
|
||||
subscription_builder_id: string
|
||||
}
|
||||
|
||||
export type TriggerSubscriptionVerifyPayload = {
|
||||
credentials: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
}
|
||||
|
||||
export type TriggerProviderListResponse = Array<TriggerProviderApiEntity>
|
||||
|
||||
export type WorkspaceCustomConfigResponse = {
|
||||
@@ -6314,7 +6320,7 @@ export type GetWorkspacesCurrentTriggerProviderByProviderSubscriptionsOauthAutho
|
||||
|
||||
export type PostWorkspacesCurrentTriggerProviderByProviderSubscriptionsVerifyBySubscriptionIdData =
|
||||
{
|
||||
body: TriggerSubscriptionBuilderVerifyPayload
|
||||
body: TriggerSubscriptionVerifyPayload
|
||||
path: {
|
||||
provider: string
|
||||
subscription_id: string
|
||||
|
||||
@@ -727,7 +727,7 @@ export const zTriggerSubscriptionBuilderCreatePayload = z.object({
|
||||
* TriggerSubscriptionBuilderVerifyPayload
|
||||
*/
|
||||
export const zTriggerSubscriptionBuilderVerifyPayload = z.object({
|
||||
credentials: z.record(z.string(), z.unknown()),
|
||||
credentials: z.record(z.string(), z.unknown()).nullish(),
|
||||
})
|
||||
|
||||
/**
|
||||
@@ -744,6 +744,13 @@ export const zTriggerProviderErrorResponse = z.object({
|
||||
error: z.string(),
|
||||
})
|
||||
|
||||
/**
|
||||
* TriggerSubscriptionVerifyPayload
|
||||
*/
|
||||
export const zTriggerSubscriptionVerifyPayload = z.object({
|
||||
credentials: z.record(z.string(), z.unknown()),
|
||||
})
|
||||
|
||||
/**
|
||||
* WorkspaceCustomConfigResponse
|
||||
*/
|
||||
@@ -6031,7 +6038,7 @@ export const zGetWorkspacesCurrentTriggerProviderByProviderSubscriptionsOauthAut
|
||||
zTriggerOAuthAuthorizeResponse
|
||||
|
||||
export const zPostWorkspacesCurrentTriggerProviderByProviderSubscriptionsVerifyBySubscriptionIdBody =
|
||||
zTriggerSubscriptionBuilderVerifyPayload
|
||||
zTriggerSubscriptionVerifyPayload
|
||||
|
||||
export const zPostWorkspacesCurrentTriggerProviderByProviderSubscriptionsVerifyBySubscriptionIdPath =
|
||||
z.object({
|
||||
|
||||
@@ -1,6 +1,73 @@
|
||||
import type { TriggerProviderApiEntity as GeneratedTriggerProvider } from '@dify/contracts/api/console/workspaces/types.gen'
|
||||
import type { ReactNode } from 'react'
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
||||
import { act, renderHook } from '@testing-library/react'
|
||||
import { createElement } from 'react'
|
||||
import { describe, expect, it } from 'vite-plus/test'
|
||||
import { convertToTriggerWithProvider, normalizeTriggerProvider } from '../use-triggers'
|
||||
import {
|
||||
convertToTriggerWithProvider,
|
||||
normalizeTriggerProvider,
|
||||
useVerifyAndUpdateTriggerSubscriptionBuilder,
|
||||
} from '../use-triggers'
|
||||
|
||||
const mocks = vi.hoisted(() => ({
|
||||
mutationKey: vi.fn(() => ['trigger-builder-verify']),
|
||||
verifyAndUpdate: vi.fn().mockResolvedValue({ verified: false }),
|
||||
}))
|
||||
|
||||
vi.mock('@/service/client', () => ({
|
||||
consoleClient: {
|
||||
workspaces: {
|
||||
current: {
|
||||
triggerProvider: {
|
||||
byProvider: {
|
||||
subscriptions: {
|
||||
builder: {
|
||||
verifyAndUpdate: {
|
||||
bySubscriptionBuilderId: {
|
||||
post: mocks.verifyAndUpdate,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
consoleQuery: {
|
||||
workspaces: {
|
||||
current: {
|
||||
triggerProvider: {
|
||||
byProvider: {
|
||||
subscriptions: {
|
||||
builder: {
|
||||
verifyAndUpdate: {
|
||||
bySubscriptionBuilderId: {
|
||||
post: {
|
||||
mutationKey: mocks.mutationKey,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}))
|
||||
|
||||
const createWrapper = () => {
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
mutations: { retry: false },
|
||||
},
|
||||
})
|
||||
|
||||
return ({ children }: { children: ReactNode }) =>
|
||||
createElement(QueryClientProvider, { client: queryClient }, children)
|
||||
}
|
||||
|
||||
const createGeneratedTriggerProvider = (): GeneratedTriggerProvider => ({
|
||||
author: 'Dify',
|
||||
@@ -42,3 +109,31 @@ describe('trigger provider normalization', () => {
|
||||
expect(triggerWithProvider.events[0]?.parameters[0]?.default).toBe(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('trigger subscription builder verification', () => {
|
||||
it('should omit credentials while polling OAuth authorization', async () => {
|
||||
const { result } = renderHook(() => useVerifyAndUpdateTriggerSubscriptionBuilder(), {
|
||||
wrapper: createWrapper(),
|
||||
})
|
||||
|
||||
await act(async () => {
|
||||
await result.current.mutateAsync({
|
||||
provider: 'langgenius/gmail_trigger/gmail_trigger',
|
||||
subscriptionBuilderId: 'builder-1',
|
||||
})
|
||||
})
|
||||
|
||||
expect(mocks.verifyAndUpdate).toHaveBeenCalledWith(
|
||||
{
|
||||
params: {
|
||||
provider: 'langgenius/gmail_trigger/gmail_trigger',
|
||||
subscription_builder_id: 'builder-1',
|
||||
},
|
||||
body: {},
|
||||
},
|
||||
{
|
||||
context: { silent: true },
|
||||
},
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -657,7 +657,7 @@ export const useVerifyAndUpdateTriggerSubscriptionBuilder = () => {
|
||||
return consoleClient.workspaces.current.triggerProvider.byProvider.subscriptions.builder.verifyAndUpdate.bySubscriptionBuilderId.post(
|
||||
{
|
||||
params: { provider, subscription_builder_id: subscriptionBuilderId },
|
||||
body: { credentials: credentials ?? {} },
|
||||
body: credentials === undefined ? {} : { credentials },
|
||||
},
|
||||
{
|
||||
context: { silent: true },
|
||||
|
||||
Reference in New Issue
Block a user