Merge pull request #3718 from thomasdarimont/issue/KEYCLOAK-4163-improve-support-for-email-addresses
KEYCLOAK-4163 Improve support for e-mail addresses
This commit is contained in:
commit
af4c74f1d9
5 changed files with 91 additions and 5 deletions
|
@ -17,6 +17,7 @@
|
||||||
|
|
||||||
package org.keycloak.email;
|
package org.keycloak.email;
|
||||||
|
|
||||||
|
import com.sun.mail.smtp.SMTPMessage;
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
|
@ -25,15 +26,17 @@ import org.keycloak.services.ServicesLogger;
|
||||||
import org.keycloak.truststore.HostnameVerificationPolicy;
|
import org.keycloak.truststore.HostnameVerificationPolicy;
|
||||||
import org.keycloak.truststore.JSSETruststoreConfigurator;
|
import org.keycloak.truststore.JSSETruststoreConfigurator;
|
||||||
|
|
||||||
|
import javax.mail.Address;
|
||||||
import javax.mail.MessagingException;
|
import javax.mail.MessagingException;
|
||||||
import javax.mail.Multipart;
|
import javax.mail.Multipart;
|
||||||
import javax.mail.Session;
|
import javax.mail.Session;
|
||||||
import javax.mail.Transport;
|
import javax.mail.Transport;
|
||||||
|
import javax.mail.internet.AddressException;
|
||||||
import javax.mail.internet.InternetAddress;
|
import javax.mail.internet.InternetAddress;
|
||||||
import javax.mail.internet.MimeBodyPart;
|
import javax.mail.internet.MimeBodyPart;
|
||||||
import javax.mail.internet.MimeMessage;
|
|
||||||
import javax.mail.internet.MimeMultipart;
|
import javax.mail.internet.MimeMultipart;
|
||||||
import javax.net.ssl.SSLSocketFactory;
|
import javax.net.ssl.SSLSocketFactory;
|
||||||
|
import java.io.UnsupportedEncodingException;
|
||||||
import java.security.KeyManagementException;
|
import java.security.KeyManagementException;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
@ -91,6 +94,10 @@ public class DefaultEmailSenderProvider implements EmailSenderProvider {
|
||||||
props.setProperty("mail.smtp.connectiontimeout", "10000");
|
props.setProperty("mail.smtp.connectiontimeout", "10000");
|
||||||
|
|
||||||
String from = config.get("from");
|
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);
|
Session session = Session.getInstance(props);
|
||||||
|
|
||||||
|
@ -108,8 +115,17 @@ public class DefaultEmailSenderProvider implements EmailSenderProvider {
|
||||||
multipart.addBodyPart(htmlPart);
|
multipart.addBodyPart(htmlPart);
|
||||||
}
|
}
|
||||||
|
|
||||||
MimeMessage msg = new MimeMessage(session);
|
SMTPMessage msg = new SMTPMessage(session);
|
||||||
msg.setFrom(new InternetAddress(from));
|
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.setHeader("To", address);
|
||||||
msg.setSubject(subject, "utf-8");
|
msg.setSubject(subject, "utf-8");
|
||||||
msg.setContent(multipart);
|
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) {
|
protected String retrieveEmailAddress(UserModel user) {
|
||||||
return user.getEmail();
|
return user.getEmail();
|
||||||
|
|
|
@ -46,7 +46,6 @@ import javax.mail.MessagingException;
|
||||||
import javax.mail.Multipart;
|
import javax.mail.Multipart;
|
||||||
import javax.mail.internet.MimeMessage;
|
import javax.mail.internet.MimeMessage;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Collections;
|
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
|
@ -98,6 +97,28 @@ public class RequiredActionEmailVerificationTest extends AbstractTestRealmKeyclo
|
||||||
ApiUtil.createUserAndResetPasswordWithAdminClient(testRealm(), user, "password");
|
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
|
@Test
|
||||||
public void verifyEmailExisting() throws IOException, MessagingException {
|
public void verifyEmailExisting() throws IOException, MessagingException {
|
||||||
loginPage.open();
|
loginPage.open();
|
||||||
|
|
|
@ -13,7 +13,11 @@
|
||||||
"smtpServer": {
|
"smtpServer": {
|
||||||
"from": "auto@keycloak.org",
|
"from": "auto@keycloak.org",
|
||||||
"host": "localhost",
|
"host": "localhost",
|
||||||
"port":"3025"
|
"port":"3025",
|
||||||
|
"fromDisplayName": "Keycloak SSO",
|
||||||
|
"replyTo":"reply-to@keycloak.org",
|
||||||
|
"replyToDisplayName": "Keycloak no-reply",
|
||||||
|
"envelopeFrom": "auto+bounces@keycloak.org"
|
||||||
},
|
},
|
||||||
"users" : [
|
"users" : [
|
||||||
{
|
{
|
||||||
|
|
|
@ -55,7 +55,18 @@ smtp-host=SMTP Host
|
||||||
port=Port
|
port=Port
|
||||||
smtp-port=SMTP Port (defaults to 25)
|
smtp-port=SMTP Port (defaults to 25)
|
||||||
from=From
|
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=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-ssl=Enable SSL
|
||||||
enable-start-tls=Enable StartTLS
|
enable-start-tls=Enable StartTLS
|
||||||
enable-auth=Enable Authentication
|
enable-auth=Enable Authentication
|
||||||
|
|
|
@ -17,12 +17,39 @@
|
||||||
<input class="form-control" id="smtpPort" type="number" ng-model="realm.smtpServer.port" placeholder="{{:: 'smtp-port' | translate}}">
|
<input class="form-control" id="smtpPort" type="number" ng-model="realm.smtpServer.port" placeholder="{{:: 'smtp-port' | translate}}">
|
||||||
</div>
|
</div>
|
||||||
</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">
|
<div class="form-group clearfix">
|
||||||
<label class="col-md-2 control-label" for="smtpFrom"><span class="required">*</span> {{:: 'from' | translate}}</label>
|
<label class="col-md-2 control-label" for="smtpFrom"><span class="required">*</span> {{:: 'from' | translate}}</label>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<input class="form-control" id="smtpFrom" type="email" ng-model="realm.smtpServer.from" placeholder="{{:: 'sender-email-addr' | translate}}" required>
|
<input class="form-control" id="smtpFrom" type="email" ng-model="realm.smtpServer.from" placeholder="{{:: 'sender-email-addr' | translate}}" required>
|
||||||
</div>
|
</div>
|
||||||
</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">
|
<div class="form-group clearfix">
|
||||||
<label class="col-md-2 control-label" for="smtpSSL">{{:: 'enable-ssl' | translate}}</label>
|
<label class="col-md-2 control-label" for="smtpSSL">{{:: 'enable-ssl' | translate}}</label>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
|
|
Loading…
Reference in a new issue