This is an automated email from the ASF dual-hosted git repository.

davsclaus pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git


The following commit(s) were added to refs/heads/main by this push:
     new 95b4c615f81 CAMEL-20118: Support JWT Authentication for Camel 
Salesforce Maven Plugin (#17329)
95b4c615f81 is described below

commit 95b4c615f817cc754c001495793d3a191bf30fd6
Author: Kai Grassnick <7880861+kaigrassn...@users.noreply.github.com>
AuthorDate: Mon Mar 3 12:46:07 2025 +0100

    CAMEL-20118: Support JWT Authentication for Camel Salesforce Maven Plugin 
(#17329)
    
    * CAMEL-20118: Support JWT Authentication for Camel Salesforce Maven Plugin
    
    * adjust MojoExecutionException to reflect property names
    
    ---------
    
    Co-authored-by: Kai Grassnick <kgrassn...@integrationmatters.com>
---
 .../codegen/AbstractSalesforceExecution.java       | 32 ++++++++-
 .../camel-salesforce-maven-plugin/README.md        | 77 ++++++++++++++++++--
 .../apache/camel/maven/AbstractSalesforceMojo.java | 82 ++++++++++++++++++++++
 .../camel/maven/AbstractSalesforceMojoTest.java    | 54 ++++++++++++++
 4 files changed, 240 insertions(+), 5 deletions(-)

diff --git 
a/components/camel-salesforce/camel-salesforce-codegen/src/main/java/org/apache/camel/component/salesforce/codegen/AbstractSalesforceExecution.java
 
b/components/camel-salesforce/camel-salesforce-codegen/src/main/java/org/apache/camel/component/salesforce/codegen/AbstractSalesforceExecution.java
index 015b0cb3a3e..d770ba9fa66 100644
--- 
a/components/camel-salesforce/camel-salesforce-codegen/src/main/java/org/apache/camel/component/salesforce/codegen/AbstractSalesforceExecution.java
+++ 
b/components/camel-salesforce/camel-salesforce-codegen/src/main/java/org/apache/camel/component/salesforce/codegen/AbstractSalesforceExecution.java
@@ -34,6 +34,7 @@ import 
org.apache.camel.component.salesforce.internal.client.PubSubApiClient;
 import org.apache.camel.component.salesforce.internal.client.RestClient;
 import org.apache.camel.impl.DefaultCamelContext;
 import org.apache.camel.support.PropertyBindingSupport;
+import org.apache.camel.support.jsse.KeyStoreParameters;
 import org.apache.camel.support.jsse.SSLContextParameters;
 import org.apache.camel.support.service.ServiceHelper;
 import org.apache.camel.util.StringHelper;
@@ -130,6 +131,16 @@ public abstract class AbstractSalesforceExecution {
      */
     String loginUrl;
 
+    /**
+     * Salesforce JWT Audience.
+     */
+    String jwtAudience;
+
+    /**
+     * Salesforce KeystoreParameters.
+     */
+    KeyStoreParameters keyStoreParameters;
+
     /**
      * Salesforce password.
      */
@@ -294,7 +305,7 @@ public abstract class AbstractSalesforceExecution {
         // set session before calling start()
         final SalesforceSession session = new SalesforceSession(
                 new DefaultCamelContext(), httpClient, httpClient.getTimeout(),
-                new SalesforceLoginConfig(loginUrl, clientId, clientSecret, 
userName, password, false));
+                getSalesforceLoginSession());
         httpClient.setSession(session);
 
         try {
@@ -306,6 +317,17 @@ public abstract class AbstractSalesforceExecution {
         return httpClient;
     }
 
+    private SalesforceLoginConfig getSalesforceLoginSession() {
+        if (keyStoreParameters != null) {
+            SalesforceLoginConfig salesforceLoginConfig
+                    = new SalesforceLoginConfig(loginUrl, clientId, userName, 
keyStoreParameters, false);
+            salesforceLoginConfig.setJwtAudience(jwtAudience);
+
+            return salesforceLoginConfig;
+        }
+        return new SalesforceLoginConfig(loginUrl, clientId, clientSecret, 
userName, password, false);
+    }
+
     private void disconnectFromSalesforce(final RestClient restClient) {
         if (restClient == null) {
             return;
@@ -383,6 +405,14 @@ public abstract class AbstractSalesforceExecution {
         this.password = password;
     }
 
+    public void setJwtAudience(String jwtAudience) {
+        this.jwtAudience = jwtAudience;
+    }
+
+    public void setKeyStoreParameters(KeyStoreParameters keyStoreParameters) {
+        this.keyStoreParameters = keyStoreParameters;
+    }
+
     public void setUserName(String userName) {
         this.userName = userName;
     }
diff --git 
a/components/camel-salesforce/camel-salesforce-maven-plugin/README.md 
b/components/camel-salesforce/camel-salesforce-maven-plugin/README.md
index 30de0b5c3e4..d06a2c1ea8a 100644
--- a/components/camel-salesforce/camel-salesforce-maven-plugin/README.md
+++ b/components/camel-salesforce/camel-salesforce-maven-plugin/README.md
@@ -14,8 +14,12 @@ The plugin configuration has the following properties.
 
 * clientId - Salesforce client Id for Remote API access
 * clientSecret - Salesforce client secret for Remote API access
-* userName - Salesforce account user name
+* userName - Salesforce account username
 * password - Salesforce account password (including secret token)
+* jwtAudience - Salesforce JWT audience (defaults to 
"https://login.salesforce.com";)
+* keystoreResource - Path to keystore file for JWT authentication
+* keystorePassword - Password for keystore file
+* keystoreType - Type of keystore file (defaults to "JKS")
 * loginUrl - Salesforce loginUrl (defaults to "https://login.salesforce.com";)
 * version - Salesforce Rest API version, defaults to 25.0
 * outputDirectory - Directory where to place generated DTOs, defaults to 
${project.build.directory}/generated-sources/camel-salesforce
@@ -41,7 +45,7 @@ Property name format: `SObject.FieldName.PicklistValue`. 
Property value is the d
     </enumerationOverrideProperties>
     ```
 
-Additonal properties to provide proxy information, if behind a firewall.
+Additional properties to provide proxy information, if behind a firewall.
 
 * httpProxyHost
 * httpProxyPort
@@ -53,7 +57,10 @@ Additonal properties to provide proxy information, if behind 
a firewall.
 * httpProxyIncludedAddresses
 * httpProxyExcludedAddresses
 
-Sample pom.xml
+There are two authentication methods supported by the plugin: 
Username-Password and JWT.
+The plugin will use the Username-Password method if the `clientSecret` is 
specified and will use the JWT method if the `keystoreResource` is specified.
+
+Sample pom.xml using Username-Password authentication:
 ```
 <?xml version="1.0" encoding="UTF-8"?>
 <project xmlns="http://maven.apache.org/POM/4.0.0"; 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
@@ -104,13 +111,75 @@ Sample pom.xml
        </build>
 
 </project>
-
 ```
 For obvious security reasons it is recommended that the clientId, 
clientSecret, userName and password fields be not set in the pom.xml. 
 The plugin should be configured for the rest of the properties, and can be 
executed using the following command:
 
        mvn camel-salesforce:generate -DcamelSalesforce.clientId=<clientid> 
-DcamelSalesforce.clientSecret=<clientsecret> 
-DcamelSalesforce.userName=<username> -DcamelSalesforce.password=<password>
 
+Sample pom.xml using JWT authentication:
+```
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"; 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+       xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
http://maven.apache.org/maven-v4_0_0.xsd";>
+
+       <properties>
+           
+               
<camelSalesforce.clientId>5MVG9uudbyLbNPZOFutIHJpIb2nchnCiNE_NqeYcewMCPPT8_6VV_LQF_CJ813456GxzhxZdxlGwbYI_yzHmz</camelSalesforce.clientId>
+               
<camelSalesforce.userName>f...@bar.com</camelSalesforce.userName>
+               
<camelSalesforce.keystore.resource>src/main/resources/salesforce.jks</camelSalesforce.keystore.resource>
+               
<camelSalesforce.keystore.password>foopasswordCbe5V27JxD0JXYFGJIdIEWB7p</camelSalesforce.keystore.password>
+               
<camelSalesforce.keystore.type>JKS</camelSalesforce.keystore.type>
+               
+               
<camelSalesforce.jwtAudience>https://login.salesforce.com</camelSalesforce.jwtAudience>
+               
+               
<camelSalesforce.loginUrl>https://myDomain.my.salesforce.com</camelSalesforce.loginUrl>
 
+               
+               
<camelSalesforce.httpProxyHost>foo.bar.com</camelSalesforce.httpProxyHost>
+               
<camelSalesforce.httpProxyPort>8090</camelSalesforce.httpProxyPort>
+               
+       </properties>
+
+       ...
+       
+       <build>
+               ...
+               <plugins>
+                       ...
+                       
+                       <!-- camel maven saleforce for creating salesforce 
objects -->
+                       <plugin>
+                               <groupId>org.apache.camel.maven</groupId>
+                               
<artifactId>camel-salesforce-maven-plugin</artifactId>
+                               <version>2.17.1</version>
+                               <configuration>
+                                       
<clientId>${camelSalesforce.clientId}</clientId>
+                                       
<userName>${camelSalesforce.userName}</userName>
+                                       
<keystoreResource>${camelSalesforce.keystore.resource}</keystoreResource>
+                                       
<keystorePassword>${camelSalesforce.keystore.password}</keystorePassword>
+                                       <keystoreType 
default-value="JKS">${camelSalesforce.keystore.type}</keystoreType>
+                                       <jwtAudience 
default-value="https://login.salesforce.com";>${camelSalesforce.jwtAudience}</jwtAudience>
+                                       <loginUrl 
default-value="https://login.salesforce.com";>${camelSalesforce.loginUrl}</loginUrl>
 
+                                       <includes>
+                                               <include>Account</include>
+                                               <include>Contacts</include>
+                                       </includes>
+                                       
<httpProxyHost>${camelSalesforce.httpProxyHost}</httpProxyHost>
+                                       
<httpProxyPort>${camelSalesforce.httpProxyPort}</httpProxyPort>
+                               </configuration>
+                       </plugin>
+
+               </plugins>
+       </build>
+
+</project>
+```
+For obvious security reasons it is recommended that the clientId, userName, 
keystoreResource, keystorePassword, keystoreType and jwtAudience fields be not 
set in the pom.xml. 
+The plugin should be configured for the rest of the properties, and can be 
executed using the following command:
+
+       mvn camel-salesforce:generate -DcamelSalesforce.clientId=<clientid> 
-DcamelSalesforce.userName=<username> 
-DcamelSalesforce.keystore.resource=<keystoreResource> 
-DcamelSalesforce.keystore.password=<keystorePassword> 
-DcamelSalesforce.keystore.type=<keystoreType> 
-DcamelSalesforce.jwtAudience=<jwtAudience>
+
+
 The generated DTOs use Jackson. All Salesforce field types are supported. Date 
and time fields are mapped to java.time.ZonedDateTime, and picklist fields are 
mapped to generated Java Enumerations.
 
 Relationship fields, e.g. `Contact.Account`, will be strongly typed if the 
referenced SObject type is listed in `includes`. Otherwise, the type of the 
reference object will be `AbstractDescribedSObjectBase`. Some useful but 
non-obvious SObjects to include are `RecordType`, `User`, `Group`, and `Name`.  
diff --git 
a/components/camel-salesforce/camel-salesforce-maven-plugin/src/main/java/org/apache/camel/maven/AbstractSalesforceMojo.java
 
b/components/camel-salesforce/camel-salesforce-maven-plugin/src/main/java/org/apache/camel/maven/AbstractSalesforceMojo.java
index 7c9b8d9fabd..b94b06154c2 100644
--- 
a/components/camel-salesforce/camel-salesforce-maven-plugin/src/main/java/org/apache/camel/maven/AbstractSalesforceMojo.java
+++ 
b/components/camel-salesforce/camel-salesforce-maven-plugin/src/main/java/org/apache/camel/maven/AbstractSalesforceMojo.java
@@ -16,12 +16,18 @@
  */
 package org.apache.camel.maven;
 
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.GeneralSecurityException;
+import java.security.KeyStore;
 import java.util.Map;
 import java.util.Set;
 
 import org.apache.camel.component.salesforce.SalesforceEndpointConfig;
 import org.apache.camel.component.salesforce.SalesforceLoginConfig;
 import 
org.apache.camel.component.salesforce.codegen.AbstractSalesforceExecution;
+import org.apache.camel.support.jsse.KeyStoreParameters;
 import org.apache.camel.support.jsse.SSLContextParameters;
 import org.apache.maven.plugin.AbstractMojo;
 import org.apache.maven.plugin.MojoExecutionException;
@@ -141,6 +147,30 @@ public abstract class AbstractSalesforceMojo extends 
AbstractMojo {
     @Parameter(property = "camelSalesforce.userName", required = true)
     String userName;
 
+    /**
+     * Salesforce JWT Audience.
+     */
+    @Parameter(property = "camelSalesforce.jwtAudience", defaultValue = 
"https://login.salesforce.com";)
+    String jwtAudience;
+
+    /**
+     * Salesforce Keystore Path.
+     */
+    @Parameter(property = "camelSalesforce.keystore.resource")
+    String keystoreResource;
+
+    /**
+     * Salesforce Keystore Password.
+     */
+    @Parameter(property = "camelSalesforce.keystore.password")
+    String keystorePassword;
+
+    /**
+     * Salesforce Keystore Type.
+     */
+    @Parameter(property = "camelSalesforce.keystore.type", defaultValue = 
"jks")
+    String keystoreType;
+
     /**
      * Salesforce API version.
      */
@@ -152,6 +182,7 @@ public abstract class AbstractSalesforceMojo extends 
AbstractMojo {
     @Override
     public final void execute() throws MojoExecutionException, 
MojoFailureException {
         try {
+            validateAuthenticationParameters();
             setup();
             execution.execute();
         } catch (Exception e) {
@@ -184,5 +215,56 @@ public abstract class AbstractSalesforceMojo extends 
AbstractMojo {
         execution.setPassword(password);
         execution.setVersion(version);
         execution.setSslContextParameters(sslContextParameters);
+        execution.setJwtAudience(jwtAudience);
+        execution.setKeyStoreParameters(generateKeyStoreParameters());
+    }
+
+    private void validateAuthenticationParameters() throws 
MojoExecutionException {
+        if (clientSecret == null && keystoreResource == null) {
+            throw new MojoExecutionException(
+                    "Either property: clientSecret or property: 
keystoreResource must be provided.");
+        } else if (clientSecret != null && keystoreResource != null) {
+            throw new MojoExecutionException(
+                    "Property: clientSecret or property: keystoreResource must 
be provided.");
+        }
+
+        if (clientSecret != null) {
+            if (password == null) {
+                throw new MojoExecutionException(
+                        generateRequiredErrorMessage("password", 
"clientSecret"));
+            }
+        }
+
+        if (keystoreResource != null) {
+            if (keystorePassword == null) {
+                throw new MojoExecutionException(
+                        generateRequiredErrorMessage("keystorePassword", 
"keystoreResource"));
+            }
+        }
+    }
+
+    private String generateRequiredErrorMessage(String parameter1, String 
parameter2) {
+        return String.format("Property: %s must be provided when property: %s 
was provided.", parameter1, parameter2);
+    }
+
+    private KeyStoreParameters generateKeyStoreParameters() {
+        if (keystoreResource == null) {
+            return null;
+        }
+
+        KeyStoreParameters keyStoreParameters = new KeyStoreParameters();
+        keyStoreParameters.setResource(keystoreResource);
+        keyStoreParameters.setPassword(keystorePassword);
+        keyStoreParameters.setType(keystoreType);
+
+        try (InputStream is = new FileInputStream(keystoreResource)) {
+            KeyStore ks = KeyStore.getInstance(keystoreType);
+            ks.load(is, keystorePassword.toCharArray());
+            keyStoreParameters.setKeyStore(ks);
+        } catch (IOException | GeneralSecurityException e) {
+            throw new RuntimeException(e);
+        }
+
+        return keyStoreParameters;
     }
 }
diff --git 
a/components/camel-salesforce/camel-salesforce-maven-plugin/src/test/java/org/apache/camel/maven/AbstractSalesforceMojoTest.java
 
b/components/camel-salesforce/camel-salesforce-maven-plugin/src/test/java/org/apache/camel/maven/AbstractSalesforceMojoTest.java
index f65ae5433df..f0f177af315 100644
--- 
a/components/camel-salesforce/camel-salesforce-maven-plugin/src/test/java/org/apache/camel/maven/AbstractSalesforceMojoTest.java
+++ 
b/components/camel-salesforce/camel-salesforce-maven-plugin/src/test/java/org/apache/camel/maven/AbstractSalesforceMojoTest.java
@@ -71,6 +71,36 @@ public abstract class AbstractSalesforceMojoTest {
         mojo.execute();
     }
 
+    @Test
+    public void shouldLoginWithJwtAndProvideRestClient() throws IOException, 
MojoExecutionException, MojoFailureException {
+        final AbstractSalesforceMojo mojo = new AbstractSalesforceMojo() {
+            final Logger logger = 
LoggerFactory.getLogger(AbstractSalesforceExecution.class.getName());
+
+            @Override
+            protected AbstractSalesforceExecution getSalesforceExecution() {
+                return new AbstractSalesforceExecution() {
+                    @Override
+                    protected void executeWithClient() {
+                        assertThat(getRestClient()).isNotNull();
+
+                        getRestClient().getGlobalObjects(NO_HEADERS, 
(response, headers, exception) -> {
+                            assertThat(exception).isNull();
+                        });
+                    }
+
+                    @Override
+                    protected Logger getLog() {
+                        return logger;
+                    }
+                };
+            }
+        };
+
+        setupJwt(mojo);
+
+        mojo.execute();
+    }
+
     static void setup(final AbstractSalesforceMojo mojo) throws IOException {
         // load test-salesforce-login properties
         try (final InputStream stream = new 
FileInputStream(TEST_LOGIN_PROPERTIES)) {
@@ -93,4 +123,28 @@ public abstract class AbstractSalesforceMojoTest {
             throw exception;
         }
     }
+
+    static void setupJwt(final AbstractSalesforceMojo mojo) throws IOException 
{
+        // load test-salesforce-login properties
+        try (final InputStream stream = new 
FileInputStream(TEST_LOGIN_PROPERTIES)) {
+            final Properties properties = new Properties();
+            properties.load(stream);
+            mojo.clientId = properties.getProperty("salesforce.client.id");
+            mojo.userName = properties.getProperty("salesforce.username");
+            mojo.loginUrl = properties.getProperty("salesforce.login.url");
+            mojo.keystoreResource = 
properties.getProperty("salesforce.keystore.resource");
+            mojo.keystorePassword = 
properties.getProperty("salesforce.keystore.password");
+            mojo.keystoreType = 
properties.getProperty("salesforce.keystore.type");
+            mojo.version = SalesforceEndpointConfig.DEFAULT_VERSION;
+        } catch (final FileNotFoundException e) {
+            final FileNotFoundException exception
+                    = new FileNotFoundException(
+                            "Create a properties file named " + 
TEST_LOGIN_PROPERTIES
+                                                + " with clientId, userName, 
keyStoreResource, keyStorePassword, keyStoreType"
+                                                + " for a Salesforce account 
with Merchandise and Invoice objects from Salesforce Guides.");
+            exception.initCause(e);
+
+            throw exception;
+        }
+    }
 }

Reply via email to