From 311fddb4ef00dddd15b98f97edefe21671429706 Mon Sep 17 00:00:00 2001 From: Harry Date: Thu, 18 Dec 2025 17:04:40 +0800 Subject: [PATCH] feat: add subscription verification endpoint and enhance subscription handling - Introduced `SubscriptionVerifyRequest` model for verifying subscription credentials. - Implemented `TriggerSubscriptionVerifyApi` to handle verification requests for existing subscriptions. - Updated `TriggerSubscriptionBuilderVerifyApi` to reflect changes in functionality, now verifying and updating subscriptions. - Enhanced `TriggerProviderService` with a new method for verifying subscription credentials without updating them, improving validation processes. --- .../console/workspace/trigger_providers.py | 52 ++++++++++++++++- .../trigger/trigger_provider_service.py | 58 +++++++++++++++++-- 2 files changed, 102 insertions(+), 8 deletions(-) diff --git a/api/controllers/console/workspace/trigger_providers.py b/api/controllers/console/workspace/trigger_providers.py index 3f1f6149c7..4ba8e2115b 100644 --- a/api/controllers/console/workspace/trigger_providers.py +++ b/api/controllers/console/workspace/trigger_providers.py @@ -45,10 +45,17 @@ class TriggerSubscriptionUpdateRequest(BaseModel): class SubscriptionRebuildRequest(BaseModel): """Request payload for rebuilding an existing subscription.""" + name: str | None = Field(default=None, description="The name for the subscription") credentials: Mapping[str, Any] | None = Field(default=None, description="The credentials for the subscription") parameters: Mapping[str, Any] = Field(default_factory=dict, description="The parameters for the subscription") +class SubscriptionVerifyRequest(BaseModel): + """Request payload for verifying subscription credentials.""" + + credentials: Mapping[str, Any] = Field(description="The credentials to verify") + + console_ns.schema_model( TriggerSubscriptionUpdateRequest.__name__, TriggerSubscriptionUpdateRequest.model_json_schema(ref_template="#/definitions/{model}"), @@ -59,6 +66,11 @@ console_ns.schema_model( SubscriptionRebuildRequest.model_json_schema(ref_template="#/definitions/{model}"), ) +console_ns.schema_model( + SubscriptionVerifyRequest.__name__, + SubscriptionVerifyRequest.model_json_schema(ref_template="#/definitions/{model}"), +) + @console_ns.route("/workspaces/current/trigger-provider//icon") class TriggerProviderIconApi(Resource): @@ -183,16 +195,16 @@ parser_api = ( @console_ns.route( - "/workspaces/current/trigger-provider//subscriptions/builder/verify/", + "/workspaces/current/trigger-provider//subscriptions/builder/verify-and-update/", ) -class TriggerSubscriptionBuilderVerifyApi(Resource): +class TriggerSubscriptionBuilderVerifyAndUpdateApi(Resource): @console_ns.expect(parser_api) @setup_required @login_required @edit_permission_required @account_initialization_required def post(self, provider, subscription_builder_id): - """Verify a subscription instance for a trigger provider""" + """Verify and update a subscription instance for a trigger provider""" user = current_user assert user.current_tenant_id is not None @@ -421,6 +433,7 @@ class TriggerSubscriptionRebuildApi(Resource): try: TriggerProviderService.rebuild_trigger_subscription( tenant_id=user.current_tenant_id, + name=rebuild_request.name, provider_id=TriggerProviderID(provider), subscription_id=subscription_id, credentials=rebuild_request.credentials or {}, @@ -686,3 +699,36 @@ class TriggerOAuthClientManageApi(Resource): except Exception as e: logger.exception("Error removing OAuth client", exc_info=e) raise + + +@console_ns.route( + "/workspaces/current/trigger-provider//subscriptions/verify/", +) +class TriggerSubscriptionVerifyApi(Resource): + @console_ns.expect(console_ns.models[SubscriptionVerifyRequest.__name__]) + @setup_required + @login_required + @edit_permission_required + @account_initialization_required + def post(self, provider, subscription_id): + """Verify credentials for an existing subscription (edit mode only)""" + user = current_user + assert user.current_tenant_id is not None + + verify_request: SubscriptionVerifyRequest = SubscriptionVerifyRequest.model_validate(console_ns.payload) + + try: + result = TriggerProviderService.verify_subscription_credentials( + tenant_id=user.current_tenant_id, + user_id=user.id, + provider_id=TriggerProviderID(provider), + subscription_id=subscription_id, + credentials=verify_request.credentials, + ) + return result + except ValueError as e: + logger.warning("Credential verification failed", exc_info=e) + raise BadRequest(str(e)) from e + except Exception as e: + logger.exception("Error verifying subscription credentials", exc_info=e) + raise BadRequest(str(e)) from e diff --git a/api/services/trigger/trigger_provider_service.py b/api/services/trigger/trigger_provider_service.py index 6aa46b89fe..012f1828bc 100644 --- a/api/services/trigger/trigger_provider_service.py +++ b/api/services/trigger/trigger_provider_service.py @@ -360,7 +360,7 @@ class TriggerProviderService: credential_type: CredentialType = CredentialType.of(subscription.credential_type) is_auto_created: bool = credential_type in [CredentialType.OAUTH2, CredentialType.API_KEY] - if is_auto_created: + if not is_auto_created: return None provider_id = TriggerProviderID(subscription.provider_id) @@ -384,8 +384,8 @@ class TriggerProviderService: except Exception as e: logger.exception("Error unsubscribing trigger", exc_info=e) - # Clear cache session.delete(subscription) + # Clear cache delete_cache_for_subscription( tenant_id=tenant_id, provider_id=subscription.provider_id, @@ -793,6 +793,50 @@ class TriggerProviderService: subscription.properties = dict(properties_encrypter.decrypt(subscription.properties)) return subscription + @classmethod + def verify_subscription_credentials( + cls, + tenant_id: str, + user_id: str, + provider_id: TriggerProviderID, + subscription_id: str, + credentials: Mapping[str, Any], + ) -> dict[str, Any]: + """ + Verify credentials for an existing subscription without updating it. + + This is used in edit mode to validate new credentials before rebuild. + + :param tenant_id: Tenant ID + :param user_id: User ID + :param provider_id: Provider identifier + :param subscription_id: Subscription ID + :param credentials: New credentials to verify + :return: dict with 'verified' boolean + """ + provider_controller = TriggerManager.get_trigger_provider(tenant_id, provider_id) + if not provider_controller: + raise ValueError(f"Provider {provider_id} not found") + + subscription = cls.get_subscription_by_id( + tenant_id=tenant_id, + subscription_id=subscription_id, + ) + if not subscription: + raise ValueError(f"Subscription {subscription_id} not found") + + credential_type = CredentialType.of(subscription.credential_type) + + # For API Key, validate the new credentials + if credential_type == CredentialType.API_KEY: + try: + provider_controller.validate_credentials(user_id, credentials) + return {"verified": True} + except Exception as e: + raise ValueError(f"Invalid credentials: {e}") from e + + return {"verified": True} + @classmethod def rebuild_trigger_subscription( cls, @@ -801,6 +845,7 @@ class TriggerProviderService: subscription_id: str, credentials: Mapping[str, Any], parameters: Mapping[str, Any], + name: str | None = None, ) -> None: """ Create a subscription builder for rebuilding an existing subscription. @@ -809,6 +854,7 @@ class TriggerProviderService: keeping the same subscription_id and endpoint_id so the webhook URL remains unchanged. :param tenant_id: Tenant ID + :param name: Name for the subscription :param subscription_id: Subscription ID :param provider_id: Provider identifier :param credentials: Credentials for the subscription @@ -849,7 +895,7 @@ class TriggerProviderService: credentials=encrypter.decrypt(subscription.credentials), credential_type=credential_type, ) - + new_credentials = credentials or subscription.credentials # Create a new subscription with the same subscription_id and endpoint_id new_subscription: TriggerSubscriptionEntity = TriggerManager.subscribe_trigger( tenant_id=tenant_id, @@ -857,13 +903,15 @@ class TriggerProviderService: provider_id=provider_id, endpoint=generate_plugin_trigger_endpoint_url(subscription.endpoint_id), parameters=parameters, - credentials=credentials or subscription.credentials, + credentials=new_credentials, credential_type=credential_type, ) TriggerProviderService.update_trigger_subscription( tenant_id=tenant_id, subscription_id=subscription.id, + name=name, parameters=parameters, - credentials=credentials, + credentials=new_credentials, + properties=new_subscription.properties, expires_at=new_subscription.expires_at, )