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 ce986d1..14708d8 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}}"/>