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>