Skip to content

Commit bd1a246

Browse files
authored
[JENKINS-75729] hookSignatureCredentialsId in a JCasC configuration sometimes is removed (#1053)
Add a new constructor to save hook signature settings. Deserialization uses reflection and the order in which setters are called is not guaranteed, this means that for example in JCasC the hookSignatureCredentialsId may not be set if enableHookSignature is not called as first. Add test case for hook signature in a JCasC configuration
1 parent 308b83c commit bd1a246

26 files changed

+451
-414
lines changed

.github/ISSUE_TEMPLATE/config.yml

Lines changed: 0 additions & 11 deletions
This file was deleted.

.github/issue_template.md

Lines changed: 0 additions & 3 deletions
This file was deleted.

src/main/java/com/cloudbees/jenkins/plugins/bitbucket/endpoints/AbstractBitbucketEndpoint.java

Lines changed: 7 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
import jenkins.authentication.tokens.api.AuthenticationTokens;
3535
import jenkins.model.Jenkins;
3636
import org.apache.commons.lang3.StringUtils;
37-
import org.jenkinsci.plugins.displayurlapi.ClassicDisplayURLProvider;
37+
import org.jenkinsci.plugins.displayurlapi.DisplayURLProvider;
3838
import org.jenkinsci.plugins.plaincredentials.StringCredentials;
3939
import org.kohsuke.stapler.DataBoundSetter;
4040

@@ -85,9 +85,12 @@ public abstract class AbstractBitbucketEndpoint extends AbstractDescribableImpl<
8585
* @param credentialsId The {@link StandardCredentials#getId()} of the credentials to use for
8686
* auto-management of hooks.
8787
*/
88-
AbstractBitbucketEndpoint(boolean manageHooks, @CheckForNull String credentialsId) {
88+
AbstractBitbucketEndpoint(boolean manageHooks, @CheckForNull String credentialsId,
89+
boolean enableHookSignature, @CheckForNull String hookSignatureCredentialsId) {
8990
this.manageHooks = manageHooks && StringUtils.isNotBlank(credentialsId);
9091
this.credentialsId = manageHooks ? fixEmptyAndTrim(credentialsId) : null;
92+
this.enableHookSignature = enableHookSignature && StringUtils.isNotBlank(hookSignatureCredentialsId);
93+
this.hookSignatureCredentialsId = enableHookSignature ? fixEmptyAndTrim(hookSignatureCredentialsId) : null;
9194
}
9295

9396
/**
@@ -149,24 +152,10 @@ public String getHookSignatureCredentialsId() {
149152
return hookSignatureCredentialsId;
150153
}
151154

152-
@DataBoundSetter
153-
public void setHookSignatureCredentialsId(String hookSignatureCredentialsId) {
154-
if (enableHookSignature) {
155-
this.hookSignatureCredentialsId = fixEmptyAndTrim(hookSignatureCredentialsId);
156-
} else {
157-
this.hookSignatureCredentialsId = null;
158-
}
159-
}
160-
161155
public boolean isEnableHookSignature() {
162156
return enableHookSignature;
163157
}
164158

165-
@DataBoundSetter
166-
public void setEnableHookSignature(boolean enableHookSignature) {
167-
this.enableHookSignature = enableHookSignature;
168-
}
169-
170159
/**
171160
* Jenkins Server Root URL to be used by this Bitbucket endpoint.
172161
* The global setting from Jenkins.get().getRootUrl()
@@ -179,7 +168,7 @@ public void setEnableHookSignature(boolean enableHookSignature) {
179168
@NonNull
180169
public String getEndpointJenkinsRootUrl() {
181170
if (StringUtils.isBlank(bitbucketJenkinsRootUrl)) {
182-
return ClassicDisplayURLProvider.get().getRoot();
171+
return DisplayURLProvider.get().getRoot();
183172
} else {
184173
return bitbucketJenkinsRootUrl;
185174
}
@@ -213,7 +202,7 @@ public static String getEndpointJenkinsRootUrl(String serverUrl) {
213202
if (endpoint != null) {
214203
return endpoint.getEndpointJenkinsRootUrl();
215204
}
216-
return ClassicDisplayURLProvider.get().getRoot();
205+
return DisplayURLProvider.get().getRoot();
217206
}
218207

219208
/**

src/main/java/com/cloudbees/jenkins/plugins/bitbucket/endpoints/BitbucketCloudEndpoint.java

Lines changed: 28 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,7 @@
3232
import hudson.util.FormValidation;
3333
import java.util.List;
3434
import jenkins.model.Jenkins;
35-
import org.kohsuke.accmod.Restricted;
36-
import org.kohsuke.accmod.restrictions.NoExternalUse;
35+
import org.jenkinsci.plugins.plaincredentials.StringCredentials;
3736
import org.kohsuke.stapler.DataBoundConstructor;
3837
import org.kohsuke.stapler.verb.POST;
3938

@@ -68,30 +67,42 @@ public class BitbucketCloudEndpoint extends AbstractBitbucketEndpoint {
6867
*/
6968
private final int repositoriesCacheDuration;
7069

71-
public BitbucketCloudEndpoint(boolean manageHooks, @CheckForNull String credentialsId) {
72-
this(false, 0, 0, manageHooks, credentialsId);
70+
/**
71+
* Default constructor.
72+
*/
73+
public BitbucketCloudEndpoint() {
74+
this(false, 0, 0, false, null, false, null);
7375
}
7476

75-
@Restricted(NoExternalUse.class) // Used for testing
76-
public BitbucketCloudEndpoint(boolean manageHooks, @CheckForNull String credentialsId, String endPointURL) {
77-
this(manageHooks, credentialsId);
78-
setBitbucketJenkinsRootUrl(endPointURL);
77+
@Deprecated(since = "936.3.1")
78+
public BitbucketCloudEndpoint(boolean enableCache, int teamCacheDuration, int repositoriesCacheDuration,
79+
boolean manageHooks, @CheckForNull String credentialsId) {
80+
this(enableCache, teamCacheDuration, repositoriesCacheDuration, manageHooks, credentialsId, false, null);
7981
}
8082

8183
/**
8284
* Constructor.
8385
*
84-
* @param enableCache {@code true} if caching should be used to reduce requests to Bitbucket.
85-
* @param teamCacheDuration How long, in minutes, to cache the team response.
86-
* @param repositoriesCacheDuration How long, in minutes, to cache the repositories response.
87-
* @param manageHooks {@code true} if and only if Jenkins is supposed to auto-manage hooks for this end-point.
88-
* @param credentialsId The {@link StandardCredentials#getId()} of the credentials to use for
89-
* auto-management of hooks.
86+
* @param enableCache {@code true} if caching should be used to reduce
87+
* requests to Bitbucket.
88+
* @param teamCacheDuration How long, in minutes, to cache the team
89+
* response.
90+
* @param repositoriesCacheDuration How long, in minutes, to cache the
91+
* repositories response.
92+
* @param manageHooks {@code true} if and only if Jenkins is supposed to
93+
* auto-manage hooks for this end-point.
94+
* @param credentialsId The {@link StandardCredentials#getId()} of the
95+
* credentials to use for auto-management of hooks.
96+
* @param enableHookSignature {@code true} hooks that comes Bitbucket Data
97+
* Center are signed.
98+
* @param credentialsId The {@link StringCredentials#getId()} of the
99+
* credentials to use for verify the signature of payload.
90100
*/
91101
@DataBoundConstructor
92-
public BitbucketCloudEndpoint(boolean enableCache, int teamCacheDuration,
93-
int repositoriesCacheDuration, boolean manageHooks, @CheckForNull String credentialsId) {
94-
super(manageHooks, credentialsId);
102+
public BitbucketCloudEndpoint(boolean enableCache, int teamCacheDuration, int repositoriesCacheDuration,
103+
boolean manageHooks, @CheckForNull String credentialsId,
104+
boolean enableHookSignature, @CheckForNull String hookSignatureCredentialsId) {
105+
super(manageHooks, credentialsId, enableHookSignature, hookSignatureCredentialsId);
95106
this.enableCache = enableCache;
96107
this.teamCacheDuration = teamCacheDuration;
97108
this.repositoriesCacheDuration = repositoriesCacheDuration;

src/main/java/com/cloudbees/jenkins/plugins/bitbucket/endpoints/BitbucketEndpointConfiguration.java

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -97,19 +97,19 @@ public Permission getRequiredGlobalConfigPagePermission() {
9797
@Restricted(NoExternalUse.class) // only for plugin internal use.
9898
@NonNull
9999
public String readResolveServerUrl(@CheckForNull String bitbucketServerUrl) {
100-
String serverUrl = normalizeServerURL(bitbucketServerUrl);
101-
serverUrl = StringUtils.defaultIfBlank(serverUrl, BitbucketCloudEndpoint.SERVER_URL);
102-
AbstractBitbucketEndpoint endpoint = findEndpoint(serverUrl).orElse(null);
100+
String serverURL = normalizeServerURL(bitbucketServerUrl);
101+
serverURL = StringUtils.defaultIfBlank(serverURL, BitbucketCloudEndpoint.SERVER_URL);
102+
AbstractBitbucketEndpoint endpoint = findEndpoint(serverURL).orElse(null);
103103
if (endpoint == null && ACL.SYSTEM2.equals(Jenkins.getAuthentication2())) {
104-
if (BitbucketCloudEndpoint.SERVER_URL.equals(serverUrl)
105-
|| BitbucketCloudEndpoint.BAD_SERVER_URL.equals(serverUrl)) {
104+
if (BitbucketCloudEndpoint.SERVER_URL.equals(serverURL)
105+
|| BitbucketCloudEndpoint.BAD_SERVER_URL.equals(serverURL)) {
106106
// exception case
107-
addEndpoint(new BitbucketCloudEndpoint(false, null));
107+
addEndpoint(new BitbucketCloudEndpoint());
108108
} else {
109-
addEndpoint(new BitbucketServerEndpoint(null, serverUrl, false, null));
109+
addEndpoint(new BitbucketServerEndpoint(serverURL));
110110
}
111111
}
112-
return endpoint == null ? serverUrl : endpoint.getServerUrl();
112+
return endpoint == null ? serverURL : endpoint.getServerUrl();
113113
}
114114

115115
/**
@@ -153,7 +153,7 @@ public boolean configure(StaplerRequest2 req, JSONObject json) throws FormExcept
153153
@NonNull
154154
public synchronized List<AbstractBitbucketEndpoint> getEndpoints() {
155155
return endpoints == null || endpoints.isEmpty()
156-
? Collections.<AbstractBitbucketEndpoint>singletonList(new BitbucketCloudEndpoint(false, null))
156+
? List.of(new BitbucketCloudEndpoint())
157157
: Collections.unmodifiableList(endpoints);
158158
}
159159

@@ -176,12 +176,16 @@ public synchronized void setEndpoints(@CheckForNull List<? extends AbstractBitbu
176176
} else if (!(endpoint instanceof BitbucketCloudEndpoint)
177177
&& BitbucketCloudEndpoint.SERVER_URL.equals(serverUrl)) {
178178
// fix type for the special case
179-
iterator.set(new BitbucketCloudEndpoint(endpoint.isManageHooks(), endpoint.getCredentialsId(), endpoint.getBitbucketJenkinsRootUrl()));
179+
BitbucketCloudEndpoint cloudEndpoint = new BitbucketCloudEndpoint(false, 0, 0,
180+
endpoint.isManageHooks(), endpoint.getCredentialsId(),
181+
endpoint.isEnableHookSignature(), endpoint.getHookSignatureCredentialsId());
182+
cloudEndpoint.setBitbucketJenkinsRootUrl(endpoint.getBitbucketJenkinsRootUrl());
183+
iterator.set(cloudEndpoint);
180184
}
181185
serverUrls.add(serverUrl);
182186
}
183187
if (eps.isEmpty()) {
184-
eps.add(new BitbucketCloudEndpoint(false, null));
188+
eps.add(new BitbucketCloudEndpoint());
185189
}
186190
this.endpoints = eps;
187191
save();

src/main/java/com/cloudbees/jenkins/plugins/bitbucket/endpoints/BitbucketServerEndpoint.java

Lines changed: 31 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
import java.util.Objects;
4040
import jenkins.scm.api.SCMName;
4141
import org.apache.commons.lang3.StringUtils;
42+
import org.jenkinsci.plugins.plaincredentials.StringCredentials;
4243
import org.kohsuke.accmod.Restricted;
4344
import org.kohsuke.accmod.restrictions.NoExternalUse;
4445
import org.kohsuke.stapler.DataBoundConstructor;
@@ -99,32 +100,45 @@ public class BitbucketServerEndpoint extends AbstractBitbucketEndpoint {
99100
private boolean callChanges = true;
100101

101102
/**
102-
* @param displayName Optional name to use to describe the end-point.
103-
* @param serverUrl The URL of this Bitbucket Server
104-
* @param manageHooks {@code true} if and only if Jenkins is supposed to auto-manage hooks for this end-point.
105-
* @param credentialsId The {@link StandardCredentials#getId()} of the credentials to use for
106-
* auto-management of hooks.
103+
* Default constructor.
104+
* @param serverURL
105+
*/
106+
BitbucketServerEndpoint(@NonNull String serverURL) {
107+
this(null, serverURL, false, null, false, null);
108+
}
109+
110+
@Deprecated(since = "936.3.1")
111+
public BitbucketServerEndpoint(@CheckForNull String displayName, @NonNull String serverUrl,
112+
boolean manageHooks, @CheckForNull String credentialsId) {
113+
this(displayName, serverUrl, manageHooks, credentialsId, false, null);
114+
}
115+
116+
/**
117+
* Constructor.
118+
*
119+
* @param displayName Optional name to use to describe the end-point.
120+
* @param serverUrl The URL of this Bitbucket Server
121+
* @param manageHooks {@code true} if and only if Jenkins is supposed to
122+
* auto-manage hooks for this end-point.
123+
* @param credentialsId The {@link StandardCredentials#getId()} of the
124+
* credentials to use for auto-management of hooks.
125+
* @param enableHookSignature {@code true} hooks that comes Bitbucket Data
126+
* Center are signed.
127+
* @param credentialsId The {@link StringCredentials#getId()} of the
128+
* credentials to use for verify the signature of payload.
107129
*/
108130
@DataBoundConstructor
109-
public BitbucketServerEndpoint(@CheckForNull String displayName,
110-
@NonNull String serverUrl,
111-
boolean manageHooks,
112-
@CheckForNull String credentialsId) {
113-
super(manageHooks, credentialsId);
131+
public BitbucketServerEndpoint(@CheckForNull String displayName, @NonNull String serverUrl,
132+
boolean manageHooks, @CheckForNull String credentialsId,
133+
boolean enableHookSignature, @CheckForNull String hookSignatureCredentialsId) {
134+
super(manageHooks, credentialsId, enableHookSignature, hookSignatureCredentialsId);
114135
// use fixNull to silent nullability check
115136
this.serverUrl = Util.fixNull(BitbucketEndpointConfiguration.normalizeServerURL(serverUrl));
116137
this.displayName = StringUtils.isBlank(displayName)
117138
? SCMName.fromUrl(this.serverUrl, COMMON_PREFIX_HOSTNAMES)
118139
: displayName.trim();
119140
}
120141

121-
@Restricted(NoExternalUse.class) // Used for testing
122-
public BitbucketServerEndpoint(@CheckForNull String displayName, @NonNull String serverUrl,
123-
boolean manageHooks, @CheckForNull String credentialsId, String endpointUrl) {
124-
this(displayName, serverUrl, manageHooks, credentialsId);
125-
setBitbucketJenkinsRootUrl(endpointUrl);
126-
}
127-
128142
@NonNull
129143
public static BitbucketServerWebhookImplementation findWebhookImplementation(String serverUrl) {
130144
final BitbucketServerEndpoint endpoint = BitbucketEndpointConfiguration.get()

src/main/java/com/cloudbees/jenkins/plugins/bitbucket/server/BitbucketServerVersion.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,11 @@
2626
import hudson.model.ModelObject;
2727

2828
public enum BitbucketServerVersion implements ModelObject {
29-
VERSION_7("Bitbucket v7.x (and later)"),
30-
VERSION_6_5("Bitbucket v6.5 to v6.10"),
31-
VERSION_6("Bitbucket v6.0 to v6.4"),
32-
VERSION_5_10("Bitbucket v5.10 to v5.16"),
33-
VERSION_5("Bitbucket v5.9 (and earlier)");
29+
VERSION_7("Bitbucket Data Center v8.x (and later)"),
30+
VERSION_6_5("Bitbucket Server v6.5 to v6.10 - EOL reached, any support DISMISSED"),
31+
VERSION_6("Bitbucket Server v6.0 to v6.4 - EOL reached, any support DISMISSED"),
32+
VERSION_5_10("Bitbucket Server v5.10 to v5.16 - EOL reached, any support DISMISSED"),
33+
VERSION_5("Bitbucket Server v5.9 (and earlier) - EOL reached, any support DISMISSED");
3434

3535
private final String displayName;
3636

src/test/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketClientMockUtils.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ public SCMFile answer(InvocationOnMock invocation) throws Throwable {
110110

111111
// Auto-registering hooks
112112
if (includeWebHooks) {
113-
when(client.getWebHooks()).thenReturn(Collections.EMPTY_LIST)
113+
when(client.getWebHooks()).thenReturn(Collections.emptyList())
114114
// Second call
115115
.thenReturn(getWebHooks());
116116
}

src/test/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketMockApiFactory.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
import java.util.Map;
3636
import java.util.Objects;
3737

38-
@Extension(ordinal = 1000)
38+
@Extension
3939
public class BitbucketMockApiFactory extends BitbucketApiFactory {
4040
private static final String NULL = "\u0000\u0000\u0000\u0000";
4141
private final Map<String, BitbucketApi> mocks = new HashMap<>();

src/test/java/com/cloudbees/jenkins/plugins/bitbucket/BranchScanningIntegrationTest.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@
3636
import com.cloudbees.plugins.credentials.common.StandardCredentials;
3737
import com.cloudbees.plugins.credentials.domains.Domain;
3838
import com.cloudbees.plugins.credentials.impl.UsernamePasswordCredentialsImpl;
39-
import hudson.Extension;
4039
import hudson.model.FreeStyleProject;
4140
import hudson.model.JobProperty;
4241
import hudson.model.JobPropertyDescriptor;
@@ -49,6 +48,7 @@
4948
import org.junit.Rule;
5049
import org.junit.Test;
5150
import org.jvnet.hudson.test.JenkinsRule;
51+
import org.jvnet.hudson.test.TestExtension;
5252

5353
import static org.hamcrest.MatcherAssert.assertThat;
5454
import static org.hamcrest.Matchers.instanceOf;
@@ -62,7 +62,7 @@ public class BranchScanningIntegrationTest {
6262
@Test
6363
public void indexingTest() throws Exception {
6464
BitbucketEndpointConfiguration.get()
65-
.addEndpoint(new BitbucketServerEndpoint("test", "http://bitbucket.test", false, null));
65+
.addEndpoint(new BitbucketServerEndpoint("test", "http://bitbucket.test", false, null, false, null));
6666
BitbucketMockApiFactory.add("http://bitbucket.test", BitbucketClientMockUtils.getAPIClientMock(false, false));
6767

6868
MockMultiBranchProjectImpl p = j.jenkins.createProject(MockMultiBranchProjectImpl.class, "test");
@@ -142,7 +142,7 @@ public JobPropertyDescriptor getDescriptor() {
142142
return new DescriptorImpl();
143143
}
144144

145-
@Extension
145+
@TestExtension
146146
public static class DescriptorImpl extends JobPropertyDescriptor {
147147

148148
@Override

src/test/java/com/cloudbees/jenkins/plugins/bitbucket/MockMultiBranchProjectImpl.java

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -40,13 +40,14 @@
4040
import jenkins.branch.MultiBranchProjectDescriptor;
4141
import jenkins.scm.api.SCMSource;
4242
import jenkins.scm.api.SCMSourceCriteria;
43-
import org.acegisecurity.Authentication;
4443

4544
public class MockMultiBranchProjectImpl extends MultiBranchProject<FreeStyleProject, FreeStyleBuild> {
4645

46+
@SuppressWarnings("serial")
4747
public static final SCMSourceCriteria CRITERIA = new SCMSourceCriteria() {
48-
@Override public boolean isHead(SCMSourceCriteria.Probe probe, TaskListener listener) throws IOException {
49-
return probe.exists("markerfile.txt");
48+
@Override
49+
public boolean isHead(SCMSourceCriteria.Probe probe, TaskListener listener) throws IOException {
50+
return probe.stat("markerfile.txt").exists();
5051
}
5152

5253
@Override
@@ -60,15 +61,10 @@ public boolean equals(Object obj) {
6061
}
6162
};
6263

63-
public MockMultiBranchProjectImpl(ItemGroup parent, String name) {
64+
public MockMultiBranchProjectImpl(ItemGroup<?> parent, String name) {
6465
super(parent, name);
6566
}
6667

67-
@Override
68-
public Authentication getDefaultAuthentication(hudson.model.Queue.Item item) {
69-
return getDefaultAuthentication();
70-
}
71-
7268
@Override
7369
protected BranchProjectFactory<FreeStyleProject, FreeStyleBuild> newProjectFactory() {
7470
return new BranchProjectFactoryImpl();
@@ -130,7 +126,7 @@ public String getDisplayName() {
130126
}
131127

132128
@Override
133-
public TopLevelItem newInstance(ItemGroup parent, String name) {
129+
public TopLevelItem newInstance(@SuppressWarnings("rawtypes") ItemGroup parent, String name) {
134130
return new MockMultiBranchProjectImpl(parent, name);
135131
}
136132

0 commit comments

Comments
 (0)