keycloak-aplcache

KEYCLOAK-4163 Improve support for e-mail addresses Added

1/5/2017 3:48:17 PM

Details

diff --git a/services/src/main/java/org/keycloak/email/DefaultEmailSenderProvider.java b/services/src/main/java/org/keycloak/email/DefaultEmailSenderProvider.java
index 311627a..7477d84 100644
--- a/services/src/main/java/org/keycloak/email/DefaultEmailSenderProvider.java
+++ b/services/src/main/java/org/keycloak/email/DefaultEmailSenderProvider.java
@@ -17,6 +17,7 @@
 
 package org.keycloak.email;
 
+import com.sun.mail.smtp.SMTPMessage;
 import org.jboss.logging.Logger;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.RealmModel;
@@ -25,15 +26,17 @@ import org.keycloak.services.ServicesLogger;
 import org.keycloak.truststore.HostnameVerificationPolicy;
 import org.keycloak.truststore.JSSETruststoreConfigurator;
 
+import javax.mail.Address;
 import javax.mail.MessagingException;
 import javax.mail.Multipart;
 import javax.mail.Session;
 import javax.mail.Transport;
+import javax.mail.internet.AddressException;
 import javax.mail.internet.InternetAddress;
 import javax.mail.internet.MimeBodyPart;
-import javax.mail.internet.MimeMessage;
 import javax.mail.internet.MimeMultipart;
 import javax.net.ssl.SSLSocketFactory;
+import java.io.UnsupportedEncodingException;
 import java.security.KeyManagementException;
 import java.security.NoSuchAlgorithmException;
 import java.util.Date;
@@ -91,6 +94,10 @@ public class DefaultEmailSenderProvider implements EmailSenderProvider {
             props.setProperty("mail.smtp.connectiontimeout", "10000");
 
             String from = config.get("from");
+            String fromDisplayName = config.get("fromDisplayName");
+            String replyTo = config.get("replyTo");
+            String replyToDisplayName = config.get("replyToDisplayName");
+            String envelopeFrom = config.get("envelopeFrom");
 
             Session session = Session.getInstance(props);
 
@@ -108,8 +115,17 @@ public class DefaultEmailSenderProvider implements EmailSenderProvider {
                 multipart.addBodyPart(htmlPart);
             }
 
-            MimeMessage msg = new MimeMessage(session);
-            msg.setFrom(new InternetAddress(from));
+            SMTPMessage msg = new SMTPMessage(session);
+            msg.setFrom(toInternetAddress(from, fromDisplayName));
+
+            msg.setReplyTo(new Address[]{toInternetAddress(from, fromDisplayName)});
+            if (replyTo != null) {
+                msg.setReplyTo(new Address[]{toInternetAddress(replyTo, replyToDisplayName)});
+            }
+            if (envelopeFrom != null) {
+                msg.setEnvelopeFrom(envelopeFrom);
+            }
+
             msg.setHeader("To", address);
             msg.setSubject(subject, "utf-8");
             msg.setContent(multipart);
@@ -136,6 +152,13 @@ public class DefaultEmailSenderProvider implements EmailSenderProvider {
             }
         }
     }
+
+    protected InternetAddress toInternetAddress(String email, String displayName) throws UnsupportedEncodingException, AddressException {
+        if (displayName == null || "".equals(displayName.trim())) {
+            return new InternetAddress(email);
+        }
+        return new InternetAddress(email, displayName, "utf-8");
+    }
     
     protected String retrieveEmailAddress(UserModel user) {
         return user.getEmail();
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/actions/RequiredActionEmailVerificationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/actions/RequiredActionEmailVerificationTest.java
index 539267f..eb719d8 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/actions/RequiredActionEmailVerificationTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/actions/RequiredActionEmailVerificationTest.java
@@ -46,7 +46,6 @@ import javax.mail.MessagingException;
 import javax.mail.Multipart;
 import javax.mail.internet.MimeMessage;
 import java.io.IOException;
-import java.util.Collections;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
@@ -98,6 +97,28 @@ public class RequiredActionEmailVerificationTest extends AbstractTestRealmKeyclo
         ApiUtil.createUserAndResetPasswordWithAdminClient(testRealm(), user, "password");
     }
 
+    /**
+     * see KEYCLOAK-4163
+     */
+    @Test
+    public void verifyEmailConfig() throws IOException, MessagingException {
+
+        loginPage.open();
+        loginPage.login("test-user@localhost", "password");
+
+        Assert.assertTrue(verifyEmailPage.isCurrent());
+
+        Assert.assertEquals(1, greenMail.getReceivedMessages().length);
+
+        MimeMessage message = greenMail.getReceivedMessages()[0];
+
+        // see testsuite/integration-arquillian/tests/base/src/test/resources/testrealm.json
+        Assert.assertEquals("<auto+bounces@keycloak.org>", message.getHeader("Return-Path")[0]);
+        // displayname <email@example.org>
+        Assert.assertEquals("Keycloak SSO <auto@keycloak.org>", message.getHeader("From")[0]);
+        Assert.assertEquals("Keycloak no-reply <reply-to@keycloak.org>", message.getHeader("Reply-To")[0]);
+    }
+
     @Test
     public void verifyEmailExisting() throws IOException, MessagingException {
         loginPage.open();
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/testrealm.json b/testsuite/integration-arquillian/tests/base/src/test/resources/testrealm.json
index 6a090ce..edb0f61 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/resources/testrealm.json
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/testrealm.json
@@ -13,7 +13,11 @@
   "smtpServer": {
     "from": "auto@keycloak.org",
     "host": "localhost",
-    "port":"3025"
+    "port":"3025",
+    "fromDisplayName": "Keycloak SSO",
+    "replyTo":"reply-to@keycloak.org",
+    "replyToDisplayName": "Keycloak no-reply",
+    "envelopeFrom": "auto+bounces@keycloak.org"
   },
   "users" : [
     {
diff --git a/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties b/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties
index 0859504..701465d 100644
--- a/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties
+++ b/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties
@@ -55,7 +55,18 @@ smtp-host=SMTP Host
 port=Port
 smtp-port=SMTP Port (defaults to 25)
 from=From
+fromDisplayName=From Display Name
+fromDisplayName.tooltip=A user-friendly name for the 'From' address (optional).
+replyTo=Reply To
+replyToDisplayName=Reply To Display Name
+replyToDisplayName.tooltip=A user-friendly name for the 'Reply-To' address (optional).
+envelopeFrom=Envelope From
+envelopeFrom.tooltip=An email address used for bounces (optional).
 sender-email-addr=Sender Email Address
+sender-email-addr-display=Display Name for Sender Email Address
+reply-to-email-addr=Reply To Email Address
+reply-to-email-addr-display=Display Name for Reply To Email Address
+sender-envelope-email-addr=Sender Envelope Email Address
 enable-ssl=Enable SSL
 enable-start-tls=Enable StartTLS
 enable-auth=Enable Authentication
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/realm-smtp.html b/themes/src/main/resources/theme/base/admin/resources/partials/realm-smtp.html
index 7d778f5..5d3c68e 100755
--- a/themes/src/main/resources/theme/base/admin/resources/partials/realm-smtp.html
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/realm-smtp.html
@@ -18,12 +18,39 @@
             </div>
         </div>
         <div class="form-group clearfix">
+            <label class="col-md-2 control-label" for="smtpFromDisplayName">{{:: 'fromDisplayName' | translate}}</label>
+            <div class="col-md-6">
+                <input class="form-control" id="smtpFromDisplayName" type="text" ng-model="realm.smtpServer.fromDisplayName" placeholder="{{:: 'sender-email-addr-display' | translate}}">
+            </div>
+            <kc-tooltip>{{:: 'fromDisplayName.tooltip' | translate}}</kc-tooltip>
+        </div>
+        <div class="form-group clearfix">
             <label class="col-md-2 control-label" for="smtpFrom"><span class="required">*</span> {{:: 'from' | translate}}</label>
             <div class="col-md-6">
                 <input class="form-control" id="smtpFrom" type="email" ng-model="realm.smtpServer.from" placeholder="{{:: 'sender-email-addr' | translate}}" required>
             </div>
         </div>
         <div class="form-group clearfix">
+            <label class="col-md-2 control-label" for="smtpReplyToDisplayName">{{:: 'replyToDisplayName' | translate}}</label>
+            <div class="col-md-6">
+                <input class="form-control" id="smtpReplyToDisplayName" type="text" ng-model="realm.smtpServer.replyToDisplayName" placeholder="{{:: 'reply-to-email-addr-display' | translate}}">
+            </div>
+            <kc-tooltip>{{:: 'replyToDisplayName.tooltip' | translate}}</kc-tooltip>
+        </div>
+        <div class="form-group clearfix">
+            <label class="col-md-2 control-label" for="smtpReplyTo">{{:: 'replyTo' | translate}}</label>
+            <div class="col-md-6">
+                <input class="form-control" id="smtpReplyTo" type="email" ng-model="realm.smtpServer.replyTo" placeholder="{{:: 'reply-to-email-addr' | translate}}">
+            </div>
+        </div>
+        <div class="form-group clearfix">
+            <label class="col-md-2 control-label" for="smtpEnvelopeFrom">{{:: 'envelopeFrom' | translate}}</label>
+            <div class="col-md-6">
+                <input class="form-control" id="smtpEnvelopeFrom" type="email" ng-model="realm.smtpServer.envelopeFrom" placeholder="{{:: 'sender-envelope-email-addr' | translate}}">
+            </div>
+            <kc-tooltip>{{:: 'envelopeFrom.tooltip' | translate}}</kc-tooltip>
+        </div>
+        <div class="form-group clearfix">
             <label class="col-md-2 control-label" for="smtpSSL">{{:: 'enable-ssl' | translate}}</label>
             <div class="col-md-6">
                 <input ng-model="realm.smtpServer.ssl" name="smtpSSL" id="smtpSSL" onoffswitch on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}"/>