diff --git a/MY-PRODUCT-NAME-product/README.md b/MY-PRODUCT-NAME-product/README.md deleted file mode 100644 index ee9062a..0000000 --- a/MY-PRODUCT-NAME-product/README.md +++ /dev/null @@ -1,15 +0,0 @@ -# MY-PRODUCT-NAME - -YOUR DESCRIPTION GOES HERE - -## Demo - -YOUR DEMO DESCRIPTION GOES HERE - -## Setup - -YOUR SETUP DESCRIPTION GOES HERE - -``` -@variables.yaml@ -``` \ No newline at end of file diff --git a/README.md b/README.md index 0f94020..fd8f20a 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ -# MY-PRODUCT-NAME +# Adobe Sign Connector -[![CI Build](https://github.com/axonivy-market/REPO-NAME/actions/workflows/ci.yml/badge.svg)](https://github.com/axonivy-market/REPO-NAME/actions/workflows/ci.yml) +[![CI Build](https://github.com/axonivy-market/adobesign-connector/actions/workflows/ci.yml/badge.svg)](https://github.com/axonivy-market/adobesign-connector/actions/workflows/ci.yml) -"YOUR SHORT DESCRIPTION GOES HERE" +The Adobe Sign Connector simplifies the authentication process and enables easy integration and use of Adobe Sign services for electronical signing of documents. -Read our [documentation](MY-PRODUCT-NAME-product/README.md). +Read our [documentation](adobe-esign-connector-product/README.md). diff --git a/adobe-esign-connector-demo/.classpath b/adobe-esign-connector-demo/.classpath new file mode 100644 index 0000000..45a97e4 --- /dev/null +++ b/adobe-esign-connector-demo/.classpath @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/adobe-esign-connector-demo/.gitignore b/adobe-esign-connector-demo/.gitignore new file mode 100644 index 0000000..9b0d458 --- /dev/null +++ b/adobe-esign-connector-demo/.gitignore @@ -0,0 +1,19 @@ +# general +Thumbs.db +.DS_Store +*~ +*.log + +# java +*.class +hs_err_pid* + +# maven +target/ +lib/mvn-deps/ + +# ivy +classes/ +src_dataClasses/ +src_wsproc/ +logs/ diff --git a/adobe-esign-connector-demo/.project b/adobe-esign-connector-demo/.project new file mode 100644 index 0000000..b1062fa --- /dev/null +++ b/adobe-esign-connector-demo/.project @@ -0,0 +1,49 @@ + + + adobe-esign-connector-demo + + + + + + ch.ivyteam.ivy.designer.dataClasses.ui.ivyDataClassBuilder + + + + + ch.ivyteam.ivy.designer.process.ui.ivyWebServiceProcessClassBuilder + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.wst.common.project.facet.core.builder + + + + + ch.ivyteam.ivy.designer.ide.ivyModelValidationBuilder + + + + + org.eclipse.m2e.core.maven2Builder + + + + + + ch.ivyteam.ivy.project.IvyProjectNature + org.eclipse.wst.common.modulecore.ModuleCoreNature + org.eclipse.jem.workbench.JavaEMFNature + org.eclipse.jdt.core.javanature + org.eclipse.m2e.core.maven2Nature + org.eclipse.jem.beaninfo.BeanInfoNature + org.eclipse.wst.common.project.facet.core.nature + org.eclipse.wst.jsdt.core.jsNature + + diff --git a/adobe-esign-connector-demo/.settings/.jsdtscope b/adobe-esign-connector-demo/.settings/.jsdtscope new file mode 100644 index 0000000..869c01d --- /dev/null +++ b/adobe-esign-connector-demo/.settings/.jsdtscope @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/adobe-esign-connector-demo/.settings/ch.ivyteam.ivy.designer.prefs b/adobe-esign-connector-demo/.settings/ch.ivyteam.ivy.designer.prefs new file mode 100644 index 0000000..43983c0 --- /dev/null +++ b/adobe-esign-connector-demo/.settings/ch.ivyteam.ivy.designer.prefs @@ -0,0 +1,5 @@ +ch.ivyteam.ivy.designer.preferences.DataClassPreferencePage\:DEFAULT_DATA_CLASS=com.axonivy.connector.adobe.esign.connector.demo.Data +ch.ivyteam.ivy.designer.preferences.DataClassPreferencePage\:DEFAULT_NAMESPACE=com.axonivy.connector.adobe.esign.connector.demo +ch.ivyteam.ivy.project.preferences\:PRIMEFACES_VERSION=11 +ch.ivyteam.ivy.project.preferences\:PROJECT_VERSION=100000 +eclipse.preferences.version=1 diff --git a/adobe-esign-connector-demo/.settings/org.eclipse.jdt.core.prefs b/adobe-esign-connector-demo/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 0000000..d4540a5 --- /dev/null +++ b/adobe-esign-connector-demo/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,10 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=17 +org.eclipse.jdt.core.compiler.compliance=17 +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=warning +org.eclipse.jdt.core.compiler.release=enabled +org.eclipse.jdt.core.compiler.source=17 diff --git a/adobe-esign-connector-demo/.settings/org.eclipse.wst.common.component b/adobe-esign-connector-demo/.settings/org.eclipse.wst.common.component new file mode 100644 index 0000000..2bb995a --- /dev/null +++ b/adobe-esign-connector-demo/.settings/org.eclipse.wst.common.component @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/adobe-esign-connector-demo/.settings/org.eclipse.wst.common.project.facet.core.prefs.xml b/adobe-esign-connector-demo/.settings/org.eclipse.wst.common.project.facet.core.prefs.xml new file mode 100644 index 0000000..9b4b9fc --- /dev/null +++ b/adobe-esign-connector-demo/.settings/org.eclipse.wst.common.project.facet.core.prefs.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/adobe-esign-connector-demo/.settings/org.eclipse.wst.common.project.facet.core.xml b/adobe-esign-connector-demo/.settings/org.eclipse.wst.common.project.facet.core.xml new file mode 100644 index 0000000..156ecdb --- /dev/null +++ b/adobe-esign-connector-demo/.settings/org.eclipse.wst.common.project.facet.core.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/adobe-esign-connector-demo/.settings/org.eclipse.wst.css.core.prefs b/adobe-esign-connector-demo/.settings/org.eclipse.wst.css.core.prefs new file mode 100644 index 0000000..5ddc6bd --- /dev/null +++ b/adobe-esign-connector-demo/.settings/org.eclipse.wst.css.core.prefs @@ -0,0 +1,2 @@ +css-profile/=org.eclipse.wst.css.core.cssprofile.css3 +eclipse.preferences.version=1 diff --git a/adobe-esign-connector-demo/.settings/org.eclipse.wst.jsdt.ui.superType.container b/adobe-esign-connector-demo/.settings/org.eclipse.wst.jsdt.ui.superType.container new file mode 100644 index 0000000..3bd5d0a --- /dev/null +++ b/adobe-esign-connector-demo/.settings/org.eclipse.wst.jsdt.ui.superType.container @@ -0,0 +1 @@ +org.eclipse.wst.jsdt.launching.baseBrowserLibrary \ No newline at end of file diff --git a/adobe-esign-connector-demo/.settings/org.eclipse.wst.jsdt.ui.superType.name b/adobe-esign-connector-demo/.settings/org.eclipse.wst.jsdt.ui.superType.name new file mode 100644 index 0000000..05bd71b --- /dev/null +++ b/adobe-esign-connector-demo/.settings/org.eclipse.wst.jsdt.ui.superType.name @@ -0,0 +1 @@ +Window \ No newline at end of file diff --git a/adobe-esign-connector-demo/cms/cms_en.yaml b/adobe-esign-connector-demo/cms/cms_en.yaml new file mode 100644 index 0000000..b0129bf --- /dev/null +++ b/adobe-esign-connector-demo/cms/cms_en.yaml @@ -0,0 +1,26 @@ +Dialogs: + com: + axonivy: + connector: + adobe: + esign: + connector: + demo: + Demo: + AdobeSignDemosA: Adobe Sign Demos. Upload PDF file, enter one or multiple signers, create an agreement and sign the document as selected signer + Agreement: Agreement + AgreementID: Agreement ID + AgreementID1: Agreement ID + CreateAgreement: Create Agreement for 1 signer + CreateAgreement1: Create Agreement for 2 signers + Documents: Documents + LoadExisting: Load existing Agreement + LoadExistingToo: Enter Agreement ID and load existing Agreement in case it's already created on Adobe side + PDFFile: PDF file + PDFFileToSign: PDF File to sign + SignAsSigner1: Sign as Signer 1 + SignAsSigner2: Sign as Signer 2 + Signer1Email: Signer 1 email + Signer2Email: Signer 2 email + Signers: Signers + Signing: Signing diff --git a/adobe-esign-connector-demo/config/custom-fields.yaml b/adobe-esign-connector-demo/config/custom-fields.yaml new file mode 100644 index 0000000..aa19ae0 --- /dev/null +++ b/adobe-esign-connector-demo/config/custom-fields.yaml @@ -0,0 +1,20 @@ +# == Custom Fields Information == +# +# You can define here your project custom fields. +# Have a look at our documentation for more information. +# +CustomFields: +# Tasks: +# MyTaskCustomField: +# Label: My task custom field +# Description: This new task custom field can be used to ... +# Type: STRING +# Cases: +# MyCaseCustomField: +# Label: My case custom field +# Description: This new case custom field can be used to ... +# Type: STRING +# Starts: +# MyStartCustomField: +# Label: My start custom field +# Description: This new start custom field can be used to ... diff --git a/adobe-esign-connector-demo/config/databases.yaml b/adobe-esign-connector-demo/config/databases.yaml new file mode 100644 index 0000000..247b128 --- /dev/null +++ b/adobe-esign-connector-demo/config/databases.yaml @@ -0,0 +1 @@ +Databases: diff --git a/adobe-esign-connector-demo/config/overrides.any b/adobe-esign-connector-demo/config/overrides.any new file mode 100644 index 0000000..f59ec20 --- /dev/null +++ b/adobe-esign-connector-demo/config/overrides.any @@ -0,0 +1 @@ +* \ No newline at end of file diff --git a/adobe-esign-connector-demo/config/persistence.xml b/adobe-esign-connector-demo/config/persistence.xml new file mode 100644 index 0000000..d6b96d7 --- /dev/null +++ b/adobe-esign-connector-demo/config/persistence.xml @@ -0,0 +1,2 @@ + + diff --git a/adobe-esign-connector-demo/config/rest-clients.yaml b/adobe-esign-connector-demo/config/rest-clients.yaml new file mode 100644 index 0000000..8e85296 --- /dev/null +++ b/adobe-esign-connector-demo/config/rest-clients.yaml @@ -0,0 +1 @@ +RestClients: diff --git a/adobe-esign-connector-demo/config/roles.xml b/adobe-esign-connector-demo/config/roles.xml new file mode 100644 index 0000000..59892fe --- /dev/null +++ b/adobe-esign-connector-demo/config/roles.xml @@ -0,0 +1,4 @@ + + + Everybody + diff --git a/adobe-esign-connector-demo/config/users.xml b/adobe-esign-connector-demo/config/users.xml new file mode 100644 index 0000000..51a6906 --- /dev/null +++ b/adobe-esign-connector-demo/config/users.xml @@ -0,0 +1,2 @@ + + diff --git a/adobe-esign-connector-demo/config/variables.yaml b/adobe-esign-connector-demo/config/variables.yaml new file mode 100644 index 0000000..5dcd07f --- /dev/null +++ b/adobe-esign-connector-demo/config/variables.yaml @@ -0,0 +1,19 @@ +# == Variables == +# +# You can define here your project Variables. +# If you want to define/override a Variable for a specific Environment, +# add an additional ‘variables.yaml’ file in a subdirectory in the ‘Config’ folder: +# '/Config/_/variables.yaml +# +Variables: + adobe-sign-connector: + host: api.eu2.adobesign.com + returnPage: /designer/page/adobe-esign-connector$1/signatureReturn.jsp + baseUri: https://api.eu2.adobesign.com/oauth/v2 + authenticationUri: https://secure.eu2.adobesign.com/public/oauth/v2 + clientId: '' + clientSecret: '' + permissions: user_read:account user_write:account user_login:account agreement_read:account agreement_write:account agreement_send:account widget_read:account widget_write:account library_read:account library_write:account workflow_read:account workflow_write:account + oauthToken: default-value + accessToken: default-value + integrationKey: '' diff --git a/adobe-esign-connector-demo/config/webservice-clients.yaml b/adobe-esign-connector-demo/config/webservice-clients.yaml new file mode 100644 index 0000000..060b018 --- /dev/null +++ b/adobe-esign-connector-demo/config/webservice-clients.yaml @@ -0,0 +1 @@ +WebServiceClients: diff --git a/adobe-esign-connector-demo/dataclasses/com/axonivy/connector/adobe/esign/connector/demo/Data.ivyClass b/adobe-esign-connector-demo/dataclasses/com/axonivy/connector/adobe/esign/connector/demo/Data.ivyClass new file mode 100644 index 0000000..6920e0f --- /dev/null +++ b/adobe-esign-connector-demo/dataclasses/com/axonivy/connector/adobe/esign/connector/demo/Data.ivyClass @@ -0,0 +1,2 @@ +Data #class +com.axonivy.connector.adobe.esign.connector.demo #namespace diff --git a/adobe-esign-connector-demo/dataclasses/com/axonivy/connector/adobe/esign/connector/demo/DemoData.ivyClass b/adobe-esign-connector-demo/dataclasses/com/axonivy/connector/adobe/esign/connector/demo/DemoData.ivyClass new file mode 100644 index 0000000..5a07197 --- /dev/null +++ b/adobe-esign-connector-demo/dataclasses/com/axonivy/connector/adobe/esign/connector/demo/DemoData.ivyClass @@ -0,0 +1,6 @@ +DemoData #class +com.axonivy.connector.adobe.esign.connector.demo #namespace +file java.io.File #field +fileId String #field +agreementId String #field +error ch.ivyteam.ivy.bpm.error.BpmError #field diff --git a/adobe-esign-connector-demo/pom.xml b/adobe-esign-connector-demo/pom.xml new file mode 100644 index 0000000..6d4ac07 --- /dev/null +++ b/adobe-esign-connector-demo/pom.xml @@ -0,0 +1,30 @@ + + + 4.0.0 + com.axonivy.connector.adobe.esign + adobe-esign-connector-demo + 10.0.11-SNAPSHOT + iar + + 10.0.6 + + + + com.axonivy.connector.adobe.esign + adobe-esign-connector + ${project.version} + iar + + + + + + com.axonivy.ivy.ci + project-build-plugin + ${build.plugin.version} + true + + + + diff --git a/adobe-esign-connector-demo/processes/Demo.p.json b/adobe-esign-connector-demo/processes/Demo.p.json new file mode 100644 index 0000000..d403e27 --- /dev/null +++ b/adobe-esign-connector-demo/processes/Demo.p.json @@ -0,0 +1,291 @@ +{ + "format" : "10.0.0", + "id" : "18822E284E99FC6E", + "config" : { + "data" : "com.axonivy.connector.adobe.esign.connector.demo.DemoData" + }, + "elements" : [ { + "id" : "f0", + "type" : "RequestStart", + "name" : "uploadDocument.ivp", + "config" : { + "callSignature" : "uploadDocument", + "outLink" : "uploadDocument.ivp" + }, + "visual" : { + "at" : { "x" : 96, "y" : 64 } + }, + "connect" : { "id" : "f8", "to" : "f7" } + }, { + "id" : "f1", + "type" : "TaskEnd", + "visual" : { + "at" : { "x" : 672, "y" : 64 } + } + }, { + "id" : "f3", + "type" : "SubProcessCall", + "name" : "connector/TransientDocuments", + "config" : { + "processCall" : "connector/TransientDocuments:uploadDocument(java.io.File)", + "output" : { + "map" : { + "out" : "in", + "out.error" : "result.#error", + "out.fileId" : "result.#id" + } + }, + "call" : { + "params" : [ + { "name" : "file", "type" : "java.io.File" } + ], + "map" : { + "param.file" : "in.file" + } + } + }, + "visual" : { + "at" : { "x" : 384, "y" : 64 } + }, + "connect" : { "id" : "f6", "to" : "f5" } + }, { + "id" : "f5", + "type" : "Script", + "name" : "log", + "config" : { + "security" : "system", + "output" : { + "code" : [ + "if(in.#error is initialized) {", + " ivy.log.error(in.error);", + "}" + ] + } + }, + "visual" : { + "at" : { "x" : 544, "y" : 64 } + }, + "connect" : { "id" : "f2", "to" : "f1" } + }, { + "id" : "f7", + "type" : "Script", + "name" : "prepare file", + "config" : { + "output" : { + "code" : "in.file = new java.io.File(\"c:\\\\Users\\\\jpl\\\\Documents\\\\loremIpsumWithTag.pdf\");" + } + }, + "visual" : { + "at" : { "x" : 224, "y" : 64 } + }, + "connect" : { "id" : "f4", "to" : "f3" } + }, { + "id" : "f9", + "type" : "RequestStart", + "name" : "createAgreement.ivp", + "config" : { + "callSignature" : "createAgreement", + "outLink" : "createAgreement.ivp" + }, + "visual" : { + "at" : { "x" : 96, "y" : 176 } + }, + "connect" : { "id" : "f13", "to" : "f11" } + }, { + "id" : "f10", + "type" : "TaskEnd", + "visual" : { + "at" : { "x" : 672, "y" : 176 } + } + }, { + "id" : "f11", + "type" : "SubProcessCall", + "name" : "connector/Agreements", + "config" : { + "call" : { + "params" : [ + { "name" : "agreement", "type" : "api.rest.v6.client.AgreementCreationInfo" } + ], + "code" : [ + "import api.rest.v6.client.AgreementsFormFieldGenerators;", + "import api.rest.v6.client.AgreementsFormFieldDescription.ContentTypeEnum;", + "import com.axonivy.connector.adobe.esign.connector.service.AdobeSignService;", + "", + "List generator;", + "generator.add(AdobeSignService.getInstance().createFormFieldWithAnchor(\"SIGNATURE_ANCHOR\", ContentTypeEnum.SIGNATURE, 0, 0));", + "List documentIds;", + "documentIds.add(\"3AAABLblqZhBsqs68phWjsqCZhVGoV658jrcsTFeVqfqT_qyXnkXY6YvPLcwQMXUACiViSnDqtXibmIj55hZkFnlKZorpCyq9oI3W6CFukovBLY3AabxnyU0oGxx7UZNJVPmSWHlxnkFA5dLbr0MswUM1MPmwwskc4kcKgjXEmO43V9pREDT0ONJpYbgMJHbxPYIBBuh-u7sRs0vV6APDtHnkdwCo4R7zMkoK_udWIo5g9NRL_o-aNn26o7aDVjDzFDXMNJyvN3CQnFwrHzl5n4b06lPDneEIL5LISb1coMzZlw8dN5_QB3qLOG7TrTf057davR8WfQZnWeNbHv5vXW0XLe4NLXTZ5SREUpb2JIDv-o9NrPZqsA**\");", + "", + "param.agreement = AdobeSignService.getInstance().buildSimpleAgreementWithFormFields(\"MyFirstAgreementWithAnchor\", documentIds,", + " \"jpl@mailinator.com\", ", + " generator);" + ] + }, + "processCall" : "connector/Agreements:createAgreement(api.rest.v6.client.AgreementCreationInfo)", + "output" : { + "map" : { + "out" : "in", + "out.agreementId" : "result.#agreementInfo.#id", + "out.error" : "result.#error" + } + } + }, + "visual" : { + "at" : { "x" : 384, "y" : 176 } + }, + "connect" : { "id" : "f17", "to" : "f12" } + }, { + "id" : "f12", + "type" : "Script", + "name" : "log", + "config" : { + "security" : "system", + "output" : { + "code" : [ + "if(in.#error is initialized) {", + " ivy.log.error(in.error);", + "}" + ] + } + }, + "visual" : { + "at" : { "x" : 544, "y" : 176 } + }, + "connect" : { "id" : "f16", "to" : "f10" } + }, { + "id" : "f14", + "type" : "RequestStart", + "name" : "getAgreement.ivp", + "config" : { + "callSignature" : "getAgreement", + "outLink" : "getAgreement.ivp" + }, + "visual" : { + "at" : { "x" : 96, "y" : 288 } + }, + "connect" : { "id" : "f18", "to" : "f15" } + }, { + "id" : "f15", + "type" : "SubProcessCall", + "name" : "connector/Agreements", + "config" : { + "processCall" : "connector/Agreements:getAgreementById(String)", + "call" : { + "params" : [ + { "name" : "agreementId", "type" : "String" } + ], + "map" : { + "param.agreementId" : "\"CBJCHBCAABAAQP3LT2gNWaeN1pRMeoeVzX5wt0FK4rp_\"" + } + } + }, + "visual" : { + "at" : { "x" : 288, "y" : 288 } + }, + "connect" : { "id" : "f20", "to" : "f19" } + }, { + "id" : "f19", + "type" : "TaskEnd", + "visual" : { + "at" : { "x" : 480, "y" : 288 } + } + }, { + "id" : "f21", + "type" : "RequestStart", + "name" : "getSigningURLs.ivp", + "config" : { + "callSignature" : "getSigningURLs", + "outLink" : "getSigningURLs.ivp" + }, + "visual" : { + "at" : { "x" : 96, "y" : 392 } + }, + "connect" : { "id" : "f24", "to" : "f22" } + }, { + "id" : "f22", + "type" : "SubProcessCall", + "name" : "connector/Agreements", + "config" : { + "processCall" : "connector/Agreements:getSigningURLs(String,String)", + "call" : { + "params" : [ + { "name" : "agreementId", "type" : "String" }, + { "name" : "frameParent", "type" : "String" } + ], + "map" : { + "param.agreementId" : "\"3AAABLblqZhBfSbYs8twdtGHmwnha8EHDVkANSjsr9OcOn7njc9HqC0Cq8J-WjiOkCzSFsCbt-rC0OF9OW37M_lB47n_XeRE7\"" + } + } + }, + "visual" : { + "at" : { "x" : 288, "y" : 392 } + }, + "connect" : { "id" : "f25", "to" : "f23" } + }, { + "id" : "f23", + "type" : "TaskEnd", + "visual" : { + "at" : { "x" : 480, "y" : 392 } + } + }, { + "id" : "f26", + "type" : "RequestStart", + "name" : "getUsers.ivp", + "config" : { + "callSignature" : "getUsers", + "outLink" : "getUsers.ivp" + }, + "visual" : { + "at" : { "x" : 96, "y" : 496 } + }, + "connect" : { "id" : "f30", "to" : "f27" } + }, { + "id" : "f27", + "type" : "SubProcessCall", + "name" : "connector/Users", + "config" : { + "processCall" : "connector/Users:call()" + }, + "visual" : { + "at" : { "x" : 288, "y" : 496 } + }, + "connect" : { "id" : "f29", "to" : "f28" } + }, { + "id" : "f28", + "type" : "TaskEnd", + "visual" : { + "at" : { "x" : 480, "y" : 496 } + } + }, { + "id" : "f31", + "type" : "RequestStart", + "name" : "demo.ivp", + "config" : { + "callSignature" : "demo", + "outLink" : "demo.ivp", + "startName" : "Demo" + }, + "visual" : { + "at" : { "x" : 96, "y" : 592 } + }, + "connect" : { "id" : "f33", "to" : "f32" } + }, { + "id" : "f32", + "type" : "DialogCall", + "name" : "Demo", + "config" : { + "dialogId" : "com.axonivy.connector.adobe.esign.connector.demo.Demo", + "startMethod" : "start()" + }, + "visual" : { + "at" : { "x" : 288, "y" : 592 } + }, + "connect" : { "id" : "f35", "to" : "f34" } + }, { + "id" : "f34", + "type" : "TaskEnd", + "visual" : { + "at" : { "x" : 480, "y" : 592 } + } + } ] +} \ No newline at end of file diff --git a/adobe-esign-connector-demo/src_hd/com/axonivy/connector/adobe/esign/connector/demo/Demo/Demo.rddescriptor b/adobe-esign-connector-demo/src_hd/com/axonivy/connector/adobe/esign/connector/demo/Demo/Demo.rddescriptor new file mode 100644 index 0000000..ae605f0 --- /dev/null +++ b/adobe-esign-connector-demo/src_hd/com/axonivy/connector/adobe/esign/connector/demo/Demo/Demo.rddescriptor @@ -0,0 +1,7 @@ + + + + viewTechnology + JSF + + diff --git a/adobe-esign-connector-demo/src_hd/com/axonivy/connector/adobe/esign/connector/demo/Demo/Demo.xhtml b/adobe-esign-connector-demo/src_hd/com/axonivy/connector/adobe/esign/connector/demo/Demo/Demo.xhtml new file mode 100644 index 0000000..1234016 --- /dev/null +++ b/adobe-esign-connector-demo/src_hd/com/axonivy/connector/adobe/esign/connector/demo/Demo/Demo.xhtml @@ -0,0 +1,189 @@ + + + + + Demo + + + +

+ #{ivy.cms.co('/Dialogs/com/axonivy/connector/adobe/esign/connector/demo/Demo/AdobeSignDemosA')} +

+ + + + + +
+
+
+ +
+
+ + +
+
+
+
+ + +
+
+
+ +
+
+ + + +
+
+ +
+
+ +
+
+ + + +
+
+
+
+ + +
+
+
+ +
+
+ +
+
+
+
+ +
+
+ + + +
+
+ +
+
+
+ + +
+
+ +
+
+ + +
+
+
+ +
+
+ +
+
+
+
+ + + + +
+
+
+ + + + + + + +
+
+ +
+
+
+
+
+
+ +
+
+ + + + + +
+
+
+ + \ No newline at end of file diff --git a/adobe-esign-connector-demo/src_hd/com/axonivy/connector/adobe/esign/connector/demo/Demo/DemoData.ivyClass b/adobe-esign-connector-demo/src_hd/com/axonivy/connector/adobe/esign/connector/demo/Demo/DemoData.ivyClass new file mode 100644 index 0000000..5cc4c69 --- /dev/null +++ b/adobe-esign-connector-demo/src_hd/com/axonivy/connector/adobe/esign/connector/demo/Demo/DemoData.ivyClass @@ -0,0 +1,24 @@ +DemoData #class +com.axonivy.connector.adobe.esign.connector.demo.Demo #namespace +file File #field +file PERSISTENT #fieldModifier +documentId String #field +documentId PERSISTENT #fieldModifier +agreementId String #field +agreementId PERSISTENT #fieldModifier +signingURI String #field +signingURI PERSISTENT #fieldModifier +signer1 String #field +signer1 PERSISTENT #fieldModifier +signer2 String #field +signer2 PERSISTENT #fieldModifier +authoringURI String #field +authoringURI PERSISTENT #fieldModifier +documents api.rest.v6.client.AgreementDocuments #field +documents PERSISTENT #fieldModifier +documentContent org.primefaces.model.StreamedContent #field +documentContent PERSISTENT #fieldModifier +download com.axonivy.connector.adobe.esign.connector.rest.DownloadResult #field +download PERSISTENT #fieldModifier +filename String #field +filename PERSISTENT #fieldModifier diff --git a/adobe-esign-connector-demo/src_hd/com/axonivy/connector/adobe/esign/connector/demo/Demo/DemoProcess.p.json b/adobe-esign-connector-demo/src_hd/com/axonivy/connector/adobe/esign/connector/demo/Demo/DemoProcess.p.json new file mode 100644 index 0000000..9942e5f --- /dev/null +++ b/adobe-esign-connector-demo/src_hd/com/axonivy/connector/adobe/esign/connector/demo/Demo/DemoProcess.p.json @@ -0,0 +1,410 @@ +{ + "format" : "10.0.0", + "id" : "1886B1A625EA901A", + "kind" : "HTML_DIALOG", + "config" : { + "data" : "com.axonivy.connector.adobe.esign.connector.demo.Demo.DemoData" + }, + "elements" : [ { + "id" : "f0", + "type" : "HtmlDialogStart", + "name" : "start()", + "config" : { + "callSignature" : "start", + "guid" : "1886B1A62614A9F5" + }, + "visual" : { + "at" : { "x" : 96, "y" : 64 } + }, + "connect" : { "id" : "f2", "to" : "f1" } + }, { + "id" : "f1", + "type" : "HtmlDialogEnd", + "visual" : { + "at" : { "x" : 224, "y" : 64 } + } + }, { + "id" : "f3", + "type" : "HtmlDialogEventStart", + "name" : "close", + "config" : { + "guid" : "1886B1A626EC83BF" + }, + "visual" : { + "at" : { "x" : 96, "y" : 160 } + }, + "connect" : { "id" : "f5", "to" : "f4" } + }, { + "id" : "f4", + "type" : "HtmlDialogExit", + "visual" : { + "at" : { "x" : 224, "y" : 160 } + } + }, { + "id" : "f6", + "type" : "HtmlDialogEventStart", + "name" : "demo1Signer", + "config" : { + "guid" : "1886B1CCAFDA9488" + }, + "visual" : { + "at" : { "x" : 96, "y" : 248 } + }, + "connect" : { "id" : "f30", "to" : "f11" } + }, { + "id" : "f7", + "type" : "HtmlDialogEnd", + "visual" : { + "at" : { "x" : 744, "y" : 248 } + } + }, { + "id" : "f11", + "type" : "SubProcessCall", + "name" : "upload document", + "config" : { + "processCall" : "connector/TransientDocuments:uploadDocument(java.io.File)", + "output" : { + "map" : { + "out" : "in", + "out.documentId" : "result.id" + } + }, + "call" : { + "params" : [ + { "name" : "file", "type" : "java.io.File" } + ], + "map" : { + "param.file" : "in.file.getJavaFile()" + } + } + }, + "visual" : { + "at" : { "x" : 224, "y" : 248 } + }, + "connect" : { "id" : "f14", "to" : "f13" } + }, { + "id" : "f13", + "type" : "SubProcessCall", + "name" : "create agreement", + "config" : { + "processCall" : "connector/Agreements:createAgreement(api.rest.v6.client.AgreementCreationInfo)", + "output" : { + "map" : { + "out" : "in", + "out.agreementId" : "result.agreementInfo.id" + } + }, + "call" : { + "params" : [ + { "name" : "agreement", "type" : "api.rest.v6.client.AgreementCreationInfo" } + ], + "map" : { + "param.agreement" : "com.axonivy.connector.adobe.esign.connector.service.AdobeSignService.getInstance().buildSimpleAgreement(\"1SignerDemo\", in.documentId, in.signer1)", + "param.agreement.senderSigns" : "api.rest.v6.client.AgreementCreationInfo.SenderSignsEnum.NEVER", + "param.agreement.signatureType" : "api.rest.v6.client.AgreementCreationInfo.SignatureTypeEnum.ESIGN" + } + } + }, + "visual" : { + "at" : { "x" : 392, "y" : 248 } + }, + "connect" : { "id" : "f39", "to" : "f38" } + }, { + "id" : "f15", + "type" : "HtmlDialogEventStart", + "name" : "sign1", + "config" : { + "guid" : "1886B532042E8984" + }, + "visual" : { + "at" : { "x" : 96, "y" : 504 } + }, + "connect" : { "id" : "f19", "to" : "f18" } + }, { + "id" : "f16", + "type" : "HtmlDialogEnd", + "visual" : { + "at" : { "x" : 352, "y" : 504 } + } + }, { + "id" : "f18", + "type" : "SubProcessCall", + "name" : "get signing urls", + "config" : { + "processCall" : "connector/Agreements:getSigningURLs(String,String)", + "call" : { + "params" : [ + { "name" : "agreementId", "type" : "String" }, + { "name" : "frameParent", "type" : "String" } + ], + "map" : { + "param.agreementId" : "in.agreementId", + "param.frameParent" : "ivy.html.applicationHomeRef()" + } + }, + "output" : { + "code" : [ + "import api.rest.v6.client.SigningUrlResponseSigningUrls;", + "import api.rest.v6.client.SigningUrlResponseSigningUrlSetInfos;", + "", + "for(SigningUrlResponseSigningUrlSetInfos signingInfos : result.signingURIs) {", + " for(SigningUrlResponseSigningUrls signingUrls : signingInfos.getSigningUrls()) {", + " if(signingUrls.email.equals(in.signer1)) {", + " in.signingURI = signingUrls.esignUrl;", + " }", + " }", + "}" + ] + } + }, + "visual" : { + "at" : { "x" : 224, "y" : 504 } + }, + "connect" : { "id" : "f17", "to" : "f16" } + }, { + "id" : "f20", + "type" : "HtmlDialogEventStart", + "name" : "demo2Signer", + "config" : { + "guid" : "18870B56110A5978" + }, + "visual" : { + "at" : { "x" : 96, "y" : 344 } + }, + "connect" : { "id" : "f9", "to" : "f23" } + }, { + "id" : "f23", + "type" : "SubProcessCall", + "name" : "upload document", + "config" : { + "processCall" : "connector/TransientDocuments:uploadDocument(java.io.File)", + "output" : { + "map" : { + "out" : "in", + "out.documentId" : "result.id" + } + }, + "call" : { + "params" : [ + { "name" : "file", "type" : "java.io.File" } + ], + "map" : { + "param.file" : "in.file.getJavaFile()" + } + } + }, + "visual" : { + "at" : { "x" : 224, "y" : 344 } + }, + "connect" : { "id" : "f48", "to" : "f47" } + }, { + "id" : "f38", + "type" : "SubProcessCall", + "name" : "get documents", + "config" : { + "processCall" : "connector/Agreements:getDocuments(String)", + "output" : { + "map" : { + "out" : "in", + "out.documents" : "result.documents" + } + }, + "call" : { + "params" : [ + { "name" : "agreementId", "type" : "String" } + ], + "map" : { + "param.agreementId" : "in.agreementId" + } + } + }, + "visual" : { + "at" : { "x" : 568, "y" : 248 } + }, + "connect" : { "id" : "f10", "to" : "f7" } + }, { + "id" : "f40", + "type" : "HtmlDialogMethodStart", + "name" : "downloadDocument(String,String)", + "config" : { + "callSignature" : "downloadDocument", + "result" : { + "params" : [ + { "name" : "file", "type" : "org.primefaces.model.StreamedContent" } + ], + "map" : { + "result.file" : "in.documentContent" + } + }, + "input" : { + "params" : [ + { "name" : "documentId", "type" : "String" }, + { "name" : "filename", "type" : "String" } + ], + "map" : { + "out.documentId" : "param.documentId", + "out.filename" : "param.filename" + } + }, + "guid" : "188B93A5550422ED" + }, + "visual" : { + "at" : { "x" : 96, "y" : 592 } + }, + "connect" : { "id" : "f44", "to" : "f43" } + }, { + "id" : "f41", + "type" : "HtmlDialogEnd", + "visual" : { + "at" : { "x" : 352, "y" : 592 } + } + }, { + "id" : "f43", + "type" : "SubProcessCall", + "name" : "download document", + "config" : { + "processCall" : "connector/Agreements:dowloadDocument(String,String,String,Boolean)", + "call" : { + "params" : [ + { "name" : "agreementId", "type" : "String" }, + { "name" : "documentId", "type" : "String" }, + { "name" : "filename", "type" : "String" }, + { "name" : "asFile", "type" : "Boolean" } + ], + "map" : { + "param.agreementId" : "in.agreementId", + "param.documentId" : "in.documentId", + "param.filename" : "in.filename", + "param.asFile" : "true" + } + }, + "output" : { + "code" : [ + "import com.axonivy.connector.adobe.esign.connector.ui.util.FileUtils;", + "in.documentContent = FileUtils.toStreamedContent(in.filename, result.download);" + ] + } + }, + "visual" : { + "at" : { "x" : 224, "y" : 592 } + }, + "connect" : { "id" : "f42", "to" : "f41" } + }, { + "id" : "f45", + "type" : "HtmlDialogEventStart", + "name" : "updateDocuments", + "config" : { + "guid" : "188B94B298216FB1" + }, + "visual" : { + "at" : { "x" : 568, "y" : 144 } + }, + "connect" : { "id" : "f46", "to" : "f38" } + }, { + "id" : "f47", + "type" : "SubProcessCall", + "name" : "create agreement", + "config" : { + "processCall" : "connector/Agreements:createAgreement(api.rest.v6.client.AgreementCreationInfo)", + "output" : { + "map" : { + "out" : "in", + "out.agreementId" : "result.agreementInfo.id" + } + }, + "call" : { + "params" : [ + { "name" : "agreement", "type" : "api.rest.v6.client.AgreementCreationInfo" } + ], + "map" : { + "param.agreement" : "com.axonivy.connector.adobe.esign.connector.service.AdobeSignService.getInstance().buildSimpleAgreementFor2SignerGroups(\"2SignerDemo\", in.documentId, [in.signer1], [in.signer2])" + } + } + }, + "visual" : { + "at" : { "x" : 392, "y" : 344 } + }, + "connect" : { "id" : "f33", "to" : "f38" } + }, { + "id" : "f21", + "type" : "HtmlDialogEventStart", + "name" : "sign2", + "config" : { + "guid" : "188F43D709132850" + }, + "visual" : { + "at" : { "x" : 424, "y" : 504 } + }, + "connect" : { "id" : "f26", "to" : "f25" } + }, { + "id" : "f24", + "type" : "HtmlDialogEnd", + "visual" : { + "at" : { "x" : 680, "y" : 504 } + } + }, { + "id" : "f25", + "type" : "SubProcessCall", + "name" : "get signing urls", + "config" : { + "processCall" : "connector/Agreements:getSigningURLs(String,String)", + "call" : { + "params" : [ + { "name" : "agreementId", "type" : "String" }, + { "name" : "frameParent", "type" : "String" } + ], + "map" : { + "param.agreementId" : "in.agreementId", + "param.frameParent" : "ivy.html.applicationHomeRef()" + } + }, + "output" : { + "code" : [ + "import api.rest.v6.client.SigningUrlResponseSigningUrls;", + "import api.rest.v6.client.SigningUrlResponseSigningUrlSetInfos;", + "", + "for(SigningUrlResponseSigningUrlSetInfos signingInfos : result.signingURIs) {", + " for(SigningUrlResponseSigningUrls signingUrls : signingInfos.getSigningUrls()) {", + " if(signingUrls.email.equals(in.signer2)) {", + " in.signingURI = signingUrls.esignUrl;", + " }", + " }", + "}" + ] + } + }, + "visual" : { + "at" : { "x" : 552, "y" : 504 } + }, + "connect" : { "id" : "f29", "to" : "f24" } + }, { + "id" : "f28", + "type" : "HtmlDialogEventStart", + "name" : "fileUpload", + "config" : { + "guid" : "188F7C5FB0E4D152" + }, + "visual" : { + "at" : { "x" : 96, "y" : 672 } + }, + "connect" : { "id" : "f35", "to" : "f34" } + }, { + "id" : "f31", + "type" : "HtmlDialogEnd", + "visual" : { + "at" : { "x" : 352, "y" : 672 } + } + }, { + "id" : "f34", + "type" : "Script", + "config" : { + "output" : { + "code" : "ivy.log.info(in.file.getAbsolutePath());" + } + }, + "visual" : { + "at" : { "x" : 224, "y" : 672 } + }, + "connect" : { "id" : "f32", "to" : "f31" } + } ] +} \ No newline at end of file diff --git a/adobe-esign-connector-demo/webContent/layouts/basic-10.xhtml b/adobe-esign-connector-demo/webContent/layouts/basic-10.xhtml new file mode 100644 index 0000000..f022574 --- /dev/null +++ b/adobe-esign-connector-demo/webContent/layouts/basic-10.xhtml @@ -0,0 +1,67 @@ + + + + + + + + + + <ui:insert name="title">Ivy Html Dialog</ui:insert> + + + + + + + + + + + + + +
+ + default content + + + +
+
+ +
+
+
+ + + + +
+ \ No newline at end of file diff --git a/adobe-esign-connector-demo/webContent/layouts/includes/exception-details.xhtml b/adobe-esign-connector-demo/webContent/layouts/includes/exception-details.xhtml new file mode 100644 index 0000000..a4979dc --- /dev/null +++ b/adobe-esign-connector-demo/webContent/layouts/includes/exception-details.xhtml @@ -0,0 +1,109 @@ + + + + + + +

+ +

+ + +

Error id

+

#{errorPage.exceptionId}

+

Error Timestamp

+

#{errorPage.createdAt}

+
+ + + + +

Attributes

+
+ + + + + + + + + + + + + + + +
NameValue
+
+
+

Thrown by

+

Process: + +
Element: + +

+
+ + +

Process call stack

+ +
#{caller.callerElement}
+
+
+ +

Technical cause

+
#{causedBy.class.simpleName}: #{causedBy.message.trim()}
+
+
+ +

Request Uri

+

#{errorPage.getRequestUri()}

+
+

Servlet

+

#{errorPage.getServletName()}

+
+ +

Application

+

#{errorPage.applicationName}

+
+ + +

Thread local values

+
+ + + + + + + + + + + + + + + +
KeyValue
+
+
+
+ +

Stack-Trace

+
#{errorPage.getStackTrace()}
+
+ diff --git a/adobe-esign-connector-demo/webContent/layouts/includes/exception.xhtml b/adobe-esign-connector-demo/webContent/layouts/includes/exception.xhtml new file mode 100644 index 0000000..2303e7c --- /dev/null +++ b/adobe-esign-connector-demo/webContent/layouts/includes/exception.xhtml @@ -0,0 +1,47 @@ + + + + + + + + + +
+
+ + +
+ + + + + + + + + +
+ + \ No newline at end of file diff --git a/adobe-esign-connector-demo/webContent/layouts/includes/footer.xhtml b/adobe-esign-connector-demo/webContent/layouts/includes/footer.xhtml new file mode 100644 index 0000000..3eb052b --- /dev/null +++ b/adobe-esign-connector-demo/webContent/layouts/includes/footer.xhtml @@ -0,0 +1,18 @@ + + + +
+ + #{ivyAdvisor.applicationName} + + +
+
+ + \ No newline at end of file diff --git a/adobe-esign-connector-demo/webContent/layouts/includes/progress-loader.xhtml b/adobe-esign-connector-demo/webContent/layouts/includes/progress-loader.xhtml new file mode 100644 index 0000000..0d68a75 --- /dev/null +++ b/adobe-esign-connector-demo/webContent/layouts/includes/progress-loader.xhtml @@ -0,0 +1,15 @@ + + + + +
+
+
Loading...
+
+
+ + + +
+
\ No newline at end of file diff --git a/adobe-esign-connector-product/.gitignore b/adobe-esign-connector-product/.gitignore new file mode 100644 index 0000000..c53bcb2 --- /dev/null +++ b/adobe-esign-connector-product/.gitignore @@ -0,0 +1 @@ +openapi.* diff --git a/adobe-esign-connector-product/README.md b/adobe-esign-connector-product/README.md new file mode 100644 index 0000000..3071389 --- /dev/null +++ b/adobe-esign-connector-product/README.md @@ -0,0 +1,135 @@ + +# Adobe Sign Connector + +The Adobe Sign Connector is a project that simplifies the authentication process and enables easy integration and use of Adobe Sign services for signing documents. + +[Adobe Sign](https://www.adobe.com/sign.html) + +An Adobe Sign account needs to be created to setup and use the connector. + +## Adobe Sign account creation + + 1. Create a **AdobeSign** Company **Account** OR for Developer Account creation use [Create Developer Account, APIs for custom applications | Acrobat Sign](https://www.adobe.com/sign/developer-form.html) and follow the steps: + + a. Fill form with personal and business data + continue + ![fill-account-info](images/createAccountFillInfo.png) + + b. Provide a password + continue + ![fill-password](images/createAccountPassword.png) + + c. Provide date of birth + continue + ![fill-birth-date](images/createAccountBirthDate.png) + + d. You will receive a verification code. Insert the code. Will automatically continue + ![verification-code](images/createAccountVerificationCode.png) + + e. Developer account is created + ![account-finished](images/createAccountFinished.png) + +## Demo + +The demo project can be used to test the authentication and signing and the demo implementation can be used as inspiration for development. + +### How to sign a document in the Demo application +:exclamation: The demo will work only after correct setup of the connector + ![demo1](images/demo1.png) + + 1. Choose and upload a PDF file + 2. Fill email of 1 or 2 signers + 3. Create Agreement for 1 or 2 signers + 4. Sign as signer 1 or 2 + 5. A modal dialog with Adobe signing page is opened. Create a signature in the document. + ![demo1](images/demo2.png) + + 6. Confirm the signature with **Click to Sign** + + ![demo1](images/demo3.png) + + 7. The signed document can be downloaded + + ![demo1](images/demo4.png) + + +## Setup + +Adobe Sign provides 2 options for authentication. (See Setup section) + + 1. Integration Key + 2. OAuth2 + + +To setup and use the Adobe Sign Connector it needs to be connected with Adobe. An Adobe administration account needs to be created. (See Adobe Sign account creation) + +### Admin Setup Page +Adobe Sign Connector provides a setup page for easy setup of the connector and setup of the authentization. +To be able to open the Admin Setup page the admin user needs to own `ADOBE_ESIGN_ADMIN` role which is part of the connector. +![admin-page](images/adminPage.png) +#### General +| Variable name | Description | +|--|--| +| adobe-sign-connector.host | hostname of Adobe Sign server | +| adobe-sign-connector.returnPage | relative part of URL that is called after singing was finished | + +#### Integration Key +:exclamation: If Integration Key is set then OAuth is disabled for the connector. If want to use OAuth for the connector then leave the Integration Key empty :exclamation: +| Variable name | Description | +|--|--| +| adobe-sign-connector.integrationKey | Integration key from Adobe Sign configuration | + +##### How to get Integration Key + + 1. Go to your Adobe Sign account page: https://secure.adobesign.com/account/ + 2. Open **Access Tokens** configuration + ![access-tokens](images/integrationKey1.png) + 3. Create new Integration Key + ![create-integration-key](images/integrationKey2.png) + 4. Copy the Integration Key to the Admin Setup Page + ![copy-integration-key](images/integrationKey3.png) + +### Oauth +Adobe API doc references for OAuth + + 1. https://secure.adobesign.com/public/static/oauthDoc.jsp + 2. https://opensource.adobe.com/acrobat-sign/developer_guide/oauth.html + +#### OAuth API Application setup +An API Application needs to be setup at Adobe Sign admin account before OAuth can be configured in the connector. + 1. Go to your Adobe Sign account page: https://secure.adobesign.com/account/ + 2. Go to **API Applications** configuration + ![api-applications](images/oauth1.png) + 3. Create new API Application. Set the Name, Display Name and Domain + ![new-application](images/oauth2.png) + 4. Open the newly created Application and copy ID and Secret to the connector's Admin Setup page + a. Application ID = `adobe-sign-connector.clientId` + b. Client Secret = `adobe-sign-connector.clientSecret` + ![application-detail](images/oauth3.png) +5. Open **Configure OAuth for Application** for your application + a. Copy **Redirect URI** from connector's Admin Setup Page and paste it to the Application Configuration + b. Enable persmissions that can be requested from this Application + ![application-oauth](images/oauth4.png) + + +#### Variables and Admin Setup page for OAuth description +| Variable name | Description | Example +|--|--|--| +| adobe-sign-connector.baseUri | Base URI for getting the access and refresh access tokens (without the `/token` or `/refresh` part) | `https://api.eu2.adobesign.com/oauth/v2` +| adobe-sign-connector.authenticationUri| URL for the Authorization request (:exclamation:differs from tokens URL)| `https://secure.eu2.adobesign.com/public/oauth/v2` +| adobe-sign-connector.clientId| Adobe API Application Client ID| +| adobe-sign-connector.clientSecret| Adobe API Application Client Secret | +| adobe-sign-connector.permissions | List of permissions that will be requested for the OAuth token | `user_read:account user_write:account user_login:account agreement_read:account agreement_write:account agreement_send:account widget_read:account widget_write:account library_read:account library_write:account workflow_read:account workflow_write:account` +| adobe-sign-connector.oauthToken | Info about the OAuth refresh token. Empty means there is no token initialized. To request a new token use the `Save and Request new Token` button | +| adobe-sign-connector.accessToken| Info about the OAuth access token. | +| Redirect URI | This URI just needs to be setup to the API Application at Adobe Sign account page. (see **OAuth API Application setup** section)| `https://localhost:8444/designer/pro/adobe-esign-connector/18A83631DA63DA93/oauthResume.ivp` + + +#### Requesting OAuth token +:exclamation::exclamation::exclamation: Please configure all the Variables in OAuth section on the Admin Setup page (see previous section) as they are necessary for requesting the token. + + 1. Click the `Save and Request new Token` button. You will be redirected to Adobe Sign login page if the configuration of the Variables is correct. +![save-and-request-token](images/tokenRequest1.png) +2. Login with your Adobe Sign account +![adobe-login](images/tokenRequest2.png) +3. After successful login you should see all the requested permissions. Click **Allow Access**. +![request-permissions](images/tokenRequest3.png) +4. The token will be retrieved and you should be redirected back to the connector's Admin Setup page and should be able to see the initialized token. +![token](images/tokenRequest4.png) diff --git a/adobe-esign-connector-product/images/adminPage.png b/adobe-esign-connector-product/images/adminPage.png new file mode 100644 index 0000000..4131954 Binary files /dev/null and b/adobe-esign-connector-product/images/adminPage.png differ diff --git a/adobe-esign-connector-product/images/createAccountBirthDate.png b/adobe-esign-connector-product/images/createAccountBirthDate.png new file mode 100644 index 0000000..0ec7e04 Binary files /dev/null and b/adobe-esign-connector-product/images/createAccountBirthDate.png differ diff --git a/adobe-esign-connector-product/images/createAccountFillInfo.png b/adobe-esign-connector-product/images/createAccountFillInfo.png new file mode 100644 index 0000000..b9b4b3e Binary files /dev/null and b/adobe-esign-connector-product/images/createAccountFillInfo.png differ diff --git a/adobe-esign-connector-product/images/createAccountFinished.png b/adobe-esign-connector-product/images/createAccountFinished.png new file mode 100644 index 0000000..49c0f0d Binary files /dev/null and b/adobe-esign-connector-product/images/createAccountFinished.png differ diff --git a/adobe-esign-connector-product/images/createAccountPassword.png b/adobe-esign-connector-product/images/createAccountPassword.png new file mode 100644 index 0000000..91aabd8 Binary files /dev/null and b/adobe-esign-connector-product/images/createAccountPassword.png differ diff --git a/adobe-esign-connector-product/images/createAccountVerificationCode.png b/adobe-esign-connector-product/images/createAccountVerificationCode.png new file mode 100644 index 0000000..d4f2d9f Binary files /dev/null and b/adobe-esign-connector-product/images/createAccountVerificationCode.png differ diff --git a/adobe-esign-connector-product/images/demo1.png b/adobe-esign-connector-product/images/demo1.png new file mode 100644 index 0000000..994a979 Binary files /dev/null and b/adobe-esign-connector-product/images/demo1.png differ diff --git a/adobe-esign-connector-product/images/demo2.png b/adobe-esign-connector-product/images/demo2.png new file mode 100644 index 0000000..93a19de Binary files /dev/null and b/adobe-esign-connector-product/images/demo2.png differ diff --git a/adobe-esign-connector-product/images/demo3.png b/adobe-esign-connector-product/images/demo3.png new file mode 100644 index 0000000..8a7b26f Binary files /dev/null and b/adobe-esign-connector-product/images/demo3.png differ diff --git a/adobe-esign-connector-product/images/demo4.png b/adobe-esign-connector-product/images/demo4.png new file mode 100644 index 0000000..e49ae20 Binary files /dev/null and b/adobe-esign-connector-product/images/demo4.png differ diff --git a/adobe-esign-connector-product/images/integrationKey1.png b/adobe-esign-connector-product/images/integrationKey1.png new file mode 100644 index 0000000..62da6af Binary files /dev/null and b/adobe-esign-connector-product/images/integrationKey1.png differ diff --git a/adobe-esign-connector-product/images/integrationKey2.png b/adobe-esign-connector-product/images/integrationKey2.png new file mode 100644 index 0000000..2a996cf Binary files /dev/null and b/adobe-esign-connector-product/images/integrationKey2.png differ diff --git a/adobe-esign-connector-product/images/integrationKey3.png b/adobe-esign-connector-product/images/integrationKey3.png new file mode 100644 index 0000000..62e4a8e Binary files /dev/null and b/adobe-esign-connector-product/images/integrationKey3.png differ diff --git a/adobe-esign-connector-product/images/oauth1.png b/adobe-esign-connector-product/images/oauth1.png new file mode 100644 index 0000000..1202bd7 Binary files /dev/null and b/adobe-esign-connector-product/images/oauth1.png differ diff --git a/adobe-esign-connector-product/images/oauth2.png b/adobe-esign-connector-product/images/oauth2.png new file mode 100644 index 0000000..4946080 Binary files /dev/null and b/adobe-esign-connector-product/images/oauth2.png differ diff --git a/adobe-esign-connector-product/images/oauth3.png b/adobe-esign-connector-product/images/oauth3.png new file mode 100644 index 0000000..071821f Binary files /dev/null and b/adobe-esign-connector-product/images/oauth3.png differ diff --git a/adobe-esign-connector-product/images/oauth4.png b/adobe-esign-connector-product/images/oauth4.png new file mode 100644 index 0000000..824c8e3 Binary files /dev/null and b/adobe-esign-connector-product/images/oauth4.png differ diff --git a/adobe-esign-connector-product/images/tokenRequest1.png b/adobe-esign-connector-product/images/tokenRequest1.png new file mode 100644 index 0000000..963817b Binary files /dev/null and b/adobe-esign-connector-product/images/tokenRequest1.png differ diff --git a/adobe-esign-connector-product/images/tokenRequest2.png b/adobe-esign-connector-product/images/tokenRequest2.png new file mode 100644 index 0000000..b5bca73 Binary files /dev/null and b/adobe-esign-connector-product/images/tokenRequest2.png differ diff --git a/adobe-esign-connector-product/images/tokenRequest3.png b/adobe-esign-connector-product/images/tokenRequest3.png new file mode 100644 index 0000000..b65e05d Binary files /dev/null and b/adobe-esign-connector-product/images/tokenRequest3.png differ diff --git a/adobe-esign-connector-product/images/tokenRequest4.png b/adobe-esign-connector-product/images/tokenRequest4.png new file mode 100644 index 0000000..b2f2af1 Binary files /dev/null and b/adobe-esign-connector-product/images/tokenRequest4.png differ diff --git a/MY-PRODUCT-NAME-product/pom.xml b/adobe-esign-connector-product/pom.xml similarity index 73% rename from MY-PRODUCT-NAME-product/pom.xml rename to adobe-esign-connector-product/pom.xml index dd59011..eda1f8c 100644 --- a/MY-PRODUCT-NAME-product/pom.xml +++ b/adobe-esign-connector-product/pom.xml @@ -1,14 +1,9 @@ 4.0.0 - com.axonivy.market - MY-PRODUCT-NAME-product - 10.0.0-SNAPSHOT + com.axonivy.connector.adobe.esign + adobe-esign-connector-product + 10.0.11-SNAPSHOT pom - - - ../MY-PRODUCT-NAME/config/variables.yaml - - @@ -37,14 +32,10 @@ generate-sources - ${skip-readme} - - - diff --git a/MY-PRODUCT-NAME-product/product.json b/adobe-esign-connector-product/product.json similarity index 56% rename from MY-PRODUCT-NAME-product/product.json rename to adobe-esign-connector-product/product.json index bc3625b..fbc60b4 100644 --- a/MY-PRODUCT-NAME-product/product.json +++ b/adobe-esign-connector-product/product.json @@ -1,12 +1,12 @@ { "installers": [ { - "id": "maven-import", + "id": "maven-dependency", "data": { - "projects": [ + "dependencies": [ { - "groupId": "MY-GROUP-ID", - "artifactId": "MY-PRODUCT-NAME-demo", + "groupId": "com.axonivy.connector.adobe.esign", + "artifactId": "adobe-esign-connector", "version": "${version}", "type": "iar" } @@ -23,12 +23,12 @@ } }, { - "id": "maven-dependency", + "id": "maven-import", "data": { - "dependencies": [ + "projects": [ { - "groupId": "MY-GROUP-ID", - "artifactId": "MY-PRODUCT-NAME", + "groupId": "com.axonivy.connector.adobe.esign", + "artifactId": "adobe-esign-connector", "version": "${version}", "type": "iar" } @@ -43,27 +43,6 @@ } ] } - }, - { - "id": "maven-dropins", - "data": { - "dependencies": [ - { - "groupId": "MY-GROUP-ID", - "artifactId": "MY-PRODUCT-NAME", - "version": "${version}" - } - ], - "repositories": [ - { - "id": "maven.axonivy.com", - "url": "https://maven.axonivy.com", - "snapshots": { - "enabled": "true" - } - } - ] - } } ] } diff --git a/MY-PRODUCT-NAME-product/zip.xml b/adobe-esign-connector-product/zip.xml similarity index 77% rename from MY-PRODUCT-NAME-product/zip.xml rename to adobe-esign-connector-product/zip.xml index 003f06c..7a40a2d 100644 --- a/MY-PRODUCT-NAME-product/zip.xml +++ b/adobe-esign-connector-product/zip.xml @@ -11,15 +11,9 @@ . product.json - openapi.json - **/*.png - - - - target - / - + openapi.* README.md + **/*.png diff --git a/adobe-esign-connector-test/.classpath b/adobe-esign-connector-test/.classpath new file mode 100644 index 0000000..e938886 --- /dev/null +++ b/adobe-esign-connector-test/.classpath @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/adobe-esign-connector-test/.gitignore b/adobe-esign-connector-test/.gitignore new file mode 100644 index 0000000..9b0d458 --- /dev/null +++ b/adobe-esign-connector-test/.gitignore @@ -0,0 +1,19 @@ +# general +Thumbs.db +.DS_Store +*~ +*.log + +# java +*.class +hs_err_pid* + +# maven +target/ +lib/mvn-deps/ + +# ivy +classes/ +src_dataClasses/ +src_wsproc/ +logs/ diff --git a/adobe-esign-connector-test/.project b/adobe-esign-connector-test/.project new file mode 100644 index 0000000..364fb0a --- /dev/null +++ b/adobe-esign-connector-test/.project @@ -0,0 +1,49 @@ + + + adobe-esign-connector-test + + + + + + ch.ivyteam.ivy.designer.dataClasses.ui.ivyDataClassBuilder + + + + + ch.ivyteam.ivy.designer.process.ui.ivyWebServiceProcessClassBuilder + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.wst.common.project.facet.core.builder + + + + + ch.ivyteam.ivy.designer.ide.ivyModelValidationBuilder + + + + + org.eclipse.m2e.core.maven2Builder + + + + + + ch.ivyteam.ivy.project.IvyProjectNature + org.eclipse.wst.common.modulecore.ModuleCoreNature + org.eclipse.jem.workbench.JavaEMFNature + org.eclipse.jdt.core.javanature + org.eclipse.m2e.core.maven2Nature + org.eclipse.jem.beaninfo.BeanInfoNature + org.eclipse.wst.common.project.facet.core.nature + org.eclipse.wst.jsdt.core.jsNature + + diff --git a/adobe-esign-connector-test/.settings/.jsdtscope b/adobe-esign-connector-test/.settings/.jsdtscope new file mode 100644 index 0000000..869c01d --- /dev/null +++ b/adobe-esign-connector-test/.settings/.jsdtscope @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/adobe-esign-connector-test/.settings/ch.ivyteam.ivy.designer.prefs b/adobe-esign-connector-test/.settings/ch.ivyteam.ivy.designer.prefs new file mode 100644 index 0000000..d7b0ff4 --- /dev/null +++ b/adobe-esign-connector-test/.settings/ch.ivyteam.ivy.designer.prefs @@ -0,0 +1,5 @@ +ch.ivyteam.ivy.designer.preferences.DataClassPreferencePage\:DEFAULT_DATA_CLASS=com.axonivy.connector.adobe.esign.test.Data +ch.ivyteam.ivy.designer.preferences.DataClassPreferencePage\:DEFAULT_NAMESPACE=com.axonivy.connector.adobe.esign.test +ch.ivyteam.ivy.project.preferences\:PRIMEFACES_VERSION=11 +ch.ivyteam.ivy.project.preferences\:PROJECT_VERSION=100000 +eclipse.preferences.version=1 diff --git a/adobe-esign-connector-test/.settings/org.eclipse.jdt.core.prefs b/adobe-esign-connector-test/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 0000000..d4540a5 --- /dev/null +++ b/adobe-esign-connector-test/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,10 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=17 +org.eclipse.jdt.core.compiler.compliance=17 +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=warning +org.eclipse.jdt.core.compiler.release=enabled +org.eclipse.jdt.core.compiler.source=17 diff --git a/adobe-esign-connector-test/.settings/org.eclipse.wst.common.component b/adobe-esign-connector-test/.settings/org.eclipse.wst.common.component new file mode 100644 index 0000000..0a27a52 --- /dev/null +++ b/adobe-esign-connector-test/.settings/org.eclipse.wst.common.component @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/adobe-esign-connector-test/.settings/org.eclipse.wst.common.project.facet.core.prefs.xml b/adobe-esign-connector-test/.settings/org.eclipse.wst.common.project.facet.core.prefs.xml new file mode 100644 index 0000000..9b4b9fc --- /dev/null +++ b/adobe-esign-connector-test/.settings/org.eclipse.wst.common.project.facet.core.prefs.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/adobe-esign-connector-test/.settings/org.eclipse.wst.common.project.facet.core.xml b/adobe-esign-connector-test/.settings/org.eclipse.wst.common.project.facet.core.xml new file mode 100644 index 0000000..156ecdb --- /dev/null +++ b/adobe-esign-connector-test/.settings/org.eclipse.wst.common.project.facet.core.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/adobe-esign-connector-test/.settings/org.eclipse.wst.css.core.prefs b/adobe-esign-connector-test/.settings/org.eclipse.wst.css.core.prefs new file mode 100644 index 0000000..5ddc6bd --- /dev/null +++ b/adobe-esign-connector-test/.settings/org.eclipse.wst.css.core.prefs @@ -0,0 +1,2 @@ +css-profile/=org.eclipse.wst.css.core.cssprofile.css3 +eclipse.preferences.version=1 diff --git a/adobe-esign-connector-test/.settings/org.eclipse.wst.jsdt.ui.superType.container b/adobe-esign-connector-test/.settings/org.eclipse.wst.jsdt.ui.superType.container new file mode 100644 index 0000000..3bd5d0a --- /dev/null +++ b/adobe-esign-connector-test/.settings/org.eclipse.wst.jsdt.ui.superType.container @@ -0,0 +1 @@ +org.eclipse.wst.jsdt.launching.baseBrowserLibrary \ No newline at end of file diff --git a/adobe-esign-connector-test/.settings/org.eclipse.wst.jsdt.ui.superType.name b/adobe-esign-connector-test/.settings/org.eclipse.wst.jsdt.ui.superType.name new file mode 100644 index 0000000..05bd71b --- /dev/null +++ b/adobe-esign-connector-test/.settings/org.eclipse.wst.jsdt.ui.superType.name @@ -0,0 +1 @@ +Window \ No newline at end of file diff --git a/adobe-esign-connector-test/cms/Files/samplePdf.pdf b/adobe-esign-connector-test/cms/Files/samplePdf.pdf new file mode 100644 index 0000000..c0e31a0 Binary files /dev/null and b/adobe-esign-connector-test/cms/Files/samplePdf.pdf differ diff --git a/adobe-esign-connector-test/config/custom-fields.yaml b/adobe-esign-connector-test/config/custom-fields.yaml new file mode 100644 index 0000000..aa19ae0 --- /dev/null +++ b/adobe-esign-connector-test/config/custom-fields.yaml @@ -0,0 +1,20 @@ +# == Custom Fields Information == +# +# You can define here your project custom fields. +# Have a look at our documentation for more information. +# +CustomFields: +# Tasks: +# MyTaskCustomField: +# Label: My task custom field +# Description: This new task custom field can be used to ... +# Type: STRING +# Cases: +# MyCaseCustomField: +# Label: My case custom field +# Description: This new case custom field can be used to ... +# Type: STRING +# Starts: +# MyStartCustomField: +# Label: My start custom field +# Description: This new start custom field can be used to ... diff --git a/adobe-esign-connector-test/config/databases.yaml b/adobe-esign-connector-test/config/databases.yaml new file mode 100644 index 0000000..247b128 --- /dev/null +++ b/adobe-esign-connector-test/config/databases.yaml @@ -0,0 +1 @@ +Databases: diff --git a/adobe-esign-connector-test/config/overrides.any b/adobe-esign-connector-test/config/overrides.any new file mode 100644 index 0000000..f59ec20 --- /dev/null +++ b/adobe-esign-connector-test/config/overrides.any @@ -0,0 +1 @@ +* \ No newline at end of file diff --git a/adobe-esign-connector-test/config/persistence.xml b/adobe-esign-connector-test/config/persistence.xml new file mode 100644 index 0000000..d6b96d7 --- /dev/null +++ b/adobe-esign-connector-test/config/persistence.xml @@ -0,0 +1,2 @@ + + diff --git a/adobe-esign-connector-test/config/rest-clients.yaml b/adobe-esign-connector-test/config/rest-clients.yaml new file mode 100644 index 0000000..8e85296 --- /dev/null +++ b/adobe-esign-connector-test/config/rest-clients.yaml @@ -0,0 +1 @@ +RestClients: diff --git a/adobe-esign-connector-test/config/roles.xml b/adobe-esign-connector-test/config/roles.xml new file mode 100644 index 0000000..59892fe --- /dev/null +++ b/adobe-esign-connector-test/config/roles.xml @@ -0,0 +1,4 @@ + + + Everybody + diff --git a/adobe-esign-connector-test/config/users.xml b/adobe-esign-connector-test/config/users.xml new file mode 100644 index 0000000..51a6906 --- /dev/null +++ b/adobe-esign-connector-test/config/users.xml @@ -0,0 +1,2 @@ + + diff --git a/adobe-esign-connector-test/config/variables.yaml b/adobe-esign-connector-test/config/variables.yaml new file mode 100644 index 0000000..64c8fa0 --- /dev/null +++ b/adobe-esign-connector-test/config/variables.yaml @@ -0,0 +1,9 @@ +# == Variables == +# +# You can define here your project Variables. +# If you want to define/override a Variable for a specific Environment, +# add an additional ‘variables.yaml’ file in a subdirectory in the ‘Config’ folder: +# '/Config/_/variables.yaml +# +Variables: +# myVariable: value diff --git a/adobe-esign-connector-test/config/webservice-clients.yaml b/adobe-esign-connector-test/config/webservice-clients.yaml new file mode 100644 index 0000000..060b018 --- /dev/null +++ b/adobe-esign-connector-test/config/webservice-clients.yaml @@ -0,0 +1 @@ +WebServiceClients: diff --git a/adobe-esign-connector-test/dataclasses/com/axonivy/connector/adobe/esign/test/Data.ivyClass b/adobe-esign-connector-test/dataclasses/com/axonivy/connector/adobe/esign/test/Data.ivyClass new file mode 100644 index 0000000..13fd8e1 --- /dev/null +++ b/adobe-esign-connector-test/dataclasses/com/axonivy/connector/adobe/esign/test/Data.ivyClass @@ -0,0 +1,2 @@ +Data #class +com.axonivy.connector.adobe.esign.test #namespace diff --git a/adobe-esign-connector-test/pom.xml b/adobe-esign-connector-test/pom.xml new file mode 100644 index 0000000..d11dc75 --- /dev/null +++ b/adobe-esign-connector-test/pom.xml @@ -0,0 +1,34 @@ + + + 4.0.0 + com.axonivy.connector.adobe.esign + adobe-esign-connector-test + 10.0.11-SNAPSHOT + iar + + + com.axonivy.connector.adobe.esign + adobe-esign-connector + 10.0.11-SNAPSHOT + iar + + + com.axonivy.ivy.test + unit-tester + 10.0.0 + test + + + + src_test + + + com.axonivy.ivy.ci + project-build-plugin + 10.0.6 + true + + + + diff --git a/adobe-esign-connector-test/src/com/axonivy/connector/adobe/esign/connector/test/AdobeSignAgreementsServiceMock.java b/adobe-esign-connector-test/src/com/axonivy/connector/adobe/esign/connector/test/AdobeSignAgreementsServiceMock.java new file mode 100644 index 0000000..9dccee9 --- /dev/null +++ b/adobe-esign-connector-test/src/com/axonivy/connector/adobe/esign/connector/test/AdobeSignAgreementsServiceMock.java @@ -0,0 +1,68 @@ +package com.axonivy.connector.adobe.esign.connector.test; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; + +import javax.annotation.security.PermitAll; +import javax.ws.rs.Consumes; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +import org.apache.commons.io.IOUtils; + +import ch.ivyteam.api.API; +import io.swagger.v3.oas.annotations.Hidden; + +@Hidden +@PermitAll +@Path("adobeSignMock") +public class AdobeSignAgreementsServiceMock { + + @POST + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + @Path("agreements") + public Response createAgreement(String payload) { + API.checkParameterNotNull(payload, "payload"); + return Response.status(201).entity(load("json/createAgreement.json")).build(); + } + + @GET + @Produces(MediaType.APPLICATION_JSON) + @Path("agreements/{agreementId}/documents") + public Response getDocuments(@PathParam(value = "agreementId") String agreementId) { + API.checkParameterNotNull(agreementId, "agreementId"); + return Response.status(201).entity(load("json/getDocuments.json")).build(); + } + + @GET + @Produces(MediaType.APPLICATION_JSON) + @Path("agreements/{agreementId}/documents/{documentId}") + public Response downloadDocument(@PathParam(value = "agreementId") String agreementId, + @PathParam(value = "documentId") String documentId) throws IOException { + API.checkParameterNotNull(agreementId, "agreementId"); + return Response.status(200).entity(TestService.getSamplePdf()).build(); + } + + @GET + @Produces(MediaType.APPLICATION_JSON) + @Path("agreements/{agreementId}/signingUrls") + public Response getSigningUrls(@PathParam(value = "agreementId") String agreementId) { + API.checkParameterNotNull(agreementId, "agreementId"); + return Response.status(201).entity(load("json/getSigningUrls.json")).build(); + } + + private static String load(String path) { + try (InputStream is = AdobeSignAgreementsServiceMock.class.getResourceAsStream(path)) { + return IOUtils.toString(is, StandardCharsets.UTF_8); + } catch (IOException ex) { + throw new RuntimeException("Failed to read resource: " + path); + } + } +} diff --git a/adobe-esign-connector-test/src/com/axonivy/connector/adobe/esign/connector/test/AdobeSignTransientDocumentsServiceMock.java b/adobe-esign-connector-test/src/com/axonivy/connector/adobe/esign/connector/test/AdobeSignTransientDocumentsServiceMock.java new file mode 100644 index 0000000..9fcb013 --- /dev/null +++ b/adobe-esign-connector-test/src/com/axonivy/connector/adobe/esign/connector/test/AdobeSignTransientDocumentsServiceMock.java @@ -0,0 +1,43 @@ +package com.axonivy.connector.adobe.esign.connector.test; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; + +import javax.annotation.security.PermitAll; +import javax.ws.rs.Consumes; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +import org.apache.commons.io.IOUtils; + +import ch.ivyteam.api.API; +import io.swagger.v3.oas.annotations.Hidden; + +@Hidden +@PermitAll +@Path("adobeSignMock") +public class AdobeSignTransientDocumentsServiceMock { + + @POST + @Produces(MediaType.APPLICATION_JSON) + @Consumes(MediaType.MULTIPART_FORM_DATA) + @Path("transientDocuments") + public Response transientDocuemts(String payload) { + API.checkParameterNotNull(payload, "payload"); + return Response.status(201) + .entity(load("json/uploadDocument.json")) + .build(); + } + + private static String load(String path) { + try (InputStream is = AdobeSignTransientDocumentsServiceMock.class.getResourceAsStream(path)) { + return IOUtils.toString(is, StandardCharsets.UTF_8); + } catch (IOException ex) { + throw new RuntimeException("Failed to read resource: " + path); + } + } +} diff --git a/adobe-esign-connector-test/src/com/axonivy/connector/adobe/esign/connector/test/TestService.java b/adobe-esign-connector-test/src/com/axonivy/connector/adobe/esign/connector/test/TestService.java new file mode 100644 index 0000000..f1ac386 --- /dev/null +++ b/adobe-esign-connector-test/src/com/axonivy/connector/adobe/esign/connector/test/TestService.java @@ -0,0 +1,27 @@ +package com.axonivy.connector.adobe.esign.connector.test; + +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.Locale; + +import org.apache.commons.compress.utils.IOUtils; + +import ch.ivyteam.ivy.cm.ContentObjectValue; +import ch.ivyteam.ivy.environment.Ivy; +import ch.ivyteam.ivy.scripting.objects.File; + +public class TestService { + private static final String SAMPLE_PDF_CMS_PATH = "/Files/samplePdf"; + + public static java.io.File getSamplePdf() throws IOException { + ContentObjectValue cov = Ivy.cm().findValue(SAMPLE_PDF_CMS_PATH).resolve(Locale.ENGLISH).orElse(null); + if (cov == null) { + return null; + } + java.io.File sampleFile = new File("samplePdf.pdf", true).getJavaFile(); + FileOutputStream fos = new FileOutputStream(sampleFile); + IOUtils.copy(cov.read().inputStream(), fos); + return sampleFile; + + } +} diff --git a/adobe-esign-connector-test/src/com/axonivy/connector/adobe/esign/connector/test/json/createAgreement.json b/adobe-esign-connector-test/src/com/axonivy/connector/adobe/esign/connector/test/json/createAgreement.json new file mode 100644 index 0000000..31c3d25 --- /dev/null +++ b/adobe-esign-connector-test/src/com/axonivy/connector/adobe/esign/connector/test/json/createAgreement.json @@ -0,0 +1,3 @@ +{ + "id": "CBJCHBCAABAAMaMFFCyiTyCblablablablabla" +} diff --git a/adobe-esign-connector-test/src/com/axonivy/connector/adobe/esign/connector/test/json/getDocuments.json b/adobe-esign-connector-test/src/com/axonivy/connector/adobe/esign/connector/test/json/getDocuments.json new file mode 100644 index 0000000..9cecbad --- /dev/null +++ b/adobe-esign-connector-test/src/com/axonivy/connector/adobe/esign/connector/test/json/getDocuments.json @@ -0,0 +1,17 @@ +{ + "documents": [ + { + "createdDate": [ + 2023, + 9, + 18 + ], + "id": "3AAABLblqZhDM2f8YEhDF1N6Oz-8ZY5jVZVgpRh2b_69hblablablablabla", + "label": null, + "mimeType": "application/pdf", + "name": "sample.pdf", + "numPages": 2 + } + ], + "supportingDocuments": null +} diff --git a/adobe-esign-connector-test/src/com/axonivy/connector/adobe/esign/connector/test/json/getSigningUrls.json b/adobe-esign-connector-test/src/com/axonivy/connector/adobe/esign/connector/test/json/getSigningUrls.json new file mode 100644 index 0000000..3dfc4d3 --- /dev/null +++ b/adobe-esign-connector-test/src/com/axonivy/connector/adobe/esign/connector/test/json/getSigningUrls.json @@ -0,0 +1,13 @@ +{ + "signingUrlSetInfos": [ + { + "signingUrlSetName": null, + "signingUrls": [ + { + "email": "stma@mailinator.com", + "esignUrl": "https://secure.eu2.adobesign.com/public/apiesign?pid=CBFCIBAA3AAABLblqZhBfFw-mWHXrt1tSN_AfnQQld3Ea9pq61iY57nLp7Duyp5I8XoIyLkT1TcjoW7lshFZXF4zoa5UPIOBRKUPhP8YuenqrWRNjVAXoTO8P7eMr3qIr8-hOhCOIfKUXiB0UOmQ%2A&client_id=CBJCHblablablablabla" + } + ] + } + ] +} diff --git a/adobe-esign-connector-test/src/com/axonivy/connector/adobe/esign/connector/test/json/uploadDocument.json b/adobe-esign-connector-test/src/com/axonivy/connector/adobe/esign/connector/test/json/uploadDocument.json new file mode 100644 index 0000000..a8c68b8 --- /dev/null +++ b/adobe-esign-connector-test/src/com/axonivy/connector/adobe/esign/connector/test/json/uploadDocument.json @@ -0,0 +1,3 @@ +{ + "transientDocumentId": "3AAABLblqZhDVSvtTTuHIxFoTcvrHMhZwdRBEz62SCvv1G8yTaPzxcu8NGkq9IIILoVyC4UHJ8vgl_Mw5CY9lBSMEbMriLGzZyy6KCWpF8Ac2TpP-3Bpfrr8LHyv92ukAsKnyJBDP9XPpSyVOm1CtLUbNtX7M6cfu_LKRSTl-nGfMK6eRalZ6AsxLzDTW8lALl20Md5x5EyOkBLW1ZvvyF2OITH-cPVgBAoh7ejVkDiHwskPq9NIUoTZ4jN3bvq0ma5Dz7gP1F5RGuLlzGDlje3_MIof7-EgdIQt92grPt7fe0-Dfgjblablabla" +} diff --git a/adobe-esign-connector-test/src_test/com/axonivy/connector/adobe/esign/connector/test/TestAdobeSignConnector.java b/adobe-esign-connector-test/src_test/com/axonivy/connector/adobe/esign/connector/test/TestAdobeSignConnector.java new file mode 100644 index 0000000..711d10d --- /dev/null +++ b/adobe-esign-connector-test/src_test/com/axonivy/connector/adobe/esign/connector/test/TestAdobeSignConnector.java @@ -0,0 +1,34 @@ +package com.axonivy.connector.adobe.esign.connector.test; + +import ch.ivyteam.ivy.application.IApplication; +import ch.ivyteam.ivy.bpm.exec.client.IvyProcessTest; +import ch.ivyteam.ivy.environment.AppFixture; +import ch.ivyteam.ivy.rest.client.RestClient; +import ch.ivyteam.ivy.rest.client.RestClient.Builder; +import ch.ivyteam.ivy.rest.client.RestClients; + +@IvyProcessTest +public class TestAdobeSignConnector { + protected static final String TRANSIENT_DOCUMENTS = "TransientDocuments"; + protected static final String AGREEMENTS = "Agreements"; + + protected void prepareRestClient(IApplication app, AppFixture fixture, String clientName) { + fixture.var("adobe-sign-connector.host", "TESTHOST"); + fixture.var("adobe-sign-connector.integrationKey", "TESTUSER"); + RestClient restClient = RestClients.of(app).find(clientName); + // change created client: use test url and a slightly different version of + // the + // DocuWare Auth feature + Builder builder = RestClient.create(restClient.name()).uuid(restClient.uniqueId()) + .uri("http://{ivy.engine.host}:{ivy.engine.http.port}/{ivy.request.application}/api/adobeSignMock") + .description(restClient.description()).properties(restClient.properties()); + + for (String feature : restClient.features()) { + builder.feature(feature); + } + + builder.feature("ch.ivyteam.ivy.rest.client.security.CsrfHeaderFeature"); + restClient = builder.toRestClient(); + RestClients.of(app).set(restClient); + } +} diff --git a/adobe-esign-connector-test/src_test/com/axonivy/connector/adobe/esign/connector/test/TestAgreementsService.java b/adobe-esign-connector-test/src_test/com/axonivy/connector/adobe/esign/connector/test/TestAgreementsService.java new file mode 100644 index 0000000..157a4e9 --- /dev/null +++ b/adobe-esign-connector-test/src_test/com/axonivy/connector/adobe/esign/connector/test/TestAgreementsService.java @@ -0,0 +1,143 @@ +package com.axonivy.connector.adobe.esign.connector.test; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.IOException; +import java.util.Arrays; +import java.util.List; + +import org.junit.jupiter.api.Test; + +import com.axonivy.connector.adobe.esign.connector.AgreementsData; +import com.axonivy.connector.adobe.esign.connector.rest.DownloadResult; +import com.axonivy.connector.adobe.esign.connector.service.AdobeSignService; + +import api.rest.v6.client.AgreementCreationInfo; +import api.rest.v6.client.AgreementCreationInfo.SignatureTypeEnum; +import api.rest.v6.client.AgreementCreationInfo.StateEnum; +import api.rest.v6.client.AgreementCreationInfoParticipantSetsInfo.RoleEnum; +import api.rest.v6.client.AgreementCreationResponse; +import api.rest.v6.client.AgreementDocuments; +import api.rest.v6.client.SigningUrlResponseSigningUrlSetInfos; +import ch.ivyteam.ivy.application.IApplication; +import ch.ivyteam.ivy.bpm.engine.client.BpmClient; +import ch.ivyteam.ivy.bpm.engine.client.ExecutionResult; +import ch.ivyteam.ivy.bpm.engine.client.element.BpmElement; +import ch.ivyteam.ivy.bpm.engine.client.element.BpmProcess; +import ch.ivyteam.ivy.bpm.exec.client.IvyProcessTest; +import ch.ivyteam.ivy.environment.AppFixture; +import ch.ivyteam.ivy.environment.Ivy; +import ch.ivyteam.ivy.security.ISession; + +@IvyProcessTest +public class TestAgreementsService extends TestAdobeSignConnector { + + private static final BpmElement testeeCreateAgreement = BpmProcess.path("connector/Agreements") + .elementName("createAgreement(AgreementCreationInfo)"); + + private static final BpmElement testeeGetDocuments = BpmProcess.path("connector/Agreements") + .elementName("getDocuments(String)"); + + private static final BpmElement testeeDownloadDocument = BpmProcess.path("connector/Agreements") + .elementName("dowloadDocument(String, String, String, Boolean)"); + + private static final BpmElement testeeGetSigningUrls = BpmProcess.path("connector/Agreements") + .elementName("getSigningURLs(String,String)"); + + @Test + public void createAgreement(BpmClient bpmClient, ISession session, AppFixture fixture, IApplication app) + throws IOException { + + prepareRestClient(app, fixture, AGREEMENTS); + + AgreementCreationInfo agreement = createTestAgreement(); + + ExecutionResult result = bpmClient.start().subProcess(testeeCreateAgreement).withParam("agreement", agreement) + .execute(); + AgreementsData data = result.data().last(); + AgreementCreationResponse response = data.getAgreementCreationResponse(); + assertThat(response).isNotNull(); + assertThat(response.getId()).isNotEmpty(); + } + + @Test + public void getDocuments(BpmClient bpmClient, ISession session, AppFixture fixture, IApplication app) + throws IOException { + + prepareRestClient(app, fixture, AGREEMENTS); + + String agreementId = "test-agreement-id"; + + ExecutionResult result = bpmClient.start().subProcess(testeeGetDocuments).withParam("agreementId", agreementId) + .execute(); + AgreementsData data = result.data().last(); + AgreementDocuments response = data.getDocuments(); + assertThat(response).isNotNull(); + assertThat(response.getDocuments()).isNotEmpty(); + } + + @Test + public void downloadDocument(BpmClient bpmClient, ISession session, AppFixture fixture, IApplication app) + throws IOException { + + prepareRestClient(app, fixture, AGREEMENTS); + + String agreementId = "test-agreement-id"; + String documentId = "test-document-id"; + String filename = "sample.pdf"; + Boolean asFile = Boolean.TRUE; + + ExecutionResult result = bpmClient.start().subProcess(testeeDownloadDocument) + .withParam("agreementId", agreementId).withParam("documentId", documentId) + .withParam("filename", filename).withParam("asFile", asFile).execute(); + AgreementsData data = result.data().last(); + DownloadResult downloadResult = data.getDownload(); + assertThat(downloadResult).isNotNull(); + if (downloadResult.getError() != null) { + Ivy.log().error(downloadResult.getError()); + } + assertThat(downloadResult.getFile()).isNotNull(); + } + + @Test + public void getSigningUrls(BpmClient bpmClient, ISession session, AppFixture fixture, IApplication app) + throws IOException { + + prepareRestClient(app, fixture, AGREEMENTS); + + String agreementId = "test-agreement-id"; + String frameParent = "test"; + + ExecutionResult result = bpmClient.start().subProcess(testeeGetSigningUrls) + .withParam("agreementId", agreementId).withParam("frameParent", frameParent).execute(); + AgreementsData data = result.data().last(); + + List signingUrls = data.getSigningUrls(); + + assertThat(signingUrls).isNotNull(); + assertThat(signingUrls).isNotEmpty(); + } + + private AgreementCreationInfo createTestAgreement() { + AgreementCreationInfo agreement = new AgreementCreationInfo(); + + agreement.setName("test name"); + agreement.setMessage("Please sign this document!"); + agreement.setSignatureType(SignatureTypeEnum.ESIGN); + agreement.setState(StateEnum.IN_PROCESS); + + // add signers + AdobeSignService.getInstance().createParticipantInfoForEmail(Arrays.asList("testEmail@test.test"), + RoleEnum.SIGNER); + agreement.setParticipantSetsInfo(AdobeSignService.getInstance() + .createParticipantInfoForEmail(Arrays.asList("testEmail@test.test"), RoleEnum.SIGNER)); + + // add documentIds - need to be already transferred with the upload document + // service + agreement.setFileInfos( + AdobeSignService.getInstance().createFileInfosForDocumentIds(Arrays.asList("test-document-id"))); + + agreement.setEmailOption(AdobeSignService.getInstance().createAllDisabledSendOptions()); + return agreement; + } +} diff --git a/adobe-esign-connector-test/src_test/com/axonivy/connector/adobe/esign/connector/test/TestTransientDocumentsService.java b/adobe-esign-connector-test/src_test/com/axonivy/connector/adobe/esign/connector/test/TestTransientDocumentsService.java new file mode 100644 index 0000000..a85fa13 --- /dev/null +++ b/adobe-esign-connector-test/src_test/com/axonivy/connector/adobe/esign/connector/test/TestTransientDocumentsService.java @@ -0,0 +1,39 @@ +package com.axonivy.connector.adobe.esign.connector.test; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.IOException; + +import org.junit.jupiter.api.Test; + +import com.axonivy.connector.adobe.esign.connector.TransientDocumentsData; + +import ch.ivyteam.ivy.application.IApplication; +import ch.ivyteam.ivy.bpm.engine.client.BpmClient; +import ch.ivyteam.ivy.bpm.engine.client.ExecutionResult; +import ch.ivyteam.ivy.bpm.engine.client.element.BpmElement; +import ch.ivyteam.ivy.bpm.engine.client.element.BpmProcess; +import ch.ivyteam.ivy.bpm.exec.client.IvyProcessTest; +import ch.ivyteam.ivy.environment.AppFixture; +import ch.ivyteam.ivy.security.ISession; + +@IvyProcessTest +public class TestTransientDocumentsService extends TestAdobeSignConnector { + + + private static final BpmElement testeeUploadDocument = BpmProcess.path("connector/TransientDocuments") + .elementName("uploadDocument(file)"); + + @Test + public void uploadFile(BpmClient bpmClient, ISession session, AppFixture fixture, IApplication app) + throws IOException { + prepareRestClient(app, fixture, TRANSIENT_DOCUMENTS); + + java.io.File pdf = TestService.getSamplePdf(); + assertThat(pdf).isNotNull(); + + ExecutionResult result = bpmClient.start().subProcess(testeeUploadDocument).withParam("file", pdf).execute(); + TransientDocumentsData data = result.data().last(); + assertThat(data.getId()).isNotEmpty(); + } +} diff --git a/adobe-esign-connector/.classpath b/adobe-esign-connector/.classpath new file mode 100644 index 0000000..70ef051 --- /dev/null +++ b/adobe-esign-connector/.classpath @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/adobe-esign-connector/.gitignore b/adobe-esign-connector/.gitignore new file mode 100644 index 0000000..9b0d458 --- /dev/null +++ b/adobe-esign-connector/.gitignore @@ -0,0 +1,19 @@ +# general +Thumbs.db +.DS_Store +*~ +*.log + +# java +*.class +hs_err_pid* + +# maven +target/ +lib/mvn-deps/ + +# ivy +classes/ +src_dataClasses/ +src_wsproc/ +logs/ diff --git a/adobe-esign-connector/.project b/adobe-esign-connector/.project new file mode 100644 index 0000000..79e7af2 --- /dev/null +++ b/adobe-esign-connector/.project @@ -0,0 +1,49 @@ + + + adobe-esign-connector + + + + + + ch.ivyteam.ivy.designer.dataClasses.ui.ivyDataClassBuilder + + + + + ch.ivyteam.ivy.designer.process.ui.ivyWebServiceProcessClassBuilder + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.wst.common.project.facet.core.builder + + + + + ch.ivyteam.ivy.designer.ide.ivyModelValidationBuilder + + + + + org.eclipse.m2e.core.maven2Builder + + + + + + ch.ivyteam.ivy.project.IvyProjectNature + org.eclipse.wst.common.modulecore.ModuleCoreNature + org.eclipse.jem.workbench.JavaEMFNature + org.eclipse.jdt.core.javanature + org.eclipse.m2e.core.maven2Nature + org.eclipse.jem.beaninfo.BeanInfoNature + org.eclipse.wst.common.project.facet.core.nature + org.eclipse.wst.jsdt.core.jsNature + + diff --git a/adobe-esign-connector/.settings/.jsdtscope b/adobe-esign-connector/.settings/.jsdtscope new file mode 100644 index 0000000..869c01d --- /dev/null +++ b/adobe-esign-connector/.settings/.jsdtscope @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/adobe-esign-connector/.settings/ch.ivyteam.ivy.designer.prefs b/adobe-esign-connector/.settings/ch.ivyteam.ivy.designer.prefs new file mode 100644 index 0000000..c6dad4e --- /dev/null +++ b/adobe-esign-connector/.settings/ch.ivyteam.ivy.designer.prefs @@ -0,0 +1,5 @@ +ch.ivyteam.ivy.designer.preferences.DataClassPreferencePage\:DEFAULT_DATA_CLASS=com.axonivy.connector.adobe.esign.connector.Data +ch.ivyteam.ivy.designer.preferences.DataClassPreferencePage\:DEFAULT_NAMESPACE=com.axonivy.connector.adobe.esign.connector +ch.ivyteam.ivy.project.preferences\:PRIMEFACES_VERSION=11 +ch.ivyteam.ivy.project.preferences\:PROJECT_VERSION=100000 +eclipse.preferences.version=1 diff --git a/adobe-esign-connector/.settings/org.eclipse.jdt.core.prefs b/adobe-esign-connector/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 0000000..d4540a5 --- /dev/null +++ b/adobe-esign-connector/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,10 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=17 +org.eclipse.jdt.core.compiler.compliance=17 +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=warning +org.eclipse.jdt.core.compiler.release=enabled +org.eclipse.jdt.core.compiler.source=17 diff --git a/adobe-esign-connector/.settings/org.eclipse.wst.common.component b/adobe-esign-connector/.settings/org.eclipse.wst.common.component new file mode 100644 index 0000000..57277a0 --- /dev/null +++ b/adobe-esign-connector/.settings/org.eclipse.wst.common.component @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/adobe-esign-connector/.settings/org.eclipse.wst.common.project.facet.core.prefs.xml b/adobe-esign-connector/.settings/org.eclipse.wst.common.project.facet.core.prefs.xml new file mode 100644 index 0000000..9b4b9fc --- /dev/null +++ b/adobe-esign-connector/.settings/org.eclipse.wst.common.project.facet.core.prefs.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/adobe-esign-connector/.settings/org.eclipse.wst.common.project.facet.core.xml b/adobe-esign-connector/.settings/org.eclipse.wst.common.project.facet.core.xml new file mode 100644 index 0000000..156ecdb --- /dev/null +++ b/adobe-esign-connector/.settings/org.eclipse.wst.common.project.facet.core.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/adobe-esign-connector/.settings/org.eclipse.wst.css.core.prefs b/adobe-esign-connector/.settings/org.eclipse.wst.css.core.prefs new file mode 100644 index 0000000..5ddc6bd --- /dev/null +++ b/adobe-esign-connector/.settings/org.eclipse.wst.css.core.prefs @@ -0,0 +1,2 @@ +css-profile/=org.eclipse.wst.css.core.cssprofile.css3 +eclipse.preferences.version=1 diff --git a/adobe-esign-connector/.settings/org.eclipse.wst.jsdt.ui.superType.container b/adobe-esign-connector/.settings/org.eclipse.wst.jsdt.ui.superType.container new file mode 100644 index 0000000..3bd5d0a --- /dev/null +++ b/adobe-esign-connector/.settings/org.eclipse.wst.jsdt.ui.superType.container @@ -0,0 +1 @@ +org.eclipse.wst.jsdt.launching.baseBrowserLibrary \ No newline at end of file diff --git a/adobe-esign-connector/.settings/org.eclipse.wst.jsdt.ui.superType.name b/adobe-esign-connector/.settings/org.eclipse.wst.jsdt.ui.superType.name new file mode 100644 index 0000000..05bd71b --- /dev/null +++ b/adobe-esign-connector/.settings/org.eclipse.wst.jsdt.ui.superType.name @@ -0,0 +1 @@ +Window \ No newline at end of file diff --git a/adobe-esign-connector/cms/cms_en.yaml b/adobe-esign-connector/cms/cms_en.yaml new file mode 100644 index 0000000..553466b --- /dev/null +++ b/adobe-esign-connector/cms/cms_en.yaml @@ -0,0 +1,15 @@ +Dialogs: + com: + axonivy: + connector: + adobe: + esign: + connector: + AdminSetup: + General: General + IntegrationKey: Integration key + IntegrationKeyInfo: Setting the integrationKey will disable OAuth. If you wanna use OAuth then fill the OAuth section and leave the integrationKey empty. + OAuth: OAuth + RedirectURI: Redirect URI + RequestToken: Save and Request new Token + Title: Adobe Sign Admin Setup diff --git a/adobe-esign-connector/config/custom-fields.yaml b/adobe-esign-connector/config/custom-fields.yaml new file mode 100644 index 0000000..aa19ae0 --- /dev/null +++ b/adobe-esign-connector/config/custom-fields.yaml @@ -0,0 +1,20 @@ +# == Custom Fields Information == +# +# You can define here your project custom fields. +# Have a look at our documentation for more information. +# +CustomFields: +# Tasks: +# MyTaskCustomField: +# Label: My task custom field +# Description: This new task custom field can be used to ... +# Type: STRING +# Cases: +# MyCaseCustomField: +# Label: My case custom field +# Description: This new case custom field can be used to ... +# Type: STRING +# Starts: +# MyStartCustomField: +# Label: My start custom field +# Description: This new start custom field can be used to ... diff --git a/adobe-esign-connector/config/databases.yaml b/adobe-esign-connector/config/databases.yaml new file mode 100644 index 0000000..247b128 --- /dev/null +++ b/adobe-esign-connector/config/databases.yaml @@ -0,0 +1 @@ +Databases: diff --git a/adobe-esign-connector/config/overrides.any b/adobe-esign-connector/config/overrides.any new file mode 100644 index 0000000..f59ec20 --- /dev/null +++ b/adobe-esign-connector/config/overrides.any @@ -0,0 +1 @@ +* \ No newline at end of file diff --git a/adobe-esign-connector/config/persistence.xml b/adobe-esign-connector/config/persistence.xml new file mode 100644 index 0000000..d6b96d7 --- /dev/null +++ b/adobe-esign-connector/config/persistence.xml @@ -0,0 +1,2 @@ + + diff --git a/adobe-esign-connector/config/rest-clients.yaml b/adobe-esign-connector/config/rest-clients.yaml new file mode 100644 index 0000000..b1c4238 --- /dev/null +++ b/adobe-esign-connector/config/rest-clients.yaml @@ -0,0 +1,80 @@ +RestClients: + Users: + UUID: 83e80d77-749b-4f53-aaf5-a796f2dab100 + Url: https://{host}/api/rest/v6 + Features: + - ch.ivyteam.ivy.rest.client.mapper.JsonFeature + - com.axonivy.connector.adobe.esign.connector.auth.OAuth2Feature + Properties: + AUTH.scope: ${ivy.var.adobe-sign-connector.permissions} + AUTH.baseUri: ${ivy.var.adobe-sign-connector.baseUri} + JSON.Serialization.PROPERTY_INCLUSION: NON_NULL + AUTH.clientId: ${ivy.var.adobe-sign-connector.clientId} + AUTH.clientSecret: ${ivy.var.adobe-sign-connector.clientSecret} + PATH.host: ${ivy.var.adobe-sign-connector.host} + AUTH.code: ${ivy.var.adobe-sign-connector.code} + AUTH.integrationKey: ${ivy.var.adobe-sign-connector.integrationKey} + OpenAPI: + SpecUrl: file:///C:/Users/jpl/Downloads/json/REST-SDK-V6-master/json/usersV3.json + Namespace: api.rest.v6.client + ResolveFully: true + BaseUris: + UUID: 9774590a-7adb-4910-8a53-22b68e50d9ed + Url: https://{host}/api/rest/v6 + Features: + - ch.ivyteam.ivy.rest.client.mapper.JsonFeature + - com.axonivy.connector.adobe.esign.connector.auth.OAuth2Feature + Properties: + AUTH.scope: ${ivy.var.adobe-sign-connector.permissions} + AUTH.baseUri: ${ivy.var.adobe-sign-connector.baseUri} + JSON.Serialization.PROPERTY_INCLUSION: NON_NULL + AUTH.clientId: ${ivy.var.adobe-sign-connector.clientId} + AUTH.clientSecret: ${ivy.var.adobe-sign-connector.clientSecret} + AUTH.useAppPermissions: ${ivy.var.adobe-sign-connector.useAppPermissions} + AUTH.useUserPassFlow: ${ivy.var.adobe-sign-connector.useUserPassFlow.enabled} + PATH.host: ${ivy.var.adobe-sign-connector.host} + AUTH.integrationKey: ${ivy.var.adobe-sign-connector.integrationKey} + OpenAPI: + SpecUrl: file:///C:/Users/jpl/Downloads/json/REST-SDK-V6-master/json/baseUrisV3.json + Namespace: api.rest.v6.client + Agreements: + UUID: 8e96fab1-4701-47cb-ae35-d821bb12305b + Url: https://{host}/api/rest/v6 + Features: + - com.axonivy.connector.adobe.esign.connector.json.OpenApiJsonFeature + - com.axonivy.connector.adobe.esign.connector.auth.OAuth2Feature + Properties: + AUTH.scope: ${ivy.var.adobe-sign-connector.permissions} + AUTH.baseUri: ${ivy.var.adobe-sign-connector.baseUri} + JSON.Serialization.PROPERTY_INCLUSION: NON_NULL + AUTH.clientId: ${ivy.var.adobe-sign-connector.clientId} + AUTH.clientSecret: ${ivy.var.adobe-sign-connector.clientSecret} + AUTH.useAppPermissions: ${ivy.var.adobe-sign-connector.useAppPermissions} + AUTH.useUserPassFlow: ${ivy.var.adobe-sign-connector.useUserPassFlow.enabled} + PATH.host: ${ivy.var.adobe-sign-connector.host} + AUTH.integrationKey: ${ivy.var.adobe-sign-connector.integrationKey} + OpenAPI: + SpecUrl: file:///C:/Users/jpl/OneDrive%20-%20Docuware%20GmbH/data/Projekte/Ricoh/HR%20Transformation/Adobe%20Sign/agreementsV2.json + Namespace: api.rest.v6.client + ResolveFully: true + TransientDocuments: + UUID: 6a243983-5f6d-4278-89ec-aab09fea161c + Url: https://{host}/api/rest/v6 + Features: + - org.glassfish.jersey.media.multipart.MultiPartFeature + - com.axonivy.connector.adobe.esign.connector.auth.OAuth2Feature + - ch.ivyteam.ivy.rest.client.mapper.JsonFeature + Properties: + AUTH.scope: ${ivy.var.adobe-sign-connector.permissions} + AUTH.baseUri: ${ivy.var.adobe-sign-connector.baseUri} + JSON.Serialization.PROPERTY_INCLUSION: NON_NULL + AUTH.clientId: ${ivy.var.adobe-sign-connector.clientId} + AUTH.clientSecret: ${ivy.var.adobe-sign-connector.clientSecret} + AUTH.useAppPermissions: ${ivy.var.adobe-sign-connector.useAppPermissions} + AUTH.useUserPassFlow: ${ivy.var.adobe-sign-connector.useUserPassFlow.enabled} + PATH.host: ${ivy.var.adobe-sign-connector.host} + AUTH.integrationKey: ${ivy.var.adobe-sign-connector.integrationKey} + OpenAPI: + SpecUrl: file:///C:/tmp/ricoh/adobe/transientDocumentsV2.json + Namespace: api.rest.v6.client + ResolveFully: true diff --git a/adobe-esign-connector/config/roles.xml b/adobe-esign-connector/config/roles.xml new file mode 100644 index 0000000..1990089 --- /dev/null +++ b/adobe-esign-connector/config/roles.xml @@ -0,0 +1,7 @@ + + + Everybody + + ADOBE_ESIGN_ADMIN + + diff --git a/adobe-esign-connector/config/users.xml b/adobe-esign-connector/config/users.xml new file mode 100644 index 0000000..51a6906 --- /dev/null +++ b/adobe-esign-connector/config/users.xml @@ -0,0 +1,2 @@ + + diff --git a/adobe-esign-connector/config/variables.yaml b/adobe-esign-connector/config/variables.yaml new file mode 100644 index 0000000..199b449 --- /dev/null +++ b/adobe-esign-connector/config/variables.yaml @@ -0,0 +1,39 @@ +# == Variables == +# +# You can define here your project Variables. +# If you want to define/override a Variable for a specific Environment, +# add an additional ‘variables.yaml’ file in a subdirectory in the ‘Config’ folder: +# '/Config/_/variables.yaml +# +Variables: + adobe-sign-connector: + # Hostname of Adobe Sign server + host: '' + + # Relative part of URL that is called after singing was finished + returnPage: '' + + # Integration key from Adobe Sign configuration + integrationKey: '' + + # Base URI for getting the access and refresh access tokens (without the `/token` or `/refresh` part) + baseUri: 'https://api.eu2.adobesign.com/oauth/v2' + + # URL for the Authorization request (differs from tokens URL) + authenticationUri: 'https://secure.eu2.adobesign.com/public/oauth/v2' + + # Adobe API Application Client ID + clientId: '' + + # Adobe API Application Client Secret + # [password] + clientSecret: '' + + # List of permissions that will be requested for the OAuth token + permissions: user_read:account user_write:account user_login:account agreement_read:account agreement_write:account agreement_send:account + + # DO NOT MODIFY DIRECTLY, USE ADMIN SETUP PAGE. Info about the OAuth refresh token. Empty means there is no token initialized. To request a new token use the `Save and Request new Token` button + oauthToken: '' + + # DO NOT MODIFY DIRECTLY, USE ADMIN SETUP PAGE. Info about the OAuth access token. + accessToken: '' diff --git a/adobe-esign-connector/config/webservice-clients.yaml b/adobe-esign-connector/config/webservice-clients.yaml new file mode 100644 index 0000000..060b018 --- /dev/null +++ b/adobe-esign-connector/config/webservice-clients.yaml @@ -0,0 +1 @@ +WebServiceClients: diff --git a/adobe-esign-connector/dataclasses/com/axonivy/connector/adobe/esign/connector/AgreementsData.ivyClass b/adobe-esign-connector/dataclasses/com/axonivy/connector/adobe/esign/connector/AgreementsData.ivyClass new file mode 100644 index 0000000..e6a9fae --- /dev/null +++ b/adobe-esign-connector/dataclasses/com/axonivy/connector/adobe/esign/connector/AgreementsData.ivyClass @@ -0,0 +1,15 @@ +AgreementsData #class +com.axonivy.connector.adobe.esign.connector #namespace +agreementCreation api.rest.v6.client.AgreementCreationInfo #field +error ch.ivyteam.ivy.bpm.error.BpmError #field +agreementCreationResponse api.rest.v6.client.AgreementCreationResponse #field +agreementId String #field +agreementInfo api.rest.v6.client.AgreementInfo #field +signingUrls java.util.List #field +frameParent String #field +documents api.rest.v6.client.AgreementDocuments #field +documentId String #field +download com.axonivy.connector.adobe.esign.connector.rest.DownloadResult #field +filename String #field +asFileOption Boolean #field +formFieldPutInfo api.rest.v6.client.FormFieldPutInfo #field diff --git a/adobe-esign-connector/dataclasses/com/axonivy/connector/adobe/esign/connector/AuthSetupData.ivyClass b/adobe-esign-connector/dataclasses/com/axonivy/connector/adobe/esign/connector/AuthSetupData.ivyClass new file mode 100644 index 0000000..4e40fbb --- /dev/null +++ b/adobe-esign-connector/dataclasses/com/axonivy/connector/adobe/esign/connector/AuthSetupData.ivyClass @@ -0,0 +1,2 @@ +AuthSetupData #class +com.axonivy.connector.adobe.esign.connector #namespace diff --git a/adobe-esign-connector/dataclasses/com/axonivy/connector/adobe/esign/connector/BaseUrisData.ivyClass b/adobe-esign-connector/dataclasses/com/axonivy/connector/adobe/esign/connector/BaseUrisData.ivyClass new file mode 100644 index 0000000..b2865a3 --- /dev/null +++ b/adobe-esign-connector/dataclasses/com/axonivy/connector/adobe/esign/connector/BaseUrisData.ivyClass @@ -0,0 +1,2 @@ +BaseUrisData #class +com.axonivy.connector.adobe.esign.connector #namespace diff --git a/adobe-esign-connector/dataclasses/com/axonivy/connector/adobe/esign/connector/Data.ivyClass b/adobe-esign-connector/dataclasses/com/axonivy/connector/adobe/esign/connector/Data.ivyClass new file mode 100644 index 0000000..6e7ef31 --- /dev/null +++ b/adobe-esign-connector/dataclasses/com/axonivy/connector/adobe/esign/connector/Data.ivyClass @@ -0,0 +1,2 @@ +Data #class +com.axonivy.connector.adobe.esign.connector #namespace diff --git a/adobe-esign-connector/dataclasses/com/axonivy/connector/adobe/esign/connector/SigningReturnData.ivyClass b/adobe-esign-connector/dataclasses/com/axonivy/connector/adobe/esign/connector/SigningReturnData.ivyClass new file mode 100644 index 0000000..dece423 --- /dev/null +++ b/adobe-esign-connector/dataclasses/com/axonivy/connector/adobe/esign/connector/SigningReturnData.ivyClass @@ -0,0 +1,2 @@ +SigningReturnData #class +com.axonivy.connector.adobe.esign.connector #namespace diff --git a/adobe-esign-connector/dataclasses/com/axonivy/connector/adobe/esign/connector/TransientDocumentsData.ivyClass b/adobe-esign-connector/dataclasses/com/axonivy/connector/adobe/esign/connector/TransientDocumentsData.ivyClass new file mode 100644 index 0000000..70052f9 --- /dev/null +++ b/adobe-esign-connector/dataclasses/com/axonivy/connector/adobe/esign/connector/TransientDocumentsData.ivyClass @@ -0,0 +1,6 @@ +TransientDocumentsData #class +com.axonivy.connector.adobe.esign.connector #namespace +file java.io.File #field +id String #field +error ch.ivyteam.ivy.bpm.error.BpmError #field +upload com.axonivy.connector.adobe.esign.connector.rest.UploadWrapper #field diff --git a/adobe-esign-connector/dataclasses/com/axonivy/connector/adobe/esign/connector/UsersData.ivyClass b/adobe-esign-connector/dataclasses/com/axonivy/connector/adobe/esign/connector/UsersData.ivyClass new file mode 100644 index 0000000..3f2ce47 --- /dev/null +++ b/adobe-esign-connector/dataclasses/com/axonivy/connector/adobe/esign/connector/UsersData.ivyClass @@ -0,0 +1,2 @@ +UsersData #class +com.axonivy.connector.adobe.esign.connector #namespace diff --git a/adobe-esign-connector/lib/generated/rest/jaxRsClient_6a243983-5f6d-4278-89ec-aab09fea161c.jar b/adobe-esign-connector/lib/generated/rest/jaxRsClient_6a243983-5f6d-4278-89ec-aab09fea161c.jar new file mode 100644 index 0000000..feaece0 Binary files /dev/null and b/adobe-esign-connector/lib/generated/rest/jaxRsClient_6a243983-5f6d-4278-89ec-aab09fea161c.jar differ diff --git a/adobe-esign-connector/lib/generated/rest/jaxRsClient_83e80d77-749b-4f53-aaf5-a796f2dab100.jar b/adobe-esign-connector/lib/generated/rest/jaxRsClient_83e80d77-749b-4f53-aaf5-a796f2dab100.jar new file mode 100644 index 0000000..75c0637 Binary files /dev/null and b/adobe-esign-connector/lib/generated/rest/jaxRsClient_83e80d77-749b-4f53-aaf5-a796f2dab100.jar differ diff --git a/adobe-esign-connector/lib/generated/rest/jaxRsClient_8e96fab1-4701-47cb-ae35-d821bb12305b.jar b/adobe-esign-connector/lib/generated/rest/jaxRsClient_8e96fab1-4701-47cb-ae35-d821bb12305b.jar new file mode 100644 index 0000000..8a548e9 Binary files /dev/null and b/adobe-esign-connector/lib/generated/rest/jaxRsClient_8e96fab1-4701-47cb-ae35-d821bb12305b.jar differ diff --git a/adobe-esign-connector/lib/generated/rest/jaxRsClient_9774590a-7adb-4910-8a53-22b68e50d9ed.jar b/adobe-esign-connector/lib/generated/rest/jaxRsClient_9774590a-7adb-4910-8a53-22b68e50d9ed.jar new file mode 100644 index 0000000..e84d578 Binary files /dev/null and b/adobe-esign-connector/lib/generated/rest/jaxRsClient_9774590a-7adb-4910-8a53-22b68e50d9ed.jar differ diff --git a/adobe-esign-connector/pom.xml b/adobe-esign-connector/pom.xml new file mode 100644 index 0000000..524f781 --- /dev/null +++ b/adobe-esign-connector/pom.xml @@ -0,0 +1,22 @@ + + + 4.0.0 + com.axonivy.connector.adobe.esign + adobe-esign-connector + 10.0.11-SNAPSHOT + iar + + 10.0.6 + + + + + com.axonivy.ivy.ci + project-build-plugin + ${build.plugin.version} + true + + + + diff --git a/adobe-esign-connector/processes/Setup/AdminSetup.p.json b/adobe-esign-connector/processes/Setup/AdminSetup.p.json new file mode 100644 index 0000000..d8c572f --- /dev/null +++ b/adobe-esign-connector/processes/Setup/AdminSetup.p.json @@ -0,0 +1,65 @@ +{ + "format" : "10.0.0", + "id" : "18A83631DA63DA93", + "config" : { + "data" : "com.axonivy.connector.adobe.esign.connector.AuthSetupData" + }, + "elements" : [ { + "id" : "f0", + "type" : "RequestStart", + "name" : "start.ivp", + "config" : { + "callSignature" : "start", + "responsible" : "ADOBE_ESIGN_ADMIN", + "outLink" : "start.ivp", + "wfuser" : "1", + "startName" : "Adobe Sign Connector Admin Setup" + }, + "visual" : { + "at" : { "x" : 96, "y" : 64 } + }, + "connect" : { "id" : "f4", "to" : "f3" } + }, { + "id" : "f1", + "type" : "TaskEnd", + "visual" : { + "at" : { "x" : 520, "y" : 64 } + } + }, { + "id" : "f3", + "type" : "DialogCall", + "name" : "AuthSetup", + "config" : { + "dialogId" : "com.axonivy.connector.adobe.esign.connector.AdminSetup", + "startMethod" : "start()" + }, + "visual" : { + "at" : { "x" : 224, "y" : 64 } + }, + "connect" : { "id" : "f2", "to" : "f1" } + }, { + "id" : "f5", + "type" : "RequestStart", + "name" : "oauthResume.ivp", + "config" : { + "callSignature" : "oauthResume", + "responsible" : "ADOBE_ESIGN_ADMIN", + "outLink" : "oauthResume.ivp", + "showInStartList" : false, + "input" : { + "params" : [ + { "name" : "code", "type" : "String" } + ], + "code" : [ + "import com.axonivy.connector.adobe.esign.connector.service.AdminSetupService;", + "AdminSetupService.getNewAccessToken(param.code);" + ] + }, + "wfuser" : "1" + }, + "visual" : { + "at" : { "x" : 96, "y" : 200 } + }, + "connect" : { "id" : "f9", "to" : "f3", "via" : [ { "x" : 224, "y" : 200 } ] } + } ] +} \ No newline at end of file diff --git a/adobe-esign-connector/processes/SigningReturn.p.json b/adobe-esign-connector/processes/SigningReturn.p.json new file mode 100644 index 0000000..055b589 --- /dev/null +++ b/adobe-esign-connector/processes/SigningReturn.p.json @@ -0,0 +1,30 @@ +{ + "format" : "10.0.0", + "id" : "188B5516C4F69340", + "config" : { + "data" : "com.axonivy.connector.adobe.esign.connector.SigningReturnData" + }, + "elements" : [ { + "id" : "f0", + "type" : "RequestStart", + "name" : "signingReturn.ivp", + "config" : { + "callSignature" : "signingReturn", + "outLink" : "signingReturn.ivp", + "showInStartList" : false + }, + "visual" : { + "at" : { "x" : 96, "y" : 64 } + }, + "connect" : { "id" : "f2", "to" : "f1" } + }, { + "id" : "f1", + "type" : "TaskEndPage", + "config" : { + "template" : "signatureReturn.jsp" + }, + "visual" : { + "at" : { "x" : 288, "y" : 64 } + } + } ] +} \ No newline at end of file diff --git a/adobe-esign-connector/processes/connector/Agreements.p.json b/adobe-esign-connector/processes/connector/Agreements.p.json new file mode 100644 index 0000000..0e3aa12 --- /dev/null +++ b/adobe-esign-connector/processes/connector/Agreements.p.json @@ -0,0 +1,450 @@ +{ + "format" : "10.0.0", + "id" : "18822C586F9CB9FB", + "kind" : "CALLABLE_SUB", + "config" : { + "data" : "com.axonivy.connector.adobe.esign.connector.AgreementsData" + }, + "elements" : [ { + "id" : "f0", + "type" : "CallSubStart", + "name" : "createAgreement(AgreementCreationInfo)", + "config" : { + "callSignature" : "createAgreement", + "input" : { + "params" : [ + { "name" : "agreement", "type" : "api.rest.v6.client.AgreementCreationInfo", "desc" : "agreement to create" } + ], + "map" : { + "out.agreementCreation" : "param.agreement" + } + }, + "result" : { + "params" : [ + { "name" : "agreementInfo", "type" : "api.rest.v6.client.AgreementCreationResponse", "desc" : "response holding id, expiration and url to created agreement" }, + { "name" : "error", "type" : "ch.ivyteam.ivy.bpm.error.BpmError", "desc" : "holds error if one occurs, empty otherwise" } + ], + "map" : { + "result.agreementInfo" : "in.#agreementCreationResponse", + "result.error" : "in.#error" + } + }, + "tags" : "connector" + }, + "visual" : { + "at" : { "x" : 88, "y" : 64 } + }, + "connect" : { "id" : "f4", "to" : "f3" } + }, { + "id" : "f1", + "type" : "CallSubEnd", + "visual" : { + "at" : { "x" : 480, "y" : 64 } + } + }, { + "id" : "f3", + "type" : "RestClientCall", + "name" : "post agreement", + "config" : { + "path" : "/agreements", + "bodyObjectMapping" : { + "param" : "in.agreementCreation" + }, + "clientId" : "8e96fab1-4701-47cb-ae35-d821bb12305b", + "clientErrorCode" : "ivy:error:rest:client", + "method" : "POST", + "statusErrorCode" : "ivy:error:rest:client", + "responseMapping" : { + "out.agreementCreationResponse" : "result" + }, + "bodyObjectType" : "api.rest.v6.client.AgreementCreationInfo", + "resultType" : "api.rest.v6.client.AgreementCreationResponse", + "bodyInputType" : "ENTITY" + }, + "visual" : { + "at" : { "x" : 280, "y" : 64 } + }, + "boundaries" : [ { + "id" : "f5", + "type" : "ErrorBoundaryEvent", + "config" : { + "output" : { + "map" : { + "out" : "in", + "out.error" : "#error" + } + } + }, + "visual" : { + "at" : { "x" : 312, "y" : 106 } + }, + "connect" : { "id" : "f6", "to" : "f1", "via" : [ { "x" : 480, "y" : 106 } ] } + } ], + "connect" : { "id" : "f2", "to" : "f1" } + }, { + "id" : "f7", + "type" : "CallSubStart", + "name" : "getAgreementById(String)", + "config" : { + "callSignature" : "getAgreementById", + "input" : { + "params" : [ + { "name" : "agreementId", "type" : "String", "desc" : "id of agreement" } + ], + "map" : { + "out.agreementId" : "param.agreementId" + } + }, + "result" : { + "params" : [ + { "name" : "agreementInfo", "type" : "api.rest.v6.client.AgreementInfo", "desc" : "detailed information of agreeement" }, + { "name" : "error", "type" : "ch.ivyteam.ivy.bpm.error.BpmError" } + ], + "map" : { + "result.agreementInfo" : "in.#agreementInfo", + "result.error" : "in.#error" + } + }, + "tags" : "connector" + }, + "visual" : { + "at" : { "x" : 88, "y" : 192 } + }, + "connect" : { "id" : "f9", "to" : "f8" } + }, { + "id" : "f8", + "type" : "RestClientCall", + "name" : "get agreement by id", + "config" : { + "path" : "/agreements/{agreementId}", + "clientId" : "8e96fab1-4701-47cb-ae35-d821bb12305b", + "clientErrorCode" : "ivy:error:rest:client", + "statusErrorCode" : "ivy:error:rest:client", + "responseMapping" : { + "out.agreementInfo" : "result" + }, + "templateParams" : { + "agreementId" : "in.agreementId" + }, + "resultType" : "api.rest.v6.client.AgreementInfo" + }, + "visual" : { + "at" : { "x" : 280, "y" : 192 } + }, + "boundaries" : [ { + "id" : "f12", + "type" : "ErrorBoundaryEvent", + "visual" : { + "at" : { "x" : 312, "y" : 232 } + }, + "connect" : { "id" : "f13", "to" : "f10", "via" : [ { "x" : 472, "y" : 232 } ] } + } ], + "connect" : { "id" : "f11", "to" : "f10" } + }, { + "id" : "f10", + "type" : "CallSubEnd", + "visual" : { + "at" : { "x" : 472, "y" : 192 } + } + }, { + "id" : "f14", + "type" : "CallSubStart", + "name" : "getSigningURLs(String,String)", + "config" : { + "callSignature" : "getSigningURLs", + "input" : { + "params" : [ + { "name" : "agreementId", "type" : "String", "desc" : "id of agreement" }, + { "name" : "frameParent", "type" : "String", "desc" : "URI of parent frame" } + ], + "map" : { + "out.agreementId" : "param.agreementId", + "out.frameParent" : "param.frameParent" + } + }, + "result" : { + "params" : [ + { "name" : "signingURIs", "type" : "java.util.List", "desc" : "detailed information of agreeement" }, + { "name" : "error", "type" : "ch.ivyteam.ivy.bpm.error.BpmError" } + ], + "map" : { + "result.signingURIs" : "in.#signingUrls", + "result.error" : "in.#error" + } + }, + "tags" : "connector" + }, + "visual" : { + "at" : { "x" : 88, "y" : 312 } + }, + "connect" : { "id" : "f20", "to" : "f15" } + }, { + "id" : "f15", + "type" : "RestClientCall", + "name" : "get signing URLs for agreement", + "config" : { + "path" : "/agreements/{agreementId}/signingUrls", + "clientId" : "8e96fab1-4701-47cb-ae35-d821bb12305b", + "clientErrorCode" : "ivy:error:rest:client", + "queryParams" : { + "frameParent" : "in.frameParent" + }, + "statusErrorCode" : "ivy:error:rest:client", + "responseMapping" : { + "out.signingUrls" : "result.signingUrlSetInfos" + }, + "templateParams" : { + "agreementId" : "in.agreementId" + }, + "resultType" : "api.rest.v6.client.SigningUrlResponse" + }, + "visual" : { + "at" : { "x" : 280, "y" : 312 } + }, + "boundaries" : [ { + "id" : "f16", + "type" : "ErrorBoundaryEvent", + "config" : { + "output" : { + "map" : { + "out" : "in", + "out.error" : "#error" + } + } + }, + "visual" : { + "at" : { "x" : 312, "y" : 352 } + }, + "connect" : { "id" : "f19", "to" : "f17", "via" : [ { "x" : 472, "y" : 352 } ] } + } ], + "connect" : { "id" : "f18", "to" : "f17" } + }, { + "id" : "f17", + "type" : "CallSubEnd", + "visual" : { + "at" : { "x" : 472, "y" : 312 } + } + }, { + "id" : "f21", + "type" : "CallSubStart", + "name" : "getDocuments(String)", + "config" : { + "callSignature" : "getDocuments", + "input" : { + "params" : [ + { "name" : "agreementId", "type" : "String", "desc" : "id of agreement" } + ], + "map" : { + "out.agreementId" : "param.agreementId" + } + }, + "result" : { + "params" : [ + { "name" : "documents", "type" : "api.rest.v6.client.AgreementDocuments", "desc" : "detailed information of agreeement" }, + { "name" : "error", "type" : "ch.ivyteam.ivy.bpm.error.BpmError" } + ], + "map" : { + "result.documents" : "in.#documents", + "result.error" : "in.#error" + } + }, + "tags" : "connector" + }, + "visual" : { + "at" : { "x" : 88, "y" : 432 } + }, + "connect" : { "id" : "f25", "to" : "f22" } + }, { + "id" : "f22", + "type" : "RestClientCall", + "name" : "get documents of agreement", + "config" : { + "path" : "/agreements/{agreementId}/documents", + "clientId" : "8e96fab1-4701-47cb-ae35-d821bb12305b", + "clientErrorCode" : "ivy:error:rest:client", + "queryParams" : { + "frameParent" : "", + "versionId" : "", + "participantEmail" : "", + "supportingDocumentContentFormat" : "" + }, + "statusErrorCode" : "ivy:error:rest:client", + "responseMapping" : { + "out.documents" : "result" + }, + "templateParams" : { + "agreementId" : "in.agreementId" + }, + "resultType" : "api.rest.v6.client.AgreementDocuments" + }, + "visual" : { + "at" : { "x" : 280, "y" : 432 } + }, + "boundaries" : [ { + "id" : "f23", + "type" : "ErrorBoundaryEvent", + "visual" : { + "at" : { "x" : 312, "y" : 472 } + }, + "connect" : { "id" : "f27", "to" : "f24", "via" : [ { "x" : 472, "y" : 472 } ] } + } ], + "connect" : { "id" : "f26", "to" : "f24" } + }, { + "id" : "f24", + "type" : "CallSubEnd", + "visual" : { + "at" : { "x" : 472, "y" : 432 } + } + }, { + "id" : "f28", + "type" : "CallSubStart", + "name" : "dowloadDocument(String, String, String, Boolean)", + "config" : { + "callSignature" : "dowloadDocument", + "input" : { + "params" : [ + { "name" : "agreementId", "type" : "String", "desc" : "id of agreement" }, + { "name" : "documentId", "type" : "String", "desc" : "id of document to retrieve" }, + { "name" : "filename", "type" : "String", "desc" : "name of file" }, + { "name" : "asFile", "type" : "Boolean", "desc" : "option to return an ivy file in result object. default is false which returns the byte[]" } + ], + "map" : { + "out.agreementId" : "param.agreementId", + "out.asFileOption" : "param.asFile", + "out.documentId" : "param.documentId", + "out.filename" : "param.filename" + } + }, + "result" : { + "params" : [ + { "name" : "download", "type" : "com.axonivy.connector.adobe.esign.connector.rest.DownloadResult", "desc" : "DownloadResult" }, + { "name" : "error", "type" : "ch.ivyteam.ivy.bpm.error.BpmError" } + ], + "map" : { + "result.download" : "in.download", + "result.error" : "in.#error" + } + }, + "tags" : "connector" + }, + "visual" : { + "at" : { "x" : 88, "y" : 544 } + }, + "connect" : { "id" : "f33", "to" : "f29" } + }, { + "id" : "f29", + "type" : "RestClientCall", + "name" : "get documents of agreement", + "config" : { + "path" : "/agreements/{agreementId}/documents/{documentId}", + "clientId" : "8e96fab1-4701-47cb-ae35-d821bb12305b", + "clientErrorCode" : "ivy:error:rest:client", + "method" : "JAX_RS", + "statusErrorCode" : "ivy:error:rest:client", + "clientCode" : [ + "import com.axonivy.connector.adobe.esign.connector.rest.ClientService;", + "", + "in.download = ClientService.download(client, in.filename, in.asFileOption);" + ], + "templateParams" : { + "agreementId" : "in.agreementId", + "documentId" : "in.documentId" + } + }, + "visual" : { + "at" : { "x" : 280, "y" : 544 } + }, + "boundaries" : [ { + "id" : "f30", + "type" : "ErrorBoundaryEvent", + "visual" : { + "at" : { "x" : 312, "y" : 584 } + }, + "connect" : { "id" : "f34", "to" : "f31", "via" : [ { "x" : 472, "y" : 584 } ] } + } ], + "connect" : { "id" : "f32", "to" : "f31" } + }, { + "id" : "f31", + "type" : "CallSubEnd", + "visual" : { + "at" : { "x" : 472, "y" : 544 } + } + }, { + "id" : "f35", + "type" : "CallSubStart", + "name" : "addFormFields(String, FormFieldPutInfo)", + "config" : { + "callSignature" : "addFormFields", + "input" : { + "params" : [ + { "name" : "agreementId", "type" : "String", "desc" : "id of the agreement" }, + { "name" : "formFieldInfo", "type" : "api.rest.v6.client.FormFieldPutInfo", "desc" : "details of form fields to add" } + ], + "map" : { + "out.agreementId" : "param.agreementId", + "out.formFieldPutInfo" : "param.formFieldInfo" + } + }, + "result" : { + "params" : [ + { "name" : "error", "type" : "ch.ivyteam.ivy.bpm.error.BpmError", "desc" : "holds error if one occurs, empty otherwise" } + ], + "map" : { + "result.error" : "in.#error" + } + }, + "tags" : "connector" + }, + "visual" : { + "at" : { "x" : 80, "y" : 648 } + }, + "connect" : { "id" : "f40", "to" : "f37" } + }, { + "id" : "f36", + "type" : "CallSubEnd", + "visual" : { + "at" : { "x" : 472, "y" : 648 } + } + }, { + "id" : "f37", + "type" : "RestClientCall", + "name" : "create formfields", + "config" : { + "path" : "/agreements/{agreementId}/formFields", + "bodyObjectMapping" : { + "param" : "in.formFieldPutInfo" + }, + "clientId" : "8e96fab1-4701-47cb-ae35-d821bb12305b", + "clientErrorCode" : "ivy:error:rest:client", + "method" : "PUT", + "statusErrorCode" : "ivy:error:rest:client", + "bodyObjectType" : "api.rest.v6.client.FormFieldPutInfo", + "templateParams" : { + "agreementId" : "in.agreementId" + }, + "resultType" : "api.rest.v6.client.AgreementFormFields", + "bodyInputType" : "ENTITY", + "bodyMediaType" : "*/*" + }, + "visual" : { + "at" : { "x" : 272, "y" : 648 } + }, + "boundaries" : [ { + "id" : "f38", + "type" : "ErrorBoundaryEvent", + "config" : { + "output" : { + "map" : { + "out" : "in", + "out.error" : "#error" + } + } + }, + "visual" : { + "at" : { "x" : 304, "y" : 690 } + }, + "connect" : { "id" : "f41", "to" : "f36", "via" : [ { "x" : 472, "y" : 690 } ] } + } ], + "connect" : { "id" : "f39", "to" : "f36" } + } ] +} \ No newline at end of file diff --git a/adobe-esign-connector/processes/connector/BaseUris.p.json b/adobe-esign-connector/processes/connector/BaseUris.p.json new file mode 100644 index 0000000..b7d12c8 --- /dev/null +++ b/adobe-esign-connector/processes/connector/BaseUris.p.json @@ -0,0 +1,41 @@ +{ + "format" : "10.0.0", + "id" : "1866EFD0633915B6", + "kind" : "CALLABLE_SUB", + "config" : { + "data" : "com.axonivy.connector.adobe.esign.connector.BaseUrisData" + }, + "elements" : [ { + "id" : "f0", + "type" : "CallSubStart", + "name" : "get()", + "config" : { + "callSignature" : "get" + }, + "visual" : { + "at" : { "x" : 96, "y" : 64 } + }, + "connect" : { "id" : "f6", "to" : "f5" } + }, { + "id" : "f1", + "type" : "CallSubEnd", + "visual" : { + "at" : { "x" : 352, "y" : 64 } + } + }, { + "id" : "f5", + "type" : "RestClientCall", + "name" : "BaseUris", + "config" : { + "path" : "/baseUris", + "clientId" : "9774590a-7adb-4910-8a53-22b68e50d9ed", + "clientErrorCode" : "ivy:error:rest:client", + "statusErrorCode" : "ivy:error:rest:client", + "resultType" : "api.rest.v6.client.BaseUriInfo" + }, + "visual" : { + "at" : { "x" : 232, "y" : 64 } + }, + "connect" : { "id" : "f4", "to" : "f1" } + } ] +} \ No newline at end of file diff --git a/adobe-esign-connector/processes/connector/TransientDocuments.p.json b/adobe-esign-connector/processes/connector/TransientDocuments.p.json new file mode 100644 index 0000000..e5b75a2 --- /dev/null +++ b/adobe-esign-connector/processes/connector/TransientDocuments.p.json @@ -0,0 +1,171 @@ +{ + "format" : "10.0.0", + "id" : "18822D67B169266A", + "kind" : "CALLABLE_SUB", + "config" : { + "data" : "com.axonivy.connector.adobe.esign.connector.TransientDocumentsData" + }, + "elements" : [ { + "id" : "f0", + "type" : "CallSubStart", + "name" : "uploadDocument(file)", + "config" : { + "callSignature" : "uploadDocument", + "input" : { + "params" : [ + { "name" : "file", "type" : "java.io.File", "desc" : "file to upload" } + ], + "map" : { + "out.file" : "param.file" + } + }, + "result" : { + "params" : [ + { "name" : "id", "type" : "String", "desc" : "id of uploaded file" }, + { "name" : "error", "type" : "ch.ivyteam.ivy.bpm.error.BpmError", "desc" : "holds error if one occurs, empty otherwise" } + ], + "map" : { + "result.id" : "in.#id", + "result.error" : "in.#error" + } + } + }, + "visual" : { + "at" : { "x" : 96, "y" : 64 } + }, + "connect" : { "id" : "f8", "to" : "f7" } + }, { + "id" : "f1", + "type" : "CallSubEnd", + "visual" : { + "at" : { "x" : 352, "y" : 64 } + } + }, { + "id" : "f7", + "type" : "RestClientCall", + "config" : { + "bodyForm" : { + "File-Name" : "", + "Mime-Type" : "", + "File" : "in.file" + }, + "path" : "/transientDocuments", + "clientId" : "6a243983-5f6d-4278-89ec-aab09fea161c", + "clientErrorCode" : "ivy:error:rest:client", + "method" : "POST", + "statusErrorCode" : "ivy:error:rest:client", + "responseMapping" : { + "out.id" : "result.transientDocumentId" + }, + "bodyObjectType" : "api.rest.v6.client.TransientDocumentsBody", + "resultType" : "api.rest.v6.client.TransientDocumentResponse", + "bodyInputType" : "FORM", + "bodyMediaType" : "multipart/form-data" + }, + "visual" : { + "at" : { "x" : 232, "y" : 64 } + }, + "boundaries" : [ { + "id" : "f9", + "type" : "ErrorBoundaryEvent", + "config" : { + "output" : { + "map" : { + "out" : "in", + "out.error" : "#error" + } + } + }, + "visual" : { + "at" : { "x" : 264, "y" : 106 } + }, + "connect" : { "id" : "f10", "to" : "f1" } + } ], + "connect" : { "id" : "f4", "to" : "f1" } + }, { + "id" : "f2", + "type" : "CallSubStart", + "name" : "uploadDocument(UploadWrapper)", + "config" : { + "callSignature" : "uploadDocument", + "input" : { + "params" : [ + { "name" : "upload", "type" : "com.axonivy.connector.adobe.esign.connector.rest.UploadWrapper", "desc" : "file to upload" } + ], + "map" : { + "out.upload" : "param.upload" + } + }, + "result" : { + "params" : [ + { "name" : "id", "type" : "String", "desc" : "id of uploaded file" }, + { "name" : "error", "type" : "ch.ivyteam.ivy.bpm.error.BpmError", "desc" : "holds error if one occurs, empty otherwise" } + ], + "map" : { + "result.id" : "in.#id", + "result.error" : "in.#error" + } + } + }, + "visual" : { + "at" : { "x" : 96, "y" : 184 } + }, + "connect" : { "id" : "f13", "to" : "f5" } + }, { + "id" : "f3", + "type" : "CallSubEnd", + "visual" : { + "at" : { "x" : 352, "y" : 184 } + } + }, { + "id" : "f5", + "type" : "RestClientCall", + "config" : { + "bodyForm" : { + "File-Name" : "", + "Mime-Type" : "", + "File" : "in.file" + }, + "path" : "/transientDocuments", + "clientId" : "6a243983-5f6d-4278-89ec-aab09fea161c", + "clientErrorCode" : "ivy:error:rest:client", + "method" : "JAX_RS", + "statusErrorCode" : "ivy:error:rest:client", + "clientCode" : [ + "import com.axonivy.connector.adobe.esign.connector.rest.UploadResult;", + "import com.axonivy.connector.adobe.esign.connector.rest.ClientService;", + "UploadResult result = ClientService.upload(client, in.upload.filename, in.upload.bytes);", + "", + "if(!result.#error is initialized) {", + " in.id = result.#id;", + "} else {", + " in.error = result.#error;", + "}" + ], + "bodyObjectType" : "api.rest.v6.client.TransientDocumentsBody", + "resultType" : "api.rest.v6.client.TransientDocumentResponse", + "bodyInputType" : "FORM", + "bodyMediaType" : "multipart/form-data" + }, + "visual" : { + "at" : { "x" : 232, "y" : 184 } + }, + "boundaries" : [ { + "id" : "f6", + "type" : "ErrorBoundaryEvent", + "config" : { + "output" : { + "map" : { + "out" : "in", + "out.error" : "#error" + } + } + }, + "visual" : { + "at" : { "x" : 264, "y" : 226 } + }, + "connect" : { "id" : "f12", "to" : "f3" } + } ], + "connect" : { "id" : "f11", "to" : "f3" } + } ] +} \ No newline at end of file diff --git a/adobe-esign-connector/processes/connector/Users.p.json b/adobe-esign-connector/processes/connector/Users.p.json new file mode 100644 index 0000000..1d9d95e --- /dev/null +++ b/adobe-esign-connector/processes/connector/Users.p.json @@ -0,0 +1,40 @@ +{ + "format" : "10.0.0", + "id" : "1866EF03C86C6870", + "kind" : "CALLABLE_SUB", + "config" : { + "data" : "com.axonivy.connector.adobe.esign.connector.UsersData" + }, + "elements" : [ { + "id" : "f0", + "type" : "CallSubStart", + "name" : "call()", + "config" : { + "callSignature" : "call" + }, + "visual" : { + "at" : { "x" : 96, "y" : 64 } + }, + "connect" : { "id" : "f4", "to" : "f3" } + }, { + "id" : "f1", + "type" : "CallSubEnd", + "visual" : { + "at" : { "x" : 352, "y" : 64 } + } + }, { + "id" : "f3", + "type" : "RestClientCall", + "config" : { + "path" : "/users", + "clientId" : "83e80d77-749b-4f53-aaf5-a796f2dab100", + "clientErrorCode" : "ivy:error:rest:client", + "statusErrorCode" : "ivy:error:rest:client", + "resultType" : "api.rest.v6.client.UsersInfo" + }, + "visual" : { + "at" : { "x" : 224, "y" : 64 } + }, + "connect" : { "id" : "f2", "to" : "f1" } + } ] +} \ No newline at end of file diff --git a/adobe-esign-connector/src/com/axonivy/connector/adobe/esign/connector/auth/BearerTokenAuthorizationFilter.java b/adobe-esign-connector/src/com/axonivy/connector/adobe/esign/connector/auth/BearerTokenAuthorizationFilter.java new file mode 100644 index 0000000..cb0b61a --- /dev/null +++ b/adobe-esign-connector/src/com/axonivy/connector/adobe/esign/connector/auth/BearerTokenAuthorizationFilter.java @@ -0,0 +1,27 @@ +package com.axonivy.connector.adobe.esign.connector.auth; + +import java.io.IOException; + +import javax.ws.rs.client.ClientRequestContext; +import javax.ws.rs.client.ClientRequestFilter; + +import org.apache.commons.lang.StringUtils; + +import ch.ivyteam.ivy.rest.client.FeatureConfig; + +public class BearerTokenAuthorizationFilter implements ClientRequestFilter { + + private static final String INTEGRATIONKEY = "AUTH.integrationKey"; + private static final String AUTHORIZATION = "Authorization"; + private static final String BEARER = "Bearer "; + + @Override + public void filter(ClientRequestContext requestContext) throws IOException { + FeatureConfig config = new FeatureConfig(requestContext.getConfiguration(), BearerTokenAuthorizationFilter.class); + String integrationKey = config.readMandatory(INTEGRATIONKEY); + if(StringUtils.isNotBlank(integrationKey)) { + requestContext.getHeaders().add(AUTHORIZATION, BEARER + integrationKey); + } + } + +} diff --git a/adobe-esign-connector/src/com/axonivy/connector/adobe/esign/connector/auth/OAuth2Feature.java b/adobe-esign-connector/src/com/axonivy/connector/adobe/esign/connector/auth/OAuth2Feature.java new file mode 100644 index 0000000..6cd100a --- /dev/null +++ b/adobe-esign-connector/src/com/axonivy/connector/adobe/esign/connector/auth/OAuth2Feature.java @@ -0,0 +1,206 @@ +package com.axonivy.connector.adobe.esign.connector.auth; + +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Arrays; +import java.util.Optional; + +import javax.ws.rs.Priorities; +import javax.ws.rs.client.Entity; +import javax.ws.rs.core.Feature; +import javax.ws.rs.core.FeatureContext; +import javax.ws.rs.core.MultivaluedHashMap; +import javax.ws.rs.core.MultivaluedMap; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.UriBuilder; +import javax.ws.rs.core.UriBuilderException; + +import org.apache.commons.lang3.StringUtils; + +import com.axonivy.connector.adobe.esign.connector.auth.oauth.OAuth2BearerFilter; +import com.axonivy.connector.adobe.esign.connector.auth.oauth.OAuth2TokenRequester.AuthContext; +import com.axonivy.connector.adobe.esign.connector.auth.oauth.OAuth2UriProperty; +import com.axonivy.connector.adobe.esign.connector.enums.AdobeVariable; + +import ch.ivyteam.ivy.bpm.error.BpmPublicErrorBuilder; +import ch.ivyteam.ivy.rest.client.FeatureConfig; +import ch.ivyteam.ivy.rest.client.oauth2.OAuth2RedirectErrorBuilder; +import ch.ivyteam.ivy.rest.client.oauth2.uri.OAuth2CallbackUriBuilder; + +public class OAuth2Feature implements Feature { + + public static interface Property { + + String CLIENT_ID = "AUTH.clientId"; + String CLIENT_SECRET = "AUTH.clientSecret"; + String AUTHORIZATION_CODE = "AUTH.code"; + String SCOPE = "AUTH.scope"; + String AUTH_BASE_URI = "AUTH.baseUri"; + String AUTH_INTEGRATION_KEY = "AUTH.integrationKey"; + } + + public static final String SESSION_TOKEN = "adobe.esign.authCode"; + + @Override + public boolean configure(FeatureContext context) { + var config = new FeatureConfig(context.getConfiguration(), OAuth2Feature.class); + String intKey = config.read(Property.AUTH_INTEGRATION_KEY).orElse(""); + + // use oauth if integration key is blank, property comes as defined (${ivy.var ...) if the variable is not defined + if(StringUtils.isBlank(intKey) || intKey.startsWith("${ivy.var")) { + var adobeSignUri = new OAuth2UriProperty(config, Property.AUTH_BASE_URI, + "https://api.eu2.echosign.com/oauth/v2"); + var oauth2 = new OAuth2BearerFilter( + ctxt -> requestToken(ctxt, adobeSignUri), + adobeSignUri); + context.register(oauth2, Priorities.AUTHORIZATION); + } + else { + context.register(BearerTokenAuthorizationFilter.class, Priorities.AUTHORIZATION - 10); + } + + return true; + } + + /** + * Get token. + * + * @param ctxt + * @param uriFactory + */ + private static Response requestToken(AuthContext ctxt, OAuth2UriProperty uriFactory) { + String authCode = ctxt.authCode().orElse(""); + var refreshToken = ctxt.refreshToken(); + if (authCode.isEmpty() && refreshToken.isEmpty()) { + try { + authRedirectError(ctxt.config, uriFactory).throwError(); + } catch (IllegalArgumentException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (UriBuilderException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (URISyntaxException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + var clientId = ctxt.config.readMandatory(Property.CLIENT_ID); + var clientSecret = ctxt.config.readMandatory(Property.CLIENT_SECRET); + Response response = null; + if (!refreshToken.isPresent()) { + AccessTokenRequest authRequest = new AccessTokenRequest(authCode, clientId, clientSecret, OAuth2CallbackUriBuilder.create().toUrl().toString()); + response = ctxt.target + .request() + .post(Entity.form(authRequest.paramsMap())); + } else { + RefreshTokenRequest authRequest = new RefreshTokenRequest(refreshToken.get(), clientId, clientSecret); + response = ctxt.target + .request() + .post(Entity.form(authRequest.paramsMap())); + } + return response; + } + + + /** + * Use the JWT grant? + * @param use + */ + public static boolean isTrue(Optional use) { + return use.filter(val -> !val.isBlank() && Boolean.parseBoolean(val)).isPresent(); + } + + public static class AccessTokenRequest { + + public String grant_type; + public String client_id; + public String client_secret; + public String redirect_uri; + public String code; + + public AccessTokenRequest(String code, String client_id, String client_secret, String redirect_uri) { + this.grant_type = "authorization_code"; + this.client_id = client_id; + this.client_secret = client_secret; + this.redirect_uri = redirect_uri; + this.code = code; + } + + public MultivaluedMap paramsMap() { + MultivaluedMap values = new MultivaluedHashMap<>(); + values.put("code", Arrays.asList(code)); + values.put("client_id", Arrays.asList(client_id)); + values.put("client_secret", Arrays.asList(client_secret)); + values.put("redirect_uri", Arrays.asList(redirect_uri)); + values.put("grant_type", Arrays.asList(grant_type)); + return values; + } + + @Override + public String toString() { + return String.format(""" + code=%s + client_id=%s& + client_secret=%s& + redirect_uri=%s + grant_type=%s + """, code, client_id, client_secret, redirect_uri, grant_type); + } + + + } + + public static class RefreshTokenRequest { + + public String grant_type; + public String refresh_token; + public String client_id; + public String client_secret; + + public RefreshTokenRequest(String refreshToken, String client_id, String client_secret) { + this.grant_type = "refresh_token"; + this.refresh_token = refreshToken; + this.client_id = client_id; + this.client_secret = client_secret; + } + + public MultivaluedMap paramsMap() { + MultivaluedMap values = new MultivaluedHashMap<>(); + values.put("client_id", Arrays.asList(client_id)); + values.put("client_secret", Arrays.asList(client_secret)); + values.put("refresh_token", Arrays.asList(refresh_token)); + values.put("grant_type", Arrays.asList(grant_type)); + return values; + } + + @Override + public String toString() { + return String.format(""" + client_id=%s& + client_secret=%s& + refresh_token=%s + grant_type=%s + """, client_id, client_secret, refresh_token, grant_type); + } + } + + private static BpmPublicErrorBuilder authRedirectError(FeatureConfig config, OAuth2UriProperty uriFactory) throws IllegalArgumentException, UriBuilderException, URISyntaxException { + URI redirectUri = OAuth2CallbackUriBuilder.create().toUrl(); + String authUri = AdobeVariable.AUTHENTICATION_URI.getValue(); + var uri = UriBuilder.fromUri(new URI(authUri)) + .queryParam("redirect_uri", redirectUri) + .queryParam("response_type", "code") + .queryParam("client_id", config.readMandatory(Property.CLIENT_ID)) + .queryParam("scope", getScope(config)) + .build(); + return OAuth2RedirectErrorBuilder + .create(uri) + .withMessage("Missing permission from user to act in his name."); + } + + static String getScope(FeatureConfig config) { + return config.read(Property.SCOPE).orElse("signature impersonation"); + } + +} \ No newline at end of file diff --git a/adobe-esign-connector/src/com/axonivy/connector/adobe/esign/connector/auth/oauth/OAuth2BearerFilter.java b/adobe-esign-connector/src/com/axonivy/connector/adobe/esign/connector/auth/oauth/OAuth2BearerFilter.java new file mode 100644 index 0000000..ed332e3 --- /dev/null +++ b/adobe-esign-connector/src/com/axonivy/connector/adobe/esign/connector/auth/oauth/OAuth2BearerFilter.java @@ -0,0 +1,160 @@ + +package com.axonivy.connector.adobe.esign.connector.auth.oauth; + +import java.io.IOException; +import java.util.Map; +import java.util.function.Supplier; + +import javax.ws.rs.client.Client; +import javax.ws.rs.client.ClientRequestContext; +import javax.ws.rs.core.GenericType; +import javax.ws.rs.core.Response.Status.Family; + +import org.apache.commons.lang3.StringUtils; + +import com.axonivy.connector.adobe.esign.connector.auth.oauth.OAuth2TokenRequester.AuthContext; +import com.axonivy.connector.adobe.esign.connector.enums.AdobeVariable; + +import ch.ivyteam.ivy.bpm.error.BpmError; +import ch.ivyteam.ivy.bpm.error.BpmPublicErrorBuilder; +import ch.ivyteam.ivy.request.IRequest; +import ch.ivyteam.ivy.rest.client.FeatureConfig; +import ch.ivyteam.ivy.rest.client.RestClientFactoryConstants; +import ch.ivyteam.ivy.rest.client.internal.oauth2.RedirectToIdentityProvider; + +@SuppressWarnings("restriction") +public class OAuth2BearerFilter implements javax.ws.rs.client.ClientRequestFilter { + private static final String AUTHORIZATION = "Authorization"; + private static final String BEARER = "Bearer "; + public static final String CODE_PARAM = "code"; + + private final OAuth2TokenRequester getToken; + private final OAuth2UriProperty uriFactory; + + private static final String TOKEN_SEPARATOR = ":"; + public static final String AUTH_SCOPE_PROPERTY = "AUTH.scope"; + + public static final AdobeVariable REFRESH_TOKEN_VAR = AdobeVariable.OAUTH_TOKEN; + public static final AdobeVariable ACCESS_TOKEN_VAR = AdobeVariable.ACCESS_TOKEN; + + private String property; + private Supplier name = null; + + public OAuth2BearerFilter(OAuth2TokenRequester getToken, OAuth2UriProperty uriFactory) { + this.getToken = getToken; + this.uriFactory = uriFactory; + } + + @Override + public void filter(ClientRequestContext context) throws IOException { + if (uriFactory.isAuthRequest(context.getUri())) { // already in token request: avoid stackOverflow + return; + } + + if (context.getHeaders().containsKey(AUTHORIZATION)) { // already set by other feature or explicit header + return; + } + + String accessToken = getAccessToken(context); + context.getHeaders().add(AUTHORIZATION, BEARER + accessToken); + } + + protected final String getAccessToken(ClientRequestContext context) { + FeatureConfig config = new FeatureConfig(context.getConfiguration(), getSource()); + VarTokenStore refreshTokenStore = VarTokenStore.get(REFRESH_TOKEN_VAR.getVariableName()); + var refreshToken = refreshTokenStore.getToken(); + + VarTokenStore accessTokenStore = VarTokenStore.get(ACCESS_TOKEN_VAR.getVariableName()); + var accessToken = accessTokenStore.getToken(); + + String resultToken = null; + + if(accessToken == null || accessToken.isExpired()) { + // refresh access token + if(refreshToken != null && refreshToken.hasRefreshToken()) { + accessToken = getRefreshedAccessToken(context.getClient(), config, refreshToken.refreshToken()); + accessTokenStore.setToken(accessToken); + resultToken = accessToken.accessToken(); + } + else { // get new access token + refreshToken = getNewAccessToken(context.getClient(), config); + refreshTokenStore.setToken(refreshToken); + accessTokenStore.setToken(refreshToken); + resultToken = refreshToken.accessToken(); + } + } + else { // use existing token + resultToken = accessToken.accessToken(); + } + + if (accessToken != null && !accessToken.hasAccessToken()) { + accessTokenStore.setToken(null); + authError().withMessage("Failed to read 'access_token' from " + refreshToken).throwError(); + } + + return resultToken; + } + + String createKey(FeatureConfig config) { + Object clientId = config.readMandatory(RestClientFactoryConstants.PROPERTY_CLIENT_ID); + var key = new StringBuilder((String) clientId); + if (property != null) { + key.append(TOKEN_SEPARATOR).append(config.readMandatory(property)); + } else { + config.read(AUTH_SCOPE_PROPERTY).ifPresent(scope -> key.append(TOKEN_SEPARATOR).append(scope)); + } + if (name != null) { + key.append(TOKEN_SEPARATOR).append(name.get()); + } + return key.toString(); + } + + private Class getSource() { + Class type = getToken.getClass(); + Class declaring = type.getDeclaringClass(); + if (declaring != null) { + return declaring; + } + return type; + } + + private Token getNewAccessToken(Client client, FeatureConfig config) { + return getAccessToken(client, config, null); + } + + private Token getRefreshedAccessToken(Client client, FeatureConfig config, String refreshToken) { + return getAccessToken(client, config, refreshToken); + } + + private Token getAccessToken(Client client, FeatureConfig config, String refreshToken) { + GenericType> map = new GenericType<>(Map.class); + + // use refresh uri for refresh token + var tokenUri = StringUtils.isNotBlank(refreshToken) ? uriFactory.getRefreshUri() : uriFactory.getTokenUri(); + String authCode = getAuthCode(); + var authContext = new AuthContext(client.target(tokenUri), config, authCode, refreshToken); + var response = getToken.requestToken(authContext); + if (response.getStatusInfo().getFamily() == Family.SUCCESSFUL) { + return new Token(response.readEntity(map)); + } + throw authError().withMessage("Failed to get or refresh access token: " + response) + .withAttribute("status", response.getStatus()) + .withAttribute("payload", response.readEntity(String.class)).build(); + } + + protected String getAuthCode() { + var request = IRequest.current(); + if (request == null) { + return null; + } + var authCode = request.getFirstParameter(CODE_PARAM); + if (StringUtils.isBlank(authCode)) { + return null; + } + return authCode; + } + + private static BpmPublicErrorBuilder authError() { + return BpmError.create(RedirectToIdentityProvider.OAUTH2_ERROR_CODE); + } +} diff --git a/adobe-esign-connector/src/com/axonivy/connector/adobe/esign/connector/auth/oauth/OAuth2TokenRequester.java b/adobe-esign-connector/src/com/axonivy/connector/adobe/esign/connector/auth/oauth/OAuth2TokenRequester.java new file mode 100644 index 0000000..8030b7f --- /dev/null +++ b/adobe-esign-connector/src/com/axonivy/connector/adobe/esign/connector/auth/oauth/OAuth2TokenRequester.java @@ -0,0 +1,57 @@ +package com.axonivy.connector.adobe.esign.connector.auth.oauth; + +import java.util.Optional; + +import javax.ws.rs.client.WebTarget; +import javax.ws.rs.core.Response; + +import ch.ivyteam.ivy.rest.client.FeatureConfig; +import ch.ivyteam.ivy.rest.client.oauth2.uri.OAuth2UriProvider; + +/** + * Requests a OAuth2 bearer access token + */ +public interface OAuth2TokenRequester { + /** + * Requests a OAuth2 bearer access token + * @param ctxt authentication context + * @return response from the remote accessToken request. Yet with an unread entity. + */ + Response requestToken(AuthContext ctxt); + + /** + * The authentication context to be used to fire an 'accessToken' request. + */ + public static class AuthContext { + /** JAX-RS client pre-configured to call the accessToken URI defined by {@link OAuth2UriProvider#getTokenUri()} */ + public final WebTarget target; + + /** Current feature configuration provided by Rest Client properties or programmatic features. */ + public final FeatureConfig config; + + private final Optional authCode; + private final Optional refreshToken; + + AuthContext(WebTarget target, FeatureConfig config, String authCode, String refreshToken) { + this.target = target; + this.config = config; + this.authCode = Optional.ofNullable(authCode); + this.refreshToken = Optional.ofNullable(refreshToken); + } + + /** + * @return User access code query parameter obtained from an OAuth2 '/auth' user consent request. Or {@link Optional#empty()} + * when an implicit grant is being implemented. + */ + public Optional authCode() { + return authCode; + } + + /** + * @return Refresh token if one is available + */ + public Optional refreshToken() { + return refreshToken; + } + } +} \ No newline at end of file diff --git a/adobe-esign-connector/src/com/axonivy/connector/adobe/esign/connector/auth/oauth/OAuth2UriProperty.java b/adobe-esign-connector/src/com/axonivy/connector/adobe/esign/connector/auth/oauth/OAuth2UriProperty.java new file mode 100644 index 0000000..69942a9 --- /dev/null +++ b/adobe-esign-connector/src/com/axonivy/connector/adobe/esign/connector/auth/oauth/OAuth2UriProperty.java @@ -0,0 +1,29 @@ +package com.axonivy.connector.adobe.esign.connector.auth.oauth; + +import java.net.URI; + +import ch.ivyteam.ivy.rest.client.FeatureConfig; + +public class OAuth2UriProperty extends ch.ivyteam.ivy.rest.client.oauth2.uri.OAuth2UriProperty { + + public static final String TOKEN_RELATIVE_PATH = "token"; + public static final String REFRESH_RELATIVE_PATH = "refresh"; + + + public OAuth2UriProperty(FeatureConfig config, String defaultBaseUri) { + super(config, defaultBaseUri); + } + + public OAuth2UriProperty(FeatureConfig config, String baseUriPropertyName, String defaultBaseUri) { + super(config, baseUriPropertyName, defaultBaseUri); + } + + public URI getRefreshUri() { + return getUri(REFRESH_RELATIVE_PATH); + } + + @Override + public URI getTokenUri() { + return getUri(TOKEN_RELATIVE_PATH); + } +} diff --git a/adobe-esign-connector/src/com/axonivy/connector/adobe/esign/connector/auth/oauth/Token.java b/adobe-esign-connector/src/com/axonivy/connector/adobe/esign/connector/auth/oauth/Token.java new file mode 100644 index 0000000..de6fa7c --- /dev/null +++ b/adobe-esign-connector/src/com/axonivy/connector/adobe/esign/connector/auth/oauth/Token.java @@ -0,0 +1,57 @@ +package com.axonivy.connector.adobe.esign.connector.auth.oauth; + +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.Map; + +import org.apache.commons.lang3.StringUtils; + +import ch.ivyteam.util.date.Now; + +public class Token { + private final Instant created; + private final Map values; + + public Token(Map values) { + this.values = values; + this.created = Now.asInstant(); + } + + public Object value(String name) { + return values.get(name); + } + + public boolean hasAccessToken() { + return StringUtils.isNotBlank(accessToken()); + } + + public String accessToken() { + return (String)values.get("access_token"); + } + + public boolean hasRefreshToken() { + return StringUtils.isNotBlank(refreshToken()); + } + + public String refreshToken() { + return (String)values.get("refresh_token"); + } + + public boolean isExpired() { + var expiresAt = created.plus(expiresIn(), ChronoUnit.SECONDS); + return Instant.now().isAfter(expiresAt); + } + + private int expiresIn() { + var expiresIn = (Double)values.get("expires_in"); + if (expiresIn == null) { + return Integer.MAX_VALUE; + } + return expiresIn.intValue(); + } + + @Override + public String toString() { + return "Token [created=" + created + " expired=" + isExpired() + " values=" + values + "]"; + } +} \ No newline at end of file diff --git a/adobe-esign-connector/src/com/axonivy/connector/adobe/esign/connector/auth/oauth/VarTokenStore.java b/adobe-esign-connector/src/com/axonivy/connector/adobe/esign/connector/auth/oauth/VarTokenStore.java new file mode 100644 index 0000000..f327bb5 --- /dev/null +++ b/adobe-esign-connector/src/com/axonivy/connector/adobe/esign/connector/auth/oauth/VarTokenStore.java @@ -0,0 +1,49 @@ +package com.axonivy.connector.adobe.esign.connector.auth.oauth; + +import com.google.gson.Gson; +import com.google.gson.JsonSyntaxException; + +import ch.ivyteam.ivy.environment.Ivy; + +/** + * Class for storing OAuth token to Variable + * @author stefan.masek + * + */ +public class VarTokenStore { + private final String varName; + + public static VarTokenStore get(String varName) { + return new VarTokenStore(varName); + } + + VarTokenStore(String varName) { + this.varName = varName; + } + + /** + * Loads Variable and tries to create the token object {@link Token} + * @return + */ + public Token getToken() { + String tokenVar = Ivy.var().get(varName); + Gson gson = new Gson(); + Token token = null; + try { + token = gson.fromJson(tokenVar, Token.class); + } catch (JsonSyntaxException e) { + Ivy.log().warn("Couldn't create Token object from variable", e); + } + return token; + } + + /** + * Stores token object {@link Token} as a json to Variable + * @param token + */ + public void setToken(Token token) { + Gson gson = new Gson(); + String tokenString = gson.toJson(token); + Ivy.var().set(varName, tokenString); + } +} diff --git a/adobe-esign-connector/src/com/axonivy/connector/adobe/esign/connector/enums/AdobeVariable.java b/adobe-esign-connector/src/com/axonivy/connector/adobe/esign/connector/enums/AdobeVariable.java new file mode 100644 index 0000000..7aed7a2 --- /dev/null +++ b/adobe-esign-connector/src/com/axonivy/connector/adobe/esign/connector/enums/AdobeVariable.java @@ -0,0 +1,46 @@ +package com.axonivy.connector.adobe.esign.connector.enums; + +import ch.ivyteam.ivy.environment.Ivy; + +public enum AdobeVariable { + BASE_URI("adobe-sign-connector.baseUri"), + HOST("adobe-sign-connector.host"), + INTEGRATION_KEY("adobe-sign-connector.integrationKey"), + RETURN_PAGE("adobe-sign-connector.returnPage"), + PERMISSIONS("adobe-sign-connector.permissions"), + CLIENT_ID("adobe-sign-connector.clientId"), + CLIENT_SECRET("adobe-sign-connector.clientSecret"), + APP_ID("adobe-sign-connector.appId"), + SECRET_KEY("adobe-sign-connector.secretKey"), + USE_APP_PERMISSIONS("adobe-sign-connector.useAppPermissions"), + CODE("adobe-sign-connector.code"), + USE_USER_PASS_FLOW_ENABLED("adobe-sign-connector.useUserPassFlow.enabled"), + USE_USER_PASS_FLOW_USER("adobe-sign-connector.useUserPassFlow.user"), + USE_USER_PASS_FLOW_PASS("adobe-sign-connector.useUserPassFlow.pass"), + OAUTH_TOKEN("adobe-sign-connector.oauthToken"), + ACCESS_TOKEN("adobe-sign-connector.accessToken"), + AUTHENTICATION_URI("adobe-sign-connector.authenticationUri"); + + private String variableName; + + private AdobeVariable(String variableName) { + this.variableName = variableName; + } + + public String getVariableName() { + return variableName; + } + + public String getValue() { + return Ivy.var().get(variableName); + } + + /** + * Sets new value to the Variable + * @param newValue + * @return old value that was set before. Empty string if it was not defined. + */ + public String updateValue(String newValue) { + return Ivy.var().set(variableName, newValue); + } +} diff --git a/adobe-esign-connector/src/com/axonivy/connector/adobe/esign/connector/json/OpenApiJsonFeature.java b/adobe-esign-connector/src/com/axonivy/connector/adobe/esign/connector/json/OpenApiJsonFeature.java new file mode 100644 index 0000000..38fd53f --- /dev/null +++ b/adobe-esign-connector/src/com/axonivy/connector/adobe/esign/connector/json/OpenApiJsonFeature.java @@ -0,0 +1,46 @@ +package com.axonivy.connector.adobe.esign.connector.json; + +import javax.ws.rs.Priorities; +import javax.ws.rs.core.FeatureContext; +import javax.ws.rs.core.MediaType; + +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.databind.MapperFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider; + +import ch.ivyteam.ivy.rest.client.mapper.JsonFeature; + +/** + * JSON object mapper that complies with generated JAX-RS client pojos. + * + * @since 9.2 + */ +public class OpenApiJsonFeature extends JsonFeature +{ + @Override + public boolean configure(FeatureContext context) + { + JacksonJsonProvider provider = new JaxRsClientJson(); + configure(provider, context.getConfiguration()); + context.register(provider, Priorities.ENTITY_CODER); + return true; + } + + public static class JaxRsClientJson extends JacksonJsonProvider + { + @Override + @SuppressWarnings("deprecation") + public ObjectMapper locateMapper(Class type, MediaType mediaType) + { + ObjectMapper mapper = super.locateMapper(type, mediaType); + // match our generated jax-rs client beans: that contain JSR310 data types + mapper.registerModule(new com.fasterxml.jackson.datatype.jsr310.JavaTimeModule()); + // allow fields starting with an upper case character (e.g. in ODATA specs)! + mapper.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true); + // not sending this optional value seems to be lass prone to errors for some remote services. + mapper.setSerializationInclusion(Include.NON_NULL); + return mapper; + } + } +} diff --git a/adobe-esign-connector/src/com/axonivy/connector/adobe/esign/connector/rest/ClientService.java b/adobe-esign-connector/src/com/axonivy/connector/adobe/esign/connector/rest/ClientService.java new file mode 100644 index 0000000..ad07f32 --- /dev/null +++ b/adobe-esign-connector/src/com/axonivy/connector/adobe/esign/connector/rest/ClientService.java @@ -0,0 +1,125 @@ +package com.axonivy.connector.adobe.esign.connector.rest; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URLConnection; + +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.client.Entity; +import javax.ws.rs.client.WebTarget; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.StringUtils; +import org.glassfish.jersey.media.multipart.Boundary; +import org.glassfish.jersey.media.multipart.FormDataBodyPart; +import org.glassfish.jersey.media.multipart.FormDataContentDisposition; +import org.glassfish.jersey.media.multipart.FormDataMultiPart; +import org.glassfish.jersey.media.multipart.MultiPart; + +import com.axonivy.connector.adobe.esign.connector.ui.util.Constants; + +import api.rest.v6.client.TransientDocumentResponse; +import ch.ivyteam.ivy.bpm.error.BpmError; +import ch.ivyteam.ivy.scripting.objects.File; + +/** + * REST client services to support download and download of attachments + * + * @author jpl + * + */ +public class ClientService { + + // error messages + private static final String IO_ERROR = "unable to read bytes from REST service stream"; + private static final String IO_IN_ERROR = "unable to create file multipart for REST service"; + + // Fallback content type, to be used when the conten't type can't be guessed from the file name + private static final String FALLBACK_CONTENT_TYPE = "application/octet-stream"; + + public static UploadResult upload(WebTarget target, String filename, byte[] bytes) { + UploadResult result = new UploadResult(); + MultiPart multipart; + + try (FormDataMultiPart formDataMultiPart = new FormDataMultiPart()) { + FormDataContentDisposition formDataContentDisposition = FormDataContentDisposition.name("File") + .fileName(filename).build(); + MediaType fileMediaType = getContentType(filename); + FormDataBodyPart filePart = new FormDataBodyPart(formDataContentDisposition, bytes, fileMediaType); + + multipart = formDataMultiPart + .field("File-Name", filename, MediaType.TEXT_PLAIN_TYPE) + .field("Mime-Type", fileMediaType.toString()) + .bodyPart(filePart); + multipart.bodyPart(filePart); + + MediaType contentType = MediaType.MULTIPART_FORM_DATA_TYPE; + contentType = Boundary.addBoundary(contentType); + + Response response = target.request().post(Entity.entity(multipart, contentType)); + + if(response.getStatus() == Response.Status.OK.getStatusCode() || + response.getStatus() == Response.Status.CREATED.getStatusCode()) { + TransientDocumentResponse entity = response.readEntity(TransientDocumentResponse.class); + result.setId(entity.getTransientDocumentId()); + } else { + result.setError(BpmError.create(Constants.ERROR_BASE) + .withCause(new WebApplicationException("Http Call failed. response code is " + response.getStatus() + + ". Error reported is " + response.getStatusInfo())) + .build()); + } + } catch (IOException e) { + result.setError(BpmError.create(Constants.ERROR_BASE) + .withMessage(IO_IN_ERROR) + .withCause(e) + .build()); + } + + return result; + } + + public static DownloadResult download(WebTarget target, String filename, boolean asFile) { + DownloadResult result = new DownloadResult(); + + try(Response response = target.request().get()) { + if(response.getStatus() == Response.Status.OK.getStatusCode()) { + try(InputStream is = response.readEntity(InputStream.class)) { + byte[] bytes = IOUtils.toByteArray(is); + result.setFilename(filename); + if(asFile) { + File file = new File(filename, true); + FileUtils.writeByteArrayToFile(file .getJavaFile(), bytes); + result.setFile(file); + } else { + result.setContent(bytes); + } + } catch (IOException e) { + result.setError(BpmError.create(Constants.ERROR_BASE) + .withMessage(IO_ERROR) + .withCause(e).build()); + } + } else { + result.setError(BpmError.create(Constants.ERROR_BASE) + .withCause(new WebApplicationException("Http Call failed. response code is " + response.getStatus() + + ". Error reported is " + response.getStatusInfo())) + .build()); + } + } + + return result; + } + + private static MediaType getContentType(String filename) throws IOException { + String contentType = URLConnection.guessContentTypeFromName(filename); + + if (StringUtils.isBlank(contentType)) { + contentType = FALLBACK_CONTENT_TYPE; + } + + return MediaType.valueOf(contentType); + } + +} diff --git a/adobe-esign-connector/src/com/axonivy/connector/adobe/esign/connector/rest/DownloadResult.java b/adobe-esign-connector/src/com/axonivy/connector/adobe/esign/connector/rest/DownloadResult.java new file mode 100644 index 0000000..67bf5e2 --- /dev/null +++ b/adobe-esign-connector/src/com/axonivy/connector/adobe/esign/connector/rest/DownloadResult.java @@ -0,0 +1,52 @@ +package com.axonivy.connector.adobe.esign.connector.rest; + +import ch.ivyteam.ivy.bpm.error.BpmError; +import ch.ivyteam.ivy.scripting.objects.File; + +/** + * Encapsulates the result of the download of a document to support bytes array and handling of multiple return values. + * + * @author jpl + * + */ +public class DownloadResult { + + String filename; + File file; + byte[] content; + BpmError error; + + public String getFilename() { + return filename; + } + + public void setFilename(String filename) { + this.filename = filename; + } + + public File getFile() { + return file; + } + + public void setFile(File file) { + this.file = file; + } + + public byte[] getContent() { + return content; + } + + public void setContent(byte[] content) { + this.content = content; + } + + public BpmError getError() { + return error; + } + + public void setError(BpmError error) { + this.error = error; + } + + +} diff --git a/adobe-esign-connector/src/com/axonivy/connector/adobe/esign/connector/rest/UploadResult.java b/adobe-esign-connector/src/com/axonivy/connector/adobe/esign/connector/rest/UploadResult.java new file mode 100644 index 0000000..b403f52 --- /dev/null +++ b/adobe-esign-connector/src/com/axonivy/connector/adobe/esign/connector/rest/UploadResult.java @@ -0,0 +1,26 @@ +package com.axonivy.connector.adobe.esign.connector.rest; + +import ch.ivyteam.ivy.bpm.error.BpmError; + +public class UploadResult { + + String id; + BpmError error; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public BpmError getError() { + return error; + } + + public void setError(BpmError error) { + this.error = error; + } + +} diff --git a/adobe-esign-connector/src/com/axonivy/connector/adobe/esign/connector/rest/UploadWrapper.java b/adobe-esign-connector/src/com/axonivy/connector/adobe/esign/connector/rest/UploadWrapper.java new file mode 100644 index 0000000..fa07c3b --- /dev/null +++ b/adobe-esign-connector/src/com/axonivy/connector/adobe/esign/connector/rest/UploadWrapper.java @@ -0,0 +1,29 @@ +package com.axonivy.connector.adobe.esign.connector.rest; + +public class UploadWrapper { + + private byte[] bytes; + private String filename; + + public UploadWrapper(String filename, byte[] bytes) { + this.filename = filename; + this.bytes = bytes; + } + + public byte[] getBytes() { + return bytes; + } + + public void setBytes(byte[] bytes) { + this.bytes = bytes; + } + + public String getFilename() { + return filename; + } + + public void setFilename(String filename) { + this.filename = filename; + } + +} diff --git a/adobe-esign-connector/src/com/axonivy/connector/adobe/esign/connector/rest/WebHookService.java b/adobe-esign-connector/src/com/axonivy/connector/adobe/esign/connector/rest/WebHookService.java new file mode 100644 index 0000000..b4c5e2c --- /dev/null +++ b/adobe-esign-connector/src/com/axonivy/connector/adobe/esign/connector/rest/WebHookService.java @@ -0,0 +1,57 @@ +package com.axonivy.connector.adobe.esign.connector.rest; + +import javax.annotation.security.PermitAll; +import javax.ws.rs.Consumes; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.Response.Status; + +import org.apache.commons.lang.StringUtils; + +import com.fasterxml.jackson.databind.JsonNode; + +import ch.ivyteam.ivy.environment.Ivy; + +/** + * REST service for Adobe Sign WebHook communication. This service provides methods for verification and to receive notifications. + *
+ * The service needs to be configured as WebHook in the Adobe Sign profile settings. + * @see WebHooks in Adobe Sign + * + * @author jpl + * + */ +@Path("/adobe/webhook") +@PermitAll +public class WebHookService { + + private static final String CLIENT_ID_HEADER = "X-ADOBESIGN-CLIENTID"; + + @GET + @Produces("application/json") + public Response verification(@Context HttpHeaders headers) { + Response response; + String clientId = headers.getHeaderString(CLIENT_ID_HEADER); + if(StringUtils.isNotBlank(clientId)) { + response = Response.accepted().entity(new ClientIdResponse(clientId)).build(); + } else { + response = Response.status(Status.NOT_ACCEPTABLE).build(); + } + + return response; + } + + @POST + @Consumes("application/json") + public void notification(JsonNode node) { + Ivy.log().info(node); + } + + private record ClientIdResponse(String xAdobeSignClientId) {}; + +} diff --git a/adobe-esign-connector/src/com/axonivy/connector/adobe/esign/connector/service/AdminSetupService.java b/adobe-esign-connector/src/com/axonivy/connector/adobe/esign/connector/service/AdminSetupService.java new file mode 100644 index 0000000..b548f19 --- /dev/null +++ b/adobe-esign-connector/src/com/axonivy/connector/adobe/esign/connector/service/AdminSetupService.java @@ -0,0 +1,135 @@ +package com.axonivy.connector.adobe.esign.connector.service; + +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Map; + +import javax.ws.rs.client.Client; +import javax.ws.rs.client.ClientBuilder; +import javax.ws.rs.client.Entity; +import javax.ws.rs.client.WebTarget; +import javax.ws.rs.core.GenericType; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.Response.Status.Family; +import javax.ws.rs.core.UriBuilder; +import javax.ws.rs.core.UriBuilderException; + +import org.primefaces.PrimeFaces; + +import com.axonivy.connector.adobe.esign.connector.AdminSetup.AdminSetupData; +import com.axonivy.connector.adobe.esign.connector.auth.OAuth2Feature.AccessTokenRequest; +import com.axonivy.connector.adobe.esign.connector.auth.oauth.OAuth2UriProperty; +import com.axonivy.connector.adobe.esign.connector.auth.oauth.Token; +import com.axonivy.connector.adobe.esign.connector.auth.oauth.VarTokenStore; +import com.axonivy.connector.adobe.esign.connector.enums.AdobeVariable; + +import ch.ivyteam.ivy.environment.Ivy; +import ch.ivyteam.ivy.request.EngineUriResolver; + +public class AdminSetupService { + private static final String OAUTH_RESUME_START_FRIENDLY_PATH = "Setup/AdminSetup/oauthResume.ivp"; + + /** + * Initializes variables for the dialog data + * @param data dialog data + */ + public static void initVars(AdminSetupData data) { + data.setAuthenticationUri(AdobeVariable.AUTHENTICATION_URI.getValue()); + data.setBaseUri(AdobeVariable.BASE_URI.getValue()); + data.setClientId(AdobeVariable.CLIENT_ID.getValue()); + data.setClientSecret(AdobeVariable.CLIENT_SECRET.getValue()); + data.setHost(AdobeVariable.HOST.getValue()); + data.setIntegrationKey(AdobeVariable.INTEGRATION_KEY.getValue()); + data.setOauthToken(AdobeVariable.OAUTH_TOKEN.getValue()); + data.setPermissions(AdobeVariable.PERMISSIONS.getValue()); + data.setReturnPage(AdobeVariable.RETURN_PAGE.getValue()); + data.setAccessToken(AdobeVariable.ACCESS_TOKEN.getValue()); + } + + /** + * Updates Variables from dialog data + * @param data dialog data + */ + public static void updateVars(AdminSetupData data) { + AdobeVariable.AUTHENTICATION_URI.updateValue(data.getAuthenticationUri()); + AdobeVariable.BASE_URI.updateValue(data.getBaseUri()); + AdobeVariable.CLIENT_ID.updateValue(data.getClientId()); + AdobeVariable.CLIENT_SECRET.updateValue(data.getClientSecret()); + AdobeVariable.HOST.updateValue(data.getHost()); + AdobeVariable.INTEGRATION_KEY.updateValue(data.getIntegrationKey()); + AdobeVariable.PERMISSIONS.updateValue(data.getPermissions()); + AdobeVariable.RETURN_PAGE.updateValue(data.getReturnPage()); + // dont update oauth token variables + } + + /** + * Opens Adobe OAuth authentication page with parameters from Variables + * @throws IOException + * @throws IllegalArgumentException + * @throws UriBuilderException + * @throws URISyntaxException + */ + public static void authRedirect() + throws IOException, IllegalArgumentException, UriBuilderException, URISyntaxException { + var clientId = AdobeVariable.CLIENT_ID.getValue(); + var scope = AdobeVariable.PERMISSIONS.getValue(); + String redirectUri = createRedirectUrl(); + + String authUri = AdobeVariable.AUTHENTICATION_URI.getValue(); + URI uri = UriBuilder.fromUri(new URI(authUri)) + .queryParam("redirect_uri", redirectUri) + .queryParam("response_type", "code") + .queryParam("client_id", clientId) + .queryParam("scope", scope) + .build(); + PrimeFaces.current().executeScript("window.open('" + uri.toString() + "', '_top')"); + } + + /** + * Tries to get the OAuth access token with given code and parameters loaded. + * Stores the token in the Variable {@link AdobeVariable#OAUTH_TOKEN} + * + * @param authCode + * @throws URISyntaxException + */ + public static void getNewAccessToken(String authCode) throws URISyntaxException { + var clientId = AdobeVariable.CLIENT_ID.getValue(); + var clientSecret = AdobeVariable.CLIENT_SECRET.getValue(); + UriBuilder tokenBaseUri = UriBuilder.fromUri(new URI(AdobeVariable.BASE_URI.getValue())); + + // get token stores for refresh and access tokens + VarTokenStore refreshTokenStore = VarTokenStore.get(AdobeVariable.OAUTH_TOKEN.getVariableName()); + VarTokenStore accessTokenStore = VarTokenStore.get(AdobeVariable.ACCESS_TOKEN.getVariableName()); + + //request new token + Client client = ClientBuilder.newClient(); + tokenBaseUri.path(OAuth2UriProperty.TOKEN_RELATIVE_PATH); + AccessTokenRequest authRequest = new AccessTokenRequest(authCode, clientId, clientSecret, + createRedirectUrl()); + WebTarget target = client.target(tokenBaseUri); + Response response = target.request().post(Entity.form(authRequest.paramsMap())); + + if (response.getStatusInfo().getFamily() == Family.SUCCESSFUL) { + GenericType> map = new GenericType<>(Map.class); + // store new token + Token newToken = new Token(response.readEntity(map)); + refreshTokenStore.setToken(newToken); + accessTokenStore.setToken(newToken); + } else { + Ivy.log().error("failed to get access token: " + response); + } + } + + /** + * Creates URI for redirecting after Adobe authentication + * + * @return + */ + public static String createRedirectUrl() { + String startUri = ProcessStartService + .findRelativeUrlByProcessStartFriendlyRequestPath(OAUTH_RESUME_START_FRIENDLY_PATH); + URI redirectUri = UriBuilder.fromUri(EngineUriResolver.instance().external()).path(startUri).build(); + return redirectUri.toString(); + } +} diff --git a/adobe-esign-connector/src/com/axonivy/connector/adobe/esign/connector/service/AdobeSignService.java b/adobe-esign-connector/src/com/axonivy/connector/adobe/esign/connector/service/AdobeSignService.java new file mode 100644 index 0000000..0076d60 --- /dev/null +++ b/adobe-esign-connector/src/com/axonivy/connector/adobe/esign/connector/service/AdobeSignService.java @@ -0,0 +1,460 @@ +package com.axonivy.connector.adobe.esign.connector.service; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; + +import javax.servlet.http.HttpServletRequest; + +import com.axonivy.connector.adobe.esign.connector.rest.DownloadResult; +import com.axonivy.connector.adobe.esign.connector.rest.UploadWrapper; + +import api.rest.v6.client.AgreementCreationInfo; +import api.rest.v6.client.AgreementCreationInfo.SignatureTypeEnum; +import api.rest.v6.client.AgreementCreationInfo.StateEnum; +import api.rest.v6.client.AgreementCreationInfoMemberInfos; +import api.rest.v6.client.AgreementCreationInfoParticipantSetsInfo; +import api.rest.v6.client.AgreementCreationInfoParticipantSetsInfo.RoleEnum; +import api.rest.v6.client.AgreementCreationResponse; +import api.rest.v6.client.AgreementDocuments; +import api.rest.v6.client.AgreementInfo; +import api.rest.v6.client.AgreementsAnchorTextInfo; +import api.rest.v6.client.AgreementsAnchorTextInfoAnchoredFormFieldLocation; +import api.rest.v6.client.AgreementsEmailOption; +import api.rest.v6.client.AgreementsEmailOptionSendOptions; +import api.rest.v6.client.AgreementsEmailOptionSendOptions.CompletionEmailsEnum; +import api.rest.v6.client.AgreementsEmailOptionSendOptions.InFlightEmailsEnum; +import api.rest.v6.client.AgreementsEmailOptionSendOptions.InitEmailsEnum; +import api.rest.v6.client.AgreementsFileInfos; +import api.rest.v6.client.AgreementsFormFieldDescription; +import api.rest.v6.client.AgreementsFormFieldDescription.ContentTypeEnum; +import api.rest.v6.client.AgreementsFormFieldGenerators; +import api.rest.v6.client.AgreementsFormFieldGenerators.GeneratorTypeEnum; +import api.rest.v6.client.AgreementsPostSignOption; +import api.rest.v6.client.FormFieldPutInfo; +import api.rest.v6.client.SigningUrlResponseSigningUrlSetInfos; +import ch.ivyteam.ivy.bpm.error.BpmError; +import ch.ivyteam.ivy.environment.Ivy; +import ch.ivyteam.ivy.process.call.SubProcessCall; +import ch.ivyteam.ivy.process.call.SubProcessCallResult; +import ch.ivyteam.ivy.request.IHttpRequest; + +/** + * Service class to call Adobe Sign services and create necessary objects + * + * @author jpl + * + */ +public class AdobeSignService { + + private static final AdobeSignService instance = new AdobeSignService(); + + // process paths, signature and parameter names + private static final String SERVICE_BASE = "connector/"; + private static final String TRANSIENT_DOCUMENTS_SERVICE = SERVICE_BASE + "TransientDocuments"; + private static final String AGREEMENTS_SERVICE = SERVICE_BASE + "Agreements"; + + private static final String CREATE_AGREEMENT_SIGNATURE = "createAgreement(AgreementCreationInfo)"; + private static final String GET_AGREEMENT_SIGNATURE = "getAgreementById(String)"; + private static final String GET_DOCUMENTS_SIGNATURE = "getDocuments(String)"; + private static final String UPLOAD_SIGNATURE = "uploadDocument(UploadWrapper)"; + private static final String DOWNLOAD_SIGNATURE = "dowloadDocument(String, String, String, Boolean)"; + private static final String SIGNING_URL_SIGNATURE = "getSigningURLs(String,String)"; + private static final String FORMFIELD_SIGNATURE = "addFormFields(String, FormFieldPutInfo)"; + + private static final String ID_PARAM = "id"; + private static final String ERROR_PARAM = "error"; + private static final String UPLOAD_PARAM = "upload"; + private static final String AGREEMENT_PARAM = "agreement"; + private static final String AGREEMENT_ID_PARAM = "agreementId"; + + + private AdobeSignService() {} // locked constructor + + public static AdobeSignService getInstance() { + return instance; + } + + /** + * Convenience method to call upload document and create agreement methods + * + * @param name + * @param workflowId + * @param signerEmail + * @param upload + * @return + */ + public String uploadDocumentAndCreateSimpleAgreement(String name, String signerEmail, UploadWrapper upload) { + String documentId = uploadDocument(upload); + return createAgreement(buildSimpleAgreement(name, documentId, signerEmail)); + } + + /** + * Convenience method to call upload document and create agreement methods + * + * @param name + * @param workflowId + * @param signerEmail + * @param upload + * @return + */ + public String uploadDocumentAndCreateSimpleAgreementWithFormFields(String name, String signerEmail, UploadWrapper upload, List formFieldGenerators) { + String documentId = uploadDocument(upload); + String agreementId = createAgreement( + buildSimpleAgreementWithFormFields(name, Arrays.asList(documentId), signerEmail, formFieldGenerators)); + + return agreementId; + } + + /** + * Helper method to create agreement object as required for the REST service + * + * @param name + * @param workflowId + * @param documentId + * @param signerEmail + * @return + */ + public AgreementCreationInfo buildSimpleAgreement(String name, String documentId, String signerEmail) { + return buildSimpleAgreementWithFormFields(name, Arrays.asList(documentId), signerEmail, null); + } + + /** + * Helper method to create agreement object as required for the REST service + * + * @param name + * @param workflowId + * @param documentId + * @param signerEmail + * @return + */ + public AgreementCreationInfo buildSimpleAgreementWithFormFields(String name, List documentIds, + String signerEmail, List formFieldGenerators) { + AgreementCreationInfo agreement = new AgreementCreationInfo(); + + agreement.setName(name); + agreement.setMessage("Please sign this document!"); + agreement.setSignatureType(SignatureTypeEnum.ESIGN); + agreement.setState(StateEnum.IN_PROCESS); + + // add signers + agreement.setParticipantSetsInfo(createParticipantInfoForEmail(Arrays.asList(signerEmail), RoleEnum.SIGNER)); + + // add documentIds - need to be already transferred with the upload document service + agreement.setFileInfos(createFileInfosForDocumentIds(documentIds)); + + agreement.setEmailOption(createAllDisabledSendOptions()); + + String baseUrl = getRequestBaseUrl(); + String fullUrl = baseUrl + Ivy.var().get("adobe-sign-connector.returnPage"); + agreement.postSignOption(new AgreementsPostSignOption().redirectUrl(fullUrl)); + + if(Objects.nonNull(formFieldGenerators)) { + agreement.setFormFieldGenerators(formFieldGenerators); + } + + return agreement; + } + + /** + * Helper method to create agreement object for two groups of signers as required for the REST service + * + * @param name + * @param workflowId + * @param documentId + * @param signer1Email + * @return + */ + public AgreementCreationInfo buildSimpleAgreementFor2SignerGroups(String name, String documentId, List signers1, List signers2) { + return buildSimpleAgreementFor2ParticipantGroups(name, documentId, signers1, RoleEnum.SIGNER, signers2, RoleEnum.SIGNER); + } + + /** + * Helper method to create agreement for two groups of participants object as required for the REST service + * + * @param name + * @param workflowId + * @param documentId + * @param signer1Email + * @return + */ + public AgreementCreationInfo buildSimpleAgreementFor2ParticipantGroups(String name, String documentId, List participants1, + RoleEnum participants1Role, List participants2, RoleEnum participants2Role) { + AgreementCreationInfo agreement = new AgreementCreationInfo(); + + agreement.setName(name); + agreement.setMessage("Please sign this document!"); + agreement.setSignatureType(SignatureTypeEnum.ESIGN); + agreement.setState(StateEnum.IN_PROCESS); + + // add signers + agreement.setParticipantSetsInfo(createParticipantInfoForEmail(participants1, participants1Role)); + agreement.getParticipantSetsInfo().add(createParticipants(participants2, participants2Role, 2)); + + // add documentIds - need to be already transferred with the upload document service + agreement.setFileInfos(createFileInfosForDocumentIds(Arrays.asList(documentId))); + + agreement.setEmailOption(createAllDisabledSendOptions()); + + String baseUrl = getRequestBaseUrl(); + String fullUrl = baseUrl + Ivy.var().get("adobe-sign-connector.returnPage"); + agreement.postSignOption(new AgreementsPostSignOption().redirectUrl(fullUrl)); + + return agreement; + } + + /** + * Wrapper method to call sub process to post a new agreement + * + * @param agreement + * @return + * @throws BpmError + */ + public String createAgreement(AgreementCreationInfo agreement) throws BpmError { + SubProcessCallResult callResult = SubProcessCall.withPath(AGREEMENTS_SERVICE).withStartSignature(CREATE_AGREEMENT_SIGNATURE).withParam(AGREEMENT_PARAM, agreement).call(); + + handleError(callResult); + AgreementCreationResponse agreementResponse = callResult.get("agreementInfo", AgreementCreationResponse.class); + + return agreementResponse.getId(); + } + + /** + * Wrapper method to call sub process to get agreement by its id + * + * @param agreementId + * @return + */ + public AgreementInfo getAgreement(String agreementId) { + SubProcessCallResult callResult = SubProcessCall.withPath(AGREEMENTS_SERVICE).withStartSignature(GET_AGREEMENT_SIGNATURE).withParam(AGREEMENT_ID_PARAM, agreementId).call(); + + handleError(callResult); + return callResult.get("agreementInfo", AgreementInfo.class); + } + + /** + * Wrapper method to call sub process to get list of documents for an agreement + * + * @param agreementId + * @return + */ + public AgreementDocuments getDocuments(String agreementId) { + SubProcessCallResult callResult = SubProcessCall.withPath(AGREEMENTS_SERVICE).withStartSignature(GET_DOCUMENTS_SIGNATURE).withParam(AGREEMENT_ID_PARAM, agreementId).call(); + + handleError(callResult); + return callResult.get("documents", AgreementDocuments.class); + } + + /** + * Wrapper method to call sub process to retrieve the signing URIs of an agreement + * + * @param agreementId + * @param frameParent + * @return + */ + @SuppressWarnings("unchecked") + public List getSigningURIs(String agreementId, String frameParent) { + SubProcessCallResult callResult = SubProcessCall.withPath(AGREEMENTS_SERVICE).withStartSignature(SIGNING_URL_SIGNATURE) + .withParam(AGREEMENT_ID_PARAM, agreementId) + .withParam("frameParent", frameParent) + .call(); + + handleError(callResult); + return (List)callResult.get("signingURIs"); + } + + /** + * Wrapper method to call sub process to upload a new transient document required to create a new agreement + * + * @param upload + * @return + * @throws BpmError + */ + public String uploadDocument(String filename, byte[] bytes) throws BpmError { + return uploadDocument(new UploadWrapper(filename, bytes)); + } + + /** + * Wrapper method to call sub process to upload a new transient document required to create a new agreement + * + * @param upload + * @return + * @throws BpmError + */ + public String uploadDocument(UploadWrapper upload) throws BpmError { + String documentId; + + SubProcessCallResult callResult = SubProcessCall.withPath(TRANSIENT_DOCUMENTS_SERVICE).withStartSignature(UPLOAD_SIGNATURE).withParam(UPLOAD_PARAM, upload).call(); + + handleError(callResult); + documentId = callResult.get(ID_PARAM, String.class); + + return documentId; + } + + /** + * Wrapper method to call sub process to download a document of an agreement + * + * @param agreementId + * @param documentId + * @param filename + * @param asFile + * @return + */ + public DownloadResult downloadDocument(String agreementId, String documentId, String filename, Boolean asFile) { + SubProcessCallResult callResult = SubProcessCall.withPath(AGREEMENTS_SERVICE).withStartSignature(DOWNLOAD_SIGNATURE) + .withParam("agreementId", agreementId) + .withParam("documentId", documentId) + .withParam("filename", filename) + .withParam("asFile", asFile) + .call(); + + handleError(callResult); + return callResult.get("download", DownloadResult.class); + } + + /** + * Wrapper method to call sub process to add form fields to an agreement + * + * @param agreementId + * @param formFieldInfo + */ + public void addFormFieldsToAgreement(String agreementId, FormFieldPutInfo formFieldInfo) { + SubProcessCallResult callResult = SubProcessCall.withPath(AGREEMENTS_SERVICE).withStartSignature(FORMFIELD_SIGNATURE) + .withParam("agreementId", agreementId) + .withParam("formFieldInfo", formFieldInfo) + .call(); + + handleError(callResult); + } + + /** + * creates participant information for a list of email addresses and role as required for the REST services + * + * @param signerEmails + * @param role + * @param order + * @return + */ + public List createParticipantInfoForEmail(List signerEmails, RoleEnum role) { + return createParticipantInfoForEmail(signerEmails, role, 1); + } + + /** + * creates participant information for a list of email addresses and role as required for the REST services + * + * @param signerEmails + * @param role + * @param order + * @return + */ + public List createParticipantInfoForEmail(List signerEmails, RoleEnum role, Integer order) { + List participantSetsInfo = new ArrayList<>(); + AgreementCreationInfoParticipantSetsInfo participant = createParticipants(signerEmails, role, order); + participantSetsInfo.add(participant); + return participantSetsInfo; + } + + /** + * creates participants for a list of email addresses + * + * @param signerEmails + * @param role + * @param order + * @return + */ + public AgreementCreationInfoParticipantSetsInfo createParticipants(List signerEmails, RoleEnum role, + Integer order) { + AgreementCreationInfoParticipantSetsInfo participant = new AgreementCreationInfoParticipantSetsInfo(); + List memberInfos = new ArrayList<>(); + + for(String email : signerEmails) { + AgreementCreationInfoMemberInfos member = new AgreementCreationInfoMemberInfos(); + member.setEmail(email); + memberInfos.add(member); + } + + participant.setMemberInfos(memberInfos); + participant.setRole(role); + participant.setOrder(order); + return participant; + } + + /** + * creates file info for a list of document ids + * + * @param documentIds + * @return + */ + public List createFileInfosForDocumentIds(List documentIds) { + List fileInfos = new ArrayList<>(); + + for(String id : documentIds) { + AgreementsFileInfos fileInfo = new AgreementsFileInfos(); + fileInfo.transientDocumentId(id); + fileInfos.add(fileInfo ); + } + return fileInfos; + } + + public AgreementsFormFieldGenerators createFormFieldWithAnchor(String anchorText, ContentTypeEnum contentType, Double offsetX, Double offsetY) { + return new AgreementsFormFieldGenerators().generatorType(GeneratorTypeEnum.ANCHOR_TEXT) + .formFieldDescription(new AgreementsFormFieldDescription().contentType(contentType)) + .anchorTextInfo( + new AgreementsAnchorTextInfo().anchorText(anchorText) + .anchoredFormFieldLocation(new AgreementsAnchorTextInfoAnchoredFormFieldLocation().offsetX(offsetX).offsetY(offsetY))); + } + + /** + * creates options for sending email + * + * @param completion + * @param inflight + * @param init + * @return + */ + public AgreementsEmailOption createSendOptions(CompletionEmailsEnum completion, InFlightEmailsEnum inflight, InitEmailsEnum init) { + AgreementsEmailOption emailOptions = new AgreementsEmailOption(); + AgreementsEmailOptionSendOptions sendOptions = new AgreementsEmailOptionSendOptions(); + sendOptions.setCompletionEmails(completion); + sendOptions.setInFlightEmails(inflight); + sendOptions.setInitEmails(init); + emailOptions.setSendOptions(sendOptions ); + return emailOptions; + } + + /** + * create options for sending email with all disabled + * + * @return + */ + public AgreementsEmailOption createAllDisabledSendOptions() { + return createSendOptions(CompletionEmailsEnum.NONE, InFlightEmailsEnum.NONE, InitEmailsEnum.NONE); + } + + private void handleError(SubProcessCallResult callResult) { + BpmError error = callResult.get(ERROR_PARAM, BpmError.class); + if(Objects.nonNull(error)) { + throw error; + } + } + + /** + * Extracts the base url from actual request. + * Example: http://localhost:8081 + * @return + */ + private String getRequestBaseUrl() { + IHttpRequest req = (IHttpRequest) Ivy.request(); + HttpServletRequest request = req.getHttpServletRequest(); + StringBuilder baseUrlBuilder = new StringBuilder(); + baseUrlBuilder.append(request.getScheme()).append("://").append(request.getServerName()); + int port = request.getServerPort(); + if (port != 80 && port != 443) { + baseUrlBuilder.append(":").append(port); + } + return baseUrlBuilder.toString(); + } + +} diff --git a/adobe-esign-connector/src/com/axonivy/connector/adobe/esign/connector/service/ProcessStartService.java b/adobe-esign-connector/src/com/axonivy/connector/adobe/esign/connector/service/ProcessStartService.java new file mode 100644 index 0000000..54fdafa --- /dev/null +++ b/adobe-esign-connector/src/com/axonivy/connector/adobe/esign/connector/service/ProcessStartService.java @@ -0,0 +1,76 @@ +package com.axonivy.connector.adobe.esign.connector.service; + +import java.util.List; +import java.util.Objects; +import java.util.stream.Stream; + +import org.apache.commons.lang3.StringUtils; + +import ch.ivyteam.ivy.application.ActivityState; +import ch.ivyteam.ivy.application.IApplication; +import ch.ivyteam.ivy.application.IProcessModel; +import ch.ivyteam.ivy.application.IProcessModelVersion; +import ch.ivyteam.ivy.application.app.IApplicationRepository; +import ch.ivyteam.ivy.environment.Ivy; +import ch.ivyteam.ivy.security.ISecurityContext; +import ch.ivyteam.ivy.security.exec.Sudo; +import ch.ivyteam.ivy.workflow.IProcessStart; +import ch.ivyteam.ivy.workflow.IWorkflowProcessModelVersion; + +/** + * Service for working with process starts. Code is from portal's + * ProcessStartAPI + */ +public class ProcessStartService { + /** + * Find start link from friendly request path + * + * @param friendlyRequestPath friendly path e.g "Start + * Processes/UserExampleGuide/userExampleGuide.ivp" + * @return start link or empty string + */ + public static String findRelativeUrlByProcessStartFriendlyRequestPath(String friendlyRequestPath) { + IProcessStart processStart = findProcessStartByUserFriendlyRequestPath(friendlyRequestPath); + return processStart != null ? processStart.getLink().getRelative() : StringUtils.EMPTY; + } + + private static IProcessStart findProcessStartByUserFriendlyRequestPath(String requestPath) { + return Sudo.get(() -> { + IProcessStart processStart = getProcessStart(requestPath, Ivy.request().getProcessModelVersion()); + if (processStart != null) { + return processStart; + } + List applicationsInSecurityContext = IApplicationRepository.instance() + .allOf(ISecurityContext.current()); + for (IApplication app : applicationsInSecurityContext) { + IProcessStart findProcessStart = filterPMV(requestPath, app).findFirst().orElse(null); + if (findProcessStart != null) { + return findProcessStart; + } + } + return null; + }); + } + + private static IProcessStart getProcessStart(String requestPath, IProcessModelVersion processModelVersion) { + return IWorkflowProcessModelVersion.of(processModelVersion) + .findStartElementByUserFriendlyRequestPath(requestPath); + } + + private static Stream filterPMV(String requestPath, IApplication application) { + return filterActivePMVOfApp(application).map(p -> getProcessStart(requestPath, p)).filter(Objects::nonNull); + } + + private static Stream filterActivePMVOfApp(IApplication application) { + return application.getProcessModelsSortedByName().stream().filter(pm -> isActive(pm)) + .map(IProcessModel::getReleasedProcessModelVersion).filter(pmv -> isActive(pmv)); + } + + private static boolean isActive(IProcessModelVersion processModelVersion) { + return processModelVersion != null && processModelVersion.getActivityState() == ActivityState.ACTIVE; + } + + private static boolean isActive(IProcessModel processModel) { + return processModel.getActivityState() == ActivityState.ACTIVE; + } +} \ No newline at end of file diff --git a/adobe-esign-connector/src/com/axonivy/connector/adobe/esign/connector/ui/SigningBean.java b/adobe-esign-connector/src/com/axonivy/connector/adobe/esign/connector/ui/SigningBean.java new file mode 100644 index 0000000..9cabd71 --- /dev/null +++ b/adobe-esign-connector/src/com/axonivy/connector/adobe/esign/connector/ui/SigningBean.java @@ -0,0 +1,43 @@ +package com.axonivy.connector.adobe.esign.connector.ui; + +import javax.faces.bean.ManagedBean; +import javax.faces.bean.ViewScoped; + +import api.rest.v6.client.AgreementDocuments; + +@ManagedBean +@ViewScoped +public class SigningBean { + + private String agreementId; + private String documentId; + private AgreementDocuments documents; + + public void createAgreement() { + + } + + public String getAgreementId() { + return agreementId; + } + + public void setAgreementId(String agreementId) { + this.agreementId = agreementId; + } + + public String getDocumentId() { + return documentId; + } + + public void setDocumentId(String documentId) { + this.documentId = documentId; + } + + public AgreementDocuments getDocuments() { + return documents; + } + + public void setDocuments(AgreementDocuments documents) { + this.documents = documents; + } +} diff --git a/adobe-esign-connector/src/com/axonivy/connector/adobe/esign/connector/ui/bean/AdminSetupBean.java b/adobe-esign-connector/src/com/axonivy/connector/adobe/esign/connector/ui/bean/AdminSetupBean.java new file mode 100644 index 0000000..8f20914 --- /dev/null +++ b/adobe-esign-connector/src/com/axonivy/connector/adobe/esign/connector/ui/bean/AdminSetupBean.java @@ -0,0 +1,19 @@ +package com.axonivy.connector.adobe.esign.connector.ui.bean; + +import javax.faces.bean.ManagedBean; +import javax.faces.bean.ViewScoped; + +import com.axonivy.connector.adobe.esign.connector.enums.AdobeVariable; +import com.axonivy.connector.adobe.esign.connector.service.AdminSetupService; + +@ManagedBean +@ViewScoped +public class AdminSetupBean { + public String getVariableName(AdobeVariable var) { + return var.getVariableName(); + } + + public String getRedirectUri() { + return AdminSetupService.createRedirectUrl(); + } +} diff --git a/adobe-esign-connector/src/com/axonivy/connector/adobe/esign/connector/ui/util/Constants.java b/adobe-esign-connector/src/com/axonivy/connector/adobe/esign/connector/ui/util/Constants.java new file mode 100644 index 0000000..161f2c6 --- /dev/null +++ b/adobe-esign-connector/src/com/axonivy/connector/adobe/esign/connector/ui/util/Constants.java @@ -0,0 +1,12 @@ +package com.axonivy.connector.adobe.esign.connector.ui.util; + +/** + * Constants for usage in project + * + * @author jpl + * + */ +public class Constants { + + public static final String ERROR_BASE = "com:axonivy:connector:adobe:esign:connector"; +} diff --git a/adobe-esign-connector/src/com/axonivy/connector/adobe/esign/connector/ui/util/FileUtils.java b/adobe-esign-connector/src/com/axonivy/connector/adobe/esign/connector/ui/util/FileUtils.java new file mode 100644 index 0000000..c5990af --- /dev/null +++ b/adobe-esign-connector/src/com/axonivy/connector/adobe/esign/connector/ui/util/FileUtils.java @@ -0,0 +1,45 @@ +package com.axonivy.connector.adobe.esign.connector.ui.util; + +import java.io.ByteArrayInputStream; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.InputStream; + +import org.primefaces.model.DefaultStreamedContent; +import org.primefaces.model.StreamedContent; + +import com.axonivy.connector.adobe.esign.connector.rest.DownloadResult; + +/** + * Utility class for JSF file handling e.g. to create StreamedContent + * + * @author jpl + * + */ +public class FileUtils { + + public static StreamedContent toStreamedContent(String filename, DownloadResult download) throws FileNotFoundException { + return DefaultStreamedContent.builder() + .name(filename) + .contentType("application/pdf") + .stream(() -> { + try { + return new FileInputStream(download.getFile().getJavaFile()); + } catch (FileNotFoundException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + return null; + }) + .build(); + } + + public static InputStream toInputStream(byte[] bytes) { + if (bytes != null) { + return new ByteArrayInputStream(bytes); + } + + return new ByteArrayInputStream(new byte[0]); + } + +} diff --git a/adobe-esign-connector/src_hd/com/axonivy/connector/adobe/esign/connector/AdminSetup/AdminSetup.rddescriptor b/adobe-esign-connector/src_hd/com/axonivy/connector/adobe/esign/connector/AdminSetup/AdminSetup.rddescriptor new file mode 100644 index 0000000..ae605f0 --- /dev/null +++ b/adobe-esign-connector/src_hd/com/axonivy/connector/adobe/esign/connector/AdminSetup/AdminSetup.rddescriptor @@ -0,0 +1,7 @@ + + + + viewTechnology + JSF + + diff --git a/adobe-esign-connector/src_hd/com/axonivy/connector/adobe/esign/connector/AdminSetup/AdminSetup.xhtml b/adobe-esign-connector/src_hd/com/axonivy/connector/adobe/esign/connector/AdminSetup/AdminSetup.xhtml new file mode 100644 index 0000000..ca0f296 --- /dev/null +++ b/adobe-esign-connector/src_hd/com/axonivy/connector/adobe/esign/connector/AdminSetup/AdminSetup.xhtml @@ -0,0 +1,162 @@ + + + + AdminSetup + + +

+ #{ivy.cms.co('/Dialogs/com/axonivy/connector/adobe/esign/connector/AdminSetup/Title')} +

+ + + +
+
+
+ +
+
+ +
+
+
+
+ +
+
+ +
+
+
+
+ + +
+
+
+ +
+
+
+
+ +
+
+ +
+
+
+
+ + +
+
+
+ +
+
+ +
+
+
+
+ +
+
+ +
+
+
+
+ +
+
+ +
+
+
+
+ +
+
+ +
+
+
+
+ +
+
+ +
+
+
+
+ +
+
+ + +
+
+ +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ +
+
+
+
+ +
+ +
+
+ +
+
+
+ + \ No newline at end of file diff --git a/adobe-esign-connector/src_hd/com/axonivy/connector/adobe/esign/connector/AdminSetup/AdminSetupData.ivyClass b/adobe-esign-connector/src_hd/com/axonivy/connector/adobe/esign/connector/AdminSetup/AdminSetupData.ivyClass new file mode 100644 index 0000000..61bd728 --- /dev/null +++ b/adobe-esign-connector/src_hd/com/axonivy/connector/adobe/esign/connector/AdminSetup/AdminSetupData.ivyClass @@ -0,0 +1,22 @@ +AdminSetupData #class +com.axonivy.connector.adobe.esign.connector.AdminSetup #namespace +host String #field +host PERSISTENT #fieldModifier +baseUri String #field +baseUri PERSISTENT #fieldModifier +authenticationUri String #field +authenticationUri PERSISTENT #fieldModifier +clientId String #field +clientId PERSISTENT #fieldModifier +clientSecret String #field +clientSecret PERSISTENT #fieldModifier +permissions String #field +permissions PERSISTENT #fieldModifier +integrationKey String #field +integrationKey PERSISTENT #fieldModifier +returnPage String #field +returnPage PERSISTENT #fieldModifier +oauthToken String #field +oauthToken PERSISTENT #fieldModifier +accessToken String #field +accessToken PERSISTENT #fieldModifier diff --git a/adobe-esign-connector/src_hd/com/axonivy/connector/adobe/esign/connector/AdminSetup/AdminSetupProcess.p.json b/adobe-esign-connector/src_hd/com/axonivy/connector/adobe/esign/connector/AdminSetup/AdminSetupProcess.p.json new file mode 100644 index 0000000..4a06258 --- /dev/null +++ b/adobe-esign-connector/src_hd/com/axonivy/connector/adobe/esign/connector/AdminSetup/AdminSetupProcess.p.json @@ -0,0 +1,136 @@ +{ + "format" : "10.0.0", + "id" : "18A8364B91F33AB9", + "kind" : "HTML_DIALOG", + "config" : { + "data" : "com.axonivy.connector.adobe.esign.connector.AdminSetup.AdminSetupData" + }, + "elements" : [ { + "id" : "f0", + "type" : "HtmlDialogStart", + "name" : "start()", + "config" : { + "callSignature" : "start", + "guid" : "18A8364B921D66CB" + }, + "visual" : { + "at" : { "x" : 96, "y" : 64 } + }, + "connect" : { "id" : "f2", "to" : "f1" } + }, { + "id" : "f1", + "type" : "HtmlDialogEnd", + "visual" : { + "at" : { "x" : 224, "y" : 64 } + } + }, { + "id" : "f3", + "type" : "HtmlDialogEventStart", + "name" : "save", + "config" : { + "guid" : "18A8364B924568D6" + }, + "visual" : { + "at" : { "x" : 88, "y" : 280 } + }, + "connect" : { "id" : "f5", "to" : "f4" } + }, { + "id" : "f6", + "type" : "HtmlDialogEnd", + "visual" : { + "at" : { "x" : 352, "y" : 280 } + } + }, { + "id" : "f4", + "type" : "Script", + "name" : "update vars", + "config" : { + "security" : "system", + "output" : { + "code" : [ + "import com.axonivy.connector.adobe.esign.connector.service.AdminSetupService;", + "AdminSetupService.updateVars(in);" + ] + } + }, + "visual" : { + "at" : { "x" : 224, "y" : 280 } + }, + "connect" : { "id" : "f7", "to" : "f6" } + }, { + "id" : "f8", + "type" : "HtmlDialogMethodStart", + "name" : "preRender(ComponentSystemEvent)", + "config" : { + "callSignature" : "preRender", + "guid" : "18A8A15BDBC42375", + "input" : { + "params" : [ + { "name" : "event", "type" : "javax.faces.event.ComponentSystemEvent" } + ] + } + }, + "visual" : { + "at" : { "x" : 96, "y" : 152 }, + "labelOffset" : { "x" : 19, "y" : 51 } + }, + "connect" : { "id" : "f12", "to" : "f11" } + }, { + "id" : "f9", + "type" : "HtmlDialogEnd", + "visual" : { + "at" : { "x" : 352, "y" : 152 } + } + }, { + "id" : "f11", + "type" : "Script", + "name" : "init vars", + "config" : { + "security" : "system", + "output" : { + "code" : [ + "import com.axonivy.connector.adobe.esign.connector.service.AdminSetupService;", + "AdminSetupService.initVars(in);" + ] + } + }, + "visual" : { + "at" : { "x" : 224, "y" : 152 } + }, + "connect" : { "id" : "f10", "to" : "f9" } + }, { + "id" : "f13", + "type" : "HtmlDialogEventStart", + "name" : "requestToken", + "config" : { + "guid" : "18A8D57408743430" + }, + "visual" : { + "at" : { "x" : 88, "y" : 392 } + }, + "connect" : { "id" : "f20", "to" : "f18" } + }, { + "id" : "f14", + "type" : "HtmlDialogEnd", + "visual" : { + "at" : { "x" : 352, "y" : 392 } + } + }, { + "id" : "f18", + "type" : "Script", + "name" : "OAuth redirect", + "config" : { + "security" : "system", + "output" : { + "code" : [ + "import com.axonivy.connector.adobe.esign.connector.service.AdminSetupService;", + "AdminSetupService.authRedirect();" + ] + } + }, + "visual" : { + "at" : { "x" : 224, "y" : 392 } + }, + "connect" : { "id" : "f17", "to" : "f14" } + } ] +} \ No newline at end of file diff --git a/adobe-esign-connector/webContent/layouts/frame-10-full-width.xhtml b/adobe-esign-connector/webContent/layouts/frame-10-full-width.xhtml new file mode 100644 index 0000000..768d89e --- /dev/null +++ b/adobe-esign-connector/webContent/layouts/frame-10-full-width.xhtml @@ -0,0 +1,60 @@ + + + + + + + + + + <ui:insert name="title">Ivy Html Dialog</ui:insert> + + + + + + + + + +
+ + default content + +
+ + + + + + + +
+ \ No newline at end of file diff --git a/adobe-esign-connector/webContent/layouts/includes/exception-details.xhtml b/adobe-esign-connector/webContent/layouts/includes/exception-details.xhtml new file mode 100644 index 0000000..a4979dc --- /dev/null +++ b/adobe-esign-connector/webContent/layouts/includes/exception-details.xhtml @@ -0,0 +1,109 @@ + + + + + + +

+ +

+ + +

Error id

+

#{errorPage.exceptionId}

+

Error Timestamp

+

#{errorPage.createdAt}

+
+ + + + +

Attributes

+
+ + + + + + + + + + + + + + + +
NameValue
+
+
+

Thrown by

+

Process: + +
Element: + +

+
+ + +

Process call stack

+ +
#{caller.callerElement}
+
+
+ +

Technical cause

+
#{causedBy.class.simpleName}: #{causedBy.message.trim()}
+
+
+ +

Request Uri

+

#{errorPage.getRequestUri()}

+
+

Servlet

+

#{errorPage.getServletName()}

+
+ +

Application

+

#{errorPage.applicationName}

+
+ + +

Thread local values

+
+ + + + + + + + + + + + + + + +
KeyValue
+
+
+
+ +

Stack-Trace

+
#{errorPage.getStackTrace()}
+
+ diff --git a/adobe-esign-connector/webContent/layouts/includes/exception.xhtml b/adobe-esign-connector/webContent/layouts/includes/exception.xhtml new file mode 100644 index 0000000..2303e7c --- /dev/null +++ b/adobe-esign-connector/webContent/layouts/includes/exception.xhtml @@ -0,0 +1,47 @@ + + + + + + + + + +
+
+ + +
+ + + + + + + + + +
+ + \ No newline at end of file diff --git a/adobe-esign-connector/webContent/layouts/includes/footer.xhtml b/adobe-esign-connector/webContent/layouts/includes/footer.xhtml new file mode 100644 index 0000000..3eb052b --- /dev/null +++ b/adobe-esign-connector/webContent/layouts/includes/footer.xhtml @@ -0,0 +1,18 @@ + + + +
+ + #{ivyAdvisor.applicationName} + + +
+
+ + \ No newline at end of file diff --git a/adobe-esign-connector/webContent/layouts/includes/progress-loader.xhtml b/adobe-esign-connector/webContent/layouts/includes/progress-loader.xhtml new file mode 100644 index 0000000..0d68a75 --- /dev/null +++ b/adobe-esign-connector/webContent/layouts/includes/progress-loader.xhtml @@ -0,0 +1,15 @@ + + + + +
+
+
Loading...
+
+
+ + + +
+
\ No newline at end of file diff --git a/adobe-esign-connector/webContent/signatureReturn.jsp b/adobe-esign-connector/webContent/signatureReturn.jsp new file mode 100644 index 0000000..bfa9899 --- /dev/null +++ b/adobe-esign-connector/webContent/signatureReturn.jsp @@ -0,0 +1,19 @@ + +<%@ page import="ch.ivyteam.ivy.page.engine.jsp.IvyJSP"%> + + + + + + + + +Signature end + + diff --git a/pom.xml b/pom.xml index 517336b..f8b35a4 100644 --- a/pom.xml +++ b/pom.xml @@ -1,9 +1,9 @@ 4.0.0 - com.axonivy.market - my-product - my-product-modules - 10.0.0-SNAPSHOT + com.axonivy.connector.adobe.esign + adobe-esign-connector + adobe-esign-connector-modules + 10.0.11-SNAPSHOT pom