Skip to content

Commit 91d55c3

Browse files
authored
[JENKINS-75680] Authentication fails when call secured REST API (#1048)
Add a check to fail fast a client instance if the authenticator is not supported.
1 parent d54103f commit 91d55c3

File tree

8 files changed

+97
-17
lines changed

8 files changed

+97
-17
lines changed

docs/USER_GUIDE.adoc

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -155,9 +155,26 @@ As the `Checkout Credential` configuration was removed in commit (link:https://g
155155

156156
image::images/screenshot-7.png[]
157157

158-
=== Access Token
158+
=== Username/Password (Bitbucket Data Center only)
159159

160-
The plugin can make use of a repository, project or workspace access token (Bitbucket Cloud only).
160+
The plugin can use a personal username and password to login (if SSO and MFA in not enabled).
161+
162+
The user must have _admin_ access to the Project if you are configuring an _Organization Folder_ or _admin_ right to the repositories if you are configuring a _Multibrach Project_.
163+
164+
=== Personal Access Token (Bitbucket Data Center only)
165+
166+
The plugin can make use of a personal access token instead of the standard username/password.
167+
168+
First, create a new _personal access token_ in Bitbucket as instructed in the link:https://confluence.atlassian.com/bitbucketserver080/http-access-tokens-1115142284.html[HTTP access tokens | Bitbucket Data Center and Server 8.0 | Atlassian Documentation].
169+
At least allow _read_ access for Projects. If you want the plugin to install the webhooks, allow _admin_ access for repositories.
170+
171+
image::images/screenshot-21.png[]
172+
173+
Then create a new _Username with password_ credential in Jenkins, enter the Bitbucket username (not the email) in the _Username_ field and the created access token in the _Password_ field.
174+
175+
=== Access Token (Bitbucket Cloud only)
176+
177+
The plugin can make use of a repository, project or workspace access token.
161178

162179
First, create a new _access token_ in Bitbucket as instructed in one of the following links:
163180

@@ -175,18 +192,9 @@ If you want be able to perform git push operation from CLI than you have to setu
175192

176193
image::images/screenshot-17.png[]
177194

178-
=== Personal Access Token
179-
180-
The plugin can make use of a personal access token (Bitbucket Datacenter only) instead of the standard username/password.
181-
182-
First, create a new _personal access token_ in Bitbucket as instructed in the link:https://confluence.atlassian.com/bitbucketserver080/http-access-tokens-1115142284.html[HTTP access tokens | Bitbucket Data Center and Server 8.0 | Atlassian Documentation].
183-
At least allow _read_ access for repositories. If you want the plugin to install the webhooks, allow _admin_ access for repositories.
184-
185-
Then create a new _Username with password_ credential in Jenkins, enter the Bitbucket username (not the email) in the _Username_ field and the created access token in the _Password_ field.
186-
187-
=== App Passwords
195+
=== App Passwords (Bitbucket Cloud only)
188196

189-
Bitbucket https://community.atlassian.com/t5/Bitbucket-articles/Announcement-Bitbucket-Cloud-account-password-usage-for-Git-over/ba-p/1948231[deprecated usage of Atlassian account password] for Bitbucket API and Git over HTTPS starting from March 1st, 2022 (Bitbucket Cloud only).
197+
Bitbucket https://community.atlassian.com/t5/Bitbucket-articles/Announcement-Bitbucket-Cloud-account-password-usage-for-Git-over/ba-p/1948231[deprecated usage of Atlassian account password] for Bitbucket API and Git over HTTPS starting from March 1st, 2022.
190198

191199
The plugin can make use of an app password instead of the standard username/password.
192200

@@ -196,9 +204,9 @@ Then create a new _Username with password credentials_ in Jenkins, enter the Bit
196204

197205
IMPORTANT: App passwords do not support email address as a username for authentication. Using the email address will raise an authentication error in scanning/checkout process.
198206

199-
=== OAuth credentials
207+
=== OAuth credentials (Bitbucket Cloud only)
200208

201-
The plugin can make use of OAuth credentials (Bitbucket Cloud only) instead of the standard username/password.
209+
The plugin can make use of OAuth credentials instead of the standard username/password.
202210

203211
First create a new _OAuth consumer_ in Bitbucket as instructed in the https://confluence.atlassian.com/bitbucket/oauth-on-bitbucket-cloud-238027431.html[Bitbucket OAuth Documentation].
204212
Don't forget to check _This is a private consumer_ and at least allow _read_ access for repositories and pull requests. If you want the plugin to install the webhooks, also allow _read_ and _write_ access for webhooks.
@@ -313,4 +321,4 @@ By default, the plugin triggers *a full branch indexing* when a push event conta
313321
* For a fork, when link:https://confluence.atlassian.com/bitbucketserver/keeping-forks-synchronized-776639961.html[Auto-Sync] is on and a branch cannot be synchronised
314322
* A link:http://confluence.atlassian.com/bitbucketserver/event-payload-938025882.html#Eventpayload-Mirrorsynchronized[mirror:repo_synchronized] event with too many refs
315323

316-
This behavior can be disabled by adding the system property `bitbucket.hooks.processor.scanOnEmptyChanges=false` on Jenkins startup.
324+
This behaviour can be disabled by adding the system property `bitbucket.hooks.processor.scanOnEmptyChanges=false` on Jenkins startup.

docs/images/screenshot-21.png

68.2 KB
Loading

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,9 @@
4545
import com.cloudbees.jenkins.plugins.bitbucket.filesystem.BitbucketSCMFile;
4646
import com.cloudbees.jenkins.plugins.bitbucket.impl.client.AbstractBitbucketApi;
4747
import com.cloudbees.jenkins.plugins.bitbucket.impl.client.ICheckedCallable;
48+
import com.cloudbees.jenkins.plugins.bitbucket.impl.credentials.BitbucketAccessTokenAuthenticator;
49+
import com.cloudbees.jenkins.plugins.bitbucket.impl.credentials.BitbucketOAuthAuthenticator;
50+
import com.cloudbees.jenkins.plugins.bitbucket.impl.credentials.BitbucketUsernamePasswordAuthenticator;
4851
import com.cloudbees.jenkins.plugins.bitbucket.impl.util.BitbucketApiUtils;
4952
import com.cloudbees.jenkins.plugins.bitbucket.impl.util.JsonParser;
5053
import com.damnhandy.uri.template.UriTemplate;
@@ -139,6 +142,14 @@ public BitbucketCloudApiClient(boolean enableCache, int teamCacheDuration, int r
139142
this.client = super.setupClientBuilder().build();
140143
}
141144

145+
@Override
146+
protected boolean isSupportedAuthenticator(@CheckForNull BitbucketAuthenticator authenticator) {
147+
return authenticator == null
148+
|| authenticator instanceof BitbucketAccessTokenAuthenticator
149+
|| authenticator instanceof BitbucketOAuthAuthenticator
150+
|| authenticator instanceof BitbucketUsernamePasswordAuthenticator; // app password
151+
}
152+
142153
/**
143154
* {@inheritDoc}
144155
*/

src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/client/AbstractBitbucketApi.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
package com.cloudbees.jenkins.plugins.bitbucket.impl.client;
2525

2626
import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketAuthenticator;
27+
import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketException;
2728
import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketRequestException;
2829
import com.cloudbees.jenkins.plugins.bitbucket.client.ClosingConnectionInputStream;
2930
import edu.umd.cs.findbugs.annotations.CheckForNull;
@@ -85,6 +86,9 @@ public abstract class AbstractBitbucketApi implements AutoCloseable {
8586
private HttpClientContext context;
8687

8788
protected AbstractBitbucketApi(BitbucketAuthenticator authenticator) {
89+
if (!isSupportedAuthenticator(authenticator)) {
90+
throw new BitbucketException("Authentication " + authenticator.getClass().getSimpleName() + " is not supported by this client. Please refer to the user documention at https://github.com/jenkinsci/bitbucket-branch-source-plugin/blob/master/docs/USER_GUIDE.adoc");
91+
}
8892
this.authenticator = authenticator;
8993
}
9094

@@ -217,6 +221,11 @@ protected void setClientProxyParams(HttpClientBuilder builder) {
217221
}
218222
}
219223

224+
/**
225+
* Implementation must validate if the configured authenticator is supported by this client implementation.
226+
*/
227+
protected abstract boolean isSupportedAuthenticator(@CheckForNull BitbucketAuthenticator authenticator);
228+
220229
@CheckForNull
221230
protected abstract HttpClientConnectionManager getConnectionManager();
222231

src/main/java/com/cloudbees/jenkins/plugins/bitbucket/server/client/BitbucketServerAPIClient.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@
4040
import com.cloudbees.jenkins.plugins.bitbucket.endpoints.BitbucketServerEndpoint;
4141
import com.cloudbees.jenkins.plugins.bitbucket.filesystem.BitbucketSCMFile;
4242
import com.cloudbees.jenkins.plugins.bitbucket.impl.client.AbstractBitbucketApi;
43+
import com.cloudbees.jenkins.plugins.bitbucket.impl.credentials.BitbucketAccessTokenAuthenticator;
44+
import com.cloudbees.jenkins.plugins.bitbucket.impl.credentials.BitbucketClientCertificateAuthenticator;
45+
import com.cloudbees.jenkins.plugins.bitbucket.impl.credentials.BitbucketUsernamePasswordAuthenticator;
4346
import com.cloudbees.jenkins.plugins.bitbucket.impl.util.BitbucketApiUtils;
4447
import com.cloudbees.jenkins.plugins.bitbucket.impl.util.JsonParser;
4548
import com.cloudbees.jenkins.plugins.bitbucket.server.BitbucketServerVersion;
@@ -176,6 +179,14 @@ public BitbucketServerAPIClient(@NonNull String baseURL, @NonNull String owner,
176179
this.client = setupClientBuilder().build();
177180
}
178181

182+
@Override
183+
protected boolean isSupportedAuthenticator(@CheckForNull BitbucketAuthenticator authenticator) {
184+
return authenticator == null
185+
|| authenticator instanceof BitbucketClientCertificateAuthenticator // undocumented mutual TLS
186+
|| authenticator instanceof BitbucketAccessTokenAuthenticator // personal access token
187+
|| authenticator instanceof BitbucketUsernamePasswordAuthenticator; // username/password credentials
188+
}
189+
179190
/**
180191
* Bitbucket Server manages two top level entities, owner and/or project.
181192
* Only one of them makes sense for a specific client object.

src/test/java/com/cloudbees/jenkins/plugins/bitbucket/client/BitbucketCloudApiClientTest.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,14 @@
2727
import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketAuthenticator;
2828
import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketBuildStatus;
2929
import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketBuildStatus.Status;
30+
import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketException;
3031
import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketWebHook;
3132
import com.cloudbees.jenkins.plugins.bitbucket.client.repository.BitbucketCloudRepository;
3233
import com.cloudbees.jenkins.plugins.bitbucket.endpoints.BitbucketCloudEndpoint;
34+
import com.cloudbees.jenkins.plugins.bitbucket.impl.credentials.BitbucketAccessTokenAuthenticator;
35+
import com.cloudbees.jenkins.plugins.bitbucket.impl.credentials.BitbucketClientCertificateAuthenticator;
36+
import com.cloudbees.jenkins.plugins.bitbucket.impl.credentials.BitbucketOAuthAuthenticator;
37+
import com.cloudbees.jenkins.plugins.bitbucket.impl.credentials.BitbucketUsernamePasswordAuthenticator;
3338
import com.cloudbees.jenkins.plugins.bitbucket.impl.util.DateUtils;
3439
import com.cloudbees.jenkins.plugins.bitbucket.impl.util.JsonParser;
3540
import com.cloudbees.jenkins.plugins.bitbucket.test.util.BitbucketTestUtil;
@@ -50,7 +55,9 @@
5055

5156
import static net.javacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson;
5257
import static org.assertj.core.api.Assertions.assertThat;
58+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
5359
import static org.mockito.ArgumentMatchers.any;
60+
import static org.mockito.Mockito.mock;
5461
import static org.mockito.Mockito.never;
5562
import static org.mockito.Mockito.spy;
5663
import static org.mockito.Mockito.verify;
@@ -127,4 +134,13 @@ void verifyUpdateWebhookURL() throws Exception {
127134
assertThat(put.getRequestUri()).isEqualTo("https://api.bitbucket.org/2.0/repositories/amuniz/test-repos/hooks/%7B202cf34e-7ccf-44b7-ba6b-8827a14d5324%7D"));
128135
}
129136

137+
@Test
138+
void test_supported_auth() throws Exception {
139+
try (BitbucketApi client = new BitbucketCloudApiClient(false, 0, 0, null, null, null, mock(BitbucketUsernamePasswordAuthenticator.class))) {}
140+
try (BitbucketApi client = new BitbucketCloudApiClient(false, 0, 0, null, null, null, mock(BitbucketOAuthAuthenticator.class))) {}
141+
try (BitbucketApi client = new BitbucketCloudApiClient(false, 0, 0, null, null, null, mock(BitbucketAccessTokenAuthenticator.class))) {}
142+
143+
assertThatThrownBy(() -> new BitbucketCloudApiClient(false, 0, 0, null, null, null, mock(BitbucketClientCertificateAuthenticator.class)))
144+
.isInstanceOf(BitbucketException.class);
145+
}
130146
}

src/test/java/com/cloudbees/jenkins/plugins/bitbucket/client/BitbucketIntegrationClientFactory.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,11 @@ private BitbucketServerIntegrationClient(String payloadRootPath, String baseURL,
155155
this.audit = new RequestAudit();
156156
}
157157

158+
@Override
159+
protected boolean isSupportedAuthenticator(BitbucketAuthenticator authenticator) {
160+
return true;
161+
}
162+
158163
@Override
159164
protected ClassicHttpResponse executeMethod(HttpUriRequest httpMethod) throws IOException {
160165
String requestURI = httpMethod.getRequestUri();
@@ -204,6 +209,11 @@ private BitbucketClouldIntegrationClient(String payloadRootPath, String owner, S
204209
this.audit = new RequestAudit();
205210
}
206211

212+
@Override
213+
protected boolean isSupportedAuthenticator(BitbucketAuthenticator authenticator) {
214+
return true;
215+
}
216+
207217
@Override
208218
protected CloseableHttpClient getClient() {
209219
CloseableHttpClient client = mock(CloseableHttpClient.class);

src/test/java/com/cloudbees/jenkins/plugins/bitbucket/server/client/BitbucketServerAPIClientTest.java

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,14 @@
2727
import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketAuthenticator;
2828
import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketBuildStatus;
2929
import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketBuildStatus.Status;
30+
import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketException;
3031
import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketRepository;
3132
import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketTeam;
3233
import com.cloudbees.jenkins.plugins.bitbucket.client.BitbucketIntegrationClientFactory;
34+
import com.cloudbees.jenkins.plugins.bitbucket.impl.credentials.BitbucketAccessTokenAuthenticator;
35+
import com.cloudbees.jenkins.plugins.bitbucket.impl.credentials.BitbucketClientCertificateAuthenticator;
36+
import com.cloudbees.jenkins.plugins.bitbucket.impl.credentials.BitbucketOAuthAuthenticator;
37+
import com.cloudbees.jenkins.plugins.bitbucket.impl.credentials.BitbucketUsernamePasswordAuthenticator;
3338
import com.cloudbees.jenkins.plugins.bitbucket.server.BitbucketServerWebhookImplementation;
3439
import com.cloudbees.jenkins.plugins.bitbucket.test.util.BitbucketTestUtil;
3540
import hudson.ProxyConfiguration;
@@ -57,6 +62,7 @@
5762

5863
import static net.javacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson;
5964
import static org.assertj.core.api.Assertions.assertThat;
65+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
6066
import static org.hamcrest.MatcherAssert.assertThat;
6167
import static org.hamcrest.Matchers.hasItem;
6268
import static org.hamcrest.Matchers.is;
@@ -263,7 +269,7 @@ void test_no_proxy_configurations() throws Exception {
263269
j.jenkins.setProxy(proxyConfiguration);
264270

265271
AtomicReference<HttpClientBuilder> builderReference = new AtomicReference<>();
266-
try(BitbucketApi client = new BitbucketServerAPIClient(serverURL, "amuniz", "test-repos", mock(BitbucketAuthenticator.class), false, BitbucketServerWebhookImplementation.NATIVE) {
272+
try(BitbucketApi client = new BitbucketServerAPIClient(serverURL, "amuniz", "test-repos", mock(BitbucketUsernamePasswordAuthenticator.class), false, BitbucketServerWebhookImplementation.NATIVE) {
267273
@Override
268274
protected void setClientProxyParams(HttpClientBuilder builder) {
269275
builderReference.set(spy(builder));
@@ -275,4 +281,13 @@ protected void setClientProxyParams(HttpClientBuilder builder) {
275281
verify(builderReference.get(), never()).setProxy(any(HttpHost.class));
276282
}
277283

284+
@Test
285+
void test_supported_auth() throws Exception {
286+
try (BitbucketApi client = new BitbucketServerAPIClient("http://localhost:7990/bitbucket", "owner", "test-repos", mock(BitbucketUsernamePasswordAuthenticator.class), false)) {}
287+
try (BitbucketApi client = new BitbucketServerAPIClient("http://localhost:7990/bitbucket", "owner", "test-repos", mock(BitbucketClientCertificateAuthenticator.class), false)) {}
288+
try (BitbucketApi client = new BitbucketServerAPIClient("http://localhost:7990/bitbucket", "owner", "test-repos", mock(BitbucketAccessTokenAuthenticator.class), false)) {}
289+
290+
assertThatThrownBy(() -> new BitbucketServerAPIClient("http://localhost:7990/bitbucket", "owner", "test-repos", mock(BitbucketOAuthAuthenticator.class), false))
291+
.isInstanceOf(BitbucketException.class);
292+
}
278293
}

0 commit comments

Comments
 (0)