Skip to content

Commit 59c6966

Browse files
authored
Vs Code Credential (#10368)
1 parent 6af40a0 commit 59c6966

17 files changed

+859
-27
lines changed

eng/code-quality-reports/src/main/resources/checkstyle/checkstyle-suppressions.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,10 @@
257257
<suppress checks="com.azure.tools.checkstyle.checks.GoodLoggingCheck"
258258
files="com.azure.endtoend.identity.WebJobsIdentityTest.java"/>
259259

260+
<!-- Accesses Windows native credential api, doesn't follow java style.-->
261+
<suppress checks="MethodName|MemberName|VisibilityModifier|TypeName"
262+
files="com.azure.identity.implementation.WindowsCredentialApi.java"/>
263+
260264
<!-- Use the logger in a Utility static method. -->
261265
<suppress checks="com.azure.tools.checkstyle.checks.GoodLoggingCheck"
262266
files="com.azure.ai.textanalytics.Transforms.java"/>

eng/code-quality-reports/src/main/resources/spotbugs/spotbugs-exclude.xml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,32 @@
4545
<Field name="defaultEncryptionAlgorithm"/>
4646
<Bug pattern="UUF_UNUSED_PUBLIC_OR_PROTECTED_FIELD"/>
4747
</Match>
48+
49+
<!-- Defines native api, doesn't follow java style.-->
50+
<Match>
51+
<Class name="~com\.azure\.identity\.implementation\.WindowsCredentialApi(.+)"/>
52+
<Bug pattern="UUF_UNUSED_PUBLIC_OR_PROTECTED_FIELD,
53+
UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD,
54+
NM_FIELD_NAMING_CONVENTION"/>
55+
</Match>
56+
57+
<!-- Exception needs to be caught here.-->
58+
<Match>
59+
<Class name="~com\.azure\.identity\.implementation\.VisualStudioCacheAccessor"/>
60+
<Bug pattern="REC_CATCH_EXCEPTION"/>
61+
</Match>
62+
63+
<!-- Accesses native api, doesn't follow java style.-->
64+
<Match>
65+
<Class name="~com\.azure\.identity\.implementation\.LinuxKeyRingAccessor(.+)"/>
66+
<Bug pattern="UWF_UNWRITTEN_FIELD"/>
67+
</Match>
68+
69+
<!-- Accesses native api, doesn't follow java style.-->
70+
<Match>
71+
<Class name="com.azure.identity.implementation.WindowsCredentialAccessor"/>
72+
<Bug pattern="NP_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD"/>
73+
</Match>
4874

4975
<!-- Public field already exists in the public API surface. -->
5076
<Match>

eng/versioning/external_dependencies.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ com.microsoft.azure:azure-mgmt-search;1.24.1
8484
com.microsoft.azure:azure-mgmt-storage;1.3.0
8585
com.microsoft.azure:azure-storage;8.0.0
8686
com.microsoft.azure:msal4j;1.3.0
87+
com.microsoft.azure:msal4j-persistence-extension;0.1
8788
com.sun.activation:jakarta.activation;1.2.1
8889
io.opentelemetry:opentelemetry-api;0.2.4
8990
io.opentelemetry:opentelemetry-sdk;0.2.4

sdk/identity/azure-identity/pom.xml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,12 @@
9393
<version>2.8.5</version> <!-- {x-version-update;com.google.code.gson:gson;external_dependency} -->
9494
<scope>test</scope>
9595
</dependency>
96+
97+
<dependency>
98+
<groupId>com.microsoft.azure</groupId>
99+
<artifactId>msal4j-persistence-extension</artifactId>
100+
<version>0.1</version> <!-- {x-version-update;com.microsoft.azure:msal4j-persistence-extension;external_dependency} -->
101+
</dependency>
96102
</dependencies>
97103

98104
<build>
@@ -107,6 +113,7 @@
107113
<includes>
108114
<include>com.azure:*</include>
109115
<include>com.microsoft.azure:msal4j:[1.3.0]</include> <!-- {x-include-update;com.microsoft.azure:msal4j;external_dependency} -->
116+
<include>com.microsoft.azure:msal4j-persistence-extension:[0.1]</include> <!-- {x-include-update;com.microsoft.azure:msal4j-persistence-extension;external_dependency} -->
110117
<include>com.nimbusds:oauth2-oidc-sdk:[6.14]</include> <!-- {x-include-update;com.nimbusds:oauth2-oidc-sdk;external_dependency} -->
111118
<include>net.java.dev.jna:jna-platform:[5.4.0]</include> <!-- {x-include-update;net.java.dev.jna:jna-platform;external_dependency} -->
112119
<include>org.nanohttpd:nanohttpd:[2.3.1]</include> <!-- {x-include-update;org.nanohttpd:nanohttpd;external_dependency} -->

sdk/identity/azure-identity/src/main/java/com/azure/identity/AuthorizationCodeCredential.java

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,6 @@ public class AuthorizationCodeCredential implements TokenCredential {
3838
*/
3939
AuthorizationCodeCredential(String clientId, String tenantId, String authCode, URI redirectUri,
4040
IdentityClientOptions identityClientOptions) {
41-
if (tenantId == null) {
42-
tenantId = "common";
43-
}
4441
identityClient = new IdentityClientBuilder()
4542
.tenantId(tenantId)
4643
.clientId(clientId)

sdk/identity/azure-identity/src/main/java/com/azure/identity/DefaultAzureCredentialBuilder.java

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,24 @@ public class DefaultAzureCredentialBuilder extends CredentialBuilderBase<Default
2020
private boolean excludeManagedIdentityCredential;
2121
private boolean excludeSharedTokenCacheCredential;
2222
private boolean excludeAzureCliCredential;
23+
private boolean excludeVsCodeCredential;
24+
private String tenantId;
2325
private final ClientLogger logger = new ClientLogger(DefaultAzureCredentialBuilder.class);
2426

2527

28+
/**
29+
* Sets the tenant id of the user to authenticate through the {@link DefaultAzureCredential}. The default is null
30+
* and will authenticate users to their default tenant.
31+
*
32+
* @param tenantId the tenant ID to set.
33+
* @return An updated instance of this builder with the tenant id set as specified.
34+
*/
35+
public DefaultAzureCredentialBuilder tenantId(String tenantId) {
36+
this.tenantId = tenantId;
37+
return this;
38+
}
39+
40+
2641
/**
2742
* Specifies the Azure Active Directory endpoint to acquire tokens.
2843
* @param authorityHost the Azure Active Directory endpoint
@@ -79,6 +94,16 @@ public DefaultAzureCredentialBuilder excludeAzureCliCredential() {
7994
return this;
8095
}
8196

97+
/**
98+
* Excludes the {@link VisualStudioCodeCredential} from the {@link DefaultAzureCredential} authentication flow.
99+
*
100+
* @return An updated instance of this builder with the Visual Studio Code credential exclusion set as specified.
101+
*/
102+
public DefaultAzureCredentialBuilder excludeVSCodeCredential() {
103+
excludeVsCodeCredential = true;
104+
return this;
105+
}
106+
82107
/**
83108
* Specifies the ExecutorService to be used to execute the authentication requests.
84109
* Developer is responsible for maintaining the lifecycle of the ExecutorService.
@@ -110,7 +135,7 @@ public DefaultAzureCredential build() {
110135
}
111136

112137
private ArrayDeque<TokenCredential> getCredentialsChain() {
113-
ArrayDeque<TokenCredential> output = new ArrayDeque<>(4);
138+
ArrayDeque<TokenCredential> output = new ArrayDeque<>(5);
114139
if (!excludeEnvironmentCredential) {
115140
output.add(new EnvironmentCredential(identityClientOptions));
116141
}
@@ -128,6 +153,10 @@ private ArrayDeque<TokenCredential> getCredentialsChain() {
128153
output.add(new AzureCliCredential(identityClientOptions));
129154
}
130155

156+
if (!excludeVsCodeCredential) {
157+
output.add(new VisualStudioCodeCredential(tenantId, identityClientOptions));
158+
}
159+
131160
if (output.size() == 0) {
132161
throw logger.logExceptionAsError(new IllegalArgumentException("At least one credential type must be"
133162
+ " included in the authentication flow."));

sdk/identity/azure-identity/src/main/java/com/azure/identity/DeviceCodeCredential.java

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,6 @@ public class DeviceCodeCredential implements TokenCredential {
3636
DeviceCodeCredential(String clientId, String tenantId, Consumer<DeviceCodeInfo> challengeConsumer,
3737
IdentityClientOptions identityClientOptions) {
3838
this.challengeConsumer = challengeConsumer;
39-
if (tenantId == null) {
40-
tenantId = "common";
41-
}
4239
identityClient = new IdentityClientBuilder()
4340
.tenantId(tenantId)
4441
.clientId(clientId)

sdk/identity/azure-identity/src/main/java/com/azure/identity/InteractiveBrowserCredential.java

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,6 @@ public class InteractiveBrowserCredential implements TokenCredential {
4242
InteractiveBrowserCredential(String clientId, String tenantId, int port,
4343
IdentityClientOptions identityClientOptions) {
4444
this.port = port;
45-
if (tenantId == null) {
46-
tenantId = "common";
47-
}
4845
identityClient = new IdentityClientBuilder()
4946
.tenantId(tenantId)
5047
.clientId(clientId)

sdk/identity/azure-identity/src/main/java/com/azure/identity/UsernamePasswordCredential.java

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,6 @@ public class UsernamePasswordCredential implements TokenCredential {
4343
Objects.requireNonNull(password, "'password' cannot be null.");
4444
this.username = username;
4545
this.password = password;
46-
if (tenantId == null) {
47-
tenantId = "common";
48-
}
4946
identityClient =
5047
new IdentityClientBuilder()
5148
.tenantId(tenantId)
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
package com.azure.identity;
5+
6+
import com.azure.core.credential.AccessToken;
7+
import com.azure.core.credential.TokenCredential;
8+
import com.azure.core.credential.TokenRequestContext;
9+
import com.azure.identity.implementation.IdentityClient;
10+
import com.azure.identity.implementation.IdentityClientBuilder;
11+
import com.azure.identity.implementation.IdentityClientOptions;
12+
import com.azure.identity.implementation.MsalToken;
13+
import com.azure.identity.implementation.VisualStudioCacheAccessor;
14+
import reactor.core.publisher.Mono;
15+
16+
import java.util.Map;
17+
import java.util.concurrent.atomic.AtomicReference;
18+
19+
/**
20+
* Enables authentication to Azure Active Directory using data from Visual Studio Code
21+
*/
22+
class VisualStudioCodeCredential implements TokenCredential {
23+
private final IdentityClient identityClient;
24+
private final AtomicReference<MsalToken> cachedToken;
25+
private final String cloudInstance;
26+
27+
/**
28+
* Creates a public class VisualStudioCodeCredential implements TokenCredential with the given tenant and
29+
* identity client options.
30+
*
31+
* @param tenantId the tenant ID of the application
32+
* @param identityClientOptions the options for configuring the identity client
33+
*/
34+
VisualStudioCodeCredential(String tenantId, IdentityClientOptions identityClientOptions) {
35+
36+
IdentityClientOptions options = (identityClientOptions == null ? new IdentityClientOptions()
37+
: identityClientOptions);
38+
39+
String tenant = tenantId;
40+
if (tenant == null) {
41+
tenant = "common";
42+
}
43+
VisualStudioCacheAccessor accessor = new VisualStudioCacheAccessor();
44+
Map<String, String> userSettings = accessor.getUserSettingsDetails(tenant);
45+
cloudInstance = userSettings.get("cloud");
46+
options.setAuthorityHost(accessor.getAzureAuthHost(cloudInstance));
47+
48+
identityClient = new IdentityClientBuilder()
49+
.clientId("aebc6443-996d-45c2-90f0-388ff96faa56")
50+
.identityClientOptions(options)
51+
.build();
52+
53+
this.cachedToken = new AtomicReference<>();
54+
}
55+
56+
@Override
57+
public Mono<AccessToken> getToken(TokenRequestContext request) {
58+
return Mono.defer(() -> {
59+
if (cachedToken.get() != null) {
60+
return identityClient.authenticateWithUserRefreshToken(request, cachedToken.get())
61+
.onErrorResume(t -> Mono.empty());
62+
} else {
63+
return Mono.empty();
64+
}
65+
}).switchIfEmpty(
66+
Mono.defer(() -> identityClient.authenticateWithVsCodeCredential(request, cloudInstance)))
67+
.map(msalToken -> {
68+
cachedToken.set(msalToken);
69+
return msalToken;
70+
});
71+
}
72+
}

0 commit comments

Comments
 (0)