diff --git a/assembly/assembly-wsmaster-war/src/main/webapp/WEB-INF/classes/che/che.properties b/assembly/assembly-wsmaster-war/src/main/webapp/WEB-INF/classes/che/che.properties index 37bbd2d1e8..5bea92e00f 100644 --- a/assembly/assembly-wsmaster-war/src/main/webapp/WEB-INF/classes/che/che.properties +++ b/assembly/assembly-wsmaster-war/src/main/webapp/WEB-INF/classes/che/che.properties @@ -123,7 +123,7 @@ che.oauth2.azure.devops.clientsecret_filepath=NULL # Azure DevOps Service OAuth2 scopes. # Separate multiple values with comma, for example: scope,scope,scope # The full list of scopes: https://learn.microsoft.com/en-us/azure/devops/integrate/get-started/authentication/oauth?view=azure-devops#scopes -che.integration.azure.devops.application_scopes=vso.code_full +che.integration.azure.devops.application_scopes=vso.code_write # Azure DevOps Service OAuth2 authorization URI. che.oauth.azure.devops.authuri=https://app.vssps.visualstudio.com/oauth2/authorize @@ -132,10 +132,10 @@ che.oauth.azure.devops.authuri=https://app.vssps.visualstudio.com/oauth2/authori che.oauth.azure.devops.tokenuri=https://app.vssps.visualstudio.com/oauth2/token # Azure DevOps Service API server address. -che.integration.azure.devops.api_endpoint=https://vssps.dev.azure.com/ +che.integration.azure.devops.api_endpoint=https://vssps.dev.azure.com # Azure DevOps SCM API server address. -che.integration.azure.devops.scm.api_endpoint=https://dev.azure.com/ +che.integration.azure.devops.scm.api_endpoint=https://dev.azure.com # Azure DevOps Service redirect URIs. # Separate multiple values with comma, for example: URI,URI,URI. diff --git a/core/commons/che-core-commons-lang/src/main/java/org/eclipse/che/commons/lang/UrlUtils.java b/core/commons/che-core-commons-lang/src/main/java/org/eclipse/che/commons/lang/UrlUtils.java index 1a43c3d44a..147392c636 100644 --- a/core/commons/che-core-commons-lang/src/main/java/org/eclipse/che/commons/lang/UrlUtils.java +++ b/core/commons/che-core-commons-lang/src/main/java/org/eclipse/che/commons/lang/UrlUtils.java @@ -19,7 +19,10 @@ import java.net.MalformedURLException; import java.net.URL; import java.net.URLDecoder; import java.nio.charset.StandardCharsets; -import java.util.*; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; /** TODO replace this class with URLEncodedUtils */ public class UrlUtils { diff --git a/wsmaster/che-core-api-factory-azure-devops/src/main/java/org/eclipse/che/api/factory/server/azure/devops/AzureDevOpsApiClient.java b/wsmaster/che-core-api-factory-azure-devops/src/main/java/org/eclipse/che/api/factory/server/azure/devops/AzureDevOpsApiClient.java index 5a949bb900..07db562cfe 100644 --- a/wsmaster/che-core-api-factory-azure-devops/src/main/java/org/eclipse/che/api/factory/server/azure/devops/AzureDevOpsApiClient.java +++ b/wsmaster/che-core-api-factory-azure-devops/src/main/java/org/eclipse/che/api/factory/server/azure/devops/AzureDevOpsApiClient.java @@ -126,7 +126,11 @@ public class AzureDevOpsApiClient { }); } - /** Returns the scopes of the OAuth token. */ + /** + * Returns the scopes of the OAuth token. Consider using the REST API: + * + *

https://learn.microsoft.com/en-us/rest/api/azure/devops/tokens/pats/get?view=azure-devops-rest-7.0&tabs=HTTP + */ public String[] getTokenScopes(String authenticationToken) throws ScmItemNotFoundException, ScmCommunicationException, ScmBadRequestException { return scopes; diff --git a/wsmaster/che-core-api-factory-azure-devops/src/main/java/org/eclipse/che/api/factory/server/azure/devops/AzureDevOpsURLParser.java b/wsmaster/che-core-api-factory-azure-devops/src/main/java/org/eclipse/che/api/factory/server/azure/devops/AzureDevOpsURLParser.java index 3b1f15c737..d9c798667c 100644 --- a/wsmaster/che-core-api-factory-azure-devops/src/main/java/org/eclipse/che/api/factory/server/azure/devops/AzureDevOpsURLParser.java +++ b/wsmaster/che-core-api-factory-azure-devops/src/main/java/org/eclipse/che/api/factory/server/azure/devops/AzureDevOpsURLParser.java @@ -11,6 +11,7 @@ */ package org.eclipse.che.api.factory.server.azure.devops; +import static com.google.common.base.Strings.isNullOrEmpty; import static java.lang.String.format; import static java.util.regex.Pattern.compile; import static org.eclipse.che.commons.lang.StringUtils.trimEnd; @@ -81,6 +82,16 @@ public class AzureDevOpsURLParser { String branch = matcher.group("branch"); String tag = matcher.group("tag"); + // The url might have the following formats: + // - https://@///_git/ + // - https://@///_git/ + // For the first case we need to remove the `organization` from the url to distinguish it from + // `credentials` + String organizationCanIgnore = matcher.group("organizationCanIgnore"); + if (!isNullOrEmpty(organization) && organization.equals(organizationCanIgnore)) { + url = url.replace(organizationCanIgnore + "@", ""); + } + return new AzureDevOpsUrl() .withHostName(azureDevOpsScmApiEndpointHost) .withProject(project) @@ -88,6 +99,7 @@ public class AzureDevOpsURLParser { .withOrganization(organization) .withBranch(branch) .withTag(tag) - .withDevfileFilenames(devfileFilenamesProvider.getConfiguredDevfileFilenames()); + .withDevfileFilenames(devfileFilenamesProvider.getConfiguredDevfileFilenames()) + .withUrl(url); } } diff --git a/wsmaster/che-core-api-factory-azure-devops/src/main/java/org/eclipse/che/api/factory/server/azure/devops/AzureDevOpsUrl.java b/wsmaster/che-core-api-factory-azure-devops/src/main/java/org/eclipse/che/api/factory/server/azure/devops/AzureDevOpsUrl.java index b6dc526cd6..ac25cf9655 100644 --- a/wsmaster/che-core-api-factory-azure-devops/src/main/java/org/eclipse/che/api/factory/server/azure/devops/AzureDevOpsUrl.java +++ b/wsmaster/che-core-api-factory-azure-devops/src/main/java/org/eclipse/che/api/factory/server/azure/devops/AzureDevOpsUrl.java @@ -18,7 +18,7 @@ import java.util.List; import java.util.Optional; import java.util.StringJoiner; import java.util.stream.Collectors; -import org.eclipse.che.api.factory.server.urlfactory.RemoteFactoryUrl; +import org.eclipse.che.api.factory.server.urlfactory.DefaultFactoryUrl; /** * Representation of Azure DevOps URL, allowing to get details from it. @@ -28,7 +28,7 @@ import org.eclipse.che.api.factory.server.urlfactory.RemoteFactoryUrl; * * @author Anatolii Bazko */ -public class AzureDevOpsUrl implements RemoteFactoryUrl { +public class AzureDevOpsUrl extends DefaultFactoryUrl { private String hostName; diff --git a/wsmaster/che-core-api-factory-azure-devops/src/main/test/java/org/eclipse/che/api/factory/server/azure/devops/AzureDevOpsURLParserTest.java b/wsmaster/che-core-api-factory-azure-devops/src/main/test/java/org/eclipse/che/api/factory/server/azure/devops/AzureDevOpsURLParserTest.java index 04f1ddfc55..921334073d 100644 --- a/wsmaster/che-core-api-factory-azure-devops/src/main/test/java/org/eclipse/che/api/factory/server/azure/devops/AzureDevOpsURLParserTest.java +++ b/wsmaster/che-core-api-factory-azure-devops/src/main/test/java/org/eclipse/che/api/factory/server/azure/devops/AzureDevOpsURLParserTest.java @@ -18,6 +18,8 @@ import org.testng.annotations.DataProvider; import org.testng.annotations.Listeners; import org.testng.annotations.Test; +import java.util.Optional; + import static org.mockito.Mockito.mock; import static org.testng.Assert.assertEquals; @@ -97,4 +99,20 @@ public class AzureDevOpsURLParserTest { }, }; } + + @Test(dataProvider = "url") + public void testCredentials(String url, String organization, Optional credentials) { + AzureDevOpsUrl azureDevOpsUrl = azureDevOpsURLParser.parse(url); + + assertEquals(azureDevOpsUrl.getOrganization(), organization); + assertEquals(azureDevOpsUrl.getCredentials(), credentials); + } + + @DataProvider(name = "url") + public Object[][] url() { + return new Object[][]{ + {"https://MyOrg@dev.azure.com/MyOrg/MyProject/_git/MyRepo", "MyOrg", Optional.empty()}, + {"https://user:pwd@dev.azure.com/MyOrg/MyProject/_git/MyRepo", "MyOrg", Optional.of("user:pwd")}, + }; + } } diff --git a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/scm/AuthorizingFileContentProvider.java b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/scm/AuthorizingFileContentProvider.java index d7a8337919..3a50ea8a70 100644 --- a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/scm/AuthorizingFileContentProvider.java +++ b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/scm/AuthorizingFileContentProvider.java @@ -11,9 +11,8 @@ */ package org.eclipse.che.api.factory.server.scm; -import static org.eclipse.che.api.factory.server.scm.PersonalAccessTokenFetcher.OAUTH_2_PREFIX; - import static com.google.common.base.Strings.isNullOrEmpty; +import static org.eclipse.che.api.factory.server.scm.PersonalAccessTokenFetcher.OAUTH_2_PREFIX; import java.io.FileNotFoundException; import java.io.IOException; @@ -80,11 +79,13 @@ public class AuthorizingFileContentProvider // try to authenticate for the given URL String authorization; if (isNullOrEmpty(credentials)) { + PersonalAccessToken token = + personalAccessTokenManager.getAndStore(remoteFactoryUrl.getHostName()); authorization = formatAuthorization( - personalAccessTokenManager - .getAndStore(remoteFactoryUrl.getHostName()) - .getToken()); + token.getToken(), + token.getScmTokenName() == null + || !token.getScmTokenName().startsWith(OAUTH_2_PREFIX)); } else { authorization = getCredentialsAuthorization(credentials); } diff --git a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/scm/PersonalAccessToken.java b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/scm/PersonalAccessToken.java index 805574542a..0d9654703e 100644 --- a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/scm/PersonalAccessToken.java +++ b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/scm/PersonalAccessToken.java @@ -12,6 +12,7 @@ package org.eclipse.che.api.factory.server.scm; import com.google.common.base.Objects; +import org.eclipse.che.commons.annotation.Nullable; import org.eclipse.che.commons.env.EnvironmentContext; /** @@ -23,7 +24,7 @@ public class PersonalAccessToken { private final String scmProviderUrl; private final String scmUserName; /** Organization that user belongs to. Can be null if user is not a member of any organization. */ - private final String scmOrganization; + @Nullable private final String scmOrganization; private final String scmUserId; private final String scmTokenName; @@ -101,6 +102,7 @@ public class PersonalAccessToken { return cheUserId; } + @Nullable public String getScmOrganization() { return scmOrganization; }