diff --git a/wsmaster/che-core-api-factory-bitbucket/src/main/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketPersonalAccessTokenFetcher.java b/wsmaster/che-core-api-factory-bitbucket/src/main/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketPersonalAccessTokenFetcher.java index e9e6835fc7..6e8bf10e7e 100644 --- a/wsmaster/che-core-api-factory-bitbucket/src/main/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketPersonalAccessTokenFetcher.java +++ b/wsmaster/che-core-api-factory-bitbucket/src/main/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketPersonalAccessTokenFetcher.java @@ -13,6 +13,7 @@ import com.google.common.collect.Sets; import java.util.Optional; +import java.util.Set; import javax.inject.Inject; import javax.inject.Named; import org.eclipse.che.api.auth.shared.dto.OAuthToken; @@ -52,7 +53,13 @@ public class BitbucketPersonalAccessTokenFetcher implements PersonalAccessTokenF private static final String OAUTH_PROVIDER_NAME = "bitbucket"; /** OAuth scope required to make integration with Bitbucket work. */ - public static final String DEFAULT_TOKEN_SCOPE = "repository:write"; + public static final String DEFAULT_REPOSITORY_WRITE_TOKEN_SCOPE = "repository:write"; + + public static final String DEFAULT_PULLREQUEST_WRITE_TOKEN_SCOPE = "pullrequest:write"; + + public static final String DEFAULT_ACCOUNT_READ_TOKEN_SCOPE = "account"; + + public static final String DEFAULT_ACCOUNT_WRITE_TOKEN_SCOPE = "account:write"; @Inject public BitbucketPersonalAccessTokenFetcher( @@ -101,7 +108,9 @@ public PersonalAccessToken fetchPersonalAccessToken(Subject cheSubject, String s } else if (!valid.get().first) { throw new ScmCommunicationException( "Current token doesn't have the necessary privileges. Please make sure Che app scopes are correct and containing at least: " - + DEFAULT_TOKEN_SCOPE); + + DEFAULT_REPOSITORY_WRITE_TOKEN_SCOPE + + " and " + + DEFAULT_ACCOUNT_READ_TOKEN_SCOPE); } return new PersonalAccessToken( scmServerUrl, @@ -136,7 +145,7 @@ public Optional isValid(PersonalAccessToken personalAccessToken) { try { String[] scopes = bitbucketApiClient.getTokenScopes(personalAccessToken.getToken()).second; - return Optional.of(Sets.newHashSet(scopes).contains(DEFAULT_TOKEN_SCOPE)); + return Optional.of(isValidScope(Sets.newHashSet(scopes))); } catch (ScmItemNotFoundException | ScmCommunicationException | ScmBadRequestException e) { return Optional.of(Boolean.FALSE); } @@ -153,9 +162,7 @@ public Optional> isValid(PersonalAccessTokenParams params) Pair pair = bitbucketApiClient.getTokenScopes(params.getToken()); return Optional.of( Pair.of( - Sets.newHashSet(pair.second).contains(DEFAULT_TOKEN_SCOPE) - ? Boolean.TRUE - : Boolean.FALSE, + isValidScope(Sets.newHashSet(pair.second)) ? Boolean.TRUE : Boolean.FALSE, pair.first)); } catch (ScmItemNotFoundException | ScmCommunicationException | ScmBadRequestException e) { return Optional.empty(); @@ -168,4 +175,15 @@ private String getLocalAuthenticateUrl() { + OAUTH_PROVIDER_NAME + "&scope=repository&request_method=POST&signature_method=rsa"; } + + /** + * Checks if the given scopes are valid for Bitbucket. Note: that pullrequest:write is a wider + * scope than repository:write, and account:write is a wider scope than account. + */ + private boolean isValidScope(Set scopes) { + return (scopes.contains(DEFAULT_REPOSITORY_WRITE_TOKEN_SCOPE) + || scopes.contains(DEFAULT_PULLREQUEST_WRITE_TOKEN_SCOPE)) + && (scopes.contains(DEFAULT_ACCOUNT_READ_TOKEN_SCOPE) + || scopes.contains(DEFAULT_ACCOUNT_WRITE_TOKEN_SCOPE)); + } } diff --git a/wsmaster/che-core-api-factory-bitbucket/src/test/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketApiClientTest.java b/wsmaster/che-core-api-factory-bitbucket/src/test/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketApiClientTest.java index 41ec29586c..ed1b205735 100644 --- a/wsmaster/che-core-api-factory-bitbucket/src/test/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketApiClientTest.java +++ b/wsmaster/che-core-api-factory-bitbucket/src/test/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketApiClientTest.java @@ -87,11 +87,12 @@ public void testGetTokenScopes() throws Exception { aResponse() .withHeader("Content-Type", "application/json; charset=utf-8") .withHeader( - BitbucketApiClient.BITBUCKET_OAUTH_SCOPES_HEADER, "repository:write") + BitbucketApiClient.BITBUCKET_OAUTH_SCOPES_HEADER, + "repository:write,account") .withBodyFile("bitbucket/rest/user/response.json"))); String[] scopes = client.getTokenScopes("token1").second; - String[] expectedScopes = {"repository:write"}; + String[] expectedScopes = {"repository:write", "account"}; assertNotNull(scopes, "Bitbucket API should have returned a non-null scope array"); assertEqualsNoOrder( scopes, expectedScopes, "Returned scope array does not match expected values"); diff --git a/wsmaster/che-core-api-factory-bitbucket/src/test/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketPersonalAccessTokenFetcherTest.java b/wsmaster/che-core-api-factory-bitbucket/src/test/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketPersonalAccessTokenFetcherTest.java index 49d06e00fa..d5ffdd471c 100644 --- a/wsmaster/che-core-api-factory-bitbucket/src/test/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketPersonalAccessTokenFetcherTest.java +++ b/wsmaster/che-core-api-factory-bitbucket/src/test/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketPersonalAccessTokenFetcherTest.java @@ -96,7 +96,7 @@ public void shouldNotValidateSCMServerWithTrailingSlash() throws Exception { @Test( expectedExceptions = ScmCommunicationException.class, expectedExceptionsMessageRegExp = - "Current token doesn't have the necessary privileges. Please make sure Che app scopes are correct and containing at least: repository:write") + "Current token doesn't have the necessary privileges. Please make sure Che app scopes are correct and containing at least: repository:write and account") public void shouldThrowExceptionOnInsufficientTokenScopes() throws Exception { Subject subject = new SubjectImpl("Username", "id1", "token", false); OAuthToken oAuthToken = newDto(OAuthToken.class).withToken(bitbucketOauthToken).withScope(""); @@ -140,7 +140,8 @@ public void shouldReturnToken() throws Exception { aResponse() .withHeader("Content-Type", "application/json; charset=utf-8") .withHeader( - BitbucketApiClient.BITBUCKET_OAUTH_SCOPES_HEADER, "repository:write") + BitbucketApiClient.BITBUCKET_OAUTH_SCOPES_HEADER, + "repository:write,account") .withBodyFile("bitbucket/rest/user/response.json"))); PersonalAccessToken token = @@ -158,7 +159,8 @@ public void shouldValidatePersonalToken() throws Exception { aResponse() .withHeader("Content-Type", "application/json; charset=utf-8") .withHeader( - BitbucketApiClient.BITBUCKET_OAUTH_SCOPES_HEADER, "repository:write") + BitbucketApiClient.BITBUCKET_OAUTH_SCOPES_HEADER, + "repository:write,account") .withBodyFile("bitbucket/rest/user/response.json"))); PersonalAccessTokenParams params = @@ -179,7 +181,8 @@ public void shouldValidateOauthToken() throws Exception { aResponse() .withHeader("Content-Type", "application/json; charset=utf-8") .withHeader( - BitbucketApiClient.BITBUCKET_OAUTH_SCOPES_HEADER, "repository:write") + BitbucketApiClient.BITBUCKET_OAUTH_SCOPES_HEADER, + "repository:write,account") .withBodyFile("bitbucket/rest/user/response.json"))); PersonalAccessTokenParams params =