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

io.gravitee.rest.api.service.impl.MessageServiceImpl Maven / Gradle / Ivy

There is a newer version: 3.10.0
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.rest.api.service.impl;

import static io.gravitee.rest.api.service.impl.MessageServiceImpl.MessageEvent.MESSAGE_SENT;

import freemarker.template.TemplateException;
import io.gravitee.common.http.HttpMethod;
import io.gravitee.repository.exceptions.TechnicalException;
import io.gravitee.repository.management.api.ApiRepository;
import io.gravitee.repository.management.api.SubscriptionRepository;
import io.gravitee.repository.management.api.search.SubscriptionCriteria;
import io.gravitee.repository.management.model.Api;
import io.gravitee.repository.management.model.Audit;
import io.gravitee.repository.management.model.Subscription;
import io.gravitee.rest.api.model.*;
import io.gravitee.rest.api.model.permissions.RoleScope;
import io.gravitee.rest.api.service.*;
import io.gravitee.rest.api.service.builder.EmailNotificationBuilder;
import io.gravitee.rest.api.service.common.GraviteeContext;
import io.gravitee.rest.api.service.exceptions.*;
import io.gravitee.rest.api.service.notification.ApiHook;
import io.gravitee.rest.api.service.notification.Hook;
import io.gravitee.rest.api.service.notification.NotificationTemplateService;
import io.gravitee.rest.api.service.notification.PortalHook;
import java.util.*;
import java.util.stream.Collectors;
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.Value;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

/**
 * @author Nicolas GERAUD (nicolas.geraud at graviteesource.com)
 * @author GraviteeSource Team
 */
@Component
public class MessageServiceImpl extends AbstractService implements MessageService, InitializingBean {

    private final Logger LOGGER = LoggerFactory.getLogger(MessageServiceImpl.class);

    @Autowired
    ApiRepository apiRepository;

    @Autowired
    MembershipService membershipService;

    @Autowired
    SubscriptionRepository subscriptionRepository;

    @Autowired
    PortalNotificationService portalNotificationService;

    @Autowired
    UserService userService;

    @Autowired
    AuditService auditService;

    @Autowired
    EmailService emailService;

    @Autowired
    ApiService apiService;

    @Autowired
    ApplicationService applicationService;

    @Autowired
    RoleService roleService;

    @Autowired
    GroupService groupService;

    @Autowired
    HttpClientService httpClientService;

    @Autowired
    private NotificationTemplateService notificationTemplateService;

    @Value("${notifiers.webhook.enabled:true}")
    private boolean httpEnabled;

    @Autowired
    private Environment environment;

    private List httpWhitelist;

    @Override
    public void afterPropertiesSet() {
        int i = 0;
        httpWhitelist = new ArrayList<>();

        String whitelistUrl;

        while ((whitelistUrl = environment.getProperty("notifiers.webhook.whitelist[" + i + "]")) != null) {
            httpWhitelist.add(whitelistUrl);
            i++;
        }
    }

    public enum MessageEvent implements Audit.AuditEvent {
        MESSAGE_SENT,
    }

    @Override
    public int create(String apiId, MessageEntity message) {
        assertMessageNotEmpty(message);
        try {
            Optional optionalApi = apiRepository.findById(apiId);
            if (!optionalApi.isPresent()) {
                throw new ApiNotFoundException(apiId);
            }
            Api api = optionalApi.get();

            int msgSize = send(api, message, getRecipientsId(api, message));

            auditService.createApiAuditLog(apiId, Collections.emptyMap(), MESSAGE_SENT, new Date(), null, message);
            return msgSize;
        } catch (TechnicalException ex) {
            LOGGER.error("An error occurs while trying to get create a message", ex);
            throw new TechnicalManagementException("An error occurs while trying to create a message", ex);
        }
    }

    @Override
    public int create(MessageEntity message) {
        assertMessageNotEmpty(message);

        int msgSize = send(null, message, getRecipientsId(message));

        auditService.createEnvironmentAuditLog(Collections.emptyMap(), MESSAGE_SENT, new Date(), null, message);
        return msgSize;
    }

    private int send(Api api, MessageEntity message, Set recipientsId) {
        switch (message.getChannel()) {
            case MAIL:
                Set mails = getRecipientsEmails(recipientsId);
                if (!mails.isEmpty()) {
                    emailService.sendAsyncEmailNotification(
                        new EmailNotificationBuilder()
                            .to(EmailService.DEFAULT_MAIL_TO)
                            .bcc(mails.toArray(new String[0]))
                            .template(EmailNotificationBuilder.EmailTemplate.TEMPLATES_FOR_ACTION_GENERIC_MESSAGE)
                            .param("message", message.getText())
                            .param("messageSubject", message.getTitle())
                            .build(),
                        GraviteeContext.getCurrentContext()
                    );
                }
                return mails.size();
            case PORTAL:
                Hook hook = api == null ? PortalHook.MESSAGE : ApiHook.MESSAGE;
                portalNotificationService.create(hook, new ArrayList<>(recipientsId), getPortalParams(api, message));
                return recipientsId.size();
            case HTTP:
                if (!httpEnabled) {
                    throw new NotifierDisabledException();
                }

                String url = recipientsId.iterator().next();

                environment.getProperty("notifiers.webhook.whitelist");

                if (httpWhitelist != null && !httpWhitelist.isEmpty()) {
                    // Check the provided url is allowed.
                    if (
                        httpWhitelist
                            .stream()
                            .noneMatch(
                                whitelistUrl ->
                                    whitelistUrl.endsWith("/")
                                        ? url.startsWith(whitelistUrl)
                                        : (url.equals(whitelistUrl) || url.startsWith(whitelistUrl + '/'))
                            )
                    ) {
                        throw new MessageUrlForbiddenException();
                    }
                }

                httpClientService.request(
                    HttpMethod.POST,
                    url,
                    message.getParams(),
                    getPostMessage(api, message),
                    Boolean.valueOf(message.isUseSystemProxy())
                );
                return 1;
            default:
                return 0;
        }
    }

    @Override
    public Set getRecipientsId(MessageEntity message) {
        if (MessageChannel.HTTP.equals(message.getChannel())) {
            return Collections.singleton(message.getRecipient().getUrl());
        }
        return getRecipientsId(null, message);
    }

    @Override
    public Set getRecipientsId(Api api, MessageEntity message) {
        if (message != null && MessageChannel.HTTP.equals(message.getChannel())) {
            return Collections.singleton(message.getRecipient().getUrl());
        }
        assertRecipientsNotEmpty(message);
        MessageRecipientEntity recipientEntity = message.getRecipient();
        // 2 cases are implemented :
        // - global sending (no apiId provided) + scope ENVIRONMENT
        // - api consumer (apiId provided) + scope APPLICATION
        // the first 2 cases are for admin communication, the last one for the api publisher communication.

        try {
            final Set recipientIds = new HashSet<>();
            // CASE 1 : global sending
            if (api == null && RoleScope.ENVIRONMENT.name().equals(recipientEntity.getRoleScope())) {
                for (String roleName : recipientEntity.getRoleValues()) {
                    Optional optRole = roleService.findByScopeAndName(
                        RoleScope.valueOf(recipientEntity.getRoleScope()),
                        roleName
                    );
                    if (optRole.isPresent()) {
                        recipientIds.addAll(
                            membershipService
                                .getMembershipsByReferenceAndRole(
                                    MembershipReferenceType.ENVIRONMENT,
                                    GraviteeContext.getCurrentEnvironment(),
                                    optRole.get().getId()
                                )
                                .stream()
                                .map(MembershipEntity::getMemberId)
                                .collect(Collectors.toSet())
                        );
                    }
                }
            }
            // CASE 2 : specific api consumers
            else if (api != null && RoleScope.APPLICATION.name().equals(recipientEntity.getRoleScope())) {
                // Get apps allowed to consume the api
                List applicationIds = subscriptionRepository
                    .search(
                        new SubscriptionCriteria.Builder()
                            .apis(Collections.singleton(api.getId()))
                            .status(Subscription.Status.ACCEPTED)
                            .build()
                    )
                    .stream()
                    .map(Subscription::getApplication)
                    .collect(Collectors.toList());

                // Get members of the applications (direct members and group members)
                for (String roleName : recipientEntity.getRoleValues()) {
                    Optional optRole = roleService.findByScopeAndName(RoleScope.APPLICATION, roleName);
                    if (optRole.isPresent()) {
                        // get all directs members
                        recipientIds.addAll(
                            membershipService
                                .getMembershipsByReferencesAndRole(
                                    MembershipReferenceType.APPLICATION,
                                    applicationIds,
                                    optRole.get().getId()
                                )
                                .stream()
                                .map(MembershipEntity::getMemberId)
                                .collect(Collectors.toSet())
                        );

                        // get all indirect members
                        if (api.getGroups() != null && !api.getGroups().isEmpty()) {
                            recipientIds.addAll(
                                membershipService
                                    .getMembershipsByReferencesAndRole(
                                        MembershipReferenceType.GROUP,
                                        new ArrayList<>(api.getGroups()),
                                        optRole.get().getId()
                                    )
                                    .stream()
                                    .map(MembershipEntity::getMemberId)
                                    .collect(Collectors.toSet())
                            );
                        }
                    }
                }
            }
            return recipientIds;
        } catch (TechnicalException ex) {
            LOGGER.error("An error occurs while trying to get recipients", ex);
            throw new TechnicalManagementException("An error occurs while trying to get recipients", ex);
        }
    }

    private Set getRecipientsEmails(Set recipientsId) {
        if (recipientsId.isEmpty()) {
            return Collections.emptySet();
        }

        Set emails = userService
            .findByIds(new ArrayList<>(recipientsId))
            .stream()
            .filter(userEntity -> !StringUtils.isEmpty(userEntity.getEmail()))
            .map(UserEntity::getEmail)
            .collect(Collectors.toSet());
        return emails;
    }

    private void assertMessageNotEmpty(MessageEntity messageEntity) {
        if (messageEntity == null || (StringUtils.isEmpty(messageEntity.getTitle()) && StringUtils.isEmpty(messageEntity.getText()))) {
            throw new MessageEmptyException();
        }
    }

    private void assertRecipientsNotEmpty(MessageEntity messageEntity) {
        if (
            messageEntity == null ||
            messageEntity.getRecipient() == null ||
            messageEntity.getChannel() == null ||
            messageEntity.getRecipient().getRoleScope() == null ||
            messageEntity.getRecipient().getRoleValues() == null ||
            messageEntity.getRecipient().getRoleValues().isEmpty()
        ) {
            throw new MessageRecipientFormatException();
        }
    }

    private Map getPortalParams(Api api, MessageEntity message) {
        Map params = new HashMap<>();
        params.put("title", message.getTitle());
        params.put("message", message.getText());
        if (api != null) {
            Api paramApi = new Api();
            paramApi.setId(api.getId());
            paramApi.setName(api.getName());
            paramApi.setVersion(api.getVersion());
            params.put("api", paramApi);
        }
        return params;
    }

    private String getPostMessage(Api api, MessageEntity message) {
        if (message.getText() == null || api == null) {
            return message.getText();
        }

        ApiModelEntity apiEntity = apiService.findByIdForTemplates(api.getId());
        Map model = new HashMap<>();
        model.put("api", apiEntity);

        return this.notificationTemplateService.resolveInlineTemplateWithParam(new Date().toString(), message.getText(), model);
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy