From d437444b37a73b4106eefb762cbebc3ec490f019 Mon Sep 17 00:00:00 2001 From: markfarkas-camunda Date: Wed, 11 Jan 2023 18:07:01 +0100 Subject: [PATCH 01/18] feat: GraphQL connector template --- .../resources/archetype-resources/pom.xml | 2 +- connectors/graphql/LICENSE.txt | 5 + connectors/graphql/README.md | 38 ++ .../element-templates/template-connector.json | 383 ++++++++++++++++++ connectors/graphql/pom.xml | 31 ++ .../connector/graphql/Authentication.java | 63 +++ .../connector/graphql/GraphQLFunction.java | 62 +++ .../connector/graphql/GraphQLRequest.java | 68 ++++ .../connector/graphql/GraphQLResult.java | 55 +++ connectors/pom.xml | 1 + 10 files changed, 707 insertions(+), 1 deletion(-) create mode 100644 connectors/graphql/LICENSE.txt create mode 100644 connectors/graphql/README.md create mode 100644 connectors/graphql/element-templates/template-connector.json create mode 100644 connectors/graphql/pom.xml create mode 100644 connectors/graphql/src/main/java/io/camunda/connector/graphql/Authentication.java create mode 100644 connectors/graphql/src/main/java/io/camunda/connector/graphql/GraphQLFunction.java create mode 100644 connectors/graphql/src/main/java/io/camunda/connector/graphql/GraphQLRequest.java create mode 100644 connectors/graphql/src/main/java/io/camunda/connector/graphql/GraphQLResult.java diff --git a/connector-archetype-internal/src/main/resources/archetype-resources/pom.xml b/connector-archetype-internal/src/main/resources/archetype-resources/pom.xml index 025a656a07..6761ab5e81 100644 --- a/connector-archetype-internal/src/main/resources/archetype-resources/pom.xml +++ b/connector-archetype-internal/src/main/resources/archetype-resources/pom.xml @@ -11,7 +11,7 @@ ${artifactId} - ${connectorName} cnnector for Camunda 8 + ${connectorName} connector for Camunda 8 ${artifactId} jar diff --git a/connectors/graphql/LICENSE.txt b/connectors/graphql/LICENSE.txt new file mode 100644 index 0000000000..faff62873e --- /dev/null +++ b/connectors/graphql/LICENSE.txt @@ -0,0 +1,5 @@ +Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH under one or more contributor license agreements and licensed to you under a proprietary license. +You may not use this file except in compliance with the proprietary license. +The proprietary license can be either the Camunda Platform Self-Managed Free Edition license (available on Camunda’s website) or the Camunda Platform Self-Managed Enterprise Edition license (a copy you obtain when you contact Camunda). +The Camunda Platform Self-Managed Free Edition comes for free but only allows for usage of the software (file) in non-production environments. +If you want to use the software (file) in production, you need to purchase the Camunda Platform Self-Managed Enterprise Edition. diff --git a/connectors/graphql/README.md b/connectors/graphql/README.md new file mode 100644 index 0000000000..27c9a99c79 --- /dev/null +++ b/connectors/graphql/README.md @@ -0,0 +1,38 @@ +# Camunda Connector Template + + +```bash +mvn clean package +``` + + + +```json +{ + "myProperty": "....." +} +``` + + +```json +{ + "result": { + "myProperty": "....." + } +} +``` + + +| Code | Description | +| - | - | +| FAIL | Message starts with 'fail' (ignoring case) | + + +Run unit tests + +```bash +mvn clean verify +``` + + +The element templates can be found in the [element-templates](element-templates) directory. diff --git a/connectors/graphql/element-templates/template-connector.json b/connectors/graphql/element-templates/template-connector.json new file mode 100644 index 0000000000..3fc48c648b --- /dev/null +++ b/connectors/graphql/element-templates/template-connector.json @@ -0,0 +1,383 @@ +{ + "$schema": "https://unpkg.com/@camunda/zeebe-element-templates-json-schema/resources/schema.json", + "name": "GraphQL Connector", + "id": "io.camunda.connectors.GraphQL.v1", + "description": "Execute GraphQL query", + "version": 1, + "documentationRef": "https://docs.camunda.io/docs/components/modeler/web-modeler/connectors/available-connectors/template/", + "icon": { + "contents": "data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' version='1.1' id='GraphQL_Logo' x='0px' y='0px' viewBox='0 0 400 400' enable-background='new 0 0 400 400' xml:space='preserve'%3E%3Cg%3E%3Cg%3E%3Cg%3E%3Crect x='122' y='-0.4' transform='matrix(-0.866 -0.5 0.5 -0.866 163.3196 363.3136)' fill='%23E535AB' width='16.6' height='320.3'/%3E%3C/g%3E%3C/g%3E%3Cg%3E%3Cg%3E%3Crect x='39.8' y='272.2' fill='%23E535AB' width='320.3' height='16.6'/%3E%3C/g%3E%3C/g%3E%3Cg%3E%3Cg%3E%3Crect x='37.9' y='312.2' transform='matrix(-0.866 -0.5 0.5 -0.866 83.0693 663.3409)' fill='%23E535AB' width='185' height='16.6'/%3E%3C/g%3E%3C/g%3E%3Cg%3E%3Cg%3E%3Crect x='177.1' y='71.1' transform='matrix(-0.866 -0.5 0.5 -0.866 463.3409 283.0693)' fill='%23E535AB' width='185' height='16.6'/%3E%3C/g%3E%3C/g%3E%3Cg%3E%3Cg%3E%3Crect x='122.1' y='-13' transform='matrix(-0.5 -0.866 0.866 -0.5 126.7903 232.1221)' fill='%23E535AB' width='16.6' height='185'/%3E%3C/g%3E%3C/g%3E%3Cg%3E%3Cg%3E%3Crect x='109.6' y='151.6' transform='matrix(-0.5 -0.866 0.866 -0.5 266.0828 473.3766)' fill='%23E535AB' width='320.3' height='16.6'/%3E%3C/g%3E%3C/g%3E%3Cg%3E%3Cg%3E%3Crect x='52.5' y='107.5' fill='%23E535AB' width='16.6' height='185'/%3E%3C/g%3E%3C/g%3E%3Cg%3E%3Cg%3E%3Crect x='330.9' y='107.5' fill='%23E535AB' width='16.6' height='185'/%3E%3C/g%3E%3C/g%3E%3Cg%3E%3Cg%3E%3Crect x='262.4' y='240.1' transform='matrix(-0.5 -0.866 0.866 -0.5 126.7953 714.2875)' fill='%23E535AB' width='14.5' height='160.9'/%3E%3C/g%3E%3C/g%3E%3Cpath fill='%23E535AB' d='M369.5,297.9c-9.6,16.7-31,22.4-47.7,12.8c-16.7-9.6-22.4-31-12.8-47.7c9.6-16.7,31-22.4,47.7-12.8 C373.5,259.9,379.2,281.2,369.5,297.9'/%3E%3Cpath fill='%23E535AB' d='M90.9,137c-9.6,16.7-31,22.4-47.7,12.8c-16.7-9.6-22.4-31-12.8-47.7c9.6-16.7,31-22.4,47.7-12.8 C94.8,99,100.5,120.3,90.9,137'/%3E%3Cpath fill='%23E535AB' d='M30.5,297.9c-9.6-16.7-3.9-38,12.8-47.7c16.7-9.6,38-3.9,47.7,12.8c9.6,16.7,3.9,38-12.8,47.7 C61.4,320.3,40.1,314.6,30.5,297.9'/%3E%3Cpath fill='%23E535AB' d='M309.1,137c-9.6-16.7-3.9-38,12.8-47.7c16.7-9.6,38-3.9,47.7,12.8c9.6,16.7,3.9,38-12.8,47.7 C340.1,159.4,318.7,153.7,309.1,137'/%3E%3Cpath fill='%23E535AB' d='M200,395.8c-19.3,0-34.9-15.6-34.9-34.9c0-19.3,15.6-34.9,34.9-34.9c19.3,0,34.9,15.6,34.9,34.9 C234.9,380.1,219.3,395.8,200,395.8'/%3E%3Cpath fill='%23E535AB' d='M200,74c-19.3,0-34.9-15.6-34.9-34.9c0-19.3,15.6-34.9,34.9-34.9c19.3,0,34.9,15.6,34.9,34.9 C234.9,58.4,219.3,74,200,74'/%3E%3C/g%3E%3C/svg%3E" + }, + "category": { + "id": "connectors", + "name": "Connectors" + }, + "appliesTo": [ + "bpmn:Task" + ], + "elementType": { + "value": "bpmn:ServiceTask" + }, + "groups": [ + { + "id": "authentication", + "label": "Authentication" + }, + { + "id": "endpoint", + "label": "HTTP Endpoint" + }, + { + "id": "input", + "label": "Payload" + }, + { + "id": "timeout", + "label": "Connect Timeout" + }, + { + "id": "output", + "label": "Response Mapping" + }, + { + "id": "errors", + "label": "Error Handling" + } + ], + "properties": [ + { + "type": "Hidden", + "value": "io.camunda:graphql:1", + "binding": { + "type": "zeebe:taskDefinition:type" + } + }, + { + "label": "Type", + "id": "authenticationType", + "group": "authentication", + "description": "Choose the authentication type. Select 'None' if no authentication is necessary", + "value": "noAuth", + "type": "Dropdown", + "choices": [ + { + "name": "None", + "value": "noAuth" + }, + { + "name": "Basic", + "value": "basic" + }, + { + "name": "Bearer Token", + "value": "bearer" + }, + { + "name": "OAuth 2.0", + "value": "oauth-client-credentials-flow" + } + ], + "binding": { + "type": "zeebe:input", + "name": "authentication.type" + } + }, + { + "id": "method", + "label": "Method", + "group": "endpoint", + "type": "Dropdown", + "value": "get", + "choices": [ + { + "name": "GET", + "value": "get" + }, + { + "name": "POST", + "value": "post" + } + ], + "binding": { + "type": "zeebe:input", + "name": "method" + } + }, + { + "label": "URL", + "group": "endpoint", + "type": "String", + "feel": "optional", + "binding": { + "type": "zeebe:input", + "name": "url" + }, + "constraints": { + "notEmpty": true, + "pattern": { + "value": "^(=|https?://).*", + "message": "Must be a http(s) URL." + } + } + }, + { + "label": "Query Parameters", + "description": "Map of query parameters to add to the request URL", + "group": "endpoint", + "type": "Text", + "feel": "required", + "binding": { + "type": "zeebe:input", + "name": "queryParameters" + }, + "optional": true + }, + { + "label": "HTTP Headers", + "description": "Map of HTTP headers to add to the request", + "group": "endpoint", + "type": "Text", + "feel": "required", + "binding": { + "type": "zeebe:input", + "name": "headers" + }, + "optional": true + }, + { + "label": "Bearer Token", + "group": "authentication", + "type": "String", + "feel": "optional", + "binding": { + "type": "zeebe:input", + "name": "authentication.token" + }, + "constraints": { + "notEmpty": true + }, + "condition": { + "property": "authenticationType", + "equals": "bearer" + } + }, + { + "label": "OAuth Token Endpoint", + "description": "The OAuth token endpoint", + "group": "authentication", + "type": "String", + "feel": "optional", + "binding": { + "type": "zeebe:input", + "name": "authentication.oauthTokenEndpoint" + }, + "constraints": { + "notEmpty": true + }, + "condition": { + "property": "authenticationType", + "equals": "oauth-client-credentials-flow" + } + }, + { + "label": "Client ID", + "description": "Your application's Client ID from the OAuth client", + "group": "authentication", + "type": "String", + "feel": "optional", + "binding": { + "type": "zeebe:input", + "name": "authentication.clientId" + }, + "constraints": { + "notEmpty": true + }, + "condition": { + "property": "authenticationType", + "equals": "oauth-client-credentials-flow" + } + }, + { + "label": "Client secret", + "description": "Your application's Client secret from the OAuth client", + "group": "authentication", + "type": "String", + "feel": "optional", + "binding": { + "type": "zeebe:input", + "name": "authentication.clientSecret" + }, + "constraints": { + "notEmpty": true + }, + "condition": { + "property": "authenticationType", + "equals": "oauth-client-credentials-flow" + } + }, + { + "label": "Scopes", + "description": "The scopes which you want to request authorization for (e.g.read:contacts)", + "group": "authentication", + "type": "String", + "feel": "optional", + "optional": true, + "binding": { + "type": "zeebe:input", + "name": "authentication.scopes" + }, + "condition": { + "property": "authenticationType", + "equals": "oauth-client-credentials-flow" + } + }, + { + "label": "Audience", + "description": "The unique identifier of the target API you want to access", + "group": "authentication", + "type": "String", + "feel": "optional", + "optional": true, + "binding": { + "type": "zeebe:input", + "name": "authentication.audience" + }, + "condition": { + "property": "authenticationType", + "equals": "oauth-client-credentials-flow" + } + }, + { + "label": "Client Authentication", + "id": "authenticationType", + "group": "authentication", + "description": "Send client id and client secret as Basic Auth request in the header, or as client credentials in the request body", + "value": "basicAuthHeader", + "type": "Dropdown", + "choices": [ + { + "name": "Send client credentials in body", + "value": "credentialsBody" + }, + { + "name": "Send as Basic Auth header", + "value": "basicAuthHeader" + } + ], + "binding": { + "type": "zeebe:input", + "name": "authentication.clientAuthentication" + }, + "condition": { + "property": "authenticationType", + "equals": "oauth-client-credentials-flow" + } + }, + { + "label": "Username", + "group": "authentication", + "type": "String", + "feel": "optional", + "binding": { + "type": "zeebe:input", + "name": "authentication.username" + }, + "constraints": { + "notEmpty": true + }, + "condition": { + "property": "authenticationType", + "equals": "basic" + } + }, + { + "label": "Password", + "group": "authentication", + "type": "String", + "feel": "optional", + "binding": { + "type": "zeebe:input", + "name": "authentication.password" + }, + "constraints": { + "notEmpty": true + }, + "condition": { + "property": "authenticationType", + "equals": "basic" + } + }, + { + "label": "Connection Timeout", + "description": "Sets the timeout in seconds to establish a connection or 0 for an infinite timeout", + "group": "timeout", + "type": "String", + "value": "20", + "binding": { + "type": "zeebe:input", + "name": "connectionTimeoutInSeconds" + }, + "optional": true, + "constraints": { + "notEmpty": false, + "pattern": { + "value": "^([0-9]*$)|(secrets.*$)", + "message": "Must be timeout in seconds (default value is 20 seconds)" + } + } + }, + { + "label": "Request Body", + "description": "JSON payload to send with the request", + "group": "input", + "type": "Text", + "feel": "required", + "binding": { + "type": "zeebe:input", + "name": "body" + }, + "condition": { + "property": "method", + "oneOf": [ + "post", + "put", + "patch", + "delete" + ] + }, + "optional": true + }, + { + "label": "Result Variable", + "description": "Name of variable to store the response in", + "group": "output", + "type": "String", + "binding": { + "type": "zeebe:taskHeader", + "key": "resultVariable" + } + }, + { + "label": "Result Expression", + "description": "Expression to map the response into process variables", + "group": "output", + "type": "Text", + "feel": "required", + "binding": { + "type": "zeebe:taskHeader", + "key": "resultExpression" + } + }, + { + "label": "Error Expression", + "description": "Expression to handle errors. Details in the documentation", + "group": "errors", + "type": "Text", + "feel": "required", + "binding": { + "type": "zeebe:taskHeader", + "key": "errorExpression" + } + } + ] +} diff --git a/connectors/graphql/pom.xml b/connectors/graphql/pom.xml new file mode 100644 index 0000000000..f580963a7e --- /dev/null +++ b/connectors/graphql/pom.xml @@ -0,0 +1,31 @@ + + 4.0.0 + + + io.camunda.connector + connector-function-parent + 0.15.0-SNAPSHOT + ../pom.xml + + + connector-graphql + graphql connector for Camunda 8 + connector-graphql + jar + + + + Camunda Platform Self-Managed Free Edition license + https://camunda.com/legal/terms/cloud-terms-and-conditions/camunda-cloud-self-managed-free-edition-terms/ + + + Camunda Platform Self-Managed Enterprise Edition license + + + + + + + diff --git a/connectors/graphql/src/main/java/io/camunda/connector/graphql/Authentication.java b/connectors/graphql/src/main/java/io/camunda/connector/graphql/Authentication.java new file mode 100644 index 0000000000..0f50f4bf58 --- /dev/null +++ b/connectors/graphql/src/main/java/io/camunda/connector/graphql/Authentication.java @@ -0,0 +1,63 @@ +/* + * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH + * under one or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information regarding copyright + * ownership. Camunda licenses this file to you under the Apache License, + * Version 2.0; you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.camunda.connector.graphql; + +import io.camunda.connector.api.annotation.Secret; +import java.util.Objects; +import javax.validation.constraints.NotEmpty; + +public class Authentication { + + @NotEmpty private String user; + + @NotEmpty @Secret private String token; + + public String getUser() { + return user; + } + + public void setUser(String user) { + this.user = user; + } + + public String getToken() { + return token; + } + + public void setToken(String token) { + this.token = token; + } + + @Override + public int hashCode() { + return Objects.hash(token, user); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (obj == null) return false; + if (getClass() != obj.getClass()) return false; + Authentication other = (Authentication) obj; + return Objects.equals(token, other.token) && Objects.equals(user, other.user); + } + + @Override + public String toString() { + return "Authentication [user=" + user + ", token=" + token + "]"; + } +} diff --git a/connectors/graphql/src/main/java/io/camunda/connector/graphql/GraphQLFunction.java b/connectors/graphql/src/main/java/io/camunda/connector/graphql/GraphQLFunction.java new file mode 100644 index 0000000000..f63bf7fcb6 --- /dev/null +++ b/connectors/graphql/src/main/java/io/camunda/connector/graphql/GraphQLFunction.java @@ -0,0 +1,62 @@ +/* + * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH + * under one or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information regarding copyright + * ownership. Camunda licenses this file to you under the Apache License, + * Version 2.0; you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.camunda.connector.graphql; + +import io.camunda.connector.api.annotation.OutboundConnector; +import io.camunda.connector.api.error.ConnectorException; +import io.camunda.connector.api.outbound.OutboundConnectorContext; +import io.camunda.connector.api.outbound.OutboundConnectorFunction; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@OutboundConnector( + name = "GRAPHQL", + inputVariables = { + "url", + "method", + "authentication", + "headers", + "queryParameters", + "connectionTimeoutInSeconds", + "body" + }, + type = "io.camunda:graphql:1") +public class GraphQLFunction implements OutboundConnectorFunction { + + private static final Logger LOGGER = LoggerFactory.getLogger(GraphQLFunction.class); + + @Override + public Object execute(OutboundConnectorContext context) throws Exception { + var connectorRequest = context.getVariablesAsType(GraphQLRequest.class); + context.validate(connectorRequest); + context.replaceSecrets(connectorRequest); + + return executeConnector(connectorRequest); + } + + private GraphQLResult executeConnector(final GraphQLRequest connectorRequest) { + // TODO: implement connector logic + LOGGER.info("Executing my connector with request {}", connectorRequest); + String myProperty = connectorRequest.getMyProperty(); + if (myProperty != null && myProperty.toLowerCase().startsWith("fail")) { + throw new ConnectorException("FAIL", "My property started with 'fail', was: " + myProperty); + } + var result = new GraphQLResult(); + result.setMyProperty(myProperty); + return result; + } +} diff --git a/connectors/graphql/src/main/java/io/camunda/connector/graphql/GraphQLRequest.java b/connectors/graphql/src/main/java/io/camunda/connector/graphql/GraphQLRequest.java new file mode 100644 index 0000000000..673818f53c --- /dev/null +++ b/connectors/graphql/src/main/java/io/camunda/connector/graphql/GraphQLRequest.java @@ -0,0 +1,68 @@ +/* + * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH + * under one or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information regarding copyright + * ownership. Camunda licenses this file to you under the Apache License, + * Version 2.0; you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.camunda.connector.graphql; + +import io.camunda.connector.api.annotation.Secret; +import java.util.Objects; +import javax.validation.Valid; +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; + +public class GraphQLRequest { + + @NotEmpty private String myProperty; + + @Valid @NotNull @Secret private Authentication authentication; + + // TODO: add request properties + + public String getMyProperty() { + return myProperty; + } + + public void setMyProperty(final String myProperty) { + this.myProperty = myProperty; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + GraphQLRequest that = (GraphQLRequest) o; + return myProperty.equals(that.myProperty) && authentication.equals(that.authentication); + } + + @Override + public int hashCode() { + return Objects.hash(myProperty, authentication); + } + + @Override + public String toString() { + return "MyConnectorRequest{" + + "myProperty='" + + myProperty + + '\'' + + ", authentication=" + + authentication + + '}'; + } +} diff --git a/connectors/graphql/src/main/java/io/camunda/connector/graphql/GraphQLResult.java b/connectors/graphql/src/main/java/io/camunda/connector/graphql/GraphQLResult.java new file mode 100644 index 0000000000..4602271d60 --- /dev/null +++ b/connectors/graphql/src/main/java/io/camunda/connector/graphql/GraphQLResult.java @@ -0,0 +1,55 @@ +/* + * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH + * under one or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information regarding copyright + * ownership. Camunda licenses this file to you under the Apache License, + * Version 2.0; you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.camunda.connector.graphql; + +import java.util.Objects; + +public class GraphQLResult { + + // TODO: define connector result properties, which are returned to the process engine + private String myProperty; + + public String getMyProperty() { + return myProperty; + } + + public void setMyProperty(String myProperty) { + this.myProperty = myProperty; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + final GraphQLResult that = (GraphQLResult) o; + return Objects.equals(myProperty, that.myProperty); + } + + @Override + public int hashCode() { + return Objects.hash(myProperty); + } + + @Override + public String toString() { + return "MyConnectorResult{" + "myProperty='" + myProperty + '\'' + '}'; + } +} diff --git a/connectors/pom.xml b/connectors/pom.xml index c3e49ab6bd..a57769ea7e 100644 --- a/connectors/pom.xml +++ b/connectors/pom.xml @@ -28,6 +28,7 @@ http-json microsoft-teams slack + graphql From 856f06e520818b357dc3278a8f3632dc34fc0433 Mon Sep 17 00:00:00 2001 From: markfarkas-camunda Date: Wed, 11 Jan 2023 18:23:20 +0100 Subject: [PATCH 02/18] fix(graphql): update license headers --- .../connector/graphql/Authentication.java | 16 +++------------- .../connector/graphql/GraphQLFunction.java | 16 +++------------- .../connector/graphql/GraphQLRequest.java | 16 +++------------- .../camunda/connector/graphql/GraphQLResult.java | 16 +++------------- 4 files changed, 12 insertions(+), 52 deletions(-) diff --git a/connectors/graphql/src/main/java/io/camunda/connector/graphql/Authentication.java b/connectors/graphql/src/main/java/io/camunda/connector/graphql/Authentication.java index 0f50f4bf58..c51ff99baa 100644 --- a/connectors/graphql/src/main/java/io/camunda/connector/graphql/Authentication.java +++ b/connectors/graphql/src/main/java/io/camunda/connector/graphql/Authentication.java @@ -1,18 +1,8 @@ /* * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH - * under one or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information regarding copyright - * ownership. Camunda licenses this file to you under the Apache License, - * Version 2.0; you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * under one or more contributor license agreements. Licensed under a proprietary license. + * See the License.txt file for more information. You may not use this file + * except in compliance with the proprietary license. */ package io.camunda.connector.graphql; diff --git a/connectors/graphql/src/main/java/io/camunda/connector/graphql/GraphQLFunction.java b/connectors/graphql/src/main/java/io/camunda/connector/graphql/GraphQLFunction.java index f63bf7fcb6..ad2edf4350 100644 --- a/connectors/graphql/src/main/java/io/camunda/connector/graphql/GraphQLFunction.java +++ b/connectors/graphql/src/main/java/io/camunda/connector/graphql/GraphQLFunction.java @@ -1,18 +1,8 @@ /* * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH - * under one or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information regarding copyright - * ownership. Camunda licenses this file to you under the Apache License, - * Version 2.0; you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * under one or more contributor license agreements. Licensed under a proprietary license. + * See the License.txt file for more information. You may not use this file + * except in compliance with the proprietary license. */ package io.camunda.connector.graphql; diff --git a/connectors/graphql/src/main/java/io/camunda/connector/graphql/GraphQLRequest.java b/connectors/graphql/src/main/java/io/camunda/connector/graphql/GraphQLRequest.java index 673818f53c..7466fb43b8 100644 --- a/connectors/graphql/src/main/java/io/camunda/connector/graphql/GraphQLRequest.java +++ b/connectors/graphql/src/main/java/io/camunda/connector/graphql/GraphQLRequest.java @@ -1,18 +1,8 @@ /* * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH - * under one or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information regarding copyright - * ownership. Camunda licenses this file to you under the Apache License, - * Version 2.0; you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * under one or more contributor license agreements. Licensed under a proprietary license. + * See the License.txt file for more information. You may not use this file + * except in compliance with the proprietary license. */ package io.camunda.connector.graphql; diff --git a/connectors/graphql/src/main/java/io/camunda/connector/graphql/GraphQLResult.java b/connectors/graphql/src/main/java/io/camunda/connector/graphql/GraphQLResult.java index 4602271d60..ee902a0d73 100644 --- a/connectors/graphql/src/main/java/io/camunda/connector/graphql/GraphQLResult.java +++ b/connectors/graphql/src/main/java/io/camunda/connector/graphql/GraphQLResult.java @@ -1,18 +1,8 @@ /* * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH - * under one or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information regarding copyright - * ownership. Camunda licenses this file to you under the Apache License, - * Version 2.0; you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * under one or more contributor license agreements. Licensed under a proprietary license. + * See the License.txt file for more information. You may not use this file + * except in compliance with the proprietary license. */ package io.camunda.connector.graphql; From 4c4a94276c3f9370535bf7fab31e4befc375d8ac Mon Sep 17 00:00:00 2001 From: markfarkas-camunda Date: Mon, 23 Jan 2023 09:45:26 +0100 Subject: [PATCH 03/18] feat(graphql): implement GraphQL GET method without variables --- bundle/mvn/default-bundle/pom.xml | 6 + connectors/graphql/connector.yml | 3 + ...-connector.json => graphql-connector.json} | 56 ++--- connectors/graphql/pom.xml | 29 +++ .../connector/graphql/Authentication.java | 53 ----- .../connector/graphql/GraphQLFunction.java | 221 ++++++++++++++++-- .../connector/graphql/GraphQLRequest.java | 58 ----- .../connector/graphql/GraphQLResult.java | 45 ---- .../graphql/auth/Authentication.java | 39 ++++ .../graphql/auth/BasicAuthentication.java | 68 ++++++ .../graphql/auth/BearerAuthentication.java | 55 +++++ .../graphql/auth/NoAuthentication.java | 30 +++ .../graphql/auth/OAuthAuthentication.java | 134 +++++++++++ .../components/GsonComponentSupplier.java | 44 ++++ .../HttpTransportComponentSupplier.java | 27 +++ .../graphql/constants/Constants.java | 22 ++ .../graphql/model/ErrorResponse.java | 53 +++++ .../graphql/model/GraphQLRequest.java | 128 ++++++++++ .../graphql/model/GraphQLResult.java | 65 ++++++ ...tor.api.outbound.OutboundConnectorFunction | 1 + .../connector/http/HttpJsonFunction.java | 3 +- 21 files changed, 930 insertions(+), 210 deletions(-) create mode 100644 connectors/graphql/connector.yml rename connectors/graphql/element-templates/{template-connector.json => graphql-connector.json} (91%) delete mode 100644 connectors/graphql/src/main/java/io/camunda/connector/graphql/Authentication.java delete mode 100644 connectors/graphql/src/main/java/io/camunda/connector/graphql/GraphQLRequest.java delete mode 100644 connectors/graphql/src/main/java/io/camunda/connector/graphql/GraphQLResult.java create mode 100644 connectors/graphql/src/main/java/io/camunda/connector/graphql/auth/Authentication.java create mode 100644 connectors/graphql/src/main/java/io/camunda/connector/graphql/auth/BasicAuthentication.java create mode 100644 connectors/graphql/src/main/java/io/camunda/connector/graphql/auth/BearerAuthentication.java create mode 100644 connectors/graphql/src/main/java/io/camunda/connector/graphql/auth/NoAuthentication.java create mode 100644 connectors/graphql/src/main/java/io/camunda/connector/graphql/auth/OAuthAuthentication.java create mode 100644 connectors/graphql/src/main/java/io/camunda/connector/graphql/components/GsonComponentSupplier.java create mode 100644 connectors/graphql/src/main/java/io/camunda/connector/graphql/components/HttpTransportComponentSupplier.java create mode 100644 connectors/graphql/src/main/java/io/camunda/connector/graphql/constants/Constants.java create mode 100644 connectors/graphql/src/main/java/io/camunda/connector/graphql/model/ErrorResponse.java create mode 100644 connectors/graphql/src/main/java/io/camunda/connector/graphql/model/GraphQLRequest.java create mode 100644 connectors/graphql/src/main/java/io/camunda/connector/graphql/model/GraphQLResult.java create mode 100644 connectors/graphql/src/main/resources/META-INF/services/io.camunda.connector.api.outbound.OutboundConnectorFunction diff --git a/bundle/mvn/default-bundle/pom.xml b/bundle/mvn/default-bundle/pom.xml index fad833645c..209b23d804 100644 --- a/bundle/mvn/default-bundle/pom.xml +++ b/bundle/mvn/default-bundle/pom.xml @@ -60,6 +60,12 @@ io.camunda.connector connector-microsoft-teams + + + io.camunda.connector + connector-graphql + 0.15.0-SNAPSHOT + diff --git a/connectors/graphql/connector.yml b/connectors/graphql/connector.yml new file mode 100644 index 0000000000..1ff9d8c068 --- /dev/null +++ b/connectors/graphql/connector.yml @@ -0,0 +1,3 @@ +name: HTTPJSON +type: io.camunda:connector-graphql:1 +variables: [ url, method, authentication, variables, query, connectionTimeoutInSeconds ] \ No newline at end of file diff --git a/connectors/graphql/element-templates/template-connector.json b/connectors/graphql/element-templates/graphql-connector.json similarity index 91% rename from connectors/graphql/element-templates/template-connector.json rename to connectors/graphql/element-templates/graphql-connector.json index 3fc48c648b..fc43b75185 100644 --- a/connectors/graphql/element-templates/template-connector.json +++ b/connectors/graphql/element-templates/graphql-connector.json @@ -4,7 +4,7 @@ "id": "io.camunda.connectors.GraphQL.v1", "description": "Execute GraphQL query", "version": 1, - "documentationRef": "https://docs.camunda.io/docs/components/modeler/web-modeler/connectors/available-connectors/template/", + "documentationRef": "https://docs.camunda.io/docs/components/connectors/out-of-the-box-connectors/graphql/", "icon": { "contents": "data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' version='1.1' id='GraphQL_Logo' x='0px' y='0px' viewBox='0 0 400 400' enable-background='new 0 0 400 400' xml:space='preserve'%3E%3Cg%3E%3Cg%3E%3Cg%3E%3Crect x='122' y='-0.4' transform='matrix(-0.866 -0.5 0.5 -0.866 163.3196 363.3136)' fill='%23E535AB' width='16.6' height='320.3'/%3E%3C/g%3E%3C/g%3E%3Cg%3E%3Cg%3E%3Crect x='39.8' y='272.2' fill='%23E535AB' width='320.3' height='16.6'/%3E%3C/g%3E%3C/g%3E%3Cg%3E%3Cg%3E%3Crect x='37.9' y='312.2' transform='matrix(-0.866 -0.5 0.5 -0.866 83.0693 663.3409)' fill='%23E535AB' width='185' height='16.6'/%3E%3C/g%3E%3C/g%3E%3Cg%3E%3Cg%3E%3Crect x='177.1' y='71.1' transform='matrix(-0.866 -0.5 0.5 -0.866 463.3409 283.0693)' fill='%23E535AB' width='185' height='16.6'/%3E%3C/g%3E%3C/g%3E%3Cg%3E%3Cg%3E%3Crect x='122.1' y='-13' transform='matrix(-0.5 -0.866 0.866 -0.5 126.7903 232.1221)' fill='%23E535AB' width='16.6' height='185'/%3E%3C/g%3E%3C/g%3E%3Cg%3E%3Cg%3E%3Crect x='109.6' y='151.6' transform='matrix(-0.5 -0.866 0.866 -0.5 266.0828 473.3766)' fill='%23E535AB' width='320.3' height='16.6'/%3E%3C/g%3E%3C/g%3E%3Cg%3E%3Cg%3E%3Crect x='52.5' y='107.5' fill='%23E535AB' width='16.6' height='185'/%3E%3C/g%3E%3C/g%3E%3Cg%3E%3Cg%3E%3Crect x='330.9' y='107.5' fill='%23E535AB' width='16.6' height='185'/%3E%3C/g%3E%3C/g%3E%3Cg%3E%3Cg%3E%3Crect x='262.4' y='240.1' transform='matrix(-0.5 -0.866 0.866 -0.5 126.7953 714.2875)' fill='%23E535AB' width='14.5' height='160.9'/%3E%3C/g%3E%3C/g%3E%3Cpath fill='%23E535AB' d='M369.5,297.9c-9.6,16.7-31,22.4-47.7,12.8c-16.7-9.6-22.4-31-12.8-47.7c9.6-16.7,31-22.4,47.7-12.8 C373.5,259.9,379.2,281.2,369.5,297.9'/%3E%3Cpath fill='%23E535AB' d='M90.9,137c-9.6,16.7-31,22.4-47.7,12.8c-16.7-9.6-22.4-31-12.8-47.7c9.6-16.7,31-22.4,47.7-12.8 C94.8,99,100.5,120.3,90.9,137'/%3E%3Cpath fill='%23E535AB' d='M30.5,297.9c-9.6-16.7-3.9-38,12.8-47.7c16.7-9.6,38-3.9,47.7,12.8c9.6,16.7,3.9,38-12.8,47.7 C61.4,320.3,40.1,314.6,30.5,297.9'/%3E%3Cpath fill='%23E535AB' d='M309.1,137c-9.6-16.7-3.9-38,12.8-47.7c16.7-9.6,38-3.9,47.7,12.8c9.6,16.7,3.9,38-12.8,47.7 C340.1,159.4,318.7,153.7,309.1,137'/%3E%3Cpath fill='%23E535AB' d='M200,395.8c-19.3,0-34.9-15.6-34.9-34.9c0-19.3,15.6-34.9,34.9-34.9c19.3,0,34.9,15.6,34.9,34.9 C234.9,380.1,219.3,395.8,200,395.8'/%3E%3Cpath fill='%23E535AB' d='M200,74c-19.3,0-34.9-15.6-34.9-34.9c0-19.3,15.6-34.9,34.9-34.9c19.3,0,34.9,15.6,34.9,34.9 C234.9,58.4,219.3,74,200,74'/%3E%3C/g%3E%3C/svg%3E" }, @@ -28,8 +28,8 @@ "label": "HTTP Endpoint" }, { - "id": "input", - "label": "Payload" + "id": "graphql", + "label": "GraphQL Query" }, { "id": "timeout", @@ -47,7 +47,7 @@ "properties": [ { "type": "Hidden", - "value": "io.camunda:graphql:1", + "value": "io.camunda:connector-graphql:1", "binding": { "type": "zeebe:taskDefinition:type" } @@ -121,26 +121,29 @@ } }, { - "label": "Query Parameters", - "description": "Map of query parameters to add to the request URL", - "group": "endpoint", + "label": "Query/Mutation", + "description": "See documentation", + "group": "graphql", "type": "Text", - "feel": "required", + "feel": "optional", "binding": { "type": "zeebe:input", - "name": "queryParameters" + "name": "query" }, - "optional": true + "optional": false, + "constraints": { + "notEmpty": true + } }, { - "label": "HTTP Headers", - "description": "Map of HTTP headers to add to the request", - "group": "endpoint", + "label": "Variables", + "description": "Variables to add to the query or mutation. See documentation", + "group": "graphql", "type": "Text", - "feel": "required", + "feel": "optional", "binding": { "type": "zeebe:input", - "name": "headers" + "name": "variables" }, "optional": true }, @@ -326,27 +329,6 @@ } } }, - { - "label": "Request Body", - "description": "JSON payload to send with the request", - "group": "input", - "type": "Text", - "feel": "required", - "binding": { - "type": "zeebe:input", - "name": "body" - }, - "condition": { - "property": "method", - "oneOf": [ - "post", - "put", - "patch", - "delete" - ] - }, - "optional": true - }, { "label": "Result Variable", "description": "Name of variable to store the response in", @@ -380,4 +362,4 @@ } } ] -} +} \ No newline at end of file diff --git a/connectors/graphql/pom.xml b/connectors/graphql/pom.xml index f580963a7e..79980ad2c6 100644 --- a/connectors/graphql/pom.xml +++ b/connectors/graphql/pom.xml @@ -26,6 +26,35 @@ + + org.danilopianini + gson-extras + + + + com.google.http-client + google-http-client-gson + + + + com.google.http-client + google-http-client-apache-v2 + + + + com.google.auth + google-auth-library-oauth2-http + + + + org.apache.httpcomponents + httpcore + + + + org.slf4j + jcl-over-slf4j + diff --git a/connectors/graphql/src/main/java/io/camunda/connector/graphql/Authentication.java b/connectors/graphql/src/main/java/io/camunda/connector/graphql/Authentication.java deleted file mode 100644 index c51ff99baa..0000000000 --- a/connectors/graphql/src/main/java/io/camunda/connector/graphql/Authentication.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH - * under one or more contributor license agreements. Licensed under a proprietary license. - * See the License.txt file for more information. You may not use this file - * except in compliance with the proprietary license. - */ -package io.camunda.connector.graphql; - -import io.camunda.connector.api.annotation.Secret; -import java.util.Objects; -import javax.validation.constraints.NotEmpty; - -public class Authentication { - - @NotEmpty private String user; - - @NotEmpty @Secret private String token; - - public String getUser() { - return user; - } - - public void setUser(String user) { - this.user = user; - } - - public String getToken() { - return token; - } - - public void setToken(String token) { - this.token = token; - } - - @Override - public int hashCode() { - return Objects.hash(token, user); - } - - @Override - public boolean equals(Object obj) { - if (this == obj) return true; - if (obj == null) return false; - if (getClass() != obj.getClass()) return false; - Authentication other = (Authentication) obj; - return Objects.equals(token, other.token) && Objects.equals(user, other.user); - } - - @Override - public String toString() { - return "Authentication [user=" + user + ", token=" + token + "]"; - } -} diff --git a/connectors/graphql/src/main/java/io/camunda/connector/graphql/GraphQLFunction.java b/connectors/graphql/src/main/java/io/camunda/connector/graphql/GraphQLFunction.java index ad2edf4350..3075720801 100644 --- a/connectors/graphql/src/main/java/io/camunda/connector/graphql/GraphQLFunction.java +++ b/connectors/graphql/src/main/java/io/camunda/connector/graphql/GraphQLFunction.java @@ -6,10 +6,39 @@ */ package io.camunda.connector.graphql; +import static org.apache.http.entity.ContentType.APPLICATION_JSON; + +import com.google.api.client.http.GenericUrl; +import com.google.api.client.http.HttpContent; +import com.google.api.client.http.HttpHeaders; +import com.google.api.client.http.HttpRequest; +import com.google.api.client.http.HttpRequestFactory; +import com.google.api.client.http.HttpResponse; +import com.google.api.client.http.HttpResponseException; +import com.google.api.client.http.UrlEncodedContent; +import com.google.api.client.http.json.JsonHttpContent; +import com.google.api.client.json.gson.GsonFactory; +import com.google.gson.Gson; +import com.google.gson.JsonObject; import io.camunda.connector.api.annotation.OutboundConnector; import io.camunda.connector.api.error.ConnectorException; import io.camunda.connector.api.outbound.OutboundConnectorContext; import io.camunda.connector.api.outbound.OutboundConnectorFunction; +import io.camunda.connector.graphql.auth.OAuthAuthentication; +import io.camunda.connector.graphql.components.GsonComponentSupplier; +import io.camunda.connector.graphql.components.HttpTransportComponentSupplier; +import io.camunda.connector.graphql.constants.Constants; +import io.camunda.connector.graphql.model.ErrorResponse; +import io.camunda.connector.graphql.model.GraphQLRequest; +import io.camunda.connector.graphql.model.GraphQLResult; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -19,34 +48,194 @@ "url", "method", "authentication", - "headers", - "queryParameters", - "connectionTimeoutInSeconds", - "body" + "query", + "variables", + "connectionTimeoutInSeconds" }, - type = "io.camunda:graphql:1") + type = "io.camunda:connector-graphql:1") public class GraphQLFunction implements OutboundConnectorFunction { private static final Logger LOGGER = LoggerFactory.getLogger(GraphQLFunction.class); + private final Gson gson; + private final GsonFactory gsonFactory; + private final HttpRequestFactory requestFactory; + + public GraphQLFunction() { + this( + GsonComponentSupplier.gsonInstance(), + HttpTransportComponentSupplier.httpRequestFactoryInstance(), + GsonComponentSupplier.gsonFactoryInstance()); + } + + public GraphQLFunction( + final Gson gson, final HttpRequestFactory requestFactory, final GsonFactory gsonFactory) { + this.gson = gson; + this.requestFactory = requestFactory; + this.gsonFactory = gsonFactory; + } + @Override public Object execute(OutboundConnectorContext context) throws Exception { - var connectorRequest = context.getVariablesAsType(GraphQLRequest.class); + //var connectorRequest = context.getVariablesAsType(GraphQLRequest.class); + final var json = context.getVariables(); + final var connectorRequest = gson.fromJson(json, GraphQLRequest.class); context.validate(connectorRequest); context.replaceSecrets(connectorRequest); + return executeGraphQLConnector(connectorRequest); + } - return executeConnector(connectorRequest); + ///////////////////////////////////////////////////////////////////////////////////// + private GraphQLResult executeGraphQLConnector(final GraphQLRequest connectorRequest) + throws IOException { + // connector logic + LOGGER.info("Executing graphql connector with request {}", connectorRequest); + String bearerToken = null; + if (connectorRequest.getAuthentication() != null + && connectorRequest.getAuthentication() instanceof OAuthAuthentication) { + final HttpRequest oauthRequest = createOAuthRequest(connectorRequest); + final HttpResponse oauthResponse = executeHttpRequest(oauthRequest); + bearerToken = extractAccessToken(oauthResponse); + } + + final HttpRequest httpRequest = createRequest(connectorRequest, bearerToken); + HttpResponse httpResponse = executeHttpRequest(httpRequest); + return toHttpJsonResponse(httpResponse); + } + + protected HttpRequest createRequest(final GraphQLRequest request, String bearerToken) + throws IOException { + final String method = request.getMethod().toUpperCase(); + final GenericUrl genericUrl = new GenericUrl(request.getUrl()); + HttpContent content = null; + final HttpHeaders headers = createHeaders(request, bearerToken); + final Map query = new HashMap<>(); + String escapedQuery = request.getQuery() + //.replaceAll("\\s+","") + .replace("\\n", ""); + if(Constants.POST.equalsIgnoreCase(method)) { + // TODO : make this GraphQL compliant and more sophisticated + content = new JsonHttpContent(gsonFactory, escapedQuery); + } else { + // TODO : add variables + query.put("query", escapedQuery); + } + + genericUrl.putAll(query); + + final var httpRequest = requestFactory.buildRequest(method, genericUrl, content); + httpRequest.setFollowRedirects(false); + setTimeout(request, httpRequest); + httpRequest.setHeaders(headers); + + return httpRequest; + } + + protected HttpHeaders createHeaders(final GraphQLRequest request, String bearerToken) { + final HttpHeaders httpHeaders = new HttpHeaders(); + if (Constants.POST.equalsIgnoreCase(request.getMethod())) { + httpHeaders.setContentType(APPLICATION_JSON.getMimeType()); + } + if (request.hasAuthentication()) { + if (bearerToken != null && !bearerToken.isEmpty()) { + httpHeaders.setAuthorization("Bearer " + bearerToken); + } + request.getAuthentication().setHeaders(httpHeaders); + } + return httpHeaders; + } + + protected String extractAccessToken(HttpResponse oauthResponse) throws IOException { + String oauthResponseStr = oauthResponse.parseAsString(); + if (oauthResponseStr != null && !oauthResponseStr.isEmpty()) { + JsonObject jsonObject = gson.fromJson(oauthResponseStr, JsonObject.class); + if (jsonObject.get(Constants.ACCESS_TOKEN) != null) { + return jsonObject.get(Constants.ACCESS_TOKEN).getAsString(); + } + } + return null; + } + + protected HttpResponse executeHttpRequest(HttpRequest externalRequest) + throws IOException { + try { + return externalRequest.execute(); + } catch (HttpResponseException httpResponseException) { + var errorCode = String.valueOf(httpResponseException.getStatusCode()); + var errorMessage = httpResponseException.getMessage(); + throw new ConnectorException(errorCode, errorMessage, httpResponseException); + } + } + + protected HttpRequest createOAuthRequest(GraphQLRequest request) throws IOException { + OAuthAuthentication authentication = (OAuthAuthentication) request.getAuthentication(); + + final GenericUrl genericUrl = new GenericUrl(authentication.getOauthTokenEndpoint()); + Map data = getDataForAuthRequestBody(authentication); + HttpContent content = new UrlEncodedContent(data); + final String method = Constants.POST; + final var httpRequest = requestFactory.buildRequest(method, genericUrl, content); + httpRequest.setFollowRedirects(false); + setTimeout(request, httpRequest); + HttpHeaders headers = new HttpHeaders(); + + if (authentication.getClientAuthentication().equals(Constants.BASIC_AUTH_HEADER)) { + headers.setBasicAuthentication( + authentication.getClientId(), authentication.getClientSecret()); + } + headers.setContentType(Constants.APPLICATION_X_WWW_FORM_URLENCODED); + httpRequest.setHeaders(headers); + return httpRequest; + } + + protected void setTimeout(GraphQLRequest request, HttpRequest httpRequest) { + if (request.getConnectionTimeoutInSeconds() != null) { + long connectionTimeout = + TimeUnit.SECONDS.toMillis(Long.parseLong(request.getConnectionTimeoutInSeconds())); + int intConnectionTimeout = Math.toIntExact(connectionTimeout); + httpRequest.setConnectTimeout(intConnectionTimeout); + httpRequest.setReadTimeout(intConnectionTimeout); + httpRequest.setWriteTimeout(intConnectionTimeout); + } + } + + private static Map getDataForAuthRequestBody(OAuthAuthentication authentication) { + Map data = new HashMap<>(); + data.put(Constants.GRANT_TYPE, authentication.getGrantType()); + data.put(Constants.AUDIENCE, authentication.getAudience()); + data.put(Constants.SCOPE, authentication.getScopes()); + + if (authentication.getClientAuthentication().equals(Constants.CREDENTIALS_BODY)) { + data.put(Constants.CLIENT_ID, authentication.getClientId()); + data.put(Constants.CLIENT_SECRET, authentication.getClientSecret()); + } + return data; } - private GraphQLResult executeConnector(final GraphQLRequest connectorRequest) { - // TODO: implement connector logic - LOGGER.info("Executing my connector with request {}", connectorRequest); - String myProperty = connectorRequest.getMyProperty(); - if (myProperty != null && myProperty.toLowerCase().startsWith("fail")) { - throw new ConnectorException("FAIL", "My property started with 'fail', was: " + myProperty); + protected GraphQLResult toHttpJsonResponse(final HttpResponse externalResponse) { + final GraphQLResult graphQLResult = new GraphQLResult(); + graphQLResult.setStatus(externalResponse.getStatusCode()); + final Map headers = new HashMap<>(); + externalResponse + .getHeaders() + .forEach( + (k, v) -> { + if (v instanceof List && ((List) v).size() == 1) { + headers.put(k, ((List) v).get(0)); + } else { + headers.put(k, v); + } + }); + graphQLResult.setHeaders(headers); + try (InputStream content = externalResponse.getContent(); + Reader reader = new InputStreamReader(content)) { + final Object body = gson.fromJson(reader, Object.class); + if (body != null) { + graphQLResult.setBody(body); + } + } catch (final Exception e) { + LOGGER.error("Failed to parse external response: {}", externalResponse, e); } - var result = new GraphQLResult(); - result.setMyProperty(myProperty); - return result; + return graphQLResult; } } diff --git a/connectors/graphql/src/main/java/io/camunda/connector/graphql/GraphQLRequest.java b/connectors/graphql/src/main/java/io/camunda/connector/graphql/GraphQLRequest.java deleted file mode 100644 index 7466fb43b8..0000000000 --- a/connectors/graphql/src/main/java/io/camunda/connector/graphql/GraphQLRequest.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH - * under one or more contributor license agreements. Licensed under a proprietary license. - * See the License.txt file for more information. You may not use this file - * except in compliance with the proprietary license. - */ -package io.camunda.connector.graphql; - -import io.camunda.connector.api.annotation.Secret; -import java.util.Objects; -import javax.validation.Valid; -import javax.validation.constraints.NotEmpty; -import javax.validation.constraints.NotNull; - -public class GraphQLRequest { - - @NotEmpty private String myProperty; - - @Valid @NotNull @Secret private Authentication authentication; - - // TODO: add request properties - - public String getMyProperty() { - return myProperty; - } - - public void setMyProperty(final String myProperty) { - this.myProperty = myProperty; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - GraphQLRequest that = (GraphQLRequest) o; - return myProperty.equals(that.myProperty) && authentication.equals(that.authentication); - } - - @Override - public int hashCode() { - return Objects.hash(myProperty, authentication); - } - - @Override - public String toString() { - return "MyConnectorRequest{" - + "myProperty='" - + myProperty - + '\'' - + ", authentication=" - + authentication - + '}'; - } -} diff --git a/connectors/graphql/src/main/java/io/camunda/connector/graphql/GraphQLResult.java b/connectors/graphql/src/main/java/io/camunda/connector/graphql/GraphQLResult.java deleted file mode 100644 index ee902a0d73..0000000000 --- a/connectors/graphql/src/main/java/io/camunda/connector/graphql/GraphQLResult.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH - * under one or more contributor license agreements. Licensed under a proprietary license. - * See the License.txt file for more information. You may not use this file - * except in compliance with the proprietary license. - */ -package io.camunda.connector.graphql; - -import java.util.Objects; - -public class GraphQLResult { - - // TODO: define connector result properties, which are returned to the process engine - private String myProperty; - - public String getMyProperty() { - return myProperty; - } - - public void setMyProperty(String myProperty) { - this.myProperty = myProperty; - } - - @Override - public boolean equals(final Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - final GraphQLResult that = (GraphQLResult) o; - return Objects.equals(myProperty, that.myProperty); - } - - @Override - public int hashCode() { - return Objects.hash(myProperty); - } - - @Override - public String toString() { - return "MyConnectorResult{" + "myProperty='" + myProperty + '\'' + '}'; - } -} diff --git a/connectors/graphql/src/main/java/io/camunda/connector/graphql/auth/Authentication.java b/connectors/graphql/src/main/java/io/camunda/connector/graphql/auth/Authentication.java new file mode 100644 index 0000000000..a40fc7ef54 --- /dev/null +++ b/connectors/graphql/src/main/java/io/camunda/connector/graphql/auth/Authentication.java @@ -0,0 +1,39 @@ +/* + * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH + * under one or more contributor license agreements. Licensed under a proprietary license. + * See the License.txt file for more information. You may not use this file + * except in compliance with the proprietary license. + */ +package io.camunda.connector.graphql.auth; + +import com.google.api.client.http.HttpHeaders; +import com.google.common.base.Objects; + +public abstract class Authentication { + + private transient String type; + + public abstract void setHeaders(HttpHeaders headers); + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Authentication that = (Authentication) o; + return Objects.equal(type, that.type); + } + + @Override + public int hashCode() { + return Objects.hashCode(type); + } + + @Override + public String toString() { + return "Authentication{" + "type='" + type + '\'' + '}'; + } +} diff --git a/connectors/graphql/src/main/java/io/camunda/connector/graphql/auth/BasicAuthentication.java b/connectors/graphql/src/main/java/io/camunda/connector/graphql/auth/BasicAuthentication.java new file mode 100644 index 0000000000..f1c5fcea13 --- /dev/null +++ b/connectors/graphql/src/main/java/io/camunda/connector/graphql/auth/BasicAuthentication.java @@ -0,0 +1,68 @@ +/* + * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH + * under one or more contributor license agreements. Licensed under a proprietary license. + * See the License.txt file for more information. You may not use this file + * except in compliance with the proprietary license. + */ +package io.camunda.connector.graphql.auth; + +import com.google.api.client.http.HttpHeaders; +import com.google.common.base.Objects; +import io.camunda.connector.api.annotation.Secret; +import javax.validation.constraints.NotEmpty; + +public class BasicAuthentication extends Authentication { + + @NotEmpty @Secret private String username; + @NotEmpty @Secret private String password; + + @Override + public void setHeaders(final HttpHeaders headers) { + headers.setBasicAuthentication(username, password); + } + + public String getUsername() { + return username; + } + + public void setUsername(final String username) { + this.username = username; + } + + public String getPassword() { + return password; + } + + public void setPassword(final String password) { + this.password = password; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + if (!super.equals(o)) { + return false; + } + BasicAuthentication that = (BasicAuthentication) o; + return Objects.equal(username, that.username) && Objects.equal(password, that.password); + } + + @Override + public int hashCode() { + return Objects.hashCode(super.hashCode(), username, password); + } + + @Override + public String toString() { + return "BasicAuthentication {" + + "username='[REDACTED]'" + + ", password='[REDACTED]'" + + "}; Super: " + + super.toString(); + } +} diff --git a/connectors/graphql/src/main/java/io/camunda/connector/graphql/auth/BearerAuthentication.java b/connectors/graphql/src/main/java/io/camunda/connector/graphql/auth/BearerAuthentication.java new file mode 100644 index 0000000000..364646d679 --- /dev/null +++ b/connectors/graphql/src/main/java/io/camunda/connector/graphql/auth/BearerAuthentication.java @@ -0,0 +1,55 @@ +/* + * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH + * under one or more contributor license agreements. Licensed under a proprietary license. + * See the License.txt file for more information. You may not use this file + * except in compliance with the proprietary license. + */ +package io.camunda.connector.graphql.auth; + +import com.google.api.client.http.HttpHeaders; +import com.google.common.base.Objects; +import io.camunda.connector.api.annotation.Secret; +import javax.validation.constraints.NotEmpty; + +public class BearerAuthentication extends Authentication { + + @NotEmpty @Secret private String token; + + @Override + public void setHeaders(final HttpHeaders headers) { + headers.setAuthorization("Bearer " + token); + } + + public String getToken() { + return token; + } + + public void setToken(String token) { + this.token = token; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + if (!super.equals(o)) { + return false; + } + BearerAuthentication that = (BearerAuthentication) o; + return Objects.equal(token, that.token); + } + + @Override + public int hashCode() { + return Objects.hashCode(super.hashCode(), token); + } + + @Override + public String toString() { + return "BearerAuthentication{" + "token='[REDACTED]'" + "}; Super: " + super.toString(); + } +} diff --git a/connectors/graphql/src/main/java/io/camunda/connector/graphql/auth/NoAuthentication.java b/connectors/graphql/src/main/java/io/camunda/connector/graphql/auth/NoAuthentication.java new file mode 100644 index 0000000000..dc066bb7bd --- /dev/null +++ b/connectors/graphql/src/main/java/io/camunda/connector/graphql/auth/NoAuthentication.java @@ -0,0 +1,30 @@ +/* + * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH + * under one or more contributor license agreements. Licensed under a proprietary license. + * See the License.txt file for more information. You may not use this file + * except in compliance with the proprietary license. + */ +package io.camunda.connector.graphql.auth; + +import com.google.api.client.http.HttpHeaders; + +public class NoAuthentication extends Authentication { + + @Override + public void setHeaders(final HttpHeaders headers) {} + + @Override + public boolean equals(final Object o) { + return super.equals(o); + } + + @Override + public int hashCode() { + return super.hashCode(); + } + + @Override + public String toString() { + return super.toString(); + } +} diff --git a/connectors/graphql/src/main/java/io/camunda/connector/graphql/auth/OAuthAuthentication.java b/connectors/graphql/src/main/java/io/camunda/connector/graphql/auth/OAuthAuthentication.java new file mode 100644 index 0000000000..01da7595a7 --- /dev/null +++ b/connectors/graphql/src/main/java/io/camunda/connector/graphql/auth/OAuthAuthentication.java @@ -0,0 +1,134 @@ +/* + * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH + * under one or more contributor license agreements. Licensed under a proprietary license. + * See the License.txt file for more information. You may not use this file + * except in compliance with the proprietary license. + */ +package io.camunda.connector.graphql.auth; + +import com.google.api.client.http.HttpHeaders; +import io.camunda.connector.api.annotation.Secret; +import java.util.Objects; +import javax.validation.constraints.NotEmpty; + +public class OAuthAuthentication extends Authentication { + private final String grantType = "client_credentials"; + @NotEmpty @Secret private String oauthTokenEndpoint; + @NotEmpty @Secret private String clientId; + @NotEmpty @Secret private String clientSecret; + @Secret private String audience; + @NotEmpty private String clientAuthentication; + + private String scopes; + + public String getClientId() { + return clientId; + } + + public void setClientId(String clientId) { + this.clientId = clientId; + } + + public String getClientSecret() { + return clientSecret; + } + + public void setClientSecret(String clientSecret) { + this.clientSecret = clientSecret; + } + + public String getGrantType() { + return grantType; + } + + public String getOauthTokenEndpoint() { + return oauthTokenEndpoint; + } + + public void setOauthTokenEndpoint(String oauthTokenEndpoint) { + this.oauthTokenEndpoint = oauthTokenEndpoint; + } + + public String getScopes() { + return scopes; + } + + public void setScopes(String scopes) { + this.scopes = scopes; + } + + public String getAudience() { + return audience; + } + + public void setAudience(String audience) { + this.audience = audience; + } + + public String getClientAuthentication() { + return clientAuthentication; + } + + public void setClientAuthentication(String clientAuthentication) { + this.clientAuthentication = clientAuthentication; + } + + @Override + public void setHeaders(final HttpHeaders headers) {} + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + if (!super.equals(o)) { + return false; + } + OAuthAuthentication that = (OAuthAuthentication) o; + return oauthTokenEndpoint.equals(that.oauthTokenEndpoint) + && clientId.equals(that.clientId) + && clientSecret.equals(that.clientSecret) + && audience.equals(that.audience) + && Objects.equals(grantType, that.grantType) + && clientAuthentication.equals(that.clientAuthentication) + && Objects.equals(scopes, that.scopes); + } + + @Override + public int hashCode() { + return Objects.hash( + super.hashCode(), + oauthTokenEndpoint, + clientId, + clientSecret, + audience, + grantType, + clientAuthentication, + scopes); + } + + @Override + public String toString() { + return "OAuthAuthentication{" + + "grantType='" + + grantType + + '\'' + + ", oauthTokenEndpoint='" + + oauthTokenEndpoint + + '\'' + + ", audience='" + + audience + + '\'' + + ", clientAuthentication='" + + clientAuthentication + + '\'' + + ", scopes='" + + scopes + + '\'' + + "} " + + super.toString(); + } +} diff --git a/connectors/graphql/src/main/java/io/camunda/connector/graphql/components/GsonComponentSupplier.java b/connectors/graphql/src/main/java/io/camunda/connector/graphql/components/GsonComponentSupplier.java new file mode 100644 index 0000000000..217e2dd8e1 --- /dev/null +++ b/connectors/graphql/src/main/java/io/camunda/connector/graphql/components/GsonComponentSupplier.java @@ -0,0 +1,44 @@ +/* + * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH + * under one or more contributor license agreements. Licensed under a proprietary license. + * See the License.txt file for more information. You may not use this file + * except in compliance with the proprietary license. + */ +package io.camunda.connector.graphql.components; + +import com.google.api.client.json.gson.GsonFactory; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.ToNumberPolicy; +import com.google.gson.typeadapters.RuntimeTypeAdapterFactory; +import io.camunda.connector.graphql.auth.Authentication; +import io.camunda.connector.graphql.auth.BasicAuthentication; +import io.camunda.connector.graphql.auth.BearerAuthentication; +import io.camunda.connector.graphql.auth.NoAuthentication; +import io.camunda.connector.graphql.auth.OAuthAuthentication; + +public class GsonComponentSupplier { + + private static final GsonFactory GSON_FACTORY = new GsonFactory(); + private static final Gson GSON = + new GsonBuilder() + .serializeNulls() + .setObjectToNumberStrategy(ToNumberPolicy.LONG_OR_DOUBLE) + .registerTypeAdapterFactory( + RuntimeTypeAdapterFactory.of(Authentication.class, "type") + .registerSubtype(NoAuthentication.class, "noAuth") + .registerSubtype(BasicAuthentication.class, "basic") + .registerSubtype(BearerAuthentication.class, "bearer") + .registerSubtype(OAuthAuthentication.class, "oauth-client-credentials-flow")) + .create(); + + private GsonComponentSupplier() {} + + public static Gson gsonInstance() { + return GSON; + } + + public static GsonFactory gsonFactoryInstance() { + return GSON_FACTORY; + } +} diff --git a/connectors/graphql/src/main/java/io/camunda/connector/graphql/components/HttpTransportComponentSupplier.java b/connectors/graphql/src/main/java/io/camunda/connector/graphql/components/HttpTransportComponentSupplier.java new file mode 100644 index 0000000000..df07e6a26d --- /dev/null +++ b/connectors/graphql/src/main/java/io/camunda/connector/graphql/components/HttpTransportComponentSupplier.java @@ -0,0 +1,27 @@ +/* + * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH + * under one or more contributor license agreements. Licensed under a proprietary license. + * See the License.txt file for more information. You may not use this file + * except in compliance with the proprietary license. + */ +package io.camunda.connector.graphql.components; + +import com.google.api.client.http.HttpRequestFactory; +import com.google.api.client.http.HttpTransport; +import com.google.api.client.http.apache.v2.ApacheHttpTransport; +import com.google.api.client.json.JsonObjectParser; + +public class HttpTransportComponentSupplier { + + private HttpTransportComponentSupplier() {} + + private static final HttpTransport HTTP_TRANSPORT = new ApacheHttpTransport(); + private static final HttpRequestFactory REQUEST_FACTORY = + HTTP_TRANSPORT.createRequestFactory( + request -> + request.setParser(new JsonObjectParser(GsonComponentSupplier.gsonFactoryInstance()))); + + public static HttpRequestFactory httpRequestFactoryInstance() { + return REQUEST_FACTORY; + } +} diff --git a/connectors/graphql/src/main/java/io/camunda/connector/graphql/constants/Constants.java b/connectors/graphql/src/main/java/io/camunda/connector/graphql/constants/Constants.java new file mode 100644 index 0000000000..1f6e18d7d0 --- /dev/null +++ b/connectors/graphql/src/main/java/io/camunda/connector/graphql/constants/Constants.java @@ -0,0 +1,22 @@ +/* + * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH + * under one or more contributor license agreements. Licensed under a proprietary license. + * See the License.txt file for more information. You may not use this file + * except in compliance with the proprietary license. + */ +package io.camunda.connector.graphql.constants; + +public class Constants { + public static final String GRANT_TYPE = "grant_type"; + public static final String CLIENT_ID = "client_id"; + public static final String CLIENT_SECRET = "client_secret"; + public static final String AUDIENCE = "audience"; + public static final String SCOPE = "scope"; + public static final String ACCESS_TOKEN = "access_token"; + public static final String BASIC_AUTH_HEADER = "basicAuthHeader"; + public static final String CREDENTIALS_BODY = "credentialsBody"; + public static final String POST = "POST"; + public static final String APPLICATION_JSON_CHARSET_UTF_8 = "application/json; charset=UTF-8"; + public static final String APPLICATION_X_WWW_FORM_URLENCODED = + "application/x-www-form-urlencoded"; +} diff --git a/connectors/graphql/src/main/java/io/camunda/connector/graphql/model/ErrorResponse.java b/connectors/graphql/src/main/java/io/camunda/connector/graphql/model/ErrorResponse.java new file mode 100644 index 0000000000..f2682a0ce1 --- /dev/null +++ b/connectors/graphql/src/main/java/io/camunda/connector/graphql/model/ErrorResponse.java @@ -0,0 +1,53 @@ +/* + * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH + * under one or more contributor license agreements. Licensed under a proprietary license. + * See the License.txt file for more information. You may not use this file + * except in compliance with the proprietary license. + */ +package io.camunda.connector.graphql.model; + +import java.util.Objects; + +public class ErrorResponse { + private String error; + + private String errorCode; + + public String getError() { + return error; + } + + public void setError(final String error) { + this.error = error; + } + + public String getErrorCode() { + return errorCode; + } + + public void setErrorCode(String errorCode) { + this.errorCode = errorCode; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + ErrorResponse that = (ErrorResponse) o; + return error.equals(that.error) && errorCode.equals(that.errorCode); + } + + @Override + public int hashCode() { + return Objects.hash(error, errorCode); + } + + @Override + public String toString() { + return "ErrorResponse{" + "error='" + error + '\'' + ", errorCode='" + errorCode + '\'' + '}'; + } +} diff --git a/connectors/graphql/src/main/java/io/camunda/connector/graphql/model/GraphQLRequest.java b/connectors/graphql/src/main/java/io/camunda/connector/graphql/model/GraphQLRequest.java new file mode 100644 index 0000000000..a684bd6e2c --- /dev/null +++ b/connectors/graphql/src/main/java/io/camunda/connector/graphql/model/GraphQLRequest.java @@ -0,0 +1,128 @@ +/* + * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH + * under one or more contributor license agreements. Licensed under a proprietary license. + * See the License.txt file for more information. You may not use this file + * except in compliance with the proprietary license. + */ +package io.camunda.connector.graphql.model; + +import io.camunda.connector.api.annotation.Secret; +import io.camunda.connector.graphql.auth.Authentication; +import java.util.Map; +import java.util.Objects; +import javax.validation.Valid; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.Pattern; + +public class GraphQLRequest { + + @NotBlank @Secret private String method; + + @NotBlank + @Pattern(regexp = "^(http://|https://|secrets).*$") + @Secret + private String url; + + @Valid @Secret private Authentication authentication; + @NotBlank @Secret private String query; + @Secret private Map variables; + + @Pattern(regexp = "^([0-9]*$)|(secrets.*$)") + @Secret + private String connectionTimeoutInSeconds; + + public boolean hasAuthentication() { + return authentication != null; + } + + public boolean hasQuery() { + return query != null; + } + + public String getMethod() { + return method; + } + + public void setMethod(String method) { + this.method = method; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public Authentication getAuthentication() { + return authentication; + } + + public void setAuthentication(Authentication authentication) { + this.authentication = authentication; + } + + public String getQuery() { + return query; + } + + public void setQuery(String query) { + this.query = query; + } + + public Map getVariables() { + return variables; + } + + public void setVariables(Map variables) { + this.variables = variables; + } + + public String getConnectionTimeoutInSeconds() { + return connectionTimeoutInSeconds; + } + + public void setConnectionTimeoutInSeconds(String connectionTimeoutInSeconds) { + this.connectionTimeoutInSeconds = connectionTimeoutInSeconds; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + GraphQLRequest that = (GraphQLRequest) o; + return method.equals(that.method) + && url.equals(that.url) + && authentication.equals(that.authentication) + && query.equals(that.query) + && Objects.equals(variables, that.variables) + && Objects.equals(connectionTimeoutInSeconds, that.connectionTimeoutInSeconds); + } + + @Override + public int hashCode() { + return Objects.hash(method, url, authentication, query, variables, connectionTimeoutInSeconds); + } + + @Override + public String toString() { + return "GraphQLRequest{" + + "method='" + + method + + '\'' + + ", url='" + + url + + '\'' + + ", authentication=" + + authentication + + ", query=" + + query + + ", variables=" + + variables + + ", connectionTimeoutInSeconds='" + + connectionTimeoutInSeconds + + '\'' + + '}'; + } +} diff --git a/connectors/graphql/src/main/java/io/camunda/connector/graphql/model/GraphQLResult.java b/connectors/graphql/src/main/java/io/camunda/connector/graphql/model/GraphQLResult.java new file mode 100644 index 0000000000..ea03752e42 --- /dev/null +++ b/connectors/graphql/src/main/java/io/camunda/connector/graphql/model/GraphQLResult.java @@ -0,0 +1,65 @@ +/* + * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH + * under one or more contributor license agreements. Licensed under a proprietary license. + * See the License.txt file for more information. You may not use this file + * except in compliance with the proprietary license. + */ +package io.camunda.connector.graphql.model; + +import com.google.common.base.Objects; +import java.util.Map; + +public class GraphQLResult { + + private int status; + private Map headers; + private Object body; + + public int getStatus() { + return status; + } + + public void setStatus(int status) { + this.status = status; + } + + public Map getHeaders() { + return headers; + } + + public void setHeaders(Map headers) { + this.headers = headers; + } + + public Object getBody() { + return body; + } + + public void setBody(Object body) { + this.body = body; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + GraphQLResult that = (GraphQLResult) o; + return status == that.status + && com.google.common.base.Objects.equal(headers, that.headers) + && com.google.common.base.Objects.equal(body, that.body); + } + + @Override + public int hashCode() { + return Objects.hashCode(status, headers, body); + } + + @Override + public String toString() { + return "GraphQLResult{" + "status=" + status + ", headers=" + headers + ", body=" + body + '}'; + } +} diff --git a/connectors/graphql/src/main/resources/META-INF/services/io.camunda.connector.api.outbound.OutboundConnectorFunction b/connectors/graphql/src/main/resources/META-INF/services/io.camunda.connector.api.outbound.OutboundConnectorFunction new file mode 100644 index 0000000000..3321632136 --- /dev/null +++ b/connectors/graphql/src/main/resources/META-INF/services/io.camunda.connector.api.outbound.OutboundConnectorFunction @@ -0,0 +1 @@ +io.camunda.connector.graphql.GraphQLFunction \ No newline at end of file diff --git a/connectors/http-json/src/main/java/io/camunda/connector/http/HttpJsonFunction.java b/connectors/http-json/src/main/java/io/camunda/connector/http/HttpJsonFunction.java index ce0c26a491..b6c7d2361e 100644 --- a/connectors/http-json/src/main/java/io/camunda/connector/http/HttpJsonFunction.java +++ b/connectors/http-json/src/main/java/io/camunda/connector/http/HttpJsonFunction.java @@ -80,7 +80,7 @@ public class HttpJsonFunction implements OutboundConnectorFunction { private final GsonFactory gsonFactory; private final HttpRequestFactory requestFactory; - private final String proxyFunctionUrl; + private String proxyFunctionUrl; private final OAuth2Credentials proxyCredentials; public HttpJsonFunction() { @@ -115,6 +115,7 @@ public Object execute(final OutboundConnectorContext context) throws IOException context.validate(request); context.replaceSecrets(request); + proxyFunctionUrl = null; if (proxyFunctionUrl != null) { return executeRequestViaProxy(request); } else { From 949d43e1a1672b4349cf326ccc2278c195d6979d Mon Sep 17 00:00:00 2001 From: markfarkas-camunda Date: Mon, 23 Jan 2023 19:06:35 +0100 Subject: [PATCH 04/18] feat(graphql): implement POST method and variable handling --- .../element-templates/graphql-connector.json | 3 +-- .../connector/graphql/GraphQLFunction.java | 23 ++++++++++++------- .../graphql/model/GraphQLRequest.java | 6 ++--- .../connector/http/HttpJsonFunction.java | 3 +-- 4 files changed, 20 insertions(+), 15 deletions(-) diff --git a/connectors/graphql/element-templates/graphql-connector.json b/connectors/graphql/element-templates/graphql-connector.json index fc43b75185..46a47c3b90 100644 --- a/connectors/graphql/element-templates/graphql-connector.json +++ b/connectors/graphql/element-templates/graphql-connector.json @@ -125,7 +125,6 @@ "description": "See documentation", "group": "graphql", "type": "Text", - "feel": "optional", "binding": { "type": "zeebe:input", "name": "query" @@ -140,7 +139,7 @@ "description": "Variables to add to the query or mutation. See documentation", "group": "graphql", "type": "Text", - "feel": "optional", + "feel": "required", "binding": { "type": "zeebe:input", "name": "variables" diff --git a/connectors/graphql/src/main/java/io/camunda/connector/graphql/GraphQLFunction.java b/connectors/graphql/src/main/java/io/camunda/connector/graphql/GraphQLFunction.java index 3075720801..6d9f347111 100644 --- a/connectors/graphql/src/main/java/io/camunda/connector/graphql/GraphQLFunction.java +++ b/connectors/graphql/src/main/java/io/camunda/connector/graphql/GraphQLFunction.java @@ -28,7 +28,6 @@ import io.camunda.connector.graphql.components.GsonComponentSupplier; import io.camunda.connector.graphql.components.HttpTransportComponentSupplier; import io.camunda.connector.graphql.constants.Constants; -import io.camunda.connector.graphql.model.ErrorResponse; import io.camunda.connector.graphql.model.GraphQLRequest; import io.camunda.connector.graphql.model.GraphQLResult; import java.io.IOException; @@ -109,20 +108,19 @@ protected HttpRequest createRequest(final GraphQLRequest request, String bearerT final GenericUrl genericUrl = new GenericUrl(request.getUrl()); HttpContent content = null; final HttpHeaders headers = createHeaders(request, bearerToken); - final Map query = new HashMap<>(); String escapedQuery = request.getQuery() - //.replaceAll("\\s+","") .replace("\\n", ""); if(Constants.POST.equalsIgnoreCase(method)) { - // TODO : make this GraphQL compliant and more sophisticated - content = new JsonHttpContent(gsonFactory, escapedQuery); + content = constructBodyForPost(escapedQuery, request.getVariables()); } else { - // TODO : add variables + final Map query = new HashMap<>(); query.put("query", escapedQuery); + if(request.getVariables() != null) { + query.put("variables", gson.toJsonTree(request.getVariables()).toString()); + } + genericUrl.putAll(query); } - genericUrl.putAll(query); - final var httpRequest = requestFactory.buildRequest(method, genericUrl, content); httpRequest.setFollowRedirects(false); setTimeout(request, httpRequest); @@ -131,6 +129,15 @@ protected HttpRequest createRequest(final GraphQLRequest request, String bearerT return httpRequest; } + private JsonHttpContent constructBodyForPost(String escapedQuery, Object escapedVariables) { + final Map body = new HashMap<>(); + body.put("query", escapedQuery); + if(escapedVariables != null) { + body.put("variables", escapedVariables); + } + return new JsonHttpContent(gsonFactory, body); + } + protected HttpHeaders createHeaders(final GraphQLRequest request, String bearerToken) { final HttpHeaders httpHeaders = new HttpHeaders(); if (Constants.POST.equalsIgnoreCase(request.getMethod())) { diff --git a/connectors/graphql/src/main/java/io/camunda/connector/graphql/model/GraphQLRequest.java b/connectors/graphql/src/main/java/io/camunda/connector/graphql/model/GraphQLRequest.java index a684bd6e2c..a19753bf0c 100644 --- a/connectors/graphql/src/main/java/io/camunda/connector/graphql/model/GraphQLRequest.java +++ b/connectors/graphql/src/main/java/io/camunda/connector/graphql/model/GraphQLRequest.java @@ -25,7 +25,7 @@ public class GraphQLRequest { @Valid @Secret private Authentication authentication; @NotBlank @Secret private String query; - @Secret private Map variables; + @Secret private Object variables; @Pattern(regexp = "^([0-9]*$)|(secrets.*$)") @Secret @@ -71,11 +71,11 @@ public void setQuery(String query) { this.query = query; } - public Map getVariables() { + public Object getVariables() { return variables; } - public void setVariables(Map variables) { + public void setVariables(Object variables) { this.variables = variables; } diff --git a/connectors/http-json/src/main/java/io/camunda/connector/http/HttpJsonFunction.java b/connectors/http-json/src/main/java/io/camunda/connector/http/HttpJsonFunction.java index b6c7d2361e..ce0c26a491 100644 --- a/connectors/http-json/src/main/java/io/camunda/connector/http/HttpJsonFunction.java +++ b/connectors/http-json/src/main/java/io/camunda/connector/http/HttpJsonFunction.java @@ -80,7 +80,7 @@ public class HttpJsonFunction implements OutboundConnectorFunction { private final GsonFactory gsonFactory; private final HttpRequestFactory requestFactory; - private String proxyFunctionUrl; + private final String proxyFunctionUrl; private final OAuth2Credentials proxyCredentials; public HttpJsonFunction() { @@ -115,7 +115,6 @@ public Object execute(final OutboundConnectorContext context) throws IOException context.validate(request); context.replaceSecrets(request); - proxyFunctionUrl = null; if (proxyFunctionUrl != null) { return executeRequestViaProxy(request); } else { From d9afadfcbbef0ed5b88a6c4b7e361f792f91386f Mon Sep 17 00:00:00 2001 From: markfarkas-camunda Date: Wed, 25 Jan 2023 22:34:53 +0100 Subject: [PATCH 05/18] fix(graphql): additional character escape, and cleanup --- .../connector/graphql/GraphQLFunction.java | 17 +++++++---------- .../connector/graphql/model/GraphQLRequest.java | 1 - 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/connectors/graphql/src/main/java/io/camunda/connector/graphql/GraphQLFunction.java b/connectors/graphql/src/main/java/io/camunda/connector/graphql/GraphQLFunction.java index 6d9f347111..802ac978d8 100644 --- a/connectors/graphql/src/main/java/io/camunda/connector/graphql/GraphQLFunction.java +++ b/connectors/graphql/src/main/java/io/camunda/connector/graphql/GraphQLFunction.java @@ -76,7 +76,6 @@ public GraphQLFunction( @Override public Object execute(OutboundConnectorContext context) throws Exception { - //var connectorRequest = context.getVariablesAsType(GraphQLRequest.class); final var json = context.getVariables(); final var connectorRequest = gson.fromJson(json, GraphQLRequest.class); context.validate(connectorRequest); @@ -108,14 +107,13 @@ protected HttpRequest createRequest(final GraphQLRequest request, String bearerT final GenericUrl genericUrl = new GenericUrl(request.getUrl()); HttpContent content = null; final HttpHeaders headers = createHeaders(request, bearerToken); - String escapedQuery = request.getQuery() - .replace("\\n", ""); - if(Constants.POST.equalsIgnoreCase(method)) { + String escapedQuery = request.getQuery().replace("\\n", "").replace("\\\"", "\""); + if (Constants.POST.equalsIgnoreCase(method)) { content = constructBodyForPost(escapedQuery, request.getVariables()); } else { final Map query = new HashMap<>(); query.put("query", escapedQuery); - if(request.getVariables() != null) { + if (request.getVariables() != null) { query.put("variables", gson.toJsonTree(request.getVariables()).toString()); } genericUrl.putAll(query); @@ -129,11 +127,11 @@ protected HttpRequest createRequest(final GraphQLRequest request, String bearerT return httpRequest; } - private JsonHttpContent constructBodyForPost(String escapedQuery, Object escapedVariables) { + private JsonHttpContent constructBodyForPost(String escapedQuery, Object variables) { final Map body = new HashMap<>(); body.put("query", escapedQuery); - if(escapedVariables != null) { - body.put("variables", escapedVariables); + if (variables != null) { + body.put("variables", variables); } return new JsonHttpContent(gsonFactory, body); } @@ -163,8 +161,7 @@ protected String extractAccessToken(HttpResponse oauthResponse) throws IOExcepti return null; } - protected HttpResponse executeHttpRequest(HttpRequest externalRequest) - throws IOException { + protected HttpResponse executeHttpRequest(HttpRequest externalRequest) throws IOException { try { return externalRequest.execute(); } catch (HttpResponseException httpResponseException) { diff --git a/connectors/graphql/src/main/java/io/camunda/connector/graphql/model/GraphQLRequest.java b/connectors/graphql/src/main/java/io/camunda/connector/graphql/model/GraphQLRequest.java index a19753bf0c..a97e2e586e 100644 --- a/connectors/graphql/src/main/java/io/camunda/connector/graphql/model/GraphQLRequest.java +++ b/connectors/graphql/src/main/java/io/camunda/connector/graphql/model/GraphQLRequest.java @@ -8,7 +8,6 @@ import io.camunda.connector.api.annotation.Secret; import io.camunda.connector.graphql.auth.Authentication; -import java.util.Map; import java.util.Objects; import javax.validation.Valid; import javax.validation.constraints.NotBlank; From 3400db743010df2434a78ab8f81234321d801cbe Mon Sep 17 00:00:00 2001 From: markfarkas-camunda Date: Thu, 26 Jan 2023 18:25:45 +0100 Subject: [PATCH 06/18] feat(graphql): modify application.properties, code restructure --- .../src/main/resources/application.properties | 18 +- .../connector/graphql/GraphQLFunction.java | 177 +----------------- .../services/AuthenticationService.java | 95 ++++++++++ .../graphql/services/HTTPService.java | 146 +++++++++++++++ .../connector/graphql/utils/Timeout.java | 25 +++ 5 files changed, 290 insertions(+), 171 deletions(-) create mode 100644 connectors/graphql/src/main/java/io/camunda/connector/graphql/services/AuthenticationService.java create mode 100644 connectors/graphql/src/main/java/io/camunda/connector/graphql/services/HTTPService.java create mode 100644 connectors/graphql/src/main/java/io/camunda/connector/graphql/utils/Timeout.java diff --git a/bundle/mvn/default-bundle/src/main/resources/application.properties b/bundle/mvn/default-bundle/src/main/resources/application.properties index 7ad85d94e1..56f24bd938 100644 --- a/bundle/mvn/default-bundle/src/main/resources/application.properties +++ b/bundle/mvn/default-bundle/src/main/resources/application.properties @@ -1,5 +1,15 @@ -camunda.connector.polling.enabled=true -camunda.connector.polling.interval=5000 +#camunda.connector.polling.enabled=true +#camunda.connector.polling.interval=5000 -camunda.connector.webhook.enabled=true -#spring.main.web-application-type=none \ No newline at end of file +#camunda.connector.webhook.enabled=true +##spring.main.web-application-type=none + +# TODO : revisit this +# Test properties for localhost +server.port=9898 +zeebe.broker.gateway-address=localhost:26500 +zeebe.client.security.plaintext=true + +management.server.port=9080 +management.context-path=/actuator +management.endpoints.web.exposure.include=metrics,health,prometheus \ No newline at end of file diff --git a/connectors/graphql/src/main/java/io/camunda/connector/graphql/GraphQLFunction.java b/connectors/graphql/src/main/java/io/camunda/connector/graphql/GraphQLFunction.java index 802ac978d8..350591a3d6 100644 --- a/connectors/graphql/src/main/java/io/camunda/connector/graphql/GraphQLFunction.java +++ b/connectors/graphql/src/main/java/io/camunda/connector/graphql/GraphQLFunction.java @@ -6,38 +6,22 @@ */ package io.camunda.connector.graphql; -import static org.apache.http.entity.ContentType.APPLICATION_JSON; - -import com.google.api.client.http.GenericUrl; -import com.google.api.client.http.HttpContent; -import com.google.api.client.http.HttpHeaders; import com.google.api.client.http.HttpRequest; import com.google.api.client.http.HttpRequestFactory; import com.google.api.client.http.HttpResponse; -import com.google.api.client.http.HttpResponseException; -import com.google.api.client.http.UrlEncodedContent; -import com.google.api.client.http.json.JsonHttpContent; import com.google.api.client.json.gson.GsonFactory; import com.google.gson.Gson; -import com.google.gson.JsonObject; import io.camunda.connector.api.annotation.OutboundConnector; -import io.camunda.connector.api.error.ConnectorException; import io.camunda.connector.api.outbound.OutboundConnectorContext; import io.camunda.connector.api.outbound.OutboundConnectorFunction; import io.camunda.connector.graphql.auth.OAuthAuthentication; import io.camunda.connector.graphql.components.GsonComponentSupplier; import io.camunda.connector.graphql.components.HttpTransportComponentSupplier; -import io.camunda.connector.graphql.constants.Constants; import io.camunda.connector.graphql.model.GraphQLRequest; import io.camunda.connector.graphql.model.GraphQLResult; +import io.camunda.connector.graphql.services.AuthenticationService; +import io.camunda.connector.graphql.services.HTTPService; import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.Reader; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.concurrent.TimeUnit; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -83,163 +67,22 @@ public Object execute(OutboundConnectorContext context) throws Exception { return executeGraphQLConnector(connectorRequest); } - ///////////////////////////////////////////////////////////////////////////////////// private GraphQLResult executeGraphQLConnector(final GraphQLRequest connectorRequest) throws IOException { // connector logic LOGGER.info("Executing graphql connector with request {}", connectorRequest); + HTTPService httpService = HTTPService.getInstance(gson, requestFactory, gsonFactory); + AuthenticationService authService = AuthenticationService.getInstance(gson, requestFactory); String bearerToken = null; if (connectorRequest.getAuthentication() != null && connectorRequest.getAuthentication() instanceof OAuthAuthentication) { - final HttpRequest oauthRequest = createOAuthRequest(connectorRequest); - final HttpResponse oauthResponse = executeHttpRequest(oauthRequest); - bearerToken = extractAccessToken(oauthResponse); - } - - final HttpRequest httpRequest = createRequest(connectorRequest, bearerToken); - HttpResponse httpResponse = executeHttpRequest(httpRequest); - return toHttpJsonResponse(httpResponse); - } - - protected HttpRequest createRequest(final GraphQLRequest request, String bearerToken) - throws IOException { - final String method = request.getMethod().toUpperCase(); - final GenericUrl genericUrl = new GenericUrl(request.getUrl()); - HttpContent content = null; - final HttpHeaders headers = createHeaders(request, bearerToken); - String escapedQuery = request.getQuery().replace("\\n", "").replace("\\\"", "\""); - if (Constants.POST.equalsIgnoreCase(method)) { - content = constructBodyForPost(escapedQuery, request.getVariables()); - } else { - final Map query = new HashMap<>(); - query.put("query", escapedQuery); - if (request.getVariables() != null) { - query.put("variables", gson.toJsonTree(request.getVariables()).toString()); - } - genericUrl.putAll(query); - } - - final var httpRequest = requestFactory.buildRequest(method, genericUrl, content); - httpRequest.setFollowRedirects(false); - setTimeout(request, httpRequest); - httpRequest.setHeaders(headers); - - return httpRequest; - } - - private JsonHttpContent constructBodyForPost(String escapedQuery, Object variables) { - final Map body = new HashMap<>(); - body.put("query", escapedQuery); - if (variables != null) { - body.put("variables", variables); + final HttpRequest oauthRequest = authService.createOAuthRequest(connectorRequest); + final HttpResponse oauthResponse = httpService.executeHttpRequest(oauthRequest); + bearerToken = authService.extractAccessToken(oauthResponse); } - return new JsonHttpContent(gsonFactory, body); - } - protected HttpHeaders createHeaders(final GraphQLRequest request, String bearerToken) { - final HttpHeaders httpHeaders = new HttpHeaders(); - if (Constants.POST.equalsIgnoreCase(request.getMethod())) { - httpHeaders.setContentType(APPLICATION_JSON.getMimeType()); - } - if (request.hasAuthentication()) { - if (bearerToken != null && !bearerToken.isEmpty()) { - httpHeaders.setAuthorization("Bearer " + bearerToken); - } - request.getAuthentication().setHeaders(httpHeaders); - } - return httpHeaders; - } - - protected String extractAccessToken(HttpResponse oauthResponse) throws IOException { - String oauthResponseStr = oauthResponse.parseAsString(); - if (oauthResponseStr != null && !oauthResponseStr.isEmpty()) { - JsonObject jsonObject = gson.fromJson(oauthResponseStr, JsonObject.class); - if (jsonObject.get(Constants.ACCESS_TOKEN) != null) { - return jsonObject.get(Constants.ACCESS_TOKEN).getAsString(); - } - } - return null; - } - - protected HttpResponse executeHttpRequest(HttpRequest externalRequest) throws IOException { - try { - return externalRequest.execute(); - } catch (HttpResponseException httpResponseException) { - var errorCode = String.valueOf(httpResponseException.getStatusCode()); - var errorMessage = httpResponseException.getMessage(); - throw new ConnectorException(errorCode, errorMessage, httpResponseException); - } - } - - protected HttpRequest createOAuthRequest(GraphQLRequest request) throws IOException { - OAuthAuthentication authentication = (OAuthAuthentication) request.getAuthentication(); - - final GenericUrl genericUrl = new GenericUrl(authentication.getOauthTokenEndpoint()); - Map data = getDataForAuthRequestBody(authentication); - HttpContent content = new UrlEncodedContent(data); - final String method = Constants.POST; - final var httpRequest = requestFactory.buildRequest(method, genericUrl, content); - httpRequest.setFollowRedirects(false); - setTimeout(request, httpRequest); - HttpHeaders headers = new HttpHeaders(); - - if (authentication.getClientAuthentication().equals(Constants.BASIC_AUTH_HEADER)) { - headers.setBasicAuthentication( - authentication.getClientId(), authentication.getClientSecret()); - } - headers.setContentType(Constants.APPLICATION_X_WWW_FORM_URLENCODED); - httpRequest.setHeaders(headers); - return httpRequest; - } - - protected void setTimeout(GraphQLRequest request, HttpRequest httpRequest) { - if (request.getConnectionTimeoutInSeconds() != null) { - long connectionTimeout = - TimeUnit.SECONDS.toMillis(Long.parseLong(request.getConnectionTimeoutInSeconds())); - int intConnectionTimeout = Math.toIntExact(connectionTimeout); - httpRequest.setConnectTimeout(intConnectionTimeout); - httpRequest.setReadTimeout(intConnectionTimeout); - httpRequest.setWriteTimeout(intConnectionTimeout); - } - } - - private static Map getDataForAuthRequestBody(OAuthAuthentication authentication) { - Map data = new HashMap<>(); - data.put(Constants.GRANT_TYPE, authentication.getGrantType()); - data.put(Constants.AUDIENCE, authentication.getAudience()); - data.put(Constants.SCOPE, authentication.getScopes()); - - if (authentication.getClientAuthentication().equals(Constants.CREDENTIALS_BODY)) { - data.put(Constants.CLIENT_ID, authentication.getClientId()); - data.put(Constants.CLIENT_SECRET, authentication.getClientSecret()); - } - return data; - } - - protected GraphQLResult toHttpJsonResponse(final HttpResponse externalResponse) { - final GraphQLResult graphQLResult = new GraphQLResult(); - graphQLResult.setStatus(externalResponse.getStatusCode()); - final Map headers = new HashMap<>(); - externalResponse - .getHeaders() - .forEach( - (k, v) -> { - if (v instanceof List && ((List) v).size() == 1) { - headers.put(k, ((List) v).get(0)); - } else { - headers.put(k, v); - } - }); - graphQLResult.setHeaders(headers); - try (InputStream content = externalResponse.getContent(); - Reader reader = new InputStreamReader(content)) { - final Object body = gson.fromJson(reader, Object.class); - if (body != null) { - graphQLResult.setBody(body); - } - } catch (final Exception e) { - LOGGER.error("Failed to parse external response: {}", externalResponse, e); - } - return graphQLResult; + final HttpRequest httpRequest = httpService.createRequest(connectorRequest, bearerToken); + HttpResponse httpResponse = httpService.executeHttpRequest(httpRequest); + return httpService.toHttpJsonResponse(httpResponse); } } diff --git a/connectors/graphql/src/main/java/io/camunda/connector/graphql/services/AuthenticationService.java b/connectors/graphql/src/main/java/io/camunda/connector/graphql/services/AuthenticationService.java new file mode 100644 index 0000000000..97f0bfcc24 --- /dev/null +++ b/connectors/graphql/src/main/java/io/camunda/connector/graphql/services/AuthenticationService.java @@ -0,0 +1,95 @@ +/* + * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH + * under one or more contributor license agreements. Licensed under a proprietary license. + * See the License.txt file for more information. You may not use this file + * except in compliance with the proprietary license. + */ +package io.camunda.connector.graphql.services; + +import static io.camunda.connector.graphql.utils.Timeout.setTimeout; + +import com.google.api.client.http.GenericUrl; +import com.google.api.client.http.HttpContent; +import com.google.api.client.http.HttpHeaders; +import com.google.api.client.http.HttpRequest; +import com.google.api.client.http.HttpRequestFactory; +import com.google.api.client.http.HttpResponse; +import com.google.api.client.http.UrlEncodedContent; +import com.google.gson.Gson; +import com.google.gson.JsonObject; +import io.camunda.connector.graphql.auth.OAuthAuthentication; +import io.camunda.connector.graphql.constants.Constants; +import io.camunda.connector.graphql.model.GraphQLRequest; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class AuthenticationService { + + private static final Logger LOGGER = LoggerFactory.getLogger(AuthenticationService.class); + + private static AuthenticationService instance; + + private final Gson gson; + private final HttpRequestFactory requestFactory; + + private AuthenticationService(final Gson gson, final HttpRequestFactory requestFactory) { + this.gson = gson; + this.requestFactory = requestFactory; + } + + public static AuthenticationService getInstance( + final Gson gson, final HttpRequestFactory requestFactory) { + if (instance == null) { + instance = new AuthenticationService(gson, requestFactory); + } + return instance; + } + + public String extractAccessToken(HttpResponse oauthResponse) throws IOException { + String oauthResponseStr = oauthResponse.parseAsString(); + if (oauthResponseStr != null && !oauthResponseStr.isEmpty()) { + JsonObject jsonObject = gson.fromJson(oauthResponseStr, JsonObject.class); + if (jsonObject.get(Constants.ACCESS_TOKEN) != null) { + return jsonObject.get(Constants.ACCESS_TOKEN).getAsString(); + } + } + return null; + } + + public HttpRequest createOAuthRequest(GraphQLRequest request) throws IOException { + OAuthAuthentication authentication = (OAuthAuthentication) request.getAuthentication(); + + final GenericUrl genericUrl = new GenericUrl(authentication.getOauthTokenEndpoint()); + Map data = getDataForAuthRequestBody(authentication); + HttpContent content = new UrlEncodedContent(data); + final String method = Constants.POST; + final var httpRequest = requestFactory.buildRequest(method, genericUrl, content); + httpRequest.setFollowRedirects(false); + setTimeout(request, httpRequest); + HttpHeaders headers = new HttpHeaders(); + + if (authentication.getClientAuthentication().equals(Constants.BASIC_AUTH_HEADER)) { + headers.setBasicAuthentication( + authentication.getClientId(), authentication.getClientSecret()); + } + headers.setContentType(Constants.APPLICATION_X_WWW_FORM_URLENCODED); + httpRequest.setHeaders(headers); + return httpRequest; + } + + private static Map getDataForAuthRequestBody(OAuthAuthentication authentication) { + Map data = new HashMap<>(); + data.put(Constants.GRANT_TYPE, authentication.getGrantType()); + data.put(Constants.AUDIENCE, authentication.getAudience()); + data.put(Constants.SCOPE, authentication.getScopes()); + + if (authentication.getClientAuthentication().equals(Constants.CREDENTIALS_BODY)) { + data.put(Constants.CLIENT_ID, authentication.getClientId()); + data.put(Constants.CLIENT_SECRET, authentication.getClientSecret()); + } + return data; + } +} diff --git a/connectors/graphql/src/main/java/io/camunda/connector/graphql/services/HTTPService.java b/connectors/graphql/src/main/java/io/camunda/connector/graphql/services/HTTPService.java new file mode 100644 index 0000000000..da60ab280d --- /dev/null +++ b/connectors/graphql/src/main/java/io/camunda/connector/graphql/services/HTTPService.java @@ -0,0 +1,146 @@ +/* + * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH + * under one or more contributor license agreements. Licensed under a proprietary license. + * See the License.txt file for more information. You may not use this file + * except in compliance with the proprietary license. + */ +package io.camunda.connector.graphql.services; + +import static io.camunda.connector.graphql.utils.Timeout.setTimeout; +import static org.apache.http.entity.ContentType.APPLICATION_JSON; + +import com.google.api.client.http.GenericUrl; +import com.google.api.client.http.HttpContent; +import com.google.api.client.http.HttpHeaders; +import com.google.api.client.http.HttpRequest; +import com.google.api.client.http.HttpRequestFactory; +import com.google.api.client.http.HttpResponse; +import com.google.api.client.http.HttpResponseException; +import com.google.api.client.http.json.JsonHttpContent; +import com.google.api.client.json.gson.GsonFactory; +import com.google.gson.Gson; +import io.camunda.connector.api.error.ConnectorException; +import io.camunda.connector.graphql.constants.Constants; +import io.camunda.connector.graphql.model.GraphQLRequest; +import io.camunda.connector.graphql.model.GraphQLResult; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class HTTPService { + + private static final Logger LOGGER = LoggerFactory.getLogger(HTTPService.class); + + private final Gson gson; + private final GsonFactory gsonFactory; + private final HttpRequestFactory requestFactory; + + private static HTTPService instance; + + private HTTPService( + final Gson gson, final HttpRequestFactory requestFactory, final GsonFactory gsonFactory) { + this.gson = gson; + this.requestFactory = requestFactory; + this.gsonFactory = gsonFactory; + } + + public static HTTPService getInstance( + final Gson gson, final HttpRequestFactory requestFactory, final GsonFactory gsonFactory) { + if (instance == null) { + instance = new HTTPService(gson, requestFactory, gsonFactory); + } + return instance; + } + + public HttpRequest createRequest(final GraphQLRequest request, String bearerToken) + throws IOException { + final String method = request.getMethod().toUpperCase(); + final GenericUrl genericUrl = new GenericUrl(request.getUrl()); + HttpContent content = null; + final HttpHeaders headers = createHeaders(request, bearerToken); + String escapedQuery = request.getQuery().replace("\\n", "").replace("\\\"", "\""); + if (Constants.POST.equalsIgnoreCase(method)) { + content = constructBodyForPost(escapedQuery, request.getVariables()); + } else { + final Map query = new HashMap<>(); + query.put("query", escapedQuery); + if (request.getVariables() != null) { + query.put("variables", gson.toJsonTree(request.getVariables()).toString()); + } + genericUrl.putAll(query); + } + + final var httpRequest = requestFactory.buildRequest(method, genericUrl, content); + httpRequest.setFollowRedirects(false); + setTimeout(request, httpRequest); + httpRequest.setHeaders(headers); + + return httpRequest; + } + + private JsonHttpContent constructBodyForPost(String escapedQuery, Object variables) { + final Map body = new HashMap<>(); + body.put("query", escapedQuery); + if (variables != null) { + body.put("variables", variables); + } + return new JsonHttpContent(gsonFactory, body); + } + + public HttpHeaders createHeaders(final GraphQLRequest request, String bearerToken) { + final HttpHeaders httpHeaders = new HttpHeaders(); + if (Constants.POST.equalsIgnoreCase(request.getMethod())) { + httpHeaders.setContentType(APPLICATION_JSON.getMimeType()); + } + if (request.hasAuthentication()) { + if (bearerToken != null && !bearerToken.isEmpty()) { + httpHeaders.setAuthorization("Bearer " + bearerToken); + } + request.getAuthentication().setHeaders(httpHeaders); + } + return httpHeaders; + } + + public HttpResponse executeHttpRequest(HttpRequest externalRequest) throws IOException { + try { + return externalRequest.execute(); + } catch (HttpResponseException httpResponseException) { + var errorCode = String.valueOf(httpResponseException.getStatusCode()); + var errorMessage = httpResponseException.getMessage(); + throw new ConnectorException(errorCode, errorMessage, httpResponseException); + } + } + + public GraphQLResult toHttpJsonResponse(final HttpResponse externalResponse) { + final GraphQLResult graphQLResult = new GraphQLResult(); + graphQLResult.setStatus(externalResponse.getStatusCode()); + final Map headers = new HashMap<>(); + externalResponse + .getHeaders() + .forEach( + (k, v) -> { + if (v instanceof List && ((List) v).size() == 1) { + headers.put(k, ((List) v).get(0)); + } else { + headers.put(k, v); + } + }); + graphQLResult.setHeaders(headers); + try (InputStream content = externalResponse.getContent(); + Reader reader = new InputStreamReader(content)) { + final Object body = gson.fromJson(reader, Object.class); + if (body != null) { + graphQLResult.setBody(body); + } + } catch (final Exception e) { + LOGGER.error("Failed to parse external response: {}", externalResponse, e); + } + return graphQLResult; + } +} diff --git a/connectors/graphql/src/main/java/io/camunda/connector/graphql/utils/Timeout.java b/connectors/graphql/src/main/java/io/camunda/connector/graphql/utils/Timeout.java new file mode 100644 index 0000000000..3747de068d --- /dev/null +++ b/connectors/graphql/src/main/java/io/camunda/connector/graphql/utils/Timeout.java @@ -0,0 +1,25 @@ +/* + * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH + * under one or more contributor license agreements. Licensed under a proprietary license. + * See the License.txt file for more information. You may not use this file + * except in compliance with the proprietary license. + */ +package io.camunda.connector.graphql.utils; + +import com.google.api.client.http.HttpRequest; +import io.camunda.connector.graphql.model.GraphQLRequest; +import java.util.concurrent.TimeUnit; + +public class Timeout { + + public static void setTimeout(GraphQLRequest request, HttpRequest httpRequest) { + if (request.getConnectionTimeoutInSeconds() != null) { + long connectionTimeout = + TimeUnit.SECONDS.toMillis(Long.parseLong(request.getConnectionTimeoutInSeconds())); + int intConnectionTimeout = Math.toIntExact(connectionTimeout); + httpRequest.setConnectTimeout(intConnectionTimeout); + httpRequest.setReadTimeout(intConnectionTimeout); + httpRequest.setWriteTimeout(intConnectionTimeout); + } + } +} From 6a111bd8e12ded22a627be11e027c2bb54cef50f Mon Sep 17 00:00:00 2001 From: markfarkas-camunda Date: Fri, 27 Jan 2023 16:19:32 +0100 Subject: [PATCH 07/18] fix(graphql): make input variable names graphql specific --- connectors/graphql/connector.yml | 2 +- .../element-templates/graphql-connector.json | 12 ++++++------ .../camunda/connector/graphql/GraphQLFunction.java | 14 +++++++------- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/connectors/graphql/connector.yml b/connectors/graphql/connector.yml index 1ff9d8c068..dfbc7df7b5 100644 --- a/connectors/graphql/connector.yml +++ b/connectors/graphql/connector.yml @@ -1,3 +1,3 @@ -name: HTTPJSON +name: GRAPHQL type: io.camunda:connector-graphql:1 variables: [ url, method, authentication, variables, query, connectionTimeoutInSeconds ] \ No newline at end of file diff --git a/connectors/graphql/element-templates/graphql-connector.json b/connectors/graphql/element-templates/graphql-connector.json index 46a47c3b90..a383fdea44 100644 --- a/connectors/graphql/element-templates/graphql-connector.json +++ b/connectors/graphql/element-templates/graphql-connector.json @@ -100,7 +100,7 @@ ], "binding": { "type": "zeebe:input", - "name": "method" + "name": "graphql.method" } }, { @@ -110,7 +110,7 @@ "feel": "optional", "binding": { "type": "zeebe:input", - "name": "url" + "name": "graphql.url" }, "constraints": { "notEmpty": true, @@ -127,7 +127,7 @@ "type": "Text", "binding": { "type": "zeebe:input", - "name": "query" + "name": "graphql.query" }, "optional": false, "constraints": { @@ -142,7 +142,7 @@ "feel": "required", "binding": { "type": "zeebe:input", - "name": "variables" + "name": "graphql.variables" }, "optional": true }, @@ -153,7 +153,7 @@ "feel": "optional", "binding": { "type": "zeebe:input", - "name": "authentication.token" + "name": "graphql.authentication.token" }, "constraints": { "notEmpty": true @@ -317,7 +317,7 @@ "value": "20", "binding": { "type": "zeebe:input", - "name": "connectionTimeoutInSeconds" + "name": "graphql.connectionTimeoutInSeconds" }, "optional": true, "constraints": { diff --git a/connectors/graphql/src/main/java/io/camunda/connector/graphql/GraphQLFunction.java b/connectors/graphql/src/main/java/io/camunda/connector/graphql/GraphQLFunction.java index 350591a3d6..1a3e71fb65 100644 --- a/connectors/graphql/src/main/java/io/camunda/connector/graphql/GraphQLFunction.java +++ b/connectors/graphql/src/main/java/io/camunda/connector/graphql/GraphQLFunction.java @@ -28,12 +28,12 @@ @OutboundConnector( name = "GRAPHQL", inputVariables = { - "url", - "method", - "authentication", - "query", - "variables", - "connectionTimeoutInSeconds" + "graphql.url", + "graphql.method", + "graphql.authentication", + "graphql.query", + "graphql.variables", + "graphql.connectionTimeoutInSeconds" }, type = "io.camunda:connector-graphql:1") public class GraphQLFunction implements OutboundConnectorFunction { @@ -59,7 +59,7 @@ public GraphQLFunction( } @Override - public Object execute(OutboundConnectorContext context) throws Exception { + public Object execute(OutboundConnectorContext context) throws IOException { final var json = context.getVariables(); final var connectorRequest = gson.fromJson(json, GraphQLRequest.class); context.validate(connectorRequest); From 1ce9f746677b8e0df746eb06ba02bab468489459 Mon Sep 17 00:00:00 2001 From: markfarkas-camunda Date: Fri, 27 Jan 2023 17:34:16 +0100 Subject: [PATCH 08/18] fix(graphql): swap url and method in the element template --- .../element-templates/graphql-connector.json | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/connectors/graphql/element-templates/graphql-connector.json b/connectors/graphql/element-templates/graphql-connector.json index a383fdea44..a90c4d27d1 100644 --- a/connectors/graphql/element-templates/graphql-connector.json +++ b/connectors/graphql/element-templates/graphql-connector.json @@ -82,6 +82,23 @@ "name": "authentication.type" } }, + { + "label": "URL", + "group": "endpoint", + "type": "String", + "feel": "optional", + "binding": { + "type": "zeebe:input", + "name": "graphql.url" + }, + "constraints": { + "notEmpty": true, + "pattern": { + "value": "^(=|https?://).*", + "message": "Must be a http(s) URL." + } + } + }, { "id": "method", "label": "Method", @@ -103,23 +120,6 @@ "name": "graphql.method" } }, - { - "label": "URL", - "group": "endpoint", - "type": "String", - "feel": "optional", - "binding": { - "type": "zeebe:input", - "name": "graphql.url" - }, - "constraints": { - "notEmpty": true, - "pattern": { - "value": "^(=|https?://).*", - "message": "Must be a http(s) URL." - } - } - }, { "label": "Query/Mutation", "description": "See documentation", @@ -136,7 +136,7 @@ }, { "label": "Variables", - "description": "Variables to add to the query or mutation. See documentation", + "description": "Learn how to define variables", "group": "graphql", "type": "Text", "feel": "required", From 7a6ea75bd4275a59048aabc138142087403d6900 Mon Sep 17 00:00:00 2001 From: markfarkas-camunda Date: Tue, 31 Jan 2023 16:25:47 +0100 Subject: [PATCH 09/18] chore(graphql): add connectors-common-library to unify http behavior --- connectors/connectors-common-library/pom.xml | 60 +++++++ .../common}/auth/Authentication.java | 2 +- .../common}/auth/BasicAuthentication.java | 2 +- .../common}/auth/BearerAuthentication.java | 2 +- .../common}/auth/NoAuthentication.java | 2 +- .../common}/auth/OAuthAuthentication.java | 2 +- .../common}/constants/Constants.java | 2 +- .../connector/common/model/CommonRequest.java | 99 ++++++++++++ .../connector/common/model/CommonResult.java | 65 ++++++++ .../common}/model/ErrorResponse.java | 2 +- .../services/AuthenticationService.java | 12 +- .../common/services/HTTPService.java | 101 ++++++++++++ .../connector/common}/utils/Timeout.java | 6 +- connectors/graphql/connector.yml | 2 +- connectors/graphql/pom.xml | 7 + .../connector/graphql/GraphQLFunction.java | 82 ++++++++-- .../components/GsonComponentSupplier.java | 10 +- .../graphql/model/GraphQLRequest.java | 81 +--------- .../graphql/model/GraphQLResult.java | 58 +------ .../graphql/services/HTTPService.java | 146 ------------------ connectors/pom.xml | 1 + 21 files changed, 427 insertions(+), 317 deletions(-) create mode 100644 connectors/connectors-common-library/pom.xml rename connectors/{graphql/src/main/java/io/camunda/connector/graphql => connectors-common-library/src/main/java/io/camunda/connector/common}/auth/Authentication.java (95%) rename connectors/{graphql/src/main/java/io/camunda/connector/graphql => connectors-common-library/src/main/java/io/camunda/connector/common}/auth/BasicAuthentication.java (97%) rename connectors/{graphql/src/main/java/io/camunda/connector/graphql => connectors-common-library/src/main/java/io/camunda/connector/common}/auth/BearerAuthentication.java (97%) rename connectors/{graphql/src/main/java/io/camunda/connector/graphql => connectors-common-library/src/main/java/io/camunda/connector/common}/auth/NoAuthentication.java (94%) rename connectors/{graphql/src/main/java/io/camunda/connector/graphql => connectors-common-library/src/main/java/io/camunda/connector/common}/auth/OAuthAuthentication.java (98%) rename connectors/{graphql/src/main/java/io/camunda/connector/graphql => connectors-common-library/src/main/java/io/camunda/connector/common}/constants/Constants.java (95%) create mode 100644 connectors/connectors-common-library/src/main/java/io/camunda/connector/common/model/CommonRequest.java create mode 100644 connectors/connectors-common-library/src/main/java/io/camunda/connector/common/model/CommonResult.java rename connectors/{graphql/src/main/java/io/camunda/connector/graphql => connectors-common-library/src/main/java/io/camunda/connector/common}/model/ErrorResponse.java (96%) rename connectors/{graphql/src/main/java/io/camunda/connector/graphql => connectors-common-library/src/main/java/io/camunda/connector/common}/services/AuthenticationService.java (90%) create mode 100644 connectors/connectors-common-library/src/main/java/io/camunda/connector/common/services/HTTPService.java rename connectors/{graphql/src/main/java/io/camunda/connector/graphql => connectors-common-library/src/main/java/io/camunda/connector/common}/utils/Timeout.java (82%) delete mode 100644 connectors/graphql/src/main/java/io/camunda/connector/graphql/services/HTTPService.java diff --git a/connectors/connectors-common-library/pom.xml b/connectors/connectors-common-library/pom.xml new file mode 100644 index 0000000000..ae460c2b31 --- /dev/null +++ b/connectors/connectors-common-library/pom.xml @@ -0,0 +1,60 @@ + + 4.0.0 + + + io.camunda.connector + connector-function-parent + 0.15.0-SNAPSHOT + ../pom.xml + + + connectors-common-library + Common library for connectors + connectors-common-library + jar + + + + Camunda Platform Self-Managed Free Edition license + https://camunda.com/legal/terms/cloud-terms-and-conditions/camunda-cloud-self-managed-free-edition-terms/ + + + Camunda Platform Self-Managed Enterprise Edition license + + + + + + org.danilopianini + gson-extras + + + + com.google.http-client + google-http-client-gson + + + + com.google.http-client + google-http-client-apache-v2 + + + + com.google.auth + google-auth-library-oauth2-http + + + + org.apache.httpcomponents + httpcore + + + + org.slf4j + jcl-over-slf4j + + + + \ No newline at end of file diff --git a/connectors/graphql/src/main/java/io/camunda/connector/graphql/auth/Authentication.java b/connectors/connectors-common-library/src/main/java/io/camunda/connector/common/auth/Authentication.java similarity index 95% rename from connectors/graphql/src/main/java/io/camunda/connector/graphql/auth/Authentication.java rename to connectors/connectors-common-library/src/main/java/io/camunda/connector/common/auth/Authentication.java index a40fc7ef54..e1d4162550 100644 --- a/connectors/graphql/src/main/java/io/camunda/connector/graphql/auth/Authentication.java +++ b/connectors/connectors-common-library/src/main/java/io/camunda/connector/common/auth/Authentication.java @@ -4,7 +4,7 @@ * See the License.txt file for more information. You may not use this file * except in compliance with the proprietary license. */ -package io.camunda.connector.graphql.auth; +package io.camunda.connector.common.auth; import com.google.api.client.http.HttpHeaders; import com.google.common.base.Objects; diff --git a/connectors/graphql/src/main/java/io/camunda/connector/graphql/auth/BasicAuthentication.java b/connectors/connectors-common-library/src/main/java/io/camunda/connector/common/auth/BasicAuthentication.java similarity index 97% rename from connectors/graphql/src/main/java/io/camunda/connector/graphql/auth/BasicAuthentication.java rename to connectors/connectors-common-library/src/main/java/io/camunda/connector/common/auth/BasicAuthentication.java index f1c5fcea13..05f72df8cf 100644 --- a/connectors/graphql/src/main/java/io/camunda/connector/graphql/auth/BasicAuthentication.java +++ b/connectors/connectors-common-library/src/main/java/io/camunda/connector/common/auth/BasicAuthentication.java @@ -4,7 +4,7 @@ * See the License.txt file for more information. You may not use this file * except in compliance with the proprietary license. */ -package io.camunda.connector.graphql.auth; +package io.camunda.connector.common.auth; import com.google.api.client.http.HttpHeaders; import com.google.common.base.Objects; diff --git a/connectors/graphql/src/main/java/io/camunda/connector/graphql/auth/BearerAuthentication.java b/connectors/connectors-common-library/src/main/java/io/camunda/connector/common/auth/BearerAuthentication.java similarity index 97% rename from connectors/graphql/src/main/java/io/camunda/connector/graphql/auth/BearerAuthentication.java rename to connectors/connectors-common-library/src/main/java/io/camunda/connector/common/auth/BearerAuthentication.java index 364646d679..86a7bc7edc 100644 --- a/connectors/graphql/src/main/java/io/camunda/connector/graphql/auth/BearerAuthentication.java +++ b/connectors/connectors-common-library/src/main/java/io/camunda/connector/common/auth/BearerAuthentication.java @@ -4,7 +4,7 @@ * See the License.txt file for more information. You may not use this file * except in compliance with the proprietary license. */ -package io.camunda.connector.graphql.auth; +package io.camunda.connector.common.auth; import com.google.api.client.http.HttpHeaders; import com.google.common.base.Objects; diff --git a/connectors/graphql/src/main/java/io/camunda/connector/graphql/auth/NoAuthentication.java b/connectors/connectors-common-library/src/main/java/io/camunda/connector/common/auth/NoAuthentication.java similarity index 94% rename from connectors/graphql/src/main/java/io/camunda/connector/graphql/auth/NoAuthentication.java rename to connectors/connectors-common-library/src/main/java/io/camunda/connector/common/auth/NoAuthentication.java index dc066bb7bd..d263eb2420 100644 --- a/connectors/graphql/src/main/java/io/camunda/connector/graphql/auth/NoAuthentication.java +++ b/connectors/connectors-common-library/src/main/java/io/camunda/connector/common/auth/NoAuthentication.java @@ -4,7 +4,7 @@ * See the License.txt file for more information. You may not use this file * except in compliance with the proprietary license. */ -package io.camunda.connector.graphql.auth; +package io.camunda.connector.common.auth; import com.google.api.client.http.HttpHeaders; diff --git a/connectors/graphql/src/main/java/io/camunda/connector/graphql/auth/OAuthAuthentication.java b/connectors/connectors-common-library/src/main/java/io/camunda/connector/common/auth/OAuthAuthentication.java similarity index 98% rename from connectors/graphql/src/main/java/io/camunda/connector/graphql/auth/OAuthAuthentication.java rename to connectors/connectors-common-library/src/main/java/io/camunda/connector/common/auth/OAuthAuthentication.java index 01da7595a7..0aad50b18d 100644 --- a/connectors/graphql/src/main/java/io/camunda/connector/graphql/auth/OAuthAuthentication.java +++ b/connectors/connectors-common-library/src/main/java/io/camunda/connector/common/auth/OAuthAuthentication.java @@ -4,7 +4,7 @@ * See the License.txt file for more information. You may not use this file * except in compliance with the proprietary license. */ -package io.camunda.connector.graphql.auth; +package io.camunda.connector.common.auth; import com.google.api.client.http.HttpHeaders; import io.camunda.connector.api.annotation.Secret; diff --git a/connectors/graphql/src/main/java/io/camunda/connector/graphql/constants/Constants.java b/connectors/connectors-common-library/src/main/java/io/camunda/connector/common/constants/Constants.java similarity index 95% rename from connectors/graphql/src/main/java/io/camunda/connector/graphql/constants/Constants.java rename to connectors/connectors-common-library/src/main/java/io/camunda/connector/common/constants/Constants.java index 1f6e18d7d0..5de3630229 100644 --- a/connectors/graphql/src/main/java/io/camunda/connector/graphql/constants/Constants.java +++ b/connectors/connectors-common-library/src/main/java/io/camunda/connector/common/constants/Constants.java @@ -4,7 +4,7 @@ * See the License.txt file for more information. You may not use this file * except in compliance with the proprietary license. */ -package io.camunda.connector.graphql.constants; +package io.camunda.connector.common.constants; public class Constants { public static final String GRANT_TYPE = "grant_type"; diff --git a/connectors/connectors-common-library/src/main/java/io/camunda/connector/common/model/CommonRequest.java b/connectors/connectors-common-library/src/main/java/io/camunda/connector/common/model/CommonRequest.java new file mode 100644 index 0000000000..e92e99c9e7 --- /dev/null +++ b/connectors/connectors-common-library/src/main/java/io/camunda/connector/common/model/CommonRequest.java @@ -0,0 +1,99 @@ +/* + * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH + * under one or more contributor license agreements. Licensed under a proprietary license. + * See the License.txt file for more information. You may not use this file + * except in compliance with the proprietary license. + */ +package io.camunda.connector.common.model; + +import io.camunda.connector.api.annotation.Secret; +import io.camunda.connector.common.auth.Authentication; +import java.util.Objects; +import javax.validation.Valid; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.Pattern; + +public class CommonRequest { + + @NotBlank + @Pattern(regexp = "^(http://|https://|secrets).*$") + @Secret + private String url; + + @NotBlank @Secret private String method; + + @Valid @Secret private Authentication authentication; + + @Pattern(regexp = "^([0-9]*$)|(secrets.*$)") + @Secret + private String connectionTimeoutInSeconds; + + public boolean hasAuthentication() { + return authentication != null; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public Authentication getAuthentication() { + return authentication; + } + + public void setAuthentication(Authentication authentication) { + this.authentication = authentication; + } + + public String getMethod() { + return method; + } + + public void setMethod(String method) { + this.method = method; + } + + public String getConnectionTimeoutInSeconds() { + return connectionTimeoutInSeconds; + } + + public void setConnectionTimeoutInSeconds(String connectionTimeoutInSeconds) { + this.connectionTimeoutInSeconds = connectionTimeoutInSeconds; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + CommonRequest that = (CommonRequest) o; + return url.equals(that.url) + && method.equals(that.method) + && Objects.equals(authentication, that.authentication) + && Objects.equals(connectionTimeoutInSeconds, that.connectionTimeoutInSeconds); + } + + @Override + public int hashCode() { + return Objects.hash(url, method, authentication, connectionTimeoutInSeconds); + } + + @Override + public String toString() { + return "CommonRequest{" + + "url='" + + url + + '\'' + + ", method='" + + method + + '\'' + + ", authentication=" + + authentication + + ", connectionTimeoutInSeconds='" + + connectionTimeoutInSeconds + + '\'' + + '}'; + } +} diff --git a/connectors/connectors-common-library/src/main/java/io/camunda/connector/common/model/CommonResult.java b/connectors/connectors-common-library/src/main/java/io/camunda/connector/common/model/CommonResult.java new file mode 100644 index 0000000000..a82b3da0fe --- /dev/null +++ b/connectors/connectors-common-library/src/main/java/io/camunda/connector/common/model/CommonResult.java @@ -0,0 +1,65 @@ +/* + * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH + * under one or more contributor license agreements. Licensed under a proprietary license. + * See the License.txt file for more information. You may not use this file + * except in compliance with the proprietary license. + */ +package io.camunda.connector.common.model; + +import com.google.common.base.Objects; +import java.util.Map; + +public class CommonResult { + + private int status; + private Map headers; + private Object body; + + public int getStatus() { + return status; + } + + public void setStatus(int status) { + this.status = status; + } + + public Map getHeaders() { + return headers; + } + + public void setHeaders(Map headers) { + this.headers = headers; + } + + public Object getBody() { + return body; + } + + public void setBody(Object body) { + this.body = body; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + CommonResult that = (CommonResult) o; + return status == that.status + && Objects.equal(headers, that.headers) + && Objects.equal(body, that.body); + } + + @Override + public int hashCode() { + return Objects.hashCode(status, headers, body); + } + + @Override + public String toString() { + return "GraphQLResult{" + "status=" + status + ", headers=" + headers + ", body=" + body + '}'; + } +} diff --git a/connectors/graphql/src/main/java/io/camunda/connector/graphql/model/ErrorResponse.java b/connectors/connectors-common-library/src/main/java/io/camunda/connector/common/model/ErrorResponse.java similarity index 96% rename from connectors/graphql/src/main/java/io/camunda/connector/graphql/model/ErrorResponse.java rename to connectors/connectors-common-library/src/main/java/io/camunda/connector/common/model/ErrorResponse.java index f2682a0ce1..72a684728a 100644 --- a/connectors/graphql/src/main/java/io/camunda/connector/graphql/model/ErrorResponse.java +++ b/connectors/connectors-common-library/src/main/java/io/camunda/connector/common/model/ErrorResponse.java @@ -4,7 +4,7 @@ * See the License.txt file for more information. You may not use this file * except in compliance with the proprietary license. */ -package io.camunda.connector.graphql.model; +package io.camunda.connector.common.model; import java.util.Objects; diff --git a/connectors/graphql/src/main/java/io/camunda/connector/graphql/services/AuthenticationService.java b/connectors/connectors-common-library/src/main/java/io/camunda/connector/common/services/AuthenticationService.java similarity index 90% rename from connectors/graphql/src/main/java/io/camunda/connector/graphql/services/AuthenticationService.java rename to connectors/connectors-common-library/src/main/java/io/camunda/connector/common/services/AuthenticationService.java index 97f0bfcc24..d1807a00c7 100644 --- a/connectors/graphql/src/main/java/io/camunda/connector/graphql/services/AuthenticationService.java +++ b/connectors/connectors-common-library/src/main/java/io/camunda/connector/common/services/AuthenticationService.java @@ -4,9 +4,9 @@ * See the License.txt file for more information. You may not use this file * except in compliance with the proprietary license. */ -package io.camunda.connector.graphql.services; +package io.camunda.connector.common.services; -import static io.camunda.connector.graphql.utils.Timeout.setTimeout; +import static io.camunda.connector.common.utils.Timeout.setTimeout; import com.google.api.client.http.GenericUrl; import com.google.api.client.http.HttpContent; @@ -17,9 +17,9 @@ import com.google.api.client.http.UrlEncodedContent; import com.google.gson.Gson; import com.google.gson.JsonObject; -import io.camunda.connector.graphql.auth.OAuthAuthentication; -import io.camunda.connector.graphql.constants.Constants; -import io.camunda.connector.graphql.model.GraphQLRequest; +import io.camunda.connector.common.auth.OAuthAuthentication; +import io.camunda.connector.common.constants.Constants; +import io.camunda.connector.common.model.CommonRequest; import java.io.IOException; import java.util.HashMap; import java.util.Map; @@ -59,7 +59,7 @@ public String extractAccessToken(HttpResponse oauthResponse) throws IOException return null; } - public HttpRequest createOAuthRequest(GraphQLRequest request) throws IOException { + public HttpRequest createOAuthRequest(CommonRequest request) throws IOException { OAuthAuthentication authentication = (OAuthAuthentication) request.getAuthentication(); final GenericUrl genericUrl = new GenericUrl(authentication.getOauthTokenEndpoint()); diff --git a/connectors/connectors-common-library/src/main/java/io/camunda/connector/common/services/HTTPService.java b/connectors/connectors-common-library/src/main/java/io/camunda/connector/common/services/HTTPService.java new file mode 100644 index 0000000000..9c5313e823 --- /dev/null +++ b/connectors/connectors-common-library/src/main/java/io/camunda/connector/common/services/HTTPService.java @@ -0,0 +1,101 @@ +/* + * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH + * under one or more contributor license agreements. Licensed under a proprietary license. + * See the License.txt file for more information. You may not use this file + * except in compliance with the proprietary license. + */ +package io.camunda.connector.common.services; + +import static org.apache.http.entity.ContentType.APPLICATION_JSON; + +import com.google.api.client.http.HttpHeaders; +import com.google.api.client.http.HttpRequest; +import com.google.api.client.http.HttpResponse; +import com.google.api.client.http.HttpResponseException; +import com.google.gson.Gson; +import io.camunda.connector.api.error.ConnectorException; +import io.camunda.connector.common.constants.Constants; +import io.camunda.connector.common.model.CommonRequest; +import io.camunda.connector.common.model.CommonResult; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class HTTPService { + + private static final Logger LOGGER = LoggerFactory.getLogger(HTTPService.class); + + private final Gson gson; + + private static HTTPService instance; + + private HTTPService(final Gson gson) { + this.gson = gson; + } + + public static HTTPService getInstance(final Gson gson) { + if (instance == null) { + instance = new HTTPService(gson); + } + return instance; + } + + public HttpHeaders createHeaders(final CommonRequest request, String bearerToken) { + final HttpHeaders httpHeaders = new HttpHeaders(); + if (Constants.POST.equalsIgnoreCase(request.getMethod())) { + httpHeaders.setContentType(APPLICATION_JSON.getMimeType()); + } + if (request.hasAuthentication()) { + if (bearerToken != null && !bearerToken.isEmpty()) { + httpHeaders.setAuthorization("Bearer " + bearerToken); + } + request.getAuthentication().setHeaders(httpHeaders); + } + return httpHeaders; + } + + public HttpResponse executeHttpRequest(HttpRequest externalRequest) throws IOException { + try { + return externalRequest.execute(); + } catch (HttpResponseException httpResponseException) { + var errorCode = String.valueOf(httpResponseException.getStatusCode()); + var errorMessage = httpResponseException.getMessage(); + throw new ConnectorException(errorCode, errorMessage, httpResponseException); + } + } + + public T toHttpJsonResponse( + final HttpResponse externalResponse, final Class resultClass) + throws InstantiationException, IllegalAccessException { + T connectorResult = resultClass.newInstance(); + connectorResult.setStatus(externalResponse.getStatusCode()); + final Map headers = new HashMap<>(); + externalResponse + .getHeaders() + .forEach( + (k, v) -> { + if (v instanceof List && ((List) v).size() == 1) { + headers.put(k, ((List) v).get(0)); + } else { + headers.put(k, v); + } + }); + connectorResult.setHeaders(headers); + try (InputStream content = externalResponse.getContent(); + Reader reader = new InputStreamReader(content)) { + final Object body = gson.fromJson(reader, Object.class); + if (body != null) { + connectorResult.setBody(body); + } + } catch (final Exception e) { + LOGGER.error("Failed to parse external response: {}", externalResponse, e); + } + return connectorResult; + } +} diff --git a/connectors/graphql/src/main/java/io/camunda/connector/graphql/utils/Timeout.java b/connectors/connectors-common-library/src/main/java/io/camunda/connector/common/utils/Timeout.java similarity index 82% rename from connectors/graphql/src/main/java/io/camunda/connector/graphql/utils/Timeout.java rename to connectors/connectors-common-library/src/main/java/io/camunda/connector/common/utils/Timeout.java index 3747de068d..45694d6faf 100644 --- a/connectors/graphql/src/main/java/io/camunda/connector/graphql/utils/Timeout.java +++ b/connectors/connectors-common-library/src/main/java/io/camunda/connector/common/utils/Timeout.java @@ -4,15 +4,15 @@ * See the License.txt file for more information. You may not use this file * except in compliance with the proprietary license. */ -package io.camunda.connector.graphql.utils; +package io.camunda.connector.common.utils; import com.google.api.client.http.HttpRequest; -import io.camunda.connector.graphql.model.GraphQLRequest; +import io.camunda.connector.common.model.CommonRequest; import java.util.concurrent.TimeUnit; public class Timeout { - public static void setTimeout(GraphQLRequest request, HttpRequest httpRequest) { + public static void setTimeout(CommonRequest request, HttpRequest httpRequest) { if (request.getConnectionTimeoutInSeconds() != null) { long connectionTimeout = TimeUnit.SECONDS.toMillis(Long.parseLong(request.getConnectionTimeoutInSeconds())); diff --git a/connectors/graphql/connector.yml b/connectors/graphql/connector.yml index dfbc7df7b5..f753d96294 100644 --- a/connectors/graphql/connector.yml +++ b/connectors/graphql/connector.yml @@ -1,3 +1,3 @@ name: GRAPHQL type: io.camunda:connector-graphql:1 -variables: [ url, method, authentication, variables, query, connectionTimeoutInSeconds ] \ No newline at end of file +variables: [ graphql, authentication ] \ No newline at end of file diff --git a/connectors/graphql/pom.xml b/connectors/graphql/pom.xml index 79980ad2c6..103b869a4a 100644 --- a/connectors/graphql/pom.xml +++ b/connectors/graphql/pom.xml @@ -55,6 +55,13 @@ org.slf4j jcl-over-slf4j + + + + io.camunda.connector + connectors-common-library + 0.15.0-SNAPSHOT + diff --git a/connectors/graphql/src/main/java/io/camunda/connector/graphql/GraphQLFunction.java b/connectors/graphql/src/main/java/io/camunda/connector/graphql/GraphQLFunction.java index 1a3e71fb65..583bc53ec7 100644 --- a/connectors/graphql/src/main/java/io/camunda/connector/graphql/GraphQLFunction.java +++ b/connectors/graphql/src/main/java/io/camunda/connector/graphql/GraphQLFunction.java @@ -6,35 +6,40 @@ */ package io.camunda.connector.graphql; +import static io.camunda.connector.common.utils.Timeout.setTimeout; + +import com.google.api.client.http.GenericUrl; +import com.google.api.client.http.HttpContent; +import com.google.api.client.http.HttpHeaders; import com.google.api.client.http.HttpRequest; import com.google.api.client.http.HttpRequestFactory; import com.google.api.client.http.HttpResponse; +import com.google.api.client.http.json.JsonHttpContent; import com.google.api.client.json.gson.GsonFactory; import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; import io.camunda.connector.api.annotation.OutboundConnector; import io.camunda.connector.api.outbound.OutboundConnectorContext; import io.camunda.connector.api.outbound.OutboundConnectorFunction; -import io.camunda.connector.graphql.auth.OAuthAuthentication; +import io.camunda.connector.common.auth.OAuthAuthentication; +import io.camunda.connector.common.constants.Constants; +import io.camunda.connector.common.services.AuthenticationService; +import io.camunda.connector.common.services.HTTPService; import io.camunda.connector.graphql.components.GsonComponentSupplier; import io.camunda.connector.graphql.components.HttpTransportComponentSupplier; import io.camunda.connector.graphql.model.GraphQLRequest; import io.camunda.connector.graphql.model.GraphQLResult; -import io.camunda.connector.graphql.services.AuthenticationService; -import io.camunda.connector.graphql.services.HTTPService; import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @OutboundConnector( name = "GRAPHQL", - inputVariables = { - "graphql.url", - "graphql.method", - "graphql.authentication", - "graphql.query", - "graphql.variables", - "graphql.connectionTimeoutInSeconds" - }, + inputVariables = {"graphql", "authentication"}, type = "io.camunda:connector-graphql:1") public class GraphQLFunction implements OutboundConnectorFunction { @@ -59,19 +64,26 @@ public GraphQLFunction( } @Override - public Object execute(OutboundConnectorContext context) throws IOException { + public Object execute(OutboundConnectorContext context) + throws IOException, InstantiationException, IllegalAccessException { final var json = context.getVariables(); - final var connectorRequest = gson.fromJson(json, GraphQLRequest.class); + JsonElement graphqlJsonElement = + Optional.ofNullable(gson.fromJson(json, JsonObject.class).get("graphql")) + .orElseThrow( + () -> + new IllegalArgumentException( + "graphql input variable is mandatory but was null! ")); + final var connectorRequest = gson.fromJson(graphqlJsonElement.toString(), GraphQLRequest.class); context.validate(connectorRequest); context.replaceSecrets(connectorRequest); return executeGraphQLConnector(connectorRequest); } private GraphQLResult executeGraphQLConnector(final GraphQLRequest connectorRequest) - throws IOException { + throws IOException, InstantiationException, IllegalAccessException { // connector logic LOGGER.info("Executing graphql connector with request {}", connectorRequest); - HTTPService httpService = HTTPService.getInstance(gson, requestFactory, gsonFactory); + HTTPService httpService = HTTPService.getInstance(gson); AuthenticationService authService = AuthenticationService.getInstance(gson, requestFactory); String bearerToken = null; if (connectorRequest.getAuthentication() != null @@ -81,8 +93,44 @@ private GraphQLResult executeGraphQLConnector(final GraphQLRequest connectorRequ bearerToken = authService.extractAccessToken(oauthResponse); } - final HttpRequest httpRequest = httpService.createRequest(connectorRequest, bearerToken); + final HttpRequest httpRequest = createRequest(httpService, connectorRequest, bearerToken); HttpResponse httpResponse = httpService.executeHttpRequest(httpRequest); - return httpService.toHttpJsonResponse(httpResponse); + return httpService.toHttpJsonResponse(httpResponse, GraphQLResult.class); + } + + public HttpRequest createRequest( + final HTTPService httpService, final GraphQLRequest request, String bearerToken) + throws IOException { + final String method = request.getMethod().toUpperCase(); + final GenericUrl genericUrl = new GenericUrl(request.getUrl()); + HttpContent content = null; + final HttpHeaders headers = httpService.createHeaders(request, bearerToken); + String escapedQuery = request.getQuery().replace("\\n", "").replace("\\\"", "\""); + if (Constants.POST.equalsIgnoreCase(method)) { + content = constructBodyForPost(escapedQuery, request.getVariables()); + } else { + final Map query = new HashMap<>(); + query.put("query", escapedQuery); + if (request.getVariables() != null) { + query.put("variables", gson.toJsonTree(request.getVariables()).toString()); + } + genericUrl.putAll(query); + } + + final var httpRequest = requestFactory.buildRequest(method, genericUrl, content); + httpRequest.setFollowRedirects(false); + setTimeout(request, httpRequest); + httpRequest.setHeaders(headers); + + return httpRequest; + } + + private JsonHttpContent constructBodyForPost(String escapedQuery, Object variables) { + final Map body = new HashMap<>(); + body.put("query", escapedQuery); + if (variables != null) { + body.put("variables", variables); + } + return new JsonHttpContent(gsonFactory, body); } } diff --git a/connectors/graphql/src/main/java/io/camunda/connector/graphql/components/GsonComponentSupplier.java b/connectors/graphql/src/main/java/io/camunda/connector/graphql/components/GsonComponentSupplier.java index 217e2dd8e1..8cbdfdaf50 100644 --- a/connectors/graphql/src/main/java/io/camunda/connector/graphql/components/GsonComponentSupplier.java +++ b/connectors/graphql/src/main/java/io/camunda/connector/graphql/components/GsonComponentSupplier.java @@ -11,11 +11,11 @@ import com.google.gson.GsonBuilder; import com.google.gson.ToNumberPolicy; import com.google.gson.typeadapters.RuntimeTypeAdapterFactory; -import io.camunda.connector.graphql.auth.Authentication; -import io.camunda.connector.graphql.auth.BasicAuthentication; -import io.camunda.connector.graphql.auth.BearerAuthentication; -import io.camunda.connector.graphql.auth.NoAuthentication; -import io.camunda.connector.graphql.auth.OAuthAuthentication; +import io.camunda.connector.common.auth.Authentication; +import io.camunda.connector.common.auth.BasicAuthentication; +import io.camunda.connector.common.auth.BearerAuthentication; +import io.camunda.connector.common.auth.NoAuthentication; +import io.camunda.connector.common.auth.OAuthAuthentication; public class GsonComponentSupplier { diff --git a/connectors/graphql/src/main/java/io/camunda/connector/graphql/model/GraphQLRequest.java b/connectors/graphql/src/main/java/io/camunda/connector/graphql/model/GraphQLRequest.java index a97e2e586e..e4ad7e1dc9 100644 --- a/connectors/graphql/src/main/java/io/camunda/connector/graphql/model/GraphQLRequest.java +++ b/connectors/graphql/src/main/java/io/camunda/connector/graphql/model/GraphQLRequest.java @@ -7,61 +7,19 @@ package io.camunda.connector.graphql.model; import io.camunda.connector.api.annotation.Secret; -import io.camunda.connector.graphql.auth.Authentication; +import io.camunda.connector.common.model.CommonRequest; import java.util.Objects; -import javax.validation.Valid; import javax.validation.constraints.NotBlank; -import javax.validation.constraints.Pattern; -public class GraphQLRequest { +public class GraphQLRequest extends CommonRequest { - @NotBlank @Secret private String method; - - @NotBlank - @Pattern(regexp = "^(http://|https://|secrets).*$") - @Secret - private String url; - - @Valid @Secret private Authentication authentication; @NotBlank @Secret private String query; @Secret private Object variables; - @Pattern(regexp = "^([0-9]*$)|(secrets.*$)") - @Secret - private String connectionTimeoutInSeconds; - - public boolean hasAuthentication() { - return authentication != null; - } - public boolean hasQuery() { return query != null; } - public String getMethod() { - return method; - } - - public void setMethod(String method) { - this.method = method; - } - - public String getUrl() { - return url; - } - - public void setUrl(String url) { - this.url = url; - } - - public Authentication getAuthentication() { - return authentication; - } - - public void setAuthentication(Authentication authentication) { - this.authentication = authentication; - } - public String getQuery() { return query; } @@ -78,50 +36,21 @@ public void setVariables(Object variables) { this.variables = variables; } - public String getConnectionTimeoutInSeconds() { - return connectionTimeoutInSeconds; - } - - public void setConnectionTimeoutInSeconds(String connectionTimeoutInSeconds) { - this.connectionTimeoutInSeconds = connectionTimeoutInSeconds; - } - @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; GraphQLRequest that = (GraphQLRequest) o; - return method.equals(that.method) - && url.equals(that.url) - && authentication.equals(that.authentication) - && query.equals(that.query) - && Objects.equals(variables, that.variables) - && Objects.equals(connectionTimeoutInSeconds, that.connectionTimeoutInSeconds); + return query.equals(that.query) && Objects.equals(variables, that.variables); } @Override public int hashCode() { - return Objects.hash(method, url, authentication, query, variables, connectionTimeoutInSeconds); + return Objects.hash(query, variables); } @Override public String toString() { - return "GraphQLRequest{" - + "method='" - + method - + '\'' - + ", url='" - + url - + '\'' - + ", authentication=" - + authentication - + ", query=" - + query - + ", variables=" - + variables - + ", connectionTimeoutInSeconds='" - + connectionTimeoutInSeconds - + '\'' - + '}'; + return "GraphQLRequest{" + "query='" + query + '\'' + ", variables=" + variables + '}'; } } diff --git a/connectors/graphql/src/main/java/io/camunda/connector/graphql/model/GraphQLResult.java b/connectors/graphql/src/main/java/io/camunda/connector/graphql/model/GraphQLResult.java index ea03752e42..0d766d9646 100644 --- a/connectors/graphql/src/main/java/io/camunda/connector/graphql/model/GraphQLResult.java +++ b/connectors/graphql/src/main/java/io/camunda/connector/graphql/model/GraphQLResult.java @@ -6,60 +6,6 @@ */ package io.camunda.connector.graphql.model; -import com.google.common.base.Objects; -import java.util.Map; +import io.camunda.connector.common.model.CommonResult; -public class GraphQLResult { - - private int status; - private Map headers; - private Object body; - - public int getStatus() { - return status; - } - - public void setStatus(int status) { - this.status = status; - } - - public Map getHeaders() { - return headers; - } - - public void setHeaders(Map headers) { - this.headers = headers; - } - - public Object getBody() { - return body; - } - - public void setBody(Object body) { - this.body = body; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - GraphQLResult that = (GraphQLResult) o; - return status == that.status - && com.google.common.base.Objects.equal(headers, that.headers) - && com.google.common.base.Objects.equal(body, that.body); - } - - @Override - public int hashCode() { - return Objects.hashCode(status, headers, body); - } - - @Override - public String toString() { - return "GraphQLResult{" + "status=" + status + ", headers=" + headers + ", body=" + body + '}'; - } -} +public class GraphQLResult extends CommonResult {} diff --git a/connectors/graphql/src/main/java/io/camunda/connector/graphql/services/HTTPService.java b/connectors/graphql/src/main/java/io/camunda/connector/graphql/services/HTTPService.java deleted file mode 100644 index da60ab280d..0000000000 --- a/connectors/graphql/src/main/java/io/camunda/connector/graphql/services/HTTPService.java +++ /dev/null @@ -1,146 +0,0 @@ -/* - * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH - * under one or more contributor license agreements. Licensed under a proprietary license. - * See the License.txt file for more information. You may not use this file - * except in compliance with the proprietary license. - */ -package io.camunda.connector.graphql.services; - -import static io.camunda.connector.graphql.utils.Timeout.setTimeout; -import static org.apache.http.entity.ContentType.APPLICATION_JSON; - -import com.google.api.client.http.GenericUrl; -import com.google.api.client.http.HttpContent; -import com.google.api.client.http.HttpHeaders; -import com.google.api.client.http.HttpRequest; -import com.google.api.client.http.HttpRequestFactory; -import com.google.api.client.http.HttpResponse; -import com.google.api.client.http.HttpResponseException; -import com.google.api.client.http.json.JsonHttpContent; -import com.google.api.client.json.gson.GsonFactory; -import com.google.gson.Gson; -import io.camunda.connector.api.error.ConnectorException; -import io.camunda.connector.graphql.constants.Constants; -import io.camunda.connector.graphql.model.GraphQLRequest; -import io.camunda.connector.graphql.model.GraphQLResult; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.Reader; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class HTTPService { - - private static final Logger LOGGER = LoggerFactory.getLogger(HTTPService.class); - - private final Gson gson; - private final GsonFactory gsonFactory; - private final HttpRequestFactory requestFactory; - - private static HTTPService instance; - - private HTTPService( - final Gson gson, final HttpRequestFactory requestFactory, final GsonFactory gsonFactory) { - this.gson = gson; - this.requestFactory = requestFactory; - this.gsonFactory = gsonFactory; - } - - public static HTTPService getInstance( - final Gson gson, final HttpRequestFactory requestFactory, final GsonFactory gsonFactory) { - if (instance == null) { - instance = new HTTPService(gson, requestFactory, gsonFactory); - } - return instance; - } - - public HttpRequest createRequest(final GraphQLRequest request, String bearerToken) - throws IOException { - final String method = request.getMethod().toUpperCase(); - final GenericUrl genericUrl = new GenericUrl(request.getUrl()); - HttpContent content = null; - final HttpHeaders headers = createHeaders(request, bearerToken); - String escapedQuery = request.getQuery().replace("\\n", "").replace("\\\"", "\""); - if (Constants.POST.equalsIgnoreCase(method)) { - content = constructBodyForPost(escapedQuery, request.getVariables()); - } else { - final Map query = new HashMap<>(); - query.put("query", escapedQuery); - if (request.getVariables() != null) { - query.put("variables", gson.toJsonTree(request.getVariables()).toString()); - } - genericUrl.putAll(query); - } - - final var httpRequest = requestFactory.buildRequest(method, genericUrl, content); - httpRequest.setFollowRedirects(false); - setTimeout(request, httpRequest); - httpRequest.setHeaders(headers); - - return httpRequest; - } - - private JsonHttpContent constructBodyForPost(String escapedQuery, Object variables) { - final Map body = new HashMap<>(); - body.put("query", escapedQuery); - if (variables != null) { - body.put("variables", variables); - } - return new JsonHttpContent(gsonFactory, body); - } - - public HttpHeaders createHeaders(final GraphQLRequest request, String bearerToken) { - final HttpHeaders httpHeaders = new HttpHeaders(); - if (Constants.POST.equalsIgnoreCase(request.getMethod())) { - httpHeaders.setContentType(APPLICATION_JSON.getMimeType()); - } - if (request.hasAuthentication()) { - if (bearerToken != null && !bearerToken.isEmpty()) { - httpHeaders.setAuthorization("Bearer " + bearerToken); - } - request.getAuthentication().setHeaders(httpHeaders); - } - return httpHeaders; - } - - public HttpResponse executeHttpRequest(HttpRequest externalRequest) throws IOException { - try { - return externalRequest.execute(); - } catch (HttpResponseException httpResponseException) { - var errorCode = String.valueOf(httpResponseException.getStatusCode()); - var errorMessage = httpResponseException.getMessage(); - throw new ConnectorException(errorCode, errorMessage, httpResponseException); - } - } - - public GraphQLResult toHttpJsonResponse(final HttpResponse externalResponse) { - final GraphQLResult graphQLResult = new GraphQLResult(); - graphQLResult.setStatus(externalResponse.getStatusCode()); - final Map headers = new HashMap<>(); - externalResponse - .getHeaders() - .forEach( - (k, v) -> { - if (v instanceof List && ((List) v).size() == 1) { - headers.put(k, ((List) v).get(0)); - } else { - headers.put(k, v); - } - }); - graphQLResult.setHeaders(headers); - try (InputStream content = externalResponse.getContent(); - Reader reader = new InputStreamReader(content)) { - final Object body = gson.fromJson(reader, Object.class); - if (body != null) { - graphQLResult.setBody(body); - } - } catch (final Exception e) { - LOGGER.error("Failed to parse external response: {}", externalResponse, e); - } - return graphQLResult; - } -} diff --git a/connectors/pom.xml b/connectors/pom.xml index a57769ea7e..739d32721d 100644 --- a/connectors/pom.xml +++ b/connectors/pom.xml @@ -29,6 +29,7 @@ microsoft-teams slack graphql + connectors-common-library From 2cb7f8eb3997a6d201117a90dc7320cad889dd30 Mon Sep 17 00:00:00 2001 From: markfarkas-camunda Date: Fri, 3 Feb 2023 11:32:01 +0100 Subject: [PATCH 10/18] chore(graphql): set post as default value for method --- .../graphql/element-templates/graphql-connector.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/connectors/graphql/element-templates/graphql-connector.json b/connectors/graphql/element-templates/graphql-connector.json index a90c4d27d1..45de66a279 100644 --- a/connectors/graphql/element-templates/graphql-connector.json +++ b/connectors/graphql/element-templates/graphql-connector.json @@ -104,15 +104,15 @@ "label": "Method", "group": "endpoint", "type": "Dropdown", - "value": "get", + "value": "post", "choices": [ - { - "name": "GET", - "value": "get" - }, { "name": "POST", "value": "post" + }, + { + "name": "GET", + "value": "get" } ], "binding": { From 59a601587cfc7c33663724bd1e2f702ce9e9531a Mon Sep 17 00:00:00 2001 From: markfarkas-camunda Date: Wed, 8 Feb 2023 09:11:21 +0100 Subject: [PATCH 11/18] feat(graphql): use connectors-common-library in http-json connector, add test cases for graphql connector --- connectors/connectors-common-library/pom.xml | 2 +- .../common}/auth/CustomAuthentication.java | 26 +-- .../connector/common/constants/Constants.java | 1 + .../connector/common/model/CommonRequest.java | 57 ++++- .../services/AuthenticationService.java | 54 +++-- .../common/services/HTTPService.java | 34 +-- .../connector/common/utils/JsonHelper.java | 21 ++ .../common/utils}/ResponseParser.java | 47 +--- .../element-templates/graphql-connector.json | 1 + connectors/graphql/pom.xml | 4 +- .../connector/graphql/GraphQLFunction.java | 20 +- .../graphql/model/GraphQLRequestWrapper.java | 55 +++++ .../graphql/utils/JsonSerializeHelper.java | 20 ++ .../camunda/connector/graphql/BaseTest.java | 132 +++++++++++ .../GraphQLFunctionInputValidationTest.java | 144 ++++++++++++ .../graphql/GraphQLFunctionSecretsTest.java | 135 +++++++++++ .../graphql/GraphQLFunctionTest.java | 212 ++++++++++++++++++ .../org.mockito.plugins.MockMaker | 1 + ...l-cases-connection-timeout-validation.json | 103 +++++++++ ...es-request-without-one-required-field.json | 178 +++++++++++++++ .../resources/requests/fail-test-cases.json | 33 +++ ...s-cases-connection-timeout-validation.json | 53 +++++ .../success-cases-replace-secrets.json | 67 ++++++ .../requests/success-test-cases-oauth.json | 22 ++ .../requests/success-test-cases.json | 88 ++++++++ connectors/http-json/pom.xml | 7 + .../connector/http/HttpJsonFunction.java | 5 +- .../connector/http/HttpRequestMapper.java | 28 ++- .../connector/http/HttpResponseMapper.java | 65 ------ .../camunda/connector/http/HttpService.java | 96 +++----- .../connector/http/auth/Authentication.java | 49 ---- .../http/auth/BasicAuthentication.java | 78 ------- .../http/auth/BearerAuthentication.java | 65 ------ .../connector/http/auth/NoAuthentication.java | 40 ---- .../http/auth/OAuthAuthentication.java | 159 ------------- .../components/GsonComponentSupplier.java | 12 +- .../connector/http/constants/Constants.java | 34 --- .../connector/http/model/ErrorResponse.java | 63 ------ .../connector/http/model/HttpJsonRequest.java | 146 +----------- .../connector/http/model/HttpJsonResult.java | 57 +---- .../http/HttpJsonFunctionProxyTest.java | 5 +- .../http/HttpJsonFunctionSecretsTest.java | 10 +- .../connector/http/HttpJsonFunctionTest.java | 18 +- .../connector/http/HttpServiceTest.java | 9 +- .../http/auth/OAuthAuthenticationTest.java | 7 +- 45 files changed, 1500 insertions(+), 963 deletions(-) rename connectors/{http-json/src/main/java/io/camunda/connector/http => connectors-common-library/src/main/java/io/camunda/connector/common}/auth/CustomAuthentication.java (65%) create mode 100644 connectors/connectors-common-library/src/main/java/io/camunda/connector/common/utils/JsonHelper.java rename connectors/{http-json/src/main/java/io/camunda/connector/http => connectors-common-library/src/main/java/io/camunda/connector/common/utils}/ResponseParser.java (60%) create mode 100644 connectors/graphql/src/main/java/io/camunda/connector/graphql/model/GraphQLRequestWrapper.java create mode 100644 connectors/graphql/src/main/java/io/camunda/connector/graphql/utils/JsonSerializeHelper.java create mode 100644 connectors/graphql/src/test/java/io/camunda/connector/graphql/BaseTest.java create mode 100644 connectors/graphql/src/test/java/io/camunda/connector/graphql/GraphQLFunctionInputValidationTest.java create mode 100644 connectors/graphql/src/test/java/io/camunda/connector/graphql/GraphQLFunctionSecretsTest.java create mode 100644 connectors/graphql/src/test/java/io/camunda/connector/graphql/GraphQLFunctionTest.java create mode 100644 connectors/graphql/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker create mode 100644 connectors/graphql/src/test/resources/requests/fail-cases-connection-timeout-validation.json create mode 100644 connectors/graphql/src/test/resources/requests/fail-cases-request-without-one-required-field.json create mode 100644 connectors/graphql/src/test/resources/requests/fail-test-cases.json create mode 100644 connectors/graphql/src/test/resources/requests/success-cases-connection-timeout-validation.json create mode 100644 connectors/graphql/src/test/resources/requests/success-cases-replace-secrets.json create mode 100644 connectors/graphql/src/test/resources/requests/success-test-cases-oauth.json create mode 100644 connectors/graphql/src/test/resources/requests/success-test-cases.json delete mode 100644 connectors/http-json/src/main/java/io/camunda/connector/http/HttpResponseMapper.java delete mode 100644 connectors/http-json/src/main/java/io/camunda/connector/http/auth/Authentication.java delete mode 100644 connectors/http-json/src/main/java/io/camunda/connector/http/auth/BasicAuthentication.java delete mode 100644 connectors/http-json/src/main/java/io/camunda/connector/http/auth/BearerAuthentication.java delete mode 100644 connectors/http-json/src/main/java/io/camunda/connector/http/auth/NoAuthentication.java delete mode 100644 connectors/http-json/src/main/java/io/camunda/connector/http/auth/OAuthAuthentication.java delete mode 100644 connectors/http-json/src/main/java/io/camunda/connector/http/constants/Constants.java delete mode 100644 connectors/http-json/src/main/java/io/camunda/connector/http/model/ErrorResponse.java diff --git a/connectors/connectors-common-library/pom.xml b/connectors/connectors-common-library/pom.xml index ae460c2b31..96c356072d 100644 --- a/connectors/connectors-common-library/pom.xml +++ b/connectors/connectors-common-library/pom.xml @@ -6,7 +6,7 @@ io.camunda.connector connector-function-parent - 0.15.0-SNAPSHOT + 0.16.0-SNAPSHOT ../pom.xml diff --git a/connectors/http-json/src/main/java/io/camunda/connector/http/auth/CustomAuthentication.java b/connectors/connectors-common-library/src/main/java/io/camunda/connector/common/auth/CustomAuthentication.java similarity index 65% rename from connectors/http-json/src/main/java/io/camunda/connector/http/auth/CustomAuthentication.java rename to connectors/connectors-common-library/src/main/java/io/camunda/connector/common/auth/CustomAuthentication.java index 4783f6ebc9..8823952bca 100644 --- a/connectors/http-json/src/main/java/io/camunda/connector/http/auth/CustomAuthentication.java +++ b/connectors/connectors-common-library/src/main/java/io/camunda/connector/common/auth/CustomAuthentication.java @@ -1,24 +1,14 @@ /* * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH - * under one or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information regarding copyright - * ownership. Camunda licenses this file to you under the Apache License, - * Version 2.0; you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * under one or more contributor license agreements. Licensed under a proprietary license. + * See the License.txt file for more information. You may not use this file + * except in compliance with the proprietary license. */ -package io.camunda.connector.http.auth; +package io.camunda.connector.common.auth; import com.google.api.client.http.HttpHeaders; import io.camunda.connector.api.annotation.Secret; -import io.camunda.connector.http.model.HttpJsonRequest; +import io.camunda.connector.common.model.CommonRequest; import java.util.Map; import java.util.Objects; import javax.validation.Valid; @@ -26,7 +16,7 @@ public class CustomAuthentication extends Authentication { - @NotNull @Valid private HttpJsonRequest request; + @NotNull @Valid private CommonRequest request; @Secret private Map outputBody; @@ -35,11 +25,11 @@ public class CustomAuthentication extends Authentication { @Override public void setHeaders(final HttpHeaders headers) {} - public HttpJsonRequest getRequest() { + public CommonRequest getRequest() { return request; } - public void setRequest(final HttpJsonRequest request) { + public void setRequest(final CommonRequest request) { this.request = request; } diff --git a/connectors/connectors-common-library/src/main/java/io/camunda/connector/common/constants/Constants.java b/connectors/connectors-common-library/src/main/java/io/camunda/connector/common/constants/Constants.java index 5de3630229..890fe98ab3 100644 --- a/connectors/connectors-common-library/src/main/java/io/camunda/connector/common/constants/Constants.java +++ b/connectors/connectors-common-library/src/main/java/io/camunda/connector/common/constants/Constants.java @@ -15,6 +15,7 @@ public class Constants { public static final String ACCESS_TOKEN = "access_token"; public static final String BASIC_AUTH_HEADER = "basicAuthHeader"; public static final String CREDENTIALS_BODY = "credentialsBody"; + public static final String PROXY_FUNCTION_URL_ENV_NAME = "CAMUNDA_CONNECTOR_HTTP_PROXY_URL"; public static final String POST = "POST"; public static final String APPLICATION_JSON_CHARSET_UTF_8 = "application/json; charset=UTF-8"; public static final String APPLICATION_X_WWW_FORM_URLENCODED = diff --git a/connectors/connectors-common-library/src/main/java/io/camunda/connector/common/model/CommonRequest.java b/connectors/connectors-common-library/src/main/java/io/camunda/connector/common/model/CommonRequest.java index e92e99c9e7..56e78271ac 100644 --- a/connectors/connectors-common-library/src/main/java/io/camunda/connector/common/model/CommonRequest.java +++ b/connectors/connectors-common-library/src/main/java/io/camunda/connector/common/model/CommonRequest.java @@ -8,6 +8,7 @@ import io.camunda.connector.api.annotation.Secret; import io.camunda.connector.common.auth.Authentication; +import java.util.Map; import java.util.Objects; import javax.validation.Valid; import javax.validation.constraints.NotBlank; @@ -28,6 +29,48 @@ public class CommonRequest { @Secret private String connectionTimeoutInSeconds; + @Secret private Map headers; + + @Secret private Object body; + + @Secret private Map queryParameters; + + public Object getBody() { + return body; + } + + public void setBody(final Object body) { + this.body = body; + } + + public boolean hasHeaders() { + return headers != null; + } + + public boolean hasBody() { + return body != null; + } + + public Map getHeaders() { + return headers; + } + + public void setHeaders(final Map headers) { + this.headers = headers; + } + + public boolean hasQueryParameters() { + return queryParameters != null; + } + + public Map getQueryParameters() { + return queryParameters; + } + + public void setQueryParameters(Map queryParameters) { + this.queryParameters = queryParameters; + } + public boolean hasAuthentication() { return authentication != null; } @@ -72,12 +115,16 @@ public boolean equals(Object o) { return url.equals(that.url) && method.equals(that.method) && Objects.equals(authentication, that.authentication) - && Objects.equals(connectionTimeoutInSeconds, that.connectionTimeoutInSeconds); + && Objects.equals(connectionTimeoutInSeconds, that.connectionTimeoutInSeconds) + && Objects.equals(headers, that.headers) + && Objects.equals(body, that.body) + && Objects.equals(queryParameters, that.queryParameters); } @Override public int hashCode() { - return Objects.hash(url, method, authentication, connectionTimeoutInSeconds); + return Objects.hash( + url, method, authentication, connectionTimeoutInSeconds, headers, body, queryParameters); } @Override @@ -94,6 +141,12 @@ public String toString() { + ", connectionTimeoutInSeconds='" + connectionTimeoutInSeconds + '\'' + + ", headers=" + + headers + + ", body=" + + body + + ", queryParameters=" + + queryParameters + '}'; } } diff --git a/connectors/connectors-common-library/src/main/java/io/camunda/connector/common/services/AuthenticationService.java b/connectors/connectors-common-library/src/main/java/io/camunda/connector/common/services/AuthenticationService.java index d1807a00c7..d7a2b95290 100644 --- a/connectors/connectors-common-library/src/main/java/io/camunda/connector/common/services/AuthenticationService.java +++ b/connectors/connectors-common-library/src/main/java/io/camunda/connector/common/services/AuthenticationService.java @@ -16,13 +16,18 @@ import com.google.api.client.http.HttpResponse; import com.google.api.client.http.UrlEncodedContent; import com.google.gson.Gson; +import com.google.gson.JsonElement; import com.google.gson.JsonObject; +import io.camunda.connector.common.auth.CustomAuthentication; import io.camunda.connector.common.auth.OAuthAuthentication; import io.camunda.connector.common.constants.Constants; import io.camunda.connector.common.model.CommonRequest; +import io.camunda.connector.common.utils.JsonHelper; +import io.camunda.connector.common.utils.ResponseParser; import java.io.IOException; import java.util.HashMap; import java.util.Map; +import java.util.Optional; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -30,33 +35,50 @@ public class AuthenticationService { private static final Logger LOGGER = LoggerFactory.getLogger(AuthenticationService.class); - private static AuthenticationService instance; - private final Gson gson; private final HttpRequestFactory requestFactory; - private AuthenticationService(final Gson gson, final HttpRequestFactory requestFactory) { + public AuthenticationService(final Gson gson, final HttpRequestFactory requestFactory) { this.gson = gson; this.requestFactory = requestFactory; } - public static AuthenticationService getInstance( - final Gson gson, final HttpRequestFactory requestFactory) { - if (instance == null) { - instance = new AuthenticationService(gson, requestFactory); - } - return instance; + public String extractOAuthAccessToken(HttpResponse oauthResponse) throws IOException { + return Optional.ofNullable(JsonHelper.getAsJsonElement(oauthResponse.parseAsString(), gson)) + .map(JsonElement::getAsJsonObject) + .map(jsonObject -> jsonObject.get(Constants.ACCESS_TOKEN)) + .map(JsonElement::getAsString) + .orElse(null); } - public String extractAccessToken(HttpResponse oauthResponse) throws IOException { - String oauthResponseStr = oauthResponse.parseAsString(); - if (oauthResponseStr != null && !oauthResponseStr.isEmpty()) { - JsonObject jsonObject = gson.fromJson(oauthResponseStr, JsonObject.class); - if (jsonObject.get(Constants.ACCESS_TOKEN) != null) { - return jsonObject.get(Constants.ACCESS_TOKEN).getAsString(); + public void fillRequestFromCustomAuthResponseData( + final CommonRequest request, + final CustomAuthentication authentication, + final HttpResponse httpResponse) + throws IOException { + String strResponse = httpResponse.parseAsString(); + Map headers = + ResponseParser.extractPropertiesFromBody( + authentication.getOutputHeaders(), strResponse, gson); + if (headers != null) { + if (!request.hasHeaders()) { + request.setHeaders(new HashMap<>()); + } + request.getHeaders().putAll(headers); + } + + Map body = + ResponseParser.extractPropertiesFromBody(authentication.getOutputBody(), strResponse, gson); + if (body != null) { + if (!request.hasBody()) { + request.setBody(new Object()); } + JsonObject requestBody = gson.toJsonTree(request.getBody()).getAsJsonObject(); + // for now, we can add only string property to body, example of this object : + // "{"key":"value"}" but we can expand this method + body.forEach(requestBody::addProperty); + request.setBody(gson.fromJson(gson.toJson(requestBody), Object.class)); } - return null; } public HttpRequest createOAuthRequest(CommonRequest request) throws IOException { diff --git a/connectors/connectors-common-library/src/main/java/io/camunda/connector/common/services/HTTPService.java b/connectors/connectors-common-library/src/main/java/io/camunda/connector/common/services/HTTPService.java index 9c5313e823..606ac5c323 100644 --- a/connectors/connectors-common-library/src/main/java/io/camunda/connector/common/services/HTTPService.java +++ b/connectors/connectors-common-library/src/main/java/io/camunda/connector/common/services/HTTPService.java @@ -17,6 +17,7 @@ import io.camunda.connector.common.constants.Constants; import io.camunda.connector.common.model.CommonRequest; import io.camunda.connector.common.model.CommonResult; +import io.camunda.connector.common.model.ErrorResponse; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; @@ -33,19 +34,10 @@ public class HTTPService { private final Gson gson; - private static HTTPService instance; - - private HTTPService(final Gson gson) { + public HTTPService(final Gson gson) { this.gson = gson; } - public static HTTPService getInstance(final Gson gson) { - if (instance == null) { - instance = new HTTPService(gson); - } - return instance; - } - public HttpHeaders createHeaders(final CommonRequest request, String bearerToken) { final HttpHeaders httpHeaders = new HttpHeaders(); if (Constants.POST.equalsIgnoreCase(request.getMethod())) { @@ -61,12 +53,26 @@ public HttpHeaders createHeaders(final CommonRequest request, String bearerToken } public HttpResponse executeHttpRequest(HttpRequest externalRequest) throws IOException { + return executeHttpRequest(externalRequest, false); + } + + public HttpResponse executeHttpRequest(HttpRequest externalRequest, boolean isProxyCall) + throws IOException { try { return externalRequest.execute(); - } catch (HttpResponseException httpResponseException) { - var errorCode = String.valueOf(httpResponseException.getStatusCode()); - var errorMessage = httpResponseException.getMessage(); - throw new ConnectorException(errorCode, errorMessage, httpResponseException); + } catch (HttpResponseException hrex) { + var errorCode = String.valueOf(hrex.getStatusCode()); + var errorMessage = hrex.getMessage(); + if (isProxyCall && hrex.getContent() != null) { + try { + final var errorContent = gson.fromJson(hrex.getContent(), ErrorResponse.class); + errorCode = errorContent.getErrorCode(); + errorMessage = errorContent.getError(); + } catch (Exception e) { + // cannot be loaded as JSON, ignore and use plain message + } + } + throw new ConnectorException(errorCode, errorMessage, hrex); } } diff --git a/connectors/connectors-common-library/src/main/java/io/camunda/connector/common/utils/JsonHelper.java b/connectors/connectors-common-library/src/main/java/io/camunda/connector/common/utils/JsonHelper.java new file mode 100644 index 0000000000..ea52505a09 --- /dev/null +++ b/connectors/connectors-common-library/src/main/java/io/camunda/connector/common/utils/JsonHelper.java @@ -0,0 +1,21 @@ +/* + * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH + * under one or more contributor license agreements. Licensed under a proprietary license. + * See the License.txt file for more information. You may not use this file + * except in compliance with the proprietary license. + */ +package io.camunda.connector.common.utils; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import java.util.Optional; + +public class JsonHelper { + + public static JsonElement getAsJsonElement(final String strResponse, final Gson gson) { + return Optional.ofNullable(strResponse) + .filter(response -> !response.isBlank()) + .map(response -> gson.fromJson(response, JsonElement.class)) + .orElse(null); + } +} diff --git a/connectors/http-json/src/main/java/io/camunda/connector/http/ResponseParser.java b/connectors/connectors-common-library/src/main/java/io/camunda/connector/common/utils/ResponseParser.java similarity index 60% rename from connectors/http-json/src/main/java/io/camunda/connector/http/ResponseParser.java rename to connectors/connectors-common-library/src/main/java/io/camunda/connector/common/utils/ResponseParser.java index 27367fe138..2967a83945 100644 --- a/connectors/http-json/src/main/java/io/camunda/connector/http/ResponseParser.java +++ b/connectors/connectors-common-library/src/main/java/io/camunda/connector/common/utils/ResponseParser.java @@ -1,53 +1,27 @@ /* * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH - * under one or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information regarding copyright - * ownership. Camunda licenses this file to you under the Apache License, - * Version 2.0; you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * under one or more contributor license agreements. Licensed under a proprietary license. + * See the License.txt file for more information. You may not use this file + * except in compliance with the proprietary license. */ -package io.camunda.connector.http; +package io.camunda.connector.common.utils; -import com.google.api.client.http.HttpResponse; import com.google.gson.Gson; import com.google.gson.JsonElement; -import io.camunda.connector.http.components.GsonComponentSupplier; -import io.camunda.connector.http.constants.Constants; -import java.io.IOException; import java.util.HashMap; import java.util.Map; import java.util.Optional; -public final class ResponseParser { - - private static final Gson gson = GsonComponentSupplier.gsonInstance(); - - private ResponseParser() {} - - public static String extractOAuthAccessToken(HttpResponse oauthResponse) throws IOException { - return Optional.ofNullable(getAsJsonElement(oauthResponse.parseAsString())) - .map(JsonElement::getAsJsonObject) - .map(jsonObject -> jsonObject.get(Constants.ACCESS_TOKEN)) - .map(JsonElement::getAsString) - .orElse(null); - } +public class ResponseParser { public static Map extractPropertiesFromBody( - final Map requestedProperties, final String strResponse) { + final Map requestedProperties, final String strResponse, final Gson gson) { if (requestedProperties == null || requestedProperties.isEmpty()) { return null; } final JsonElement asJsonElement = - Optional.ofNullable(getAsJsonElement(strResponse)) + Optional.ofNullable(JsonHelper.getAsJsonElement(strResponse, gson)) .orElseThrow( () -> new IllegalArgumentException("Authentication response body is empty")); @@ -97,11 +71,4 @@ public static Map extractPropertiesFromBody( }); return extractedProperties.isEmpty() ? null : extractedProperties; } - - private static JsonElement getAsJsonElement(final String strResponse) { - return Optional.ofNullable(strResponse) - .filter(response -> !response.isBlank()) - .map(response -> gson.fromJson(response, JsonElement.class)) - .orElse(null); - } } diff --git a/connectors/graphql/element-templates/graphql-connector.json b/connectors/graphql/element-templates/graphql-connector.json index 45de66a279..c040e42a06 100644 --- a/connectors/graphql/element-templates/graphql-connector.json +++ b/connectors/graphql/element-templates/graphql-connector.json @@ -125,6 +125,7 @@ "description": "See documentation", "group": "graphql", "type": "Text", + "language": "graphql", "binding": { "type": "zeebe:input", "name": "graphql.query" diff --git a/connectors/graphql/pom.xml b/connectors/graphql/pom.xml index 103b869a4a..59c5360c61 100644 --- a/connectors/graphql/pom.xml +++ b/connectors/graphql/pom.xml @@ -6,7 +6,7 @@ io.camunda.connector connector-function-parent - 0.15.0-SNAPSHOT + 0.16.0-SNAPSHOT ../pom.xml @@ -60,7 +60,7 @@ io.camunda.connector connectors-common-library - 0.15.0-SNAPSHOT + 0.16.0-SNAPSHOT diff --git a/connectors/graphql/src/main/java/io/camunda/connector/graphql/GraphQLFunction.java b/connectors/graphql/src/main/java/io/camunda/connector/graphql/GraphQLFunction.java index 583bc53ec7..0b6e5ec176 100644 --- a/connectors/graphql/src/main/java/io/camunda/connector/graphql/GraphQLFunction.java +++ b/connectors/graphql/src/main/java/io/camunda/connector/graphql/GraphQLFunction.java @@ -17,8 +17,6 @@ import com.google.api.client.http.json.JsonHttpContent; import com.google.api.client.json.gson.GsonFactory; import com.google.gson.Gson; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; import io.camunda.connector.api.annotation.OutboundConnector; import io.camunda.connector.api.outbound.OutboundConnectorContext; import io.camunda.connector.api.outbound.OutboundConnectorFunction; @@ -30,10 +28,10 @@ import io.camunda.connector.graphql.components.HttpTransportComponentSupplier; import io.camunda.connector.graphql.model.GraphQLRequest; import io.camunda.connector.graphql.model.GraphQLResult; +import io.camunda.connector.graphql.utils.JsonSerializeHelper; import java.io.IOException; import java.util.HashMap; import java.util.Map; -import java.util.Optional; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -67,13 +65,7 @@ public GraphQLFunction( public Object execute(OutboundConnectorContext context) throws IOException, InstantiationException, IllegalAccessException { final var json = context.getVariables(); - JsonElement graphqlJsonElement = - Optional.ofNullable(gson.fromJson(json, JsonObject.class).get("graphql")) - .orElseThrow( - () -> - new IllegalArgumentException( - "graphql input variable is mandatory but was null! ")); - final var connectorRequest = gson.fromJson(graphqlJsonElement.toString(), GraphQLRequest.class); + var connectorRequest = JsonSerializeHelper.serializeRequest(gson, json); context.validate(connectorRequest); context.replaceSecrets(connectorRequest); return executeGraphQLConnector(connectorRequest); @@ -82,15 +74,15 @@ public Object execute(OutboundConnectorContext context) private GraphQLResult executeGraphQLConnector(final GraphQLRequest connectorRequest) throws IOException, InstantiationException, IllegalAccessException { // connector logic - LOGGER.info("Executing graphql connector with request {}", connectorRequest); - HTTPService httpService = HTTPService.getInstance(gson); - AuthenticationService authService = AuthenticationService.getInstance(gson, requestFactory); + LOGGER.debug("Executing graphql connector with request {}", connectorRequest); + HTTPService httpService = new HTTPService(gson); + AuthenticationService authService = new AuthenticationService(gson, requestFactory); String bearerToken = null; if (connectorRequest.getAuthentication() != null && connectorRequest.getAuthentication() instanceof OAuthAuthentication) { final HttpRequest oauthRequest = authService.createOAuthRequest(connectorRequest); final HttpResponse oauthResponse = httpService.executeHttpRequest(oauthRequest); - bearerToken = authService.extractAccessToken(oauthResponse); + bearerToken = authService.extractOAuthAccessToken(oauthResponse); } final HttpRequest httpRequest = createRequest(httpService, connectorRequest, bearerToken); diff --git a/connectors/graphql/src/main/java/io/camunda/connector/graphql/model/GraphQLRequestWrapper.java b/connectors/graphql/src/main/java/io/camunda/connector/graphql/model/GraphQLRequestWrapper.java new file mode 100644 index 0000000000..05d447ce02 --- /dev/null +++ b/connectors/graphql/src/main/java/io/camunda/connector/graphql/model/GraphQLRequestWrapper.java @@ -0,0 +1,55 @@ +/* + * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH + * under one or more contributor license agreements. Licensed under a proprietary license. + * See the License.txt file for more information. You may not use this file + * except in compliance with the proprietary license. + */ +package io.camunda.connector.graphql.model; + +import io.camunda.connector.common.auth.Authentication; +import java.util.Objects; +import javax.validation.constraints.NotBlank; + +public class GraphQLRequestWrapper { + @NotBlank private GraphQLRequest graphql; + private Authentication authentication; + + public GraphQLRequest getGraphql() { + return graphql; + } + + public void setGraphql(GraphQLRequest graphql) { + this.graphql = graphql; + } + + public Authentication getAuthentication() { + return authentication; + } + + public void setAuthentication(Authentication authentication) { + this.authentication = authentication; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + GraphQLRequestWrapper that = (GraphQLRequestWrapper) o; + return graphql.equals(that.graphql) && Objects.equals(authentication, that.authentication); + } + + @Override + public int hashCode() { + return Objects.hash(graphql, authentication); + } + + @Override + public String toString() { + return "GraphQLRequestWrapper{" + + "graphql=" + + graphql + + ", authentication=" + + authentication + + '}'; + } +} diff --git a/connectors/graphql/src/main/java/io/camunda/connector/graphql/utils/JsonSerializeHelper.java b/connectors/graphql/src/main/java/io/camunda/connector/graphql/utils/JsonSerializeHelper.java new file mode 100644 index 0000000000..cf0989b330 --- /dev/null +++ b/connectors/graphql/src/main/java/io/camunda/connector/graphql/utils/JsonSerializeHelper.java @@ -0,0 +1,20 @@ +/* + * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH + * under one or more contributor license agreements. Licensed under a proprietary license. + * See the License.txt file for more information. You may not use this file + * except in compliance with the proprietary license. + */ +package io.camunda.connector.graphql.utils; + +import com.google.gson.Gson; +import io.camunda.connector.graphql.model.GraphQLRequest; +import io.camunda.connector.graphql.model.GraphQLRequestWrapper; + +public class JsonSerializeHelper { + public static GraphQLRequest serializeRequest(Gson gson, String input) { + GraphQLRequestWrapper graphQLRequestWrapper = gson.fromJson(input, GraphQLRequestWrapper.class); + GraphQLRequest graphQLRequest = graphQLRequestWrapper.getGraphql(); + graphQLRequest.setAuthentication(graphQLRequestWrapper.getAuthentication()); + return graphQLRequest; + } +} diff --git a/connectors/graphql/src/test/java/io/camunda/connector/graphql/BaseTest.java b/connectors/graphql/src/test/java/io/camunda/connector/graphql/BaseTest.java new file mode 100644 index 0000000000..34a391e999 --- /dev/null +++ b/connectors/graphql/src/test/java/io/camunda/connector/graphql/BaseTest.java @@ -0,0 +1,132 @@ +/* + * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH + * under one or more contributor license agreements. Licensed under a proprietary license. + * See the License.txt file for more information. You may not use this file + * except in compliance with the proprietary license. + */ +package io.camunda.connector.graphql; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static java.nio.file.Files.readString; + +import com.google.api.client.json.gson.GsonFactory; +import com.google.gson.Gson; +import io.camunda.connector.graphql.components.GsonComponentSupplier; +import io.camunda.connector.test.outbound.OutboundConnectorContextBuilder; +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.stream.Stream; +import org.junit.jupiter.params.provider.Arguments; + +public class BaseTest { + + protected Gson gson = GsonComponentSupplier.gsonInstance(); + + protected GsonFactory gsonFactory = GsonComponentSupplier.gsonFactoryInstance(); + + protected interface SecretsConstant { + String URL = "URL_KEY"; + String METHOD = "METHOD_KEY"; + String CONNECT_TIMEOUT = "CONNECT_TIMEOUT_KEY"; + + interface Authentication { + String TOKEN = "TOKEN_KEY"; + String PASSWORD = "PASSWORD_KEY"; + String USERNAME = "USERNAME_KEY"; + String OAUTH_TOKEN_ENDPOINT = "OAUTH_TOKEN_ENDPOINT_KEY"; + String CLIENT_ID = "CLIENT_ID_KEY"; + String CLIENT_SECRET = "CLIENT_SECRET_KEY"; + String AUDIENCE = "AUDIENCE_KEY"; + } + + interface Variables { + String ID = "VARIABLE_ID"; + } + + interface Query { + String ID = "QUERY_ID"; + String TEXT = "TEXT_KEY"; + String TEXT_PART_1 = "TEXT_PART_1_KEY"; + String TEXT_PART_2 = "TEXT_PART_2_KEY"; + String TEXT_PART_3 = "TEXT_PART_3_KEY"; + } + } + + protected interface ActualValue { + String URL = "https://camunda.io/http-endpoint"; + String METHOD = "GET"; + String CONNECT_TIMEOUT = "50"; + + interface Authentication { + String TOKEN = "test token"; + String PASSWORD = "1234567890"; + String USERNAME = "test username"; + String OAUTH_TOKEN_ENDPOINT = "https://test/api/v2/"; + String CLIENT_ID = "bi1cekB123456GRWBBEgzdxA89S2T"; + String CLIENT_SECRET = "Bzw6SL12345678934562eqg4fJM72EeeM2JQiF4BfbyYZUDCur7ntB"; + String AUDIENCE = "https://test/api/v2/"; + } + + interface Variables { + String ID = "variableId"; + String USER_AGENT = "http-connector-demo"; + } + + interface Query { + String ID = "secret id key"; + String CUSTOMER_NAME_SECRET = "secret name"; + String CUSTOMER_NAME_REAL = CUSTOMER_NAME_SECRET + " plus some text"; + String CUSTOMER_EMAIL_SECRET = "start email plus secret email part plus end email"; + String CUSTOMER_EMAIL_REAL = "start email plus " + CUSTOMER_EMAIL_SECRET + " plus end email"; + String TEXT_PART_1 = "start secret text plus "; + String TEXT_PART_2 = "mid of text plus "; + String TEXT_PART_3 = "end of text"; + String TEXT = TEXT_PART_1 + TEXT_PART_2 + TEXT_PART_3; + } + } + + protected interface JsonKeys { + String CLUSTER_ID = "X-Camunda-Cluster-ID"; + String USER_AGENT = "User-Agent"; + String QUERY = "q"; + String PRIORITY = "priority"; + String CUSTOMER = "customer"; + String ID = "id"; + String NAME = "name"; + String EMAIL = "email"; + String TEXT = "text"; + } + + protected OutboundConnectorContextBuilder getContextBuilderWithSecrets() { + return OutboundConnectorContextBuilder.create() + .secret(SecretsConstant.URL, ActualValue.URL) + .secret(SecretsConstant.METHOD, ActualValue.METHOD) + .secret(SecretsConstant.CONNECT_TIMEOUT, ActualValue.CONNECT_TIMEOUT) + .secret(SecretsConstant.Authentication.TOKEN, ActualValue.Authentication.TOKEN) + .secret(SecretsConstant.Authentication.USERNAME, ActualValue.Authentication.USERNAME) + .secret(SecretsConstant.Authentication.PASSWORD, ActualValue.Authentication.PASSWORD) + .secret( + SecretsConstant.Authentication.OAUTH_TOKEN_ENDPOINT, + ActualValue.Authentication.OAUTH_TOKEN_ENDPOINT) + .secret(SecretsConstant.Authentication.CLIENT_ID, ActualValue.Authentication.CLIENT_ID) + .secret( + SecretsConstant.Authentication.CLIENT_SECRET, ActualValue.Authentication.CLIENT_SECRET) + .secret(SecretsConstant.Authentication.AUDIENCE, ActualValue.Authentication.AUDIENCE) + .secret(SecretsConstant.Variables.ID, ActualValue.Variables.ID) + .secret(SecretsConstant.Query.ID, ActualValue.Query.ID) + .secret(SecretsConstant.Query.TEXT, ActualValue.Query.TEXT) + .secret(SecretsConstant.Query.TEXT_PART_1, ActualValue.Query.TEXT_PART_1) + .secret(SecretsConstant.Query.TEXT_PART_2, ActualValue.Query.TEXT_PART_2) + .secret(SecretsConstant.Query.TEXT_PART_3, ActualValue.Query.TEXT_PART_3); + } + + @SuppressWarnings("unchecked") + protected static Stream loadTestCasesFromResourceFile(final String fileWithTestCasesUri) + throws IOException { + final String cases = readString(new File(fileWithTestCasesUri).toPath(), UTF_8); + final Gson testingGson = GsonComponentSupplier.gsonInstance(); + var array = testingGson.fromJson(cases, ArrayList.class); + return array.stream().map(testingGson::toJson).map(Arguments::of); + } +} diff --git a/connectors/graphql/src/test/java/io/camunda/connector/graphql/GraphQLFunctionInputValidationTest.java b/connectors/graphql/src/test/java/io/camunda/connector/graphql/GraphQLFunctionInputValidationTest.java new file mode 100644 index 0000000000..629246b614 --- /dev/null +++ b/connectors/graphql/src/test/java/io/camunda/connector/graphql/GraphQLFunctionInputValidationTest.java @@ -0,0 +1,144 @@ +/* + * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH + * under one or more contributor license agreements. Licensed under a proprietary license. + * See the License.txt file for more information. You may not use this file + * except in compliance with the proprietary license. + */ +package io.camunda.connector.graphql; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import io.camunda.connector.api.outbound.OutboundConnectorContext; +import io.camunda.connector.graphql.model.GraphQLRequest; +import io.camunda.connector.graphql.utils.JsonSerializeHelper; +import io.camunda.connector.impl.ConnectorInputException; +import io.camunda.connector.test.outbound.OutboundConnectorContextBuilder; +import java.io.IOException; +import java.util.stream.Stream; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.provider.ValueSource; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +public class GraphQLFunctionInputValidationTest extends BaseTest { + + private static final String FAIL_REQUEST_CASES_PATH = + "src/test/resources/requests/fail-cases-request-without-one-required-field.json"; + + private static final String FAIL_CASES_TIMEOUT_CONNECTION_RESOURCE_PATH = + "src/test/resources/requests/fail-cases-connection-timeout-validation.json"; + + private static final String SUCCESS_CASES_TIMEOUT_CONNECTION_RESOURCE_PATH = + "src/test/resources/requests/success-cases-connection-timeout-validation.json"; + + private static final String REQUEST_METHOD_OBJECT_PLACEHOLDER = + "{\"graphql\":{\n \"method\": \"%s\",\n \"url\": \"https://camunda.io/http-endpoint\"\n}}"; + + private static final String REQUEST_ENDPOINT_OBJECT_PLACEHOLDER = + "{\"graphql\":{\n \"method\": \"get\",\n \"url\": \"%s\"\n}}"; + + private GraphQLFunction functionUnderTest; + + @BeforeEach + void setup() { + functionUnderTest = new GraphQLFunction(); + } + + @ParameterizedTest + @ValueSource(strings = {"", " ", "\r\n"}) + void shouldRaiseException_WhenExecuted_MethodMalformed(final String input) { + // Given + OutboundConnectorContext ctx = + OutboundConnectorContextBuilder.create() + .variables(String.format(REQUEST_METHOD_OBJECT_PLACEHOLDER, input)) + .build(); + + // When + Throwable exception = + assertThrows(ConnectorInputException.class, () -> functionUnderTest.execute(ctx)); + + // Then + assertThat(exception.getMessage()) + .contains("Found constraints violated while validating input", "method: must not be blank"); + } + + @ParameterizedTest + @ValueSource(strings = {"", " ", "iAmWrongUrl", "ftp://camunda.org/", "camunda@camunda.com"}) + void shouldRaiseException_WhenExecuted_EndpointMalformed(final String input) { + // Given + OutboundConnectorContext ctx = + OutboundConnectorContextBuilder.create() + .variables(String.format(REQUEST_ENDPOINT_OBJECT_PLACEHOLDER, input)) + .build(); + // When + Throwable exception = + assertThrows(ConnectorInputException.class, () -> functionUnderTest.execute(ctx)); + // Then + assertThat(exception.getMessage()) + .contains( + "Found constraints violated while validating input", + "must match \"^(http://|https://|secrets).*$\""); + } + + @ParameterizedTest(name = "Validate null field # {index}") + @MethodSource("failRequestCases") + void validate_shouldThrowExceptionWhenLeastOneNotExistRequestField(String input) { + // Given request without one required field + GraphQLRequest httpJsonRequest = JsonSerializeHelper.serializeRequest(gson, input); + OutboundConnectorContext context = + OutboundConnectorContextBuilder.create().variables(httpJsonRequest).build(); + // When context.validate(request); + // Then expect exception that one required field not set + ConnectorInputException thrown = + assertThrows( + ConnectorInputException.class, + () -> context.validate(httpJsonRequest), + "ConnectorInputException was expected"); + assertThat(thrown.getMessage()).contains("Found constraints violated while validating input"); + } + + @ParameterizedTest(name = "Validate connectionTimeout # {index}") + @MethodSource("failTimeOutConnectionCases") + void validate_shouldThrowExceptionConnectionTimeoutIsWrong(String input) { + // Given request without one required field + GraphQLRequest httpJsonRequest = JsonSerializeHelper.serializeRequest(gson, input); + OutboundConnectorContext context = + OutboundConnectorContextBuilder.create().variables(httpJsonRequest).build(); + // When context.validate(request); + // Then expect exception + ConnectorInputException thrown = + assertThrows( + ConnectorInputException.class, + () -> context.validate(httpJsonRequest), + "ConnectorInputException was expected"); + assertThat(thrown.getMessage()).contains("Found constraints violated while validating input"); + } + + @ParameterizedTest(name = "Success validate connectionTimeout # {index}") + @MethodSource("successTimeOutConnectionCases") + void validate_shouldValidateWithoutException(String input) { + // Given request without one required field + GraphQLRequest httpJsonRequest = JsonSerializeHelper.serializeRequest(gson, input); + OutboundConnectorContext context = + OutboundConnectorContextBuilder.create().variables(httpJsonRequest).build(); + // When context.validate(request); + // Then expect normal validate without exception + context.validate(httpJsonRequest); + } + + protected static Stream failRequestCases() throws IOException { + return loadTestCasesFromResourceFile(FAIL_REQUEST_CASES_PATH); + } + + private static Stream failTimeOutConnectionCases() throws IOException { + return loadTestCasesFromResourceFile(FAIL_CASES_TIMEOUT_CONNECTION_RESOURCE_PATH); + } + + private static Stream successTimeOutConnectionCases() throws IOException { + return loadTestCasesFromResourceFile(SUCCESS_CASES_TIMEOUT_CONNECTION_RESOURCE_PATH); + } +} diff --git a/connectors/graphql/src/test/java/io/camunda/connector/graphql/GraphQLFunctionSecretsTest.java b/connectors/graphql/src/test/java/io/camunda/connector/graphql/GraphQLFunctionSecretsTest.java new file mode 100644 index 0000000000..b0f62f31e2 --- /dev/null +++ b/connectors/graphql/src/test/java/io/camunda/connector/graphql/GraphQLFunctionSecretsTest.java @@ -0,0 +1,135 @@ +/* + * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH + * under one or more contributor license agreements. Licensed under a proprietary license. + * See the License.txt file for more information. You may not use this file + * except in compliance with the proprietary license. + */ +package io.camunda.connector.graphql; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.google.gson.JsonObject; +import io.camunda.connector.api.outbound.OutboundConnectorContext; +import io.camunda.connector.common.auth.Authentication; +import io.camunda.connector.common.auth.BasicAuthentication; +import io.camunda.connector.common.auth.BearerAuthentication; +import io.camunda.connector.common.auth.NoAuthentication; +import io.camunda.connector.common.auth.OAuthAuthentication; +import io.camunda.connector.graphql.model.GraphQLRequest; +import io.camunda.connector.graphql.utils.JsonSerializeHelper; +import java.io.IOException; +import java.util.stream.Stream; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +public class GraphQLFunctionSecretsTest extends BaseTest { + private static final String SUCCESS_REPLACE_SECRETS_CASES_PATH = + "src/test/resources/requests/success-cases-replace-secrets.json"; + + private OutboundConnectorContext context; + + protected static Stream successReplaceSecretsCases() throws IOException { + return loadTestCasesFromResourceFile(SUCCESS_REPLACE_SECRETS_CASES_PATH); + } + + @ParameterizedTest(name = "Should replace request secrets") + @MethodSource("successReplaceSecretsCases") + void replaceSecrets_shouldReplaceRequestSecrets(String input) { + // Given request with secrets + GraphQLRequest graphQLRequest = JsonSerializeHelper.serializeRequest(gson, input); + context = getContextBuilderWithSecrets().variables(graphQLRequest).build(); + // When + context.replaceSecrets(graphQLRequest); + // Then should replace secrets + assertThat(graphQLRequest.getUrl()).isEqualTo(ActualValue.URL); + assertThat(graphQLRequest.getMethod()).isEqualTo(ActualValue.METHOD); + assertThat(graphQLRequest.getConnectionTimeoutInSeconds()) + .isEqualTo(ActualValue.CONNECT_TIMEOUT); + } + + @ParameterizedTest(name = "Should replace auth secrets") + @MethodSource("successReplaceSecretsCases") + void replaceSecrets_shouldReplaceAuthSecrets(String input) { + // Given request with secrets + GraphQLRequest graphQLRequest = JsonSerializeHelper.serializeRequest(gson, input); + context = getContextBuilderWithSecrets().variables(graphQLRequest).build(); + // When + context.replaceSecrets(graphQLRequest); + // Then should replace secrets + Authentication authentication = graphQLRequest.getAuthentication(); + if (authentication instanceof NoAuthentication) { + // nothing check in this case + } else if (authentication instanceof BearerAuthentication) { + BearerAuthentication bearerAuth = (BearerAuthentication) authentication; + assertThat(bearerAuth.getToken()).isEqualTo(ActualValue.Authentication.TOKEN); + } else if (authentication instanceof BasicAuthentication) { + BasicAuthentication basicAuth = (BasicAuthentication) authentication; + assertThat(basicAuth.getPassword()).isEqualTo(ActualValue.Authentication.PASSWORD); + assertThat(basicAuth.getUsername()).isEqualTo(ActualValue.Authentication.USERNAME); + } else if (authentication instanceof OAuthAuthentication) { + OAuthAuthentication oAuthAuthentication = (OAuthAuthentication) authentication; + assertThat(oAuthAuthentication.getOauthTokenEndpoint()) + .isEqualTo(ActualValue.Authentication.OAUTH_TOKEN_ENDPOINT); + assertThat(oAuthAuthentication.getClientId()).isEqualTo(ActualValue.Authentication.CLIENT_ID); + assertThat(oAuthAuthentication.getClientSecret()) + .isEqualTo(ActualValue.Authentication.CLIENT_SECRET); + assertThat(oAuthAuthentication.getAudience()).isEqualTo(ActualValue.Authentication.AUDIENCE); + } else { + fail("unknown authentication type"); + } + } + + @ParameterizedTest(name = "Should replace variables secrets") + @MethodSource("successReplaceSecretsCases") + void replaceSecrets_shouldReplaceVariablesSecrets(String input) { + // Given request with secrets + // GraphQLRequestWrapper graphQLRequest = gson.fromJson(input, GraphQLRequestWrapper.class); + GraphQLRequest graphQLRequest = JsonSerializeHelper.serializeRequest(gson, input); + context = getContextBuilderWithSecrets().variables(graphQLRequest).build(); + // When + context.replaceSecrets(graphQLRequest); + // Then should replace secrets + JsonObject variables = gson.toJsonTree(graphQLRequest.getVariables()).getAsJsonObject(); + + assertThat(variables.get(JsonKeys.ID).getAsString()).isEqualTo(ActualValue.Variables.ID); + } + + @ParameterizedTest(name = "Should replace query secrets") + @MethodSource("successReplaceSecretsCases") + void replaceSecrets_shouldReplaceQuerySecrets(String input) { + // Given request with secrets + GraphQLRequest graphQLRequest = JsonSerializeHelper.serializeRequest(gson, input); + context = getContextBuilderWithSecrets().variables(graphQLRequest).build(); + // When + context.replaceSecrets(graphQLRequest); + // Then should replace secrets + String query = graphQLRequest.getQuery(); + assertFalse(query.contains("{{secrets.QUERY_ID}}")); + assertTrue(query.contains(ActualValue.Query.ID)); + } + + @Test + void replaceSecrets_shouldReplaceQueryWhenQueryIsString() { + // Given request with secrets + GraphQLRequest request = new GraphQLRequest(); + request.setQuery( + "{{secrets." + + SecretsConstant.Query.TEXT_PART_1 + + "}}" + + "{{secrets." + + SecretsConstant.Query.TEXT_PART_2 + + "}}" + + "{{secrets." + + SecretsConstant.Query.TEXT_PART_3 + + "}}"); + context = getContextBuilderWithSecrets().variables(request).build(); + // When + context.replaceSecrets(request); + // Then should replace secrets + assertThat(request.getQuery().toString()).isEqualTo(ActualValue.Query.TEXT); + } +} diff --git a/connectors/graphql/src/test/java/io/camunda/connector/graphql/GraphQLFunctionTest.java b/connectors/graphql/src/test/java/io/camunda/connector/graphql/GraphQLFunctionTest.java new file mode 100644 index 0000000000..af793b8178 --- /dev/null +++ b/connectors/graphql/src/test/java/io/camunda/connector/graphql/GraphQLFunctionTest.java @@ -0,0 +1,212 @@ +/* + * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH + * under one or more contributor license agreements. Licensed under a proprietary license. + * See the License.txt file for more information. You may not use this file + * except in compliance with the proprietary license. + */ +package io.camunda.connector.graphql; + +import static org.apache.http.entity.ContentType.APPLICATION_JSON; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.catchException; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.nullable; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import com.google.api.client.http.GenericUrl; +import com.google.api.client.http.HttpContent; +import com.google.api.client.http.HttpHeaders; +import com.google.api.client.http.HttpRequest; +import com.google.api.client.http.HttpRequestFactory; +import com.google.api.client.http.HttpResponse; +import com.google.api.client.http.HttpResponseException; +import com.google.gson.JsonObject; +import io.camunda.connector.api.error.ConnectorException; +import io.camunda.connector.graphql.model.GraphQLRequest; +import io.camunda.connector.graphql.model.GraphQLResult; +import io.camunda.connector.impl.ConnectorInputException; +import io.camunda.connector.test.outbound.OutboundConnectorContextBuilder; +import java.io.IOException; +import java.util.stream.Stream; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.provider.ValueSource; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +public class GraphQLFunctionTest extends BaseTest { + + private static final String SUCCESS_CASES_RESOURCE_PATH = + "src/test/resources/requests/success-test-cases.json"; + + private static final String SUCCESS_CASES_OAUTH_RESOURCE_PATH = + "src/test/resources/requests/success-test-cases-oauth.json"; + private static final String FAIL_CASES_RESOURCE_PATH = + "src/test/resources/requests/fail-test-cases.json"; + + @Mock private HttpRequestFactory requestFactory; + @Mock private HttpRequest httpRequest; + @Mock private HttpResponse httpResponse; + + private GraphQLFunction functionUnderTest; + + @BeforeEach + public void setup() { + functionUnderTest = new GraphQLFunction(gson, requestFactory, gsonFactory); + } + + @ParameterizedTest(name = "Executing test case: {0}") + @MethodSource("successCases") + void shouldReturnResult_WhenExecuted(final String input) + throws IOException, InstantiationException, IllegalAccessException { + // given - minimal required entity + Object functionCallResponseAsObject = arrange(input); + + // then + verify(httpRequest).execute(); + assertThat(functionCallResponseAsObject).isInstanceOf(GraphQLResult.class); + assertThat(((GraphQLResult) functionCallResponseAsObject).getHeaders()) + .containsValue(APPLICATION_JSON.getMimeType()); + } + + @ParameterizedTest(name = "Executing test case: {0}") + @MethodSource("successCasesOauth") + void shouldReturnResultOAuth_WhenExecuted(final String input) + throws IOException, InstantiationException, IllegalAccessException { + Object functionCallResponseAsObject = arrange(input); + + // then + verify(httpRequest, times(2)).execute(); + assertThat(functionCallResponseAsObject).isInstanceOf(GraphQLResult.class); + assertThat(((GraphQLResult) functionCallResponseAsObject).getHeaders()) + .containsValue(APPLICATION_JSON.getMimeType()); + } + + private Object arrange(String input) + throws IOException, InstantiationException, IllegalAccessException { + final var context = + OutboundConnectorContextBuilder.create().variables(input).secrets(name -> "foo").build(); + when(requestFactory.buildRequest( + anyString(), any(GenericUrl.class), nullable(HttpContent.class))) + .thenReturn(httpRequest); + when(httpResponse.getHeaders()) + .thenReturn(new HttpHeaders().setContentType(APPLICATION_JSON.getMimeType())); + when(httpRequest.execute()).thenReturn(httpResponse); + + // when + return functionUnderTest.execute(context); + } + + @ParameterizedTest(name = "Executing test case: {0}") + @MethodSource("failCases") + void shouldReturnFallbackResult_WhenMalformedRequest(final String input) { + final var context = + OutboundConnectorContextBuilder.create().variables(input).secrets(name -> "foo").build(); + + // when + var exceptionThrown = catchException(() -> functionUnderTest.execute(context)); + + // then + assertThat(exceptionThrown) + .isInstanceOf(ConnectorInputException.class) + .hasMessageContaining("ValidationException"); + } + + @ParameterizedTest(name = "Executing test case: {0}") + @MethodSource("successCases") + void execute_shouldSetConnectTime(final String input) + throws IOException, InstantiationException, IllegalAccessException { + // given - minimal required entity + final var context = + OutboundConnectorContextBuilder.create().variables(input).secrets(name -> "foo").build(); + final var expectedTimeInMilliseconds = + Integer.parseInt( + gson.fromJson( + gson.fromJson(input, JsonObject.class).get("graphql").toString(), + GraphQLRequest.class) + .getConnectionTimeoutInSeconds()) + * 1000; + + when(requestFactory.buildRequest( + anyString(), any(GenericUrl.class), nullable(HttpContent.class))) + .thenReturn(httpRequest); + when(httpResponse.getHeaders()) + .thenReturn(new HttpHeaders().setContentType(APPLICATION_JSON.getMimeType())); + when(httpRequest.execute()).thenReturn(httpResponse); + // when + functionUnderTest.execute(context); + // then + verify(httpRequest).setConnectTimeout(expectedTimeInMilliseconds); + } + + @ParameterizedTest + @ValueSource(ints = {400, 404, 500}) + void execute_shouldPassOnHttpErrorAsErrorCode(final int input) throws IOException { + // given + final var request = + "{\"graphql\": { \"method\": \"get\", \"url\": \"https://camunda.io/http-endpoint\", \"query\": \"testQuery\"}, \"authentication\": { \"type\": \"noAuth\" } }"; + final var context = OutboundConnectorContextBuilder.create().variables(request).build(); + + when(requestFactory.buildRequest( + anyString(), any(GenericUrl.class), nullable(HttpContent.class))) + .thenReturn(httpRequest); + when(httpResponse.getHeaders()) + .thenReturn(new HttpHeaders().setContentType(APPLICATION_JSON.getMimeType())); + when(httpResponse.getStatusCode()).thenReturn(input); + when(httpResponse.parseAsString()).thenReturn("message"); + doThrow(new HttpResponseException(httpResponse)).when(httpRequest).execute(); + // when + final var result = catchException(() -> functionUnderTest.execute(context)); + // then HTTP status code is passed on as error code + assertThat(result) + .isInstanceOf(ConnectorException.class) + .extracting("errorCode") + .isEqualTo(String.valueOf(input)); + } + + @Test + void execute_shouldNotUseErrorDataOnHttpError() throws IOException { + // given + final var request = + "{\"graphql\": {\"method\": \"get\", \"url\": \"https://camunda.io/http-endpoint\", \"query\": \"testQuery\"}, \"authentication\": { \"type\": \"noAuth\" } }"; + final var context = OutboundConnectorContextBuilder.create().variables(request).build(); + final var httpException = mock(HttpResponseException.class); + + when(requestFactory.buildRequest( + anyString(), any(GenericUrl.class), nullable(HttpContent.class))) + .thenReturn(httpRequest); + when(httpException.getStatusCode()).thenReturn(500); + when(httpException.getMessage()).thenReturn("message"); + doThrow(httpException).when(httpRequest).execute(); + // when + final var result = catchException(() -> functionUnderTest.execute(context)); + // then HTTP status code is passed on as error code + verify(httpException, times(0)).getContent(); + assertThat(result) + .isInstanceOf(ConnectorException.class) + .hasMessage("message") + .extracting("errorCode") + .isEqualTo("500"); + } + + private static Stream successCases() throws IOException { + return loadTestCasesFromResourceFile(SUCCESS_CASES_RESOURCE_PATH); + } + + private static Stream successCasesOauth() throws IOException { + return loadTestCasesFromResourceFile(SUCCESS_CASES_OAUTH_RESOURCE_PATH); + } + + private static Stream failCases() throws IOException { + return loadTestCasesFromResourceFile(FAIL_CASES_RESOURCE_PATH); + } +} diff --git a/connectors/graphql/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker b/connectors/graphql/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker new file mode 100644 index 0000000000..ca6ee9cea8 --- /dev/null +++ b/connectors/graphql/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker @@ -0,0 +1 @@ +mock-maker-inline \ No newline at end of file diff --git a/connectors/graphql/src/test/resources/requests/fail-cases-connection-timeout-validation.json b/connectors/graphql/src/test/resources/requests/fail-cases-connection-timeout-validation.json new file mode 100644 index 0000000000..9098aecca7 --- /dev/null +++ b/connectors/graphql/src/test/resources/requests/fail-cases-connection-timeout-validation.json @@ -0,0 +1,103 @@ +[ + { + "descriptionOfTest": "Negative value", + "graphql": { + "method": "get", + "url": "https://camunda.io/http-endpoint", + "connectionTimeoutInSeconds": "-1", + "authentication": { + "type": "noAuth" + } + } + }, + { + "descriptionOfTest": "Negative value", + "graphql": { + "method": "get", + "url": "https://camunda.io/http-endpoint", + "connectionTimeoutInSeconds": "-99", + "authentication": { + "type": "noAuth" + } + } + }, + { + "descriptionOfTest": "String value", + "graphql": { + "method": "get", + "url": "https://camunda.io/http-endpoint", + "connectionTimeoutInSeconds": "bad value", + "authentication": { + "type": "noAuth" + } + } + }, + { + "descriptionOfTest": "Normal value with space", + "graphql": { + "method": "get", + "url": "https://camunda.io/http-endpoint", + "connectionTimeoutInSeconds": "23 ", + "authentication": { + "type": "noAuth" + } + } + }, + { + "descriptionOfTest": "Blank value", + "graphql": { + "method": "get", + "url": "https://camunda.io/http-endpoint", + "connectionTimeoutInSeconds": " ", + "authentication": { + "type": "noAuth" + } + } + }, + { + "descriptionOfTest": "Float value", + "graphql": { + "method": "get", + "url": "https://camunda.io/http-endpoint", + "connectionTimeoutInSeconds": "1.56", + "authentication": { + "type": "bearer", + "token": "secrets.TOKEN_KEY" + } + } + }, + { + "descriptionOfTest": "Float value", + "graphql": { + "method": "get", + "url": "https://camunda.io/http-endpoint", + "connectionTimeoutInSeconds": "0.1", + "authentication": { + "type": "noAuth" + } + } + }, + { + "descriptionOfTest": "Binary value", + "graphql": { + "method": "get", + "url": "https://camunda.io/http-endpoint", + "connectionTimeoutInSeconds": "0xFF", + "authentication": { + "type": "noAuth" + } + } + }, + { + "descriptionOfTest": "Binary value", + "graphql": { + "method": "get", + "url": "https://camunda.io/http-endpoint", + "connectionTimeoutInSeconds": "0b0011", + "authentication": { + "type": "bearer", + "token": "secrets.TOKEN_KEY" + } + } + } +] \ No newline at end of file diff --git a/connectors/graphql/src/test/resources/requests/fail-cases-request-without-one-required-field.json b/connectors/graphql/src/test/resources/requests/fail-cases-request-without-one-required-field.json new file mode 100644 index 0000000000..e5770cb394 --- /dev/null +++ b/connectors/graphql/src/test/resources/requests/fail-cases-request-without-one-required-field.json @@ -0,0 +1,178 @@ +[ + { + "testDescription": "without method field", + "authentication": { + "type": "noAuth" + }, + "graphql": { + "url": "testmail@testmail.com", + "query": "query Root($id: ID) {\n person (id: $id) {\n id\n name\n }\n}", + "variables": { + "id": "cGVvcGxlOjI=" + } + } + }, + { + "testDescription": "without URL field", + "authentication": { + "type": "noAuth" + }, + "graphql": { + "method": "get", + "query": "query Root($id: ID) {\n person (id: $id) {\n id\n name\n }\n}", + "variables": { + "id": "cGVvcGxlOjI=" + } + } + }, + { + "testDescription": "bearer auth type without URL field", + "authentication": { + "type": "bearer" + }, + "graphql": { + "method": "get", + "query": "query Root($id: ID) {\n person (id: $id) {\n id\n name\n }\n}", + "variables": { + "id": "cGVvcGxlOjI=" + } + } + }, + { + "testDescription": "basic auth type without URL field", + "authentication": { + "type": "basic" + }, + "graphql": { + "method": "get", + "query": "query Root($id: ID) {\n person (id: $id) {\n id\n name\n }\n}", + "variables": { + "id": "cGVvcGxlOjI=" + } + } + }, + { + "testDescription": "bearer auth without token", + "authentication": { + "type": "bearer" + }, + "graphql": { + "method": "get", + "url": "testmail@testmail.com", + "query": "query Root($id: ID) {\n person (id: $id) {\n id\n name\n }\n}", + "variables": { + "id": "cGVvcGxlOjI=" + } + } + }, + { + "testDescription": "basic auth without password", + "authentication": { + "type": "basic", + "username": "username" + }, + "graphql": { + "method": "get", + "url": "testmail@testmail.com", + "query": "query Root($id: ID) {\n person (id: $id) {\n id\n name\n }\n}", + "variables": { + "id": "cGVvcGxlOjI=" + } + } + }, + { + "testDescription": "basic auth without username", + "authentication": { + "type": "basic", + "password": "password" + }, + "graphql": { + "method": "get", + "url": "testmail@testmail.com", + "query": "query Root($id: ID) {\n person (id: $id) {\n id\n name\n }\n}", + "variables": { + "id": "cGVvcGxlOjI=" + } + } + }, + { + "descriptionOfTest": "oauth request without client id", + "authentication":{ + "oauthTokenEndpoint":"https://abc.eu.auth0.com/api/v2/", + "scopes": "read:clients", + "audience":"https://abc.eu.auth0.com/api/v2/", + "clientSecret":"secrets.CLIENT_SECRET", + "type":"oauth-client-credentials-flow", + "clientAuthentication":"basicAuthHeader" + }, + "graphql": { + "method": "post", + "url": "https://abc/def", + "connectionTimeoutInSeconds": "30", + "query": "query Root($id: ID) {\n person (id: $id) {\n id\n name\n }\n}", + "variables": { + "id": "cGVvcGxlOjI=" + } + } + }, + { + "descriptionOfTest": "oauth request without client secret", + "authentication":{ + "oauthTokenEndpoint":"https://abc.eu.auth0.com/api/v2/", + "scopes": "read:clients", + "audience":"https://abc.eu.auth0.com/api/v2/", + "clientId":"secrets.CLIENT_ID", + "type":"oauth-client-credentials-flow", + "clientAuthentication":"basicAuthHeader" + }, + "graphql": { + "method": "post", + "url": "https://abc/def", + "connectionTimeoutInSeconds": "30", + "query": "query Root($id: ID) {\n person (id: $id) {\n id\n name\n }\n}", + "variables": { + "id": "cGVvcGxlOjI=" + } + } + }, + { + "descriptionOfTest": "oauth request without oauth token endpoint", + "authentication":{ + "scopes": "read:clients", + "audience":"https://abc.eu.auth0.com/api/v2/", + "clientId":"secrets.CLIENT_ID", + "clientSecret":"secrets.CLIENT_SECRET", + "type":"oauth-client-credentials-flow", + "clientAuthentication":"basicAuthHeader" + }, + "graphql": { + "method": "post", + "url": "https://abc/def", + "connectionTimeoutInSeconds": "30", + "query": "query Root($id: ID) {\n person (id: $id) {\n id\n name\n }\n}", + "variables": { + "id": "cGVvcGxlOjI=" + } + } + }, + { + "descriptionOfTest": "oauth request without client authentication", + "authentication":{ + "oauthTokenEndpoint":"https://abc.eu.auth0.com/api/v2/", + "scopes": "read:clients", + "audience":"https://abc.eu.auth0.com/api/v2/", + "clientId":"secrets.CLIENT_ID", + "clientSecret":"secrets.CLIENT_SECRET", + "type":"oauth-client-credentials-flow" + }, + "graphql": { + "method": "post", + "url": "https://abc/def", + "connectionTimeoutInSeconds": "30", + "query": "query Root($id: ID) {\n person (id: $id) {\n id\n name\n }\n}", + "variables": { + "id": "cGVvcGxlOjI=" + } + } + } +] \ No newline at end of file diff --git a/connectors/graphql/src/test/resources/requests/fail-test-cases.json b/connectors/graphql/src/test/resources/requests/fail-test-cases.json new file mode 100644 index 0000000000..4497488fc2 --- /dev/null +++ b/connectors/graphql/src/test/resources/requests/fail-test-cases.json @@ -0,0 +1,33 @@ +[ + { + "descriptionOfTest": "No method", + "graphql": { + "url": "https://camunda.io/http-endpoint", + "query": "query Root($id: ID) {\n person (id: $id) {\n id\n name\n }\n}", + "variables": { + "id": "cGVvcGxlOjI=" + } + } + }, + { + "descriptionOfTest": "No URL", + "graphql": { + "method": "get", + "query": "query Root($id: ID) {\n person (id: $id) {\n id\n name\n }\n}", + "variables": { + "id": "cGVvcGxlOjI=" + } + } + }, + { + "descriptionOfTest": "Malformed URL", + "graphql": { + "method": "get", + "url": "testmail@testmail.com", + "query": "query Root($id: ID) {\n person (id: $id) {\n id\n name\n }\n}", + "variables": { + "id": "cGVvcGxlOjI=" + } + } + } +] \ No newline at end of file diff --git a/connectors/graphql/src/test/resources/requests/success-cases-connection-timeout-validation.json b/connectors/graphql/src/test/resources/requests/success-cases-connection-timeout-validation.json new file mode 100644 index 0000000000..5a839b3d3a --- /dev/null +++ b/connectors/graphql/src/test/resources/requests/success-cases-connection-timeout-validation.json @@ -0,0 +1,53 @@ +[ + { + "descriptionOfTest": "0 value", + "graphql": { + "method": "get", + "url": "https://camunda.io/http-endpoint", + "query": "query Root($id: ID) {\n person (id: $id) {\n id\n name\n }\n}", + "connectionTimeoutInSeconds": "0" + }, + "authentication": { + "type": "noAuth" + } + }, + { + "descriptionOfTest": "0 value with bearer auth", + "graphql": { + "method": "get", + "url": "https://camunda.io/http-endpoint", + "query": "query Root($id: ID) {\n person (id: $id) {\n id\n name\n }\n}", + "connectionTimeoutInSeconds": "0" + }, + "authentication": { + "type": "bearer", + "token": "secrets.TOKEN_KEY" + } + }, + { + "descriptionOfTest": "positive value", + "graphql": { + "method": "get", + "url": "https://camunda.io/http-endpoint", + "query": "query Root($id: ID) {\n person (id: $id) {\n id\n name\n }\n}", + "connectionTimeoutInSeconds": "99" + }, + "authentication": { + "type": "bearer", + "token": "secrets.TOKEN_KEY" + } + }, + { + "descriptionOfTest": "secrets value", + "graphql": { + "method": "get", + "url": "https://camunda.io/http-endpoint", + "query": "query Root($id: ID) {\n person (id: $id) {\n id\n name\n }\n}", + "connectionTimeoutInSeconds": "secrets.SOME_SECRET_KEY" + }, + "authentication": { + "type": "bearer", + "token": "secrets.TOKEN_KEY" + } + } +] \ No newline at end of file diff --git a/connectors/graphql/src/test/resources/requests/success-cases-replace-secrets.json b/connectors/graphql/src/test/resources/requests/success-cases-replace-secrets.json new file mode 100644 index 0000000000..901ae3db00 --- /dev/null +++ b/connectors/graphql/src/test/resources/requests/success-cases-replace-secrets.json @@ -0,0 +1,67 @@ +[ + { + "graphql": { + "url": "secrets.URL_KEY", + "method": "secrets.METHOD_KEY", + "connectionTimeoutInSeconds":"secrets.CONNECT_TIMEOUT_KEY", + "query": "query Root($id: {{secrets.QUERY_ID}}) {\n person (id: $id) {\n id\n name\n }\n}", + "variables": { + "id": "{{secrets.VARIABLE_ID}}" + } + }, + "authentication": { + "type": "noAuth" + } + }, + { + "graphql": { + "url": "secrets.URL_KEY", + "method": "secrets.METHOD_KEY", + "connectionTimeoutInSeconds":"secrets.CONNECT_TIMEOUT_KEY", + "query": "query Root($id: {{secrets.QUERY_ID}}) {\n person (id: $id) {\n id\n name\n }\n}", + "variables": { + "id": "{{secrets.VARIABLE_ID}}" + } + }, + "authentication": { + "type": "bearer", + "token": "secrets.TOKEN_KEY" + } + }, + { + "graphql": { + "url": "secrets.URL_KEY", + "method": "secrets.METHOD_KEY", + "connectionTimeoutInSeconds":"secrets.CONNECT_TIMEOUT_KEY", + "query": "query Root($id: {{secrets.QUERY_ID}}) {\n person (id: $id) {\n id\n name\n }\n}", + "variables": { + "id": "{{secrets.VARIABLE_ID}}" + } + }, + "authentication": { + "type": "basic", + "password": "secrets.PASSWORD_KEY", + "username": "secrets.USERNAME_KEY" + } + }, + { + "graphql": { + "url": "secrets.URL_KEY", + "method": "secrets.METHOD_KEY", + "connectionTimeoutInSeconds":"secrets.CONNECT_TIMEOUT_KEY", + "query": "query Root($id: {{secrets.QUERY_ID}}) {\n person (id: $id) {\n id\n name\n }\n}", + "variables": { + "id": "{{secrets.VARIABLE_ID}}" + } + }, + "authentication": { + "oauthTokenEndpoint":"secrets.OAUTH_TOKEN_ENDPOINT_KEY", + "scopes": "test", + "audience":"secrets.AUDIENCE_KEY", + "clientId":"secrets.CLIENT_ID_KEY", + "clientSecret":"secrets.CLIENT_SECRET_KEY", + "type": "oauth-client-credentials-flow", + "clientAuthentication":"secrets.CLIENT_AUTHENTICATION_KEY" + } + } +] \ No newline at end of file diff --git a/connectors/graphql/src/test/resources/requests/success-test-cases-oauth.json b/connectors/graphql/src/test/resources/requests/success-test-cases-oauth.json new file mode 100644 index 0000000000..8286e07c5d --- /dev/null +++ b/connectors/graphql/src/test/resources/requests/success-test-cases-oauth.json @@ -0,0 +1,22 @@ +[{ + "descriptionOfTest": "Normal request with oauth", + "authentication":{ + "oauthTokenEndpoint":"https://dev-test.eu.auth0.com/api/v2/", + "scopes": "read:clients", + "audience":"https://dev-test.eu.auth0.com/api/v2/", + "clientId":"secrets.CLIENT_ID", + "clientSecret":"secrets.CLIENT_SECRET", + "type":"oauth-client-credentials-flow", + "clientAuthentication":"basicAuthHeader" + }, + "graphql": { + "method": "post", + "url": "https://camunda.io/http-endpoint", + "connectionTimeoutInSeconds": "30", + "query": "query Root($id: ID) {\n person (id: $id) {\n id\n name\n }\n}", + "variables": { + "id": "cGVvcGxlOjI=" + } + } +} +] \ No newline at end of file diff --git a/connectors/graphql/src/test/resources/requests/success-test-cases.json b/connectors/graphql/src/test/resources/requests/success-test-cases.json new file mode 100644 index 0000000000..23f95d02fe --- /dev/null +++ b/connectors/graphql/src/test/resources/requests/success-test-cases.json @@ -0,0 +1,88 @@ +[ + { + "descriptionOfTest": "Normal request with no auth", + "authentication": { + "type": "noAuth" + }, + "graphql": { + "method": "get", + "url": "https://camunda.io/http-endpoint", + "connectionTimeoutInSeconds": "20", + "query": "query Root($id: ID) {\n person (id: $id) {\n id\n name\n }\n}", + "variables": { + "id": "cGVvcGxlOjI=" + } + } + }, + { + "descriptionOfTest": "Normal request with basic auth", + "graphql": { + "method": "get", + "url": "https://camunda.io/http-endpoint", + "connectionTimeoutInSeconds": "0", + "query": "query Root($id: ID) {\n person (id: $id) {\n id\n name\n }\n}", + "variables": { + "id": "cGVvcGxlOjI=" + } + } + }, + { + "descriptionOfTest": "Normal request with bearer auth", + "authentication": { + "type": "bearer", + "token": "secrets.MY_TOKEN" + }, + "graphql": { + "method": "get", + "url": "https://camunda.io/http-endpoint", + "connectionTimeoutInSeconds": "30", + "query": "query Root($id: ID) {\n person (id: $id) {\n id\n name\n }\n}", + "variables": { + "id": "cGVvcGxlOjI=" + } + } + }, + { + "descriptionOfTest": "Normal request with no variables", + "authentication": { + "type": "bearer", + "token": "secrets.MY_TOKEN" + }, + "graphql": { + "method": "get", + "url": "https://camunda.io/http-endpoint", + "connectionTimeoutInSeconds": "200", + "query": "query Root($id: ID) {\n person (id: $id) {\n id\n name\n }\n}" + } + }, + { + "descriptionOfTest": "Normal request with empty variables", + "authentication": { + "type": "bearer", + "token": "secrets.MY_TOKEN" + }, + "graphql": { + "method": "get", + "url": "https://camunda.io/http-endpoint", + "connectionTimeoutInSeconds": "0", + "query": "query Root($id: ID) {\n person (id: $id) {\n id\n name\n }\n}", + "variables": {} + } + }, + { + "descriptionOfTest": "Normal request with connectionTimeoutInSeconds", + "authentication": { + "type": "bearer", + "token": "secrets.MY_TOKEN" + }, + "graphql": { + "method": "get", + "url": "https://camunda.io/http-endpoint", + "connectionTimeoutInSeconds": "50", + "query": "query Root($id: ID) {\n person (id: $id) {\n id\n name\n }\n}", + "variables": { + "id": "cGVvcGxlOjI=" + } + } + } +] diff --git a/connectors/http-json/pom.xml b/connectors/http-json/pom.xml index 01e5d54844..65c1ab43be 100644 --- a/connectors/http-json/pom.xml +++ b/connectors/http-json/pom.xml @@ -62,6 +62,13 @@ limitations under the License. org.slf4j jcl-over-slf4j + + + + io.camunda.connector + connectors-common-library + 0.16.0-SNAPSHOT + diff --git a/connectors/http-json/src/main/java/io/camunda/connector/http/HttpJsonFunction.java b/connectors/http-json/src/main/java/io/camunda/connector/http/HttpJsonFunction.java index 6bd71835a5..4c609728eb 100644 --- a/connectors/http-json/src/main/java/io/camunda/connector/http/HttpJsonFunction.java +++ b/connectors/http-json/src/main/java/io/camunda/connector/http/HttpJsonFunction.java @@ -21,9 +21,9 @@ import io.camunda.connector.api.annotation.OutboundConnector; import io.camunda.connector.api.outbound.OutboundConnectorContext; import io.camunda.connector.api.outbound.OutboundConnectorFunction; +import io.camunda.connector.common.constants.Constants; import io.camunda.connector.http.components.GsonComponentSupplier; import io.camunda.connector.http.components.HttpTransportComponentSupplier; -import io.camunda.connector.http.constants.Constants; import io.camunda.connector.http.model.HttpJsonRequest; import io.camunda.connector.impl.config.ConnectorConfigurationUtil; import java.io.IOException; @@ -63,7 +63,8 @@ public HttpJsonFunction( } @Override - public Object execute(final OutboundConnectorContext context) throws IOException { + public Object execute(final OutboundConnectorContext context) + throws IOException, InstantiationException, IllegalAccessException { final var json = context.getVariables(); final var request = gson.fromJson(json, HttpJsonRequest.class); diff --git a/connectors/http-json/src/main/java/io/camunda/connector/http/HttpRequestMapper.java b/connectors/http-json/src/main/java/io/camunda/connector/http/HttpRequestMapper.java index 2de84be953..5add9a8f45 100644 --- a/connectors/http-json/src/main/java/io/camunda/connector/http/HttpRequestMapper.java +++ b/connectors/http-json/src/main/java/io/camunda/connector/http/HttpRequestMapper.java @@ -27,16 +27,19 @@ import com.google.api.client.http.UrlEncodedContent; import com.google.api.client.http.json.JsonHttpContent; import com.google.gson.Gson; -import io.camunda.connector.http.auth.OAuthAuthentication; +import io.camunda.connector.common.auth.OAuthAuthentication; +import io.camunda.connector.common.constants.Constants; +import io.camunda.connector.common.model.CommonRequest; import io.camunda.connector.http.auth.ProxyOAuthHelper; import io.camunda.connector.http.components.GsonComponentSupplier; -import io.camunda.connector.http.constants.Constants; import io.camunda.connector.http.model.HttpJsonRequest; import io.camunda.connector.http.model.HttpRequestBuilder; import io.camunda.connector.impl.ConnectorInputException; import java.io.IOException; import java.io.OutputStream; import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; import javax.validation.ValidationException; public class HttpRequestMapper { @@ -89,21 +92,34 @@ public static HttpRequest toOAuthHttpRequest( return new HttpRequestBuilder() .method(Constants.POST) .genericUrl(new GenericUrl(authentication.getOauthTokenEndpoint())) - .content(new UrlEncodedContent(authentication.getDataForAuthRequestBody())) + .content(new UrlEncodedContent(getDataForAuthRequestBody(authentication))) .headers(headers) .connectionTimeoutInSeconds(request.getConnectionTimeoutInSeconds()) .followRedirects(false) .build(requestFactory); } + public static Map getDataForAuthRequestBody(OAuthAuthentication authentication) { + Map data = new HashMap<>(); + data.put(Constants.GRANT_TYPE, authentication.getGrantType()); + data.put(Constants.AUDIENCE, authentication.getAudience()); + data.put(Constants.SCOPE, authentication.getScopes()); + + if (Constants.CREDENTIALS_BODY.equals(authentication.getClientAuthentication())) { + data.put(Constants.CLIENT_ID, authentication.getClientId()); + data.put(Constants.CLIENT_SECRET, authentication.getClientSecret()); + } + return data; + } + public static HttpRequest toHttpRequest( - final HttpRequestFactory requestFactory, final HttpJsonRequest request) throws IOException { + final HttpRequestFactory requestFactory, final CommonRequest request) throws IOException { return toHttpRequest(requestFactory, request, null); } public static HttpRequest toHttpRequest( final HttpRequestFactory requestFactory, - final HttpJsonRequest request, + final CommonRequest request, final String bearerToken) throws IOException { // TODO: add more holistic solution @@ -131,7 +147,7 @@ public static HttpRequest toHttpRequest( .build(requestFactory); } - private static HttpHeaders createHeaders(final HttpJsonRequest request, String bearerToken) { + private static HttpHeaders createHeaders(final CommonRequest request, String bearerToken) { final HttpHeaders httpHeaders = new HttpHeaders(); if (request.hasBody()) { httpHeaders.setContentType(APPLICATION_JSON.getMimeType()); diff --git a/connectors/http-json/src/main/java/io/camunda/connector/http/HttpResponseMapper.java b/connectors/http-json/src/main/java/io/camunda/connector/http/HttpResponseMapper.java deleted file mode 100644 index 8efbaf46c7..0000000000 --- a/connectors/http-json/src/main/java/io/camunda/connector/http/HttpResponseMapper.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH - * under one or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information regarding copyright - * ownership. Camunda licenses this file to you under the Apache License, - * Version 2.0; you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.camunda.connector.http; - -import com.google.api.client.http.HttpResponse; -import com.google.gson.Gson; -import io.camunda.connector.http.components.GsonComponentSupplier; -import io.camunda.connector.http.model.HttpJsonResult; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.Reader; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class HttpResponseMapper { - - private static final Logger LOGGER = LoggerFactory.getLogger(HttpResponseMapper.class); - private static final Gson gson = GsonComponentSupplier.gsonInstance(); - - private HttpResponseMapper() {} - - public static HttpJsonResult toHttpJsonResponse(final HttpResponse externalResponse) { - final HttpJsonResult httpJsonResult = new HttpJsonResult(); - httpJsonResult.setStatus(externalResponse.getStatusCode()); - final Map headers = new HashMap<>(); - externalResponse - .getHeaders() - .forEach( - (k, v) -> { - if (v instanceof List && ((List) v).size() == 1) { - headers.put(k, ((List) v).get(0)); - } else { - headers.put(k, v); - } - }); - httpJsonResult.setHeaders(headers); - try (InputStream content = externalResponse.getContent(); - Reader reader = new InputStreamReader(content)) { - final Object body = gson.fromJson(reader, Object.class); - if (body != null) { - httpJsonResult.setBody(body); - } - } catch (final Exception e) { - LOGGER.error("Failed to parse external response: {}", externalResponse, e); - } - return httpJsonResult; - } -} diff --git a/connectors/http-json/src/main/java/io/camunda/connector/http/HttpService.java b/connectors/http-json/src/main/java/io/camunda/connector/http/HttpService.java index 220ee5e3d9..05ac116055 100644 --- a/connectors/http-json/src/main/java/io/camunda/connector/http/HttpService.java +++ b/connectors/http-json/src/main/java/io/camunda/connector/http/HttpService.java @@ -19,21 +19,18 @@ import com.google.api.client.http.HttpRequest; import com.google.api.client.http.HttpRequestFactory; import com.google.api.client.http.HttpResponse; -import com.google.api.client.http.HttpResponseException; import com.google.gson.Gson; -import com.google.gson.JsonObject; import io.camunda.connector.api.error.ConnectorException; -import io.camunda.connector.http.auth.CustomAuthentication; -import io.camunda.connector.http.auth.OAuthAuthentication; -import io.camunda.connector.http.model.ErrorResponse; +import io.camunda.connector.common.auth.CustomAuthentication; +import io.camunda.connector.common.auth.OAuthAuthentication; +import io.camunda.connector.common.services.AuthenticationService; +import io.camunda.connector.common.services.HTTPService; import io.camunda.connector.http.model.HttpJsonRequest; import io.camunda.connector.http.model.HttpJsonResult; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; -import java.util.HashMap; -import java.util.Map; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -51,24 +48,28 @@ public HttpService( this.proxyFunctionUrl = proxyFunctionUrl; } - public Object executeConnectorRequest(final HttpJsonRequest request) throws IOException { + public Object executeConnectorRequest(final HttpJsonRequest request) + throws IOException, InstantiationException, IllegalAccessException { return proxyFunctionUrl == null ? executeRequestDirectly(request) : executeRequestViaProxy(request); } - private HttpJsonResult executeRequestDirectly(HttpJsonRequest request) throws IOException { + private HttpJsonResult executeRequestDirectly(HttpJsonRequest request) + throws IOException, InstantiationException, IllegalAccessException { String bearerToken = null; + HTTPService httpService = new HTTPService(gson); + AuthenticationService authService = new AuthenticationService(gson, requestFactory); if (request.getAuthentication() != null) { if (request.getAuthentication() instanceof OAuthAuthentication) { - bearerToken = getTokenFromOAuthRequest(request); + bearerToken = getTokenFromOAuthRequest(request, httpService, authService); } else if (request.getAuthentication() instanceof CustomAuthentication) { final var authentication = (CustomAuthentication) request.getAuthentication(); final var httpRequest = HttpRequestMapper.toHttpRequest(requestFactory, authentication.getRequest()); - HttpResponse httpResponse = executeHttpRequest(httpRequest); + HttpResponse httpResponse = httpService.executeHttpRequest(httpRequest); if (httpResponse.isSuccessStatusCode()) { - fillRequestFromCustomAuthResponseData(request, authentication, httpResponse); + authService.fillRequestFromCustomAuthResponseData(request, authentication, httpResponse); } else { throw new RuntimeException( "Authenticate is fail; status code : [" @@ -80,74 +81,27 @@ private HttpJsonResult executeRequestDirectly(HttpJsonRequest request) throws IO } } HttpRequest httpRequest = HttpRequestMapper.toHttpRequest(requestFactory, request, bearerToken); - HttpResponse httpResponse = executeHttpRequest(httpRequest, false); - return HttpResponseMapper.toHttpJsonResponse(httpResponse); + HttpResponse httpResponse = httpService.executeHttpRequest(httpRequest, false); + return httpService.toHttpJsonResponse(httpResponse, HttpJsonResult.class); } - private void fillRequestFromCustomAuthResponseData( - final HttpJsonRequest request, - final CustomAuthentication authentication, - final HttpResponse httpResponse) + private String getTokenFromOAuthRequest( + final HttpJsonRequest connectorRequest, + final HTTPService httpService, + final AuthenticationService authService) throws IOException { - String strResponse = httpResponse.parseAsString(); - Map headers = - ResponseParser.extractPropertiesFromBody(authentication.getOutputHeaders(), strResponse); - if (headers != null) { - if (!request.hasHeaders()) { - request.setHeaders(new HashMap<>()); - } - request.getHeaders().putAll(headers); - } - - Map body = - ResponseParser.extractPropertiesFromBody(authentication.getOutputBody(), strResponse); - if (body != null) { - if (!request.hasBody()) { - request.setBody(new Object()); - } - JsonObject requestBody = gson.toJsonTree(request.getBody()).getAsJsonObject(); - // for now, we can add only string property to body, example of this object : - // "{"key":"value"}" but we can expand this method - body.forEach(requestBody::addProperty); - request.setBody(gson.fromJson(gson.toJson(requestBody), Object.class)); - } - } - - private String getTokenFromOAuthRequest(final HttpJsonRequest request) throws IOException { - final HttpRequest httpRequest = HttpRequestMapper.toOAuthHttpRequest(requestFactory, request); - final HttpResponse oauthResponse = executeHttpRequest(httpRequest); - return ResponseParser.extractOAuthAccessToken(oauthResponse); - } - - private HttpResponse executeHttpRequest(HttpRequest externalRequest) throws IOException { - return executeHttpRequest(externalRequest, false); - } - - private HttpResponse executeHttpRequest(HttpRequest externalRequest, boolean isProxyCall) - throws IOException { - try { - return externalRequest.execute(); - } catch (HttpResponseException hrex) { - var errorCode = String.valueOf(hrex.getStatusCode()); - var errorMessage = hrex.getMessage(); - if (isProxyCall && hrex.getContent() != null) { - try { - final var errorContent = gson.fromJson(hrex.getContent(), ErrorResponse.class); - errorCode = errorContent.getErrorCode(); - errorMessage = errorContent.getError(); - } catch (Exception e) { - // cannot be loaded as JSON, ignore and use plain message - } - } - throw new ConnectorException(errorCode, errorMessage, hrex); - } + final HttpRequest oauthRequest = authService.createOAuthRequest(connectorRequest); + final HttpResponse oauthResponse = httpService.executeHttpRequest(oauthRequest); + return authService.extractOAuthAccessToken(oauthResponse); } private HttpJsonResult executeRequestViaProxy(HttpJsonRequest request) throws IOException { HttpRequest httpRequest = HttpRequestMapper.toRequestViaProxy(requestFactory, request, proxyFunctionUrl); - HttpResponse httpResponse = executeHttpRequest(httpRequest, true); + HTTPService httpService = new HTTPService(gson); + + HttpResponse httpResponse = httpService.executeHttpRequest(httpRequest, true); try (InputStream responseContentStream = httpResponse.getContent(); Reader reader = new InputStreamReader(responseContentStream)) { diff --git a/connectors/http-json/src/main/java/io/camunda/connector/http/auth/Authentication.java b/connectors/http-json/src/main/java/io/camunda/connector/http/auth/Authentication.java deleted file mode 100644 index ff37b792ab..0000000000 --- a/connectors/http-json/src/main/java/io/camunda/connector/http/auth/Authentication.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH - * under one or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information regarding copyright - * ownership. Camunda licenses this file to you under the Apache License, - * Version 2.0; you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.camunda.connector.http.auth; - -import com.google.api.client.http.HttpHeaders; -import com.google.common.base.Objects; - -public abstract class Authentication { - - private transient String type; - - public abstract void setHeaders(HttpHeaders headers); - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - Authentication that = (Authentication) o; - return Objects.equal(type, that.type); - } - - @Override - public int hashCode() { - return Objects.hashCode(type); - } - - @Override - public String toString() { - return "Authentication{" + "type='" + type + '\'' + '}'; - } -} diff --git a/connectors/http-json/src/main/java/io/camunda/connector/http/auth/BasicAuthentication.java b/connectors/http-json/src/main/java/io/camunda/connector/http/auth/BasicAuthentication.java deleted file mode 100644 index ac8ee62bc6..0000000000 --- a/connectors/http-json/src/main/java/io/camunda/connector/http/auth/BasicAuthentication.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH - * under one or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information regarding copyright - * ownership. Camunda licenses this file to you under the Apache License, - * Version 2.0; you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.camunda.connector.http.auth; - -import com.google.api.client.http.HttpHeaders; -import com.google.common.base.Objects; -import io.camunda.connector.api.annotation.Secret; -import javax.validation.constraints.NotEmpty; - -public class BasicAuthentication extends Authentication { - - @NotEmpty @Secret private String username; - @NotEmpty @Secret private String password; - - @Override - public void setHeaders(final HttpHeaders headers) { - headers.setBasicAuthentication(username, password); - } - - public String getUsername() { - return username; - } - - public void setUsername(final String username) { - this.username = username; - } - - public String getPassword() { - return password; - } - - public void setPassword(final String password) { - this.password = password; - } - - @Override - public boolean equals(final Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - if (!super.equals(o)) { - return false; - } - BasicAuthentication that = (BasicAuthentication) o; - return Objects.equal(username, that.username) && Objects.equal(password, that.password); - } - - @Override - public int hashCode() { - return Objects.hashCode(super.hashCode(), username, password); - } - - @Override - public String toString() { - return "BasicAuthentication {" - + "username='[REDACTED]'" - + ", password='[REDACTED]'" - + "}; Super: " - + super.toString(); - } -} diff --git a/connectors/http-json/src/main/java/io/camunda/connector/http/auth/BearerAuthentication.java b/connectors/http-json/src/main/java/io/camunda/connector/http/auth/BearerAuthentication.java deleted file mode 100644 index a82cf35a38..0000000000 --- a/connectors/http-json/src/main/java/io/camunda/connector/http/auth/BearerAuthentication.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH - * under one or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information regarding copyright - * ownership. Camunda licenses this file to you under the Apache License, - * Version 2.0; you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.camunda.connector.http.auth; - -import com.google.api.client.http.HttpHeaders; -import com.google.common.base.Objects; -import io.camunda.connector.api.annotation.Secret; -import javax.validation.constraints.NotEmpty; - -public class BearerAuthentication extends Authentication { - - @NotEmpty @Secret private String token; - - @Override - public void setHeaders(final HttpHeaders headers) { - headers.setAuthorization("Bearer " + token); - } - - public String getToken() { - return token; - } - - public void setToken(String token) { - this.token = token; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - if (!super.equals(o)) { - return false; - } - BearerAuthentication that = (BearerAuthentication) o; - return Objects.equal(token, that.token); - } - - @Override - public int hashCode() { - return Objects.hashCode(super.hashCode(), token); - } - - @Override - public String toString() { - return "BearerAuthentication{" + "token='[REDACTED]'" + "}; Super: " + super.toString(); - } -} diff --git a/connectors/http-json/src/main/java/io/camunda/connector/http/auth/NoAuthentication.java b/connectors/http-json/src/main/java/io/camunda/connector/http/auth/NoAuthentication.java deleted file mode 100644 index 5a84625b60..0000000000 --- a/connectors/http-json/src/main/java/io/camunda/connector/http/auth/NoAuthentication.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH - * under one or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information regarding copyright - * ownership. Camunda licenses this file to you under the Apache License, - * Version 2.0; you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.camunda.connector.http.auth; - -import com.google.api.client.http.HttpHeaders; - -public class NoAuthentication extends Authentication { - - @Override - public void setHeaders(final HttpHeaders headers) {} - - @Override - public boolean equals(final Object o) { - return super.equals(o); - } - - @Override - public int hashCode() { - return super.hashCode(); - } - - @Override - public String toString() { - return super.toString(); - } -} diff --git a/connectors/http-json/src/main/java/io/camunda/connector/http/auth/OAuthAuthentication.java b/connectors/http-json/src/main/java/io/camunda/connector/http/auth/OAuthAuthentication.java deleted file mode 100644 index af3c9a9d82..0000000000 --- a/connectors/http-json/src/main/java/io/camunda/connector/http/auth/OAuthAuthentication.java +++ /dev/null @@ -1,159 +0,0 @@ -/* - * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH - * under one or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information regarding copyright - * ownership. Camunda licenses this file to you under the Apache License, - * Version 2.0; you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.camunda.connector.http.auth; - -import com.google.api.client.http.HttpHeaders; -import io.camunda.connector.api.annotation.Secret; -import io.camunda.connector.http.constants.Constants; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; -import javax.validation.constraints.NotEmpty; - -public class OAuthAuthentication extends Authentication { - private final String grantType = "client_credentials"; - @NotEmpty @Secret private String oauthTokenEndpoint; - @NotEmpty @Secret private String clientId; - @NotEmpty @Secret private String clientSecret; - @Secret private String audience; - @NotEmpty private String clientAuthentication; - private String scopes; - - public Map getDataForAuthRequestBody() { - Map data = new HashMap<>(); - data.put(Constants.GRANT_TYPE, grantType); - data.put(Constants.AUDIENCE, audience); - data.put(Constants.SCOPE, scopes); - - if (clientAuthentication.equals(Constants.CREDENTIALS_BODY)) { - data.put(Constants.CLIENT_ID, clientId); - data.put(Constants.CLIENT_SECRET, clientSecret); - } - return data; - } - - public String getClientId() { - return clientId; - } - - public void setClientId(String clientId) { - this.clientId = clientId; - } - - public String getClientSecret() { - return clientSecret; - } - - public void setClientSecret(String clientSecret) { - this.clientSecret = clientSecret; - } - - public String getGrantType() { - return grantType; - } - - public String getOauthTokenEndpoint() { - return oauthTokenEndpoint; - } - - public void setOauthTokenEndpoint(String oauthTokenEndpoint) { - this.oauthTokenEndpoint = oauthTokenEndpoint; - } - - public String getScopes() { - return scopes; - } - - public void setScopes(String scopes) { - this.scopes = scopes; - } - - public String getAudience() { - return audience; - } - - public void setAudience(String audience) { - this.audience = audience; - } - - public String getClientAuthentication() { - return clientAuthentication; - } - - public void setClientAuthentication(String clientAuthentication) { - this.clientAuthentication = clientAuthentication; - } - - @Override - public void setHeaders(final HttpHeaders headers) {} - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - if (!super.equals(o)) { - return false; - } - OAuthAuthentication that = (OAuthAuthentication) o; - return oauthTokenEndpoint.equals(that.oauthTokenEndpoint) - && clientId.equals(that.clientId) - && clientSecret.equals(that.clientSecret) - && audience.equals(that.audience) - && Objects.equals(grantType, that.grantType) - && clientAuthentication.equals(that.clientAuthentication) - && Objects.equals(scopes, that.scopes); - } - - @Override - public int hashCode() { - return Objects.hash( - super.hashCode(), - oauthTokenEndpoint, - clientId, - clientSecret, - audience, - grantType, - clientAuthentication, - scopes); - } - - @Override - public String toString() { - return "OAuthAuthentication{" - + "grantType='" - + grantType - + '\'' - + ", oauthTokenEndpoint='" - + oauthTokenEndpoint - + '\'' - + ", audience='" - + audience - + '\'' - + ", clientAuthentication='" - + clientAuthentication - + '\'' - + ", scopes='" - + scopes - + '\'' - + "} " - + super.toString(); - } -} diff --git a/connectors/http-json/src/main/java/io/camunda/connector/http/components/GsonComponentSupplier.java b/connectors/http-json/src/main/java/io/camunda/connector/http/components/GsonComponentSupplier.java index 28efdd427d..e590087277 100644 --- a/connectors/http-json/src/main/java/io/camunda/connector/http/components/GsonComponentSupplier.java +++ b/connectors/http-json/src/main/java/io/camunda/connector/http/components/GsonComponentSupplier.java @@ -21,12 +21,12 @@ import com.google.gson.GsonBuilder; import com.google.gson.ToNumberPolicy; import com.google.gson.typeadapters.RuntimeTypeAdapterFactory; -import io.camunda.connector.http.auth.Authentication; -import io.camunda.connector.http.auth.BasicAuthentication; -import io.camunda.connector.http.auth.BearerAuthentication; -import io.camunda.connector.http.auth.CustomAuthentication; -import io.camunda.connector.http.auth.NoAuthentication; -import io.camunda.connector.http.auth.OAuthAuthentication; +import io.camunda.connector.common.auth.Authentication; +import io.camunda.connector.common.auth.BasicAuthentication; +import io.camunda.connector.common.auth.BearerAuthentication; +import io.camunda.connector.common.auth.CustomAuthentication; +import io.camunda.connector.common.auth.NoAuthentication; +import io.camunda.connector.common.auth.OAuthAuthentication; public class GsonComponentSupplier { diff --git a/connectors/http-json/src/main/java/io/camunda/connector/http/constants/Constants.java b/connectors/http-json/src/main/java/io/camunda/connector/http/constants/Constants.java deleted file mode 100644 index 0f29108410..0000000000 --- a/connectors/http-json/src/main/java/io/camunda/connector/http/constants/Constants.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH - * under one or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information regarding copyright - * ownership. Camunda licenses this file to you under the Apache License, - * Version 2.0; you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.camunda.connector.http.constants; - -public class Constants { - public static final String GRANT_TYPE = "grant_type"; - public static final String CLIENT_ID = "client_id"; - public static final String CLIENT_SECRET = "client_secret"; - public static final String AUDIENCE = "audience"; - public static final String SCOPE = "scope"; - public static final String ACCESS_TOKEN = "access_token"; - public static final String COMPUTE_METADATA = "computeMetadata"; - public static final String BASIC_AUTH_HEADER = "basicAuthHeader"; - public static final String CREDENTIALS_BODY = "credentialsBody"; - public static final String PROXY_FUNCTION_URL_ENV_NAME = "CAMUNDA_CONNECTOR_HTTP_PROXY_URL"; - public static final String POST = "POST"; - public static final String APPLICATION_JSON_CHARSET_UTF_8 = "application/json; charset=UTF-8"; - public static final String APPLICATION_X_WWW_FORM_URLENCODED = - "application/x-www-form-urlencoded"; -} diff --git a/connectors/http-json/src/main/java/io/camunda/connector/http/model/ErrorResponse.java b/connectors/http-json/src/main/java/io/camunda/connector/http/model/ErrorResponse.java deleted file mode 100644 index 8f6285f435..0000000000 --- a/connectors/http-json/src/main/java/io/camunda/connector/http/model/ErrorResponse.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH - * under one or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information regarding copyright - * ownership. Camunda licenses this file to you under the Apache License, - * Version 2.0; you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.camunda.connector.http.model; - -import java.util.Objects; - -public class ErrorResponse { - private String error; - - private String errorCode; - - public String getError() { - return error; - } - - public void setError(final String error) { - this.error = error; - } - - public String getErrorCode() { - return errorCode; - } - - public void setErrorCode(String errorCode) { - this.errorCode = errorCode; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - ErrorResponse that = (ErrorResponse) o; - return error.equals(that.error) && errorCode.equals(that.errorCode); - } - - @Override - public int hashCode() { - return Objects.hash(error, errorCode); - } - - @Override - public String toString() { - return "ErrorResponse{" + "error='" + error + '\'' + ", errorCode='" + errorCode + '\'' + '}'; - } -} diff --git a/connectors/http-json/src/main/java/io/camunda/connector/http/model/HttpJsonRequest.java b/connectors/http-json/src/main/java/io/camunda/connector/http/model/HttpJsonRequest.java index a407e5f540..5f8ac6b623 100644 --- a/connectors/http-json/src/main/java/io/camunda/connector/http/model/HttpJsonRequest.java +++ b/connectors/http-json/src/main/java/io/camunda/connector/http/model/HttpJsonRequest.java @@ -16,148 +16,6 @@ */ package io.camunda.connector.http.model; -import com.google.common.base.Objects; -import io.camunda.connector.api.annotation.Secret; -import io.camunda.connector.http.auth.Authentication; -import java.util.Map; -import javax.validation.Valid; -import javax.validation.constraints.NotBlank; -import javax.validation.constraints.Pattern; +import io.camunda.connector.common.model.CommonRequest; -public class HttpJsonRequest { - - @NotBlank @Secret private String method; - - @NotBlank - @Pattern(regexp = "^(http://|https://|secrets).*$") - @Secret - private String url; - - @Valid @Secret private Authentication authentication; - @Secret private Map queryParameters; - @Secret private Map headers; - - @Pattern(regexp = "^([0-9]*$)|(secrets.*$)") - @Secret - private String connectionTimeoutInSeconds; - - @Secret private Object body; - - public boolean hasAuthentication() { - return authentication != null; - } - - public boolean hasQueryParameters() { - return queryParameters != null; - } - - public boolean hasHeaders() { - return headers != null; - } - - public boolean hasBody() { - return body != null; - } - - public String getMethod() { - return method; - } - - public void setMethod(final String method) { - this.method = method; - } - - public String getUrl() { - return url; - } - - public void setUrl(final String url) { - this.url = url; - } - - public Authentication getAuthentication() { - return authentication; - } - - public void setAuthentication(final Authentication authentication) { - this.authentication = authentication; - } - - public Map getQueryParameters() { - return queryParameters; - } - - public void setQueryParameters(final Map queryParameters) { - this.queryParameters = queryParameters; - } - - public Map getHeaders() { - return headers; - } - - public void setHeaders(final Map headers) { - this.headers = headers; - } - - public String getConnectionTimeoutInSeconds() { - return connectionTimeoutInSeconds; - } - - public void setConnectionTimeoutInSeconds(final String connectionTimeoutInSeconds) { - this.connectionTimeoutInSeconds = connectionTimeoutInSeconds; - } - - public Object getBody() { - return body; - } - - public void setBody(final Object body) { - this.body = body; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - final HttpJsonRequest that = (HttpJsonRequest) o; - return java.util.Objects.equals(method, that.method) - && java.util.Objects.equals(url, that.url) - && java.util.Objects.equals(authentication, that.authentication) - && java.util.Objects.equals(queryParameters, that.queryParameters) - && java.util.Objects.equals(headers, that.headers) - && java.util.Objects.equals(connectionTimeoutInSeconds, that.connectionTimeoutInSeconds) - && java.util.Objects.equals(body, that.body); - } - - @Override - public int hashCode() { - return Objects.hashCode(method, url, authentication, queryParameters, headers, body); - } - - @Override - public String toString() { - return "HttpJsonRequest{" - + "method='" - + method - + '\'' - + ", url='" - + url - + '\'' - + ", authentication=" - + authentication - + ", queryParameters=" - + queryParameters - + ", headers=" - + headers - + ", connectionTimeoutInSeconds='" - + connectionTimeoutInSeconds - + '\'' - + ", body=" - + body - + '}'; - } -} +public class HttpJsonRequest extends CommonRequest {} diff --git a/connectors/http-json/src/main/java/io/camunda/connector/http/model/HttpJsonResult.java b/connectors/http-json/src/main/java/io/camunda/connector/http/model/HttpJsonResult.java index 22c22e08c2..5d208084d1 100644 --- a/connectors/http-json/src/main/java/io/camunda/connector/http/model/HttpJsonResult.java +++ b/connectors/http-json/src/main/java/io/camunda/connector/http/model/HttpJsonResult.java @@ -16,59 +16,6 @@ */ package io.camunda.connector.http.model; -import com.google.common.base.Objects; -import java.util.Map; +import io.camunda.connector.common.model.CommonResult; -public class HttpJsonResult { - private int status; - private Map headers; - private Object body; - - public int getStatus() { - return status; - } - - public void setStatus(int status) { - this.status = status; - } - - public Map getHeaders() { - return headers; - } - - public void setHeaders(Map headers) { - this.headers = headers; - } - - public Object getBody() { - return body; - } - - public void setBody(Object body) { - this.body = body; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - HttpJsonResult that = (HttpJsonResult) o; - return status == that.status - && Objects.equal(headers, that.headers) - && Objects.equal(body, that.body); - } - - @Override - public int hashCode() { - return Objects.hashCode(status, headers, body); - } - - @Override - public String toString() { - return "HttpJsonResult{" + "status=" + status + ", headers=" + headers + ", body=" + body + '}'; - } -} +public class HttpJsonResult extends CommonResult {} diff --git a/connectors/http-json/src/test/java/io/camunda/connector/http/HttpJsonFunctionProxyTest.java b/connectors/http-json/src/test/java/io/camunda/connector/http/HttpJsonFunctionProxyTest.java index 16fd187198..a99d49a607 100644 --- a/connectors/http-json/src/test/java/io/camunda/connector/http/HttpJsonFunctionProxyTest.java +++ b/connectors/http-json/src/test/java/io/camunda/connector/http/HttpJsonFunctionProxyTest.java @@ -32,7 +32,7 @@ import com.google.api.client.http.HttpResponse; import com.google.api.client.http.HttpResponseException; import io.camunda.connector.api.error.ConnectorException; -import io.camunda.connector.http.constants.Constants; +import io.camunda.connector.common.constants.Constants; import io.camunda.connector.http.model.HttpJsonResult; import io.camunda.connector.test.outbound.OutboundConnectorContextBuilder; import java.io.ByteArrayInputStream; @@ -67,7 +67,8 @@ public void setup() { @ParameterizedTest(name = "Executing test case: {0}") @MethodSource("successCases") - void shouldReturnResult_WhenExecuted(final String input) throws IOException { + void shouldReturnResult_WhenExecuted(final String input) + throws IOException, InstantiationException, IllegalAccessException { // given - minimal required entity final var context = OutboundConnectorContextBuilder.create().variables(input).secrets(name -> "foo").build(); diff --git a/connectors/http-json/src/test/java/io/camunda/connector/http/HttpJsonFunctionSecretsTest.java b/connectors/http-json/src/test/java/io/camunda/connector/http/HttpJsonFunctionSecretsTest.java index 1250f7dc61..b9291c3d67 100644 --- a/connectors/http-json/src/test/java/io/camunda/connector/http/HttpJsonFunctionSecretsTest.java +++ b/connectors/http-json/src/test/java/io/camunda/connector/http/HttpJsonFunctionSecretsTest.java @@ -21,11 +21,11 @@ import com.google.gson.JsonObject; import io.camunda.connector.api.outbound.OutboundConnectorContext; -import io.camunda.connector.http.auth.Authentication; -import io.camunda.connector.http.auth.BasicAuthentication; -import io.camunda.connector.http.auth.BearerAuthentication; -import io.camunda.connector.http.auth.NoAuthentication; -import io.camunda.connector.http.auth.OAuthAuthentication; +import io.camunda.connector.common.auth.Authentication; +import io.camunda.connector.common.auth.BasicAuthentication; +import io.camunda.connector.common.auth.BearerAuthentication; +import io.camunda.connector.common.auth.NoAuthentication; +import io.camunda.connector.common.auth.OAuthAuthentication; import io.camunda.connector.http.model.HttpJsonRequest; import java.io.IOException; import java.util.stream.Stream; diff --git a/connectors/http-json/src/test/java/io/camunda/connector/http/HttpJsonFunctionTest.java b/connectors/http-json/src/test/java/io/camunda/connector/http/HttpJsonFunctionTest.java index 2272d4dffd..ce3b68b661 100644 --- a/connectors/http-json/src/test/java/io/camunda/connector/http/HttpJsonFunctionTest.java +++ b/connectors/http-json/src/test/java/io/camunda/connector/http/HttpJsonFunctionTest.java @@ -79,7 +79,8 @@ public void setup() { @ParameterizedTest(name = "Executing test case: {0}") @MethodSource("successCases") - void shouldReturnResult_WhenExecuted(final String input) throws IOException { + void shouldReturnResult_WhenExecuted(final String input) + throws IOException, InstantiationException, IllegalAccessException { // given - minimal required entity Object functionCallResponseAsObject = arrange(input); @@ -92,7 +93,8 @@ void shouldReturnResult_WhenExecuted(final String input) throws IOException { @ParameterizedTest(name = "Executing test case: {0}") @MethodSource("successCasesCustomAuth") - void shouldReturnResultCustom_WhenExecuted(final String input) throws IOException { + void shouldReturnResultCustom_WhenExecuted(final String input) + throws IOException, InstantiationException, IllegalAccessException { String response = "{\"token\":\"eyJhbJNtIbehBWQLAGapcHIctws7gavjTCSCCC0Xd5sIn7DaB52Pwmabdj-9AkrVru_fZwLQseAq38n1-DkiyAaewxB0VbQgQ\",\"user\":{\"id\":331707,\"principalId\":331707,\"deleted\":false,\"permissions\":[{\"id\":13044559,\"resourceType\":\"processdiscovery\"},{\"id\":13044527,\"resourceType\":\"credentials\"},],\"emailVerified\":true,\"passwordSet\":true},\"tenantUuid\":\"08b93cfe-a6dd-4d6b-94aa-9369fdd2a026\"}"; @@ -109,7 +111,8 @@ void shouldReturnResultCustom_WhenExecuted(final String input) throws IOExceptio @ParameterizedTest(name = "Executing test case: {0}") @MethodSource("successCasesOauth") - void shouldReturnResultOAuth_WhenExecuted(final String input) throws IOException { + void shouldReturnResultOAuth_WhenExecuted(final String input) + throws IOException, InstantiationException, IllegalAccessException { Object functionCallResponseAsObject = arrange(input); // then @@ -119,7 +122,8 @@ void shouldReturnResultOAuth_WhenExecuted(final String input) throws IOException .containsValue(APPLICATION_JSON.getMimeType()); } - private Object arrange(String input) throws IOException { + private Object arrange(String input) + throws IOException, InstantiationException, IllegalAccessException { final var context = OutboundConnectorContextBuilder.create().variables(input).secrets(name -> "foo").build(); when(requestFactory.buildRequest( @@ -149,7 +153,8 @@ void shouldReturnFallbackResult_WhenMalformedRequest(final String input) { } @Test - void execute_shouldReturnNullFieldWhenResponseWithContainNullField() throws IOException { + void execute_shouldReturnNullFieldWhenResponseWithContainNullField() + throws IOException, InstantiationException, IllegalAccessException { // given request, and response body with null field value final var request = "{ \"method\": \"get\", \"url\": \"https://camunda.io/http-endpoint\", \"authentication\": { \"type\": \"noAuth\" } }"; @@ -177,7 +182,8 @@ void execute_shouldReturnNullFieldWhenResponseWithContainNullField() throws IOEx @ParameterizedTest(name = "Executing test case: {0}") @MethodSource("successCases") - void execute_shouldSetConnectTime(final String input) throws IOException { + void execute_shouldSetConnectTime(final String input) + throws IOException, InstantiationException, IllegalAccessException { // given - minimal required entity final var context = OutboundConnectorContextBuilder.create().variables(input).secrets(name -> "foo").build(); diff --git a/connectors/http-json/src/test/java/io/camunda/connector/http/HttpServiceTest.java b/connectors/http-json/src/test/java/io/camunda/connector/http/HttpServiceTest.java index 6ddce91274..e141ec4d37 100644 --- a/connectors/http-json/src/test/java/io/camunda/connector/http/HttpServiceTest.java +++ b/connectors/http-json/src/test/java/io/camunda/connector/http/HttpServiceTest.java @@ -33,7 +33,8 @@ import com.google.api.client.testing.http.MockHttpTransport; import com.google.api.client.testing.http.MockLowLevelHttpRequest; import com.google.api.client.testing.http.MockLowLevelHttpResponse; -import io.camunda.connector.http.constants.Constants; +import io.camunda.connector.common.constants.Constants; +import io.camunda.connector.common.services.AuthenticationService; import io.camunda.connector.http.model.HttpJsonRequest; import io.camunda.connector.http.model.HttpJsonResult; import io.camunda.connector.impl.config.ConnectorConfigurationUtil; @@ -78,7 +79,8 @@ void checkIfOAuthBearerTokenIsAddedOnTheRequestHeader(final String input) throws when(httpResponse.parseAsString()).thenReturn(ACCESS_TOKEN); // when - String bearerToken = ResponseParser.extractOAuthAccessToken(httpResponse); + AuthenticationService authenticationService = new AuthenticationService(gson, requestFactory); + String bearerToken = authenticationService.extractOAuthAccessToken(httpResponse); HttpRequest request = HttpRequestMapper.toHttpRequest(requestFactory, httpJsonRequest, bearerToken); // check if the bearer token is correctly added on the header of the main request @@ -88,7 +90,8 @@ void checkIfOAuthBearerTokenIsAddedOnTheRequestHeader(final String input) throws @ParameterizedTest(name = "Executing test case: {0}") @MethodSource("successCasesCustomAuth") - void execute_shouldPassAllStepsAndParsing(final String input) throws IOException { + void execute_shouldPassAllStepsAndParsing(final String input) + throws IOException, InstantiationException, IllegalAccessException { // given final var context = OutboundConnectorContextBuilder.create().variables(input).build(); final var httpJsonRequest = gson.fromJson(context.getVariables(), HttpJsonRequest.class); diff --git a/connectors/http-json/src/test/java/io/camunda/connector/http/auth/OAuthAuthenticationTest.java b/connectors/http-json/src/test/java/io/camunda/connector/http/auth/OAuthAuthenticationTest.java index 54660f9b89..6c299c582a 100644 --- a/connectors/http-json/src/test/java/io/camunda/connector/http/auth/OAuthAuthenticationTest.java +++ b/connectors/http-json/src/test/java/io/camunda/connector/http/auth/OAuthAuthenticationTest.java @@ -27,10 +27,10 @@ import com.google.api.client.http.HttpRequestFactory; import com.google.api.client.http.HttpResponse; import com.google.api.client.testing.http.MockHttpTransport; +import io.camunda.connector.common.constants.Constants; +import io.camunda.connector.common.services.AuthenticationService; import io.camunda.connector.http.BaseTest; import io.camunda.connector.http.HttpRequestMapper; -import io.camunda.connector.http.ResponseParser; -import io.camunda.connector.http.constants.Constants; import io.camunda.connector.http.model.HttpJsonRequest; import io.camunda.connector.test.outbound.OutboundConnectorContextBuilder; import java.io.IOException; @@ -81,7 +81,8 @@ void checkOAuthBearerTokenFormat(final String input) throws IOException { // check if the bearer token has the correct format and doesn't contain quotes - assertFalse(ResponseParser.extractOAuthAccessToken(httpResponse).contains("\"")); + AuthenticationService authenticationService = new AuthenticationService(gson, requestFactory); + assertFalse(authenticationService.extractOAuthAccessToken(httpResponse).contains("\"")); } private static Stream successCasesOauth() throws IOException { From 706618baa4a26f55a7aba97fac0fb3668f6581df Mon Sep 17 00:00:00 2001 From: markfarkas-camunda Date: Thu, 9 Feb 2023 15:43:32 +0100 Subject: [PATCH 12/18] feat(graphql): add proxy behavior --- bundle/mvn/default-bundle/pom.xml | 2 +- .../common}/model/HttpRequestBuilder.java | 18 ++--- .../common/services/HTTPProxyService.java | 53 ++++++++++++++ .../common/services}/ProxyOAuthHelper.java | 18 ++--- connectors/graphql/pom.xml | 2 - .../connector/graphql/GraphQLFunction.java | 71 +++++++++++++------ .../graphql/utils/GraphQLRequestMapper.java | 35 +++++++++ .../graphql/utils/JsonSerializeHelper.java | 17 ++++- .../GraphQLFunctionInputValidationTest.java | 2 +- .../graphql/GraphQLFunctionTest.java | 2 +- connectors/http-json/pom.xml | 2 - .../connector/http/HttpRequestMapper.java | 36 +--------- .../camunda/connector/http/HttpService.java | 3 +- pom.xml | 8 ++- 14 files changed, 175 insertions(+), 94 deletions(-) rename connectors/{http-json/src/main/java/io/camunda/connector/http => connectors-common-library/src/main/java/io/camunda/connector/common}/model/HttpRequestBuilder.java (73%) create mode 100644 connectors/connectors-common-library/src/main/java/io/camunda/connector/common/services/HTTPProxyService.java rename connectors/{http-json/src/main/java/io/camunda/connector/http/auth => connectors-common-library/src/main/java/io/camunda/connector/common/services}/ProxyOAuthHelper.java (74%) create mode 100644 connectors/graphql/src/main/java/io/camunda/connector/graphql/utils/GraphQLRequestMapper.java diff --git a/bundle/mvn/default-bundle/pom.xml b/bundle/mvn/default-bundle/pom.xml index e4a9365016..5201493ebb 100644 --- a/bundle/mvn/default-bundle/pom.xml +++ b/bundle/mvn/default-bundle/pom.xml @@ -64,7 +64,7 @@ io.camunda.connector connector-graphql - 0.15.0-SNAPSHOT + 0.16.0-SNAPSHOT diff --git a/connectors/http-json/src/main/java/io/camunda/connector/http/model/HttpRequestBuilder.java b/connectors/connectors-common-library/src/main/java/io/camunda/connector/common/model/HttpRequestBuilder.java similarity index 73% rename from connectors/http-json/src/main/java/io/camunda/connector/http/model/HttpRequestBuilder.java rename to connectors/connectors-common-library/src/main/java/io/camunda/connector/common/model/HttpRequestBuilder.java index 6790ff42ac..f65398b2f3 100644 --- a/connectors/http-json/src/main/java/io/camunda/connector/http/model/HttpRequestBuilder.java +++ b/connectors/connectors-common-library/src/main/java/io/camunda/connector/common/model/HttpRequestBuilder.java @@ -1,20 +1,10 @@ /* * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH - * under one or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information regarding copyright - * ownership. Camunda licenses this file to you under the Apache License, - * Version 2.0; you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * under one or more contributor license agreements. Licensed under a proprietary license. + * See the License.txt file for more information. You may not use this file + * except in compliance with the proprietary license. */ -package io.camunda.connector.http.model; +package io.camunda.connector.common.model; import com.google.api.client.http.GenericUrl; import com.google.api.client.http.HttpContent; diff --git a/connectors/connectors-common-library/src/main/java/io/camunda/connector/common/services/HTTPProxyService.java b/connectors/connectors-common-library/src/main/java/io/camunda/connector/common/services/HTTPProxyService.java new file mode 100644 index 0000000000..8bed650229 --- /dev/null +++ b/connectors/connectors-common-library/src/main/java/io/camunda/connector/common/services/HTTPProxyService.java @@ -0,0 +1,53 @@ +/* + * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH + * under one or more contributor license agreements. Licensed under a proprietary license. + * See the License.txt file for more information. You may not use this file + * except in compliance with the proprietary license. + */ +package io.camunda.connector.common.services; + +import com.google.api.client.http.AbstractHttpContent; +import com.google.api.client.http.GenericUrl; +import com.google.api.client.http.HttpContent; +import com.google.api.client.http.HttpRequest; +import com.google.api.client.http.HttpRequestFactory; +import com.google.gson.Gson; +import io.camunda.connector.common.constants.Constants; +import io.camunda.connector.common.model.CommonRequest; +import io.camunda.connector.common.model.HttpRequestBuilder; +import java.io.IOException; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; + +public final class HTTPProxyService { + + public static HttpRequest toRequestViaProxy( + final Gson gson, + final HttpRequestFactory requestFactory, + final CommonRequest request, + final String proxyFunctionUrl) + throws IOException { + // Using the JsonHttpContent cannot work with an element on the root content, + // hence write it ourselves: + final String contentAsJson = gson.toJson(request); + HttpContent content = + new AbstractHttpContent(Constants.APPLICATION_JSON_CHARSET_UTF_8) { + public void writeTo(OutputStream outputStream) throws IOException { + outputStream.write(contentAsJson.getBytes(StandardCharsets.UTF_8)); + } + }; + + HttpRequest httpRequest = + new HttpRequestBuilder() + .method(Constants.POST) + .genericUrl(new GenericUrl(proxyFunctionUrl)) + .content(content) + .connectionTimeoutInSeconds(request.getConnectionTimeoutInSeconds()) + .followRedirects(false) + .build(requestFactory); + + ProxyOAuthHelper.addOauthHeaders( + httpRequest, ProxyOAuthHelper.initializeCredentials(proxyFunctionUrl)); + return httpRequest; + } +} diff --git a/connectors/http-json/src/main/java/io/camunda/connector/http/auth/ProxyOAuthHelper.java b/connectors/connectors-common-library/src/main/java/io/camunda/connector/common/services/ProxyOAuthHelper.java similarity index 74% rename from connectors/http-json/src/main/java/io/camunda/connector/http/auth/ProxyOAuthHelper.java rename to connectors/connectors-common-library/src/main/java/io/camunda/connector/common/services/ProxyOAuthHelper.java index 030049cff1..78d02becaa 100644 --- a/connectors/http-json/src/main/java/io/camunda/connector/http/auth/ProxyOAuthHelper.java +++ b/connectors/connectors-common-library/src/main/java/io/camunda/connector/common/services/ProxyOAuthHelper.java @@ -1,20 +1,10 @@ /* * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH - * under one or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information regarding copyright - * ownership. Camunda licenses this file to you under the Apache License, - * Version 2.0; you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * under one or more contributor license agreements. Licensed under a proprietary license. + * See the License.txt file for more information. You may not use this file + * except in compliance with the proprietary license. */ -package io.camunda.connector.http.auth; +package io.camunda.connector.common.services; import com.google.api.client.http.HttpRequest; import com.google.auth.oauth2.GoogleCredentials; diff --git a/connectors/graphql/pom.xml b/connectors/graphql/pom.xml index 59c5360c61..973c2bbd50 100644 --- a/connectors/graphql/pom.xml +++ b/connectors/graphql/pom.xml @@ -56,11 +56,9 @@ jcl-over-slf4j - io.camunda.connector connectors-common-library - 0.16.0-SNAPSHOT diff --git a/connectors/graphql/src/main/java/io/camunda/connector/graphql/GraphQLFunction.java b/connectors/graphql/src/main/java/io/camunda/connector/graphql/GraphQLFunction.java index 0b6e5ec176..632b97f1bf 100644 --- a/connectors/graphql/src/main/java/io/camunda/connector/graphql/GraphQLFunction.java +++ b/connectors/graphql/src/main/java/io/camunda/connector/graphql/GraphQLFunction.java @@ -18,19 +18,27 @@ import com.google.api.client.json.gson.GsonFactory; import com.google.gson.Gson; import io.camunda.connector.api.annotation.OutboundConnector; +import io.camunda.connector.api.error.ConnectorException; import io.camunda.connector.api.outbound.OutboundConnectorContext; import io.camunda.connector.api.outbound.OutboundConnectorFunction; import io.camunda.connector.common.auth.OAuthAuthentication; import io.camunda.connector.common.constants.Constants; +import io.camunda.connector.common.model.CommonRequest; +import io.camunda.connector.common.model.CommonResult; import io.camunda.connector.common.services.AuthenticationService; +import io.camunda.connector.common.services.HTTPProxyService; import io.camunda.connector.common.services.HTTPService; import io.camunda.connector.graphql.components.GsonComponentSupplier; import io.camunda.connector.graphql.components.HttpTransportComponentSupplier; import io.camunda.connector.graphql.model.GraphQLRequest; import io.camunda.connector.graphql.model.GraphQLResult; +import io.camunda.connector.graphql.utils.GraphQLRequestMapper; import io.camunda.connector.graphql.utils.JsonSerializeHelper; +import io.camunda.connector.impl.config.ConnectorConfigurationUtil; import java.io.IOException; -import java.util.HashMap; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; import java.util.Map; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -47,18 +55,29 @@ public class GraphQLFunction implements OutboundConnectorFunction { private final GsonFactory gsonFactory; private final HttpRequestFactory requestFactory; + private final String proxyFunctionUrl; + public GraphQLFunction() { + this(ConnectorConfigurationUtil.getProperty(Constants.PROXY_FUNCTION_URL_ENV_NAME)); + } + + public GraphQLFunction(String proxyFunctionUrl) { this( GsonComponentSupplier.gsonInstance(), HttpTransportComponentSupplier.httpRequestFactoryInstance(), - GsonComponentSupplier.gsonFactoryInstance()); + GsonComponentSupplier.gsonFactoryInstance(), + proxyFunctionUrl); } public GraphQLFunction( - final Gson gson, final HttpRequestFactory requestFactory, final GsonFactory gsonFactory) { + final Gson gson, + final HttpRequestFactory requestFactory, + final GsonFactory gsonFactory, + final String proxyFunctionUrl) { this.gson = gson; this.requestFactory = requestFactory; this.gsonFactory = gsonFactory; + this.proxyFunctionUrl = proxyFunctionUrl; } @Override @@ -68,7 +87,10 @@ public Object execute(OutboundConnectorContext context) var connectorRequest = JsonSerializeHelper.serializeRequest(gson, json); context.validate(connectorRequest); context.replaceSecrets(connectorRequest); - return executeGraphQLConnector(connectorRequest); + + return proxyFunctionUrl == null + ? executeGraphQLConnector(connectorRequest) + : executeGraphQLConnectorViaProxy(connectorRequest); } private GraphQLResult executeGraphQLConnector(final GraphQLRequest connectorRequest) @@ -90,6 +112,26 @@ private GraphQLResult executeGraphQLConnector(final GraphQLRequest connectorRequ return httpService.toHttpJsonResponse(httpResponse, GraphQLResult.class); } + private CommonResult executeGraphQLConnectorViaProxy(GraphQLRequest request) throws IOException { + CommonRequest commonRequest = GraphQLRequestMapper.toCommonRequest(request); + HttpRequest httpRequest = + HTTPProxyService.toRequestViaProxy(gson, requestFactory, commonRequest, proxyFunctionUrl); + + HTTPService httpService = new HTTPService(gson); + + HttpResponse httpResponse = httpService.executeHttpRequest(httpRequest, true); + + try (InputStream responseContentStream = httpResponse.getContent(); + Reader reader = new InputStreamReader(responseContentStream)) { + final CommonResult jsonResult = gson.fromJson(reader, CommonResult.class); + LOGGER.debug("Proxy returned result: " + jsonResult); + return jsonResult; + } catch (final Exception e) { + LOGGER.debug("Failed to parse external response: {}", httpResponse, e); + throw new ConnectorException("Failed to parse result: " + e.getMessage(), e); + } + } + public HttpRequest createRequest( final HTTPService httpService, final GraphQLRequest request, String bearerToken) throws IOException { @@ -97,16 +139,12 @@ public HttpRequest createRequest( final GenericUrl genericUrl = new GenericUrl(request.getUrl()); HttpContent content = null; final HttpHeaders headers = httpService.createHeaders(request, bearerToken); - String escapedQuery = request.getQuery().replace("\\n", "").replace("\\\"", "\""); + final Map queryAndVariablesMap = + JsonSerializeHelper.queryAndVariablesToMap(request); if (Constants.POST.equalsIgnoreCase(method)) { - content = constructBodyForPost(escapedQuery, request.getVariables()); + content = new JsonHttpContent(gsonFactory, queryAndVariablesMap); } else { - final Map query = new HashMap<>(); - query.put("query", escapedQuery); - if (request.getVariables() != null) { - query.put("variables", gson.toJsonTree(request.getVariables()).toString()); - } - genericUrl.putAll(query); + genericUrl.putAll(queryAndVariablesMap); } final var httpRequest = requestFactory.buildRequest(method, genericUrl, content); @@ -116,13 +154,4 @@ public HttpRequest createRequest( return httpRequest; } - - private JsonHttpContent constructBodyForPost(String escapedQuery, Object variables) { - final Map body = new HashMap<>(); - body.put("query", escapedQuery); - if (variables != null) { - body.put("variables", variables); - } - return new JsonHttpContent(gsonFactory, body); - } } diff --git a/connectors/graphql/src/main/java/io/camunda/connector/graphql/utils/GraphQLRequestMapper.java b/connectors/graphql/src/main/java/io/camunda/connector/graphql/utils/GraphQLRequestMapper.java new file mode 100644 index 0000000000..65f167b20f --- /dev/null +++ b/connectors/graphql/src/main/java/io/camunda/connector/graphql/utils/GraphQLRequestMapper.java @@ -0,0 +1,35 @@ +/* + * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH + * under one or more contributor license agreements. Licensed under a proprietary license. + * See the License.txt file for more information. You may not use this file + * except in compliance with the proprietary license. + */ +package io.camunda.connector.graphql.utils; + +import io.camunda.connector.common.constants.Constants; +import io.camunda.connector.common.model.CommonRequest; +import io.camunda.connector.graphql.model.GraphQLRequest; +import java.util.Map; +import java.util.stream.Collectors; + +public final class GraphQLRequestMapper { + + public static CommonRequest toCommonRequest(GraphQLRequest graphQLRequest) { + CommonRequest commonRequest = new CommonRequest(); + final Map queryAndVariablesMap = + JsonSerializeHelper.queryAndVariablesToMap(graphQLRequest); + if (Constants.POST.equalsIgnoreCase(graphQLRequest.getMethod())) { + commonRequest.setBody(queryAndVariablesMap); + } else { + final Map queryAndVariablesStringMap = + queryAndVariablesMap.entrySet().stream() + .collect(Collectors.toMap(Map.Entry::getKey, e -> String.valueOf(e.getValue()))); + commonRequest.setQueryParameters(queryAndVariablesStringMap); + } + commonRequest.setAuthentication(graphQLRequest.getAuthentication()); + commonRequest.setMethod(graphQLRequest.getMethod()); + commonRequest.setUrl(graphQLRequest.getUrl()); + commonRequest.setConnectionTimeoutInSeconds(graphQLRequest.getConnectionTimeoutInSeconds()); + return commonRequest; + } +} diff --git a/connectors/graphql/src/main/java/io/camunda/connector/graphql/utils/JsonSerializeHelper.java b/connectors/graphql/src/main/java/io/camunda/connector/graphql/utils/JsonSerializeHelper.java index cf0989b330..85877ace4b 100644 --- a/connectors/graphql/src/main/java/io/camunda/connector/graphql/utils/JsonSerializeHelper.java +++ b/connectors/graphql/src/main/java/io/camunda/connector/graphql/utils/JsonSerializeHelper.java @@ -9,12 +9,27 @@ import com.google.gson.Gson; import io.camunda.connector.graphql.model.GraphQLRequest; import io.camunda.connector.graphql.model.GraphQLRequestWrapper; +import java.util.HashMap; +import java.util.Map; -public class JsonSerializeHelper { +public final class JsonSerializeHelper { public static GraphQLRequest serializeRequest(Gson gson, String input) { GraphQLRequestWrapper graphQLRequestWrapper = gson.fromJson(input, GraphQLRequestWrapper.class); GraphQLRequest graphQLRequest = graphQLRequestWrapper.getGraphql(); graphQLRequest.setAuthentication(graphQLRequestWrapper.getAuthentication()); return graphQLRequest; } + + public static Map queryAndVariablesToMap(GraphQLRequest graphQLRequest) { + final Map map = new HashMap<>(); + map.put("query", getEscapedQuery(graphQLRequest)); + if (graphQLRequest.getVariables() != null) { + map.put("variables", graphQLRequest.getVariables()); + } + return map; + } + + public static String getEscapedQuery(GraphQLRequest graphQLRequest) { + return graphQLRequest.getQuery().replace("\\n", "").replace("\\\"", "\""); + } } diff --git a/connectors/graphql/src/test/java/io/camunda/connector/graphql/GraphQLFunctionInputValidationTest.java b/connectors/graphql/src/test/java/io/camunda/connector/graphql/GraphQLFunctionInputValidationTest.java index 629246b614..99c35c80ea 100644 --- a/connectors/graphql/src/test/java/io/camunda/connector/graphql/GraphQLFunctionInputValidationTest.java +++ b/connectors/graphql/src/test/java/io/camunda/connector/graphql/GraphQLFunctionInputValidationTest.java @@ -45,7 +45,7 @@ public class GraphQLFunctionInputValidationTest extends BaseTest { @BeforeEach void setup() { - functionUnderTest = new GraphQLFunction(); + functionUnderTest = new GraphQLFunction(null); } @ParameterizedTest diff --git a/connectors/graphql/src/test/java/io/camunda/connector/graphql/GraphQLFunctionTest.java b/connectors/graphql/src/test/java/io/camunda/connector/graphql/GraphQLFunctionTest.java index af793b8178..babc6f3da6 100644 --- a/connectors/graphql/src/test/java/io/camunda/connector/graphql/GraphQLFunctionTest.java +++ b/connectors/graphql/src/test/java/io/camunda/connector/graphql/GraphQLFunctionTest.java @@ -61,7 +61,7 @@ public class GraphQLFunctionTest extends BaseTest { @BeforeEach public void setup() { - functionUnderTest = new GraphQLFunction(gson, requestFactory, gsonFactory); + functionUnderTest = new GraphQLFunction(gson, requestFactory, gsonFactory, null); } @ParameterizedTest(name = "Executing test case: {0}") diff --git a/connectors/http-json/pom.xml b/connectors/http-json/pom.xml index 65c1ab43be..38b6cab23a 100644 --- a/connectors/http-json/pom.xml +++ b/connectors/http-json/pom.xml @@ -63,11 +63,9 @@ limitations under the License. jcl-over-slf4j - io.camunda.connector connectors-common-library - 0.16.0-SNAPSHOT diff --git a/connectors/http-json/src/main/java/io/camunda/connector/http/HttpRequestMapper.java b/connectors/http-json/src/main/java/io/camunda/connector/http/HttpRequestMapper.java index 5add9a8f45..d55d7dea62 100644 --- a/connectors/http-json/src/main/java/io/camunda/connector/http/HttpRequestMapper.java +++ b/connectors/http-json/src/main/java/io/camunda/connector/http/HttpRequestMapper.java @@ -18,9 +18,7 @@ import static org.apache.http.entity.ContentType.APPLICATION_JSON; -import com.google.api.client.http.AbstractHttpContent; import com.google.api.client.http.GenericUrl; -import com.google.api.client.http.HttpContent; import com.google.api.client.http.HttpHeaders; import com.google.api.client.http.HttpRequest; import com.google.api.client.http.HttpRequestFactory; @@ -30,14 +28,11 @@ import io.camunda.connector.common.auth.OAuthAuthentication; import io.camunda.connector.common.constants.Constants; import io.camunda.connector.common.model.CommonRequest; -import io.camunda.connector.http.auth.ProxyOAuthHelper; +import io.camunda.connector.common.model.HttpRequestBuilder; import io.camunda.connector.http.components.GsonComponentSupplier; import io.camunda.connector.http.model.HttpJsonRequest; -import io.camunda.connector.http.model.HttpRequestBuilder; import io.camunda.connector.impl.ConnectorInputException; import java.io.IOException; -import java.io.OutputStream; -import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.Map; import javax.validation.ValidationException; @@ -48,35 +43,6 @@ public class HttpRequestMapper { private HttpRequestMapper() {} - public static HttpRequest toRequestViaProxy( - final HttpRequestFactory requestFactory, - final HttpJsonRequest request, - final String proxyFunctionUrl) - throws IOException { - // Using the JsonHttpContent cannot work with an element on the root content, - // hence write it ourselves: - final String contentAsJson = gson.toJson(request); - HttpContent content = - new AbstractHttpContent(Constants.APPLICATION_JSON_CHARSET_UTF_8) { - public void writeTo(OutputStream outputStream) throws IOException { - outputStream.write(contentAsJson.getBytes(StandardCharsets.UTF_8)); - } - }; - - HttpRequest httpRequest = - new HttpRequestBuilder() - .method(Constants.POST) - .genericUrl(new GenericUrl(proxyFunctionUrl)) - .content(content) - .connectionTimeoutInSeconds(request.getConnectionTimeoutInSeconds()) - .followRedirects(false) - .build(requestFactory); - - ProxyOAuthHelper.addOauthHeaders( - httpRequest, ProxyOAuthHelper.initializeCredentials(proxyFunctionUrl)); - return httpRequest; - } - public static HttpRequest toOAuthHttpRequest( final HttpRequestFactory requestFactory, final HttpJsonRequest request) throws IOException { diff --git a/connectors/http-json/src/main/java/io/camunda/connector/http/HttpService.java b/connectors/http-json/src/main/java/io/camunda/connector/http/HttpService.java index 05ac116055..3caafa5f9c 100644 --- a/connectors/http-json/src/main/java/io/camunda/connector/http/HttpService.java +++ b/connectors/http-json/src/main/java/io/camunda/connector/http/HttpService.java @@ -24,6 +24,7 @@ import io.camunda.connector.common.auth.CustomAuthentication; import io.camunda.connector.common.auth.OAuthAuthentication; import io.camunda.connector.common.services.AuthenticationService; +import io.camunda.connector.common.services.HTTPProxyService; import io.camunda.connector.common.services.HTTPService; import io.camunda.connector.http.model.HttpJsonRequest; import io.camunda.connector.http.model.HttpJsonResult; @@ -97,7 +98,7 @@ private String getTokenFromOAuthRequest( private HttpJsonResult executeRequestViaProxy(HttpJsonRequest request) throws IOException { HttpRequest httpRequest = - HttpRequestMapper.toRequestViaProxy(requestFactory, request, proxyFunctionUrl); + HTTPProxyService.toRequestViaProxy(gson, requestFactory, request, proxyFunctionUrl); HTTPService httpService = new HTTPService(gson); diff --git a/pom.xml b/pom.xml index 13dc72f7f3..c4ff5048be 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ io.camunda.connector connector-parent - 0.6.0-SNAPSHOT + 0.6.0-alpha3 @@ -183,6 +183,12 @@ ${version.slack} + + io.camunda.connector + connectors-common-library + ${project.version} + + org.apache.commons From cf5d61a9af66b7d98fe95201c732bb21c9ac7efc Mon Sep 17 00:00:00 2001 From: markfarkas-camunda Date: Thu, 9 Feb 2023 16:07:37 +0100 Subject: [PATCH 13/18] chore(graphql): bundle pom cleanup --- bundle/mvn/default-bundle/pom.xml | 2 -- bundle/mvn/pom.xml | 5 +++++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/bundle/mvn/default-bundle/pom.xml b/bundle/mvn/default-bundle/pom.xml index 5201493ebb..5828127f84 100644 --- a/bundle/mvn/default-bundle/pom.xml +++ b/bundle/mvn/default-bundle/pom.xml @@ -60,11 +60,9 @@ io.camunda.connector connector-microsoft-teams - io.camunda.connector connector-graphql - 0.16.0-SNAPSHOT diff --git a/bundle/mvn/pom.xml b/bundle/mvn/pom.xml index 0403b4fa9b..e08fe64b5c 100644 --- a/bundle/mvn/pom.xml +++ b/bundle/mvn/pom.xml @@ -74,6 +74,11 @@ connector-slack ${project.version} + + io.camunda.connector + connector-graphql + ${project.version} + From 8768ae524115ea1c638477e7d952454fc3ed1c0e Mon Sep 17 00:00:00 2001 From: markfarkas-camunda Date: Thu, 9 Feb 2023 16:33:47 +0100 Subject: [PATCH 14/18] chore(graphql): add README --- connectors/graphql/README.md | 137 ++++++++++++++++++++++++++++++++--- 1 file changed, 126 insertions(+), 11 deletions(-) diff --git a/connectors/graphql/README.md b/connectors/graphql/README.md index 27c9a99c79..863b75a344 100644 --- a/connectors/graphql/README.md +++ b/connectors/graphql/README.md @@ -1,38 +1,153 @@ -# Camunda Connector Template +# Camunda GraphQL Connector +Find the user documentation in our [Camunda Platform 8 Docs](https://docs.camunda.io/docs/components/integration-framework/connectors/out-of-the-box-connectors/graphql/). + +## Build ```bash mvn clean package ``` +## API +### Input ```json { - "myProperty": "....." + "url": "https://swapi-graphql.netlify.app/.netlify/functions/index", + "method": "post", + "query": "query Root($id: ID) {person (id: $id) {id, name}}", + "variables": "{\"id\": \"cGVvcGxlOjI=\"}" } ``` +### Output + +The response will contain the status code, the headers and the body of the response of the GraphQL query response. ```json { - "result": { - "myProperty": "....." + "body": { + "data":{ + "person": + { + "id":"cGVvcGxlOjI=", + "name":"C-3PO" + } + } + }, + "headers": { + "access-control-allow-credentials": "true", + "access-control-allow-origin": "*", + "connection": "keep-alive", + "content-length": 56, + "content-type": "application/json; charset=utf-8", + "date": "Tue, 15 Mar 2022 21:31:20 GMT", + "server": "Netlify" + }, + "status": 200 +} +``` + +### Input (Basic) + +```json +{ + "method": "get", + "url": "https://httpbin.org/basic-auth/user/password", + "authentication": { + "type": "basic", + "username": "secrets.USERNAME", + "password": "secrets.PASSWORD" } } ``` +### Output (Bearer Token) + +```json +{ + "method": "get", + "url": "https://httpbin.org/bearer", + "authentication": { + "type": "bearer", + "token": "secrets.TOKEN" + } +} +``` -| Code | Description | -| - | - | -| FAIL | Message starts with 'fail' (ignoring case) | +### Input (OAuth 2.0) +```json +{ + "method": "post", + "url": "https://youroauthclientdomainname.eu.auth0.com/oauth/token", + "authentication": { + "oauthTokenEndpoint":"secrets.OAUTH_TOKEN_ENDPOINT_KEY", + "scopes": "read:clients read:users", + "audience":"secrets.AUDIENCE_KEY", + "clientId":"secrets.CLIENT_ID_KEY", + "clientSecret":"secrets.CLIENT_SECRET_KEY", + "type": "oauth-client-credentials-flow", + "clientAuthentication":"secrets.CLIENT_AUTHENTICATION_KEY" + } +} +``` -Run unit tests +### Output (Access Token) -```bash -mvn clean verify +```json +{ + "access_token":"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IlUtN2N6WG1sMzljUFNfUnlQQkNMWCJ9.kjhwfjkhfejkrhfbwjkfbhetcetc", + "scope":"read:clients create:users", + "expires_in":86400, + "token_type":"Bearer" +} +``` +### Error codes + +The Connector will fail on any non-2XX HTTP status code in the response. This error status code will be passed on as error code, e.g. "404". + +## Use proxy-mechanism + +The graphQL connector executes the queries/mutations using an HTTP call. You can configure the GraphQL Connector to do any outgoing HTTP call via a proxy. This proxy should be effectively an HTTP JSON Connector +running in a different environment. + +For example, you can build the following runtime architecture: + +``` + Camunda Process --> GraphQL Connector (Proxy-mode) --> HTTP Connector --> GraphQL Endpoint + [ Camunda Network, e.g. K8S ] [ Separate network, e.g. Google Function ] +``` + +Now, any GraphQL query/mutation will be just forwarded to a specified hardcoded URL. And this proxy does the real call then. +This avoids that you could reach internal endpoints in your Camunda network (e.g. the current Kubernetes cluster). + +Just set the following property to enable proxy mode for the connector, e.g. in application.properties when using the Spring-based runtime: + +```properties +camunda.connector.http.proxy.url=https://someUrl/ +``` + +You can also set this via environment variables: + +``` +CAMUNDA_CONNECTOR_HTTP_PROXY_URL=https://someUrl/ ``` +If the other party requiring OAuth for authentication, you need to set the following environment property: + +```shell +GOOGLE_APPLICATION_CREDENTIALS=... +``` + +### :lock: Test the Connector locally with Google Cloud Function as a proxy + +Run the [:lock:connector-proxy-saas](https://github.com/camunda/connector-proxy-saas) project locally as described in its [:lock:README](https://github.com/camunda/connector-proxy-saas#usage). + +Set the specific property or environment variable to enable proxy mode as described above. + +## Element Template -The element templates can be found in the [element-templates](element-templates) directory. +The element templates can be found in +the [element-templates/graphql-connector.json](element-templates/graphql-connector.json) file. From ae158c97e7e92e023a7835468dac91fa98470236 Mon Sep 17 00:00:00 2001 From: markfarkas-camunda Date: Thu, 9 Feb 2023 16:37:54 +0100 Subject: [PATCH 15/18] chore(graphql): fix README format --- connectors/graphql/README.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/connectors/graphql/README.md b/connectors/graphql/README.md index 863b75a344..c1135043a4 100644 --- a/connectors/graphql/README.md +++ b/connectors/graphql/README.md @@ -29,9 +29,8 @@ The response will contain the status code, the headers and the body of the respo { "body": { "data":{ - "person": - { - "id":"cGVvcGxlOjI=", + "person": { + "id":"cGVvcGxlOjI=", "name":"C-3PO" } } From 5b8dbab04c4d6f848d6c5e9647cde4fa45439eec Mon Sep 17 00:00:00 2001 From: markfarkas-camunda Date: Thu, 9 Feb 2023 16:58:58 +0100 Subject: [PATCH 16/18] chore(graphql): update links to point to internal documentation --- connectors/graphql/element-templates/graphql-connector.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/connectors/graphql/element-templates/graphql-connector.json b/connectors/graphql/element-templates/graphql-connector.json index c040e42a06..fcd0628bda 100644 --- a/connectors/graphql/element-templates/graphql-connector.json +++ b/connectors/graphql/element-templates/graphql-connector.json @@ -122,7 +122,7 @@ }, { "label": "Query/Mutation", - "description": "See documentation", + "description": "See documentation", "group": "graphql", "type": "Text", "language": "graphql", @@ -137,7 +137,7 @@ }, { "label": "Variables", - "description": "Learn how to define variables", + "description": "Learn how to define variables", "group": "graphql", "type": "Text", "feel": "required", From 32238f234b714412b4b37228d6afd40a6e59c16d Mon Sep 17 00:00:00 2001 From: markfarkas-camunda Date: Fri, 10 Feb 2023 11:36:46 +0100 Subject: [PATCH 17/18] fix(graphql): common library licensing, null safety --- .../src/main/resources/application.properties | 18 +++---------- connectors/connectors-common-library/pom.xml | 25 ++++++++++++------- .../connector/common/auth/Authentication.java | 16 +++++++++--- .../common/auth/BasicAuthentication.java | 16 +++++++++--- .../common/auth/BearerAuthentication.java | 16 +++++++++--- .../common/auth/CustomAuthentication.java | 16 +++++++++--- .../common/auth/NoAuthentication.java | 16 +++++++++--- .../common/auth/OAuthAuthentication.java | 16 +++++++++--- .../connector/common/constants/Constants.java | 16 +++++++++--- .../connector/common/model/CommonRequest.java | 16 +++++++++--- .../connector/common/model/CommonResult.java | 16 +++++++++--- .../connector/common/model/ErrorResponse.java | 16 +++++++++--- .../common/model/HttpRequestBuilder.java | 16 +++++++++--- .../services/AuthenticationService.java | 20 +++++++++++---- .../common/services/HTTPProxyService.java | 16 +++++++++--- .../common/services/HTTPService.java | 17 ++++++++++--- .../common/services/ProxyOAuthHelper.java | 16 +++++++++--- .../connector/common/utils/JsonHelper.java | 16 +++++++++--- .../common/utils/ResponseParser.java | 16 +++++++++--- .../connector/common/utils/Timeout.java | 16 +++++++++--- .../element-templates/graphql-connector.json | 2 +- connectors/graphql/pom.xml | 5 ++++ .../connector/graphql/GraphQLFunction.java | 4 ++- 23 files changed, 266 insertions(+), 81 deletions(-) diff --git a/bundle/mvn/default-bundle/src/main/resources/application.properties b/bundle/mvn/default-bundle/src/main/resources/application.properties index 56f24bd938..7ad85d94e1 100644 --- a/bundle/mvn/default-bundle/src/main/resources/application.properties +++ b/bundle/mvn/default-bundle/src/main/resources/application.properties @@ -1,15 +1,5 @@ -#camunda.connector.polling.enabled=true -#camunda.connector.polling.interval=5000 +camunda.connector.polling.enabled=true +camunda.connector.polling.interval=5000 -#camunda.connector.webhook.enabled=true -##spring.main.web-application-type=none - -# TODO : revisit this -# Test properties for localhost -server.port=9898 -zeebe.broker.gateway-address=localhost:26500 -zeebe.client.security.plaintext=true - -management.server.port=9080 -management.context-path=/actuator -management.endpoints.web.exposure.include=metrics,health,prometheus \ No newline at end of file +camunda.connector.webhook.enabled=true +#spring.main.web-application-type=none \ No newline at end of file diff --git a/connectors/connectors-common-library/pom.xml b/connectors/connectors-common-library/pom.xml index 96c356072d..9404a9c4e4 100644 --- a/connectors/connectors-common-library/pom.xml +++ b/connectors/connectors-common-library/pom.xml @@ -15,15 +15,22 @@ connectors-common-library jar - - - Camunda Platform Self-Managed Free Edition license - https://camunda.com/legal/terms/cloud-terms-and-conditions/camunda-cloud-self-managed-free-edition-terms/ - - - Camunda Platform Self-Managed Enterprise Edition license - - + + Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH +under one or more contributor license agreements. See the NOTICE file +distributed with this work for additional information regarding copyright +ownership. Camunda licenses this file to you under the Apache License, +Version 2.0; you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + diff --git a/connectors/connectors-common-library/src/main/java/io/camunda/connector/common/auth/Authentication.java b/connectors/connectors-common-library/src/main/java/io/camunda/connector/common/auth/Authentication.java index e1d4162550..20086a80c5 100644 --- a/connectors/connectors-common-library/src/main/java/io/camunda/connector/common/auth/Authentication.java +++ b/connectors/connectors-common-library/src/main/java/io/camunda/connector/common/auth/Authentication.java @@ -1,8 +1,18 @@ /* * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH - * under one or more contributor license agreements. Licensed under a proprietary license. - * See the License.txt file for more information. You may not use this file - * except in compliance with the proprietary license. + * under one or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information regarding copyright + * ownership. Camunda licenses this file to you under the Apache License, + * Version 2.0; you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package io.camunda.connector.common.auth; diff --git a/connectors/connectors-common-library/src/main/java/io/camunda/connector/common/auth/BasicAuthentication.java b/connectors/connectors-common-library/src/main/java/io/camunda/connector/common/auth/BasicAuthentication.java index 05f72df8cf..91a10b5bad 100644 --- a/connectors/connectors-common-library/src/main/java/io/camunda/connector/common/auth/BasicAuthentication.java +++ b/connectors/connectors-common-library/src/main/java/io/camunda/connector/common/auth/BasicAuthentication.java @@ -1,8 +1,18 @@ /* * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH - * under one or more contributor license agreements. Licensed under a proprietary license. - * See the License.txt file for more information. You may not use this file - * except in compliance with the proprietary license. + * under one or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information regarding copyright + * ownership. Camunda licenses this file to you under the Apache License, + * Version 2.0; you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package io.camunda.connector.common.auth; diff --git a/connectors/connectors-common-library/src/main/java/io/camunda/connector/common/auth/BearerAuthentication.java b/connectors/connectors-common-library/src/main/java/io/camunda/connector/common/auth/BearerAuthentication.java index 86a7bc7edc..7c95ef2f91 100644 --- a/connectors/connectors-common-library/src/main/java/io/camunda/connector/common/auth/BearerAuthentication.java +++ b/connectors/connectors-common-library/src/main/java/io/camunda/connector/common/auth/BearerAuthentication.java @@ -1,8 +1,18 @@ /* * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH - * under one or more contributor license agreements. Licensed under a proprietary license. - * See the License.txt file for more information. You may not use this file - * except in compliance with the proprietary license. + * under one or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information regarding copyright + * ownership. Camunda licenses this file to you under the Apache License, + * Version 2.0; you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package io.camunda.connector.common.auth; diff --git a/connectors/connectors-common-library/src/main/java/io/camunda/connector/common/auth/CustomAuthentication.java b/connectors/connectors-common-library/src/main/java/io/camunda/connector/common/auth/CustomAuthentication.java index 8823952bca..29dd2f40d8 100644 --- a/connectors/connectors-common-library/src/main/java/io/camunda/connector/common/auth/CustomAuthentication.java +++ b/connectors/connectors-common-library/src/main/java/io/camunda/connector/common/auth/CustomAuthentication.java @@ -1,8 +1,18 @@ /* * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH - * under one or more contributor license agreements. Licensed under a proprietary license. - * See the License.txt file for more information. You may not use this file - * except in compliance with the proprietary license. + * under one or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information regarding copyright + * ownership. Camunda licenses this file to you under the Apache License, + * Version 2.0; you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package io.camunda.connector.common.auth; diff --git a/connectors/connectors-common-library/src/main/java/io/camunda/connector/common/auth/NoAuthentication.java b/connectors/connectors-common-library/src/main/java/io/camunda/connector/common/auth/NoAuthentication.java index d263eb2420..bd8b5ce061 100644 --- a/connectors/connectors-common-library/src/main/java/io/camunda/connector/common/auth/NoAuthentication.java +++ b/connectors/connectors-common-library/src/main/java/io/camunda/connector/common/auth/NoAuthentication.java @@ -1,8 +1,18 @@ /* * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH - * under one or more contributor license agreements. Licensed under a proprietary license. - * See the License.txt file for more information. You may not use this file - * except in compliance with the proprietary license. + * under one or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information regarding copyright + * ownership. Camunda licenses this file to you under the Apache License, + * Version 2.0; you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package io.camunda.connector.common.auth; diff --git a/connectors/connectors-common-library/src/main/java/io/camunda/connector/common/auth/OAuthAuthentication.java b/connectors/connectors-common-library/src/main/java/io/camunda/connector/common/auth/OAuthAuthentication.java index 0aad50b18d..ea3370db58 100644 --- a/connectors/connectors-common-library/src/main/java/io/camunda/connector/common/auth/OAuthAuthentication.java +++ b/connectors/connectors-common-library/src/main/java/io/camunda/connector/common/auth/OAuthAuthentication.java @@ -1,8 +1,18 @@ /* * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH - * under one or more contributor license agreements. Licensed under a proprietary license. - * See the License.txt file for more information. You may not use this file - * except in compliance with the proprietary license. + * under one or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information regarding copyright + * ownership. Camunda licenses this file to you under the Apache License, + * Version 2.0; you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package io.camunda.connector.common.auth; diff --git a/connectors/connectors-common-library/src/main/java/io/camunda/connector/common/constants/Constants.java b/connectors/connectors-common-library/src/main/java/io/camunda/connector/common/constants/Constants.java index 890fe98ab3..552e88b468 100644 --- a/connectors/connectors-common-library/src/main/java/io/camunda/connector/common/constants/Constants.java +++ b/connectors/connectors-common-library/src/main/java/io/camunda/connector/common/constants/Constants.java @@ -1,8 +1,18 @@ /* * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH - * under one or more contributor license agreements. Licensed under a proprietary license. - * See the License.txt file for more information. You may not use this file - * except in compliance with the proprietary license. + * under one or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information regarding copyright + * ownership. Camunda licenses this file to you under the Apache License, + * Version 2.0; you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package io.camunda.connector.common.constants; diff --git a/connectors/connectors-common-library/src/main/java/io/camunda/connector/common/model/CommonRequest.java b/connectors/connectors-common-library/src/main/java/io/camunda/connector/common/model/CommonRequest.java index 56e78271ac..6a42de90d9 100644 --- a/connectors/connectors-common-library/src/main/java/io/camunda/connector/common/model/CommonRequest.java +++ b/connectors/connectors-common-library/src/main/java/io/camunda/connector/common/model/CommonRequest.java @@ -1,8 +1,18 @@ /* * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH - * under one or more contributor license agreements. Licensed under a proprietary license. - * See the License.txt file for more information. You may not use this file - * except in compliance with the proprietary license. + * under one or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information regarding copyright + * ownership. Camunda licenses this file to you under the Apache License, + * Version 2.0; you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package io.camunda.connector.common.model; diff --git a/connectors/connectors-common-library/src/main/java/io/camunda/connector/common/model/CommonResult.java b/connectors/connectors-common-library/src/main/java/io/camunda/connector/common/model/CommonResult.java index a82b3da0fe..3e3fee0f62 100644 --- a/connectors/connectors-common-library/src/main/java/io/camunda/connector/common/model/CommonResult.java +++ b/connectors/connectors-common-library/src/main/java/io/camunda/connector/common/model/CommonResult.java @@ -1,8 +1,18 @@ /* * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH - * under one or more contributor license agreements. Licensed under a proprietary license. - * See the License.txt file for more information. You may not use this file - * except in compliance with the proprietary license. + * under one or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information regarding copyright + * ownership. Camunda licenses this file to you under the Apache License, + * Version 2.0; you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package io.camunda.connector.common.model; diff --git a/connectors/connectors-common-library/src/main/java/io/camunda/connector/common/model/ErrorResponse.java b/connectors/connectors-common-library/src/main/java/io/camunda/connector/common/model/ErrorResponse.java index 72a684728a..9029e14cf7 100644 --- a/connectors/connectors-common-library/src/main/java/io/camunda/connector/common/model/ErrorResponse.java +++ b/connectors/connectors-common-library/src/main/java/io/camunda/connector/common/model/ErrorResponse.java @@ -1,8 +1,18 @@ /* * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH - * under one or more contributor license agreements. Licensed under a proprietary license. - * See the License.txt file for more information. You may not use this file - * except in compliance with the proprietary license. + * under one or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information regarding copyright + * ownership. Camunda licenses this file to you under the Apache License, + * Version 2.0; you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package io.camunda.connector.common.model; diff --git a/connectors/connectors-common-library/src/main/java/io/camunda/connector/common/model/HttpRequestBuilder.java b/connectors/connectors-common-library/src/main/java/io/camunda/connector/common/model/HttpRequestBuilder.java index f65398b2f3..b904c2b091 100644 --- a/connectors/connectors-common-library/src/main/java/io/camunda/connector/common/model/HttpRequestBuilder.java +++ b/connectors/connectors-common-library/src/main/java/io/camunda/connector/common/model/HttpRequestBuilder.java @@ -1,8 +1,18 @@ /* * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH - * under one or more contributor license agreements. Licensed under a proprietary license. - * See the License.txt file for more information. You may not use this file - * except in compliance with the proprietary license. + * under one or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information regarding copyright + * ownership. Camunda licenses this file to you under the Apache License, + * Version 2.0; you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package io.camunda.connector.common.model; diff --git a/connectors/connectors-common-library/src/main/java/io/camunda/connector/common/services/AuthenticationService.java b/connectors/connectors-common-library/src/main/java/io/camunda/connector/common/services/AuthenticationService.java index d7a2b95290..733bd7820f 100644 --- a/connectors/connectors-common-library/src/main/java/io/camunda/connector/common/services/AuthenticationService.java +++ b/connectors/connectors-common-library/src/main/java/io/camunda/connector/common/services/AuthenticationService.java @@ -1,8 +1,18 @@ /* * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH - * under one or more contributor license agreements. Licensed under a proprietary license. - * See the License.txt file for more information. You may not use this file - * except in compliance with the proprietary license. + * under one or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information regarding copyright + * ownership. Camunda licenses this file to you under the Apache License, + * Version 2.0; you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package io.camunda.connector.common.services; @@ -93,7 +103,7 @@ public HttpRequest createOAuthRequest(CommonRequest request) throws IOException setTimeout(request, httpRequest); HttpHeaders headers = new HttpHeaders(); - if (authentication.getClientAuthentication().equals(Constants.BASIC_AUTH_HEADER)) { + if (Constants.BASIC_AUTH_HEADER.equals(authentication.getClientAuthentication())) { headers.setBasicAuthentication( authentication.getClientId(), authentication.getClientSecret()); } @@ -108,7 +118,7 @@ private static Map getDataForAuthRequestBody(OAuthAuthentication data.put(Constants.AUDIENCE, authentication.getAudience()); data.put(Constants.SCOPE, authentication.getScopes()); - if (authentication.getClientAuthentication().equals(Constants.CREDENTIALS_BODY)) { + if (Constants.CREDENTIALS_BODY.equals(authentication.getClientAuthentication())) { data.put(Constants.CLIENT_ID, authentication.getClientId()); data.put(Constants.CLIENT_SECRET, authentication.getClientSecret()); } diff --git a/connectors/connectors-common-library/src/main/java/io/camunda/connector/common/services/HTTPProxyService.java b/connectors/connectors-common-library/src/main/java/io/camunda/connector/common/services/HTTPProxyService.java index 8bed650229..cb423d59b0 100644 --- a/connectors/connectors-common-library/src/main/java/io/camunda/connector/common/services/HTTPProxyService.java +++ b/connectors/connectors-common-library/src/main/java/io/camunda/connector/common/services/HTTPProxyService.java @@ -1,8 +1,18 @@ /* * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH - * under one or more contributor license agreements. Licensed under a proprietary license. - * See the License.txt file for more information. You may not use this file - * except in compliance with the proprietary license. + * under one or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information regarding copyright + * ownership. Camunda licenses this file to you under the Apache License, + * Version 2.0; you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package io.camunda.connector.common.services; diff --git a/connectors/connectors-common-library/src/main/java/io/camunda/connector/common/services/HTTPService.java b/connectors/connectors-common-library/src/main/java/io/camunda/connector/common/services/HTTPService.java index 606ac5c323..381db705cc 100644 --- a/connectors/connectors-common-library/src/main/java/io/camunda/connector/common/services/HTTPService.java +++ b/connectors/connectors-common-library/src/main/java/io/camunda/connector/common/services/HTTPService.java @@ -1,8 +1,18 @@ /* * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH - * under one or more contributor license agreements. Licensed under a proprietary license. - * See the License.txt file for more information. You may not use this file - * except in compliance with the proprietary license. + * under one or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information regarding copyright + * ownership. Camunda licenses this file to you under the Apache License, + * Version 2.0; you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package io.camunda.connector.common.services; @@ -70,6 +80,7 @@ public HttpResponse executeHttpRequest(HttpRequest externalRequest, boolean isPr errorMessage = errorContent.getError(); } catch (Exception e) { // cannot be loaded as JSON, ignore and use plain message + LOGGER.warn("Error response cannot be parsed as JSON! Will use the plain message."); } } throw new ConnectorException(errorCode, errorMessage, hrex); diff --git a/connectors/connectors-common-library/src/main/java/io/camunda/connector/common/services/ProxyOAuthHelper.java b/connectors/connectors-common-library/src/main/java/io/camunda/connector/common/services/ProxyOAuthHelper.java index 78d02becaa..a71058258f 100644 --- a/connectors/connectors-common-library/src/main/java/io/camunda/connector/common/services/ProxyOAuthHelper.java +++ b/connectors/connectors-common-library/src/main/java/io/camunda/connector/common/services/ProxyOAuthHelper.java @@ -1,8 +1,18 @@ /* * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH - * under one or more contributor license agreements. Licensed under a proprietary license. - * See the License.txt file for more information. You may not use this file - * except in compliance with the proprietary license. + * under one or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information regarding copyright + * ownership. Camunda licenses this file to you under the Apache License, + * Version 2.0; you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package io.camunda.connector.common.services; diff --git a/connectors/connectors-common-library/src/main/java/io/camunda/connector/common/utils/JsonHelper.java b/connectors/connectors-common-library/src/main/java/io/camunda/connector/common/utils/JsonHelper.java index ea52505a09..6c7f71af00 100644 --- a/connectors/connectors-common-library/src/main/java/io/camunda/connector/common/utils/JsonHelper.java +++ b/connectors/connectors-common-library/src/main/java/io/camunda/connector/common/utils/JsonHelper.java @@ -1,8 +1,18 @@ /* * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH - * under one or more contributor license agreements. Licensed under a proprietary license. - * See the License.txt file for more information. You may not use this file - * except in compliance with the proprietary license. + * under one or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information regarding copyright + * ownership. Camunda licenses this file to you under the Apache License, + * Version 2.0; you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package io.camunda.connector.common.utils; diff --git a/connectors/connectors-common-library/src/main/java/io/camunda/connector/common/utils/ResponseParser.java b/connectors/connectors-common-library/src/main/java/io/camunda/connector/common/utils/ResponseParser.java index 2967a83945..40007d2def 100644 --- a/connectors/connectors-common-library/src/main/java/io/camunda/connector/common/utils/ResponseParser.java +++ b/connectors/connectors-common-library/src/main/java/io/camunda/connector/common/utils/ResponseParser.java @@ -1,8 +1,18 @@ /* * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH - * under one or more contributor license agreements. Licensed under a proprietary license. - * See the License.txt file for more information. You may not use this file - * except in compliance with the proprietary license. + * under one or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information regarding copyright + * ownership. Camunda licenses this file to you under the Apache License, + * Version 2.0; you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package io.camunda.connector.common.utils; diff --git a/connectors/connectors-common-library/src/main/java/io/camunda/connector/common/utils/Timeout.java b/connectors/connectors-common-library/src/main/java/io/camunda/connector/common/utils/Timeout.java index 45694d6faf..2f7283ce43 100644 --- a/connectors/connectors-common-library/src/main/java/io/camunda/connector/common/utils/Timeout.java +++ b/connectors/connectors-common-library/src/main/java/io/camunda/connector/common/utils/Timeout.java @@ -1,8 +1,18 @@ /* * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH - * under one or more contributor license agreements. Licensed under a proprietary license. - * See the License.txt file for more information. You may not use this file - * except in compliance with the proprietary license. + * under one or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information regarding copyright + * ownership. Camunda licenses this file to you under the Apache License, + * Version 2.0; you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package io.camunda.connector.common.utils; diff --git a/connectors/graphql/element-templates/graphql-connector.json b/connectors/graphql/element-templates/graphql-connector.json index fcd0628bda..62c9e73937 100644 --- a/connectors/graphql/element-templates/graphql-connector.json +++ b/connectors/graphql/element-templates/graphql-connector.json @@ -94,7 +94,7 @@ "constraints": { "notEmpty": true, "pattern": { - "value": "^(=|https?://).*", + "value": "^(=|http://|https://|secrets).*$", "message": "Must be a http(s) URL." } } diff --git a/connectors/graphql/pom.xml b/connectors/graphql/pom.xml index 973c2bbd50..4ca4cc4d8f 100644 --- a/connectors/graphql/pom.xml +++ b/connectors/graphql/pom.xml @@ -56,6 +56,11 @@ jcl-over-slf4j + + org.apache.commons + commons-text + + io.camunda.connector connectors-common-library diff --git a/connectors/graphql/src/main/java/io/camunda/connector/graphql/GraphQLFunction.java b/connectors/graphql/src/main/java/io/camunda/connector/graphql/GraphQLFunction.java index 632b97f1bf..b046515621 100644 --- a/connectors/graphql/src/main/java/io/camunda/connector/graphql/GraphQLFunction.java +++ b/connectors/graphql/src/main/java/io/camunda/connector/graphql/GraphQLFunction.java @@ -40,6 +40,8 @@ import java.io.InputStreamReader; import java.io.Reader; import java.util.Map; + +import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -88,7 +90,7 @@ public Object execute(OutboundConnectorContext context) context.validate(connectorRequest); context.replaceSecrets(connectorRequest); - return proxyFunctionUrl == null + return StringUtils.isBlank(proxyFunctionUrl) ? executeGraphQLConnector(connectorRequest) : executeGraphQLConnectorViaProxy(connectorRequest); } From 518a5bab9d00d7bdcb958ab4ffa903d9951eef26 Mon Sep 17 00:00:00 2001 From: markfarkas-camunda Date: Fri, 10 Feb 2023 13:03:31 +0100 Subject: [PATCH 18/18] fix(graphql): fix code style --- .../main/java/io/camunda/connector/graphql/GraphQLFunction.java | 1 - 1 file changed, 1 deletion(-) diff --git a/connectors/graphql/src/main/java/io/camunda/connector/graphql/GraphQLFunction.java b/connectors/graphql/src/main/java/io/camunda/connector/graphql/GraphQLFunction.java index b046515621..9dc668d808 100644 --- a/connectors/graphql/src/main/java/io/camunda/connector/graphql/GraphQLFunction.java +++ b/connectors/graphql/src/main/java/io/camunda/connector/graphql/GraphQLFunction.java @@ -40,7 +40,6 @@ import java.io.InputStreamReader; import java.io.Reader; import java.util.Map; - import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory;