|
| 1 | +/* |
| 2 | + * The MIT License |
| 3 | + * |
| 4 | + * Copyright (c) 2025, Falco Nikolas |
| 5 | + * |
| 6 | + * Permission is hereby granted, free of charge, to any person obtaining a copy |
| 7 | + * of this software and associated documentation files (the "Software"), to deal |
| 8 | + * in the Software without restriction, including without limitation the rights |
| 9 | + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
| 10 | + * copies of the Software, and to permit persons to whom the Software is |
| 11 | + * furnished to do so, subject to the following conditions: |
| 12 | + * |
| 13 | + * The above copyright notice and this permission notice shall be included in |
| 14 | + * all copies or substantial portions of the Software. |
| 15 | + * |
| 16 | + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| 17 | + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| 18 | + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
| 19 | + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
| 20 | + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
| 21 | + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
| 22 | + * THE SOFTWARE. |
| 23 | + */ |
| 24 | +package com.cloudbees.jenkins.plugins.bitbucket.api.hook; |
| 25 | + |
| 26 | +import com.cloudbees.jenkins.plugins.bitbucket.BitbucketSCMSource; |
| 27 | +import com.cloudbees.jenkins.plugins.bitbucket.api.endpoint.BitbucketEndpoint; |
| 28 | +import edu.umd.cs.findbugs.annotations.NonNull; |
| 29 | +import hudson.ExtensionPoint; |
| 30 | +import jakarta.servlet.http.HttpServletRequest; |
| 31 | +import java.util.Map; |
| 32 | +import java.util.concurrent.TimeUnit; |
| 33 | +import jenkins.scm.api.SCMEvent; |
| 34 | +import jenkins.scm.api.SCMHeadEvent; |
| 35 | +import jenkins.util.SystemProperties; |
| 36 | +import org.apache.commons.collections4.MultiValuedMap; |
| 37 | +import org.kohsuke.accmod.Restricted; |
| 38 | +import org.kohsuke.accmod.restrictions.Beta; |
| 39 | + |
| 40 | +/** |
| 41 | + * Implementations of this extension point must provide new behaviours to |
| 42 | + * accommodate custom event payloads from webhooks sent from Bitbucket Cloud, |
| 43 | + * Bitbucket Data Center, or installed plugins. |
| 44 | + * <p> |
| 45 | + * There cannot be multiple processors processing the same incoming webhook for |
| 46 | + * a specific event installed on the system, meaning the processor must fit to |
| 47 | + * the incoming request as much as possible or the hook will be rejected. |
| 48 | + */ |
| 49 | +@Restricted(Beta.class) |
| 50 | +public interface BitbucketHookProcessor extends ExtensionPoint { |
| 51 | + static final String SCAN_ON_EMPTY_CHANGES_PROPERTY_NAME = "bitbucket.hooks.processor.scanOnEmptyChanges"; |
| 52 | + |
| 53 | + /** |
| 54 | + * Called by first for this processor that must respond if is able to handle |
| 55 | + * this specific request |
| 56 | + * |
| 57 | + * @param headers request |
| 58 | + * @param parameters request |
| 59 | + * @return {@code true} if this processor is able to handle this hook |
| 60 | + * request, {@code false} otherwise. |
| 61 | + */ |
| 62 | + boolean canHandle(@NonNull Map<String, String> headers, @NonNull MultiValuedMap<String, String> parameters); |
| 63 | + |
| 64 | + /** |
| 65 | + * Extracts the server URL from where this request coming from, the URL must |
| 66 | + * match one of the configured {@link BitbucketEndpoint}s. |
| 67 | + * |
| 68 | + * @param headers request |
| 69 | + * @param parameters request |
| 70 | + * @return the URL of the server from where this request has been sent. |
| 71 | + */ |
| 72 | + @NonNull |
| 73 | + String getServerURL(@NonNull Map<String, String> headers, @NonNull MultiValuedMap<String, String> parameters); |
| 74 | + |
| 75 | + /** |
| 76 | + * Extracts the event type that represent the payload in the request. |
| 77 | + * |
| 78 | + * @param headers request |
| 79 | + * @param parameters request |
| 80 | + * @return the event type key. |
| 81 | + */ |
| 82 | + @NonNull |
| 83 | + String getEventType(Map<String, String> headers, MultiValuedMap<String, String> parameters); |
| 84 | + |
| 85 | + /** |
| 86 | + * Returns a context for a given request used when process the payload. |
| 87 | + * |
| 88 | + * @param request hook |
| 89 | + * @return a map of information extracted by the given request to be used in |
| 90 | + * the {@link #process(String, String, Map, BitbucketEndpoint)} |
| 91 | + * method. |
| 92 | + */ |
| 93 | + @NonNull |
| 94 | + default Map<String, Object> buildHookContext(@NonNull HttpServletRequest request) { |
| 95 | + return Map.of("origin", SCMEvent.originOf(request)); |
| 96 | + } |
| 97 | + |
| 98 | + /** |
| 99 | + * The implementation must verify if the incoming request is secured or not |
| 100 | + * eventually gather some settings from the given {@link BitbucketEndpoint} |
| 101 | + * configuration. |
| 102 | + * |
| 103 | + * @param headers request |
| 104 | + * @param payload request |
| 105 | + * @param endpoint configured for the given |
| 106 | + * {@link #getServerURL(Map, MultiValuedMap)} |
| 107 | + * @throws BitbucketHookProcessorException when signature verification fails |
| 108 | + */ |
| 109 | + void verifyPayload(@NonNull Map<String, String> headers, |
| 110 | + @NonNull String payload, |
| 111 | + @NonNull BitbucketEndpoint endpoint) throws BitbucketHookProcessorException; |
| 112 | + |
| 113 | + /** |
| 114 | + * Settings that will trigger a re-index of the multibranch |
| 115 | + * project/organization folder when the request does not ship any source |
| 116 | + * changes. |
| 117 | + * |
| 118 | + * @return if should perform a reindex of the project or not. |
| 119 | + */ |
| 120 | + default boolean reindexOnEmptyChanges() { |
| 121 | + return SystemProperties.getBoolean(SCAN_ON_EMPTY_CHANGES_PROPERTY_NAME, false); |
| 122 | + } |
| 123 | + |
| 124 | + /** |
| 125 | + * See <a href="https://confluence.atlassian.com/bitbucket/event-payloads-740262817.html">Event |
| 126 | + * Payloads</a> for more information about the payload parameter format. |
| 127 | + * |
| 128 | + * @param eventType the type of hook event. |
| 129 | + * @param payload the hook payload |
| 130 | + * @param context build from incoming request |
| 131 | + * @param endpoint configured in the Jenkins global page |
| 132 | + */ |
| 133 | + void process(@NonNull String eventType, @NonNull String payload, @NonNull Map<String, Object> context, @NonNull BitbucketEndpoint endpoint); |
| 134 | + |
| 135 | + /** |
| 136 | + * Implementations have to call this method when want propagate an |
| 137 | + * {@link SCMHeadEvent} to the scm-api. |
| 138 | + * |
| 139 | + * @param event the to fire |
| 140 | + * @param delaySeconds a delay in seconds to wait before propagate the |
| 141 | + * event. If the given value is less than 0 than default will be |
| 142 | + * used. |
| 143 | + */ |
| 144 | + default void notifyEvent(SCMHeadEvent<?> event, int delaySeconds) { |
| 145 | + if (delaySeconds == 0) { |
| 146 | + SCMHeadEvent.fireNow(event); |
| 147 | + } else { |
| 148 | + SCMHeadEvent.fireLater(event, delaySeconds > 0 ? delaySeconds : BitbucketSCMSource.getEventDelaySeconds(), TimeUnit.SECONDS); |
| 149 | + } |
| 150 | + } |
| 151 | +} |
0 commit comments