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;
}