Skip to content

Commit 32cae45

Browse files
kurddtStrangerxxxjetersen
authored
add OAuth authentication (#262)
Co-authored-by: strangerx <[email protected]> Co-authored-by: Joseph Petersen <[email protected]>
1 parent 25932e4 commit 32cae45

File tree

13 files changed

+283
-3
lines changed

13 files changed

+283
-3
lines changed

docs/USER_GUIDE.adoc

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,8 +96,23 @@ two credentials to configure:
9696

9797
. *Scan Credentials*: credentials used to access Bitbucket API in order to discover repositories, branches and pull requests.
9898
If not set then anonymous access is used, so only public repositories, branches and pull requests are discovered and managed. Note that the
99-
Webhooks auto-register feature requires scan credentials to be set. Only HTTP credentials are accepted in this field.
100-
. *Checkout Credentials*: credentials used to checkout sources once the repository, branch or pull request is discovered. HTTP and SSH credentials
99+
Webhooks auto-register feature requires scan credentials to be set. Only HTTP or OAuth credentials are accepted in this field.
100+
. *Checkout Credentials*: credentials used to checkout sources once the repository, branch or pull request is discovered. HTTP, SSH and OAuthcredentials
101101
are allowed. If not set then _Scan Credentials_ are used.
102102

103103
image::images/screenshot-3.png[scaledwidth=90%]
104+
105+
=== OAuth credentials
106+
107+
Bitbucket plugin can make use of OAuth credentials instead of the standard username/password.
108+
109+
First create a new OAuth consumer as instructed in https://confluence.atlassian.com/bitbucket/oauth-on-bitbucket-cloud-238027431.html[Bitbucket OAuth Documentation].
110+
Don't forget to check _This is a private consumer_ and at least allow read access to the repositories and Pull requests. If you want the Bitbucket to install the Webhooks also allow the read and write access of the Webhooks
111+
112+
image::images/screenshot-10.png[scaledwidth=90%]
113+
114+
Then create new _Username with password credentials_, enter the Bitbucket OAuth consumer key in _Username_ field and the Bitbucket OAuth consumer secret in _Password_ field
115+
116+
image::images/screenshot-11.png[scaledwidth=90%]
117+
118+
image::images/screenshot-12.png[scaledwidth=90%]

docs/images/screenshot-10.png

93.3 KB
Loading

docs/images/screenshot-11.png

13.2 KB
Loading

docs/images/screenshot-12.png

44.6 KB
Loading

pom.xml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,17 @@
173173
<groupId>org.jenkins-ci.plugins</groupId>
174174
<artifactId>authentication-tokens</artifactId>
175175
<version>1.3</version>
176+
</dependency>
177+
<dependency>
178+
<groupId>org.scribe</groupId>
179+
<artifactId>scribe</artifactId>
180+
<version>1.3.3</version>
181+
<exclusions>
182+
<exclusion>
183+
<groupId>commons-codec</groupId>
184+
<artifactId>commons-codec</artifactId>
185+
</exclusion>
186+
</exclusions>
176187
</dependency>
177188
</dependencies>
178189

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,17 @@ public void configureRequest(HttpRequest request) {
107107
// override to configure HttpRequest
108108
}
109109

110+
/**
111+
* Return the user to be used in the clone Uri. Override this if your
112+
* authentication method needs to set the user in the repository Uri
113+
*
114+
* @return user name to use in the repository Uri
115+
*/
116+
public String getUserUri() {
117+
// override to return a user
118+
return "";
119+
}
120+
110121
/**
111122
* Generates context that sub-classes can use to determine if they would be able to authenticate against the
112123
* provided server.
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package com.cloudbees.jenkins.plugins.bitbucket.api.credentials;
2+
3+
import org.scribe.builder.api.DefaultApi20;
4+
import org.scribe.extractors.AccessTokenExtractor;
5+
import org.scribe.extractors.JsonTokenExtractor;
6+
import org.scribe.model.OAuthConfig;
7+
import org.scribe.model.Verb;
8+
import org.scribe.oauth.OAuthService;
9+
10+
public class BitbucketOAuth extends DefaultApi20 {
11+
public static final String OAUTH_ENDPOINT = "https://bitbucket.org/site/oauth2/";
12+
13+
@Override
14+
public String getAccessTokenEndpoint() {
15+
return OAUTH_ENDPOINT + "access_token";
16+
}
17+
18+
@Override
19+
public String getAuthorizationUrl(OAuthConfig config) {
20+
return OAUTH_ENDPOINT + "authorize";
21+
}
22+
23+
@Override
24+
public Verb getAccessTokenVerb() {
25+
return Verb.POST;
26+
}
27+
28+
@Override
29+
public AccessTokenExtractor getAccessTokenExtractor() {
30+
return new JsonTokenExtractor();
31+
}
32+
33+
@Override
34+
public OAuthService createService(OAuthConfig config) {
35+
return new BitbucketOAuthService(this, config);
36+
}
37+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package com.cloudbees.jenkins.plugins.bitbucket.api.credentials;
2+
3+
import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketAuthenticator;
4+
import com.cloudbees.plugins.credentials.common.StandardUsernamePasswordCredentials;
5+
import hudson.util.Secret;
6+
import org.apache.http.HttpRequest;
7+
import org.scribe.model.OAuthConfig;
8+
import org.scribe.model.OAuthConstants;
9+
import org.scribe.model.Token;
10+
11+
public class BitbucketOAuthAuthenticator extends BitbucketAuthenticator {
12+
13+
private Token token;
14+
15+
/**
16+
* Constructor.
17+
*
18+
* @param credentials the key/pass that will be used
19+
*/
20+
public BitbucketOAuthAuthenticator(StandardUsernamePasswordCredentials credentials) {
21+
super(credentials);
22+
23+
OAuthConfig config = new OAuthConfig(credentials.getUsername(), Secret.toString(credentials.getPassword()));
24+
25+
BitbucketOAuthService OAuthService = (BitbucketOAuthService) new BitbucketOAuth().createService(config);
26+
27+
token = OAuthService.getAccessToken(OAuthConstants.EMPTY_TOKEN, null);
28+
}
29+
30+
/**
31+
* Set up request with token in header
32+
*/
33+
@Override
34+
public void configureRequest(HttpRequest request) {
35+
request.addHeader(OAuthConstants.HEADER, "Bearer " + this.token.getToken());
36+
}
37+
38+
@Override
39+
public String getUserUri() {
40+
return "x-token-auth:{" + token.getToken() + "}";
41+
}
42+
43+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package com.cloudbees.jenkins.plugins.bitbucket.api.credentials;
2+
3+
import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketAuthenticator;
4+
import com.cloudbees.plugins.credentials.CredentialsMatcher;
5+
import com.cloudbees.plugins.credentials.common.StandardUsernamePasswordCredentials;
6+
import edu.umd.cs.findbugs.annotations.NonNull;
7+
import hudson.Extension;
8+
import jenkins.authentication.tokens.api.AuthenticationTokenContext;
9+
import jenkins.authentication.tokens.api.AuthenticationTokenSource;
10+
11+
12+
/**
13+
* Source for OAuth authenticators.
14+
*/
15+
@Extension
16+
public class BitbucketOAuthAuthenticatorSource extends AuthenticationTokenSource<BitbucketOAuthAuthenticator, StandardUsernamePasswordCredentials> {
17+
18+
/**
19+
* Constructor.
20+
*/
21+
public BitbucketOAuthAuthenticatorSource() {
22+
super(BitbucketOAuthAuthenticator.class, StandardUsernamePasswordCredentials.class);
23+
}
24+
25+
/**
26+
* Converts username/password credentials to an authenticator.
27+
*
28+
* @param standardUsernamePasswordCredentials the username/password combo
29+
* @return an authenticator that will use them.
30+
*/
31+
@NonNull
32+
@Override
33+
public BitbucketOAuthAuthenticator convert(
34+
@NonNull StandardUsernamePasswordCredentials standardUsernamePasswordCredentials) {
35+
return new BitbucketOAuthAuthenticator(standardUsernamePasswordCredentials);
36+
}
37+
38+
/**
39+
* Whether this source works in the given context. For client certs, only HTTPS
40+
* BitbucketServer instances make sense
41+
*
42+
* @param ctx the context
43+
* @return whether or not this can authenticate given the context
44+
*/
45+
@Override
46+
public boolean isFit(AuthenticationTokenContext ctx) {
47+
return ctx.mustHave(BitbucketAuthenticator.SCHEME, "https") && ctx.mustHave(
48+
BitbucketAuthenticator.BITBUCKET_INSTANCE_TYPE, BitbucketAuthenticator.BITBUCKET_INSTANCE_TYPE_CLOUD);
49+
}
50+
51+
/**
52+
* {@inheritDoc}
53+
*/
54+
@Override
55+
public CredentialsMatcher matcher() {
56+
return new BitbucketOAuthCredentialMatcher();
57+
}
58+
59+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package com.cloudbees.jenkins.plugins.bitbucket.api.credentials;
2+
3+
import com.cloudbees.plugins.credentials.Credentials;
4+
import com.cloudbees.plugins.credentials.CredentialsMatcher;
5+
import com.cloudbees.plugins.credentials.common.UsernamePasswordCredentials;
6+
import hudson.util.Secret;
7+
8+
public class BitbucketOAuthCredentialMatcher implements CredentialsMatcher, CredentialsMatcher.CQL {
9+
private static int keyLenght = 18;
10+
private static int secretLenght = 32;
11+
12+
private static final long serialVersionUID = 6458784517693211197L;
13+
14+
/**
15+
* {@inheritDoc}
16+
*/
17+
@Override
18+
public boolean matches(Credentials item) {
19+
if (!(item instanceof UsernamePasswordCredentials))
20+
return false;
21+
22+
UsernamePasswordCredentials usernamePasswordCredential = ((UsernamePasswordCredentials) item);
23+
String username = usernamePasswordCredential.getUsername();
24+
boolean isEMail = username.contains(".") && username.contains("@");
25+
boolean validSecretLenght = Secret.toString(usernamePasswordCredential.getPassword()).length() == secretLenght;
26+
boolean validKeyLenght = username.length() == keyLenght;
27+
28+
return !isEMail && validKeyLenght && validSecretLenght;
29+
}
30+
31+
/**
32+
* {@inheritDoc}
33+
*/
34+
@Override
35+
public String describe() {
36+
return String.format(
37+
"(username.lenght == %d && password.lenght == %d && !(username CONTAINS \".\" && username CONTAINS \"@\")",
38+
keyLenght, secretLenght);
39+
}
40+
41+
42+
}

0 commit comments

Comments
 (0)