From 5e21d739b12e0095806753b794fc3e0cdb598814 Mon Sep 17 00:00:00 2001 From: Markus Meyer Date: Fri, 5 Apr 2024 09:03:44 +0200 Subject: [PATCH] add plugin "webhook" --- plugins/webhook/CONTRIBUTING.md | 85 ++++++++ plugins/webhook/LICENSE | 202 ++++++++++++++++++ plugins/webhook/MAINTAINERS | 5 + plugins/webhook/README.md | 23 ++ plugins/webhook/SECURITY.md | 5 + plugins/webhook/pom.xml | 186 ++++++++++++++++ .../com/freenow/sauron/plugins/Webhook.java | 96 +++++++++ .../freenow/sauron/plugins/WebhookTest.java | 105 +++++++++ pom.xml | 1 + 9 files changed, 708 insertions(+) create mode 100644 plugins/webhook/CONTRIBUTING.md create mode 100644 plugins/webhook/LICENSE create mode 100644 plugins/webhook/MAINTAINERS create mode 100644 plugins/webhook/README.md create mode 100644 plugins/webhook/SECURITY.md create mode 100644 plugins/webhook/pom.xml create mode 100644 plugins/webhook/src/main/java/com/freenow/sauron/plugins/Webhook.java create mode 100644 plugins/webhook/src/test/java/com/freenow/sauron/plugins/WebhookTest.java diff --git a/plugins/webhook/CONTRIBUTING.md b/plugins/webhook/CONTRIBUTING.md new file mode 100644 index 0000000..d6f36c1 --- /dev/null +++ b/plugins/webhook/CONTRIBUTING.md @@ -0,0 +1,85 @@ +# Contributing + +When contributing to this repository, please first discuss the change you wish to make via issue, +email, or any other method with the owners of this repository before making a change. + +Please note we have a code of conduct, please follow it in all your interactions with the project. + + +1. Ensure any install or build dependencies are removed before the end of the layer when doing a + build. +2. Update the README.md and CHANGELOG.md with details of changes to the interface, this includes new environment + variables, exposed ports, useful file locations and container parameters. +3. Increase the version numbers in any examples files and the README.md to the new version that this + Pull Request would represent. The versioning scheme we use is [SemVer](http://semver.org/). +4. You may merge the Pull Request in once you have the sign-off of two other developers, or if you + do not have permission to do that, you may request the second reviewer to merge it for you. + + + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, gender identity and expression, level of experience, +nationality, personal appearance, race, religion, or sexual identity and +orientation. + + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or +advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at +[upscaler.team@free-now.com](mailto:upscaler.team@free-now.com). All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at [http://contributor-covenant.org/version/1/4][version] + +[homepage]: http://contributor-covenant.org +[version]: http://contributor-covenant.org/version/1/4/ \ No newline at end of file diff --git a/plugins/webhook/LICENSE b/plugins/webhook/LICENSE new file mode 100644 index 0000000..9063fad --- /dev/null +++ b/plugins/webhook/LICENSE @@ -0,0 +1,202 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [2020] Intelligent Apps GmbH + + 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. + diff --git a/plugins/webhook/MAINTAINERS b/plugins/webhook/MAINTAINERS new file mode 100644 index 0000000..502f653 --- /dev/null +++ b/plugins/webhook/MAINTAINERS @@ -0,0 +1,5 @@ +Sergio Santiago (@sergioasantiago) +Felix Oldenburg (@felixoldenburg) +Luan Lucas Lourenco (@luanlucas) +Cristian Silva (@ocristian) +Mohammed Ammer (@mohammedalics) diff --git a/plugins/webhook/README.md b/plugins/webhook/README.md new file mode 100644 index 0000000..a4e5a6a --- /dev/null +++ b/plugins/webhook/README.md @@ -0,0 +1,23 @@ +# Webhook Plugin + +## Description + +This plugin sends HTTP requests to third-party endpoints to trigger further processing. + +## Configuration + +```yaml +- webhook: + endpoints: + - url: "http://example.localhost/trigger" # URL to send the request to. Required. + method: "POST" # HTTP method to use. Default is POST. + includeDataSet: false # Send the Sauron Data Set as the body of the request. Default is false. +``` + +## Input + +This plugin doesn't require specific input keys to be set. + +## Output + +This plugin doesn't modify the Sauron Data Set. It returns the Data Set as is. diff --git a/plugins/webhook/SECURITY.md b/plugins/webhook/SECURITY.md new file mode 100644 index 0000000..2c0f64c --- /dev/null +++ b/plugins/webhook/SECURITY.md @@ -0,0 +1,5 @@ +We acknowledge that every line of code that we write may potentially contain security issues. + +We are trying to deal with it responsibly and provide patches as quickly as possible. If you have anything to report to us please use the following channels: + +Email: security-incident@free-now.com diff --git a/plugins/webhook/pom.xml b/plugins/webhook/pom.xml new file mode 100644 index 0000000..aeac591 --- /dev/null +++ b/plugins/webhook/pom.xml @@ -0,0 +1,186 @@ + + + 4.0.0 + + webhook + com.free-now.sauron.plugins + Sauron Webhook + Sauron Webhook Plugin + 0.0.1-SNAPSHOT + + https://github.com/freenowtech/sauron + + + + Apache License Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + + + + + + Markus Meyer + m.meyer@mytaxi.com + + + + + webhook + com.freenow.sauron.plugins.SauronPlugin + freenow + + 11 + 11 + 2.5.3 + scm:git:git@github.com:freenowtech/sauron.git + 5.3.1 + + + + HEAD + ${scm.url} + ${scm.url} + ${scm.url} + + + + + ossrh + https://s01.oss.sonatype.org/content/repositories/snapshots + + + ossrh + https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/ + + + + + + com.free-now.sauron + sauron-core + 0.0.15 + provided + + + + org.springframework + spring-web + ${spring.version} + + + + junit + junit + 4.13.1 + + + + org.mockito + mockito-core + 2.25.1 + test + + + + + + + maven-assembly-plugin + 3.1.1 + + + + ${plugin.id} + ${plugin.class} + ${project.version} + ${plugin.provider} + ${plugin.dependencies} + + + ${project.build.finalName} + + jar-with-dependencies + + false + + + + make-assembly + package + + single + + + + + + + maven-release-plugin + ${maven-release-plugin.version} + + [ci skip] + + + + + org.apache.maven.plugins + maven-gpg-plugin + 1.5 + + + sign-artifacts + verify + + sign + + + + + + + org.apache.maven.plugins + maven-source-plugin + 3.2.1 + + + attach-sources + + jar-no-fork + + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + 3.2.0 + + 8 + false + + + + attach-javadocs + + jar + + + + + + + org.sonatype.plugins + nexus-staging-maven-plugin + 1.6.8 + true + + ossrh + https://s01.oss.sonatype.org/ + true + + + + + diff --git a/plugins/webhook/src/main/java/com/freenow/sauron/plugins/Webhook.java b/plugins/webhook/src/main/java/com/freenow/sauron/plugins/Webhook.java new file mode 100644 index 0000000..50157e2 --- /dev/null +++ b/plugins/webhook/src/main/java/com/freenow/sauron/plugins/Webhook.java @@ -0,0 +1,96 @@ +package com.freenow.sauron.plugins; + +import com.freenow.sauron.model.DataSet; +import com.freenow.sauron.properties.PluginsConfigurationProperties; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import lombok.extern.slf4j.Slf4j; +import org.pf4j.Extension; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpMethod; +import org.springframework.http.ResponseEntity; +import org.springframework.web.client.RestTemplate; + +/** + * Webhook sends a request to an HTTP endpoint to trigger further processing. + * Multiple endpoints can be configured. + *

+ * Each configuration of an endpoint can have the following properties: + *

+ * url - the full URL to send the request to. For example, http://example.localhost/trigger. + * method - the HTTP method to use. Default to POST. + * includeDataSet - boolean flag to indicate if the Sauron Data Set should be sent as part of the request. + * Default is false, which means that no Data Set is sent. + */ +@Slf4j +@Extension +public class Webhook implements SauronExtension +{ + private static final String PLUGIN_ID = "webhook"; + private static final String PROPERTY_ENDPOINTS = "endpoints"; + private static final String PROPERTY_INCLUDE_DATASET = "includeDataSet"; + private static final String PROPERTY_METHOD = "method"; + private static final String PROPERTY_URL = "url"; + + private final RestTemplate restTemplate; + + private static final Logger logger = LoggerFactory.getLogger(Webhook.class.getName()); + + public Webhook() + { + this.restTemplate = new RestTemplate(); + } + + public Webhook(RestTemplate restTemplate) + { + this.restTemplate = restTemplate; + } + + /** + * Implements SauronExtension. + * + * @param properties Configuration of the plugin. + * @param input Data set currently being processed by Sauron. + * @return DataSet + */ + @Override + public DataSet apply(PluginsConfigurationProperties properties, DataSet input) { + properties.getPluginConfigurationProperty(PLUGIN_ID, PROPERTY_ENDPOINTS).ifPresent(endpoints -> { + for (Map endpoint : (List>) endpoints) + { + String url = (String) endpoint.getOrDefault(PROPERTY_URL, ""); + if (Objects.equals(url, "")) + { + logger.error("URL of webhook endpoint not set"); + continue; + } + + String methodRaw = (String) endpoint.getOrDefault(PROPERTY_METHOD, "POST"); + HttpMethod method = HttpMethod.resolve(methodRaw); + if (method == null) + { + logger.error("HTTP method {} of webhook endpoint is invalid", methodRaw); + continue; + } + + HttpEntity payload = null; + Boolean includeDataSet = (Boolean) endpoint.getOrDefault(PROPERTY_INCLUDE_DATASET, false); + if (includeDataSet) + { + payload = new HttpEntity<>(input); + } + + ResponseEntity response = restTemplate.exchange(url, method, payload, Object.class); + if (!response.getStatusCode().is2xxSuccessful()) + { + logger.error("Sending webhook to {} failed with HTTP status code {}", url, response.getStatusCode()); + } + } + }); + + return input; + } +} diff --git a/plugins/webhook/src/test/java/com/freenow/sauron/plugins/WebhookTest.java b/plugins/webhook/src/test/java/com/freenow/sauron/plugins/WebhookTest.java new file mode 100644 index 0000000..5267291 --- /dev/null +++ b/plugins/webhook/src/test/java/com/freenow/sauron/plugins/WebhookTest.java @@ -0,0 +1,105 @@ +package com.freenow.sauron.plugins; + +import com.freenow.sauron.model.DataSet; +import com.freenow.sauron.properties.PluginsConfigurationProperties; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.junit.Test; +import org.mockito.Mockito; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.client.RestTemplate; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +public class WebhookTest +{ + private final RestTemplate restTemplate = Mockito.mock(RestTemplate.class); + + @Test + public void testNotifierApply() + { + Mockito.when(restTemplate.exchange(any(String.class), any(HttpMethod.class), any(), eq(Object.class))) + .thenReturn(new ResponseEntity<>(HttpStatus.CREATED)); + + Webhook plugin = new Webhook(restTemplate); + DataSet dataSet = new DataSet(); + plugin.apply(createPluginConfigurationProperties(null, false), dataSet); + + verify(restTemplate, times(1)) + .exchange( + eq("https://backstage.localhost/events/sauron"), + eq(HttpMethod.POST), + eq(null), + eq(Object.class) + ); + } + + @Test + public void testNotifierApply_customHttpMethod() + { + Mockito.when(restTemplate.exchange(any(String.class), any(HttpMethod.class), any(), eq(Object.class))) + .thenReturn(new ResponseEntity<>(HttpStatus.CREATED)); + + Webhook plugin = new Webhook(restTemplate); + DataSet dataSet = new DataSet(); + plugin.apply(createPluginConfigurationProperties("PUT", false), dataSet); + + verify(restTemplate, times(1)) + .exchange( + eq("https://backstage.localhost/events/sauron"), + eq(HttpMethod.PUT), + eq(null), + eq(Object.class) + ); + } + + @Test + public void testNotifierApply_includeDataSet() + { + Mockito.when(restTemplate.exchange(any(String.class), any(HttpMethod.class), any(), eq(Object.class))) + .thenReturn(new ResponseEntity<>(HttpStatus.CREATED)); + + Webhook plugin = new Webhook(restTemplate); + DataSet dataSet = new DataSet(); + dataSet.setServiceName("TestService"); + plugin.apply(createPluginConfigurationProperties("POST", true), dataSet); + + verify(restTemplate, times(1)) + .exchange( + eq("https://backstage.localhost/events/sauron"), + eq(HttpMethod.POST), + eq(new HttpEntity<>(dataSet)), + eq(Object.class) + ); + } + + private PluginsConfigurationProperties createPluginConfigurationProperties(String method, Boolean includeDataSet) + { + Map endpoint = new HashMap<>(); + endpoint.put("url", "https://backstage.localhost/events/sauron"); + if (method != null) + { + endpoint.put("method", method); + } + + if (includeDataSet) + { + endpoint.put("includeDataSet", includeDataSet); + } + + return new PluginsConfigurationProperties() + {{ + put("webhook", new HashMap<>() + {{ + put("endpoints", List.of(endpoint)); + }}); + }}; + } +} diff --git a/pom.xml b/pom.xml index 7b4e370..bcdaa8a 100644 --- a/pom.xml +++ b/pom.xml @@ -44,5 +44,6 @@ plugins/bcrypt-passwordencoder-checker plugins/jaegerapi-report plugins/cleanup + plugins/webhook