diff --git a/docs/git.md b/docs/git.md index cddfcae4c7..0162a480b8 100644 --- a/docs/git.md +++ b/docs/git.md @@ -32,7 +32,7 @@ The following configuration properties are supported for each provider configura : User password required to access private repositories on the SCM server. `providers..token` -: *Required only for private Gitlab servers* +: *Required only for private Gitlab servers and supported for BitBucket* : Private API access token. `providers..platform` @@ -51,19 +51,23 @@ The following configuration properties are supported for each provider configura ### BitBucket -Create a `bitbucket` entry in the [SCM configuration file](#git-configuration) specifying your user name and app password, as shown below: +Create a `bitbucket` entry in the [SCM configuration file](#git-configuration) specifying your user name and either an API token or app password, as shown below: ```groovy providers { bitbucket { user = 'me' - password = 'my-secret' + token = 'my-api-token' } } ``` +:::{versionadded} 25.07.0-edge +API tokens are supported for BitBucket authentication. When both `token` and `password` are provided, the API token takes priority over the app password. +::: + :::{note} -App passwords are substitute passwords for a user account which you can use for scripts and integrating tools in order to avoid putting your real password into configuration files. Learn more at [this link](https://support.atlassian.com/bitbucket-cloud/docs/app-passwords/). +API tokens are substitute passwords for a user account which you can use for scripts and integrating tools in order to avoid putting your real password into configuration files. Learn more about [app passwords](https://support.atlassian.com/bitbucket-cloud/docs/app-passwords/) and [API tokens](https://support.atlassian.com/bitbucket-cloud/docs/using-api-tokens/). ::: ### BitBucket Server diff --git a/modules/nextflow/src/main/groovy/nextflow/scm/BitbucketRepositoryProvider.groovy b/modules/nextflow/src/main/groovy/nextflow/scm/BitbucketRepositoryProvider.groovy index df1038f747..2f900e5d01 100644 --- a/modules/nextflow/src/main/groovy/nextflow/scm/BitbucketRepositoryProvider.groovy +++ b/modules/nextflow/src/main/groovy/nextflow/scm/BitbucketRepositoryProvider.groovy @@ -19,6 +19,9 @@ package nextflow.scm import groovy.transform.Memoized import groovy.util.logging.Slf4j import nextflow.exception.AbortOperationException +import org.eclipse.jgit.transport.CredentialsProvider +import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider + /** * Implements a repository provider for the BitBucket service * @@ -41,19 +44,28 @@ final class BitbucketRepositoryProvider extends RepositoryProvider { @Override protected String[] getAuth() { - return config.token - ? new String[] { "Authorization", "Bearer ${config.token}" } - : super.getAuth() + if (!hasCredentials()) { + return null + } + + String secret = getToken() ?: getPassword() + String authString = "${getUser()}:${secret}".bytes.encodeBase64().toString() + + return new String[] { "Authorization", "Basic " + authString } } @Override boolean hasCredentials() { - return getToken() - ? true - : super.hasCredentials() + return getUser() && (getPassword() || getToken()) + } + + /** {@inheritDoc} */ + @Override + CredentialsProvider getGitCredentials() { + return new UsernamePasswordCredentialsProvider(getUser(), getToken() ?: getPassword()) } - /** {@inheritDoc} */ + /** {@inheritDoc} */ @Override String getName() { "BitBucket" } @@ -163,7 +175,12 @@ final class BitbucketRepositoryProvider extends RepositoryProvider { if( !result ) throw new IllegalStateException("Missing clone URL for: $project") - return result.href + /** + * The clone URL when an API token is used is of the form: https://{bitbucket_username}:{api_token}@bitbucket.org/{workspace}/{repository}.git + * @see Using API tokens + */ + String cloneUrl = result.href + return getToken() ? cloneUrl.replace('@', ":${getToken()}@") : cloneUrl } @Override diff --git a/modules/nextflow/src/test/groovy/nextflow/scm/BitbucketRepositoryProviderTest.groovy b/modules/nextflow/src/test/groovy/nextflow/scm/BitbucketRepositoryProviderTest.groovy index 225aef107a..9765ea2c44 100644 --- a/modules/nextflow/src/test/groovy/nextflow/scm/BitbucketRepositoryProviderTest.groovy +++ b/modules/nextflow/src/test/groovy/nextflow/scm/BitbucketRepositoryProviderTest.groovy @@ -36,7 +36,7 @@ class BitbucketRepositoryProviderTest extends Specification { when: def url = new BitbucketRepositoryProvider('pditommaso/tutorial',config).getCloneUrl() then: - url ==~ /https:\/\/\w+@bitbucket.org\/pditommaso\/tutorial.git/ + url ==~ /https:\/\/.+@bitbucket.org\/pditommaso\/tutorial.git/ } def testGetHomePage() { @@ -182,11 +182,13 @@ class BitbucketRepositoryProviderTest extends Specification { provider.hasCredentials() == EXPECTED where: - EXPECTED | CONFIG - false | new ProviderConfig('bitbucket') - false | new ProviderConfig('bitbucket').setUser('foo') - true | new ProviderConfig('bitbucket').setUser('foo').setPassword('bar') - true | new ProviderConfig('bitbucket').setToken('xyz') + EXPECTED | CONFIG + false | new ProviderConfig('bitbucket') + false | new ProviderConfig('bitbucket').setUser('foo') + false | new ProviderConfig('bitbucket').setToken('xyz') + true | new ProviderConfig('bitbucket').setUser('foo').setPassword('bar') + true | new ProviderConfig('bitbucket').setUser('foo').setToken('xyz') + true | new ProviderConfig('bitbucket').setUser('foo').setPassword('bar').setToken('xyz') } @Unroll @@ -198,9 +200,9 @@ class BitbucketRepositoryProviderTest extends Specification { provider.getAuth() == EXPECTED as String[] where: - EXPECTED | CONFIG - null | new ProviderConfig('bitbucket') - ["Authorization", "Bearer xyz"] | new ProviderConfig('bitbucket').setToken('xyz') - ["Authorization", "Basic ${"foo:bar".bytes.encodeBase64()}"] | new ProviderConfig('bitbucket').setUser('foo').setPassword('bar') + EXPECTED | CONFIG + null | new ProviderConfig('bitbucket') + ["Authorization", "Basic ${"foo:bar".bytes.encodeBase64()}"] | new ProviderConfig('bitbucket').setUser('foo').setPassword('bar') + ["Authorization", "Basic ${"foo@nextflow.io:xyz".bytes.encodeBase64()}"] | new ProviderConfig('bitbucket').setUser('foo@nextflow.io').setToken('xyz') } }