Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add support for AuthFileCredential #7552

Closed
wants to merge 10 commits into from
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

package com.azure.identity;

import com.azure.core.annotation.Immutable;
import com.azure.core.credential.AccessToken;
import com.azure.core.credential.TokenCredential;
import com.azure.core.credential.TokenRequestContext;
import com.azure.core.util.serializer.JacksonAdapter;
import com.azure.core.util.serializer.SerializerAdapter;
import com.azure.core.util.serializer.SerializerEncoding;
import com.azure.identity.implementation.IdentityClientOptions;
import reactor.core.publisher.Mono;

import java.io.File;
import java.io.FileInputStream;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.Objects;

/**
* Enables authentication to Azure Active Directory using configuration
* information stored Azure SDK Auth File.
*
* An Azaure SDK Auth file may be generated by passing <code>--sdk-auth</code>
* when generating an service principal with
* <code>az ad sp create-for-rbac</code>. At this time
* <see cref="AuthFileCredential"/> only supports SDK Auth Files which contain a
* client secret, client certificates are not supported at this time.
*/
@Immutable
public class AuthFileCredential implements TokenCredential {
/* The file path value. */
private final String filepath;
private IdentityClientOptions identityClientOptions;
private TokenCredential credential;
private static final SerializerAdapter SERIALIZER_ADAPTER = JacksonAdapter.createDefaultSerializerAdapter();

/**
* Creates an instance of the SdkAuthFileCredential class based on information in given SDK Auth file.
* If the file is not found or there are errors parsing it, <see cref="GetToken(TokenRequestContext)"/>
* and <see cref="GetToken(TokenRequestContext)"/> will throw a <see cref="Exception"/>
* with details on why the file could not be used.
*
* @param filepath The path to the SDK Auth file.
* @param identityClientOptions the options for configuring the identity client
*/
AuthFileCredential(String filepath, IdentityClientOptions identityClientOptions) {
Objects.requireNonNull(filepath, "'filepath' cannot be null.");
Objects.requireNonNull(identityClientOptions, "'identityClientOptions' cannot be null.");
this.filepath = filepath;
this.identityClientOptions=identityClientOptions;
}

/**
* Obtains a token from the Azure Active Directory service, using the specified client detailed specified in the SDK Auth file.
* This method is called by Azure SDK clients. It isn't intended for use in application code.
* If the SDK Auth file is missing or invalid, this method throws a <see cref="AuthenticationFailedException"/> exception.
* <param name="requestContext">The details of the authentication request</param>
* <returns>An <see cref="AccessToken"/> which can be used to authenticate service client calls.</returns>
*/
@Override
public Mono<AccessToken> getToken(TokenRequestContext request) {
try {
ensureCredential();
Luyunmt marked this conversation as resolved.
Show resolved Hide resolved
} catch (Exception e) {
return Mono.error(e);
}
return credential.getToken(request);
}

/**
* Ensures that credential information is loaded from the SDK Auth file. This method should be called to initialize
* <code>_credential</code> before it is used. If the SDK Auth file is not found or invalid, this method will throw
* <see cref="AuthenticationFailedException"/>.
* <returns>A method that will ensure <code>credential</code> has been initialized</returns>
*/
private void ensureCredential() throws Exception {
if(credential ==null){
try
{
credential = BuildCredentialForCredentialsFile(ParseCredentialsFile(filepath));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Guard this creation of credential in a multi-threaded environment.

Copy link
Contributor Author

@Luyunmt Luyunmt Mar 2, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

credential is instance field and method credential.gettoken doesn't change the state of credential,so we don't think it is necessary to take care the multi-threaded environment

} catch (Exception e){
throw new Exception("Error parsing SDK Auth File", e);
Luyunmt marked this conversation as resolved.
Show resolved Hide resolved
}
}
}

private static Map<String, String> ParseCredentialsFile(String filePath) throws Exception
Luyunmt marked this conversation as resolved.
Show resolved Hide resolved
{
Luyunmt marked this conversation as resolved.
Show resolved Hide resolved
File file = new File(filePath);
if (!file.exists()) {
throw new Exception("Auth File doesn't exist");
Luyunmt marked this conversation as resolved.
Show resolved Hide resolved
}
FileInputStream inputStream = new FileInputStream(file);
int length = inputStream.available();
byte bytes[] = new byte[length];
inputStream.read(bytes);
inputStream.close();
String result = new String(bytes, StandardCharsets.UTF_8);

return SERIALIZER_ADAPTER.deserialize(result,Map.class , SerializerEncoding.JSON);
}

private TokenCredential BuildCredentialForCredentialsFile(Map<String, String> authData) throws Exception
Luyunmt marked this conversation as resolved.
Show resolved Hide resolved
{
String clientId = authData.get("clientId");
String clientSecret = authData.get("clientSecret");
String tenantId = authData.get("tenantId");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use string constants.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In other credentials also using "clientid","tenant" as follow .
public ClientCertificateCredential build() {
ValidationUtil.validate(getClass().getSimpleName(), new HashMap<String, Object>() {{
put("clientId", clientId);
put("tenantId", tenantId);
put("clientCertificate", clientCertificate);
}});
return new ClientCertificateCredential(tenantId, clientId, clientCertificate, clientCertificatePassword,
identityClientOptions);
}
Do we take it into consideration?

String activeDirectoryEndpointUrl = authData.get("activeDirectoryEndpointUrl");

if (clientId == null || clientSecret == null || tenantId == null || activeDirectoryEndpointUrl == null)
{
throw new Exception("Malformed Azure SDK Auth file. The file should contain 'clientId', 'clientSecret', 'tenentId' and 'activeDirectoryEndpointUrl' values.");
Luyunmt marked this conversation as resolved.
Show resolved Hide resolved
}

return new ClientSecretCredential(tenantId, clientId, clientSecret, identityClientOptions.setAuthorityHost(activeDirectoryEndpointUrl));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

package com.azure.identity;

import com.azure.identity.implementation.util.ValidationUtil;

import java.util.HashMap;

/**
* Fluent credential builder for instantiating a {@link AuthFileCredential}.
*
* @see AuthFileCredential
*/
public class AuthFileCredentialBuilder extends AadCredentialBuilderBase<AuthFileCredentialBuilder> {
private String filepath;

/**
* Sets the file path for the authentication.
* @param filepath The path to the SDK Auth file.
* @return the AuthFileCredentialBuilder itself
*/
public AuthFileCredentialBuilder filePath(String filepath) {
this.filepath = filepath;
return this;
}

/**
* Creates a new {@link AuthFileCredential} with the current configurations.
*
* @return a {@link AuthFileCredential} with the current configurations.
*/
public AuthFileCredential build() {
ValidationUtil.validate(getClass().getSimpleName(), new HashMap<String, Object>() {{
put("filepath", filepath);
}});
Luyunmt marked this conversation as resolved.
Show resolved Hide resolved
return new AuthFileCredential(filepath, identityClientOptions);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

package com.azure.identity;

import com.azure.core.credential.TokenRequestContext;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.core.classloader.annotations.PowerMockIgnore;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
Comment on lines +12 to +14
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Try using Mockito and avoid PowerMockito.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be deferred as discussed.


import reactor.test.StepVerifier;

@RunWith(PowerMockRunner.class)
@PrepareForTest(fullyQualifiedNames = "com.azure.identity.*")
@PowerMockIgnore({ "com.sun.org.apache.xerces.*", "javax.xml.*", "org.xml.*" })
public class AuthFileCredentialTests<T> {

// @Test
// public void SdkAuthFileEnsureCredentialParsesCorrectly() throws Exception {
//ClientId,TenantId,ClientSecret in ClientSecretCredential is privated
Luyunmt marked this conversation as resolved.
Show resolved Hide resolved
// }

@Test
public void BadSdkAuthFilePathThrowsDuringGetToken()
{
TokenRequestContext request = new TokenRequestContext().addScopes("https://management.azure.com");

// test
AuthFileCredential credential =new AuthFileCredentialBuilder().filePath("Bougs*File*Path").build();
StepVerifier.create(credential.getToken(request))
.expectErrorMatches(e -> e instanceof Exception && "Error parsing SDK Auth File".equals(e.getMessage()))
.verify();
}
}