/*
* 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 org.keycloak.email.freemarker;
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.theme.FreeMarkerException;
import org.keycloak.theme.FreeMarkerUtil;
import org.keycloak.theme.Theme;
import org.keycloak.theme.ThemeProvider;
import org.keycloak.theme.beans.MessageFormatterMethod;
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;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class FreeMarkerEmailTemplateProvider implements EmailTemplateProvider {
private KeycloakSession session;
private FreeMarkerUtil freeMarker;
private RealmModel realm;
private UserModel user;
private final Map<String, Object> attributes = new HashMap<>();
public FreeMarkerEmailTemplateProvider(KeycloakSession session, FreeMarkerUtil freeMarker) {
this.session = session;
this.freeMarker = freeMarker;
}
@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;
}
private String getRealmName() {
if (realm.getDisplayName() != null) {
return realm.getDisplayName();
} else {
return ObjectUtil.capitalize(realm.getName());
}
}
@Override
public void sendEvent(Event event) throws EmailException {
Map<String, Object> attributes = new HashMap<String, Object>();
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<String, Object> attributes = new HashMap<String, Object>();
attributes.put("user", new ProfileBean(user));
attributes.put("link", link);
attributes.put("linkExpiration", expirationInMinutes);
attributes.put("realmName", getRealmName());
send("passwordResetSubject", "password-reset.ftl", attributes);
}
@Override
public void sendConfirmIdentityBrokerLink(String link, long expirationInMinutes) throws EmailException {
Map<String, Object> attributes = new HashMap<String, Object>();
attributes.put("user", new ProfileBean(user));
attributes.put("link", link);
attributes.put("linkExpiration", expirationInMinutes);
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<Object> subjectAttrs = Arrays.<Object>asList(idpAlias);
send("identityProviderLinkSubject", subjectAttrs, "identity-provider-link.ftl", attributes);
}
@Override
public void sendExecuteActions(String link, long expirationInMinutes) throws EmailException {
Map<String, Object> attributes = new HashMap<String, Object>();
attributes.put("user", new ProfileBean(user));
attributes.put("link", link);
attributes.put("linkExpiration", expirationInMinutes);
attributes.put("realmName", getRealmName());
send("executeActionsSubject", "executeActions.ftl", attributes);
}
@Override
public void sendVerifyEmail(String link, long expirationInMinutes) throws EmailException {
Map<String, Object> attributes = new HashMap<String, Object>();
attributes.put("user", new ProfileBean(user));
attributes.put("link", link);
attributes.put("linkExpiration", expirationInMinutes);
attributes.put("realmName", getRealmName());
send("emailVerificationSubject", "email-verification.ftl", attributes);
}
private void send(String subjectKey, String template, Map<String, Object> attributes) throws EmailException {
send(subjectKey, Collections.emptyList(), template, attributes);
}
private void send(String subjectKey, List<Object> subjectAttributes, String template, Map<String, Object> attributes) throws EmailException {
try {
ThemeProvider themeProvider = session.getProvider(ThemeProvider.class, "extending");
Theme theme = themeProvider.getTheme(realm.getEmailTheme(), Theme.Type.EMAIL);
Locale locale = session.getContext().resolveLocale(user);
attributes.put("locale", locale);
Properties rb = theme.getMessages(locale);
attributes.put("msg", new MessageFormatterMethod(locale, rb));
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;
}
send(subject, textBody, htmlBody);
} catch (Exception e) {
throw new EmailException("Failed to template email", e);
}
}
private void send(String subject, String textBody, String htmlBody) throws EmailException {
EmailSenderProvider emailSender = session.getProvider(EmailSenderProvider.class);
emailSender.send(realm, user, subject, textBody, htmlBody);
}
@Override
public void close() {
}
private String toCamelCase(EventType event){
StringBuilder sb = new StringBuilder("event");
for(String s : event.name().toString().toLowerCase().split("_")){
sb.append(ObjectUtil.capitalize(s));
}
return sb.toString();
}
}