Skip to content

Commit a588c5e

Browse files
authored
Apply Microsoft Azure OIDC Authentication flow (#968)
1 parent 82ad970 commit a588c5e

4 files changed

Lines changed: 37 additions & 62 deletions

File tree

assembly/assembly-wsmaster-war/src/main/webapp/WEB-INF/classes/che/che.properties

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -110,22 +110,27 @@ che.auth.access_denied_error_page=/error-oauth
110110
che.auth.reserved_user_names=
111111

112112
# Configuration of the Azure DevOps Service OAuth2 client. Used to obtain personal access tokens.
113-
# Location of the file with Azure DevOps Service Application ID.
113+
# Location of the file with Microsoft OIDC Directory (tenant) ID.
114+
che.oauth2.azure.devops.tenantid_filepath=NULL
115+
116+
# Location of the file with Microsoft OIDC Application (client) ID.
114117
che.oauth2.azure.devops.clientid_filepath=NULL
115118

116-
# Location of the file with Azure DevOps Service Client Secret.
119+
# Location of the file with Microsoft OIDC Application (client) secret.
117120
che.oauth2.azure.devops.clientsecret_filepath=NULL
118121

119122
# Azure DevOps Service OAuth2 scopes.
120123
# Separate multiple values with comma, for example: scope,scope,scope
121124
# The full list of scopes: https://learn.microsoft.com/en-us/azure/devops/integrate/get-started/authentication/oauth?view=azure-devops#scopes
122-
che.integration.azure.devops.application_scopes=vso.code_write
125+
# Entra ID v2.0 requires Azure DevOps App ID URI in the scope, which is 499b84ac-1321-427f-aa17-267ca6975798
126+
# see https://learn.microsoft.com/en-us/answers/questions/5807316/invalid-scope-error-on-oidc-token-request
127+
che.integration.azure.devops.application_scopes=499b84ac-1321-427f-aa17-267ca6975798/vso.code_write
123128

124-
# Azure DevOps Service OAuth2 authorization URI.
125-
che.oauth.azure.devops.authuri=https://app.vssps.visualstudio.com/oauth2/authorize
129+
# Azure DevOps Service OAuth2 authorization URI template. Tenant-id must be injected.
130+
che.oauth.azure.devops.authuri.template=https://login.microsoftonline.com/%s/oauth2/v2.0/authorize
126131

127-
# Azure DevOps OAuth Service token URI.
128-
che.oauth.azure.devops.tokenuri=https://app.vssps.visualstudio.com/oauth2/token
132+
# Azure DevOps OAuth Service token URI template. Tenant-id must be injected.
133+
che.oauth.azure.devops.tokenuri.template=https://login.microsoftonline.com/%s/oauth2/v2.0/token
129134

130135
# Azure DevOps Service API server address.
131136
che.integration.azure.devops.api_endpoint=https://vssps.dev.azure.com

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

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2012-2024 Red Hat, Inc.
2+
* Copyright (c) 2012-2026 Red Hat, Inc.
33
* This program and the accompanying materials are made
44
* available under the terms of the Eclipse Public License 2.0
55
* which is available at https://www.eclipse.org/legal/epl-2.0/
@@ -22,13 +22,15 @@
2222
import com.google.api.client.auth.oauth2.AuthorizationCodeRequestUrl;
2323
import com.google.api.client.auth.oauth2.AuthorizationCodeTokenRequest;
2424
import com.google.api.client.auth.oauth2.Credential;
25+
import com.google.api.client.auth.oauth2.TokenResponse;
2526
import com.google.api.client.util.store.MemoryDataStoreFactory;
2627
import com.google.common.io.CharStreams;
2728
import java.io.IOException;
2829
import java.io.InputStream;
2930
import java.io.InputStreamReader;
3031
import java.net.URI;
3132
import java.net.URL;
33+
import java.net.URLEncoder;
3234
import java.net.http.HttpClient;
3335
import java.net.http.HttpRequest;
3436
import java.net.http.HttpResponse;
@@ -51,6 +53,7 @@ public class AzureDevOpsOAuthAuthenticator extends OAuthAuthenticator {
5153
private final String[] redirectUris;
5254
private final String API_VERSION = "7.0";
5355
private final String PROVIDER_NAME = "azure-devops";
56+
private final String clientId;
5457
private final String clientSecret;
5558

5659
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
@@ -66,6 +69,7 @@ public AzureDevOpsOAuthAuthenticator(
6669
String[] redirectUris)
6770
throws IOException {
6871
this.cheApiEndpoint = cheApiEndpoint;
72+
this.clientId = clientId;
6973
this.clientSecret = clientSecret;
7074
this.azureDevOpsScmApiEndpoint = trimEnd(azureDevOpsScmApiEndpoint, '/');
7175
this.azureDevOpsUserProfileDataApiUrl =
@@ -87,7 +91,7 @@ public AzureDevOpsOAuthAuthenticator(
8791
@Override
8892
public String getAuthenticateUrl(URL requestUrl, List<String> scopes) {
8993
AuthorizationCodeRequestUrl url = flow.newAuthorizationUrl().setScopes(scopes);
90-
url.set("response_type", "Assertion");
94+
url.set("response_type", "code");
9195
url.set("redirect_uri", format("%s/oauth/callback", cheApiEndpoint));
9296
url.setState(prepareState(requestUrl));
9397
return url.build();
@@ -200,11 +204,10 @@ protected AuthorizationCodeTokenRequest getAuthorizationCodeTokenRequest(
200204
URL requestUrl, List<String> scopes, String code) {
201205
AuthorizationCodeTokenRequest request =
202206
super.getAuthorizationCodeTokenRequest(requestUrl, scopes, code);
203-
request.set("grant_type", "urn:ietf:params:oauth:grant-type:jwt-bearer");
204-
request.set("assertion", code);
205-
request.set("client_assertion", clientSecret);
206-
request.set("client_assertion_type", "urn:ietf:params:oauth:client-assertion-type:jwt-bearer");
207-
request.setResponseClass(AzureDevOpsTokenResponse.class);
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);
208211
return request;
209212
}
210213
}

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

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2012-2023 Red Hat, Inc.
2+
* Copyright (c) 2012-2026 Red Hat, Inc.
33
* This program and the accompanying materials are made
44
* available under the terms of the Eclipse Public License 2.0
55
* which is available at https://www.eclipse.org/legal/epl-2.0/
@@ -38,18 +38,20 @@ public class AzureDevOpsOAuthAuthenticatorProvider implements Provider<OAuthAuth
3838
@Inject
3939
public AzureDevOpsOAuthAuthenticatorProvider(
4040
@Named("che.api") String cheApiEndpoint,
41+
@Nullable @Named("che.oauth2.azure.devops.tenantid_filepath") String azureDevOpsTennantIdPath,
4142
@Nullable @Named("che.oauth2.azure.devops.clientid_filepath") String azureDevOpsClientIdPath,
4243
@Nullable @Named("che.oauth2.azure.devops.clientsecret_filepath")
4344
String azureDevOpsClientSecretPath,
4445
@Named("che.integration.azure.devops.api_endpoint") String azureDevOpsApiEndpoint,
4546
@Named("che.integration.azure.devops.scm.api_endpoint") String azureDevOpsScmApiEndpoint,
46-
@Named("che.oauth.azure.devops.authuri") String authUri,
47-
@Named("che.oauth.azure.devops.tokenuri") String tokenUri,
47+
@Named("che.oauth.azure.devops.authuri.template") String authUri,
48+
@Named("che.oauth.azure.devops.tokenuri.template") String tokenUri,
4849
@Named("che.oauth.azure.devops.redirecturis") String[] redirectUris)
4950
throws IOException {
5051
authenticator =
5152
getOAuthAuthenticator(
5253
cheApiEndpoint,
54+
azureDevOpsTennantIdPath,
5355
azureDevOpsClientIdPath,
5456
azureDevOpsClientSecretPath,
5557
azureDevOpsApiEndpoint,
@@ -67,27 +69,31 @@ public OAuthAuthenticator get() {
6769

6870
private OAuthAuthenticator getOAuthAuthenticator(
6971
String cheApiEndpoint,
72+
String tenantIdPath,
7073
String clientIdPath,
7174
String clientSecretPath,
7275
String azureDevOpsApiEndpoint,
7376
String azureDevOpsScmApiEndpoint,
74-
String authUri,
75-
String tokenUri,
77+
String authUriTemplate,
78+
String tokenUriTemplate,
7679
String[] redirectUris)
7780
throws IOException {
7881

79-
if (!isNullOrEmpty(clientIdPath) && !isNullOrEmpty(clientSecretPath)) {
82+
if (!isNullOrEmpty(clientIdPath)
83+
&& !isNullOrEmpty(clientSecretPath)
84+
&& !isNullOrEmpty(tenantIdPath)) {
85+
final String tenantId = Files.readString(Path.of(tenantIdPath)).trim();
8086
final String clientId = Files.readString(Path.of(clientIdPath)).trim();
8187
final String clientSecret = Files.readString(Path.of(clientSecretPath)).trim();
82-
if (!isNullOrEmpty(clientId) && !isNullOrEmpty(clientSecret)) {
88+
if (!isNullOrEmpty(clientId) && !isNullOrEmpty(clientSecret) && !isNullOrEmpty(tenantId)) {
8389
return new AzureDevOpsOAuthAuthenticator(
8490
cheApiEndpoint,
8591
clientId,
8692
clientSecret,
8793
azureDevOpsApiEndpoint,
8894
azureDevOpsScmApiEndpoint,
89-
authUri,
90-
tokenUri,
95+
String.format(authUriTemplate, tenantId),
96+
String.format(tokenUriTemplate, tenantId),
9197
redirectUris);
9298
}
9399
}

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

Lines changed: 0 additions & 39 deletions
This file was deleted.

0 commit comments

Comments
 (0)