diff --git a/pom.xml b/pom.xml index fef3bc8..77d8b0d 100644 --- a/pom.xml +++ b/pom.xml @@ -1,22 +1,30 @@ - + 4.0.0 org.jenkins-ci.plugins plugin - 2.11 + 2.30 - org.jenkins-ci.plugins gogs-webhook 1.0.16-SNAPSHOT hpi - 1.625.3 8 - 2.13 8 + + + 2.138.3 + 2.38 + + + 3.1.2 false + + + 2.9.9 Jenkins Gogs plugin @@ -49,18 +57,28 @@ org.jenkins-ci.plugins git - 2.2.5 + 3.10.0 org.apache.httpcomponents httpclient - 4.5.3 + 4.5.5 commons-codec commons-codec 1.10 + + com.fasterxml.jackson.core + jackson-annotations + ${jackson.version} + + + com.fasterxml.jackson.core + jackson-databind + ${jackson.version} + junit junit @@ -77,14 +95,7 @@ com.google.guava guava 25.0-jre - - - - - - - org.apache.httpcomponents fluent-hc @@ -116,9 +127,105 @@ 6.3 test + + org.jenkins-ci.plugins + display-url-api + 2.2.0 + test + + + default + + true + + + + + org.codehaus.mojo + findbugs-maven-plugin + 3.0.5 + + + org.apache.maven.plugins + maven-surefire-plugin + 2.21.0 + + ${jaCoCoArgLine} + + **/IT*.java + + + true + + + + + default-test + test + + test + + + + + + org.jacoco + jacoco-maven-plugin + 0.8.1 + + + default-prepare-agent + + prepare-agent + + + jaCoCoArgLine + + + + default-report + + report + + + + default-check + + check + + + + + CLASS + + + LINE + COVEREDRATIO + 0.0 + + + BRANCH + COVEREDRATIO + 0.0 + + + + + + + + + + org.eluder.coveralls + coveralls-maven-plugin + 4.3.0 + + + + withIntegrationTest diff --git a/src/main/java/org/jenkinsci/plugins/gogs/GogsCause.java b/src/main/java/org/jenkinsci/plugins/gogs/GogsCause.java index a00583e..a85c101 100644 --- a/src/main/java/org/jenkinsci/plugins/gogs/GogsCause.java +++ b/src/main/java/org/jenkinsci/plugins/gogs/GogsCause.java @@ -23,15 +23,70 @@ associated documentation files (the "Software"), to deal in the Software without package org.jenkinsci.plugins.gogs; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; import hudson.model.Cause; +import java.util.HashMap; +import java.util.Map; +import java.util.logging.Logger; + +import static org.jenkinsci.plugins.gogs.GogsUtils.cast; + class GogsCause extends Cause { - private final String deliveryID; + private String deliveryID; + private final Map envVars = new HashMap<>(); + private final static Logger LOGGER = Logger.getLogger(GogsCause.class.getName()); + + + public GogsCause() { + } public GogsCause(String deliveryID) { this.deliveryID = deliveryID; } + public Map getEnvVars() { + return envVars; + } + + public void setDeliveryID(String deliveryID) { + this.deliveryID = deliveryID; + } + + public void setGogsPayloadData(String json) { + Map gogsPayloadData = null; + + ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true); + objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + + try { + gogsPayloadData = cast(objectMapper.readValue(json, Map.class)); + } catch (Exception e) { + LOGGER.severe(e.getMessage()); + } + if (gogsPayloadData != null) { + iterate(gogsPayloadData, null); + } + } + + private void iterate(Map map, String prefix) { + for (Map.Entry entry : map.entrySet()) { + StringBuilder env_name = new StringBuilder(); + if (prefix != null) + env_name.append(prefix.toUpperCase()).append("_"); + + if (entry.getValue() instanceof Map) { + //noinspection unchecked + iterate((Map) entry.getValue(), env_name + entry.getKey().toUpperCase()); + } else if (entry.getValue() instanceof String || entry.getValue() instanceof Long || entry.getValue() instanceof Boolean) { + env_name.append(entry.getKey().toUpperCase()); + envVars.put("GOGS_" + env_name.toString(), entry.getValue().toString()); + } + } + } + @Override public String getShortDescription() { return this.deliveryID; diff --git a/src/main/java/org/jenkinsci/plugins/gogs/GogsEnvironmentContributor.java b/src/main/java/org/jenkinsci/plugins/gogs/GogsEnvironmentContributor.java new file mode 100644 index 0000000..d84d481 --- /dev/null +++ b/src/main/java/org/jenkinsci/plugins/gogs/GogsEnvironmentContributor.java @@ -0,0 +1,23 @@ +package org.jenkinsci.plugins.gogs; + +import hudson.EnvVars; +import hudson.Extension; +import hudson.model.Run; +import hudson.model.TaskListener; +import jenkins.model.CoreEnvironmentContributor; + +import javax.annotation.Nonnull; + +@SuppressWarnings("unused") +@Extension +public class GogsEnvironmentContributor extends CoreEnvironmentContributor { + @Override + public void buildEnvironmentFor(@Nonnull Run r, @Nonnull EnvVars envs, @Nonnull TaskListener listener) { + GogsCause gogsCause; + + gogsCause = (GogsCause) r.getCause(GogsCause.class); + if (gogsCause != null) { + envs.putAll(gogsCause.getEnvVars()); + } + } +} diff --git a/src/main/java/org/jenkinsci/plugins/gogs/GogsPayload.java b/src/main/java/org/jenkinsci/plugins/gogs/GogsPayload.java index fb0ffc8..d34b467 100644 --- a/src/main/java/org/jenkinsci/plugins/gogs/GogsPayload.java +++ b/src/main/java/org/jenkinsci/plugins/gogs/GogsPayload.java @@ -5,30 +5,20 @@ import hudson.model.EnvironmentContributingAction; import hudson.model.InvisibleAction; -import javax.annotation.Nonnull; -import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; class GogsPayload extends InvisibleAction implements EnvironmentContributingAction { - private final Map payload; + private final GogsCause gogsCause; - public GogsPayload(Map payload) { - this.payload = payload; - } - - @Nonnull - private Map getPayload() { - return payload; + public GogsPayload(GogsCause gogsCause) { + this.gogsCause = gogsCause; } @Override public void buildEnvVars(AbstractBuild abstractBuild, EnvVars envVars) { - LOGGER.log(Level.FINEST, "Injecting GOGS_PAYLOAD: {0}", getPayload()); - for (Map.Entry entry : payload.entrySet()) { - envVars.put("GOGS_" + entry.getKey().toUpperCase(), entry.getValue()); - } - + LOGGER.log(Level.FINEST, "Injecting GOGS_PAYLOAD: {0}", gogsCause.getEnvVars()); + envVars.putAll(gogsCause.getEnvVars()); } private static final Logger LOGGER = Logger.getLogger(GogsPayload.class.getName()); diff --git a/src/main/java/org/jenkinsci/plugins/gogs/GogsPayloadProcessor.java b/src/main/java/org/jenkinsci/plugins/gogs/GogsPayloadProcessor.java index 5c7be42..fb99ed3 100644 --- a/src/main/java/org/jenkinsci/plugins/gogs/GogsPayloadProcessor.java +++ b/src/main/java/org/jenkinsci/plugins/gogs/GogsPayloadProcessor.java @@ -24,65 +24,55 @@ associated documentation files (the "Software"), to deal in the Software without package org.jenkinsci.plugins.gogs; import hudson.model.BuildableItem; -import hudson.model.Cause; import hudson.security.ACL; -import hudson.triggers.Trigger; -import jenkins.model.ParameterizedJobMixIn; +import hudson.security.ACLContext; import jenkins.triggers.SCMTriggerItem; -import org.acegisecurity.context.SecurityContext; -import org.acegisecurity.context.SecurityContextHolder; import java.io.PrintWriter; import java.io.StringWriter; -import java.util.HashMap; -import java.util.Map; +import java.util.Optional; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.logging.Logger; +import static jenkins.model.ParameterizedJobMixIn.ParameterizedJob; + class GogsPayloadProcessor { private static final Logger LOGGER = Logger.getLogger(GogsPayloadProcessor.class.getName()); - private final Map payload = new HashMap<>(); + private GogsCause gogsCause = new GogsCause(); GogsPayloadProcessor() { } - public void setPayload(String k, String v) { - this.payload.put(k, v); + public void setCause(GogsCause gogsCause) { + this.gogsCause = gogsCause; } - public GogsResults triggerJobs(String jobName, String deliveryID) { - SecurityContext saveCtx = ACL.impersonate(ACL.SYSTEM); + public GogsResults triggerJobs(String jobName) { + AtomicBoolean jobdone = new AtomicBoolean(false); GogsResults result = new GogsResults(); - try { + try (ACLContext ctx = ACL.as(ACL.SYSTEM)) { BuildableItem project = GogsUtils.find(jobName, BuildableItem.class); if (project != null) { - GogsTrigger gTrigger = null; - Cause cause = new GogsCause(deliveryID); - - if (project instanceof ParameterizedJobMixIn.ParameterizedJob) { - ParameterizedJobMixIn.ParameterizedJob pJob = (ParameterizedJobMixIn.ParameterizedJob) project; - for (Trigger trigger : pJob.getTriggers().values()) { - if (trigger instanceof GogsTrigger) { - gTrigger = (GogsTrigger) trigger; - break; - } - } - } - if (gTrigger != null) { - SCMTriggerItem item = SCMTriggerItem.SCMTriggerItems.asSCMTriggerItem(project); - GogsPayload gogsPayload = new GogsPayload(this.payload); - if (item != null) { - item.scheduleBuild2(0, gogsPayload); - } - } else { - project.scheduleBuild(0, cause); + if (project instanceof ParameterizedJob) { + ParameterizedJob pJob = (ParameterizedJob) project; + pJob.getTriggers().values().stream() + .filter(trigger1 -> trigger1 instanceof GogsTrigger).findFirst() + .ifPresent((g) -> { +// GogsPayload gogsPayload = new GogsPayload(this.payload); + GogsPayload gogsPayload = new GogsPayload(this.gogsCause); + Optional.ofNullable(SCMTriggerItem.SCMTriggerItems.asSCMTriggerItem(project)) + .ifPresent((item) -> { + item.scheduleBuild2(0, gogsPayload); + jobdone.set(true); + }); + }); + } + if (!jobdone.get()) { + project.scheduleBuild(0, gogsCause); + jobdone.set(true); } - result.setMessage(String.format("Job '%s' is executed", jobName)); - } else { - String msg = String.format("Job '%s' is not defined in Jenkins", jobName); - result.setStatus(404, msg); - LOGGER.warning(msg); } } catch (Exception e) { StringWriter sw = new StringWriter(); @@ -90,7 +80,13 @@ public GogsResults triggerJobs(String jobName, String deliveryID) { e.printStackTrace(pw); LOGGER.severe(sw.toString()); } finally { - SecurityContextHolder.setContext(saveCtx); + if (jobdone.get()) { + result.setMessage(String.format("Job '%s' is executed", jobName)); + } else { + String msg = String.format("Job '%s' is not defined in Jenkins", jobName); + result.setStatus(404, msg); + LOGGER.warning(msg); + } } return result; diff --git a/src/main/java/org/jenkinsci/plugins/gogs/GogsProjectProperty.java b/src/main/java/org/jenkinsci/plugins/gogs/GogsProjectProperty.java index ca1cab9..117c8ed 100644 --- a/src/main/java/org/jenkinsci/plugins/gogs/GogsProjectProperty.java +++ b/src/main/java/org/jenkinsci/plugins/gogs/GogsProjectProperty.java @@ -23,6 +23,8 @@ associated documentation files (the "Software"), to deal in the Software without package org.jenkinsci.plugins.gogs; +import java.util.logging.Logger; + import hudson.Extension; import hudson.model.Job; import hudson.model.JobProperty; @@ -32,10 +34,10 @@ associated documentation files (the "Software"), to deal in the Software without import org.kohsuke.stapler.DataBoundConstructor; import org.kohsuke.stapler.StaplerRequest; -import java.util.logging.Logger; - @SuppressWarnings("ALL") public class GogsProjectProperty extends JobProperty> { + private static final Logger LOGGER = Logger.getLogger(GogsWebHook.class.getName()); + private final Secret gogsSecret; private final boolean gogsUsePayload; private final String gogsBranchFilter; @@ -61,7 +63,7 @@ public boolean getGogsUsePayload() { } public String getGogsBranchFilter() { - return this.gogsBranchFilter; + return gogsBranchFilter; } public boolean getHasBranchFilter() { @@ -75,8 +77,6 @@ public boolean filterBranch(String ref) { return true; } - private static final Logger LOGGER = Logger.getLogger(GogsWebHook.class.getName()); - @Extension public static final class DescriptorImpl extends JobPropertyDescriptor { public static final String GOGS_PROJECT_BLOCK_NAME = "gogsProject"; @@ -105,6 +105,7 @@ public JobProperty newInstance(StaplerRequest req, JSONObject formData) { formData.getJSONObject(GOGS_PROJECT_BLOCK_NAME) ); } + if (tpp != null) { LOGGER.finest(formData.toString()); LOGGER.finest(tpp.gogsBranchFilter); diff --git a/src/main/java/org/jenkinsci/plugins/gogs/GogsUtils.java b/src/main/java/org/jenkinsci/plugins/gogs/GogsUtils.java index 04110c0..0fe2bb2 100644 --- a/src/main/java/org/jenkinsci/plugins/gogs/GogsUtils.java +++ b/src/main/java/org/jenkinsci/plugins/gogs/GogsUtils.java @@ -1,32 +1,92 @@ package org.jenkinsci.plugins.gogs; +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.util.Map; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + import hudson.model.Item; import jenkins.model.Jenkins; +import org.apache.commons.codec.binary.Hex; class GogsUtils { - private GogsUtils() { - } - - /** - * Search in Jenkins for a item with type T based on the job name - * @param jobName job to find, for jobs inside a folder use : {@literal //} - * @return the Job matching the given name, or {@code null} when not found - */ - static T find(String jobName, Class type) { - Jenkins jenkins = Jenkins.getActiveInstance(); - // direct search, can be used to find folder based items // - T item = jenkins.getItemByFullName(jobName, type); - if (item == null) { - // not found in a direct search, search in all items since the item might be in a folder but given without folder structure - // (to keep it backwards compatible) - for (T allItem : jenkins.getAllItems(type)) { - if (allItem.getName().equals(jobName)) { - item = allItem; - break; - } - } - } - return item; - } + private GogsUtils() { + } + + /** + * Search in Jenkins for a item with type T based on the job name + * + * @param jobName job to find, for jobs inside a folder use : {@literal //} + * @return the Job matching the given name, or {@code null} when not found + */ + static T find(String jobName, Class type) { + Jenkins jenkins = Jenkins.get(); + // direct search, can be used to find folder based items // + T item = jenkins.getItemByFullName(jobName, type); + if (item == null) { + // not found in a direct search, search in all items since the item might be in a folder but given without folder structure + // (to keep it backwards compatible) + item = jenkins.getAllItems(type).stream().filter(i -> i.getName().equals(jobName)).findFirst().orElse(null); + } + return item; + } + + /** + * Converts Querystring into Map + * + * @param qs Querystring + * @return returns map from querystring + */ + static Map splitQuery(String qs) { + return Pattern.compile("&").splitAsStream(qs) + .map(p -> p.split("=")) + .collect(Collectors.toMap(a -> a[0], a -> a.length > 1 ? urlDecode(a[1]) : "")); + } + + /** + * Decode URL + * + * @param s String to decode + * @return returns decoded string + */ + static String urlDecode(String s) { + try { + return URLDecoder.decode(s, StandardCharsets.UTF_8.name()); + } catch (UnsupportedEncodingException ignored) { } + return ""; + } + + /** + * encode sha256 hmac + * + * @param data data to hex + * @param key key of HmacSHA256 + * @return a String with the encoded sha256 hmac + * @throws Exception Something went wrong getting the sha256 hmac + */ + static String encode(String data, String key) throws Exception { + final Charset asciiCs = Charset.forName("UTF-8"); + final Mac sha256_HMAC = Mac.getInstance("HmacSHA256"); + final SecretKeySpec secret_key = new javax.crypto.spec.SecretKeySpec(asciiCs.encode(key).array(), "HmacSHA256"); + sha256_HMAC.init(secret_key); + return Hex.encodeHexString(sha256_HMAC.doFinal(data.getBytes("UTF-8"))); + } + + /** + * Cast object + * + * @param obj object to cast + * @param cast to type + * @return Casted object + */ + @SuppressWarnings("unchecked") + static public T cast(Object obj) { + return (T) obj; + } } diff --git a/src/main/java/org/jenkinsci/plugins/gogs/GogsWebHook.java b/src/main/java/org/jenkinsci/plugins/gogs/GogsWebHook.java index 82502db..6c5c979 100644 --- a/src/main/java/org/jenkinsci/plugins/gogs/GogsWebHook.java +++ b/src/main/java/org/jenkinsci/plugins/gogs/GogsWebHook.java @@ -23,43 +23,46 @@ associated documentation files (the "Software"), to deal in the Software without package org.jenkinsci.plugins.gogs; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Strings.isNullOrEmpty; + +import java.io.IOException; +import java.io.PrintWriter; +import java.net.URLDecoder; +import java.util.Map; +import java.util.Objects; +import java.util.StringJoiner; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; +import java.util.logging.Logger; +import java.util.regex.Pattern; +import java.util.stream.Stream; + import hudson.Extension; import hudson.model.Job; import hudson.model.UnprotectedRootAction; import hudson.security.ACL; +import hudson.security.ACLContext; import hudson.util.Secret; +import net.sf.json.JSONException; import net.sf.json.JSONObject; -import org.acegisecurity.context.SecurityContext; -import org.acegisecurity.context.SecurityContextHolder; -import org.apache.commons.codec.binary.Hex; import org.apache.commons.io.IOUtils; -import org.apache.commons.lang.StringUtils; import org.kohsuke.stapler.StaplerRequest; import org.kohsuke.stapler.StaplerResponse; -import javax.crypto.Mac; -import javax.crypto.spec.SecretKeySpec; -import java.io.IOException; -import java.io.PrintWriter; -import java.io.UnsupportedEncodingException; -import java.net.URLDecoder; -import java.nio.charset.Charset; -import java.util.Arrays; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.logging.Logger; - -import static com.google.common.base.Preconditions.checkNotNull; - /** * @author Alexander Verhaar */ @Extension public class GogsWebHook implements UnprotectedRootAction { private final static Logger LOGGER = Logger.getLogger(GogsWebHook.class.getName()); - static final String URLNAME = "gogs-webhook"; private static final String DEFAULT_CHARSET = "UTF-8"; + private GogsResults result = new GogsResults(); + private String gogsDelivery = null; + private String gogsSignature = null; + private String jobName = null; + private String xGogsEvent = null; + static final String URLNAME = "gogs-webhook"; public String getDisplayName() { return null; @@ -73,20 +76,26 @@ public String getUrlName() { return URLNAME; } - /** - * encode sha256 hmac - * - * @param data data to hex - * @param key key of HmacSHA256 - * @return a String with the encoded sha256 hmac - * @throws Exception Something went wrong getting the sha256 hmac - */ - private static String encode(String data, String key) throws Exception { - final Charset asciiCs = Charset.forName("UTF-8"); - final Mac sha256_HMAC = Mac.getInstance("HmacSHA256"); - final SecretKeySpec secret_key = new javax.crypto.spec.SecretKeySpec(asciiCs.encode(key).array(), "HmacSHA256"); - sha256_HMAC.init(secret_key); - return Hex.encodeHexString(sha256_HMAC.doFinal(data.getBytes("UTF-8"))); + private String getGogsDelivery() { + if (isNullOrEmpty(gogsDelivery)) { + return "Triggered by Jenkins-Gogs-Plugin. Delivery ID unknown."; + } + return "Gogs-ID: " + gogsDelivery; + } + + private void setGogsDelivery(String gogsDelivery) { + this.gogsDelivery = gogsDelivery; + } + + private String getGogsSignature() { + if (isNullOrEmpty(gogsSignature)) { + gogsSignature = null; + } + return gogsSignature; + } + + private void setGogsSignature(String gogsSignature) { + this.gogsSignature = gogsSignature; } /** @@ -96,65 +105,41 @@ private static String encode(String data, String key) throws Exception { * @param rsp response * @throws IOException problem while parsing */ + @SuppressWarnings("WeakerAccess") public void doIndex(StaplerRequest req, StaplerResponse rsp) throws IOException { - GogsResults result = new GogsResults(); - GogsPayloadProcessor payloadProcessor = new GogsPayloadProcessor(); + JSONObject jsonObject = null; - //Check that we have something to process - checkNotNull(req, "Null request submitted to doIndex method"); - checkNotNull(rsp, "Null reply submitted to doIndex method"); + AtomicReference jSecret = new AtomicReference<>(null); + AtomicBoolean foundJob = new AtomicBoolean(false); + AtomicBoolean isRefMatched = new AtomicBoolean(true); - // Get X-Gogs-Event - String event = req.getHeader("X-Gogs-Event"); - if (!"push".equals(event)) { - result.setStatus(403, "Only push event can be accepted."); - exitWebHook(result, rsp); + GogsPayloadProcessor payloadProcessor = new GogsPayloadProcessor(); + GogsCause gogsCause = new GogsCause(); + + if (!sanityChecks(req, rsp)) { return; } // Get X-Gogs-Delivery header with deliveryID - String gogsDelivery = req.getHeader("X-Gogs-Delivery"); - if (gogsDelivery == null || gogsDelivery.isEmpty()) { - gogsDelivery = "Triggered by Jenkins-Gogs-Plugin. Delivery ID unknown."; - } else { - gogsDelivery = "Gogs-ID: " + gogsDelivery; - } + setGogsDelivery(req.getHeader("X-Gogs-Delivery")); // Get X-Gogs-Signature - String gogsSignature = req.getHeader("X-Gogs-Signature"); - if (gogsSignature == null || gogsSignature.isEmpty()) { - gogsSignature = null; - } + setGogsSignature(req.getHeader("X-Gogs-Signature")); - - // Get queryStringMap from the URI - String queryString = checkNotNull(req.getQueryString(), "The queryString in the request is null"); - Map queryStringMap = checkNotNull(splitQuery(queryString), "Null queryStringMap"); - - //Do we have the job name parameter ? - if (!queryStringMap.containsKey("job")) { - result.setStatus(404, "Parameter 'job' is missing."); - exitWebHook(result, rsp); - return; - } - Object jobObject = queryStringMap.get("job"); - String jobName; - if (jobObject == null) { - result.setStatus(404, "No value assigned to parameter 'job'"); + // Get the POST stream + String body = IOUtils.toString(req.getInputStream(), DEFAULT_CHARSET); + try { + jsonObject = JSONObject.fromObject(body); + } catch (JSONException e) { + result.setStatus(400, "Invalid JSON"); exitWebHook(result, rsp); return; - } else { - jobName = jobObject.toString(); } + String ref = jsonObject.optString("ref", null); - final Object branchName = queryStringMap.get("branch"); - - // Get the POST stream - String body = IOUtils.toString(req.getInputStream(), DEFAULT_CHARSET); - if (!body.isEmpty() && req.getRequestURI().contains("/" + URLNAME + "/")) { - JSONObject jsonObject = JSONObject.fromObject(body); + if (xGogsEvent.equals("push") && req.getRequestURI().contains("/" + URLNAME + "/") && !body.isEmpty()) { JSONObject commits = (JSONObject) jsonObject.getJSONArray("commits").get(0); - String message = (String) commits.get("message"); + String message = commits.getString("message"); if (message.startsWith("[IGNORE]")) { // Ignore commits starting with message "[IGNORE]" @@ -163,17 +148,6 @@ public void doIndex(StaplerRequest req, StaplerResponse rsp) throws IOException return; } - String ref = jsonObject.getString("ref"); - LOGGER.fine("found ref " + ref); - LOGGER.fine("found branch " + branchName); - if (null != branchName && !StringUtils.containsIgnoreCase(ref, (String) branchName)) { - // ignore all commit if they is not in context - LOGGER.fine("build was rejected"); - result.setStatus(200, String.format("Commit is not relevant. Relevant context is %s", branchName)); - exitWebHook(result, rsp); - return; - } - String contentType = req.getContentType(); if (contentType != null && contentType.startsWith("application/x-www-form-urlencoded")) { body = URLDecoder.decode(body, DEFAULT_CHARSET); @@ -182,62 +156,51 @@ public void doIndex(StaplerRequest req, StaplerResponse rsp) throws IOException body = body.substring(8); } - String jSecret = null; - boolean foundJob = false; + gogsCause.setGogsPayloadData(jsonObject.toString()); + gogsCause.setDeliveryID(getGogsDelivery()); + payloadProcessor.setCause(gogsCause); - // filter branch, if ref not match branch filter, skip trigger job. - boolean isRefMatched = true; + try (ACLContext ignored = ACL.as(ACL.SYSTEM)) { + StringJoiner stringJoiner = new StringJoiner("%2F"); + Pattern.compile("/").splitAsStream(jsonObject.getString("ref")).skip(2).forEach(stringJoiner::add); + String ref_strj = stringJoiner.toString(); - - payloadProcessor.setPayload("ref", ref); - payloadProcessor.setPayload("before", jsonObject.getString("before")); - - SecurityContext saveCtx = ACL.impersonate(ACL.SYSTEM); - - try { - Job job = GogsUtils.find(jobName, Job.class); - - if (job != null) { - foundJob = true; - /* secret is stored in the properties of Job */ + /* secret is stored in the properties of Job */ + Stream.of(jobName, jobName + "/" + ref_strj).map(j -> GogsUtils.find(j, Job.class)).filter(Objects::nonNull).forEach(job -> { + foundJob.set(true); final GogsProjectProperty property = (GogsProjectProperty) job.getProperty(GogsProjectProperty.class); if (property != null) { /* only if Gogs secret is defined on the job */ - jSecret = Secret.toString(property.getGogsSecret()); /* Secret provided by Jenkins */ - isRefMatched = property.filterBranch(ref); + jSecret.set(Secret.toString(property.getGogsSecret())); /* Secret provided by Jenkins */ + isRefMatched.set(property.filterBranch(ref)); } - } else { - String[] components = ref.split("/"); - if (components.length > 3) { - /* refs contains branch/tag with a slash */ - List test = Arrays.asList(ref.split("/")); - ref = String.join("%2F", test.subList(2, test.size())); - } else { - ref = components[components.length - 1]; - } - - job = GogsUtils.find(jobName + "/" + ref, Job.class); + }); + } + } else { + result.setStatus(404, "No payload or URI contains invalid entries."); + } - if (job != null) { - foundJob = true; - /* secret is stored in the properties of Job */ - final GogsProjectProperty property = (GogsProjectProperty) job.getProperty(GogsProjectProperty.class); - if (property != null) { /* only if Gogs secret is defined on the job */ - jSecret = Secret.toString(property.getGogsSecret()); /* Secret provided by Jenkins */ - isRefMatched = property.filterBranch(ref); - } + // Gogs release event + if (xGogsEvent.equals("release")) { + try (ACLContext ignored = ACL.as(ACL.SYSTEM)) { + /* secret is stored in the properties of Job */ + Stream.of(jobName).map(j -> GogsUtils.find(j, Job.class)).filter(Objects::nonNull).forEach(job -> { + foundJob.set(true); + final GogsProjectProperty property = (GogsProjectProperty) job.getProperty(GogsProjectProperty.class); + if (property != null) { /* only if Gogs secret is defined on the job */ + jSecret.set(Secret.toString(property.getGogsSecret())); /* Secret provided by Jenkins */ } - } - } finally { - SecurityContextHolder.setContext(saveCtx); + }); } + } + if (result.getStatus() == 200) { String gSecret = null; - if (gogsSignature == null) { + if (getGogsSignature() == null) { gSecret = jsonObject.optString("secret", null); /* Secret provided by Gogs < 0.10.x */ } else { try { - if (gogsSignature.equals(encode(body, jSecret))) { - gSecret = jSecret; + if (getGogsSignature().equals(GogsUtils.encode(body, jSecret.get()))) { + gSecret = jSecret.get(); // now hex is right, continue to old logic } } catch (Exception e) { @@ -245,31 +208,69 @@ public void doIndex(StaplerRequest req, StaplerResponse rsp) throws IOException } } - if (!foundJob) { + if (!foundJob.get()) { String msg = String.format("Job '%s' is not defined in Jenkins", jobName); result.setStatus(404, msg); LOGGER.warning(msg); - } else if (!isRefMatched) { + } else if (!isRefMatched.get()) { String msg = String.format("received ref ('%s') is not matched with branch filter in job '%s'", ref, jobName); result.setStatus(200, msg); LOGGER.info(msg); - } else if (isNullOrEmpty(jSecret) && isNullOrEmpty(gSecret)) { + } else if (isNullOrEmpty(jSecret.get()) && isNullOrEmpty(gSecret)) { /* No password is set in Jenkins and Gogs, run without secrets */ - result = payloadProcessor.triggerJobs(jobName, gogsDelivery); - } else if (!isNullOrEmpty(jSecret) && jSecret.equals(gSecret)) { + result = payloadProcessor.triggerJobs(jobName); + } else if (!isNullOrEmpty(jSecret.get()) && jSecret.get().equals(gSecret)) { /* Password is set in Jenkins and Gogs, and is correct */ - result = payloadProcessor.triggerJobs(jobName, gogsDelivery); + result = payloadProcessor.triggerJobs(jobName); } else { /* Gogs and Jenkins secrets differs */ result.setStatus(403, "Incorrect secret"); } - } else { - result.setStatus(404, "No payload or URI contains invalid entries."); } exitWebHook(result, rsp); } + /*** + * Do sanity checks + * + * @param req Request + * @param rsp Response + * @throws IOException Exception + */ + private boolean sanityChecks(StaplerRequest req, StaplerResponse rsp) throws IOException { + //Check that we have something to process + checkNotNull(req, "Null request submitted to doIndex method"); + checkNotNull(rsp, "Null reply submitted to doIndex method"); + + // Get X-Gogs-Event + xGogsEvent = req.getHeader("X-Gogs-Event"); + if (!"push".equals(xGogsEvent) && !"release".equals(xGogsEvent)) { + result.setStatus(403, "Only push or release events are accepted."); + exitWebHook(result, rsp); + return false; + } + + // Get queryStringMap from the URI + String queryString = checkNotNull(req.getQueryString(), "The queryString in the request is null"); + Map queryStringMap = checkNotNull(GogsUtils.splitQuery(queryString), "Null queryStringMap"); + + //Do we have the job name parameter ? + if (!queryStringMap.containsKey("job")) { + result.setStatus(404, "Parameter 'job' is missing."); + exitWebHook(result, rsp); + return false; + } + + jobName = queryStringMap.get("job").toString(); + if (isNullOrEmpty(jobName)) { + result.setStatus(404, "No value assigned to parameter 'job'"); + exitWebHook(result, rsp); + return false; + } + return true; + } + /** * Exit the WebHook * @@ -288,28 +289,6 @@ private void exitWebHook(GogsResults result, StaplerResponse resp) throws IOExce PrintWriter printer = resp.getWriter(); printer.print(json.toString()); } - - /** - * Converts Querystring into Map - * - * @param qs Querystring - * @return returns map from querystring - */ - private static Map splitQuery(String qs) throws UnsupportedEncodingException { - final Map query_pairs = new LinkedHashMap<>(); - final String[] pairs = qs.split("&"); - for (String pair : pairs) { - final int idx = pair.indexOf("="); - final String key = idx > 0 ? URLDecoder.decode(pair.substring(0, idx), DEFAULT_CHARSET) : pair; - final String value = idx > 0 && pair.length() > idx + 1 ? URLDecoder.decode(pair.substring(idx + 1), DEFAULT_CHARSET) : null; - query_pairs.put(key, value); - } - return query_pairs; - } - - private boolean isNullOrEmpty(String s) { - return s == null || s.trim().isEmpty(); - } } // vim: set ts=4 sw=4 tw=0 ft=java et : diff --git a/src/main/resources/org/jenkinsci/plugins/gogs/GogsProjectProperty/config.jelly b/src/main/resources/org/jenkinsci/plugins/gogs/GogsProjectProperty/config.jelly index b7679ec..7c14134 100644 --- a/src/main/resources/org/jenkinsci/plugins/gogs/GogsProjectProperty/config.jelly +++ b/src/main/resources/org/jenkinsci/plugins/gogs/GogsProjectProperty/config.jelly @@ -1,9 +1,14 @@ - + - + + + + + + diff --git a/src/test/java/org/jenkinsci/plugins/gogs/GogsWebHookJenkinsTest.java b/src/test/java/org/jenkinsci/plugins/gogs/GogsWebHookJenkinsTest.java index 4890487..3cf3010 100644 --- a/src/test/java/org/jenkinsci/plugins/gogs/GogsWebHookJenkinsTest.java +++ b/src/test/java/org/jenkinsci/plugins/gogs/GogsWebHookJenkinsTest.java @@ -1,5 +1,13 @@ package org.jenkinsci.plugins.gogs; +import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.CoreMatchers.not; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertThat; + +import java.io.File; +import java.nio.charset.StandardCharsets; + import com.gargoylesoftware.htmlunit.html.HtmlPage; import hudson.model.FreeStyleProject; import hudson.util.Secret; @@ -11,14 +19,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.File; -import java.nio.charset.StandardCharsets; - -import static org.hamcrest.CoreMatchers.containsString; -import static org.hamcrest.CoreMatchers.not; -import static org.junit.Assert.assertSame; -import static org.junit.Assert.assertThat; - public class GogsWebHookJenkinsTest { final Logger log = LoggerFactory.getLogger(GogsWebHookJenkinsTest.class); diff --git a/src/test/java/org/jenkinsci/plugins/gogs/GogsWebHookTest.java b/src/test/java/org/jenkinsci/plugins/gogs/GogsWebHookTest.java index a73d016..0a42197 100644 --- a/src/test/java/org/jenkinsci/plugins/gogs/GogsWebHookTest.java +++ b/src/test/java/org/jenkinsci/plugins/gogs/GogsWebHookTest.java @@ -89,7 +89,7 @@ public void whenEmptyHeaderTypeMustReturnError() throws Exception { //validate that everything was done as planed verify(staplerResponse).setStatus(403); - String expectedOutput = "Only push event can be accepted."; + String expectedOutput = "Only push or release events are accepted."; isExpectedOutput(uniqueFile, expectedOutput); log.info("Test succeeded."); @@ -104,13 +104,13 @@ public void whenWrongHeaderTypeMustReturnError() throws Exception { StaplerResponse staplerResponse = Mockito.mock(ResponseImpl.class); when(staplerRequest.getHeader("X-Gogs-Event")).thenReturn("junk"); - //perform the testÎ + //perform the test performDoIndexTest(staplerRequest, staplerResponse, uniqueFile); //validate that everything was done as planed verify(staplerResponse).setStatus(403); - String expectedOutput = "Only push event can be accepted."; + String expectedOutput = "Only push or release events are accepted."; isExpectedOutput(uniqueFile, expectedOutput); log.info("Test succeeded."); @@ -149,7 +149,7 @@ public void whenNoJobInQueryStringMustReturnError() throws Exception { when(staplerRequest.getHeader("X-Gogs-Event")).thenReturn("push"); when(staplerRequest.getQueryString()).thenReturn("foo=bar&blaah=blaah"); - //perform the testÎ + //perform the test performDoIndexTest(staplerRequest, staplerResponse, uniqueFile); //validate that everything was done as planed @@ -171,7 +171,7 @@ public void whenEmptyJobInQueryStringMustReturnError() throws Exception { when(staplerRequest.getHeader("X-Gogs-Event")).thenReturn("push"); when(staplerRequest.getQueryString()).thenReturn("job&foo=bar"); - //perform the testÎ + //perform the test performDoIndexTest(staplerRequest, staplerResponse, uniqueFile); //validate that everything was done as planed @@ -193,7 +193,7 @@ public void whenEmptyJob2InQueryStringMustReturnError() throws Exception { when(staplerRequest.getHeader("X-Gogs-Event")).thenReturn("push"); when(staplerRequest.getQueryString()).thenReturn("job=&foo=bar"); - //perform the testÎ + //perform the test performDoIndexTest(staplerRequest, staplerResponse, uniqueFile); //validate that everything was done as planed @@ -220,13 +220,13 @@ public void whenUriDoesNotContainUrlNameMustReturnError() throws Exception { when(staplerRequest.getInputStream()).thenReturn(inputStream); when(staplerRequest.getRequestURI()).thenReturn("/badUri/aaa"); - //perform the testÎ + //perform the test performDoIndexTest(staplerRequest, staplerResponse, uniqueFile); //validate that everything was done as planed - verify(staplerResponse).setStatus(404); + verify(staplerResponse).setStatus(400); - String expectedOutput = "No payload or URI contains invalid entries."; + String expectedOutput = "Invalid JSON"; isExpectedOutput(uniqueFile, expectedOutput); log.info("Test succeeded."); @@ -235,8 +235,6 @@ public void whenUriDoesNotContainUrlNameMustReturnError() throws Exception { // // Helper methods // - - private void performDoIndexTest(StaplerRequest staplerRequest, StaplerResponse staplerResponse, File file) throws IOException { PrintWriter printWriter = new PrintWriter(file.getAbsoluteFile(), "UTF-8"); when(staplerResponse.getWriter()).thenReturn(printWriter); @@ -286,4 +284,4 @@ public void setReadListener(ReadListener readListener) { } } -} +} \ No newline at end of file diff --git a/src/test/java/org/jenkinsci/plugins/gogs/GogsWebHook_IT.java b/src/test/java/org/jenkinsci/plugins/gogs/GogsWebHook_IT.java index 72e3612..aaa534b 100644 --- a/src/test/java/org/jenkinsci/plugins/gogs/GogsWebHook_IT.java +++ b/src/test/java/org/jenkinsci/plugins/gogs/GogsWebHook_IT.java @@ -143,7 +143,7 @@ private void doItTest(final String jenkinsJobName, log.info("Commit" + commit.getName()); //push - UsernamePasswordCredentialsProvider user2 = new UsernamePasswordCredentialsProvider("butler", "butler"); + UsernamePasswordCredentialsProvider user2 = new UsernamePasswordCredentialsProvider(GOGS_USER, GOGS_PASSWORD); if (branch == null) { refSpec.append("master"); diff --git a/src/test/resources/Gogs-config-json/webHookDefinition_branch.json b/src/test/resources/Gogs-config-json/webHookDefinition_branch.json index ce932a4..9a3e0e6 100644 --- a/src/test/resources/Gogs-config-json/webHookDefinition_branch.json +++ b/src/test/resources/Gogs-config-json/webHookDefinition_branch.json @@ -2,7 +2,7 @@ "type":"gogs", "config": { - "url":"http://jenkins:8080/gogs-webhook/?job=test%20project2&branch=test", + "url":"http://jenkins:8080/gogs-webhook/?job=test%20project2", "content_type":"json" }, "events":["create","push","pull_request"], diff --git a/src/test/resources/Jenkins-config/test-project-branch.xml b/src/test/resources/Jenkins-config/test-project-branch.xml index 46139f1..2952f9e 100644 --- a/src/test/resources/Jenkins-config/test-project-branch.xml +++ b/src/test/resources/Jenkins-config/test-project-branch.xml @@ -7,6 +7,9 @@ + + test +