Skip to content

Commit 4c44404

Browse files
authored
Support the deprecated Azure DevOps oauth apps (eclipse-che#985)
1 parent f358b50 commit 4c44404

3 files changed

Lines changed: 85 additions & 11 deletions

File tree

wsmaster/che-core-api-auth-azure-devops/src/main/java/org/eclipse/che/security/oauth/AzureDevOpsOAuthAuthenticator.java

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import java.net.http.HttpClient;
3535
import java.net.http.HttpRequest;
3636
import java.net.http.HttpResponse;
37+
import java.util.Collections;
3738
import java.util.List;
3839
import javax.inject.Singleton;
3940
import org.eclipse.che.api.auth.shared.dto.OAuthToken;
@@ -55,6 +56,7 @@ public class AzureDevOpsOAuthAuthenticator extends OAuthAuthenticator {
5556
private final String PROVIDER_NAME = "azure-devops";
5657
private final String clientId;
5758
private final String clientSecret;
59+
private final boolean isDevOpsOauth;
5860

5961
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
6062

@@ -66,7 +68,8 @@ public AzureDevOpsOAuthAuthenticator(
6668
String azureDevOpsScmApiEndpoint,
6769
String authUri,
6870
String tokenUri,
69-
String[] redirectUris)
71+
String[] redirectUris,
72+
boolean isDevOpsOauth)
7073
throws IOException {
7174
this.cheApiEndpoint = cheApiEndpoint;
7275
this.clientId = clientId;
@@ -78,6 +81,7 @@ public AzureDevOpsOAuthAuthenticator(
7881
trimEnd(azureDevOpsApiEndpoint, '/'), API_VERSION);
7982
this.tokenUri = tokenUri;
8083
this.redirectUris = redirectUris;
84+
this.isDevOpsOauth = isDevOpsOauth;
8185
configure(
8286
clientId, clientSecret, redirectUris, authUri, tokenUri, new MemoryDataStoreFactory());
8387
}
@@ -90,8 +94,15 @@ public AzureDevOpsOAuthAuthenticator(
9094
*/
9195
@Override
9296
public String getAuthenticateUrl(URL requestUrl, List<String> scopes) {
97+
if (isDevOpsOauth) {
98+
scopes = Collections.singletonList("vso.code_write");
99+
}
93100
AuthorizationCodeRequestUrl url = flow.newAuthorizationUrl().setScopes(scopes);
94-
url.set("response_type", "code");
101+
if (isDevOpsOauth) {
102+
url.set("response_type", "Assertion");
103+
} else {
104+
url.set("response_type", "code");
105+
}
95106
url.set("redirect_uri", format("%s/oauth/callback", cheApiEndpoint));
96107
url.setState(prepareState(requestUrl));
97108
return url.build();
@@ -204,10 +215,19 @@ protected AuthorizationCodeTokenRequest getAuthorizationCodeTokenRequest(
204215
URL requestUrl, List<String> scopes, String code) {
205216
AuthorizationCodeTokenRequest request =
206217
super.getAuthorizationCodeTokenRequest(requestUrl, scopes, code);
207-
request.set("client_id", clientId);
208-
request.set("grant_type", "authorization_code");
209-
request.set("client_secret", URLEncoder.encode(clientSecret));
210-
request.setResponseClass(TokenResponse.class);
218+
if (isDevOpsOauth) {
219+
request.set("grant_type", "urn:ietf:params:oauth:grant-type:jwt-bearer");
220+
request.set("assertion", code);
221+
request.set("client_assertion", clientSecret);
222+
request.set(
223+
"client_assertion_type", "urn:ietf:params:oauth:client-assertion-type:jwt-bearer");
224+
request.setResponseClass(AzureDevOpsTokenResponse.class);
225+
} else {
226+
request.set("client_id", clientId);
227+
request.set("grant_type", "authorization_code");
228+
request.set("client_secret", URLEncoder.encode(clientSecret));
229+
request.setResponseClass(TokenResponse.class);
230+
}
211231
return request;
212232
}
213233
}

wsmaster/che-core-api-auth-azure-devops/src/main/java/org/eclipse/che/security/oauth/AzureDevOpsOAuthAuthenticatorProvider.java

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -82,19 +82,34 @@ private OAuthAuthenticator getOAuthAuthenticator(
8282
if (!isNullOrEmpty(clientIdPath)
8383
&& !isNullOrEmpty(clientSecretPath)
8484
&& !isNullOrEmpty(tenantIdPath)) {
85-
final String tenantId = Files.readString(Path.of(tenantIdPath)).trim();
85+
// This flag is needed to support the deprecated Azure DevOps oauth apps.
86+
// TODO remove the related logic when the deprecated Azure DevOps oauth app is no longer
87+
// available, see
88+
// https://learn.microsoft.com/en-us/azure/devops/integrate/get-started/authentication/oauth?view=azure-devops#azure-devops-oauth-deprecated
89+
boolean isDevOpsOauth = false;
90+
String tenantId = null;
91+
try {
92+
tenantId = Files.readString(Path.of(tenantIdPath)).trim();
93+
} catch (IOException e) {
94+
isDevOpsOauth = true;
95+
}
8696
final String clientId = Files.readString(Path.of(clientIdPath)).trim();
8797
final String clientSecret = Files.readString(Path.of(clientSecretPath)).trim();
88-
if (!isNullOrEmpty(clientId) && !isNullOrEmpty(clientSecret) && !isNullOrEmpty(tenantId)) {
98+
if (!isNullOrEmpty(clientId) && !isNullOrEmpty(clientSecret)) {
8999
return new AzureDevOpsOAuthAuthenticator(
90100
cheApiEndpoint,
91101
clientId,
92102
clientSecret,
93103
azureDevOpsApiEndpoint,
94104
azureDevOpsScmApiEndpoint,
95-
String.format(authUriTemplate, tenantId),
96-
String.format(tokenUriTemplate, tenantId),
97-
redirectUris);
105+
isDevOpsOauth
106+
? "https://app.vssps.visualstudio.com/oauth2/authorize"
107+
: String.format(authUriTemplate, tenantId),
108+
isDevOpsOauth
109+
? "https://app.vssps.visualstudio.com/oauth2/authorize"
110+
: String.format(tokenUriTemplate, tenantId),
111+
redirectUris,
112+
isDevOpsOauth);
98113
}
99114
}
100115
return new NoopOAuthAuthenticator();
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*
2+
* Copyright (c) 2012-2026 Red Hat, Inc.
3+
* This program and the accompanying materials are made
4+
* available under the terms of the Eclipse Public License 2.0
5+
* which is available at https://www.eclipse.org/legal/epl-2.0/
6+
*
7+
* SPDX-License-Identifier: EPL-2.0
8+
*
9+
* Contributors:
10+
* Red Hat, Inc. - initial API and implementation
11+
*/
12+
package org.eclipse.che.security.oauth;
13+
14+
import com.google.api.client.auth.oauth2.TokenResponse;
15+
import com.google.api.client.json.JsonString;
16+
import com.google.api.client.util.Key;
17+
18+
/**
19+
* The only difference between from {@link TokenResponse} is that {@link #expiresInSeconds} field is
20+
* represented in a {@link String} format.
21+
*
22+
* <p>https://learn.microsoft.com/en-us/azure/devops/integrate/get-started/authentication/oauth?view=azure-devops#response---authorize-app
23+
*
24+
* @author Anatolii Bazko
25+
*/
26+
public class AzureDevOpsTokenResponse extends TokenResponse {
27+
@JsonString
28+
@Key("expires_in")
29+
private Long expiresInSeconds;
30+
31+
public Long getExpiresInSeconds() {
32+
return expiresInSeconds;
33+
}
34+
35+
public AzureDevOpsTokenResponse setExpiresInSeconds(Long expiresInSeconds) {
36+
this.expiresInSeconds = expiresInSeconds;
37+
return this;
38+
}
39+
}

0 commit comments

Comments
 (0)