From 4fe0f08e4a0ddbf6a385de9b638ff0e47acbd472 Mon Sep 17 00:00:00 2001 From: CChemin Date: Wed, 20 Jul 2022 12:52:25 +0200 Subject: [PATCH] [BUG] :boom: Fix getting external template on webhook call by loading from external source instead of directly from userstorage Signed-off-by: CChemin --- docs/realm-configuration.md | 10 +-- .../webhook/service/TemplateConfigKeys.java | 45 +++++++++++ .../service/impl/WebHookServiceImpl.java | 76 +++++++++++++------ 3 files changed, 102 insertions(+), 29 deletions(-) create mode 100644 sugoi-api-event-webhook/src/main/java/fr/insee/sugoi/event/listener/webhook/service/TemplateConfigKeys.java diff --git a/docs/realm-configuration.md b/docs/realm-configuration.md index 3b2b1ab8..b2b7ca4e 100644 --- a/docs/realm-configuration.md +++ b/docs/realm-configuration.md @@ -121,11 +121,11 @@ These configuration should be set for each UserStorage contained in a Realm : Those are optional properties to set on a userstorage. If the property is not set at the userstorage level, the corresponding realm property will be used as default if set. -| Key | Description | -| ---------------------------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | -| {name}\_send_login_template with name being a configured external webservice | A template to complete and send to the {name} webservice on /send-login call (see [Notify external webservices](concepts.md#notify-external-webservices) and [Webhooks configuration](configuration.md#webhooks-configuration)) | -| {name}\_reset_template with name being a configured webservice | A template to complete and send to the {name} webservice on /reinit-password call (see [Notify external webservices](concepts.md#notify-external-webservices) and [Webhooks configuration](configuration.md#webhooks-configuration)) | -| {name}\_changepwd_template with name being a configured webservice | A template to complete and send to the {name} webservice on /change-password call (see [Notify external webservices](concepts.md#notify-external-webservices) and [Webhooks configuration](configuration.md#webhooks-configuration)) | +| Key | Description | Example | +| ---------------------------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | -------- | +| send_login_template | The url of each template to complete and send to a webservice on /send-login call (see [Notify external webservices](concepts.md#notify-external-webservices) and [Webhooks configuration](configuration.md#webhooks-configuration)). Each configuration webhook should be prefixed by {name of the webhook}`:` and separated from other webhooks with a `\|` | spoc:https://spoc.insee.fr/template|test:http://test.insee.fr | +| reset_template | The url of each template to complete and send to a webservice on /reinit-password call (see [Notify external webservices](concepts.md#notify-external-webservices) and [Webhooks configuration](configuration.md#webhooks-configuration)). Each configuration webhook should be prefixed by {name of the webhook}`:` and separated from other webhooks with a `\|` | spoc:https://spoc.insee.fr/template|test:http://test.insee.fr | +| changepwd_template | The url of each template to complete and send to a webservice on /change-password call (see [Notify external webservices](concepts.md#notify-external-webservices) and [Webhooks configuration](configuration.md#webhooks-configuration)). Each configuration webhook should be prefixed by {name of the webhook}`:` and separated from other webhooks with a `\|` | spoc:https://spoc.insee.fr/template|test:http://test.insee.fr | ### Userstorage configuration properties on password diff --git a/sugoi-api-event-webhook/src/main/java/fr/insee/sugoi/event/listener/webhook/service/TemplateConfigKeys.java b/sugoi-api-event-webhook/src/main/java/fr/insee/sugoi/event/listener/webhook/service/TemplateConfigKeys.java new file mode 100644 index 00000000..ddf2dbcc --- /dev/null +++ b/sugoi-api-event-webhook/src/main/java/fr/insee/sugoi/event/listener/webhook/service/TemplateConfigKeys.java @@ -0,0 +1,45 @@ +/* +* 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 fr.insee.sugoi.event.listener.webhook.service; + +import fr.insee.sugoi.model.RealmConfigKeys; +import java.util.Arrays; +import java.util.Optional; + +public enum TemplateConfigKeys implements RealmConfigKeys { + LOGIN_TEMPLATE("send_login_template"), + RESET_TEMPLATE("reset_template"), + CHANGEPWD_TEMPLATE("changepwd_template"); + + private String name; + + TemplateConfigKeys(String name) { + this.name = name; + } + + @Override + public String getName() { + return name; + } + + private static Optional getTemplateConfigKeys(String name) { + return Arrays.stream(TemplateConfigKeys.values()) + .filter(tkc -> tkc.getName().equalsIgnoreCase(name)) + .findFirst(); + } + + public static RealmConfigKeys getRealmConfigKey(String key) { + return getTemplateConfigKeys(key).orElse(null); + } +} diff --git a/sugoi-api-event-webhook/src/main/java/fr/insee/sugoi/event/listener/webhook/service/impl/WebHookServiceImpl.java b/sugoi-api-event-webhook/src/main/java/fr/insee/sugoi/event/listener/webhook/service/impl/WebHookServiceImpl.java index 7f18f37b..cc7ad463 100644 --- a/sugoi-api-event-webhook/src/main/java/fr/insee/sugoi/event/listener/webhook/service/impl/WebHookServiceImpl.java +++ b/sugoi-api-event-webhook/src/main/java/fr/insee/sugoi/event/listener/webhook/service/impl/WebHookServiceImpl.java @@ -15,8 +15,10 @@ import fr.insee.sugoi.core.event.configuration.EventKeysConfig; import fr.insee.sugoi.core.realm.RealmProvider; +import fr.insee.sugoi.event.listener.webhook.service.TemplateConfigKeys; import fr.insee.sugoi.event.listener.webhook.service.WebHookService; import fr.insee.sugoi.model.exceptions.NoReceiverMailException; +import fr.insee.sugoi.model.exceptions.UserStorageNotFoundException; import freemarker.template.Configuration; import freemarker.template.Template; import freemarker.template.TemplateException; @@ -25,10 +27,13 @@ import java.io.StringWriter; import java.io.Writer; import java.nio.charset.StandardCharsets; +import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -50,15 +55,17 @@ public class WebHookServiceImpl implements WebHookService { public static final Logger logger = LoggerFactory.getLogger(WebHookServiceImpl.class); private static final String WEBHOOK_PROPERTY_PREFIX = "sugoi.api.event.webhook."; + private static RestTemplate restTemplate = new RestTemplate(); + @Autowired private Environment env; @Autowired private RealmProvider realmProvider; @Override public void resetPassword(String webHookName, Map values) { - if (!((List) values.get(EventKeysConfig.MAILS)).isEmpty()) { + if (!((List) values.get(EventKeysConfig.MAILS)).isEmpty()) { sendRequestToWebhookFromTemplate( - values, webHookName, "_reset_template", ".default.reset.template"); + values, webHookName, TemplateConfigKeys.RESET_TEMPLATE, ".default.reset.template"); } else { throw new NoReceiverMailException("There is no mail address to send the message to"); } @@ -66,9 +73,12 @@ public void resetPassword(String webHookName, Map values) { @Override public void changePassword(String webHookName, Map values) { - if (!((List) values.get(EventKeysConfig.MAILS)).isEmpty()) { + if (!((List) values.get(EventKeysConfig.MAILS)).isEmpty()) { sendRequestToWebhookFromTemplate( - values, webHookName, "_changepwd_template", ".default.changepwd.template"); + values, + webHookName, + TemplateConfigKeys.CHANGEPWD_TEMPLATE, + ".default.changepwd.template"); } else { throw new NoReceiverMailException("There is no mail address to send the message to"); } @@ -76,9 +86,9 @@ public void changePassword(String webHookName, Map values) { @Override public void sendLogin(String webHookName, Map values) { - if (!((List) values.get(EventKeysConfig.MAILS)).isEmpty()) { + if (!((List) values.get(EventKeysConfig.MAILS)).isEmpty()) { sendRequestToWebhookFromTemplate( - values, webHookName, "_send_login_template", ".default.send-login.template"); + values, webHookName, TemplateConfigKeys.LOGIN_TEMPLATE, ".default.send-login.template"); } else { throw new NoReceiverMailException("There is no mail address to send the message to"); } @@ -87,11 +97,12 @@ public void sendLogin(String webHookName, Map values) { private void sendRequestToWebhookFromTemplate( Map templateProperties, String webHookName, - String realmTemplateSuffix, + TemplateConfigKeys realmTemplateKey, String propertyTemplateSuffix) { String template = loadTemplate( - webHookName + realmTemplateSuffix, + realmTemplateKey, + webHookName, WEBHOOK_PROPERTY_PREFIX + webHookName + propertyTemplateSuffix, (String) templateProperties.get(EventKeysConfig.REALM), (String) templateProperties.get(EventKeysConfig.USERSTORAGE)); @@ -112,7 +123,6 @@ public void send(String target, String content, Map headers) { try { HttpHeaders finalHeaders = new HttpHeaders(); headers.keySet().stream().forEach(header -> finalHeaders.add(header, headers.get(header))); - RestTemplate restTemplate = new RestTemplate(); HttpEntity request = new HttpEntity<>(content, finalHeaders); restTemplate.postForEntity(target, request, String.class); logger.info("Sending webHook to {} success", target); @@ -169,25 +179,43 @@ private String injectValueInTemplate(String content, Map values) } private String loadTemplate( - String realmTemplateConfiguration, + TemplateConfigKeys realmTemplateKey, + String webhookName, String propertyTemplateConfiguration, String realmName, String userStorageName) { - String template = null; - try { - template = - realmProvider.load(realmName).orElseThrow().getUserStorages().stream() - .filter(us -> us.getName().equalsIgnoreCase(userStorageName)) - .findFirst() - .orElseThrow() - .getProperties() - .get(realmTemplateConfiguration); - } catch (Exception e) { - // we don't need to manage this exception here + String templateLocation = + createTemplateLocationMapByWebhook( + realmProvider.load(realmName).orElseThrow().getUserStorages().stream() + .filter(us -> us.getName().equalsIgnoreCase(userStorageName)) + .findFirst() + .orElseThrow(() -> new UserStorageNotFoundException(realmName, userStorageName)) + .getProperties() + .get(realmTemplateKey)) + .get(webhookName); + if (templateLocation != null) { + try { + return restTemplate.getForEntity(templateLocation, String.class).getBody(); + } catch (Exception e) { + logger.error( + "Could not retrieve template at location {} . Falling back on property. {}", + templateLocation, + e.getLocalizedMessage()); + } } - if (template == null && propertyTemplateConfiguration != null) { - template = loadResource(env.getProperty(propertyTemplateConfiguration)); + if (propertyTemplateConfiguration != null) { + return loadResource(env.getProperty(propertyTemplateConfiguration)); } - return template; + return null; + } + + private Map createTemplateLocationMapByWebhook(String allWebhookConfiguration) { + if (allWebhookConfiguration != null) { + return Arrays.stream(allWebhookConfiguration.split("\\|")) + .collect( + Collectors.toMap( + conf -> StringUtils.substringBefore(conf, ":"), + conf -> StringUtils.substringAfter(conf, ":"))); + } else return new HashMap<>(); } }