KEYCLOAK-2133 KEYCLOAK-1782
This commit is contained in:
parent
889679c715
commit
be040eaa18
23 changed files with 240 additions and 170 deletions
|
@ -2,7 +2,7 @@ package org.keycloak.events.email;
|
||||||
|
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.keycloak.email.EmailException;
|
import org.keycloak.email.EmailException;
|
||||||
import org.keycloak.email.EmailProvider;
|
import org.keycloak.email.EmailTemplateProvider;
|
||||||
import org.keycloak.events.admin.AdminEvent;
|
import org.keycloak.events.admin.AdminEvent;
|
||||||
import org.keycloak.events.Event;
|
import org.keycloak.events.Event;
|
||||||
import org.keycloak.events.EventListenerProvider;
|
import org.keycloak.events.EventListenerProvider;
|
||||||
|
@ -23,13 +23,13 @@ public class EmailEventListenerProvider implements EventListenerProvider {
|
||||||
|
|
||||||
private KeycloakSession session;
|
private KeycloakSession session;
|
||||||
private RealmProvider model;
|
private RealmProvider model;
|
||||||
private EmailProvider emailProvider;
|
private EmailTemplateProvider emailTemplateProvider;
|
||||||
private Set<EventType> includedEvents;
|
private Set<EventType> includedEvents;
|
||||||
|
|
||||||
public EmailEventListenerProvider(KeycloakSession session, EmailProvider emailProvider, Set<EventType> includedEvents) {
|
public EmailEventListenerProvider(KeycloakSession session, EmailTemplateProvider emailTemplateProvider, Set<EventType> includedEvents) {
|
||||||
this.session = session;
|
this.session = session;
|
||||||
this.model = session.realms();
|
this.model = session.realms();
|
||||||
this.emailProvider = emailProvider;
|
this.emailTemplateProvider = emailTemplateProvider;
|
||||||
this.includedEvents = includedEvents;
|
this.includedEvents = includedEvents;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,7 +41,7 @@ public class EmailEventListenerProvider implements EventListenerProvider {
|
||||||
UserModel user = session.users().getUserById(event.getUserId(), realm);
|
UserModel user = session.users().getUserById(event.getUserId(), realm);
|
||||||
if (user != null && user.getEmail() != null && user.isEmailVerified()) {
|
if (user != null && user.getEmail() != null && user.isEmailVerified()) {
|
||||||
try {
|
try {
|
||||||
emailProvider.setRealm(realm).setUser(user).sendEvent(event);
|
emailTemplateProvider.setRealm(realm).setUser(user).sendEvent(event);
|
||||||
} catch (EmailException e) {
|
} catch (EmailException e) {
|
||||||
log.error("Failed to send type mail", e);
|
log.error("Failed to send type mail", e);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package org.keycloak.events.email;
|
package org.keycloak.events.email;
|
||||||
|
|
||||||
import org.keycloak.Config;
|
import org.keycloak.Config;
|
||||||
import org.keycloak.email.EmailProvider;
|
import org.keycloak.email.EmailTemplateProvider;
|
||||||
import org.keycloak.events.EventListenerProvider;
|
import org.keycloak.events.EventListenerProvider;
|
||||||
import org.keycloak.events.EventListenerProviderFactory;
|
import org.keycloak.events.EventListenerProviderFactory;
|
||||||
import org.keycloak.events.EventType;
|
import org.keycloak.events.EventType;
|
||||||
|
@ -26,8 +26,8 @@ public class EmailEventListenerProviderFactory implements EventListenerProviderF
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public EventListenerProvider create(KeycloakSession session) {
|
public EventListenerProvider create(KeycloakSession session) {
|
||||||
EmailProvider emailProvider = session.getProvider(EmailProvider.class);
|
EmailTemplateProvider emailTemplateProvider = session.getProvider(EmailTemplateProvider.class);
|
||||||
return new EmailEventListenerProvider(session, emailProvider, includedEvents);
|
return new EmailEventListenerProvider(session, emailTemplateProvider, includedEvents);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -1195,7 +1195,7 @@ module.controller('RealmSMTPSettingsCtrl', function($scope, Current, Realm, real
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
obj['port'] = obj['port'].toString();
|
obj['port'] = obj['port'] && obj['port'].toString();
|
||||||
|
|
||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
|
|
14
forms/email-api/src/main/java/org/keycloak/email/EmailSenderProvider.java
Executable file
14
forms/email-api/src/main/java/org/keycloak/email/EmailSenderProvider.java
Executable file
|
@ -0,0 +1,14 @@
|
||||||
|
package org.keycloak.email;
|
||||||
|
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.UserModel;
|
||||||
|
import org.keycloak.provider.Provider;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
*/
|
||||||
|
public interface EmailSenderProvider extends Provider {
|
||||||
|
|
||||||
|
void send(RealmModel realm, UserModel user, String subject, String textBody, String htmlBody) throws EmailException;
|
||||||
|
|
||||||
|
}
|
|
@ -5,5 +5,5 @@ import org.keycloak.provider.ProviderFactory;
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
*/
|
*/
|
||||||
public interface EmailProviderFactory extends ProviderFactory<EmailProvider> {
|
public interface EmailSenderProviderFactory extends ProviderFactory<EmailSenderProvider> {
|
||||||
}
|
}
|
|
@ -7,7 +7,7 @@ import org.keycloak.provider.Spi;
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
*/
|
*/
|
||||||
public class EmailSpi implements Spi {
|
public class EmailSenderSpi implements Spi {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isInternal() {
|
public boolean isInternal() {
|
||||||
|
@ -16,16 +16,16 @@ public class EmailSpi implements Spi {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getName() {
|
public String getName() {
|
||||||
return "email";
|
return "emailSender";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Class<? extends Provider> getProviderClass() {
|
public Class<? extends Provider> getProviderClass() {
|
||||||
return EmailProvider.class;
|
return EmailSenderProvider.class;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Class<? extends ProviderFactory> getProviderFactoryClass() {
|
public Class<? extends ProviderFactory> getProviderFactoryClass() {
|
||||||
return EmailProviderFactory.class;
|
return EmailSenderProviderFactory.class;
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -8,15 +8,15 @@ import org.keycloak.provider.Provider;
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
*/
|
*/
|
||||||
public interface EmailProvider extends Provider {
|
public interface EmailTemplateProvider extends Provider {
|
||||||
|
|
||||||
String IDENTITY_PROVIDER_BROKER_CONTEXT = "identityProviderBrokerCtx";
|
String IDENTITY_PROVIDER_BROKER_CONTEXT = "identityProviderBrokerCtx";
|
||||||
|
|
||||||
public EmailProvider setRealm(RealmModel realm);
|
public EmailTemplateProvider setRealm(RealmModel realm);
|
||||||
|
|
||||||
public EmailProvider setUser(UserModel user);
|
public EmailTemplateProvider setUser(UserModel user);
|
||||||
|
|
||||||
public EmailProvider setAttribute(String name, Object value);
|
public EmailTemplateProvider setAttribute(String name, Object value);
|
||||||
|
|
||||||
public void sendEvent(Event event) throws EmailException;
|
public void sendEvent(Event event) throws EmailException;
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
package org.keycloak.email;
|
||||||
|
|
||||||
|
import org.keycloak.provider.ProviderFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
*/
|
||||||
|
public interface EmailTemplateProviderFactory extends ProviderFactory<EmailTemplateProvider> {
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
package org.keycloak.email;
|
||||||
|
|
||||||
|
import org.keycloak.provider.Provider;
|
||||||
|
import org.keycloak.provider.ProviderFactory;
|
||||||
|
import org.keycloak.provider.Spi;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
*/
|
||||||
|
public class EmailTemplateSpi implements Spi {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isInternal() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return "emailTemplate";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<? extends Provider> getProviderClass() {
|
||||||
|
return EmailTemplateProvider.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<? extends ProviderFactory> getProviderFactoryClass() {
|
||||||
|
return org.keycloak.email.EmailTemplateProviderFactory.class;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1 +1,2 @@
|
||||||
org.keycloak.email.EmailSpi
|
org.keycloak.email.EmailSenderSpi
|
||||||
|
org.keycloak.email.EmailTemplateSpi
|
|
@ -1,29 +1,10 @@
|
||||||
package org.keycloak.email.freemarker;
|
package org.keycloak.email.freemarker;
|
||||||
|
|
||||||
import java.text.MessageFormat;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Locale;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Properties;
|
|
||||||
|
|
||||||
import javax.mail.Message;
|
|
||||||
import javax.mail.Multipart;
|
|
||||||
import javax.mail.Session;
|
|
||||||
import javax.mail.Transport;
|
|
||||||
import javax.mail.internet.InternetAddress;
|
|
||||||
import javax.mail.internet.MimeBodyPart;
|
|
||||||
import javax.mail.internet.MimeMessage;
|
|
||||||
import javax.mail.internet.MimeMultipart;
|
|
||||||
|
|
||||||
import org.jboss.logging.Logger;
|
|
||||||
import org.keycloak.broker.provider.BrokeredIdentityContext;
|
import org.keycloak.broker.provider.BrokeredIdentityContext;
|
||||||
import org.keycloak.common.util.ObjectUtil;
|
import org.keycloak.common.util.ObjectUtil;
|
||||||
import org.keycloak.email.EmailException;
|
import org.keycloak.email.EmailException;
|
||||||
import org.keycloak.email.EmailProvider;
|
import org.keycloak.email.EmailSenderProvider;
|
||||||
|
import org.keycloak.email.EmailTemplateProvider;
|
||||||
import org.keycloak.email.freemarker.beans.EventBean;
|
import org.keycloak.email.freemarker.beans.EventBean;
|
||||||
import org.keycloak.email.freemarker.beans.ProfileBean;
|
import org.keycloak.email.freemarker.beans.ProfileBean;
|
||||||
import org.keycloak.events.Event;
|
import org.keycloak.events.Event;
|
||||||
|
@ -33,43 +14,43 @@ import org.keycloak.freemarker.FreeMarkerUtil;
|
||||||
import org.keycloak.freemarker.Theme;
|
import org.keycloak.freemarker.Theme;
|
||||||
import org.keycloak.freemarker.ThemeProvider;
|
import org.keycloak.freemarker.ThemeProvider;
|
||||||
import org.keycloak.freemarker.beans.MessageFormatterMethod;
|
import org.keycloak.freemarker.beans.MessageFormatterMethod;
|
||||||
import org.keycloak.models.IdentityProviderModel;
|
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
|
|
||||||
|
import java.text.MessageFormat;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
*/
|
*/
|
||||||
public class FreeMarkerEmailProvider implements EmailProvider {
|
public class FreeMarkerEmailTemplateProvider implements EmailTemplateProvider {
|
||||||
|
|
||||||
private static final Logger log = Logger.getLogger(FreeMarkerEmailProvider.class);
|
|
||||||
|
|
||||||
private KeycloakSession session;
|
private KeycloakSession session;
|
||||||
private FreeMarkerUtil freeMarker;
|
private FreeMarkerUtil freeMarker;
|
||||||
private RealmModel realm;
|
private RealmModel realm;
|
||||||
private UserModel user;
|
private UserModel user;
|
||||||
private final Map<String, Object> attributes = new HashMap<String, Object>();
|
private final Map<String, Object> attributes = new HashMap<>();
|
||||||
|
|
||||||
public FreeMarkerEmailProvider(KeycloakSession session, FreeMarkerUtil freeMarker) {
|
public FreeMarkerEmailTemplateProvider(KeycloakSession session, FreeMarkerUtil freeMarker) {
|
||||||
this.session = session;
|
this.session = session;
|
||||||
this.freeMarker = freeMarker;
|
this.freeMarker = freeMarker;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public EmailProvider setRealm(RealmModel realm) {
|
public EmailTemplateProvider setRealm(RealmModel realm) {
|
||||||
this.realm = realm;
|
this.realm = realm;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public EmailProvider setUser(UserModel user) {
|
public EmailTemplateProvider setUser(UserModel user) {
|
||||||
this.user = user;
|
this.user = user;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public EmailProvider setAttribute(String name, Object value) {
|
public EmailTemplateProvider setAttribute(String name, Object value) {
|
||||||
attributes.put(name, value);
|
attributes.put(name, value);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
@ -178,73 +159,9 @@ public class FreeMarkerEmailProvider implements EmailProvider {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void send(String subject, String textBody, String htmlBody) throws EmailException {
|
private void send(String subject, String textBody, String htmlBody) throws EmailException {
|
||||||
try {
|
EmailSenderProvider emailSender = session.getProvider(EmailSenderProvider.class);
|
||||||
String address = user.getEmail();
|
emailSender.send(realm, user, subject, textBody, htmlBody);
|
||||||
Map<String, String> config = realm.getSmtpConfig();
|
|
||||||
|
|
||||||
Properties props = new Properties();
|
|
||||||
props.setProperty("mail.smtp.host", config.get("host"));
|
|
||||||
|
|
||||||
boolean auth = "true".equals(config.get("auth"));
|
|
||||||
boolean ssl = "true".equals(config.get("ssl"));
|
|
||||||
boolean starttls = "true".equals(config.get("starttls"));
|
|
||||||
|
|
||||||
if (config.containsKey("port")) {
|
|
||||||
props.setProperty("mail.smtp.port", config.get("port"));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (auth) {
|
|
||||||
props.put("mail.smtp.auth", "true");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ssl) {
|
|
||||||
props.put("mail.smtp.socketFactory.port", config.get("port"));
|
|
||||||
props.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (starttls) {
|
|
||||||
props.put("mail.smtp.starttls.enable", "true");
|
|
||||||
}
|
|
||||||
|
|
||||||
String from = config.get("from");
|
|
||||||
|
|
||||||
Session session = Session.getInstance(props);
|
|
||||||
|
|
||||||
Multipart multipart = new MimeMultipart("alternative");
|
|
||||||
|
|
||||||
if(textBody != null) {
|
|
||||||
MimeBodyPart textPart = new MimeBodyPart();
|
|
||||||
textPart.setText(textBody, "UTF-8");
|
|
||||||
multipart.addBodyPart(textPart);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(htmlBody != null) {
|
|
||||||
MimeBodyPart htmlPart = new MimeBodyPart();
|
|
||||||
htmlPart.setContent(htmlBody, "text/html; charset=UTF-8");
|
|
||||||
multipart.addBodyPart(htmlPart);
|
|
||||||
}
|
|
||||||
|
|
||||||
Message msg = new MimeMessage(session);
|
|
||||||
msg.setFrom(new InternetAddress(from));
|
|
||||||
msg.setHeader("To", address);
|
|
||||||
msg.setSubject(subject);
|
|
||||||
msg.setContent(multipart);
|
|
||||||
msg.saveChanges();
|
|
||||||
msg.setSentDate(new Date());
|
|
||||||
|
|
||||||
Transport transport = session.getTransport("smtp");
|
|
||||||
if (auth) {
|
|
||||||
transport.connect(config.get("user"), config.get("password"));
|
|
||||||
} else {
|
|
||||||
transport.connect();
|
|
||||||
}
|
|
||||||
transport.sendMessage(msg, new InternetAddress[]{new InternetAddress(address)});
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.warn("Failed to send email", e);
|
|
||||||
throw new EmailException(e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
|
@ -1,8 +1,8 @@
|
||||||
package org.keycloak.email.freemarker;
|
package org.keycloak.email.freemarker;
|
||||||
|
|
||||||
import org.keycloak.Config;
|
import org.keycloak.Config;
|
||||||
import org.keycloak.email.EmailProvider;
|
import org.keycloak.email.EmailTemplateProvider;
|
||||||
import org.keycloak.email.EmailProviderFactory;
|
import org.keycloak.email.EmailTemplateProviderFactory;
|
||||||
import org.keycloak.freemarker.FreeMarkerUtil;
|
import org.keycloak.freemarker.FreeMarkerUtil;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.KeycloakSessionFactory;
|
import org.keycloak.models.KeycloakSessionFactory;
|
||||||
|
@ -10,13 +10,13 @@ import org.keycloak.models.KeycloakSessionFactory;
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
*/
|
*/
|
||||||
public class FreeMarkerEmailProviderFactory implements EmailProviderFactory {
|
public class FreeMarkerEmailTemplateProviderFactory implements EmailTemplateProviderFactory {
|
||||||
|
|
||||||
private FreeMarkerUtil freeMarker;
|
private FreeMarkerUtil freeMarker;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public EmailProvider create(KeycloakSession session) {
|
public EmailTemplateProvider create(KeycloakSession session) {
|
||||||
return new FreeMarkerEmailProvider(session, freeMarker);
|
return new FreeMarkerEmailTemplateProvider(session, freeMarker);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -26,7 +26,6 @@ public class FreeMarkerEmailProviderFactory implements EmailProviderFactory {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void postInit(KeycloakSessionFactory factory) {
|
public void postInit(KeycloakSessionFactory factory) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
|
@ -1 +0,0 @@
|
||||||
org.keycloak.email.freemarker.FreeMarkerEmailProviderFactory
|
|
|
@ -0,0 +1 @@
|
||||||
|
org.keycloak.email.freemarker.FreeMarkerEmailTemplateProviderFactory
|
|
@ -24,7 +24,7 @@ import org.keycloak.authentication.requiredactions.util.UserUpdateProfileContext
|
||||||
import org.keycloak.broker.provider.BrokeredIdentityContext;
|
import org.keycloak.broker.provider.BrokeredIdentityContext;
|
||||||
import org.keycloak.common.util.ObjectUtil;
|
import org.keycloak.common.util.ObjectUtil;
|
||||||
import org.keycloak.email.EmailException;
|
import org.keycloak.email.EmailException;
|
||||||
import org.keycloak.email.EmailProvider;
|
import org.keycloak.email.EmailTemplateProvider;
|
||||||
import org.keycloak.freemarker.BrowserSecurityHeaderSetup;
|
import org.keycloak.freemarker.BrowserSecurityHeaderSetup;
|
||||||
import org.keycloak.freemarker.FreeMarkerException;
|
import org.keycloak.freemarker.FreeMarkerException;
|
||||||
import org.keycloak.freemarker.FreeMarkerUtil;
|
import org.keycloak.freemarker.FreeMarkerUtil;
|
||||||
|
@ -153,7 +153,7 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
|
||||||
String link = builder.build(realm.getName()).toString();
|
String link = builder.build(realm.getName()).toString();
|
||||||
long expiration = TimeUnit.SECONDS.toMinutes(realm.getAccessCodeLifespanUserAction());
|
long expiration = TimeUnit.SECONDS.toMinutes(realm.getAccessCodeLifespanUserAction());
|
||||||
|
|
||||||
session.getProvider(EmailProvider.class).setRealm(realm).setUser(user).sendVerifyEmail(link, expiration);
|
session.getProvider(EmailTemplateProvider.class).setRealm(realm).setUser(user).sendVerifyEmail(link, expiration);
|
||||||
} catch (EmailException e) {
|
} catch (EmailException e) {
|
||||||
logger.error("Failed to send verification email", e);
|
logger.error("Failed to send verification email", e);
|
||||||
return setError(Messages.EMAIL_SENT_ERROR).createErrorPage();
|
return setError(Messages.EMAIL_SENT_ERROR).createErrorPage();
|
||||||
|
|
|
@ -13,7 +13,7 @@ import org.keycloak.authentication.requiredactions.VerifyEmail;
|
||||||
import org.keycloak.authentication.authenticators.broker.util.SerializedBrokeredIdentityContext;
|
import org.keycloak.authentication.authenticators.broker.util.SerializedBrokeredIdentityContext;
|
||||||
import org.keycloak.broker.provider.BrokeredIdentityContext;
|
import org.keycloak.broker.provider.BrokeredIdentityContext;
|
||||||
import org.keycloak.email.EmailException;
|
import org.keycloak.email.EmailException;
|
||||||
import org.keycloak.email.EmailProvider;
|
import org.keycloak.email.EmailTemplateProvider;
|
||||||
import org.keycloak.events.Details;
|
import org.keycloak.events.Details;
|
||||||
import org.keycloak.events.Errors;
|
import org.keycloak.events.Errors;
|
||||||
import org.keycloak.events.EventBuilder;
|
import org.keycloak.events.EventBuilder;
|
||||||
|
@ -68,10 +68,10 @@ public class IdpEmailVerificationAuthenticator extends AbstractIdpAuthenticator
|
||||||
long expiration = TimeUnit.SECONDS.toMinutes(context.getRealm().getAccessCodeLifespanUserAction());
|
long expiration = TimeUnit.SECONDS.toMinutes(context.getRealm().getAccessCodeLifespanUserAction());
|
||||||
try {
|
try {
|
||||||
|
|
||||||
context.getSession().getProvider(EmailProvider.class)
|
context.getSession().getProvider(EmailTemplateProvider.class)
|
||||||
.setRealm(realm)
|
.setRealm(realm)
|
||||||
.setUser(existingUser)
|
.setUser(existingUser)
|
||||||
.setAttribute(EmailProvider.IDENTITY_PROVIDER_BROKER_CONTEXT, brokerContext)
|
.setAttribute(EmailTemplateProvider.IDENTITY_PROVIDER_BROKER_CONTEXT, brokerContext)
|
||||||
.sendConfirmIdentityBrokerLink(link, expiration);
|
.sendConfirmIdentityBrokerLink(link, expiration);
|
||||||
|
|
||||||
event.success();
|
event.success();
|
||||||
|
|
|
@ -8,29 +8,20 @@ import org.keycloak.authentication.Authenticator;
|
||||||
import org.keycloak.authentication.AuthenticatorFactory;
|
import org.keycloak.authentication.AuthenticatorFactory;
|
||||||
import org.keycloak.authentication.authenticators.broker.AbstractIdpAuthenticator;
|
import org.keycloak.authentication.authenticators.broker.AbstractIdpAuthenticator;
|
||||||
import org.keycloak.authentication.authenticators.browser.AbstractUsernameFormAuthenticator;
|
import org.keycloak.authentication.authenticators.browser.AbstractUsernameFormAuthenticator;
|
||||||
import org.keycloak.email.EmailException;
|
|
||||||
import org.keycloak.email.EmailProvider;
|
|
||||||
import org.keycloak.events.Details;
|
import org.keycloak.events.Details;
|
||||||
import org.keycloak.events.Errors;
|
import org.keycloak.events.Errors;
|
||||||
import org.keycloak.events.EventBuilder;
|
import org.keycloak.events.EventBuilder;
|
||||||
import org.keycloak.login.LoginFormsProvider;
|
|
||||||
import org.keycloak.models.AuthenticationExecutionModel;
|
import org.keycloak.models.AuthenticationExecutionModel;
|
||||||
import org.keycloak.models.ClientSessionModel;
|
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.KeycloakSessionFactory;
|
import org.keycloak.models.KeycloakSessionFactory;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.models.UserSessionModel;
|
|
||||||
import org.keycloak.protocol.oidc.TokenManager;
|
|
||||||
import org.keycloak.provider.ProviderConfigProperty;
|
import org.keycloak.provider.ProviderConfigProperty;
|
||||||
import org.keycloak.services.Urls;
|
|
||||||
import org.keycloak.services.messages.Messages;
|
import org.keycloak.services.messages.Messages;
|
||||||
|
|
||||||
import javax.ws.rs.core.MultivaluedMap;
|
import javax.ws.rs.core.MultivaluedMap;
|
||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
import javax.ws.rs.core.UriBuilder;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
|
|
|
@ -8,7 +8,7 @@ import org.keycloak.authentication.Authenticator;
|
||||||
import org.keycloak.authentication.AuthenticatorFactory;
|
import org.keycloak.authentication.AuthenticatorFactory;
|
||||||
import org.keycloak.authentication.authenticators.browser.AbstractUsernameFormAuthenticator;
|
import org.keycloak.authentication.authenticators.browser.AbstractUsernameFormAuthenticator;
|
||||||
import org.keycloak.email.EmailException;
|
import org.keycloak.email.EmailException;
|
||||||
import org.keycloak.email.EmailProvider;
|
import org.keycloak.email.EmailTemplateProvider;
|
||||||
import org.keycloak.events.Details;
|
import org.keycloak.events.Details;
|
||||||
import org.keycloak.events.Errors;
|
import org.keycloak.events.Errors;
|
||||||
import org.keycloak.events.EventBuilder;
|
import org.keycloak.events.EventBuilder;
|
||||||
|
@ -72,7 +72,7 @@ public class ResetCredentialEmail implements Authenticator, AuthenticatorFactory
|
||||||
long expiration = TimeUnit.SECONDS.toMinutes(context.getRealm().getAccessCodeLifespanUserAction());
|
long expiration = TimeUnit.SECONDS.toMinutes(context.getRealm().getAccessCodeLifespanUserAction());
|
||||||
try {
|
try {
|
||||||
|
|
||||||
context.getSession().getProvider(EmailProvider.class).setRealm(context.getRealm()).setUser(user).sendPasswordReset(link, expiration);
|
context.getSession().getProvider(EmailTemplateProvider.class).setRealm(context.getRealm()).setUser(user).sendPasswordReset(link, expiration);
|
||||||
event.clone().event(EventType.SEND_RESET_PASSWORD)
|
event.clone().event(EventType.SEND_RESET_PASSWORD)
|
||||||
.user(user)
|
.user(user)
|
||||||
.detail(Details.USERNAME, username)
|
.detail(Details.USERNAME, username)
|
||||||
|
|
|
@ -0,0 +1,102 @@
|
||||||
|
package org.keycloak.email;
|
||||||
|
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.UserModel;
|
||||||
|
|
||||||
|
import javax.mail.Message;
|
||||||
|
import javax.mail.Multipart;
|
||||||
|
import javax.mail.Session;
|
||||||
|
import javax.mail.Transport;
|
||||||
|
import javax.mail.internet.InternetAddress;
|
||||||
|
import javax.mail.internet.MimeBodyPart;
|
||||||
|
import javax.mail.internet.MimeMessage;
|
||||||
|
import javax.mail.internet.MimeMultipart;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Properties;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
*/
|
||||||
|
public class DefaultEmailSenderProvider implements EmailSenderProvider {
|
||||||
|
|
||||||
|
private static final Logger log = Logger.getLogger(DefaultEmailSenderProvider.class);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void send(RealmModel realm, UserModel user, String subject, String textBody, String htmlBody) throws EmailException {
|
||||||
|
try {
|
||||||
|
String address = user.getEmail();
|
||||||
|
Map<String, String> config = realm.getSmtpConfig();
|
||||||
|
|
||||||
|
Properties props = new Properties();
|
||||||
|
props.setProperty("mail.smtp.host", config.get("host"));
|
||||||
|
|
||||||
|
boolean auth = "true".equals(config.get("auth"));
|
||||||
|
boolean ssl = "true".equals(config.get("ssl"));
|
||||||
|
boolean starttls = "true".equals(config.get("starttls"));
|
||||||
|
|
||||||
|
if (config.containsKey("port")) {
|
||||||
|
props.setProperty("mail.smtp.port", config.get("port"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (auth) {
|
||||||
|
props.setProperty("mail.smtp.auth", "true");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ssl) {
|
||||||
|
props.setProperty("mail.smtp.ssl.enable", "true");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (starttls) {
|
||||||
|
props.setProperty("mail.smtp.starttls.enable", "true");
|
||||||
|
}
|
||||||
|
|
||||||
|
props.setProperty("mail.smtp.timeout", "10000");
|
||||||
|
props.setProperty("mail.smtp.connectiontimeout", "10000");
|
||||||
|
|
||||||
|
String from = config.get("from");
|
||||||
|
|
||||||
|
Session session = Session.getInstance(props);
|
||||||
|
|
||||||
|
Multipart multipart = new MimeMultipart("alternative");
|
||||||
|
|
||||||
|
if(textBody != null) {
|
||||||
|
MimeBodyPart textPart = new MimeBodyPart();
|
||||||
|
textPart.setText(textBody, "UTF-8");
|
||||||
|
multipart.addBodyPart(textPart);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(htmlBody != null) {
|
||||||
|
MimeBodyPart htmlPart = new MimeBodyPart();
|
||||||
|
htmlPart.setContent(htmlBody, "text/html; charset=UTF-8");
|
||||||
|
multipart.addBodyPart(htmlPart);
|
||||||
|
}
|
||||||
|
|
||||||
|
Message msg = new MimeMessage(session);
|
||||||
|
msg.setFrom(new InternetAddress(from));
|
||||||
|
msg.setHeader("To", address);
|
||||||
|
msg.setSubject(subject);
|
||||||
|
msg.setContent(multipart);
|
||||||
|
msg.saveChanges();
|
||||||
|
msg.setSentDate(new Date());
|
||||||
|
|
||||||
|
Transport transport = session.getTransport("smtp");
|
||||||
|
if (auth) {
|
||||||
|
transport.connect(config.get("user"), config.get("password"));
|
||||||
|
} else {
|
||||||
|
transport.connect();
|
||||||
|
}
|
||||||
|
transport.sendMessage(msg, new InternetAddress[]{new InternetAddress(address)});
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Failed to send email", e);
|
||||||
|
throw new EmailException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
package org.keycloak.email;
|
||||||
|
|
||||||
|
import org.keycloak.Config;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.KeycloakSessionFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
*/
|
||||||
|
public class DefaultEmailSenderProviderFactory implements EmailSenderProviderFactory {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public EmailSenderProvider create(KeycloakSession session) {
|
||||||
|
return new DefaultEmailSenderProvider();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init(Config.Scope config) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void postInit(KeycloakSessionFactory factory) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getId() {
|
||||||
|
return "default";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -2,61 +2,37 @@ package org.keycloak.services.resources.admin;
|
||||||
|
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.jboss.resteasy.annotations.cache.NoCache;
|
import org.jboss.resteasy.annotations.cache.NoCache;
|
||||||
import org.jboss.resteasy.spi.BadRequestException;
|
|
||||||
import org.jboss.resteasy.spi.NotFoundException;
|
import org.jboss.resteasy.spi.NotFoundException;
|
||||||
import org.keycloak.common.ClientConnection;
|
import org.keycloak.common.ClientConnection;
|
||||||
import org.keycloak.email.EmailException;
|
|
||||||
import org.keycloak.email.EmailProvider;
|
|
||||||
import org.keycloak.events.admin.OperationType;
|
import org.keycloak.events.admin.OperationType;
|
||||||
import org.keycloak.models.ClientModel;
|
import org.keycloak.models.ClientModel;
|
||||||
import org.keycloak.models.ClientSessionModel;
|
|
||||||
import org.keycloak.models.Constants;
|
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.ModelReadOnlyException;
|
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.RoleMapperModel;
|
import org.keycloak.models.RoleMapperModel;
|
||||||
import org.keycloak.models.RoleModel;
|
import org.keycloak.models.RoleModel;
|
||||||
import org.keycloak.models.UserCredentialModel;
|
|
||||||
import org.keycloak.models.UserModel;
|
|
||||||
import org.keycloak.models.UserSessionModel;
|
|
||||||
import org.keycloak.models.utils.ModelToRepresentation;
|
import org.keycloak.models.utils.ModelToRepresentation;
|
||||||
import org.keycloak.models.utils.RepresentationToModel;
|
|
||||||
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
|
||||||
import org.keycloak.protocol.oidc.TokenManager;
|
|
||||||
import org.keycloak.protocol.oidc.utils.RedirectUtils;
|
|
||||||
import org.keycloak.representations.idm.ClientMappingsRepresentation;
|
import org.keycloak.representations.idm.ClientMappingsRepresentation;
|
||||||
import org.keycloak.representations.idm.CredentialRepresentation;
|
|
||||||
import org.keycloak.representations.idm.MappingsRepresentation;
|
import org.keycloak.representations.idm.MappingsRepresentation;
|
||||||
import org.keycloak.representations.idm.RoleRepresentation;
|
import org.keycloak.representations.idm.RoleRepresentation;
|
||||||
import org.keycloak.services.ErrorResponse;
|
|
||||||
import org.keycloak.services.Urls;
|
|
||||||
import org.keycloak.services.managers.BruteForceProtector;
|
import org.keycloak.services.managers.BruteForceProtector;
|
||||||
import org.keycloak.services.managers.ClientSessionCode;
|
|
||||||
import org.keycloak.services.managers.RealmManager;
|
import org.keycloak.services.managers.RealmManager;
|
||||||
|
|
||||||
import javax.ws.rs.Consumes;
|
import javax.ws.rs.Consumes;
|
||||||
import javax.ws.rs.DELETE;
|
import javax.ws.rs.DELETE;
|
||||||
import javax.ws.rs.GET;
|
import javax.ws.rs.GET;
|
||||||
import javax.ws.rs.POST;
|
import javax.ws.rs.POST;
|
||||||
import javax.ws.rs.PUT;
|
|
||||||
import javax.ws.rs.Path;
|
import javax.ws.rs.Path;
|
||||||
import javax.ws.rs.PathParam;
|
import javax.ws.rs.PathParam;
|
||||||
import javax.ws.rs.Produces;
|
import javax.ws.rs.Produces;
|
||||||
import javax.ws.rs.QueryParam;
|
|
||||||
import javax.ws.rs.WebApplicationException;
|
|
||||||
import javax.ws.rs.core.Context;
|
import javax.ws.rs.core.Context;
|
||||||
import javax.ws.rs.core.HttpHeaders;
|
import javax.ws.rs.core.HttpHeaders;
|
||||||
import javax.ws.rs.core.MediaType;
|
import javax.ws.rs.core.MediaType;
|
||||||
import javax.ws.rs.core.Response;
|
|
||||||
import javax.ws.rs.core.UriBuilder;
|
|
||||||
import javax.ws.rs.core.UriInfo;
|
import javax.ws.rs.core.UriInfo;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base resource for managing users
|
* Base resource for managing users
|
||||||
|
|
|
@ -8,7 +8,7 @@ import org.jboss.resteasy.spi.ResteasyProviderFactory;
|
||||||
import org.keycloak.common.ClientConnection;
|
import org.keycloak.common.ClientConnection;
|
||||||
import org.keycloak.authentication.RequiredActionProvider;
|
import org.keycloak.authentication.RequiredActionProvider;
|
||||||
import org.keycloak.email.EmailException;
|
import org.keycloak.email.EmailException;
|
||||||
import org.keycloak.email.EmailProvider;
|
import org.keycloak.email.EmailTemplateProvider;
|
||||||
import org.keycloak.events.Details;
|
import org.keycloak.events.Details;
|
||||||
import org.keycloak.events.EventBuilder;
|
import org.keycloak.events.EventBuilder;
|
||||||
import org.keycloak.events.EventType;
|
import org.keycloak.events.EventType;
|
||||||
|
@ -23,7 +23,6 @@ import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.ModelDuplicateException;
|
import org.keycloak.models.ModelDuplicateException;
|
||||||
import org.keycloak.models.ModelReadOnlyException;
|
import org.keycloak.models.ModelReadOnlyException;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.RoleModel;
|
|
||||||
import org.keycloak.models.UserConsentModel;
|
import org.keycloak.models.UserConsentModel;
|
||||||
import org.keycloak.models.UserCredentialModel;
|
import org.keycloak.models.UserCredentialModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
|
@ -34,18 +33,14 @@ import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
||||||
import org.keycloak.protocol.oidc.TokenManager;
|
import org.keycloak.protocol.oidc.TokenManager;
|
||||||
import org.keycloak.protocol.oidc.utils.RedirectUtils;
|
import org.keycloak.protocol.oidc.utils.RedirectUtils;
|
||||||
import org.keycloak.provider.ProviderFactory;
|
import org.keycloak.provider.ProviderFactory;
|
||||||
import org.keycloak.representations.idm.ClientMappingsRepresentation;
|
|
||||||
import org.keycloak.representations.idm.CredentialRepresentation;
|
import org.keycloak.representations.idm.CredentialRepresentation;
|
||||||
import org.keycloak.representations.idm.FederatedIdentityRepresentation;
|
import org.keycloak.representations.idm.FederatedIdentityRepresentation;
|
||||||
import org.keycloak.representations.idm.GroupRepresentation;
|
import org.keycloak.representations.idm.GroupRepresentation;
|
||||||
import org.keycloak.representations.idm.MappingsRepresentation;
|
|
||||||
import org.keycloak.representations.idm.RoleRepresentation;
|
|
||||||
import org.keycloak.representations.idm.UserConsentRepresentation;
|
import org.keycloak.representations.idm.UserConsentRepresentation;
|
||||||
import org.keycloak.representations.idm.UserRepresentation;
|
import org.keycloak.representations.idm.UserRepresentation;
|
||||||
import org.keycloak.representations.idm.UserSessionRepresentation;
|
import org.keycloak.representations.idm.UserSessionRepresentation;
|
||||||
import org.keycloak.services.managers.AuthenticationManager;
|
import org.keycloak.services.managers.AuthenticationManager;
|
||||||
import org.keycloak.services.managers.ClientSessionCode;
|
import org.keycloak.services.managers.ClientSessionCode;
|
||||||
import org.keycloak.services.managers.RealmManager;
|
|
||||||
import org.keycloak.services.managers.UserManager;
|
import org.keycloak.services.managers.UserManager;
|
||||||
import org.keycloak.services.ErrorResponse;
|
import org.keycloak.services.ErrorResponse;
|
||||||
import org.keycloak.services.Urls;
|
import org.keycloak.services.Urls;
|
||||||
|
@ -804,7 +799,7 @@ public class UsersResource {
|
||||||
String link = builder.build(realm.getName()).toString();
|
String link = builder.build(realm.getName()).toString();
|
||||||
long expiration = TimeUnit.SECONDS.toMinutes(realm.getAccessCodeLifespanUserAction());
|
long expiration = TimeUnit.SECONDS.toMinutes(realm.getAccessCodeLifespanUserAction());
|
||||||
|
|
||||||
this.session.getProvider(EmailProvider.class).setRealm(realm).setUser(user).sendExecuteActions(link, expiration);
|
this.session.getProvider(EmailTemplateProvider.class).setRealm(realm).setUser(user).sendExecuteActions(link, expiration);
|
||||||
|
|
||||||
//audit.user(user).detail(Details.EMAIL, user.getEmail()).detail(Details.CODE_ID, accessCode.getCodeId()).success();
|
//audit.user(user).detail(Details.EMAIL, user.getEmail()).detail(Details.CODE_ID, accessCode.getCodeId()).success();
|
||||||
|
|
||||||
|
@ -856,7 +851,7 @@ public class UsersResource {
|
||||||
String link = builder.build(realm.getName()).toString();
|
String link = builder.build(realm.getName()).toString();
|
||||||
long expiration = TimeUnit.SECONDS.toMinutes(realm.getAccessCodeLifespanUserAction());
|
long expiration = TimeUnit.SECONDS.toMinutes(realm.getAccessCodeLifespanUserAction());
|
||||||
|
|
||||||
this.session.getProvider(EmailProvider.class).setRealm(realm).setUser(user).sendVerifyEmail(link, expiration);
|
this.session.getProvider(EmailTemplateProvider.class).setRealm(realm).setUser(user).sendVerifyEmail(link, expiration);
|
||||||
|
|
||||||
//audit.user(user).detail(Details.EMAIL, user.getEmail()).detail(Details.CODE_ID, accessCode.getCodeId()).success();
|
//audit.user(user).detail(Details.EMAIL, user.getEmail()).detail(Details.CODE_ID, accessCode.getCodeId()).success();
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
org.keycloak.email.DefaultEmailSenderProviderFactory
|
Loading…
Reference in a new issue