/* * Copyright 2016 Red Hat, Inc. and/or its affiliates * and other contributors as indicated by the @author tags. * * Licensed 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 com.gwidgets.providers; import java.io.IOException; import java.text.MessageFormat; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Properties; import org.keycloak.broker.provider.BrokeredIdentityContext; import org.keycloak.common.util.ObjectUtil; import org.keycloak.email.EmailException; import org.keycloak.email.EmailSenderProvider; import org.keycloak.email.EmailTemplateProvider; import org.keycloak.email.freemarker.beans.EventBean; import org.keycloak.email.freemarker.beans.ProfileBean; import org.keycloak.events.Event; import org.keycloak.events.EventType; import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; import org.keycloak.models.UserModel; import org.keycloak.sessions.AuthenticationSessionModel; import org.keycloak.theme.FreeMarkerException; import org.keycloak.theme.FreeMarkerUtil; import org.keycloak.theme.Theme; import org.keycloak.theme.ThemeProvider; import org.keycloak.theme.beans.LinkExpirationFormatterMethod; import org.keycloak.theme.beans.MessageFormatterMethod; /** * @author Stian Thorgersen */ public class CustomEmailTemplateProvider implements EmailTemplateProvider { protected KeycloakSession session; /** * authenticationSession can be null for some email sendings, it is filled only for email sendings performed as part of the authentication session (email verification, password reset, broker link * etc.)! */ protected AuthenticationSessionModel authenticationSession; protected FreeMarkerUtil freeMarker; protected RealmModel realm; protected UserModel user; protected final Map attributes = new HashMap<>(); public CustomEmailTemplateProvider(KeycloakSession session, FreeMarkerUtil freeMarker) { this.session = session; } @Override public EmailTemplateProvider setRealm(RealmModel realm) { this.realm = realm; return this; } @Override public EmailTemplateProvider setUser(UserModel user) { this.user = user; return this; } @Override public EmailTemplateProvider setAttribute(String name, Object value) { attributes.put(name, value); return this; } @Override public EmailTemplateProvider setAuthenticationSession(AuthenticationSessionModel authenticationSession) { this.authenticationSession = authenticationSession; return this; } protected String getRealmName() { if (realm.getDisplayName() != null) { return realm.getDisplayName(); } else { return ObjectUtil.capitalize(realm.getName()); } } @Override public void sendEvent(Event event) throws EmailException { Map attributes = new HashMap(); attributes.put("user", new ProfileBean(user)); attributes.put("event", new EventBean(event)); send(toCamelCase(event.getType()) + "Subject", "event-" + event.getType().toString().toLowerCase() + ".ftl", attributes); } @Override public void sendPasswordReset(String link, long expirationInMinutes) throws EmailException { Map attributes = new HashMap(this.attributes); attributes.put("user", new ProfileBean(user)); addLinkInfoIntoAttributes(link, expirationInMinutes, attributes); attributes.put("realmName", getRealmName()); send("passwordResetSubject", "password-reset.ftl", attributes); } @Override public void sendSmtpTestEmail(Map config, UserModel user) throws EmailException { setRealm(session.getContext().getRealm()); setUser(user); Map attributes = new HashMap(this.attributes); attributes.put("user", new ProfileBean(user)); attributes.put("realmName", realm.getName()); EmailTemplate email = processTemplate("emailTestSubject", Collections.emptyList(), "email-test.ftl", attributes); send(config, email.getSubject(), email.getTextBody(), email.getHtmlBody()); } @Override public void sendConfirmIdentityBrokerLink(String link, long expirationInMinutes) throws EmailException { Map attributes = new HashMap(this.attributes); attributes.put("user", new ProfileBean(user)); addLinkInfoIntoAttributes(link, expirationInMinutes, attributes); attributes.put("realmName", getRealmName()); BrokeredIdentityContext brokerContext = (BrokeredIdentityContext) this.attributes.get(IDENTITY_PROVIDER_BROKER_CONTEXT); String idpAlias = brokerContext.getIdpConfig().getAlias(); idpAlias = ObjectUtil.capitalize(idpAlias); attributes.put("identityProviderContext", brokerContext); attributes.put("identityProviderAlias", idpAlias); List subjectAttrs = Arrays. asList(idpAlias); send("identityProviderLinkSubject", subjectAttrs, "identity-provider-link.ftl", attributes); } @Override public void sendExecuteActions(String link, long expirationInMinutes) throws EmailException { Map attributes = new HashMap(this.attributes); attributes.put("user", new ProfileBean(user)); addLinkInfoIntoAttributes(link, expirationInMinutes, attributes); attributes.put("realmName", getRealmName()); send("executeActionsSubject", "executeActions.ftl", attributes); } @Override public void sendVerifyEmail(String link, long expirationInMinutes) throws EmailException { System.out.println("<<<<<<<<<<<<<<>>>>>>>>>>>>>>>"); Map attributes = new HashMap(this.attributes); attributes.put("user", new ProfileBean(user)); addLinkInfoIntoAttributes(link, expirationInMinutes, attributes); attributes.put("realmName", getRealmName()); send("emailVerificationSubject", "email-verification.ftl", attributes); } /** * Add link info into template attributes. * * @param link to add * @param expirationInMinutes to add * @param attributes to add link info into */ protected void addLinkInfoIntoAttributes(String link, long expirationInMinutes, Map attributes) throws EmailException { attributes.put("link", link); attributes.put("linkExpiration", expirationInMinutes); try { Locale locale = session.getContext().resolveLocale(user); attributes.put("linkExpirationFormatter", new LinkExpirationFormatterMethod(getTheme().getMessages(locale), locale)); } catch (IOException e) { throw new EmailException("Failed to template email", e); } } @Override public void send(String subjectFormatKey, String bodyTemplate, Map bodyAttributes) throws EmailException { send(subjectFormatKey, Collections.emptyList(), bodyTemplate, bodyAttributes); } protected EmailTemplate processTemplate(String subjectKey, List subjectAttributes, String template, Map attributes) throws EmailException { try { Theme theme = getTheme(); Locale locale = session.getContext().resolveLocale(user); attributes.put("locale", locale); Properties rb = theme.getMessages(locale); attributes.put("msg", new MessageFormatterMethod(locale, rb)); attributes.put("properties", theme.getProperties()); String subject = new MessageFormat(rb.getProperty(subjectKey, subjectKey), locale).format(subjectAttributes.toArray()); String textTemplate = String.format("text/%s", template); String textBody; try { textBody = freeMarker.processTemplate(attributes, textTemplate, theme); } catch (final FreeMarkerException e) { textBody = null; } String htmlTemplate = String.format("html/%s", template); String htmlBody; try { htmlBody = freeMarker.processTemplate(attributes, htmlTemplate, theme); } catch (final FreeMarkerException e) { htmlBody = null; } return new EmailTemplate(subject, textBody, htmlBody); } catch (Exception e) { throw new EmailException("Failed to template email", e); } } protected Theme getTheme() throws IOException { return session.theme().getTheme(Theme.Type.EMAIL); } @Override public void send(String subjectFormatKey, List subjectAttributes, String bodyTemplate, Map bodyAttributes) throws EmailException { try { EmailTemplate email = processTemplate(subjectFormatKey, subjectAttributes, bodyTemplate, bodyAttributes); send(email.getSubject(), email.getTextBody(), email.getHtmlBody()); } catch (EmailException e) { throw e; } catch (Exception e) { throw new EmailException("Failed to template email", e); } } protected void send(String subject, String textBody, String htmlBody) throws EmailException { send(realm.getSmtpConfig(), subject, textBody, htmlBody); } protected void send(Map config, String subject, String textBody, String htmlBody) throws EmailException { System.out.println("REACHED HERE TO SEND THE VERIFY MAIL"); EmailSenderProvider emailSender = session.getProvider(CustomEmailSenderProvider.class); emailSender.send(config, user, subject, textBody, htmlBody); } @Override public void close() { } protected String toCamelCase(EventType event) { StringBuilder sb = new StringBuilder("event"); for (String s : event.name().toLowerCase().split("_")) { sb.append(ObjectUtil.capitalize(s)); } return sb.toString(); } protected class EmailTemplate { private String subject; private String textBody; private String htmlBody; public EmailTemplate(String subject, String textBody, String htmlBody) { this.subject = subject; this.textBody = textBody; this.htmlBody = htmlBody; } public String getSubject() { return subject; } public String getTextBody() { return textBody; } public String getHtmlBody() { return htmlBody; } } }