Skip to content

Commit

Permalink
Merge pull request Azure#144 from jianghaolu/clicreds
Browse files Browse the repository at this point in the history
Add AzureCliCredentials
  • Loading branch information
jianghaolu committed Feb 13, 2017
2 parents 8a41049 + 8f47f5a commit 877ce26
Show file tree
Hide file tree
Showing 31 changed files with 644 additions and 247 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@
import com.microsoft.aad.adal4j.AuthenticationResult;
import com.microsoft.aad.adal4j.ClientCredential;
import com.microsoft.azure.AzureEnvironment;
import com.microsoft.rest.credentials.TokenCredentials;
import okhttp3.OkHttpClient;

import java.io.File;
import java.io.FileInputStream;
Expand All @@ -26,15 +24,11 @@
/**
* Token based credentials for use with a REST Service Client.
*/
public class ApplicationTokenCredentials extends TokenCredentials implements AzureTokenCredentials {
public class ApplicationTokenCredentials extends AzureTokenCredentials {
/** A mapping from resource endpoint to its cached access token. */
private Map<String, AuthenticationResult> tokens;
/** The Azure environment to authenticate with. */
private AzureEnvironment environment;
/** The active directory application client id. */
private String clientId;
/** The tenant or domain the containing the application. */
private String domain;
/** The authentication secret for the application. */
private String secret;
/** The default subscription to use, if any. */
Expand All @@ -50,10 +44,8 @@ public class ApplicationTokenCredentials extends TokenCredentials implements Azu
* If null is provided, AzureEnvironment.AZURE will be used.
*/
public ApplicationTokenCredentials(String clientId, String domain, String secret, AzureEnvironment environment) {
super(null, null); // defer token acquisition
this.environment = (environment == null) ? AzureEnvironment.AZURE : environment;
super(environment, domain); // defer token acquisition
this.clientId = clientId;
this.domain = domain;
this.secret = secret;
this.tokens = new HashMap<>();
}
Expand Down Expand Up @@ -169,16 +161,6 @@ public String getClientId() {
return clientId;
}

/**
* Gets the tenant or domain the containing the application.
*
* @return the tenant or domain the containing the application.
*/
@Override
public String domain() {
return domain;
}

/**
* Gets the authentication secret for the application.
*
Expand All @@ -189,19 +171,15 @@ public String getSecret() {
}

@Override
public String getToken(String resource) throws IOException {
public synchronized String getToken(String resource) throws IOException {
AuthenticationResult authenticationResult = tokens.get(resource);
if (authenticationResult == null || authenticationResult.getExpiresOnDate().before(new Date())) {
authenticationResult = acquireAccessToken(resource);
}
tokens.put(resource, authenticationResult);
return authenticationResult.getAccessToken();
}

@Override
public AzureEnvironment environment() {
return this.environment;
}

private AuthenticationResult acquireAccessToken(String resource) throws IOException {
String authorityUrl = this.environment().authenticationEndpoint() + this.domain();
ExecutorService executor = Executors.newSingleThreadExecutor();
Expand All @@ -211,17 +189,11 @@ private AuthenticationResult acquireAccessToken(String resource) throws IOExcept
resource,
new ClientCredential(this.getClientId(), this.getSecret()),
null).get();
tokens.put(resource, result);
return result;
} catch (Exception e) {
throw new IOException(e.getMessage(), e);
} finally {
executor.shutdown();
}
}

@Override
public void applyCredentialsFilter(OkHttpClient.Builder clientBuilder) {
clientBuilder.interceptors().add(new AzureTokenCredentialsInterceptor(this));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
/**
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for
* license information.
*/

package com.microsoft.azure.credentials;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.microsoft.azure.AzureEnvironment;
import com.microsoft.rest.serializer.JacksonAdapter;
import okhttp3.OkHttpClient;

import java.io.File;
import java.io.IOException;
import java.nio.file.Paths;
import java.text.SimpleDateFormat;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
* Token based credentials for use with a REST Service Client.
*/
public final class AzureCliCredentials extends AzureTokenCredentials {
private static final ObjectMapper MAPPER = new JacksonAdapter().serializer().setDateFormat(new SimpleDateFormat("yyyy-MM-dd hh:mm:ssssss"));
/** A mapping from resource endpoint to its cached access token. */
private Map<String, AzureCliSubscription> subscriptions;
private String defaultSubscriptionId;
private File azureProfile;
private File accessTokens;
private Lock lock = new ReentrantLock();

private AzureCliCredentials() {
super(null, null);
subscriptions = new ConcurrentHashMap<>();
}

private synchronized void loadAccessTokens() throws IOException {
try {
AzureCliSubscription.Wrapper wrapper = MAPPER.readValue(azureProfile, AzureCliSubscription.Wrapper.class);
List<AzureCliToken> tokens = MAPPER.readValue(accessTokens, new TypeReference<List<AzureCliToken>>() { });
while (wrapper == null || tokens == null || tokens.isEmpty() || wrapper.subscriptions == null || wrapper.subscriptions.isEmpty()) {
System.err.println("Please login in Azure CLI and press any key to continue after you've successfully logged in.");
System.in.read();
wrapper = MAPPER.readValue(azureProfile, AzureCliSubscription.Wrapper.class);
tokens = MAPPER.readValue(accessTokens, new TypeReference<List<AzureCliToken>>() { });
}
for (AzureCliSubscription subscription : wrapper.subscriptions) {
for (AzureCliToken token : tokens) {
// Find match of user and tenant
if (subscription.isServicePrincipal() == token.isServicePrincipal()
&& subscription.userName().equalsIgnoreCase(token.user())
&& subscription.tenant().equalsIgnoreCase(token.tenant())) {
subscriptions.put(subscription.id(), subscription.withToken(token));
if (subscription.isDefault()) {
defaultSubscriptionId = subscription.id();
}
}
}
}
} catch (IOException e) {
System.err.println(String.format("Cannot read files %s and %s. Are you logged in Azure CLI?", azureProfile.getAbsolutePath(), accessTokens.getAbsolutePath()));
throw e;
}
}

/**
* Creates an instance of AzureCliCredentials with the default Azure CLI configuration.
*
* @return an instance of AzureCliCredentials
* @throws IOException if the Azure CLI token files are not accessible
*/
public static AzureCliCredentials create() throws IOException {
return create(
Paths.get(System.getProperty("user.home"), ".azure", "azureProfile.json").toFile(),
Paths.get(System.getProperty("user.home"), ".azure", "accessTokens.json").toFile());
}

/**
* Creates an instance of AzureCliCredentials with custom locations of the token files.
*
* @param azureProfile the azureProfile.json file created by Azure CLI
* @param accessTokens the accessTokens.json file created by Azure CLI
* @return an instance of AzureCliCredentials
* @throws IOException if the Azure CLI token files are not accessible
*/
public static AzureCliCredentials create(File azureProfile, File accessTokens) throws IOException {
AzureCliCredentials credentials = new AzureCliCredentials();
credentials.azureProfile = azureProfile;
credentials.accessTokens = accessTokens;
credentials.loadAccessTokens();
return credentials;
}

/**
* @return the active directory application client id
*/
public String clientId() {
return subscriptions.get(defaultSubscriptionId).clientId();
}

/**
* @return the tenant or domain the containing the application
*/
@Override
public String domain() {
return subscriptions.get(defaultSubscriptionId).tenant();
}

/**
* @return the Azure environment to authenticate with
*/
public AzureEnvironment environment() {
return subscriptions.get(defaultSubscriptionId).environment();
}

/**
* @return the default subscription ID logged in in Azure CLI
*/
public String defaultSubscriptionId() {
return defaultSubscriptionId;
}

/**
* Set default subscription ID.
*
* @param subscriptionId the default subscription ID.
* @return the credentials object itself.
*/
public AzureCliCredentials withDefaultSubscriptionId(String subscriptionId) {
this.defaultSubscriptionId = subscriptionId;
return this;
}

@Override
public synchronized String getToken(String resource) throws IOException {
String token = subscriptions.get(defaultSubscriptionId).credentialInstance().getToken(resource);
if (token == null) {
System.err.println("Please login in Azure CLI and press any key to continue after you've successfully logged in.");
System.in.read();
loadAccessTokens();
token = subscriptions.get(defaultSubscriptionId).credentialInstance().getToken(resource);
}
return token;
}

@Override
public void applyCredentialsFilter(OkHttpClient.Builder clientBuilder) {
clientBuilder.interceptors().add(new AzureTokenCredentialsInterceptor(this));
}
}
Loading

0 comments on commit 877ce26

Please sign in to comment.