All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.gravitee.am.gateway.handler.common.email.impl.EmailServiceImpl Maven / Gradle / Ivy

There is a newer version: 4.6.0-alpha.2
Show newest version
/**
 * Copyright (C) 2015 The Gravitee team (http://gravitee.io)
 *
 * 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 io.gravitee.am.gateway.handler.common.email.impl;

import com.google.common.base.Strings;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import io.gravitee.am.common.email.Email;
import io.gravitee.am.common.email.EmailBuilder;
import io.gravitee.am.common.jwt.Claims;
import io.gravitee.am.common.jwt.JWT;
import io.gravitee.am.common.utils.ConstantKeys;
import io.gravitee.am.gateway.handler.common.email.EmailManager;
import io.gravitee.am.gateway.handler.common.email.EmailService;
import io.gravitee.am.gateway.handler.common.utils.FreemarkerDataHelper;
import io.gravitee.am.jwt.JWTBuilder;
import io.gravitee.am.model.Domain;
import io.gravitee.am.model.User;
import io.gravitee.am.model.oidc.Client;
import io.gravitee.am.model.safe.ClientProperties;
import io.gravitee.am.model.safe.DomainProperties;
import io.gravitee.am.model.safe.UserProperties;
import io.gravitee.am.service.AuditService;
import io.gravitee.am.service.DomainService;
import io.gravitee.am.service.i18n.FreemarkerMessageResolver;
import io.gravitee.am.service.i18n.GraviteeMessageResolver;
import io.gravitee.am.service.reporter.builder.AuditBuilder;
import io.gravitee.am.service.reporter.builder.EmailAuditBuilder;
import io.vertx.rxjava3.core.MultiMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;

import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;

import static io.gravitee.am.common.oauth2.Parameters.CLIENT_ID;
import static io.gravitee.am.common.web.UriBuilder.encodeURIComponent;
import static io.gravitee.am.service.utils.UserProfileUtils.preferredLanguage;

/**
 * @author Titouan COMPIEGNE (titouan.compiegne at graviteesource.com)
 * @author GraviteeSource Team
 */
public class EmailServiceImpl implements EmailService, InitializingBean {

    private final boolean enabled;
    private final String resetPasswordSubject;
    private final Integer resetPasswordExpireAfter;
    private final String blockedAccountSubject;
    private final Integer blockedAccountExpireAfter;
    private final String mfaChallengeSubject;
    private final Integer mfaChallengeExpireAfter;
    private final String mfaVerifyAttemptSubject;
    private final String registrationVerifySubject;
    private final int userRegistrationExpireAfter;
    @Autowired
    private EmailManager emailManager;

    @Autowired
    private io.gravitee.am.service.EmailService emailService;

    @Autowired
    private Configuration freemarkerConfiguration;

    @Autowired
    private Domain domain;

    @Autowired
    private AuditService auditService;

    @Autowired
    @Qualifier("managementJwtBuilder")
    private JWTBuilder jwtBuilder;

    @Autowired
    private DomainService domainService;

    @Autowired
    private GraviteeMessageResolver graviteeMessageResolver;

    public EmailServiceImpl(
            boolean enabled,
            String resetPasswordSubject,
            int resetPasswordExpireAfter,
            String blockedAccountSubject,
            int blockedAccountExpireAfter,
            String mfaChallengeSubject,
            int mfaChallengeExpireAfter,
            String mfaVerifyAttemptSubject,
            String registrationVerifySubject,
            int userRegistrationExpiresAfter) {
        this.enabled = enabled;
        this.resetPasswordSubject = resetPasswordSubject;
        this.resetPasswordExpireAfter = resetPasswordExpireAfter;
        this.blockedAccountSubject = blockedAccountSubject;
        this.blockedAccountExpireAfter = blockedAccountExpireAfter;
        this.mfaChallengeSubject = mfaChallengeSubject;
        this.mfaChallengeExpireAfter = mfaChallengeExpireAfter;
        this.mfaVerifyAttemptSubject = mfaVerifyAttemptSubject;
        this.registrationVerifySubject = registrationVerifySubject;
        this.userRegistrationExpireAfter = userRegistrationExpiresAfter;
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        this.emailService.setDictionaryProvider(this.graviteeMessageResolver.getDynamicDictionaryProvider());
    }

    @Override
    public void send(io.gravitee.am.model.Template template, User user, Client client, MultiMap queryParams) {
        if (enabled) {
            // get raw email template
            io.gravitee.am.model.Email emailTemplate = getEmailTemplate(template, client);
            // prepare email
            Email email = prepareEmail(template, emailTemplate, user, client, queryParams);
            // send email
            sendEmail(email, user, client);
        }
    }

    @Override
    public void send(Email email) {
        if (enabled) {
            try {

                Locale language = Locale.ENGLISH;
                if (email.getParams().containsKey(ConstantKeys.USER_CONTEXT_KEY)) {
                    language = preferredLanguage((User) email.getParams().get(ConstantKeys.USER_CONTEXT_KEY), Locale.ENGLISH);
                }

                // sanitize data
                final Map params = FreemarkerDataHelper.generateData(email.getParams());

                // compute email to
                final List to = new ArrayList<>();
                for (String emailTo : email.getTo()) {
                    to.add(processTemplate(
                            new Template("to", new StringReader(emailTo), freemarkerConfiguration),
                            params, language));
                }
                // compute email from
                final String from = processTemplate(
                        new Template("from", new StringReader(email.getFrom()), freemarkerConfiguration),
                        params, language);
                // compute email fromName
                final String fromName = Strings.isNullOrEmpty(email.getFromName()) ? null : processTemplate(
                        new Template("fromName", new StringReader(email.getFromName()), freemarkerConfiguration),
                        params, language);
                // compute email subject
                final String subject = processTemplate(
                        new Template("subject", new StringReader(email.getSubject()), freemarkerConfiguration),
                        params, language);
                // compute email content
                final String content = processTemplate(
                        new Template("content", new StringReader(email.getContent()), freemarkerConfiguration),
                        params, language);
                // send the email
                final Email emailToSend = new Email(email);
                emailToSend.setFrom(from);
                emailToSend.setFromName(fromName);
                emailToSend.setTo(to.toArray(new String[0]));
                emailToSend.setSubject(subject);
                emailToSend.setContent(content);
                emailService.send(emailToSend);
                auditService.report(AuditBuilder.builder(EmailAuditBuilder.class).domain(domain.getId()).email(email));
            } catch (Exception ex) {
                auditService.report(AuditBuilder.builder(EmailAuditBuilder.class).domain(domain.getId()).email(email).throwable(ex));
            }
        }
    }

    private void sendEmail(Email email, User user, Client client) {
        try {
            final Locale preferredLanguage = preferredLanguage(user, Locale.ENGLISH);

            // compute email subject
            final Template plainTextTemplate = new Template("subject", new StringReader(email.getSubject()), freemarkerConfiguration);
            final String subject = processTemplate(plainTextTemplate, email.getParams(), preferredLanguage);

            // compute email content
            final Template template = freemarkerConfiguration.getTemplate(email.getTemplate());
            final String content = processTemplate(template, email.getParams(), preferredLanguage);

            final Email emailToSend = new Email(email);
            emailToSend.setSubject(subject);
            emailToSend.setContent(content);
            emailService.send(emailToSend);
            auditService.report(AuditBuilder.builder(EmailAuditBuilder.class).domain(domain.getId()).client(client).email(email).user(user));
        } catch (final Exception ex) {
            auditService.report(AuditBuilder.builder(EmailAuditBuilder.class).domain(domain.getId()).client(client).email(email).throwable(ex));
        }
    }

    private Email prepareEmail(io.gravitee.am.model.Template template, io.gravitee.am.model.Email emailTemplate, User user, Client client, MultiMap queryParams) {
        Map params = prepareEmailParams(user, client, emailTemplate.getExpiresAfter(), template.redirectUri(), queryParams);
        return new EmailBuilder()
                .to(user.getEmail())
                .from(emailTemplate.getFrom())
                .fromName(emailTemplate.getFromName())
                .subject(emailTemplate.getSubject())
                .template(emailTemplate.getTemplate())
                .params(params)
                .build();
    }

    private Map prepareEmailParams(User user, Client client, Integer expiresAfter, String path, MultiMap queryParams) {
        // generate a JWT to store user's information and for security purpose
        final Map claims = new HashMap<>();
        Instant now = Instant.now();
        claims.put(Claims.iat, now.getEpochSecond());
        claims.put(Claims.exp, now.plusSeconds(expiresAfter).getEpochSecond());
        claims.put(Claims.sub, user.getId());
        if (client != null) {
            claims.put(Claims.aud, client.getId());
        }

        if (client != null && !queryParams.contains(CLIENT_ID)) {
            queryParams.add(CLIENT_ID, encodeURIComponent(client.getClientId()));
        }

        String token = jwtBuilder.sign(new JWT(claims));
        queryParams.add("token", token);

        Map params = new HashMap<>();
        params.put("user", new UserProperties(user));
        params.put("token", token);
        params.put("expireAfterSeconds", expiresAfter);
        params.put("domain", new DomainProperties(domain));

        if (client != null) {
            params.put("client", new ClientProperties(client));
        }

        params.put("url", domainService.buildUrl(domain, path, queryParams));

        return params;
    }

    protected io.gravitee.am.model.Email getEmailTemplate(io.gravitee.am.model.Template template, Client client) {
        return emailManager.getEmail(getTemplateName(template, client), getDefaultSubject(template), getDefaultExpireAt(template));
    }

    @Override
    public EmailWrapper createEmail(io.gravitee.am.model.Template template, Client client, List recipients, Map params, Locale preferredLanguage) throws IOException, TemplateException {
        io.gravitee.am.model.Email emailTpl = getEmailTemplate(template, client);
        params.putIfAbsent("expireAfterSeconds", emailTpl.getExpiresAfter());
        final long expiresAt = Instant.now().plus(emailTpl.getExpiresAfter(), ChronoUnit.SECONDS).toEpochMilli();
        params.putIfAbsent("expireAt", expiresAt);

        io.gravitee.am.common.email.Email email = new EmailBuilder()
                .from(emailTpl.getFrom())
                .fromName(emailTpl.getFromName())
                .template(emailTpl.getTemplate())
                .to(recipients.toArray(new String[recipients.size()]))
                .build();

        // compute email subject
        final Template plainTextTemplate = new Template("subject", new StringReader(emailTpl.getSubject()), freemarkerConfiguration);
        email.setSubject(processTemplate(plainTextTemplate, params, preferredLanguage));

        // compute email content
        final Template subjectTemplate = freemarkerConfiguration.getTemplate(email.getTemplate());
        email.setContent(processTemplate(subjectTemplate, params, preferredLanguage));

        EmailWrapper wrapper = new EmailWrapper(email);
        wrapper.setExpireAt(expiresAt);
        wrapper.setFromDefaultTemplate(emailTpl.isDefaultTemplate());
        return wrapper;
    }

    private String processTemplate(Template plainTextTemplate, Map params, Locale preferredLanguage) throws TemplateException, IOException {
        var result = new StringWriter(1024);

        var dataModel = new HashMap<>(params);
        dataModel.put(FreemarkerMessageResolver.METHOD_NAME, new FreemarkerMessageResolver(this.emailService.getDictionaryProvider().getDictionaryFor(preferredLanguage)));

        var env = plainTextTemplate.createProcessingEnvironment(dataModel, result);
        env.process();

        return result.toString();
    }

    private String getTemplateName(io.gravitee.am.model.Template template, Client client) {
        return template.template() + ((client != null) ? EmailManager.TEMPLATE_NAME_SEPARATOR + client.getId() : "");
    }

    private String getDefaultSubject(io.gravitee.am.model.Template template) {
        switch (template) {
            case RESET_PASSWORD:
                return resetPasswordSubject;
            case BLOCKED_ACCOUNT:
                return blockedAccountSubject;
            case MFA_CHALLENGE:
                return mfaChallengeSubject;
            case VERIFY_ATTEMPT:
                return mfaVerifyAttemptSubject;
            case REGISTRATION_VERIFY:
                return registrationVerifySubject;
            default:
                throw new IllegalArgumentException(template.template() + " not found");
        }
    }

    private Integer getDefaultExpireAt(io.gravitee.am.model.Template template) {
        switch (template) {
            case RESET_PASSWORD:
                return resetPasswordExpireAfter;
            case BLOCKED_ACCOUNT:
                return blockedAccountExpireAfter;
            case MFA_CHALLENGE:
                return mfaChallengeExpireAfter;
            case VERIFY_ATTEMPT:
                return 0;
            case REGISTRATION_VERIFY:
                return userRegistrationExpireAfter;
            default:
                throw new IllegalArgumentException(template.template() + " not found");
        }
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy