Skip to content

Commit a98b9a3

Browse files
committed
[JENKINS-76027] Allow Bitbucket build status to be customised
Restrict BitbucketAuthenticatedClient to perform call only to the configured server to avoid security issue calling any endpoint using the configured credentials. Simplify extensions points interfaces removing those informations now shipped with the authenticated client.
1 parent 506ed6a commit a98b9a3

17 files changed

+229
-339
lines changed

src/main/java/com/cloudbees/jenkins/plugins/bitbucket/api/BitbucketAuthenticatedClient.java

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,12 +38,62 @@
3838
*/
3939
public interface BitbucketAuthenticatedClient extends AutoCloseable {
4040

41+
/**
42+
* The owner of the repository where register the webhook.
43+
*/
44+
@NonNull
45+
String getRepositoryOwner();
46+
47+
/**
48+
* Name of the repository where register the webhook.
49+
*/
50+
@CheckForNull
51+
String getRepositoryName();
52+
53+
/**
54+
* Perform an HTTP POST to the configured endpoint.
55+
* <p>
56+
* Request will be sent as JSON
57+
*
58+
* @param path to call, it will prepend with the server URL
59+
* @param payload to send
60+
* @return the JSON string of the response
61+
* @throws IOException in case of connection failures
62+
*/
4163
String post(@NonNull String path, @CheckForNull String payload) throws IOException;
4264

65+
/**
66+
* Perform an HTTP PUT to the configured endpoint.
67+
* <p>
68+
* Request will be sent as JSON
69+
*
70+
* @param path to call, it will prepend with the server URL
71+
* @param payload to send
72+
* @return the JSON string of the response
73+
* @throws IOException in case of connection failures
74+
*/
4375
String put(@NonNull String path, @CheckForNull String payload) throws IOException;
4476

77+
/**
78+
* Perform an HTTP DELETE to the configured endpoint.
79+
* <p>
80+
* Request will be sent as JSON
81+
*
82+
* @param path to call, it will prepend with the server URL
83+
* @return the JSON string of the response
84+
* @throws IOException in case of connection failures
85+
*/
4586
String delete(@NonNull String path) throws IOException;
4687

88+
/**
89+
* Perform an HTTP GET to the configured endpoint.
90+
* <p>
91+
* Request will be sent as JSON
92+
*
93+
* @param path to call, it will prepend with the server URL
94+
* @return the JSON string of the response
95+
* @throws IOException in case of connection failures
96+
*/
4797
@NonNull
4898
String get(@NonNull String path) throws IOException;
4999

src/main/java/com/cloudbees/jenkins/plugins/bitbucket/api/BitbucketBuildStatus.java

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,13 @@
2323
*/
2424
package com.cloudbees.jenkins.plugins.bitbucket.api;
2525

26+
import com.cloudbees.jenkins.plugins.bitbucket.api.buildstatus.BitbucketBuildStatusCustomizer;
2627
import com.fasterxml.jackson.annotation.JsonAnyGetter;
2728
import com.fasterxml.jackson.annotation.JsonIgnore;
2829
import com.fasterxml.jackson.annotation.JsonValue;
2930
import edu.umd.cs.findbugs.annotations.NonNull;
3031
import edu.umd.cs.findbugs.annotations.Nullable;
32+
import java.util.Collections;
3133
import java.util.HashMap;
3234
import java.util.Map;
3335
import java.util.Objects;
@@ -235,13 +237,52 @@ public String getParent() {
235237
return parent;
236238
}
237239

240+
/**
241+
* This represent additional informations contributed by
242+
* {@link BitbucketBuildStatusCustomizer}s.
243+
* <p>
244+
* The content of this map will be serialised in the root of the sent
245+
* payload.
246+
* <p>
247+
* For example:
248+
*
249+
* <pre>
250+
* buildStatus.addOptionalData("testResults", new TestResult(1, 2, 3));
251+
* buildStatus.addOptionalData("optX", true);
252+
* </pre>
253+
*
254+
* Will be serialised as:
255+
*
256+
* <pre>
257+
* {
258+
* "description": "The build is in progress..."
259+
* ...
260+
* "testResult": {
261+
* "successful": 5,
262+
* "failed": 2,
263+
* "skipped": 1
264+
* },
265+
* "optX": true
266+
* }
267+
* </pre>
268+
*
269+
* @return an unmodifiable map of extra informations
270+
*/
238271
@JsonAnyGetter
239272
public Map<String, Object> getOptionalData() {
240-
return optionalData;
273+
return Collections.unmodifiableMap(optionalData);
241274
}
242275

243-
public void setOptionalData(Map<String, Object> optionalData) {
244-
this.optionalData = optionalData == null ? new HashMap<>() : new HashMap<>(optionalData);
276+
/**
277+
* Add a single entry key value to the payload to send.
278+
*
279+
* @param key attribute of build status, refer to the Bitbucket API
280+
* @param optionalData value associate to the given key
281+
* @see https://developer.atlassian.com/cloud/bitbucket/rest/api-group-commit-statuses/#api-repositories-workspace-repo-slug-commit-commit-statuses-build-post
282+
* @see https://developer.atlassian.com/server/bitbucket/rest/v906/api-group-builds-and-deployments/#api-api-latest-projects-projectkey-repos-repositoryslug-commits-commitid-builds-post
283+
*/
284+
public void addOptionalData(String key, Object optionalData) {
285+
this.optionalData.put(key, optionalData);
245286
}
246287

247288
@Override

src/main/java/com/cloudbees/jenkins/plugins/bitbucket/api/buildstatus/BitbucketBuildStatusCustomizer.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ default Collection<Class<? extends SCMSourceTrait>> supportedTraits() {
9090
*
9191
* @param traits to apply if supported too
9292
*/
93-
default void withTrais(List<SCMSourceTrait> traits) {
93+
default void withTraits(List<SCMSourceTrait> traits) {
9494
supportedTraits().forEach(traitClass -> {
9595
SCMSourceTrait trait = SCMTrait.find(traits, traitClass);
9696
if (trait != null) {

src/main/java/com/cloudbees/jenkins/plugins/bitbucket/api/buildstatus/BitbucketBuildStatusNotifier.java

Lines changed: 0 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -41,26 +41,5 @@ public interface BitbucketBuildStatusNotifier extends ExtensionPoint {
4141
*/
4242
boolean isApplicable(@NonNull EndpointType type);
4343

44-
/**
45-
* The owner of the repository where register the webhook.
46-
*
47-
* @param repositoryOwner name
48-
*/
49-
void setRepositoryOwner(@NonNull String repositoryOwner);
50-
51-
/**
52-
* Name of the repository where register the webhook.
53-
*
54-
* @param repositoryName
55-
*/
56-
void setRepositoryName(@NonNull String repositoryName);
57-
58-
/**
59-
* The base URL of endpoint of the Bitbucket host.
60-
*
61-
* @param serverURL the base of the endpoint to call.
62-
*/
63-
void setServerURL(@NonNull String serverURL);
64-
6544
void sendBuildStatus(@NonNull BitbucketBuildStatus status, @NonNull BitbucketAuthenticatedClient client) throws IOException;
6645
}

src/main/java/com/cloudbees/jenkins/plugins/bitbucket/api/webhook/BitbucketWebhookManager.java

Lines changed: 5 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525

2626
import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketAuthenticatedClient;
2727
import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketWebHook;
28+
import com.cloudbees.jenkins.plugins.bitbucket.api.endpoint.BitbucketEndpoint;
2829
import edu.umd.cs.findbugs.annotations.NonNull;
2930
import hudson.ExtensionPoint;
3031
import java.io.IOException;
@@ -48,27 +49,6 @@
4849
@Restricted(Beta.class)
4950
public interface BitbucketWebhookManager extends ExtensionPoint {
5051

51-
/**
52-
* The owner of the repository where register the webhook.
53-
*
54-
* @param repositoryOwner name
55-
*/
56-
void setRepositoryOwner(@NonNull String repositoryOwner);
57-
58-
/**
59-
* Name of the repository where register the webhook.
60-
*
61-
* @param repositoryName
62-
*/
63-
void setRepositoryName(@NonNull String repositoryName);
64-
65-
/**
66-
* The base URL of endpoint of the Bitbucket host.
67-
*
68-
* @param serverURL the base of the endpoint to call.
69-
*/
70-
void setServerURL(@NonNull String serverURL);
71-
7252
/**
7353
* The callback URL where send event payload.
7454
* <p>
@@ -78,8 +58,10 @@ public interface BitbucketWebhookManager extends ExtensionPoint {
7858
* endpoint to process own events.
7959
*
8060
* @param callbackURL used to send webhook payload.
61+
* @param endpoint this webhook is registered for, it could be used to
62+
* retrieve additional information to compose the callbackURL
8163
*/
82-
void setCallbackURL(@NonNull String callbackURL);
64+
void setCallbackURL(@NonNull String callbackURL, @NonNull BitbucketEndpoint endpoint);
8365

8466
/**
8567
* The configuration that returned this implementation class.
@@ -115,7 +97,7 @@ default void apply(SCMSourceTrait trait) {}
11597
*
11698
* @param traits to apply if supported too
11799
*/
118-
default void withTrais(List<SCMSourceTrait> traits) {
100+
default void withTraits(List<SCMSourceTrait> traits) {
119101
supportedTraits().forEach(traitClass -> {
120102
SCMSourceTrait trait = SCMTrait.find(traits, traitClass);
121103
if (trait != null) {

src/main/java/com/cloudbees/jenkins/plugins/bitbucket/client/BitbucketCloudApiClient.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -528,8 +528,6 @@ public List<BitbucketCloudWebhook> getWebHooks() throws IOException {
528528
@Override
529529
public void postBuildStatus(@NonNull BitbucketBuildStatus status) throws IOException {
530530
CloudBuildStatusNotifier notifier = new CloudBuildStatusNotifier();
531-
notifier.setRepositoryName(repositoryName);
532-
notifier.setRepositoryOwner(owner);
533531
notifier.sendBuildStatus(status, adapt(BitbucketAuthenticatedClient.class));
534532
}
535533

@@ -679,6 +677,12 @@ protected HttpHost getHost() {
679677
return API_HOST;
680678
}
681679

680+
@NonNull
681+
@Override
682+
protected String getBaseURL() {
683+
return "https://api.bitbucket.org";
684+
}
685+
682686
@NonNull
683687
@Override
684688
protected CloseableHttpClient getClient() {

src/main/java/com/cloudbees/jenkins/plugins/bitbucket/hooks/WebhookAutoRegisterListener.java

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -187,17 +187,14 @@ private BitbucketWebhookManager buildWebhookManager(BitbucketSCMSource source, B
187187
BitbucketWebhookManager manager = ExtensionList.lookupFirst(webhookConfig.getManager());
188188
// setup manager with base required information
189189
manager.apply(webhookConfig);
190-
manager.setServerURL(endpoint.getServerURL());
191-
manager.setRepositoryOwner(source.getRepoOwner());
192-
manager.setRepositoryName(source.getRepository());
193190

194191
String callbackRootURL = getCallbackRootURL(webhookConfig);
195192
// this is the base callback URL that webhook usually should call to be processed
196193
String callbackURL = callbackRootURL + BitbucketSCMSourcePushHookReceiver.FULL_PATH;
197-
manager.setCallbackURL(callbackURL);
194+
manager.setCallbackURL(callbackURL, endpoint);
198195

199196
// setup traits extra informations
200-
manager.withTrais(source.getTraits());
197+
manager.withTraits(source.getTraits());
201198
return manager;
202199
}
203200

src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/buildstatus/CloudBuildStatusNotifier.java

Lines changed: 3 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -37,33 +37,16 @@
3737

3838
@Extension
3939
public class CloudBuildStatusNotifier implements BitbucketBuildStatusNotifier {
40-
private static final String COMMIT_BUILD_STATUS_URL = "https://api.bitbucket.org/2.0/repositories{/owner,repo}/commit/{hash}/statuses/build";
41-
42-
private String repositoryOwner;
43-
private String repositoryName;
44-
45-
@Override
46-
public void setRepositoryOwner(@NonNull String repositoryOwner) {
47-
this.repositoryOwner = repositoryOwner;
48-
}
49-
50-
@Override
51-
public void setRepositoryName(@NonNull String repositoryName) {
52-
this.repositoryName = repositoryName;
53-
}
54-
55-
@Override
56-
public void setServerURL(@NonNull String serverURL) {
57-
}
40+
private static final String COMMIT_BUILD_STATUS_URL = "/2.0/repositories{/owner,repo}/commit/{hash}/statuses/build";
5841

5942
@Override
6043
public void sendBuildStatus(@NonNull BitbucketBuildStatus status, @NonNull BitbucketAuthenticatedClient client) throws IOException {
6144
BitbucketBuildStatus newStatus = new BitbucketBuildStatus(status);
6245
newStatus.setName(abbreviate(newStatus.getName(), 255));
6346

6447
String url = UriTemplate.fromTemplate(COMMIT_BUILD_STATUS_URL)
65-
.set("owner", repositoryOwner)
66-
.set("repo", repositoryName)
48+
.set("owner", client.getRepositoryOwner())
49+
.set("repo", client.getRepositoryName())
6750
.set("hash", newStatus.getHash())
6851
.expand();
6952
client.post(url, JsonParser.toString(newStatus));

src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/buildstatus/ServerBuildStatusNotifier.java

Lines changed: 3 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -43,25 +43,6 @@
4343
public class ServerBuildStatusNotifier implements BitbucketBuildStatusNotifier {
4444
private static final String API_COMMIT_STATUS_PATH = "/rest/api/1.0/projects/{owner}/repos/{repo}/commits/{hash}/builds";
4545

46-
private String repositoryOwner;
47-
private String repositoryName;
48-
private String serverURL;
49-
50-
@Override
51-
public void setRepositoryOwner(@NonNull String repositoryOwner) {
52-
this.repositoryOwner = repositoryOwner;
53-
}
54-
55-
@Override
56-
public void setRepositoryName(@NonNull String repositoryName) {
57-
this.repositoryName = repositoryName;
58-
}
59-
60-
@Override
61-
public void setServerURL(@NonNull String serverURL) {
62-
this.serverURL = serverURL;
63-
}
64-
6546
@Override
6647
public void sendBuildStatus(@NonNull BitbucketBuildStatus status, @NonNull BitbucketAuthenticatedClient client) throws IOException {
6748
BitbucketServerBuildStatus newStatus = new BitbucketServerBuildStatus(status);
@@ -72,15 +53,14 @@ public void sendBuildStatus(@NonNull BitbucketBuildStatus status, @NonNull Bitbu
7253
newStatus.setKey(substring(key, 0, 255 - 33) + '/' + DigestUtils.md5Hex(key));
7354
}
7455

75-
String url = UriTemplate.fromTemplate(this.serverURL + API_COMMIT_STATUS_PATH)
76-
.set("owner", repositoryOwner)
77-
.set("repo", repositoryName)
56+
String url = UriTemplate.fromTemplate(API_COMMIT_STATUS_PATH)
57+
.set("owner", client.getRepositoryOwner())
58+
.set("repo", client.getRepositoryName())
7859
.set("hash", newStatus.getHash())
7960
.expand();
8061
client.post(url, JsonParser.toString(newStatus));
8162
}
8263

83-
8464
@Override
8565
public boolean isApplicable(@NonNull EndpointType type) {
8666
return type == EndpointType.SERVER;

0 commit comments

Comments
 (0)