Skip to content

Commit

Permalink
Vs Code Credential (#10368)
Browse files Browse the repository at this point in the history
  • Loading branch information
g2vinay authored May 4, 2020
1 parent 6af40a0 commit 59c6966
Show file tree
Hide file tree
Showing 17 changed files with 859 additions and 27 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,10 @@
<suppress checks="com.azure.tools.checkstyle.checks.GoodLoggingCheck"
files="com.azure.endtoend.identity.WebJobsIdentityTest.java"/>

<!-- Accesses Windows native credential api, doesn't follow java style.-->
<suppress checks="MethodName|MemberName|VisibilityModifier|TypeName"
files="com.azure.identity.implementation.WindowsCredentialApi.java"/>

<!-- Use the logger in a Utility static method. -->
<suppress checks="com.azure.tools.checkstyle.checks.GoodLoggingCheck"
files="com.azure.ai.textanalytics.Transforms.java"/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,32 @@
<Field name="defaultEncryptionAlgorithm"/>
<Bug pattern="UUF_UNUSED_PUBLIC_OR_PROTECTED_FIELD"/>
</Match>

<!-- Defines native api, doesn't follow java style.-->
<Match>
<Class name="~com\.azure\.identity\.implementation\.WindowsCredentialApi(.+)"/>
<Bug pattern="UUF_UNUSED_PUBLIC_OR_PROTECTED_FIELD,
UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD,
NM_FIELD_NAMING_CONVENTION"/>
</Match>

<!-- Exception needs to be caught here.-->
<Match>
<Class name="~com\.azure\.identity\.implementation\.VisualStudioCacheAccessor"/>
<Bug pattern="REC_CATCH_EXCEPTION"/>
</Match>

<!-- Accesses native api, doesn't follow java style.-->
<Match>
<Class name="~com\.azure\.identity\.implementation\.LinuxKeyRingAccessor(.+)"/>
<Bug pattern="UWF_UNWRITTEN_FIELD"/>
</Match>

<!-- Accesses native api, doesn't follow java style.-->
<Match>
<Class name="com.azure.identity.implementation.WindowsCredentialAccessor"/>
<Bug pattern="NP_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD"/>
</Match>

<!-- Public field already exists in the public API surface. -->
<Match>
Expand Down
1 change: 1 addition & 0 deletions eng/versioning/external_dependencies.txt
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ com.microsoft.azure:azure-mgmt-search;1.24.1
com.microsoft.azure:azure-mgmt-storage;1.3.0
com.microsoft.azure:azure-storage;8.0.0
com.microsoft.azure:msal4j;1.3.0
com.microsoft.azure:msal4j-persistence-extension;0.1
com.sun.activation:jakarta.activation;1.2.1
io.opentelemetry:opentelemetry-api;0.2.4
io.opentelemetry:opentelemetry-sdk;0.2.4
Expand Down
7 changes: 7 additions & 0 deletions sdk/identity/azure-identity/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,12 @@
<version>2.8.5</version> <!-- {x-version-update;com.google.code.gson:gson;external_dependency} -->
<scope>test</scope>
</dependency>

<dependency>
<groupId>com.microsoft.azure</groupId>
<artifactId>msal4j-persistence-extension</artifactId>
<version>0.1</version> <!-- {x-version-update;com.microsoft.azure:msal4j-persistence-extension;external_dependency} -->
</dependency>
</dependencies>

<build>
Expand All @@ -107,6 +113,7 @@
<includes>
<include>com.azure:*</include>
<include>com.microsoft.azure:msal4j:[1.3.0]</include> <!-- {x-include-update;com.microsoft.azure:msal4j;external_dependency} -->
<include>com.microsoft.azure:msal4j-persistence-extension:[0.1]</include> <!-- {x-include-update;com.microsoft.azure:msal4j-persistence-extension;external_dependency} -->
<include>com.nimbusds:oauth2-oidc-sdk:[6.14]</include> <!-- {x-include-update;com.nimbusds:oauth2-oidc-sdk;external_dependency} -->
<include>net.java.dev.jna:jna-platform:[5.4.0]</include> <!-- {x-include-update;net.java.dev.jna:jna-platform;external_dependency} -->
<include>org.nanohttpd:nanohttpd:[2.3.1]</include> <!-- {x-include-update;org.nanohttpd:nanohttpd;external_dependency} -->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,6 @@ public class AuthorizationCodeCredential implements TokenCredential {
*/
AuthorizationCodeCredential(String clientId, String tenantId, String authCode, URI redirectUri,
IdentityClientOptions identityClientOptions) {
if (tenantId == null) {
tenantId = "common";
}
identityClient = new IdentityClientBuilder()
.tenantId(tenantId)
.clientId(clientId)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,24 @@ public class DefaultAzureCredentialBuilder extends CredentialBuilderBase<Default
private boolean excludeManagedIdentityCredential;
private boolean excludeSharedTokenCacheCredential;
private boolean excludeAzureCliCredential;
private boolean excludeVsCodeCredential;
private String tenantId;
private final ClientLogger logger = new ClientLogger(DefaultAzureCredentialBuilder.class);


/**
* Sets the tenant id of the user to authenticate through the {@link DefaultAzureCredential}. The default is null
* and will authenticate users to their default tenant.
*
* @param tenantId the tenant ID to set.
* @return An updated instance of this builder with the tenant id set as specified.
*/
public DefaultAzureCredentialBuilder tenantId(String tenantId) {
this.tenantId = tenantId;
return this;
}


/**
* Specifies the Azure Active Directory endpoint to acquire tokens.
* @param authorityHost the Azure Active Directory endpoint
Expand Down Expand Up @@ -79,6 +94,16 @@ public DefaultAzureCredentialBuilder excludeAzureCliCredential() {
return this;
}

/**
* Excludes the {@link VisualStudioCodeCredential} from the {@link DefaultAzureCredential} authentication flow.
*
* @return An updated instance of this builder with the Visual Studio Code credential exclusion set as specified.
*/
public DefaultAzureCredentialBuilder excludeVSCodeCredential() {
excludeVsCodeCredential = true;
return this;
}

/**
* Specifies the ExecutorService to be used to execute the authentication requests.
* Developer is responsible for maintaining the lifecycle of the ExecutorService.
Expand Down Expand Up @@ -110,7 +135,7 @@ public DefaultAzureCredential build() {
}

private ArrayDeque<TokenCredential> getCredentialsChain() {
ArrayDeque<TokenCredential> output = new ArrayDeque<>(4);
ArrayDeque<TokenCredential> output = new ArrayDeque<>(5);
if (!excludeEnvironmentCredential) {
output.add(new EnvironmentCredential(identityClientOptions));
}
Expand All @@ -128,6 +153,10 @@ private ArrayDeque<TokenCredential> getCredentialsChain() {
output.add(new AzureCliCredential(identityClientOptions));
}

if (!excludeVsCodeCredential) {
output.add(new VisualStudioCodeCredential(tenantId, identityClientOptions));
}

if (output.size() == 0) {
throw logger.logExceptionAsError(new IllegalArgumentException("At least one credential type must be"
+ " included in the authentication flow."));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,6 @@ public class DeviceCodeCredential implements TokenCredential {
DeviceCodeCredential(String clientId, String tenantId, Consumer<DeviceCodeInfo> challengeConsumer,
IdentityClientOptions identityClientOptions) {
this.challengeConsumer = challengeConsumer;
if (tenantId == null) {
tenantId = "common";
}
identityClient = new IdentityClientBuilder()
.tenantId(tenantId)
.clientId(clientId)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,6 @@ public class InteractiveBrowserCredential implements TokenCredential {
InteractiveBrowserCredential(String clientId, String tenantId, int port,
IdentityClientOptions identityClientOptions) {
this.port = port;
if (tenantId == null) {
tenantId = "common";
}
identityClient = new IdentityClientBuilder()
.tenantId(tenantId)
.clientId(clientId)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,6 @@ public class UsernamePasswordCredential implements TokenCredential {
Objects.requireNonNull(password, "'password' cannot be null.");
this.username = username;
this.password = password;
if (tenantId == null) {
tenantId = "common";
}
identityClient =
new IdentityClientBuilder()
.tenantId(tenantId)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

package com.azure.identity;

import com.azure.core.credential.AccessToken;
import com.azure.core.credential.TokenCredential;
import com.azure.core.credential.TokenRequestContext;
import com.azure.identity.implementation.IdentityClient;
import com.azure.identity.implementation.IdentityClientBuilder;
import com.azure.identity.implementation.IdentityClientOptions;
import com.azure.identity.implementation.MsalToken;
import com.azure.identity.implementation.VisualStudioCacheAccessor;
import reactor.core.publisher.Mono;

import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;

/**
* Enables authentication to Azure Active Directory using data from Visual Studio Code
*/
class VisualStudioCodeCredential implements TokenCredential {
private final IdentityClient identityClient;
private final AtomicReference<MsalToken> cachedToken;
private final String cloudInstance;

/**
* Creates a public class VisualStudioCodeCredential implements TokenCredential with the given tenant and
* identity client options.
*
* @param tenantId the tenant ID of the application
* @param identityClientOptions the options for configuring the identity client
*/
VisualStudioCodeCredential(String tenantId, IdentityClientOptions identityClientOptions) {

IdentityClientOptions options = (identityClientOptions == null ? new IdentityClientOptions()
: identityClientOptions);

String tenant = tenantId;
if (tenant == null) {
tenant = "common";
}
VisualStudioCacheAccessor accessor = new VisualStudioCacheAccessor();
Map<String, String> userSettings = accessor.getUserSettingsDetails(tenant);
cloudInstance = userSettings.get("cloud");
options.setAuthorityHost(accessor.getAzureAuthHost(cloudInstance));

identityClient = new IdentityClientBuilder()
.clientId("aebc6443-996d-45c2-90f0-388ff96faa56")
.identityClientOptions(options)
.build();

this.cachedToken = new AtomicReference<>();
}

@Override
public Mono<AccessToken> getToken(TokenRequestContext request) {
return Mono.defer(() -> {
if (cachedToken.get() != null) {
return identityClient.authenticateWithUserRefreshToken(request, cachedToken.get())
.onErrorResume(t -> Mono.empty());
} else {
return Mono.empty();
}
}).switchIfEmpty(
Mono.defer(() -> identityClient.authenticateWithVsCodeCredential(request, cloudInstance)))
.map(msalToken -> {
cachedToken.set(msalToken);
return msalToken;
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import com.microsoft.aad.msal4j.ConfidentialClientApplication;
import com.microsoft.aad.msal4j.DeviceCodeFlowParameters;
import com.microsoft.aad.msal4j.PublicClientApplication;
import com.microsoft.aad.msal4j.RefreshTokenParameters;
import com.microsoft.aad.msal4j.SilentParameters;
import com.microsoft.aad.msal4j.UserNamePasswordParameters;
import reactor.core.publisher.Mono;
Expand Down Expand Up @@ -101,7 +102,7 @@ public class IdentityClient {
*/
IdentityClient(String tenantId, String clientId, IdentityClientOptions options) {
if (tenantId == null) {
tenantId = "common";
tenantId = "organizations";
}
if (options == null) {
options = new IdentityClientOptions();
Expand All @@ -112,7 +113,7 @@ public class IdentityClient {
if (clientId == null) {
this.publicClientApplication = null;
} else {
String authorityUrl = options.getAuthorityHost().replaceAll("/+$", "") + "/organizations/" + tenantId;
String authorityUrl = options.getAuthorityHost().replaceAll("/+$", "") + "/" + tenantId;
PublicClientApplication.Builder publicClientApplicationBuilder = PublicClientApplication.builder(clientId);
try {
publicClientApplicationBuilder = publicClientApplicationBuilder.authority(authorityUrl);
Expand Down Expand Up @@ -429,6 +430,26 @@ public Mono<MsalToken> authenticateWithDeviceCode(TokenRequestContext request,
}).map(ar -> new MsalToken(ar, options));
}

/**
* Asynchronously acquire a token from Active Directory with Visual Sutdio cached refresh token.
*
* @param request the details of the token request
* @return a Publisher that emits an AccessToken.
*/
public Mono<MsalToken> authenticateWithVsCodeCredential(TokenRequestContext request, String cloud) {

VisualStudioCacheAccessor accessor = new VisualStudioCacheAccessor();

String credential = accessor.getCredentials("VS Code Azure", cloud);

RefreshTokenParameters parameters = RefreshTokenParameters
.builder(new HashSet<>(request.getScopes()), credential)
.build();

return Mono.defer(() -> Mono.fromFuture(publicClientApplication.acquireToken(parameters))
.map(ar -> new MsalToken(ar, options)));
}

/**
* Asynchronously acquire a token from Active Directory with an authorization code from an oauth flow.
*
Expand Down
Loading

0 comments on commit 59c6966

Please sign in to comment.