You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by ac...@apache.org on 2022/11/07 13:45:16 UTC

[camel] branch main updated: CAMEL-18684: Add Microsoft Exchange Online OAuth2 Mail Authenticator

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

acosentino 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 0e47df20308 CAMEL-18684: Add Microsoft Exchange Online OAuth2 Mail Authenticator
0e47df20308 is described below

commit 0e47df20308d2ec80d408da0022c678fa57c66c4
Author: Luigi De Masi <ld...@redhat.com>
AuthorDate: Thu Nov 3 17:31:11 2022 +0100

    CAMEL-18684: Add Microsoft Exchange Online OAuth2 Mail Authenticator
---
 camel-dependencies/pom.xml                         |  19 +--
 components/camel-mail/pom.xml                      |   6 +
 .../camel-mail/src/main/docs/mail-component.adoc   |  26 +++
 ...osoftExchangeOnlineOAuth2MailAuthenticator.java | 174 +++++++++++++++++++++
 parent/pom.xml                                     |   8 +
 5 files changed, 224 insertions(+), 9 deletions(-)

diff --git a/camel-dependencies/pom.xml b/camel-dependencies/pom.xml
index 31804d922b1..d23560cf7c1 100644
--- a/camel-dependencies/pom.xml
+++ b/camel-dependencies/pom.xml
@@ -80,18 +80,18 @@
     <californium-scandium-version>2.7.3</californium-scandium-version>
     <californium-version>2.7.2</californium-version>
     <camel.failsafe.forkTimeout>600</camel.failsafe.forkTimeout>
-    <camel.osgi.activator></camel.osgi.activator>
-    <camel.osgi.dynamic></camel.osgi.dynamic>
+    <camel.osgi.activator/>
+    <camel.osgi.dynamic/>
     <camel.osgi.exclude.dependencies>false</camel.osgi.exclude.dependencies>
     <camel.osgi.export>${camel.osgi.export.pkg};-noimport:=true;${camel.osgi.version}</camel.osgi.export>
     <camel.osgi.export.pkg>$${replace;{local-packages};;;\;}</camel.osgi.export.pkg>
     <camel.osgi.failok>false</camel.osgi.failok>
     <camel.osgi.import>${camel.osgi.import.pkg}</camel.osgi.import>
-    <camel.osgi.import.additional></camel.osgi.import.additional>
-    <camel.osgi.import.before.defaults></camel.osgi.import.before.defaults>
+    <camel.osgi.import.additional/>
+    <camel.osgi.import.before.defaults/>
     <camel.osgi.import.camel.version>version="[$(version;==;${camel.osgi.version.clean}),$(version;=+;${camel.osgi.version.clean}))"</camel.osgi.import.camel.version>
     <camel.osgi.import.default.version>[$(version;==;$(@)),$(version;+;$(@)))</camel.osgi.import.default.version>
-    <camel.osgi.import.defaults></camel.osgi.import.defaults>
+    <camel.osgi.import.defaults/>
     <camel.osgi.import.pkg>org.apache.camel.*;${camel.osgi.import.camel.version},
             ${camel.osgi.import.before.defaults},
             ${camel.osgi.import.defaults},
@@ -100,8 +100,8 @@
     <camel.osgi.import.strict.version>version="[$(version;===;${camel.osgi.version.clean}),$(version;==+;${camel.osgi.version.clean}))"</camel.osgi.import.strict.version>
     <camel.osgi.manifest>${project.build.outputDirectory}/META-INF/MANIFEST.MF</camel.osgi.manifest>
     <camel.osgi.private.pkg>!*</camel.osgi.private.pkg>
-    <camel.osgi.provide.capability></camel.osgi.provide.capability>
-    <camel.osgi.require.capability></camel.osgi.require.capability>
+    <camel.osgi.provide.capability/>
+    <camel.osgi.require.capability/>
     <camel.osgi.symbolic.name>${project.groupId}.${project.artifactId}</camel.osgi.symbolic.name>
     <camel.osgi.version>version=${project.version}</camel.osgi.version>
     <camel.surefire.fork.vmargs>-XX:+ExitOnOutOfMemoryError</camel.surefire.fork.vmargs>
@@ -155,9 +155,9 @@
     <cxf-version-range>[3.5,3.6)</cxf-version-range>
     <cxf-xjc-plugin-version>3.3.2</cxf-xjc-plugin-version>
     <cxf-xjc-utils-version>3.3.1</cxf-xjc-utils-version>
-    <cxf.codegen.jvmArgs></cxf.codegen.jvmArgs>
+    <cxf.codegen.jvmArgs/>
     <cxf.codegenplugin.forkmode>true</cxf.codegenplugin.forkmode>
-    <cxf.xjc.jvmArgs></cxf.xjc.jvmArgs>
+    <cxf.xjc.jvmArgs/>
     <datasonnet-mapper-version>2.1.4</datasonnet-mapper-version>
     <debezium-mysql-connector-version>8.0.28</debezium-mysql-connector-version>
     <debezium-version>1.9.6.Final</debezium-version>
@@ -420,6 +420,7 @@
     <mockwebserver-version>0.2.2</mockwebserver-version>
     <mongo-hadoop-version>1.5.0</mongo-hadoop-version>
     <mongo-java-driver-version>4.7.2</mongo-java-driver-version>
+    <msal4j-version>1.13.3</msal4j-version>
     <mustache-java-version>0.9.10</mustache-java-version>
     <mvel-version>2.4.14.Final</mvel-version>
     <mybatis-version>3.5.11</mybatis-version>
diff --git a/components/camel-mail/pom.xml b/components/camel-mail/pom.xml
index 25e49ca9cea..d3cda7bcc9b 100644
--- a/components/camel-mail/pom.xml
+++ b/components/camel-mail/pom.xml
@@ -59,6 +59,12 @@
             </exclusions>
         </dependency>
 
+        <!--Microsoft Authentication Library for Java  -->
+        <dependency>
+            <groupId>com.microsoft.azure</groupId>
+            <artifactId>msal4j</artifactId>
+        </dependency>
+
         <!-- testing -->
         <dependency>
             <groupId>org.apache.camel</groupId>
diff --git a/components/camel-mail/src/main/docs/mail-component.adoc b/components/camel-mail/src/main/docs/mail-component.adoc
index d0f5fcfd9b2..b1cd4b23f13 100644
--- a/components/camel-mail/src/main/docs/mail-component.adoc
+++ b/components/camel-mail/src/main/docs/mail-component.adoc
@@ -445,6 +445,32 @@ As you can see the API to handle attachments is a bit clunky but it's
 there so you can get the `javax.activation.DataHandler` so you can
 handle the attachments using standard API.
 
+== Microsoft Exchange Online OAuth2 Mail Authenticator sample
+To use OAuth, an application must be registered with Azure Active Directory.
+Follow the instructions listed in https://learn.microsoft.com/en-us/azure/active-directory/develop/quickstart-register-app[Register an application with the Microsoft identity platform] guide to register a new application. +
+Enable application to access Exchange mailboxes via client credentials flow. Instructions https://learn.microsoft.com/en-us/exchange/client-developer/legacy-protocols/how-to-authenticate-an-imap-pop-smtp-application-by-using-oauth[here] +
+Once everything is set up, declare and register in the registry, an instance of `+org.apache.camel.component.mail.MicrosoftExchangeOnlineOAuth2MailAuthenticator+`. +
+For Example in a Spring Boot application:
+
+[source,java]
+---------------------------------------------------------------------------------
+@Bean("auth")
+public MicrosoftExchangeOnlineOAuth2MailAuthenticator exchangeAuthenticator(){
+    return new MicrosoftExchangeOnlineOAuth2MailAuthenticator(tenantId, clientId, clientSecret, "jon@doe.com");
+}
+---------------------------------------------------------------------------------
+
+and then reference it in the Camel URI:
+
+[source,java]
+---------------------------------------------------------------------------------------
+ from("imaps://outlook.office365.com:993"
+                    +  "?authenticator=#auth"
+                    +  "&mail.imaps.auth.mechanisms=XOAUTH2"
+                    +  "&debugMode=true"
+                    +  "&delete=false")
+---------------------------------------------------------------------------------------
+
 == How to split a mail message with attachments
 
 In this example we consume mail messages which may have a number of
diff --git a/components/camel-mail/src/main/java/org/apache/camel/component/mail/MicrosoftExchangeOnlineOAuth2MailAuthenticator.java b/components/camel-mail/src/main/java/org/apache/camel/component/mail/MicrosoftExchangeOnlineOAuth2MailAuthenticator.java
new file mode 100644
index 00000000000..6aaded69117
--- /dev/null
+++ b/components/camel-mail/src/main/java/org/apache/camel/component/mail/MicrosoftExchangeOnlineOAuth2MailAuthenticator.java
@@ -0,0 +1,174 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.component.mail;
+
+import java.net.MalformedURLException;
+import java.util.Collections;
+import java.util.Set;
+
+import javax.mail.PasswordAuthentication;
+
+import com.microsoft.aad.msal4j.ClientCredentialFactory;
+import com.microsoft.aad.msal4j.ClientCredentialParameters;
+import com.microsoft.aad.msal4j.ConfidentialClientApplication;
+import com.microsoft.aad.msal4j.IAuthenticationResult;
+import com.microsoft.aad.msal4j.IClientCredential;
+import com.microsoft.aad.msal4j.IConfidentialClientApplication;
+
+public class MicrosoftExchangeOnlineOAuth2MailAuthenticator extends MailAuthenticator {
+
+    /**
+     * The default scope application is requesting access to.
+     */
+    private static final Set<String> DEFAULT_SCOPES = Collections.singleton("https://outlook.office365.com/.default");
+
+    /**
+     * The authenticating authority base URL.
+     */
+    private static final String AUTHORITY_BASE_URL = "https://login.microsoftonline.com/";
+
+    /**
+     * Client ID (Application ID) of the application as registered in the application registration portal
+     * (portal.azure.com)
+     */
+    private String clientId;
+
+    /**
+     * secret of application requesting a token
+     */
+    private String clientSecret;
+
+    /**
+     * The authenticating authority or security token service (STS) from which will acquire security tokens.
+     */
+    private String authority;
+
+    /**
+     * The username for login.
+     */
+    private final String user;
+
+    /**
+     * Indicates whether the request should skip looking into the token cache. Be default it is set to false.
+     */
+    private Boolean skipCache;
+
+    /**
+     * The scopes application is requesting access to.
+     */
+    private Set<String> scopes;
+
+    /**
+     * Object containing parameters for client credential flow.
+     */
+    private final ClientCredentialParameters clientCredentialParameters;
+
+    /**
+     * Object used to acquire tokens for confidential client applications (Web Apps, Web APIs, and daemon applications.
+     * Confidential client applications are trusted to safely store application secrets, and therefore can be used to
+     * acquire tokens in then name of either the application or an user. For details see
+     * https://aka.ms/msal4jclientapplications
+     */
+    private IConfidentialClientApplication confidentialClientApplication;
+
+    public MicrosoftExchangeOnlineOAuth2MailAuthenticator(String tenantId, String clientId, String clientSecret, String user) {
+        this(tenantId, clientId, clientSecret, user, false, null, null);
+    }
+
+    public MicrosoftExchangeOnlineOAuth2MailAuthenticator(String tenantId, String clientId, String clientSecret, String user,
+                                                          Boolean skipCache) {
+        this(tenantId, clientId, clientSecret, user, skipCache, null, null);
+    }
+
+    public MicrosoftExchangeOnlineOAuth2MailAuthenticator(String tenantId, String clientId, String clientSecret, String user,
+                                                          Boolean skipCache, Set<String> scopes) {
+        this(tenantId, clientId, clientSecret, user, skipCache, scopes, null);
+    }
+
+    public MicrosoftExchangeOnlineOAuth2MailAuthenticator(String tenantId, String clientId, String clientSecret, String user,
+                                                          ClientCredentialParameters clientCredentialParameters) {
+        this(tenantId, clientId, clientSecret, user, null, null, clientCredentialParameters);
+    }
+
+    public MicrosoftExchangeOnlineOAuth2MailAuthenticator(String user,
+                                                          IConfidentialClientApplication confidentialClientApplication,
+                                                          ClientCredentialParameters clientCredentialParameters) {
+        this.user = user;
+        this.confidentialClientApplication = confidentialClientApplication;
+        this.clientCredentialParameters = clientCredentialParameters;
+    }
+
+    private MicrosoftExchangeOnlineOAuth2MailAuthenticator(String tenantId, String clientId, String clientSecret, String user,
+                                                           Boolean skipCache, Set<String> scopes,
+                                                           ClientCredentialParameters parametes) {
+        this.clientId = clientId;
+        this.clientSecret = clientSecret;
+        this.authority = AUTHORITY_BASE_URL + tenantId;
+        this.skipCache = skipCache;
+        this.user = user;
+        this.clientCredentialParameters = parametes;
+
+        if (scopes == null || scopes.isEmpty()) {
+            this.scopes = DEFAULT_SCOPES;
+        } else {
+            this.scopes = scopes;
+        }
+    }
+
+    @Override
+    public PasswordAuthentication getPasswordAuthentication() {
+        IAuthenticationResult result = getClientCredential().acquireToken(getClientCredentialParameters()).join();
+        return new PasswordAuthentication(user, result.accessToken());
+    }
+
+    private IConfidentialClientApplication getClientCredential() {
+
+        if (confidentialClientApplication != null) {
+            return confidentialClientApplication;
+        }
+
+        try {
+            // This is the secret that is created in the Azure portal when registering the application
+            IClientCredential credential = ClientCredentialFactory.createFromSecret(clientSecret);
+
+            confidentialClientApplication = ConfidentialClientApplication
+                    .builder(clientId, credential)
+                    .authority(authority)
+                    .build();
+        } catch (MalformedURLException e) {
+            throw new RuntimeException(e);
+        }
+
+        return confidentialClientApplication;
+    }
+
+    private ClientCredentialParameters getClientCredentialParameters() {
+        if (clientCredentialParameters != null) {
+            return clientCredentialParameters;
+        }
+
+        // Client credential requests will by default try to look for a valid token in the
+        // in-memory token cache. If found, it will return this token. If a token is not found, or the
+        // token is not valid, it will fall back to acquiring a token from the AAD service. Although
+        // not recommended unless there is a reason for doing so, you can skip the cache lookup
+        // by setting skipCache to true.
+        return ClientCredentialParameters
+                .builder(scopes)
+                .skipCache(skipCache)
+                .build();
+    }
+}
diff --git a/parent/pom.xml b/parent/pom.xml
index 3129da556f2..98afe3677f1 100644
--- a/parent/pom.xml
+++ b/parent/pom.xml
@@ -406,6 +406,7 @@
         <mockito-version>3.12.4</mockito-version>
         <mongo-java-driver-version>4.7.2</mongo-java-driver-version>
         <mongo-hadoop-version>1.5.0</mongo-hadoop-version>
+        <msal4j-version>1.13.3</msal4j-version>
         <mustache-java-version>0.9.10</mustache-java-version>
         <mvel-version>2.4.14.Final</mvel-version>
         <mybatis-version>3.5.11</mybatis-version>
@@ -3604,6 +3605,13 @@
                 <version>${mina-version}</version>
             </dependency>
 
+            <!-- Optional Microsoft Authentication Library for Java  -->
+            <dependency>
+                <groupId>com.microsoft.azure</groupId>
+                <artifactId>msal4j</artifactId>
+                <version>${msal4j-version}</version>
+            </dependency>
+
             <dependency>
                 <groupId>org.slf4j</groupId>
                 <artifactId>slf4j-simple</artifactId>