Skip to content

Commit 1563c40

Browse files
committed
[JENKINS-75735] Hook payload is discarded if not recognized and comes from Bitbucket Cloud and Data Center instances
Change test cases to better reflect the header sent in the request of a webhook Add payload examples provided by Alexey P. from moveworkforward support team.
1 parent 44b1a90 commit 1563c40

File tree

11 files changed

+710
-35
lines changed

11 files changed

+710
-35
lines changed

docs/USER_GUIDE.adoc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ For Bitbucket Data Center only it is possible chose which webhooks implementatio
119119

120120
- Native implementation will configure the webhooks provided by default with the Server, so it will always be available.
121121

122-
- Plugin implementation relies on the configuration available via specific APIs provided by the https://marketplace.atlassian.com/apps/1215474/post-webhooks-for-bitbucket?tab=overview&hosting=datacenter[Post Webhooks for Bitbucket] plugin itself. To get it worked plugin must be already pre-installed on the server instance. This provider allows custom settings managed by the _ignore committers_ trait. _Note: This specific implementation will be moved to an individual repository as soon as https://issues.jenkins.io/browse/JENKINS-74913[JENKINS-74913] is implemented._
122+
- Plugin implementation (*deprecated*) relies on the configuration available via specific APIs provided by the https://marketplace.atlassian.com/apps/1215474/post-webhooks-for-bitbucket?tab=overview&hosting=datacenter[Post Webhooks for Bitbucket] plugin itself. To get it worked plugin must be already pre-installed on the server instance. This provider allows custom settings managed by the _ignore committers_ trait. _Note: This specific implementation will be moved to an individual repository as soon as https://issues.jenkins.io/browse/JENKINS-74913[JENKINS-74913] is implemented._
123123

124124
image::images/screenshot-14.png[]
125125

@@ -131,7 +131,7 @@ image::images/screenshot-18.png[]
131131
IMPORTANT: In order to have the auto-registering process working fine the Jenkins base URL must be
132132
properly configured in _Manage Jenkins_ » _System_
133133

134-
=== Webhooks signature
134+
=== Signature verification for incoming webhooks
135135

136136
Once Jenkins is configured to receive payloads, it will listen for any delivery that's sent to the endpoint you configured. For security reasons, you should only process deliveries from Bitbucket.
137137
To ensure your self-hosted server only processes deliveries from Bitbucket, you need to:

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

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -107,16 +107,20 @@ public HttpResponse doNotify(StaplerRequest2 req) throws IOException {
107107
return HttpResponses.error(HttpServletResponse.SC_BAD_REQUEST, "X-Event-Key HTTP header invalid: " + eventKey);
108108
}
109109

110-
String bitbucketKey = req.getHeader("X-Bitbucket-Type");
110+
String bitbucketKey = req.getHeader("X-Bitbucket-Type"); // specific header from Plugin implementation
111111
String serverURL = req.getParameter("server_url");
112112

113113
BitbucketType instanceType = null;
114114
if (bitbucketKey != null) {
115115
instanceType = BitbucketType.fromString(bitbucketKey);
116116
}
117-
if (instanceType == null && serverURL != null) {
118-
LOGGER.log(Level.FINE, "server_url request parameter found. Bitbucket Native Server webhook incoming.");
119-
instanceType = BitbucketType.SERVER;
117+
if (serverURL != null) {
118+
if (instanceType == null) {
119+
LOGGER.log(Level.FINE, "server_url request parameter found. Bitbucket Native Server webhook incoming.");
120+
instanceType = BitbucketType.SERVER;
121+
} else {
122+
LOGGER.log(Level.FINE, "X-Bitbucket-Type header / server_url request parameter found. Bitbucket Plugin Server webhook incoming.");
123+
}
120124
} else {
121125
LOGGER.log(Level.FINE, "X-Bitbucket-Type header / server_url request parameter not found. Bitbucket Cloud webhook incoming.");
122126
instanceType = BitbucketType.CLOUD;
@@ -179,7 +183,7 @@ private HttpResponseException checkSignature(@NonNull StaplerRequest2 req, @NonN
179183
String requestId = ObjectUtils.firstNonNull(req.getHeader("X-Request-UUID"), req.getHeader("X-Request-Id"));
180184
String hookSignatureCredentialsId = endpoint.getHookSignatureCredentialsId();
181185
LOGGER.log(Level.WARNING, "No credentials {0} found to verify the signature of incoming webhook {1} request {2}", new Object[] { hookSignatureCredentialsId, hookId, requestId });
182-
return HttpResponses.error(HttpServletResponse.SC_FORBIDDEN, "No credentials " + hookSignatureCredentialsId + " found to verify the signature");
186+
return HttpResponses.error(HttpServletResponse.SC_FORBIDDEN, "No credentials " + hookSignatureCredentialsId + " found in Jenkins to verify the signature");
183187
}
184188
return null;
185189
}

src/test/java/com/cloudbees/jenkins/plugins/bitbucket/hooks/BitbucketSCMSourcePushHookReceiverTest.java

Lines changed: 170 additions & 28 deletions
Large diffs are not rendered by default.
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"test": true
3+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
{
2+
"actor": {
3+
"username": "admin",
4+
"displayName": "Administrator",
5+
"emailAddress": "admin@example.com"
6+
},
7+
"repository": {
8+
"scmId": "git",
9+
"project": {
10+
"key": "PROJECT_1",
11+
"name": "Project 1"
12+
},
13+
"slug": "rep_1",
14+
"links": {
15+
"self": [
16+
{
17+
"href": "http://localhost:7990/bitbucket/projects/PROJECT_1/repos/rep_1/browse"
18+
}
19+
]
20+
},
21+
"public": false,
22+
"ownerName": "PROJECT_1",
23+
"fullName": "PROJECT_1/rep_1",
24+
"owner": {
25+
"username": "PROJECT_1",
26+
"displayName": "PROJECT_1",
27+
"emailAddress": null
28+
}
29+
},
30+
"push": {
31+
"changes": [
32+
{
33+
"created": true,
34+
"closed": false,
35+
"new": {
36+
"type": "branch",
37+
"name": "test-webhook",
38+
"target": {
39+
"type": "commit",
40+
"hash": "417b2f673581ee6000e260a5fa65e62b56c7a3cd",
41+
"commitMessage": "Test webhooks"
42+
}
43+
},
44+
"old": null
45+
}
46+
]
47+
}
48+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
{
2+
"actor": {
3+
"username": "admin",
4+
"displayName": "Administrator",
5+
"emailAddress": "admin@example.com"
6+
},
7+
"repository": {
8+
"scmId": "git",
9+
"project": {
10+
"key": "PROJECT_1",
11+
"name": "Project 1"
12+
},
13+
"slug": "rep_1",
14+
"links": {
15+
"self": [
16+
{
17+
"href": "http://localhost:7990/bitbucket/projects/PROJECT_1/repos/rep_1/browse"
18+
}
19+
]
20+
},
21+
"public": false,
22+
"ownerName": "PROJECT_1",
23+
"fullName": "PROJECT_1/rep_1",
24+
"owner": {
25+
"username": "PROJECT_1",
26+
"displayName": "PROJECT_1",
27+
"emailAddress": null
28+
}
29+
},
30+
"push": {
31+
"changes": [
32+
{
33+
"created": false,
34+
"closed": true,
35+
"new": null,
36+
"old": {
37+
"type": "branch",
38+
"name": "test-webhook",
39+
"target": {
40+
"type": "commit",
41+
"hash": "c0158b3e6c8cecf3bddc39d20957a98660cd23fd",
42+
"commitMessage": "Test webhook 2"
43+
}
44+
}
45+
}
46+
]
47+
}
48+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
{
2+
"actor": {
3+
"username": "admin",
4+
"displayName": "Administrator",
5+
"emailAddress": "admin@example.com"
6+
},
7+
"repository": {
8+
"scmId": "git",
9+
"project": {
10+
"key": "PROJECT_1",
11+
"name": "Project 1"
12+
},
13+
"slug": "rep_1",
14+
"links": {
15+
"self": [
16+
{
17+
"href": "http://localhost:7990/bitbucket/projects/PROJECT_1/repos/rep_1/browse"
18+
}
19+
]
20+
},
21+
"public": false,
22+
"ownerName": "PROJECT_1",
23+
"fullName": "PROJECT_1/rep_1",
24+
"owner": {
25+
"username": "PROJECT_1",
26+
"displayName": "PROJECT_1",
27+
"emailAddress": null
28+
}
29+
},
30+
"push": {
31+
"changes": [
32+
{
33+
"created": false,
34+
"closed": false,
35+
"new": {
36+
"type": "branch",
37+
"name": "test-webhook",
38+
"target": {
39+
"type": "commit",
40+
"hash": "c0158b3e6c8cecf3bddc39d20957a98660cd23fd",
41+
"commitMessage": "Test webhook 2"
42+
}
43+
},
44+
"old": {
45+
"type": "branch",
46+
"name": "test-webhook",
47+
"target": {
48+
"type": "commit",
49+
"hash": "417b2f673581ee6000e260a5fa65e62b56c7a3cd",
50+
"commitMessage": "Test webhooks"
51+
}
52+
}
53+
}
54+
]
55+
}
56+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
{
2+
"actor": {
3+
"username": "admin",
4+
"displayName": "Administrator",
5+
"emailAddress": "admin@example.com"
6+
},
7+
"repository": {
8+
"scmId": "git",
9+
"project": {
10+
"key": "PROJECT_1",
11+
"name": "Project 1"
12+
},
13+
"slug": "rep_1",
14+
"links": {
15+
"self": [
16+
{
17+
"href": "http://localhost:7990/bitbucket/projects/PROJECT_1/repos/rep_1/browse"
18+
}
19+
]
20+
},
21+
"public": false,
22+
"ownerName": "PROJECT_1",
23+
"fullName": "PROJECT_1/rep_1",
24+
"owner": {
25+
"username": "PROJECT_1",
26+
"displayName": "PROJECT_1",
27+
"emailAddress": null
28+
}
29+
},
30+
"push": {
31+
"changes": [
32+
{
33+
"created": false,
34+
"closed": false,
35+
"new": {
36+
"type": "branch",
37+
"name": "master",
38+
"target": {
39+
"type": "commit",
40+
"hash": "500cf91e7b4b7d9f995cdb6e81cb5538216ac02e",
41+
"commitMessage": "Pull request #1: Test webhooks\n\nMerge in PROJECT_1/rep_1 from test-webhook to master\n\n* commit 'c0158b3e6c8cecf3bddc39d20957a98660cd23fd':\n Test webhook 2\n Test webhooks"
42+
}
43+
},
44+
"old": {
45+
"type": "branch",
46+
"name": "master",
47+
"target": {
48+
"type": "commit",
49+
"hash": "0a943a29376f2336b78312d99e65da17048951db",
50+
"commitMessage": "Copy C.zip as D.zip"
51+
}
52+
}
53+
}
54+
]
55+
}
56+
}
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
{
2+
"actor": {
3+
"username": "admin",
4+
"displayName": "Administrator",
5+
"emailAddress": "admin@example.com"
6+
},
7+
"pullrequest": {
8+
"id": "1",
9+
"title": "Test webhooks",
10+
"link": "http://localhost:7990/bitbucket/projects/PROJECT_1/repos/rep_1/pull-requests/1",
11+
"authorLogin": "Administrator",
12+
"fromRef": {
13+
"repository": {
14+
"scmId": "git",
15+
"project": {
16+
"key": "PROJECT_1",
17+
"name": "Project 1"
18+
},
19+
"slug": "rep_1",
20+
"links": {
21+
"self": [
22+
{
23+
"href": "http://localhost:7990/bitbucket/projects/PROJECT_1/repos/rep_1/browse"
24+
}
25+
]
26+
},
27+
"public": false,
28+
"ownerName": "PROJECT_1",
29+
"fullName": "PROJECT_1/rep_1",
30+
"owner": {
31+
"username": "PROJECT_1",
32+
"displayName": "PROJECT_1",
33+
"emailAddress": null
34+
}
35+
},
36+
"branch": {
37+
"rawNode": "417b2f673581ee6000e260a5fa65e62b56c7a3cd",
38+
"name": "test-webhook"
39+
},
40+
"commit": {
41+
"message": null,
42+
"date": "6/4/25 11:12 PM",
43+
"hash": "417b2f673581ee6000e260a5fa65e62b56c7a3cd",
44+
"authorTimestamp": 1749093143379
45+
}
46+
},
47+
"toRef": {
48+
"repository": {
49+
"scmId": "git",
50+
"project": {
51+
"key": "PROJECT_1",
52+
"name": "Project 1"
53+
},
54+
"slug": "rep_1",
55+
"links": {
56+
"self": [
57+
{
58+
"href": "http://localhost:7990/bitbucket/projects/PROJECT_1/repos/rep_1/browse"
59+
}
60+
]
61+
},
62+
"public": false,
63+
"ownerName": "PROJECT_1",
64+
"fullName": "PROJECT_1/rep_1",
65+
"owner": {
66+
"username": "PROJECT_1",
67+
"displayName": "PROJECT_1",
68+
"emailAddress": null
69+
}
70+
},
71+
"branch": {
72+
"rawNode": "0a943a29376f2336b78312d99e65da17048951db",
73+
"name": "master"
74+
},
75+
"commit": {
76+
"message": null,
77+
"date": "6/4/25 11:12 PM",
78+
"hash": "0a943a29376f2336b78312d99e65da17048951db",
79+
"authorTimestamp": 1749093143385
80+
}
81+
}
82+
},
83+
"repository": {
84+
"scmId": "git",
85+
"project": {
86+
"key": "PROJECT_1",
87+
"name": "Project 1"
88+
},
89+
"slug": "rep_1",
90+
"links": {
91+
"self": [
92+
{
93+
"href": "http://localhost:7990/bitbucket/projects/PROJECT_1/repos/rep_1/browse"
94+
}
95+
]
96+
},
97+
"public": false,
98+
"ownerName": "PROJECT_1",
99+
"fullName": "PROJECT_1/rep_1",
100+
"owner": {
101+
"username": "PROJECT_1",
102+
"displayName": "PROJECT_1",
103+
"emailAddress": null
104+
}
105+
}
106+
}

0 commit comments

Comments
 (0)