From 898a02f16e1a727d50d3dfb6bba976d26949162e Mon Sep 17 00:00:00 2001 From: Jianghao Lu Date: Mon, 16 Mar 2020 17:11:30 -0700 Subject: [PATCH 01/45] Depend on msal persistence extension --- sdk/identity/azure-identity/pom.xml | 5 + .../identity/SharedTokenCacheCredential.java | 9 +- .../msalextensions/CacheLock.java | 154 ---------- .../CacheLockNotObtainedException.java | 19 -- .../PersistentTokenCacheAccessAspect.java | 87 ------ .../cachepersister/CachePersister.java | 111 -------- .../cachepersister/CacheProtectorBase.java | 128 --------- .../PlatformNotSupportedException.java | 21 -- .../windows/WindowsDPAPICacheProtector.java | 95 ------- .../msalextensions/CacheLockTest.java | 235 --------------- .../msalextensions/CrossProgramVSTest.java | 127 --------- .../msalextensions/FileWriter.java | 47 --- .../msalextensions/MsalCacheStorageTest.java | 47 --- .../MultithreadedTokenCacheTest.java | 193 ------------- .../PersistentTokenCacheAccessAspectTest.java | 267 ------------------ .../msalextensions/TestConfiguration.java | 19 -- .../azure-security-keyvault-secrets/pom.xml | 2 +- 17 files changed, 13 insertions(+), 1553 deletions(-) delete mode 100644 sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/msalextensions/CacheLock.java delete mode 100644 sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/msalextensions/CacheLockNotObtainedException.java delete mode 100644 sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/msalextensions/PersistentTokenCacheAccessAspect.java delete mode 100644 sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/msalextensions/cachepersister/CachePersister.java delete mode 100644 sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/msalextensions/cachepersister/CacheProtectorBase.java delete mode 100644 sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/msalextensions/cachepersister/PlatformNotSupportedException.java delete mode 100644 sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/msalextensions/cachepersister/windows/WindowsDPAPICacheProtector.java delete mode 100644 sdk/identity/azure-identity/src/test/java/com/azure/identity/implementation/msalextensions/CacheLockTest.java delete mode 100644 sdk/identity/azure-identity/src/test/java/com/azure/identity/implementation/msalextensions/CrossProgramVSTest.java delete mode 100644 sdk/identity/azure-identity/src/test/java/com/azure/identity/implementation/msalextensions/FileWriter.java delete mode 100644 sdk/identity/azure-identity/src/test/java/com/azure/identity/implementation/msalextensions/MsalCacheStorageTest.java delete mode 100644 sdk/identity/azure-identity/src/test/java/com/azure/identity/implementation/msalextensions/MultithreadedTokenCacheTest.java delete mode 100644 sdk/identity/azure-identity/src/test/java/com/azure/identity/implementation/msalextensions/PersistentTokenCacheAccessAspectTest.java delete mode 100644 sdk/identity/azure-identity/src/test/java/com/azure/identity/implementation/msalextensions/TestConfiguration.java diff --git a/sdk/identity/azure-identity/pom.xml b/sdk/identity/azure-identity/pom.xml index f144efb735670..d3bdb3d359835 100644 --- a/sdk/identity/azure-identity/pom.xml +++ b/sdk/identity/azure-identity/pom.xml @@ -39,6 +39,11 @@ msal4j 1.3.0 + + com.microsoft.azure + msal4j-persistence-extension + 0.1 + com.nimbusds oauth2-oidc-sdk diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/SharedTokenCacheCredential.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/SharedTokenCacheCredential.java index 6323f449bdff2..45ab02f36275b 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/SharedTokenCacheCredential.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/SharedTokenCacheCredential.java @@ -8,14 +8,16 @@ import com.azure.core.credential.TokenRequestContext; import com.azure.core.util.Configuration; import com.azure.identity.implementation.IdentityClientOptions; -import com.azure.identity.implementation.msalextensions.PersistentTokenCacheAccessAspect; import com.microsoft.aad.msal4j.IAccount; import com.microsoft.aad.msal4j.IAuthenticationResult; import com.microsoft.aad.msal4j.PublicClientApplication; import com.microsoft.aad.msal4j.SilentParameters; +import com.microsoft.aad.msal4jextensions.PersistenceSettings; +import com.microsoft.aad.msal4jextensions.PersistenceTokenCacheAccessAspect; import reactor.core.publisher.Mono; import java.net.MalformedURLException; +import java.nio.file.Paths; import java.time.ZoneOffset; import java.util.HashMap; import java.util.HashSet; @@ -75,7 +77,10 @@ public Mono getToken(TokenRequestContext request) { // Initialize here so that the constructor doesn't throw if (pubClient == null) { try { - PersistentTokenCacheAccessAspect accessAspect = new PersistentTokenCacheAccessAspect(); + PersistenceSettings persistenceSettings = PersistenceSettings.builder("msal.cache", Paths.get(System.getProperty("user.home"), "AppData", "Local", ".IdentityService")) + .setMacKeychain("Microsoft.Developer.IdentityService", "MSALCache") + .build(); + PersistenceTokenCacheAccessAspect accessAspect = new PersistenceTokenCacheAccessAspect(persistenceSettings); PublicClientApplication.Builder applicationBuilder = PublicClientApplication.builder(this.clientId); if (options.getExecutorService() != null) { applicationBuilder.executorService(options.getExecutorService()); diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/msalextensions/CacheLock.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/msalextensions/CacheLock.java deleted file mode 100644 index 9d29e2666e0e1..0000000000000 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/msalextensions/CacheLock.java +++ /dev/null @@ -1,154 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.identity.implementation.msalextensions; - -import java.io.File; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.RandomAccessFile; -import java.nio.channels.FileChannel; -import java.nio.channels.FileLock; -import java.nio.channels.OverlappingFileLockException; -import java.nio.file.Files; -import java.util.Random; - -/** - * Cache lock for the persistent shared MSAL token cache - * - * Needed to maintain the integrity of the cache if multiple processes are trying to access it at the same time. - * */ -public class CacheLock { - - private int lockfileRetryWait = 100; - private int lockfileRetryCount = 60000 / lockfileRetryWait; - - private File lockFile; - - private FileOutputStream fos; - private FileChannel channel; - private FileLock lock = null; - - private File debugFile; - private String debugFilename = java.nio.file.Paths.get( - System.getProperty("user.dir"), "target", "debug").toString(); - private final boolean debugFlag; - - /** - * Default constructor to be used to initialize CacheLock - * - * @param lockfileName path of the lock file to be used - * */ - public CacheLock(String lockfileName) { - lockFile = new File(lockfileName); - debugFlag = false; - } - - /** - * Constructor to be used for debugging purposes - * Enables printing the actions for each process while using the cache lock - * - * @param lockfileName path of the lock file to be used - * @param id name of the current process so - * */ - public CacheLock(String lockfileName, String id) { - lockFile = new File(lockfileName); - debugFile = new File(debugFilename + id + ".txt"); - debugFlag = true; - } - - /** - * Tries to obtain the lock by creating a file lock on the provided lockFile - * If it cannot be obtained right away, it retries lockfileRetryCount = 60000 / lockfileRetryWait times - * - * @throws CacheLockNotObtainedException if the lock cannot be obtained after all these tries. - * */ - public void lock() throws CacheLockNotObtainedException { - try { - for (int tryCount = 0; tryCount < lockfileRetryCount; tryCount++) { - - if (debugFlag) { - try { - fos = new FileOutputStream(debugFile, true); - } catch (FileNotFoundException e) { - e.printStackTrace(); - } - } - - if (!lockFile.exists()) { // file doesn't already exist so now you have to make a new one - if (lockFile.createNewFile()) { - lockFile.deleteOnExit(); - - try { - channel = new RandomAccessFile(lockFile, "rw").getChannel(); - lock = channel.lock(); - - printToFileIfDebug("Locked!\n"); - return; //success - - } catch (OverlappingFileLockException e) { - printToFileIfDebug("overlap error\n"); - } catch (Exception e) { - printToFileIfDebug("something else went wrong.. general exception\n"); - } - - } else { - printToFileIfDebug("lockfile already exists\n"); - } - } else { - printToFileIfDebug("create new file failed"); - } - - printToFileIfDebug("retry\n"); - - try { - Random rand = new Random(System.currentTimeMillis()); - int offset = rand.nextInt(10); - // slight offset in case multiple threads/processes have the same wait time - Thread.sleep(lockfileRetryWait + offset); - } catch (InterruptedException ex) { - printToFileIfDebug("thread sleep issue"); - } - } - - } catch (IOException e) { - printToFileIfDebug("general exception, not sure what happened here...no retries\n"); - } - - throw new CacheLockNotObtainedException("Maximum retries used; could not obtain CacheLock"); - } - - /** - * Tries to unlock the file lock - * - * @return true if the file was unlocked, false otherwise - * */ - public boolean unlock() { - try { - lock.release(); - channel.close(); - Files.delete(java.nio.file.Paths.get(lockFile.getPath())); - - printToFileIfDebug("unlocked\n"); - - return true; - } catch (IOException e) { - printToFileIfDebug("not unlocked... IOException: " + e.getMessage()); - return false; - } - } - - /** - * If debugFlag is true, then this will print logs to the file, otherwise it will do nothing - */ - private void printToFileIfDebug(String message) { - if (debugFlag && fos != null) { - try { - fos.write(message.getBytes("UTF-8")); - } catch (IOException e) { - e.printStackTrace(); - } - } - } -} diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/msalextensions/CacheLockNotObtainedException.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/msalextensions/CacheLockNotObtainedException.java deleted file mode 100644 index de36ebf7be389..0000000000000 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/msalextensions/CacheLockNotObtainedException.java +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.identity.implementation.msalextensions; - -/** - * Exception for when the {@link CacheLock} cannot be obtained when trying cacheLock.lock() - * */ -public class CacheLockNotObtainedException extends RuntimeException { - - /** - * Initializes CacheLockNotObtainedException - * - * @param message Error message - * */ - public CacheLockNotObtainedException(String message) { - super(message); - } -} diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/msalextensions/PersistentTokenCacheAccessAspect.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/msalextensions/PersistentTokenCacheAccessAspect.java deleted file mode 100644 index 74d5377d19c79..0000000000000 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/msalextensions/PersistentTokenCacheAccessAspect.java +++ /dev/null @@ -1,87 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.identity.implementation.msalextensions; - -import com.azure.core.util.logging.ClientLogger; -import com.azure.identity.implementation.msalextensions.cachepersister.CachePersister; -import com.azure.identity.implementation.msalextensions.cachepersister.PlatformNotSupportedException; -import com.microsoft.aad.msal4j.ITokenCacheAccessAspect; -import com.microsoft.aad.msal4j.ITokenCacheAccessContext; - -import java.io.IOException; -import java.io.UnsupportedEncodingException; - -/** - * Access Aspect for accessing the token cache - * Allows for notifications for the cache before/after access so the lock can be used - * */ -public class PersistentTokenCacheAccessAspect implements ITokenCacheAccessAspect { - - private CachePersister cachePersister; - private ClientLogger logger; - - /** - * Default constructor, creates a CachePersister object - * - * @throws IOException from errors in creating the CachePersister - * @throws PlatformNotSupportedException from errors in creating the CachePersister - * */ - public PersistentTokenCacheAccessAspect() throws RuntimeException, PlatformNotSupportedException { - logger = new ClientLogger(PersistentTokenCacheAccessAspect.class); - - cachePersister = new CachePersister.Builder().build(); - } - - /** - * Constructor with a custom CachePersister object - * - * @param customCachePersister - * */ - public PersistentTokenCacheAccessAspect(CachePersister customCachePersister) { - cachePersister = customCachePersister; - } - - /** - * Loads token cache to memory using CachePersister - deserialize data in file to Token Cache class - * - * @param iTokenCacheAccessContext - * */ - public void beforeCacheAccess(ITokenCacheAccessContext iTokenCacheAccessContext) { - - byte[] bytes = cachePersister.readCache(); - String data; - try { - data = new String(bytes, "UTF-8"); - } catch (UnsupportedEncodingException e) { - data = ""; - } - - iTokenCacheAccessContext.tokenCache().deserialize(data); - } - - /** - * Reads memory and writes to token cache file using CachePersister - * - * @param iTokenCacheAccessContext - * */ - public void afterCacheAccess(ITokenCacheAccessContext iTokenCacheAccessContext) { - - if (iTokenCacheAccessContext.hasCacheChanged()) { - String newData = iTokenCacheAccessContext.tokenCache().serialize(); - try { - cachePersister.writeCache(newData.getBytes("UTF-8")); - } catch (UnsupportedEncodingException e) { - // don't update cache - logger.error("was not able to write to cache"); - } - } - } - - /** - * Wrapper method to delete cache - * */ - public void deleteCache() { - cachePersister.deleteCache(); - } -} diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/msalextensions/cachepersister/CachePersister.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/msalextensions/cachepersister/CachePersister.java deleted file mode 100644 index 42d4ff0e5cb45..0000000000000 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/msalextensions/cachepersister/CachePersister.java +++ /dev/null @@ -1,111 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.identity.implementation.msalextensions.cachepersister; - -import com.azure.identity.implementation.msalextensions.cachepersister.windows.WindowsDPAPICacheProtector; -import com.sun.jna.Platform; - -import java.io.IOException; - -/** - * Wrapper class for CacheProtector - * Determines the type of CacheProtector to use (if possible) and instantiates it - * Also contains wrapper methods for read, write, and delete cache - * */ -public final class CachePersister { - - private CacheProtectorBase cacheProtector; - - /** - * Default constructor - * */ - private CachePersister(CacheProtectorBase cacheProtector) { - this.cacheProtector = cacheProtector; - } - - /** - * Wrapper method for reading cache - * - * @return byte[] of cache contents - * */ - public byte[] readCache() { - return cacheProtector.readCache(); - } - - /** - * Wrapper method for writing to the cache - * - * @param data Cache contents - * */ - public void writeCache(byte[] data) { - cacheProtector.writeCache(data); - } - - public boolean deleteCache() { - return cacheProtector.deleteCache(); - } - - /** - * Builder for CachePersister class - * Creates appropriate file paths and account and service names, and calls createCacheProtector - * */ - public static class Builder { - - private String cacheLocation; - private String lockfileLocation; - - /** - * Default builder based on platform for cache file, and default service and account names - */ - public Builder() { - - // determine platform and create cache file location - if (Platform.isWindows()) { - cacheLocation = java.nio.file.Paths.get(System.getProperty("user.home"), - "AppData", "Local", ".IdentityService", "msal.cache").toString(); - } else { - cacheLocation = java.nio.file.Paths.get(System.getProperty("user.home"), - "msal.cache").toString(); - } - lockfileLocation = cacheLocation + ".lockfile"; -// -// serviceName = "Microsoft.Developer.IdentityService"; -// accountName = "MSALCache"; - } - - /** - * @return Builder with updated cacheLocation and lockfileLocation - * */ - public Builder cacheLocation(String cacheLocation) { - this.cacheLocation = cacheLocation; - this.lockfileLocation = cacheLocation + ".lockfile"; - return this; - } - - /** - * @return Builder with updated lockfileLocation - * */ - public Builder lockfileLocation(String lockfileLocation) { - this.lockfileLocation = lockfileLocation; - return this; - } - - /** - * Builds CachePersister with all the information passed into the Builder - * - * @return newly instantiated CachePersister - * */ - public CachePersister build() throws RuntimeException { - if (Platform.isWindows()) { - try { - return new CachePersister(new WindowsDPAPICacheProtector(cacheLocation, lockfileLocation)); - } catch (IOException e) { - throw new RuntimeException("IO Exception in creating the WindowsDPAPICacheProtector"); - } - } else { - throw new PlatformNotSupportedException("Platform is not supported"); - } - } - } -} diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/msalextensions/cachepersister/CacheProtectorBase.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/msalextensions/cachepersister/CacheProtectorBase.java deleted file mode 100644 index 453043940e0fb..0000000000000 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/msalextensions/cachepersister/CacheProtectorBase.java +++ /dev/null @@ -1,128 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.identity.implementation.msalextensions.cachepersister; - -import com.azure.core.util.logging.ClientLogger; -import com.azure.identity.implementation.msalextensions.CacheLock; -import com.azure.identity.implementation.msalextensions.CacheLockNotObtainedException; - -import java.io.IOException; - -/** - * Abstract class for Cache Protectors - * Provides methods to read and write cache while using a CacheLock - * */ -public abstract class CacheProtectorBase { - - private String lockfileLocation; - private CacheLock lock; - - private ClientLogger logger; - - /** - * Constructor - * initializes cacheLock - * */ - public CacheProtectorBase(String lockfileLocation) { - logger = new ClientLogger(CacheProtectorBase.class); - - this.lockfileLocation = lockfileLocation; - lock = new CacheLock(this.lockfileLocation); - } - - /** - * Obtains lock and uses unprotect() to read and decrypt contents of the cache - * - * @return byte[] contents of cache - * */ - public byte[] readCache() { - byte[] contents = null; - - try { - lock.lock(); - } catch (CacheLockNotObtainedException ex) { - logger.error("readCache() - Issue in locking"); - return contents; - } - - try { - contents = unprotect(); - } catch (IOException ex) { - logger.error("readCache() - Issue in reading"); - } - - lock.unlock(); - return contents; - } - - /** - * Obtains lock and uses protect() to read and encrypt contents of the cache - * - * @param data data to write to cache - * */ - public void writeCache(byte[] data) { - - try { - lock.lock(); - } catch (CacheLockNotObtainedException e) { - logger.error("writeCache() - Issue in locking"); - return; - } - - try { - protect(data); - } catch (IOException e) { - logger.error("writeCache() - Issue in writing"); - } - - lock.unlock(); - } - - /** - * Decrypts data from cache - * - * @return byte[] of cache contents - * - * Overwritten by subclasses; each OS handles differently - * */ - protected byte[] unprotect() throws IOException { - byte[] empty = {}; - return empty; - } - - /** - * Encrypts data and writes to cache - * - * @param data - * - * Overwritten by subclasses; each OS handles differently - * */ - protected void protect(byte[] data) throws IOException { - } - - /** - * Obtains lock and deletes cache using deleteCacheHelper() - * - * @return true if cache is deleted, false otherwise - * */ - public boolean deleteCache() { - try { - lock.lock(); - } catch (CacheLockNotObtainedException e) { - logger.error("deleteCache() - issue in locking"); - return false; - } - - deleteCacheHelper(); - lock.unlock(); - - return true; - } - - /** - * Overwritten by subclasses; each OS handles differently - * */ - protected void deleteCacheHelper() { - } -} diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/msalextensions/cachepersister/PlatformNotSupportedException.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/msalextensions/cachepersister/PlatformNotSupportedException.java deleted file mode 100644 index 61bab965a1d5b..0000000000000 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/msalextensions/cachepersister/PlatformNotSupportedException.java +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.identity.implementation.msalextensions.cachepersister; - -/** - * Exception for when the current OS is not supported by {@link CachePersister} - * so the OS specific DPAPI cannot be used to encrypt the token cache. - * */ -public class PlatformNotSupportedException extends RuntimeException { - - /** - * Initializes PlatformNotSupportedException - * - * @param message Error message - * */ - public PlatformNotSupportedException(String message) { - super(message); - } - -} diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/msalextensions/cachepersister/windows/WindowsDPAPICacheProtector.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/msalextensions/cachepersister/windows/WindowsDPAPICacheProtector.java deleted file mode 100644 index 97f3091ad8f39..0000000000000 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/msalextensions/cachepersister/windows/WindowsDPAPICacheProtector.java +++ /dev/null @@ -1,95 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.identity.implementation.msalextensions.cachepersister.windows; - -import com.azure.identity.implementation.msalextensions.cachepersister.CacheProtectorBase; -import com.sun.jna.platform.win32.Crypt32Util; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; - -/** - * Cache Protector for Windows which uses Windows DPAPI to encrypt the cache - * */ -public class WindowsDPAPICacheProtector extends CacheProtectorBase { - - private final String cacheFilename; - private File cacheFile; - - /** - * Constructor to initialize WindowsDPAPICacheProtector - * Calls super constructor to initialize lock - * - * @param cacheLocation - * @param lockfileLocation - * - * @throws IOException if cacheFile File isn't created - * */ - public WindowsDPAPICacheProtector(String cacheLocation, String lockfileLocation) throws IOException { - super(lockfileLocation); - cacheFilename = cacheLocation; - cacheFile = new File(cacheFilename); - - makeSureFileExists(); - } - - /** - * Uses DPAPI to read and decrypt cache contents - * - * @return byte[] cache contents - * */ - protected byte[] unprotect() throws IOException { - makeSureFileExists(); - - byte[] encryptedBytes = new byte[(int) cacheFile.length()]; - - try (FileInputStream stream = new FileInputStream(cacheFile)) { - int read = 0; - while (read != encryptedBytes.length) { - read += stream.read(encryptedBytes); - } - } - - byte[] decryptedBytes = Crypt32Util.cryptUnprotectData(encryptedBytes); - return decryptedBytes; - } - - /** - * Uses DPAPI to write and protect cache contents - * - * @param data contents to write to cache - * */ - protected void protect(byte[] data) throws IOException { - makeSureFileExists(); - - byte[] encryptedBytes = Crypt32Util.cryptProtectData(data); - - try (FileOutputStream stream = new FileOutputStream(cacheFile)) { - stream.write(encryptedBytes); - } - } - - /** - * Make sure file exists - and write " " if it was just created - * Just a backup in case the cache was deleted - * */ - private void makeSureFileExists() throws IOException { - if (!cacheFile.exists()) { - cacheFile.createNewFile(); - protect(" ".getBytes("UTF-8")); - } - } - - /** - * Deletes the cache file if it exists - * */ - public void deleteCacheHelper() { - if (cacheFile.exists()) { - cacheFile.delete(); - } - } - -} diff --git a/sdk/identity/azure-identity/src/test/java/com/azure/identity/implementation/msalextensions/CacheLockTest.java b/sdk/identity/azure-identity/src/test/java/com/azure/identity/implementation/msalextensions/CacheLockTest.java deleted file mode 100644 index f28d76a8aef3a..0000000000000 --- a/sdk/identity/azure-identity/src/test/java/com/azure/identity/implementation/msalextensions/CacheLockTest.java +++ /dev/null @@ -1,235 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.identity.implementation.msalextensions; - -import com.sun.jna.Platform; -import org.junit.*; - -import java.io.*; -import java.util.Stack; -import java.util.stream.Collectors; - -public class CacheLockTest { - - private static String folder; - private static String testerFilename; - private static String lockfile; - - @BeforeClass - public static void setup() { - // get proper file paths - String currDir = System.getProperty("user.dir"); - String home = System.getProperty("user.home"); - - java.nio.file.Path classes = java.nio.file.Paths.get(currDir, "target", "classes"); - java.nio.file.Path tests = java.nio.file.Paths.get(currDir, "target", "test-classes"); - - testerFilename = java.nio.file.Paths.get(home, "tester.txt").toString(); - lockfile = java.nio.file.Paths.get(home, "testlock.lockfile").toString(); - - String delimiter = ":"; - if (Platform.isWindows()) { - delimiter = ";"; - } - folder = classes.toString() + delimiter + tests; - } - - @Test - public void tenThreadsWritingToFile() throws IOException { - - // make sure tester.json file doesn't already exist - File tester = new File(testerFilename); - tester.delete(); - - // delete the lock file just in case before starting - File lock = new File(lockfile); - lock.delete(); - - FileWriter a = new FileWriter("a", lockfile, testerFilename); - FileWriter b = new FileWriter("b", lockfile, testerFilename); - FileWriter c = new FileWriter("c", lockfile, testerFilename); - FileWriter d = new FileWriter("d", lockfile, testerFilename); - FileWriter e = new FileWriter("e", lockfile, testerFilename); - FileWriter f = new FileWriter("f", lockfile, testerFilename); - FileWriter g = new FileWriter("g", lockfile, testerFilename); - FileWriter h = new FileWriter("h", lockfile, testerFilename); - FileWriter i = new FileWriter("i", lockfile, testerFilename); - FileWriter j = new FileWriter("j", lockfile, testerFilename); - - try { - a.t.join(); - b.t.join(); - c.t.join(); - d.t.join(); - e.t.join(); - f.t.join(); - g.t.join(); - h.t.join(); - i.t.join(); - j.t.join(); - } catch (Exception ex) { - System.out.printf("Error with threads"); - } - - Stack stack = new Stack<>(); - int popped = 0; - - File file = new File(testerFilename); - - if (file.exists()) { - FileReader reader = new FileReader(file); - BufferedReader bufferedReader = new BufferedReader(reader); - StringBuffer stringBuffer = new StringBuffer(); - String line; - while ((line = bufferedReader.readLine()) != null) { - String[] tokens = line.split(" "); - if (tokens[0].equals("<")) { // enter - stack.push(tokens[1]); - } else if (tokens[0].equals(">")) { // exit - if (stack.peek().equals(tokens[1])) { - stack.pop(); - popped++; - } else { - System.out.println("messed up: " + tokens[1]); - } - } - } - reader.close(); - - if (!stack.empty()) { - Assert.fail(); - } - } else { - Assert.fail("File does not exist"); - } - - Assert.assertEquals("10 processes didn't write", popped, 10); - - } - - @Ignore("Run local only - CI does not support classpath well") - public void tenProcessesWritingToFile() throws IOException, InterruptedException { - // make sure tester.json file doesn't already exist - File tester = new File(testerFilename); - tester.delete(); - - // delete the lock file just in case before starting - File lock = new File(lockfile); - lock.delete(); - - String mainClass = com.azure.identity.implementation.msalextensions.FileWriter.class.getName(); - Process process1 = new ProcessBuilder(new String[]{"java", "-cp", folder, mainClass, Integer.toString(1), lockfile, testerFilename}).start(); - Process process2 = new ProcessBuilder(new String[]{"java", "-cp", folder, mainClass, Integer.toString(2), lockfile, testerFilename}).start(); - Process process3 = new ProcessBuilder(new String[]{"java", "-cp", folder, mainClass, Integer.toString(3), lockfile, testerFilename}).start(); - Process process4 = new ProcessBuilder(new String[]{"java", "-cp", folder, mainClass, Integer.toString(4), lockfile, testerFilename}).start(); - Process process5 = new ProcessBuilder(new String[]{"java", "-cp", folder, mainClass, Integer.toString(5), lockfile, testerFilename}).start(); - Process process6 = new ProcessBuilder(new String[]{"java", "-cp", folder, mainClass, Integer.toString(6), lockfile, testerFilename}).start(); - Process process7 = new ProcessBuilder(new String[]{"java", "-cp", folder, mainClass, Integer.toString(7), lockfile, testerFilename}).start(); - Process process8 = new ProcessBuilder(new String[]{"java", "-cp", folder, mainClass, Integer.toString(8), lockfile, testerFilename}).start(); - Process process9 = new ProcessBuilder(new String[]{"java", "-cp", folder, mainClass, Integer.toString(9), lockfile, testerFilename}).start(); - Process process10 = new ProcessBuilder(new String[]{"java", "-cp", folder, mainClass, Integer.toString(10), lockfile, testerFilename}).start(); - - waitForProcess(process1); - waitForProcess(process2); - waitForProcess(process3); - waitForProcess(process4); - waitForProcess(process5); - waitForProcess(process6); - waitForProcess(process7); - waitForProcess(process8); - waitForProcess(process9); - waitForProcess(process10); - - Stack stack = new Stack<>(); - int popped = 0; - - File file = new File(testerFilename); - if (file.exists()) { - FileReader reader = new FileReader(file); - BufferedReader bufferedReader = new BufferedReader(reader); - StringBuffer stringBuffer = new StringBuffer(); - String line; - while ((line = bufferedReader.readLine()) != null) { - String[] tokens = line.split(" "); - if (tokens[0].equals("<")) { // enter - stack.push(tokens[1]); - } else if (tokens[0].equals(">")) { // exit - if (stack.peek().equals(tokens[1])) { - stack.pop(); - popped++; - } else { - System.out.println("messed up: " + tokens[1]); - } - } - } - reader.close(); - - if (!stack.empty()) { - Assert.fail(); - } - } else { - Assert.fail("File does not exist"); - } - - Assert.assertEquals("10 processes didn't write", popped, 10); - } - - private void waitForProcess(Process process) throws InterruptedException { - if (process.waitFor() != 0) { - throw new RuntimeException(new BufferedReader(new InputStreamReader(process.getErrorStream())) - .lines().collect(Collectors.joining("\n"))); - } - } - - /* - * Class to be used for testing threads - * */ - class FileWriter implements Runnable { - - String threadName; - File file; - String lockfile; - Thread t; - - FileWriter(String threadName, String lockfile, String filename) { - this.threadName = threadName; - this.lockfile = lockfile; - this.file = new File(filename); - - t = new Thread(this, threadName); - t.start(); - } - - public void run() { - CacheLock lock = new CacheLock(lockfile); - try { - lock.lock(); - try { - if (!file.exists()) { - file.createNewFile(); - } - FileOutputStream os = new FileOutputStream(file, true); - - os.write(("< " + threadName + "\n").getBytes()); - Thread.sleep(1000); - os.write(("> " + threadName + "\n").getBytes()); - - os.close(); - } catch (Exception ex) { - - } - } catch (Exception ex) { - System.out.println("Couldn't obtain lock"); - } finally { - try { - lock.unlock(); - } catch (Exception ex) { - System.out.println("aljsdladsk"); - } - } - - - } - } -} diff --git a/sdk/identity/azure-identity/src/test/java/com/azure/identity/implementation/msalextensions/CrossProgramVSTest.java b/sdk/identity/azure-identity/src/test/java/com/azure/identity/implementation/msalextensions/CrossProgramVSTest.java deleted file mode 100644 index 5ff72cc819083..0000000000000 --- a/sdk/identity/azure-identity/src/test/java/com/azure/identity/implementation/msalextensions/CrossProgramVSTest.java +++ /dev/null @@ -1,127 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.identity.implementation.msalextensions; - -import com.azure.identity.implementation.msalextensions.cachepersister.CachePersister; -import com.google.gson.JsonObject; -import com.google.gson.JsonParser; -import com.microsoft.aad.msal4j.ClientCredentialFactory; -import com.microsoft.aad.msal4j.ClientCredentialParameters; -import com.microsoft.aad.msal4j.ConfidentialClientApplication; -import com.microsoft.aad.msal4j.IAuthenticationResult; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; - -import java.util.Collections; -import java.util.concurrent.CompletableFuture; - -/* - * Before running these tests, log into Azure with the new Visual Studio 16.3.0 Preview 1 - * This should create a msal.cache file in the the user directory, and these tests should be able to read and - * write from the same file. - * Note that deleting this cache file will cause the user to have to re-log in with Visual Studio as this will delete - * the tokens in the cache - * - * NOTE: These tests are written assuming that nothing else has written to the MSAL cache besides visual studio - * */ -public class CrossProgramVSTest { - - CachePersister cachePersister; - PersistentTokenCacheAccessAspect accessAspect; - - private ConfidentialClientApplication confApp; - private ClientCredentialParameters confParameters; - - private int count = 0; - - @Before - public void setup() throws Exception { - org.junit.Assume.assumeTrue("Skipping these tests until we mock or record it", false); - //using the default cachepersister and accessAspect objects - cachePersister = new CachePersister.Builder().build(); - accessAspect = new PersistentTokenCacheAccessAspect(); - - confApp = ConfidentialClientApplication.builder(TestConfiguration.CONFIDENTIAL_CLIENT_ID, - ClientCredentialFactory.createFromSecret(TestConfiguration.CONFIDENTIAL_CLIENT_SECRET)) - .authority(TestConfiguration.TENANT_SPECIFIC_AUTHORITY) - .setTokenCacheAccessAspect(accessAspect) - .build(); - - confParameters = ClientCredentialParameters.builder( - Collections.singleton(TestConfiguration.GRAPH_DEFAULT_SCOPE)) - .build(); - } - - @Test - public void readCacheAfterVSAzureLogin() { - byte[] currJsonBytes = cachePersister.readCache(); - String currJson = new String(currJsonBytes); - - JsonObject jsonObj = new JsonParser().parse(currJson).getAsJsonObject(); - - Assert.assertTrue(jsonObj.has("AccessToken")); - Assert.assertTrue(jsonObj.has("RefreshToken")); - Assert.assertTrue(jsonObj.has("IdToken")); - Assert.assertTrue(jsonObj.has("Account")); - Assert.assertTrue(jsonObj.has("AppMetadata")); - - System.out.println(currJson); - - count = jsonObj.get("AccessToken").getAsJsonObject().keySet().size(); - } - - @Test - public void writeToSameCacheFileAfterVSAzureLogin() { - String currJson = new String(cachePersister.readCache()); - JsonObject jsonObj = new JsonParser().parse(currJson).getAsJsonObject(); - - int set = jsonObj.get("AccessToken").getAsJsonObject().keySet().size(); - - CompletableFuture result = confApp.acquireToken(confParameters); - result.handle((res, ex) -> { - if (ex != null) { - System.out.println("Oops! We have an exception - " + ex.getMessage()); - return "Unknown!"; - } - return res; - - }).join(); - - currJson = new String(cachePersister.readCache()); - jsonObj = new JsonParser().parse(currJson).getAsJsonObject(); - - int newSet = jsonObj.get("AccessToken").getAsJsonObject().keySet().size(); - - Assert.assertEquals(newSet, set + 1); - count++; - - System.out.println(currJson); - } - - @Test - public void countCache() { - byte[] currJsonBytes = cachePersister.readCache(); - String currJson = new String(currJsonBytes); - - JsonObject jsonObj = new JsonParser().parse(currJson).getAsJsonObject(); - int newSet = jsonObj.get("AccessToken").getAsJsonObject().keySet().size(); - System.out.println(newSet); - } - - @Test - public void readCacheAfterPowershellAzureLogin() { - byte[] currJsonBytes = cachePersister.readCache(); - String currJson = new String(currJsonBytes); - - JsonObject jsonObj = new JsonParser().parse(currJson).getAsJsonObject(); - System.out.println(currJson); - - int newSet = jsonObj.get("AccessToken").getAsJsonObject().keySet().size(); - - Assert.assertEquals(newSet, 6); - count++; - } - -} diff --git a/sdk/identity/azure-identity/src/test/java/com/azure/identity/implementation/msalextensions/FileWriter.java b/sdk/identity/azure-identity/src/test/java/com/azure/identity/implementation/msalextensions/FileWriter.java deleted file mode 100644 index 17dd249c7493f..0000000000000 --- a/sdk/identity/azure-identity/src/test/java/com/azure/identity/implementation/msalextensions/FileWriter.java +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.identity.implementation.msalextensions; - -import java.io.File; -import java.io.FileOutputStream; - -public class FileWriter { - - public static void main(String[] args) throws Exception { - File file; - String lockfile; - - if (args.length == 3) { - lockfile = args[1]; - file = new File(args[2]); - } else { - System.out.println("wrong number of args lol????"); - return; - } - CacheLock lock = new CacheLock(lockfile); - - int retries = 3; - boolean succeeded = false; - while (retries-- > 0 && !succeeded) { - try { - lock.lock(); - - if (!file.exists()) { - file.createNewFile(); - } - FileOutputStream os = new FileOutputStream(file, true); - - os.write(("< " + args[0] + "\n").getBytes()); - Thread.sleep(1000); - os.write(("> " + args[0] + "\n").getBytes()); - - os.close(); - succeeded = true; - } finally { - lock.unlock(); - } - } - - } -} diff --git a/sdk/identity/azure-identity/src/test/java/com/azure/identity/implementation/msalextensions/MsalCacheStorageTest.java b/sdk/identity/azure-identity/src/test/java/com/azure/identity/implementation/msalextensions/MsalCacheStorageTest.java deleted file mode 100644 index 90193076612eb..0000000000000 --- a/sdk/identity/azure-identity/src/test/java/com/azure/identity/implementation/msalextensions/MsalCacheStorageTest.java +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.identity.implementation.msalextensions; - -import com.azure.identity.implementation.msalextensions.cachepersister.CachePersister; -import com.sun.jna.Platform; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; - -import java.io.File; - -public class MsalCacheStorageTest { - - private CachePersister cachePersister; - private String cacheLocation; - - @Before - public void setup() throws Exception { - org.junit.Assume.assumeTrue(Platform.isWindows()); - cacheLocation = java.nio.file.Paths.get(System.getProperty("user.home"), "test.cache").toString(); - cachePersister = new CachePersister.Builder() - .cacheLocation(cacheLocation) - .lockfileLocation(cacheLocation + ".lockfile") - .build(); - } - - @Test - public void writesReadsCacheData() { - try { - File f = new File(cacheLocation); - - String testString = "hello world"; - - cachePersister.writeCache(testString.getBytes()); - String receivedString = new String(cachePersister.readCache()); - - Assert.assertEquals(receivedString, testString); - - cachePersister.deleteCache(); - } finally { - cachePersister.deleteCache(); - } - } - -} diff --git a/sdk/identity/azure-identity/src/test/java/com/azure/identity/implementation/msalextensions/MultithreadedTokenCacheTest.java b/sdk/identity/azure-identity/src/test/java/com/azure/identity/implementation/msalextensions/MultithreadedTokenCacheTest.java deleted file mode 100644 index 667b295314ed9..0000000000000 --- a/sdk/identity/azure-identity/src/test/java/com/azure/identity/implementation/msalextensions/MultithreadedTokenCacheTest.java +++ /dev/null @@ -1,193 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.identity.implementation.msalextensions; - -import com.azure.identity.implementation.msalextensions.cachepersister.CachePersister; -import com.google.gson.JsonObject; -import com.google.gson.JsonParser; -import com.microsoft.aad.msal4j.*; -import org.junit.After; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; - -import java.util.Collections; -import java.util.concurrent.CompletableFuture; -import java.util.function.Consumer; - -public class MultithreadedTokenCacheTest { - - private PersistentTokenCacheAccessAspect accessAspect; - private CachePersister cachePersister; - - private ConfidentialClientApplication confApp; - private ConfidentialClientApplication confApp2; - private PublicClientApplication pubApp; - private ClientCredentialParameters confParameters; - private DeviceCodeFlowParameters pubParameters; - - @Before - public void setup() throws Exception { - org.junit.Assume.assumeTrue("Skipping these tests until we mock or record it", false); - // custom MsalCacheStorage for testing purposes so we don't overwrite the real one - cachePersister = new CachePersister.Builder() - .cacheLocation(java.nio.file.Paths.get(System.getProperty("user.home"), "test.cache").toString()) - .build(); - - accessAspect = new PersistentTokenCacheAccessAspect(cachePersister); - - confApp = ConfidentialClientApplication.builder(TestConfiguration.CONFIDENTIAL_CLIENT_ID, - ClientCredentialFactory.createFromSecret(TestConfiguration.CONFIDENTIAL_CLIENT_SECRET)) - .authority(TestConfiguration.TENANT_SPECIFIC_AUTHORITY) - .setTokenCacheAccessAspect(accessAspect) - .build(); - - confApp2 = ConfidentialClientApplication.builder(TestConfiguration.CONFIDENTIAL_CLIENT_ID_2, - ClientCredentialFactory.createFromSecret(TestConfiguration.CONFIDENTIAL_CLIENT_SECRET_2)) - .authority(TestConfiguration.TENANT_SPECIFIC_AUTHORITY) - .setTokenCacheAccessAspect(accessAspect) - .build(); - - confParameters = ClientCredentialParameters.builder( - Collections.singleton(TestConfiguration.GRAPH_DEFAULT_SCOPE)) - .build(); - - - pubApp = PublicClientApplication.builder(TestConfiguration.PUBLIC_CLIENT_ID) - .authority(TestConfiguration.TENANT_SPECIFIC_AUTHORITY) - .setTokenCacheAccessAspect(accessAspect) - .build(); - - Consumer deviceCodeConsumer = (DeviceCode deviceCode) -> System.out.println(deviceCode.message()); - - pubParameters = DeviceCodeFlowParameters.builder( - Collections.singleton(TestConfiguration.GRAPH_DEFAULT_SCOPE), - deviceCodeConsumer) - .build(); - } - - @After - public void cleanup() { - if (accessAspect != null) { - accessAspect.deleteCache(); - } - } - - @Test - public void twoThreadsWritingTokens() { - - ConcurrentClient a = new ConcurrentClient("conf"); - ConcurrentClient b = new ConcurrentClient("pub"); - - try { - a.t.join(); - b.t.join(); - } catch (Exception e) { - System.out.printf("Error with threads"); - } - - byte[] currJsonBytes = cachePersister.readCache(); - String currJson = new String(currJsonBytes); - - JsonObject jsonObj = new JsonParser().parse(currJson).getAsJsonObject(); - - Assert.assertTrue(jsonObj.get("AccessToken").getAsJsonObject().keySet().size() == 2); - Assert.assertTrue(jsonObj.get("RefreshToken").getAsJsonObject().keySet().size() == 1); - Assert.assertTrue(jsonObj.get("IdToken").getAsJsonObject().keySet().size() == 1); - Assert.assertTrue(jsonObj.get("Account").getAsJsonObject().keySet().size() == 1); - Assert.assertTrue(jsonObj.get("AppMetadata").getAsJsonObject().keySet().size() == 1); - - accessAspect.deleteCache(); - } - - @Test - public void tenThreadsWritingSameConfTokens() { - - ConcurrentClient a = new ConcurrentClient("conf"); - ConcurrentClient b = new ConcurrentClient("conf"); - ConcurrentClient c = new ConcurrentClient("conf"); - ConcurrentClient d = new ConcurrentClient("conf"); - ConcurrentClient e = new ConcurrentClient("conf"); - ConcurrentClient f = new ConcurrentClient("conf"); - ConcurrentClient g = new ConcurrentClient("conf"); - ConcurrentClient h = new ConcurrentClient("conf"); - ConcurrentClient i = new ConcurrentClient("conf"); - ConcurrentClient j = new ConcurrentClient("conf"); - - try { - a.t.join(); - b.t.join(); - c.t.join(); - d.t.join(); - e.t.join(); - f.t.join(); - g.t.join(); - h.t.join(); - i.t.join(); - j.t.join(); - } catch (Exception ex) { - System.out.printf("Error with threads"); - } - - - byte[] currJsonBytes = cachePersister.readCache(); - String currJson = new String(currJsonBytes); - - JsonObject jsonObj = new JsonParser().parse(currJson).getAsJsonObject(); - - - System.out.println("keys: " + jsonObj.get("AccessToken").getAsJsonObject().keySet().size()); - - Assert.assertTrue(jsonObj.get("AccessToken").getAsJsonObject().keySet().size() == 1); - Assert.assertTrue(jsonObj.get("RefreshToken").getAsJsonObject().keySet().size() == 0); - Assert.assertTrue(jsonObj.get("IdToken").getAsJsonObject().keySet().size() == 0); - Assert.assertTrue(jsonObj.get("Account").getAsJsonObject().keySet().size() == 0); - Assert.assertTrue(jsonObj.get("AppMetadata").getAsJsonObject().keySet().size() == 0); - - accessAspect.deleteCache(); - } - - class ConcurrentClient implements Runnable { - - String threadName; - Thread t; - - ConcurrentClient(String threadName) { - this.threadName = threadName; - t = new Thread(this, threadName); - t.start(); - } - - public void run() { - - if (threadName.equals("conf")) { - CompletableFuture result = confApp.acquireToken(confParameters); - - result.handle((res, ex) -> { - if (ex != null) { - System.out.println("Oops! We have an exception 1 - " + ex.getMessage()); - return "Unknown!"; - } - return res; - - }).join(); - } else if (threadName.equals("pub")) { - - CompletableFuture result = pubApp.acquireToken( - pubParameters); - - result.handle((res, ex) -> { - if (ex != null) { - System.out.println("Oops! We have an exception 2 - " + ex.getMessage()); - return "Unknown!"; - } - - return res; - - }).join(); - } - } - - } -} diff --git a/sdk/identity/azure-identity/src/test/java/com/azure/identity/implementation/msalextensions/PersistentTokenCacheAccessAspectTest.java b/sdk/identity/azure-identity/src/test/java/com/azure/identity/implementation/msalextensions/PersistentTokenCacheAccessAspectTest.java deleted file mode 100644 index b9afcbaaed4f4..0000000000000 --- a/sdk/identity/azure-identity/src/test/java/com/azure/identity/implementation/msalextensions/PersistentTokenCacheAccessAspectTest.java +++ /dev/null @@ -1,267 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.identity.implementation.msalextensions; - -import com.azure.identity.implementation.msalextensions.cachepersister.CachePersister; -import com.google.gson.JsonObject; -import com.google.gson.JsonParser; -import com.microsoft.aad.msal4j.*; -import org.junit.After; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; - -import java.util.Collections; -import java.util.concurrent.CompletableFuture; -import java.util.function.Consumer; - -public class PersistentTokenCacheAccessAspectTest { - - private PersistentTokenCacheAccessAspect accessAspect; - private CachePersister cachePersister; - - private ConfidentialClientApplication confApp; - private ConfidentialClientApplication confApp2; - private PublicClientApplication pubApp; - private ClientCredentialParameters confParameters; - private DeviceCodeFlowParameters pubParameters; - - @Before - public void setup() throws Exception { - org.junit.Assume.assumeTrue("Skipping these tests until we mock or record it", false); - // custom MsalCacheStorage for testing purposes so we don't overwrite the real one - cachePersister = new CachePersister.Builder() - .cacheLocation(java.nio.file.Paths.get(System.getProperty("user.home"), "test.cache").toString()) - .build(); - - accessAspect = new PersistentTokenCacheAccessAspect(cachePersister); - - Consumer deviceCodeConsumer = (DeviceCode deviceCode) -> { - System.out.println(deviceCode.message()); - }; - - confApp = ConfidentialClientApplication.builder(TestConfiguration.CONFIDENTIAL_CLIENT_ID, - ClientCredentialFactory.createFromSecret(TestConfiguration.CONFIDENTIAL_CLIENT_SECRET)) - .authority(TestConfiguration.TENANT_SPECIFIC_AUTHORITY) - .setTokenCacheAccessAspect(accessAspect) - .build(); - - confApp2 = ConfidentialClientApplication.builder(TestConfiguration.CONFIDENTIAL_CLIENT_ID_2, - ClientCredentialFactory.createFromSecret(TestConfiguration.CONFIDENTIAL_CLIENT_SECRET_2)) - .authority(TestConfiguration.TENANT_SPECIFIC_AUTHORITY) - .setTokenCacheAccessAspect(accessAspect) - .build(); - - pubApp = PublicClientApplication.builder(TestConfiguration.PUBLIC_CLIENT_ID) - .authority(TestConfiguration.TENANT_SPECIFIC_AUTHORITY) - .setTokenCacheAccessAspect(accessAspect) - .build(); - - confParameters = ClientCredentialParameters.builder( - Collections.singleton(TestConfiguration.GRAPH_DEFAULT_SCOPE)) - .build(); - - pubParameters = DeviceCodeFlowParameters.builder( - Collections.singleton(TestConfiguration.GRAPH_DEFAULT_SCOPE), - deviceCodeConsumer) - .build(); - } - - @After - public void cleanup() { - if (accessAspect != null) { - accessAspect.deleteCache(); - } - } - - @Test - public void checkIfWritesToFileFirstTimeConfidentialClient() { - - CompletableFuture result = confApp.acquireToken(confParameters); - - result.handle((res, ex) -> { - if (ex != null) { - System.out.println("Oops! We have an exception - " + ex.getMessage()); - return "Unknown!"; - } - return res; - - }).join(); - - byte[] currJsonBytes = cachePersister.readCache(); - String currJson = new String(currJsonBytes); - - JsonObject jsonObj = new JsonParser().parse(currJson).getAsJsonObject(); - - Assert.assertTrue(jsonObj.has("AccessToken")); - Assert.assertTrue(jsonObj.has("RefreshToken")); - Assert.assertTrue(jsonObj.has("IdToken")); - Assert.assertTrue(jsonObj.has("Account")); - Assert.assertTrue(jsonObj.has("AppMetadata")); - - int set = jsonObj.get("AccessToken").getAsJsonObject().keySet().size(); - - Assert.assertEquals(set, 1); - - accessAspect.deleteCache(); - } - - @Test - public void checkIfWritesToFileFirstTimePublicClient() { - - CompletableFuture result = pubApp.acquireToken( - pubParameters); - - result.handle((res, ex) -> { - if (ex != null) { - System.out.println("Oops! We have an exception - " + ex.getMessage()); - return "Unknown!"; - } - return res; - - }).join(); - - byte[] currJsonBytes = cachePersister.readCache(); - String currJson = new String(currJsonBytes); - - JsonObject jsonObj = new JsonParser().parse(currJson).getAsJsonObject(); - - Assert.assertTrue(jsonObj.has("AccessToken")); - Assert.assertTrue(jsonObj.has("RefreshToken")); - Assert.assertTrue(jsonObj.has("IdToken")); - Assert.assertTrue(jsonObj.has("Account")); - Assert.assertTrue(jsonObj.has("AppMetadata")); - - Assert.assertTrue(jsonObj.get("AccessToken").getAsJsonObject().keySet().size() == 1); - - accessAspect.deleteCache(); - } - - @Test - public void addsAccountToListPubClient() { - - CompletableFuture result = pubApp.acquireToken( - pubParameters); - - result.handle((res, ex) -> { - if (ex != null) { - System.out.println("Oops! We have an exception - " + ex.getMessage()); - return "Unknown!"; - } - - return res; - - }).join(); - - Assert.assertEquals(pubApp.getAccounts().join().size(), 1); - - accessAspect.deleteCache(); - } - - @Test - public void writesTwoTokensToCache() { - CompletableFuture result = pubApp.acquireToken( - pubParameters); - - result.handle((res, ex) -> { - if (ex != null) { - System.out.println("Oops! We have an exception 1 - " + ex.getMessage()); - return "Unknown!"; - } - - return res; - - }).join(); - - CompletableFuture result2 = confApp.acquireToken(confParameters); - - result2.handle((res, ex) -> { - if (ex != null) { - System.out.println("Oops! We have an exception 2 - " + ex.getMessage()); - return "Unknown!"; - } - return res; - - }).join(); - - byte[] currJsonBytes = cachePersister.readCache(); - String currJson = new String(currJsonBytes); - - JsonObject jsonObj = new JsonParser().parse(currJson).getAsJsonObject(); - - Assert.assertTrue(jsonObj.get("AccessToken").getAsJsonObject().keySet().size() == 2); - Assert.assertTrue(jsonObj.get("RefreshToken").getAsJsonObject().keySet().size() == 1); - Assert.assertTrue(jsonObj.get("IdToken").getAsJsonObject().keySet().size() == 1); - Assert.assertTrue(jsonObj.get("Account").getAsJsonObject().keySet().size() == 1); - Assert.assertTrue(jsonObj.get("AppMetadata").getAsJsonObject().keySet().size() == 1); - - accessAspect.deleteCache(); - } - - @Test - public void writesReadsMultipleTokensToCache() { - CompletableFuture result = pubApp.acquireToken( - pubParameters); - - result.handle((res, ex) -> { - if (ex != null) { - System.out.println("Oops! We have an exception 1 - " + ex.getMessage()); - return "Unknown!"; - } - return res; - - }).join(); - - CompletableFuture result2 = confApp.acquireToken(confParameters); - - result2.handle((res, ex) -> { - if (ex != null) { - System.out.println("Oops! We have an exception 2 - " + ex.getMessage()); - return "Unknown!"; - } - return res; - - }).join(); - - CompletableFuture result3 = confApp2.acquireToken(confParameters); - - result3.handle((res, ex) -> { - if (ex != null) { - System.out.println("Oops! We have an exception 3 - " + ex.getMessage()); - return "Unknown!"; - } - return res; - - }).join(); - - byte[] currJsonBytes = cachePersister.readCache(); - String currJson = new String(currJsonBytes); - - JsonObject jsonObj = new JsonParser().parse(currJson).getAsJsonObject(); - - Assert.assertTrue(jsonObj.get("AccessToken").getAsJsonObject().keySet().size() == 3); - Assert.assertTrue(jsonObj.get("RefreshToken").getAsJsonObject().keySet().size() == 1); - Assert.assertTrue(jsonObj.get("IdToken").getAsJsonObject().keySet().size() == 1); - Assert.assertTrue(jsonObj.get("Account").getAsJsonObject().keySet().size() == 1); - Assert.assertTrue(jsonObj.get("AppMetadata").getAsJsonObject().keySet().size() == 1); - - accessAspect.deleteCache(); - } - - @Test - public void syncsCacheWithExpiredTokens() { - CompletableFuture result3 = confApp2.acquireToken(confParameters); - - result3.handle((res, ex) -> { - if (ex != null) { - System.out.println("Oops! We have an exception 3 - " + ex.getMessage()); - return "Unknown!"; - } - return res; - - }).join(); - - accessAspect.deleteCache(); - } -} diff --git a/sdk/identity/azure-identity/src/test/java/com/azure/identity/implementation/msalextensions/TestConfiguration.java b/sdk/identity/azure-identity/src/test/java/com/azure/identity/implementation/msalextensions/TestConfiguration.java deleted file mode 100644 index 8841df4f0557a..0000000000000 --- a/sdk/identity/azure-identity/src/test/java/com/azure/identity/implementation/msalextensions/TestConfiguration.java +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.identity.implementation.msalextensions; - -public class TestConfiguration { - - static final String TENANT_SPECIFIC_AUTHORITY = "https://login.microsoftonline.com/[insert here]/"; - - static final String PUBLIC_CLIENT_ID = ""; - - static final String GRAPH_DEFAULT_SCOPE = "https://graph.windows.net/.default"; - - static final String CONFIDENTIAL_CLIENT_ID = ""; - static final String CONFIDENTIAL_CLIENT_ID_2 = ""; - - static final String CONFIDENTIAL_CLIENT_SECRET = ""; - static final String CONFIDENTIAL_CLIENT_SECRET_2 = ""; -} diff --git a/sdk/keyvault/azure-security-keyvault-secrets/pom.xml b/sdk/keyvault/azure-security-keyvault-secrets/pom.xml index b4f0e65f43bfc..0349c835061d1 100644 --- a/sdk/keyvault/azure-security-keyvault-secrets/pom.xml +++ b/sdk/keyvault/azure-security-keyvault-secrets/pom.xml @@ -99,7 +99,7 @@ com.azure azure-identity - 1.0.4 + 1.1.0-beta.3 test From 132c01a578726cf3c8227a80e6bc38ba34171470 Mon Sep 17 00:00:00 2001 From: Jianghao Lu Date: Wed, 18 Mar 2020 00:27:29 -0700 Subject: [PATCH 02/45] Initial draft of shared token cache for Mac and Linux --- .../identity/DefaultAzureCredential.java | 25 ++- .../com/azure/identity/KeyRingItemSchema.java | 22 +++ .../identity/SharedTokenCacheCredential.java | 8 +- .../SharedTokenCacheCredentialBuilder.java | 150 +++++++++++++++++- .../implementation/IdentityClient.java | 8 +- .../implementation/IdentityClientOptions.java | 11 +- .../secrets/PersistentTokenCacheDemo.java | 8 +- 7 files changed, 215 insertions(+), 17 deletions(-) create mode 100644 sdk/identity/azure-identity/src/main/java/com/azure/identity/KeyRingItemSchema.java diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/DefaultAzureCredential.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/DefaultAzureCredential.java index 32f3fe297b878..a1855d3468a8e 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/DefaultAzureCredential.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/DefaultAzureCredential.java @@ -5,7 +5,11 @@ import com.azure.core.annotation.Immutable; import com.azure.identity.implementation.IdentityClientOptions; +import com.microsoft.aad.msal4jextensions.PersistenceSettings; +import com.sun.jna.Platform; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.ArrayDeque; import java.util.Arrays; @@ -23,6 +27,18 @@ */ @Immutable public final class DefaultAzureCredential extends ChainedTokenCredential { + private static final String DEFAULT_CACHE_FILE_NAME = "msal.cache"; + private static final Path DEFAULT_CACHE_DIRECTORY = Platform.isWindows() ? + Paths.get(System.getProperty("user.home"), "AppData", "Local", ".IdentityService", "msal.cache") : + Paths.get(System.getProperty("user.home"),".IdentityService", "msal.cache"); + private static final String DEFAULT_KEYCHAIN_SERVICE = "Microsoft.Developer.IdentityService"; + private static final String DEFAULT_KEYCHAIN_ACCOUNT = "MSALCache"; + private static final String DEFAULT_KEYRING_NAME = "default"; + private static final KeyRingItemSchema DEFAULT_KEYRING_SCHEMA = KeyRingItemSchema.GENERIC_SECRET; + private static final String DEFAULT_KEYRING_ITEM_NAME = DEFAULT_KEYCHAIN_ACCOUNT; + private static final String DEFAULT_KEYRING_ATTR_NAME = "MsalClientID"; + private static final String DEFAULT_KEYRING_ATTR_VALUE = "Microsoft.Developer.IdentityService"; + /** * Creates default DefaultAzureCredential instance to use. This will use AZURE_CLIENT_ID, @@ -37,8 +53,13 @@ public final class DefaultAzureCredential extends ChainedTokenCredential { DefaultAzureCredential(IdentityClientOptions identityClientOptions) { super(new ArrayDeque<>(Arrays.asList(new EnvironmentCredential(identityClientOptions), new ManagedIdentityCredential(null, identityClientOptions), - new SharedTokenCacheCredential(null, null, "04b07795-8ddb-461a-bbee-02f9e1bf7b46", - identityClientOptions), + new SharedTokenCacheCredential(null, "04b07795-8ddb-461a-bbee-02f9e1bf7b46", null, + identityClientOptions, PersistenceSettings.builder(DEFAULT_CACHE_FILE_NAME, DEFAULT_CACHE_DIRECTORY) + .setMacKeychain(DEFAULT_KEYCHAIN_SERVICE, DEFAULT_KEYCHAIN_ACCOUNT) + .setLinuxKeyring(DEFAULT_KEYRING_NAME, DEFAULT_KEYRING_SCHEMA.toString(), DEFAULT_KEYRING_ITEM_NAME, + DEFAULT_KEYRING_ATTR_NAME, DEFAULT_KEYRING_ATTR_VALUE, null, null) + // TODO: Check if libsecret is installed for Linux and use unprotected file cache if not + .build()), new AzureCliCredential(identityClientOptions)))); } } diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/KeyRingItemSchema.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/KeyRingItemSchema.java new file mode 100644 index 0000000000000..b7680e41ec4a1 --- /dev/null +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/KeyRingItemSchema.java @@ -0,0 +1,22 @@ +package com.azure.identity; + +public final class KeyRingItemSchema { + public static final KeyRingItemSchema GENERIC_SECRET = new KeyRingItemSchema("org.freedesktop.Secret.Generic"); + public static final KeyRingItemSchema NETWORK_PASSWORD = new KeyRingItemSchema("org.gnome.keyring.NetworkPassword"); + public static final KeyRingItemSchema NOTE = new KeyRingItemSchema("org.gnome.keyring.Note"); + + private final String value; + + private KeyRingItemSchema(String value) { + this.value = value; + } + + public static KeyRingItemSchema fromString(String schema) { + return new KeyRingItemSchema(schema); + } + + @Override + public String toString() { + return value; + } +} diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/SharedTokenCacheCredential.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/SharedTokenCacheCredential.java index 45ab02f36275b..8c59e2fcb5bb5 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/SharedTokenCacheCredential.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/SharedTokenCacheCredential.java @@ -17,7 +17,6 @@ import reactor.core.publisher.Mono; import java.net.MalformedURLException; -import java.nio.file.Paths; import java.time.ZoneOffset; import java.util.HashMap; import java.util.HashSet; @@ -35,6 +34,7 @@ public class SharedTokenCacheCredential implements TokenCredential { private final String clientId; private final String tenantId; private final IdentityClientOptions options; + private final PersistenceSettings persistenceSettings; private PublicClientApplication pubClient = null; @@ -46,7 +46,7 @@ public class SharedTokenCacheCredential implements TokenCredential { * @param identityClientOptions the options for configuring the identity client */ SharedTokenCacheCredential(String username, String clientId, String tenantId, - IdentityClientOptions identityClientOptions) { + IdentityClientOptions identityClientOptions, PersistenceSettings persistenceSettings) { Configuration configuration = Configuration.getGlobalConfiguration().clone(); if (username == null) { @@ -66,6 +66,7 @@ public class SharedTokenCacheCredential implements TokenCredential { this.tenantId = tenantId; } this.options = identityClientOptions; + this.persistenceSettings = persistenceSettings; } /** @@ -77,9 +78,6 @@ public Mono getToken(TokenRequestContext request) { // Initialize here so that the constructor doesn't throw if (pubClient == null) { try { - PersistenceSettings persistenceSettings = PersistenceSettings.builder("msal.cache", Paths.get(System.getProperty("user.home"), "AppData", "Local", ".IdentityService")) - .setMacKeychain("Microsoft.Developer.IdentityService", "MSALCache") - .build(); PersistenceTokenCacheAccessAspect accessAspect = new PersistenceTokenCacheAccessAspect(persistenceSettings); PublicClientApplication.Builder applicationBuilder = PublicClientApplication.builder(this.clientId); if (options.getExecutorService() != null) { diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/SharedTokenCacheCredentialBuilder.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/SharedTokenCacheCredentialBuilder.java index 9270fcb60d5a6..4871eacb50c2f 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/SharedTokenCacheCredentialBuilder.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/SharedTokenCacheCredentialBuilder.java @@ -3,6 +3,15 @@ package com.azure.identity; +import com.azure.identity.implementation.util.ValidationUtil; +import com.microsoft.aad.msal4jextensions.PersistenceSettings; + +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; + /** * Fluent credential builder for instantiating a {@link SharedTokenCacheCredential}. * @@ -10,7 +19,14 @@ */ public class SharedTokenCacheCredentialBuilder extends AadCredentialBuilderBase { private String username; - + private Path cacheFileLocation; + private String keyChainService; + private String keyChainAccount; + private String keyRingName; + private KeyRingItemSchema keyRingItemSchema; + private String keyRingItemName; + private LinkedHashMap attributes = new LinkedHashMap<>(); // preserve order + private boolean useUnprotectedFileOnLinux = false; /** * Sets the username for the account. @@ -24,12 +40,142 @@ public SharedTokenCacheCredentialBuilder username(String username) { return this; } + /** + * Sets the location for the token cache file on Windows or Linux systems. The default + * location is {user home}/AppData/Local/.IdentityService/msal.cache on + * Windows and ~/.IdentityService/msal.cache on Linux. + * + * @param cacheFileLocation The location for the token cache file. + * + * @return The updated SharedTokenCacheCredentialBuilder object. + */ + public SharedTokenCacheCredentialBuilder cacheFileLocation(Path cacheFileLocation) { + this.cacheFileLocation = cacheFileLocation; + return this; + } + + /** + * Sets the service name for the Keychain item on MacOS. The default value is + * "Microsoft.Developer.IdentityService". + * + * @param serviceName The service name for the Keychain item. + * + * @return The updated SharedTokenCacheCredentialBuilder object. + */ + public SharedTokenCacheCredentialBuilder keyChainService(String serviceName) { + this.keyChainService = serviceName; + return this; + } + + /** + * Sets the account name for the Keychain item on MacOS. The default value is + * "MSALCache". + * + * @param accountName The account name for the Keychain item. + * + * @return The updated SharedTokenCacheCredentialBuilder object. + */ + public SharedTokenCacheCredentialBuilder keyChainAccount(String accountName) { + this.keyChainAccount = accountName; + return this; + } + + /** + * Sets the name of the Gnome keyring to store the cache on Gnome keyring enabled + * Linux systems. The default value is "default". + * + * @param keyRingName The name of the Gnome keyring. + * + * @return The updated SharedTokenCacheCredentialBuilder object. + */ + public SharedTokenCacheCredentialBuilder keyRingName(String keyRingName) { + this.keyRingName = keyRingName; + return this; + } + + /** + * Sets the schema of the Gnome keyring to store the cache on Gnome keyring enabled + * Linux systems. The default value is KeyRingItemSchema.GenericSecret. + * + * @param keyRingItemSchema The schema of the Gnome keyring. + * + * @return The updated SharedTokenCacheCredentialBuilder object. + */ + public SharedTokenCacheCredentialBuilder keyRingItemSchema(KeyRingItemSchema keyRingItemSchema) { + this.keyRingItemSchema = keyRingItemSchema; + return this; + } + + /** + * Sets the name of the Gnome keyring item to store the cache on Gnome keyring enabled + * Linux systems. The default value is "MSALCache". + * + * @param keyRingItemName The name of the Gnome keyring item. + * + * @return The updated SharedTokenCacheCredentialBuilder object. + */ + public SharedTokenCacheCredentialBuilder keyRingItemName(String keyRingItemName) { + this.keyRingItemName = keyRingItemName; + return this; + } + + /** + * Adds an attribute to the Gnome keyring item to store the cache on Gnome keyring enabled + * Linux systems. Only 2 attributes are allowed. + * + * @param attributeName The name of the attribute. + * @param attributeValue The value of the attribute. + * + * @return The updated SharedTokenCacheCredentialBuilder object. + */ + public SharedTokenCacheCredentialBuilder addKeyRingItemAttribute(String attributeName, String attributeValue) { + if (this.attributes.size() < 2) { + this.attributes.put(attributeName, attributeValue); + } else { + throw new IllegalArgumentException("Currently does not support more than 2 attributes for linux KeyRing"); + } + return this; + } + + /** + * Sets whether to use an unprotected file specified by cacheFileLocation() instead of + * Gnome keyring on Linux. This is false by default. + * + * @param useUnprotectedFileOnLinux whether to use an unprotected file for cache storage. + * + * @return The updated SharedTokenCacheCredentialBuilder object. + */ + public SharedTokenCacheCredentialBuilder useUnprotectedFileOnLinux(boolean useUnprotectedFileOnLinux) { + this.useUnprotectedFileOnLinux = useUnprotectedFileOnLinux; + return this; + } + /** * Creates a new {@link SharedTokenCacheCredentialBuilder} with the current configurations. * * @return a {@link SharedTokenCacheCredentialBuilder} with the current configurations. */ public SharedTokenCacheCredential build() { - return new SharedTokenCacheCredential(username, clientId, tenantId, identityClientOptions); + ValidationUtil.validate(getClass().getSimpleName(), new HashMap() {{ + put("cacheFileLocation", cacheFileLocation); + }}); + List attributeKeyList = new ArrayList<>(attributes.keySet()); + while (attributeKeyList.size() < 2) { + attributeKeyList.add(null); + } + String key1 = attributeKeyList.get(0); + String key2 = attributeKeyList.get(1); + PersistenceSettings.Builder persistenceSettings = PersistenceSettings.builder( + cacheFileLocation.getFileName().toString(), cacheFileLocation.getParent()) + .setLinuxUseUnprotectedFileAsCacheStorage(useUnprotectedFileOnLinux); + if (keyChainService != null && keyChainAccount != null) { + persistenceSettings.setMacKeychain(keyChainService, keyChainAccount); + } + if (keyRingName != null && keyRingItemName != null && keyRingItemSchema != null) { + persistenceSettings.setLinuxKeyring(keyRingName, keyRingItemSchema.toString(), keyRingItemName, + key1, attributes.getOrDefault(key1, null), key2, attributes.getOrDefault(key2, null)); + } + return new SharedTokenCacheCredential( + username, clientId, tenantId, identityClientOptions, persistenceSettings.build()); } } diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java index 1b3a8e779f4fe..889015587c74b 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java @@ -30,6 +30,7 @@ import com.microsoft.aad.msal4j.PublicClientApplication; import com.microsoft.aad.msal4j.SilentParameters; import com.microsoft.aad.msal4j.UserNamePasswordParameters; +import com.microsoft.aad.msal4jextensions.PersistenceTokenCacheAccessAspect; import reactor.core.publisher.Mono; import reactor.core.scheduler.Schedulers; @@ -111,8 +112,11 @@ public class IdentityClient { String authorityUrl = options.getAuthorityHost().replaceAll("/+$", "") + "/organizations/" + tenantId; PublicClientApplication.Builder publicClientApplicationBuilder = PublicClientApplication.builder(clientId); try { - publicClientApplicationBuilder = publicClientApplicationBuilder.authority(authorityUrl); - } catch (MalformedURLException e) { + publicClientApplicationBuilder = publicClientApplicationBuilder + .authority(authorityUrl) + .setTokenCacheAccessAspect( + new PersistenceTokenCacheAccessAspect(options.getCachePersistenceSettings())); + } catch (IOException e) { throw logger.logExceptionAsWarning(new IllegalStateException(e)); } diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClientOptions.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClientOptions.java index 65efc05e527a4..9d79e727bb384 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClientOptions.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClientOptions.java @@ -6,6 +6,7 @@ import com.azure.core.http.HttpClient; import com.azure.core.http.HttpPipeline; import com.azure.core.http.ProxyOptions; +import com.microsoft.aad.msal4jextensions.PersistenceSettings; import java.time.Duration; import java.util.Objects; @@ -19,7 +20,6 @@ public final class IdentityClientOptions { private static final String DEFAULT_AUTHORITY_HOST = "https://login.microsoftonline.com/"; private static final int MAX_RETRY_DEFAULT_LIMIT = 3; - private String authorityHost; private int maxRetry; private Function retryTimeout; @@ -28,6 +28,7 @@ public final class IdentityClientOptions { private ExecutorService executorService; private Duration tokenRefreshOffset = Duration.ofMinutes(2); private HttpClient httpClient; + private PersistenceSettings cachePersistenceSettings; /** * Creates an instance of IdentityClientOptions with default settings. @@ -192,4 +193,12 @@ public IdentityClientOptions setHttpClient(HttpClient httpClient) { this.httpClient = httpClient; return this; } + + public PersistenceSettings getCachePersistenceSettings() { + return cachePersistenceSettings; + } + + public void setCachePersistenceSettings(PersistenceSettings cachePersistenceSettings) { + this.cachePersistenceSettings = cachePersistenceSettings; + } } diff --git a/sdk/keyvault/azure-security-keyvault-secrets/src/samples/java/com/azure/security/keyvault/secrets/PersistentTokenCacheDemo.java b/sdk/keyvault/azure-security-keyvault-secrets/src/samples/java/com/azure/security/keyvault/secrets/PersistentTokenCacheDemo.java index fb98d42c13f34..fcfbbacf9762e 100644 --- a/sdk/keyvault/azure-security-keyvault-secrets/src/samples/java/com/azure/security/keyvault/secrets/PersistentTokenCacheDemo.java +++ b/sdk/keyvault/azure-security-keyvault-secrets/src/samples/java/com/azure/security/keyvault/secrets/PersistentTokenCacheDemo.java @@ -3,9 +3,9 @@ package com.azure.security.keyvault.secrets; +import com.azure.identity.DefaultAzureCredential; +import com.azure.identity.DefaultAzureCredentialBuilder; import com.azure.security.keyvault.secrets.models.KeyVaultSecret; -import com.azure.identity.SharedTokenCacheCredential; -import com.azure.identity.SharedTokenCacheCredentialBuilder; /** * Sample showing how to authenticate to key vault with a shared token cache credential. @@ -19,9 +19,7 @@ public class PersistentTokenCacheDemo { public static void main(String[] args) { // Wrote to AZURE_USERNAME env variable - SharedTokenCacheCredential defaultCredential = new SharedTokenCacheCredentialBuilder() - .clientId("04b07795-8ddb-461a-bbee-02f9e1bf7b46") - .build(); + DefaultAzureCredential defaultCredential = new DefaultAzureCredentialBuilder().build(); SecretClient client = new SecretClientBuilder() .vaultUrl("https://persistentcachedemo.vault.azure.net") From 3c034433635405cd77f8f5dd3a03f504e645a6e8 Mon Sep 17 00:00:00 2001 From: Jianghao Lu Date: Wed, 18 Mar 2020 00:31:32 -0700 Subject: [PATCH 03/45] Clean up --- .../identity/implementation/IdentityClient.java | 14 +++++--------- .../implementation/IdentityClientOptions.java | 10 ---------- 2 files changed, 5 insertions(+), 19 deletions(-) diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java index 889015587c74b..1de117cb308f5 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java @@ -6,16 +6,16 @@ import com.azure.core.credential.AccessToken; import com.azure.core.credential.TokenRequestContext; import com.azure.core.exception.ClientAuthenticationException; -import com.azure.core.http.ProxyOptions; -import com.azure.core.util.CoreUtils; import com.azure.core.http.HttpClient; import com.azure.core.http.HttpPipeline; import com.azure.core.http.HttpPipelineBuilder; +import com.azure.core.http.ProxyOptions; import com.azure.core.http.policy.HttpLogOptions; import com.azure.core.http.policy.HttpLoggingPolicy; import com.azure.core.http.policy.HttpPipelinePolicy; import com.azure.core.http.policy.HttpPolicyProviders; import com.azure.core.http.policy.RetryPolicy; +import com.azure.core.util.CoreUtils; import com.azure.core.util.logging.ClientLogger; import com.azure.core.util.serializer.JacksonAdapter; import com.azure.core.util.serializer.SerializerAdapter; @@ -30,7 +30,6 @@ import com.microsoft.aad.msal4j.PublicClientApplication; import com.microsoft.aad.msal4j.SilentParameters; import com.microsoft.aad.msal4j.UserNamePasswordParameters; -import com.microsoft.aad.msal4jextensions.PersistenceTokenCacheAccessAspect; import reactor.core.publisher.Mono; import reactor.core.scheduler.Schedulers; @@ -58,9 +57,9 @@ import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.HashSet; -import java.util.Map; import java.util.List; import java.util.Locale; +import java.util.Map; import java.util.Random; import java.util.Scanner; import java.util.UUID; @@ -112,11 +111,8 @@ public class IdentityClient { String authorityUrl = options.getAuthorityHost().replaceAll("/+$", "") + "/organizations/" + tenantId; PublicClientApplication.Builder publicClientApplicationBuilder = PublicClientApplication.builder(clientId); try { - publicClientApplicationBuilder = publicClientApplicationBuilder - .authority(authorityUrl) - .setTokenCacheAccessAspect( - new PersistenceTokenCacheAccessAspect(options.getCachePersistenceSettings())); - } catch (IOException e) { + publicClientApplicationBuilder = publicClientApplicationBuilder.authority(authorityUrl); + } catch (MalformedURLException e) { throw logger.logExceptionAsWarning(new IllegalStateException(e)); } diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClientOptions.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClientOptions.java index 9d79e727bb384..66a07de85a262 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClientOptions.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClientOptions.java @@ -6,7 +6,6 @@ import com.azure.core.http.HttpClient; import com.azure.core.http.HttpPipeline; import com.azure.core.http.ProxyOptions; -import com.microsoft.aad.msal4jextensions.PersistenceSettings; import java.time.Duration; import java.util.Objects; @@ -28,7 +27,6 @@ public final class IdentityClientOptions { private ExecutorService executorService; private Duration tokenRefreshOffset = Duration.ofMinutes(2); private HttpClient httpClient; - private PersistenceSettings cachePersistenceSettings; /** * Creates an instance of IdentityClientOptions with default settings. @@ -193,12 +191,4 @@ public IdentityClientOptions setHttpClient(HttpClient httpClient) { this.httpClient = httpClient; return this; } - - public PersistenceSettings getCachePersistenceSettings() { - return cachePersistenceSettings; - } - - public void setCachePersistenceSettings(PersistenceSettings cachePersistenceSettings) { - this.cachePersistenceSettings = cachePersistenceSettings; - } } From 49acab7e6a50a2cbf902b3fb78565792d34b260a Mon Sep 17 00:00:00 2001 From: Jianghao Lu Date: Wed, 18 Mar 2020 14:18:46 -0700 Subject: [PATCH 04/45] Add msal ext to module-info --- eng/versioning/external_dependencies.txt | 1 + sdk/identity/azure-identity/pom.xml | 2 +- .../identity/DefaultAzureCredential.java | 2 +- .../com/azure/identity/KeyRingItemSchema.java | 22 ------------- .../com/azure/identity/KeyringItemSchema.java | 31 +++++++++++++++++++ .../identity/SharedTokenCacheCredential.java | 2 +- .../SharedTokenCacheCredentialBuilder.java | 6 ++-- .../src/main/java/module-info.java | 2 ++ 8 files changed, 40 insertions(+), 28 deletions(-) delete mode 100644 sdk/identity/azure-identity/src/main/java/com/azure/identity/KeyRingItemSchema.java create mode 100644 sdk/identity/azure-identity/src/main/java/com/azure/identity/KeyringItemSchema.java diff --git a/eng/versioning/external_dependencies.txt b/eng/versioning/external_dependencies.txt index 1f4e65d6b1061..5b537d6980edc 100644 --- a/eng/versioning/external_dependencies.txt +++ b/eng/versioning/external_dependencies.txt @@ -61,6 +61,7 @@ com.microsoft.azure:azure-mgmt-resources;1.3.0 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 commons-collections:commons-collections;3.2.2 io.opentelemetry:opentelemetry-api;0.2.0 diff --git a/sdk/identity/azure-identity/pom.xml b/sdk/identity/azure-identity/pom.xml index d3bdb3d359835..b7fe62eaeb197 100644 --- a/sdk/identity/azure-identity/pom.xml +++ b/sdk/identity/azure-identity/pom.xml @@ -42,7 +42,7 @@ com.microsoft.azure msal4j-persistence-extension - 0.1 + 0.1 com.nimbusds diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/DefaultAzureCredential.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/DefaultAzureCredential.java index a1855d3468a8e..1dfd7b5a6153b 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/DefaultAzureCredential.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/DefaultAzureCredential.java @@ -34,7 +34,7 @@ public final class DefaultAzureCredential extends ChainedTokenCredential { private static final String DEFAULT_KEYCHAIN_SERVICE = "Microsoft.Developer.IdentityService"; private static final String DEFAULT_KEYCHAIN_ACCOUNT = "MSALCache"; private static final String DEFAULT_KEYRING_NAME = "default"; - private static final KeyRingItemSchema DEFAULT_KEYRING_SCHEMA = KeyRingItemSchema.GENERIC_SECRET; + private static final KeyringItemSchema DEFAULT_KEYRING_SCHEMA = KeyringItemSchema.GENERIC_SECRET; private static final String DEFAULT_KEYRING_ITEM_NAME = DEFAULT_KEYCHAIN_ACCOUNT; private static final String DEFAULT_KEYRING_ATTR_NAME = "MsalClientID"; private static final String DEFAULT_KEYRING_ATTR_VALUE = "Microsoft.Developer.IdentityService"; diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/KeyRingItemSchema.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/KeyRingItemSchema.java deleted file mode 100644 index b7680e41ec4a1..0000000000000 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/KeyRingItemSchema.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.azure.identity; - -public final class KeyRingItemSchema { - public static final KeyRingItemSchema GENERIC_SECRET = new KeyRingItemSchema("org.freedesktop.Secret.Generic"); - public static final KeyRingItemSchema NETWORK_PASSWORD = new KeyRingItemSchema("org.gnome.keyring.NetworkPassword"); - public static final KeyRingItemSchema NOTE = new KeyRingItemSchema("org.gnome.keyring.Note"); - - private final String value; - - private KeyRingItemSchema(String value) { - this.value = value; - } - - public static KeyRingItemSchema fromString(String schema) { - return new KeyRingItemSchema(schema); - } - - @Override - public String toString() { - return value; - } -} diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/KeyringItemSchema.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/KeyringItemSchema.java new file mode 100644 index 0000000000000..de8d01bbc6c46 --- /dev/null +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/KeyringItemSchema.java @@ -0,0 +1,31 @@ +package com.azure.identity; + +/** + * An expandable enum for types of item schema in a Keyring. + */ +public final class KeyringItemSchema { + public static final KeyringItemSchema GENERIC_SECRET = new KeyringItemSchema("org.freedesktop.Secret.Generic"); + public static final KeyringItemSchema NETWORK_PASSWORD = new KeyringItemSchema( + "org.gnome.keyring.NetworkPassword"); + public static final KeyringItemSchema NOTE = new KeyringItemSchema("org.gnome.keyring.Note"); + + private final String value; + + private KeyringItemSchema(String value) { + this.value = value; + } + + /** + * Parses a String into a new Keyring schema. + * @param schema the full name of the schema + * @return the KeyRingItemSchema enum representing this schema + */ + public static KeyringItemSchema fromString(String schema) { + return new KeyringItemSchema(schema); + } + + @Override + public String toString() { + return value; + } +} diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/SharedTokenCacheCredential.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/SharedTokenCacheCredential.java index 8c59e2fcb5bb5..313901b25f5bf 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/SharedTokenCacheCredential.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/SharedTokenCacheCredential.java @@ -88,7 +88,7 @@ public Mono getToken(TokenRequestContext request) { .authority(authorityUrl) .setTokenCacheAccessAspect(accessAspect) .build(); - } catch (Exception e) { + } catch (Throwable e) { return Mono.error(e); } } diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/SharedTokenCacheCredentialBuilder.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/SharedTokenCacheCredentialBuilder.java index 4871eacb50c2f..25b6b1c8b9d2e 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/SharedTokenCacheCredentialBuilder.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/SharedTokenCacheCredentialBuilder.java @@ -23,7 +23,7 @@ public class SharedTokenCacheCredentialBuilder extends AadCredentialBuilderBase< private String keyChainService; private String keyChainAccount; private String keyRingName; - private KeyRingItemSchema keyRingItemSchema; + private KeyringItemSchema keyRingItemSchema; private String keyRingItemName; private LinkedHashMap attributes = new LinkedHashMap<>(); // preserve order private boolean useUnprotectedFileOnLinux = false; @@ -95,13 +95,13 @@ public SharedTokenCacheCredentialBuilder keyRingName(String keyRingName) { /** * Sets the schema of the Gnome keyring to store the cache on Gnome keyring enabled - * Linux systems. The default value is KeyRingItemSchema.GenericSecret. + * Linux systems. The default value is KeyringItemSchema.GenericSecret. * * @param keyRingItemSchema The schema of the Gnome keyring. * * @return The updated SharedTokenCacheCredentialBuilder object. */ - public SharedTokenCacheCredentialBuilder keyRingItemSchema(KeyRingItemSchema keyRingItemSchema) { + public SharedTokenCacheCredentialBuilder keyRingItemSchema(KeyringItemSchema keyRingItemSchema) { this.keyRingItemSchema = keyRingItemSchema; return this; } diff --git a/sdk/identity/azure-identity/src/main/java/module-info.java b/sdk/identity/azure-identity/src/main/java/module-info.java index 16a8b19c892f5..5a0d9570d4ff0 100644 --- a/sdk/identity/azure-identity/src/main/java/module-info.java +++ b/sdk/identity/azure-identity/src/main/java/module-info.java @@ -5,6 +5,8 @@ requires transitive com.azure.core; requires msal4j; + requires com.microsoft.aad.msal4j; + requires com.microsoft.aad.msal4jextensions; requires com.sun.jna; requires com.sun.jna.platform; requires nanohttpd; From f50d8b7e12ea6e7b535b025192c678a7732bc26e Mon Sep 17 00:00:00 2001 From: Jianghao Lu Date: Wed, 18 Mar 2020 15:40:05 -0700 Subject: [PATCH 05/45] Wrap MSAL error and fix module-info --- .../java/com/azure/identity/SharedTokenCacheCredential.java | 3 +++ sdk/identity/azure-identity/src/main/java/module-info.java | 3 +-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/SharedTokenCacheCredential.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/SharedTokenCacheCredential.java index 313901b25f5bf..7b493006f1b34 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/SharedTokenCacheCredential.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/SharedTokenCacheCredential.java @@ -6,6 +6,7 @@ import com.azure.core.credential.AccessToken; import com.azure.core.credential.TokenCredential; import com.azure.core.credential.TokenRequestContext; +import com.azure.core.exception.ClientAuthenticationException; import com.azure.core.util.Configuration; import com.azure.identity.implementation.IdentityClientOptions; import com.microsoft.aad.msal4j.IAccount; @@ -95,6 +96,8 @@ public Mono getToken(TokenRequestContext request) { // find if the Public Client app with the requested username exists return Mono.fromFuture(pubClient.getAccounts()) + .onErrorResume(t -> Mono.error(new ClientAuthenticationException( + "Cannot get accounts from token cache. Error: " + t.getMessage(), null))) .flatMap(set -> { IAccount requestedAccount; Map accounts = new HashMap<>(); // home account id -> account diff --git a/sdk/identity/azure-identity/src/main/java/module-info.java b/sdk/identity/azure-identity/src/main/java/module-info.java index 5a0d9570d4ff0..bb1493631ae34 100644 --- a/sdk/identity/azure-identity/src/main/java/module-info.java +++ b/sdk/identity/azure-identity/src/main/java/module-info.java @@ -5,8 +5,7 @@ requires transitive com.azure.core; requires msal4j; - requires com.microsoft.aad.msal4j; - requires com.microsoft.aad.msal4jextensions; + requires msal4j.persistence.extension; requires com.sun.jna; requires com.sun.jna.platform; requires nanohttpd; From 4e4c467657ae1143afcb456d0d1faca0df7adc09 Mon Sep 17 00:00:00 2001 From: Jianghao Lu Date: Wed, 18 Mar 2020 16:13:23 -0700 Subject: [PATCH 06/45] checkstyle --- .../com/azure/identity/DefaultAzureCredential.java | 6 +++--- .../java/com/azure/identity/KeyringItemSchema.java | 3 +++ .../azure/identity/SharedTokenCacheCredential.java | 3 +-- .../identity/SharedTokenCacheCredentialBuilder.java | 12 ++++++++---- 4 files changed, 15 insertions(+), 9 deletions(-) diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/DefaultAzureCredential.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/DefaultAzureCredential.java index 1dfd7b5a6153b..e9c2ccf783ca8 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/DefaultAzureCredential.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/DefaultAzureCredential.java @@ -28,9 +28,9 @@ @Immutable public final class DefaultAzureCredential extends ChainedTokenCredential { private static final String DEFAULT_CACHE_FILE_NAME = "msal.cache"; - private static final Path DEFAULT_CACHE_DIRECTORY = Platform.isWindows() ? - Paths.get(System.getProperty("user.home"), "AppData", "Local", ".IdentityService", "msal.cache") : - Paths.get(System.getProperty("user.home"),".IdentityService", "msal.cache"); + private static final Path DEFAULT_CACHE_DIRECTORY = Platform.isWindows() + ? Paths.get(System.getProperty("user.home"), "AppData", "Local", ".IdentityService", "msal.cache") + : Paths.get(System.getProperty("user.home"), ".IdentityService", "msal.cache"); private static final String DEFAULT_KEYCHAIN_SERVICE = "Microsoft.Developer.IdentityService"; private static final String DEFAULT_KEYCHAIN_ACCOUNT = "MSALCache"; private static final String DEFAULT_KEYRING_NAME = "default"; diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/KeyringItemSchema.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/KeyringItemSchema.java index de8d01bbc6c46..b9661ad0aa663 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/KeyringItemSchema.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/KeyringItemSchema.java @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + package com.azure.identity; /** diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/SharedTokenCacheCredential.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/SharedTokenCacheCredential.java index 7b493006f1b34..e341cd4c987fc 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/SharedTokenCacheCredential.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/SharedTokenCacheCredential.java @@ -79,7 +79,6 @@ public Mono getToken(TokenRequestContext request) { // Initialize here so that the constructor doesn't throw if (pubClient == null) { try { - PersistenceTokenCacheAccessAspect accessAspect = new PersistenceTokenCacheAccessAspect(persistenceSettings); PublicClientApplication.Builder applicationBuilder = PublicClientApplication.builder(this.clientId); if (options.getExecutorService() != null) { applicationBuilder.executorService(options.getExecutorService()); @@ -87,7 +86,7 @@ public Mono getToken(TokenRequestContext request) { pubClient = applicationBuilder .authority(authorityUrl) - .setTokenCacheAccessAspect(accessAspect) + .setTokenCacheAccessAspect(new PersistenceTokenCacheAccessAspect(persistenceSettings)) .build(); } catch (Throwable e) { return Mono.error(e); diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/SharedTokenCacheCredentialBuilder.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/SharedTokenCacheCredentialBuilder.java index 25b6b1c8b9d2e..cd6d6904e9167 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/SharedTokenCacheCredentialBuilder.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/SharedTokenCacheCredentialBuilder.java @@ -3,6 +3,7 @@ package com.azure.identity; +import com.azure.core.util.logging.ClientLogger; import com.azure.identity.implementation.util.ValidationUtil; import com.microsoft.aad.msal4jextensions.PersistenceSettings; @@ -18,6 +19,7 @@ * @see SharedTokenCacheCredential */ public class SharedTokenCacheCredentialBuilder extends AadCredentialBuilderBase { + private final ClientLogger logger = new ClientLogger(SharedTokenCacheCredentialBuilder.class); private String username; private Path cacheFileLocation; private String keyChainService; @@ -25,7 +27,7 @@ public class SharedTokenCacheCredentialBuilder extends AadCredentialBuilderBase< private String keyRingName; private KeyringItemSchema keyRingItemSchema; private String keyRingItemName; - private LinkedHashMap attributes = new LinkedHashMap<>(); // preserve order + private final LinkedHashMap attributes = new LinkedHashMap<>(); // preserve order private boolean useUnprotectedFileOnLinux = false; /** @@ -127,12 +129,14 @@ public SharedTokenCacheCredentialBuilder keyRingItemName(String keyRingItemName) * @param attributeValue The value of the attribute. * * @return The updated SharedTokenCacheCredentialBuilder object. + * @throws IllegalArgumentException if there are already 2 attributes */ public SharedTokenCacheCredentialBuilder addKeyRingItemAttribute(String attributeName, String attributeValue) { if (this.attributes.size() < 2) { this.attributes.put(attributeName, attributeValue); } else { - throw new IllegalArgumentException("Currently does not support more than 2 attributes for linux KeyRing"); + throw logger.logExceptionAsError(new IllegalArgumentException( + "Currently does not support more than 2 attributes for linux KeyRing")); } return this; } @@ -157,8 +161,8 @@ public SharedTokenCacheCredentialBuilder useUnprotectedFileOnLinux(boolean useUn */ public SharedTokenCacheCredential build() { ValidationUtil.validate(getClass().getSimpleName(), new HashMap() {{ - put("cacheFileLocation", cacheFileLocation); - }}); + put("cacheFileLocation", cacheFileLocation); + }}); List attributeKeyList = new ArrayList<>(attributes.keySet()); while (attributeKeyList.size() < 2) { attributeKeyList.add(null); From fca0133afb51e296ab1ea63dfcc02fcb37ff4935 Mon Sep 17 00:00:00 2001 From: Jianghao Lu Date: Wed, 18 Mar 2020 16:37:50 -0700 Subject: [PATCH 07/45] Fix spotbugs and naming --- .../com/azure/identity/KeyringItemSchema.java | 2 +- .../SharedTokenCacheCredentialBuilder.java | 62 ++++++++++--------- 2 files changed, 33 insertions(+), 31 deletions(-) diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/KeyringItemSchema.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/KeyringItemSchema.java index b9661ad0aa663..7b9006d86d225 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/KeyringItemSchema.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/KeyringItemSchema.java @@ -21,7 +21,7 @@ private KeyringItemSchema(String value) { /** * Parses a String into a new Keyring schema. * @param schema the full name of the schema - * @return the KeyRingItemSchema enum representing this schema + * @return the KeyringItemSchema enum representing this schema */ public static KeyringItemSchema fromString(String schema) { return new KeyringItemSchema(schema); diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/SharedTokenCacheCredentialBuilder.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/SharedTokenCacheCredentialBuilder.java index cd6d6904e9167..668425535c6d1 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/SharedTokenCacheCredentialBuilder.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/SharedTokenCacheCredentialBuilder.java @@ -21,12 +21,13 @@ public class SharedTokenCacheCredentialBuilder extends AadCredentialBuilderBase { private final ClientLogger logger = new ClientLogger(SharedTokenCacheCredentialBuilder.class); private String username; - private Path cacheFileLocation; - private String keyChainService; - private String keyChainAccount; - private String keyRingName; - private KeyringItemSchema keyRingItemSchema; - private String keyRingItemName; + private String cacheFileName; + private Path cacheFileDirectory; + private String keychainService; + private String keychainAccount; + private String keyringName; + private KeyringItemSchema keyringItemSchema; + private String keyringItemName; private final LinkedHashMap attributes = new LinkedHashMap<>(); // preserve order private boolean useUnprotectedFileOnLinux = false; @@ -52,7 +53,8 @@ public SharedTokenCacheCredentialBuilder username(String username) { * @return The updated SharedTokenCacheCredentialBuilder object. */ public SharedTokenCacheCredentialBuilder cacheFileLocation(Path cacheFileLocation) { - this.cacheFileLocation = cacheFileLocation; + this.cacheFileName = cacheFileLocation.getFileName().toString(); + this.cacheFileDirectory = cacheFileLocation.getParent(); return this; } @@ -64,8 +66,8 @@ public SharedTokenCacheCredentialBuilder cacheFileLocation(Path cacheFileLocatio * * @return The updated SharedTokenCacheCredentialBuilder object. */ - public SharedTokenCacheCredentialBuilder keyChainService(String serviceName) { - this.keyChainService = serviceName; + public SharedTokenCacheCredentialBuilder keychainService(String serviceName) { + this.keychainService = serviceName; return this; } @@ -77,8 +79,8 @@ public SharedTokenCacheCredentialBuilder keyChainService(String serviceName) { * * @return The updated SharedTokenCacheCredentialBuilder object. */ - public SharedTokenCacheCredentialBuilder keyChainAccount(String accountName) { - this.keyChainAccount = accountName; + public SharedTokenCacheCredentialBuilder keychainAccount(String accountName) { + this.keychainAccount = accountName; return this; } @@ -86,12 +88,12 @@ public SharedTokenCacheCredentialBuilder keyChainAccount(String accountName) { * Sets the name of the Gnome keyring to store the cache on Gnome keyring enabled * Linux systems. The default value is "default". * - * @param keyRingName The name of the Gnome keyring. + * @param keyringName The name of the Gnome keyring. * * @return The updated SharedTokenCacheCredentialBuilder object. */ - public SharedTokenCacheCredentialBuilder keyRingName(String keyRingName) { - this.keyRingName = keyRingName; + public SharedTokenCacheCredentialBuilder keyringName(String keyringName) { + this.keyringName = keyringName; return this; } @@ -99,12 +101,12 @@ public SharedTokenCacheCredentialBuilder keyRingName(String keyRingName) { * Sets the schema of the Gnome keyring to store the cache on Gnome keyring enabled * Linux systems. The default value is KeyringItemSchema.GenericSecret. * - * @param keyRingItemSchema The schema of the Gnome keyring. + * @param keyringItemSchema The schema of the Gnome keyring. * * @return The updated SharedTokenCacheCredentialBuilder object. */ - public SharedTokenCacheCredentialBuilder keyRingItemSchema(KeyringItemSchema keyRingItemSchema) { - this.keyRingItemSchema = keyRingItemSchema; + public SharedTokenCacheCredentialBuilder keyringItemSchema(KeyringItemSchema keyringItemSchema) { + this.keyringItemSchema = keyringItemSchema; return this; } @@ -112,12 +114,12 @@ public SharedTokenCacheCredentialBuilder keyRingItemSchema(KeyringItemSchema key * Sets the name of the Gnome keyring item to store the cache on Gnome keyring enabled * Linux systems. The default value is "MSALCache". * - * @param keyRingItemName The name of the Gnome keyring item. + * @param keyringItemName The name of the Gnome keyring item. * * @return The updated SharedTokenCacheCredentialBuilder object. */ - public SharedTokenCacheCredentialBuilder keyRingItemName(String keyRingItemName) { - this.keyRingItemName = keyRingItemName; + public SharedTokenCacheCredentialBuilder keyringItemName(String keyringItemName) { + this.keyringItemName = keyringItemName; return this; } @@ -131,12 +133,12 @@ public SharedTokenCacheCredentialBuilder keyRingItemName(String keyRingItemName) * @return The updated SharedTokenCacheCredentialBuilder object. * @throws IllegalArgumentException if there are already 2 attributes */ - public SharedTokenCacheCredentialBuilder addKeyRingItemAttribute(String attributeName, String attributeValue) { + public SharedTokenCacheCredentialBuilder addKeyringItemAttribute(String attributeName, String attributeValue) { if (this.attributes.size() < 2) { this.attributes.put(attributeName, attributeValue); } else { throw logger.logExceptionAsError(new IllegalArgumentException( - "Currently does not support more than 2 attributes for linux KeyRing")); + "Currently does not support more than 2 attributes for linux Keyring")); } return this; } @@ -161,7 +163,8 @@ public SharedTokenCacheCredentialBuilder useUnprotectedFileOnLinux(boolean useUn */ public SharedTokenCacheCredential build() { ValidationUtil.validate(getClass().getSimpleName(), new HashMap() {{ - put("cacheFileLocation", cacheFileLocation); + put("cacheFileName", cacheFileName); + put("cacheFileDirectory", cacheFileDirectory); }}); List attributeKeyList = new ArrayList<>(attributes.keySet()); while (attributeKeyList.size() < 2) { @@ -169,17 +172,16 @@ public SharedTokenCacheCredential build() { } String key1 = attributeKeyList.get(0); String key2 = attributeKeyList.get(1); - PersistenceSettings.Builder persistenceSettings = PersistenceSettings.builder( - cacheFileLocation.getFileName().toString(), cacheFileLocation.getParent()) + PersistenceSettings.Builder persistenceBuilder = PersistenceSettings.builder(cacheFileName, cacheFileDirectory) .setLinuxUseUnprotectedFileAsCacheStorage(useUnprotectedFileOnLinux); - if (keyChainService != null && keyChainAccount != null) { - persistenceSettings.setMacKeychain(keyChainService, keyChainAccount); + if (keychainService != null && keychainAccount != null) { + persistenceBuilder.setMacKeychain(keychainService, keychainAccount); } - if (keyRingName != null && keyRingItemName != null && keyRingItemSchema != null) { - persistenceSettings.setLinuxKeyring(keyRingName, keyRingItemSchema.toString(), keyRingItemName, + if (keyringName != null && keyringItemName != null && keyringItemSchema != null) { + persistenceBuilder.setLinuxKeyring(keyringName, keyringItemSchema.toString(), keyringItemName, key1, attributes.getOrDefault(key1, null), key2, attributes.getOrDefault(key2, null)); } return new SharedTokenCacheCredential( - username, clientId, tenantId, identityClientOptions, persistenceSettings.build()); + username, clientId, tenantId, identityClientOptions, persistenceBuilder.build()); } } From 571b44c4cb9ed408cea69de5e323ee9206224834 Mon Sep 17 00:00:00 2001 From: Jianghao Lu Date: Mon, 23 Mar 2020 14:20:30 -0700 Subject: [PATCH 08/45] Fix default azure credential test --- .../java/com/azure/identity/DefaultAzureCredentialTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/identity/azure-identity/src/test/java/com/azure/identity/DefaultAzureCredentialTest.java b/sdk/identity/azure-identity/src/test/java/com/azure/identity/DefaultAzureCredentialTest.java index 48469168f7fd2..f84caac4bb8cb 100644 --- a/sdk/identity/azure-identity/src/test/java/com/azure/identity/DefaultAzureCredentialTest.java +++ b/sdk/identity/azure-identity/src/test/java/com/azure/identity/DefaultAzureCredentialTest.java @@ -24,7 +24,7 @@ @RunWith(PowerMockRunner.class) @PrepareForTest(fullyQualifiedNames = "com.azure.identity.*") -@PowerMockIgnore({"com.sun.org.apache.xerces.*", "javax.xml.*", "org.xml.*"}) +@PowerMockIgnore({"com.sun.org.apache.xerces.*", "javax.xml.*", "org.xml.*", "javax.net.ssl.*"}) public class DefaultAzureCredentialTest { private final String tenantId = "contoso.com"; From e2c5dc36599d20d8277d47228ac94d6d6d8af086 Mon Sep 17 00:00:00 2001 From: Jianghao Lu Date: Wed, 1 Apr 2020 12:36:11 -0700 Subject: [PATCH 09/45] Add initial perf test --- .../identity/AadCredentialBuilderBase.java | 133 +++++++++++++++ .../identity/DefaultAzureCredential.java | 14 +- .../identity/SharedTokenCacheCredential.java | 104 ++---------- .../SharedTokenCacheCredentialBuilder.java | 152 +----------------- .../implementation/IdentityClient.java | 91 +++++++++++ .../implementation/IdentityClientOptions.java | 38 +++++ sdk/identity/perf-test/README.md | 34 ++++ sdk/identity/perf-test/pom.xml | 68 ++++++++ ...OnlyPersistenceTokenCacheAccessAspect.java | 18 +++ .../ReadOnlySharedTokenCacheCredential.java | 139 ++++++++++++++++ ...OnlySharedTokenCacheCredentialBuilder.java | 45 ++++++ .../java/com/azure/identity/perf/App.java | 38 +++++ .../perf/ReadCacheSingleProcessTest.java | 38 +++++ .../perf/WriteCacheSingleProcessTest.java | 40 +++++ .../azure/identity/perf/core/ServiceTest.java | 29 ++++ sdk/identity/pom.service.xml | 1 + .../secrets/PersistentTokenCacheDemo.java | 5 +- 17 files changed, 737 insertions(+), 250 deletions(-) create mode 100644 sdk/identity/perf-test/README.md create mode 100644 sdk/identity/perf-test/pom.xml create mode 100644 sdk/identity/perf-test/src/main/java/com/azure/identity/ReadOnlyPersistenceTokenCacheAccessAspect.java create mode 100644 sdk/identity/perf-test/src/main/java/com/azure/identity/ReadOnlySharedTokenCacheCredential.java create mode 100644 sdk/identity/perf-test/src/main/java/com/azure/identity/ReadOnlySharedTokenCacheCredentialBuilder.java create mode 100644 sdk/identity/perf-test/src/main/java/com/azure/identity/perf/App.java create mode 100644 sdk/identity/perf-test/src/main/java/com/azure/identity/perf/ReadCacheSingleProcessTest.java create mode 100644 sdk/identity/perf-test/src/main/java/com/azure/identity/perf/WriteCacheSingleProcessTest.java create mode 100644 sdk/identity/perf-test/src/main/java/com/azure/identity/perf/core/ServiceTest.java diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/AadCredentialBuilderBase.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/AadCredentialBuilderBase.java index e64d14c086768..1295c106eb9d8 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/AadCredentialBuilderBase.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/AadCredentialBuilderBase.java @@ -3,6 +3,10 @@ package com.azure.identity; +import com.azure.core.util.logging.ClientLogger; + +import java.nio.file.Path; +import java.util.LinkedHashMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.ForkJoinPool; @@ -11,8 +15,17 @@ * @param the type of the credential builder */ public abstract class AadCredentialBuilderBase> extends CredentialBuilderBase { + private final ClientLogger logger = new ClientLogger(AadCredentialBuilderBase.class); String clientId; String tenantId; + Path cacheFileLocation; + String keychainService; + String keychainAccount; + String keyringName; + KeyringItemSchema keyringItemSchema; + String keyringItemName; + final LinkedHashMap attributes = new LinkedHashMap<>(); // preserve order + boolean useUnprotectedFileOnLinux = false; /** * Specifies the Azure Active Directory endpoint to acquire tokens. @@ -70,4 +83,124 @@ public T executorService(ExecutorService executorService) { this.identityClientOptions.setExecutorService(executorService); return (T) this; } + + /** + * Sets the location for the token cache file on Windows or Linux systems. The default + * location is {user home}/AppData/Local/.IdentityService/msal.cache on + * Windows and ~/.IdentityService/msal.cache on Linux. + * + * @param cacheFileLocation The location for the token cache file. + * + * @return The updated T object. + */ + @SuppressWarnings("unchecked") + public T cacheFileLocation(Path cacheFileLocation) { + this.cacheFileLocation = cacheFileLocation; + return (T) this; + } + + /** + * Sets the service name for the Keychain item on MacOS. The default value is + * "Microsoft.Developer.IdentityService". + * + * @param serviceName The service name for the Keychain item. + * + * @return The updated T object. + */ + @SuppressWarnings("unchecked") + public T keychainService(String serviceName) { + this.keychainService = serviceName; + return (T) this; + } + + /** + * Sets the account name for the Keychain item on MacOS. The default value is + * "MSALCache". + * + * @param accountName The account name for the Keychain item. + * + * @return The updated T object. + */ + @SuppressWarnings("unchecked") + public T keychainAccount(String accountName) { + this.keychainAccount = accountName; + return (T) this; + } + + /** + * Sets the name of the Gnome keyring to store the cache on Gnome keyring enabled + * Linux systems. The default value is "default". + * + * @param keyringName The name of the Gnome keyring. + * + * @return The updated T object. + */ + @SuppressWarnings("unchecked") + public T keyringName(String keyringName) { + this.keyringName = keyringName; + return (T) this; + } + + /** + * Sets the schema of the Gnome keyring to store the cache on Gnome keyring enabled + * Linux systems. The default value is KeyringItemSchema.GenericSecret. + * + * @param keyringItemSchema The schema of the Gnome keyring. + * + * @return The updated T object. + */ + @SuppressWarnings("unchecked") + public T keyringItemSchema(KeyringItemSchema keyringItemSchema) { + this.keyringItemSchema = keyringItemSchema; + return (T) this; + } + + /** + * Sets the name of the Gnome keyring item to store the cache on Gnome keyring enabled + * Linux systems. The default value is "MSALCache". + * + * @param keyringItemName The name of the Gnome keyring item. + * + * @return The updated T object. + */ + @SuppressWarnings("unchecked") + public T keyringItemName(String keyringItemName) { + this.keyringItemName = keyringItemName; + return (T) this; + } + + /** + * Adds an attribute to the Gnome keyring item to store the cache on Gnome keyring enabled + * Linux systems. Only 2 attributes are allowed. + * + * @param attributeName The name of the attribute. + * @param attributeValue The value of the attribute. + * + * @return The updated T object. + * @throws IllegalArgumentException if there are already 2 attributes + */ + @SuppressWarnings("unchecked") + public T addKeyringItemAttribute(String attributeName, String attributeValue) { + if (this.attributes.size() < 2) { + this.attributes.put(attributeName, attributeValue); + } else { + throw logger.logExceptionAsError(new IllegalArgumentException( + "Currently does not support more than 2 attributes for linux Keyring")); + } + return (T) this; + } + + /** + * Sets whether to use an unprotected file specified by cacheFileLocation() instead of + * Gnome keyring on Linux. This is false by default. + * + * @param useUnprotectedFileOnLinux whether to use an unprotected file for cache storage. + * + * @return The updated T object. + */ + @SuppressWarnings("unchecked") + public T useUnprotectedFileOnLinux(boolean useUnprotectedFileOnLinux) { + this.useUnprotectedFileOnLinux = useUnprotectedFileOnLinux; + return (T) this; + } } diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/DefaultAzureCredential.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/DefaultAzureCredential.java index e9c2ccf783ca8..0c710f96d1a42 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/DefaultAzureCredential.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/DefaultAzureCredential.java @@ -5,13 +5,13 @@ import com.azure.core.annotation.Immutable; import com.azure.identity.implementation.IdentityClientOptions; -import com.microsoft.aad.msal4jextensions.PersistenceSettings; import com.sun.jna.Platform; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayDeque; import java.util.Arrays; +import java.util.Collections; /** * Creates a credential using environment variables or the shared token cache. It tries to create a valid credential in @@ -27,8 +27,7 @@ */ @Immutable public final class DefaultAzureCredential extends ChainedTokenCredential { - private static final String DEFAULT_CACHE_FILE_NAME = "msal.cache"; - private static final Path DEFAULT_CACHE_DIRECTORY = Platform.isWindows() + private static final Path DEFAULT_CACHE_PATH = Platform.isWindows() ? Paths.get(System.getProperty("user.home"), "AppData", "Local", ".IdentityService", "msal.cache") : Paths.get(System.getProperty("user.home"), ".IdentityService", "msal.cache"); private static final String DEFAULT_KEYCHAIN_SERVICE = "Microsoft.Developer.IdentityService"; @@ -54,12 +53,11 @@ public final class DefaultAzureCredential extends ChainedTokenCredential { super(new ArrayDeque<>(Arrays.asList(new EnvironmentCredential(identityClientOptions), new ManagedIdentityCredential(null, identityClientOptions), new SharedTokenCacheCredential(null, "04b07795-8ddb-461a-bbee-02f9e1bf7b46", null, - identityClientOptions, PersistenceSettings.builder(DEFAULT_CACHE_FILE_NAME, DEFAULT_CACHE_DIRECTORY) - .setMacKeychain(DEFAULT_KEYCHAIN_SERVICE, DEFAULT_KEYCHAIN_ACCOUNT) - .setLinuxKeyring(DEFAULT_KEYRING_NAME, DEFAULT_KEYRING_SCHEMA.toString(), DEFAULT_KEYRING_ITEM_NAME, - DEFAULT_KEYRING_ATTR_NAME, DEFAULT_KEYRING_ATTR_VALUE, null, null) + identityClientOptions.setPersistenceSettings(DEFAULT_CACHE_PATH, DEFAULT_KEYCHAIN_SERVICE, + DEFAULT_KEYCHAIN_ACCOUNT, DEFAULT_KEYRING_NAME, DEFAULT_KEYRING_SCHEMA, + DEFAULT_KEYRING_ITEM_NAME, Collections.singletonMap(DEFAULT_KEYRING_ATTR_NAME, + DEFAULT_KEYRING_ATTR_VALUE), false)), // TODO: Check if libsecret is installed for Linux and use unprotected file cache if not - .build()), new AzureCliCredential(identityClientOptions)))); } } diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/SharedTokenCacheCredential.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/SharedTokenCacheCredential.java index e341cd4c987fc..8663cf66b5ef6 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/SharedTokenCacheCredential.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/SharedTokenCacheCredential.java @@ -6,25 +6,12 @@ import com.azure.core.credential.AccessToken; import com.azure.core.credential.TokenCredential; import com.azure.core.credential.TokenRequestContext; -import com.azure.core.exception.ClientAuthenticationException; import com.azure.core.util.Configuration; +import com.azure.identity.implementation.IdentityClient; +import com.azure.identity.implementation.IdentityClientBuilder; import com.azure.identity.implementation.IdentityClientOptions; -import com.microsoft.aad.msal4j.IAccount; -import com.microsoft.aad.msal4j.IAuthenticationResult; -import com.microsoft.aad.msal4j.PublicClientApplication; -import com.microsoft.aad.msal4j.SilentParameters; -import com.microsoft.aad.msal4jextensions.PersistenceSettings; -import com.microsoft.aad.msal4jextensions.PersistenceTokenCacheAccessAspect; import reactor.core.publisher.Mono; -import java.net.MalformedURLException; -import java.time.ZoneOffset; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.concurrent.CompletableFuture; -import java.util.stream.Collectors; - /** * A credential provider that provides token credentials from the MSAL shared token cache. * Requires a username and client Id. If a username is not provided, then the @@ -35,9 +22,8 @@ public class SharedTokenCacheCredential implements TokenCredential { private final String clientId; private final String tenantId; private final IdentityClientOptions options; - private final PersistenceSettings persistenceSettings; - private PublicClientApplication pubClient = null; + private final IdentityClient identityClient; /** * Creates an instance of the Shared Token Cache Credential Provider. @@ -47,7 +33,7 @@ public class SharedTokenCacheCredential implements TokenCredential { * @param identityClientOptions the options for configuring the identity client */ SharedTokenCacheCredential(String username, String clientId, String tenantId, - IdentityClientOptions identityClientOptions, PersistenceSettings persistenceSettings) { + IdentityClientOptions identityClientOptions) { Configuration configuration = Configuration.getGlobalConfiguration().clone(); if (username == null) { @@ -67,7 +53,11 @@ public class SharedTokenCacheCredential implements TokenCredential { this.tenantId = tenantId; } this.options = identityClientOptions; - this.persistenceSettings = persistenceSettings; + this.identityClient = new IdentityClientBuilder() + .tenantId(this.tenantId) + .clientId(this.clientId) + .identityClientOptions(identityClientOptions) + .build(); } /** @@ -75,80 +65,6 @@ public class SharedTokenCacheCredential implements TokenCredential { * */ @Override public Mono getToken(TokenRequestContext request) { - String authorityUrl = options.getAuthorityHost().replaceAll("/+$", "") + "/" + tenantId + "/"; - // Initialize here so that the constructor doesn't throw - if (pubClient == null) { - try { - PublicClientApplication.Builder applicationBuilder = PublicClientApplication.builder(this.clientId); - if (options.getExecutorService() != null) { - applicationBuilder.executorService(options.getExecutorService()); - } - - pubClient = applicationBuilder - .authority(authorityUrl) - .setTokenCacheAccessAspect(new PersistenceTokenCacheAccessAspect(persistenceSettings)) - .build(); - } catch (Throwable e) { - return Mono.error(e); - } - } - - // find if the Public Client app with the requested username exists - return Mono.fromFuture(pubClient.getAccounts()) - .onErrorResume(t -> Mono.error(new ClientAuthenticationException( - "Cannot get accounts from token cache. Error: " + t.getMessage(), null))) - .flatMap(set -> { - IAccount requestedAccount; - Map accounts = new HashMap<>(); // home account id -> account - - for (IAccount cached : set) { - if (username == null || username.equals(cached.username())) { - if (!accounts.containsKey(cached.homeAccountId())) { // only put the first one - accounts.put(cached.homeAccountId(), cached); - } - } - } - - if (accounts.size() == 0) { - if (username == null) { - return Mono.error(new RuntimeException("No accounts were discovered in the shared token cache." - + " To fix, authenticate through tooling supporting azure developer sign on.")); - } else { - return Mono.error(new RuntimeException(String.format("User account '%s' was not found in the " - + "shared token cache. Discovered Accounts: [ '%s' ]", username, set.stream() - .map(IAccount::username).distinct().collect(Collectors.joining(", "))))); - } - } else if (accounts.size() > 1) { - if (username == null) { - return Mono.error(new RuntimeException("Multiple accounts were discovered in the shared token " - + "cache. To fix, set the AZURE_USERNAME and AZURE_TENANT_ID environment variable to the " - + "preferred username, or specify it when constructing SharedTokenCacheCredential.")); - } else { - return Mono.error(new RuntimeException("Multiple entries for the user account " + username - + " were found in the shared token cache. This is not currently supported by the" - + " SharedTokenCacheCredential.")); - } - } else { - requestedAccount = accounts.values().iterator().next(); - } - - // if it does, then request the token - SilentParameters params = SilentParameters.builder( - new HashSet<>(request.getScopes()), requestedAccount) - .authorityUrl(authorityUrl) - .build(); - - CompletableFuture future; - try { - future = pubClient.acquireTokenSilently(params); - return Mono.fromFuture(() -> future).map(result -> - new AccessToken(result.accessToken(), - result.expiresOnDate().toInstant().atOffset(ZoneOffset.UTC))); - - } catch (MalformedURLException e) { - e.printStackTrace(); - return Mono.error(new RuntimeException("Token was not found")); - } - }); + return identityClient.authenticateWithSharedTokenCache(request, username); } } diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/SharedTokenCacheCredentialBuilder.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/SharedTokenCacheCredentialBuilder.java index 668425535c6d1..08a0ff63020a4 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/SharedTokenCacheCredentialBuilder.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/SharedTokenCacheCredentialBuilder.java @@ -3,15 +3,9 @@ package com.azure.identity; -import com.azure.core.util.logging.ClientLogger; import com.azure.identity.implementation.util.ValidationUtil; -import com.microsoft.aad.msal4jextensions.PersistenceSettings; -import java.nio.file.Path; -import java.util.ArrayList; import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.List; /** * Fluent credential builder for instantiating a {@link SharedTokenCacheCredential}. @@ -19,17 +13,7 @@ * @see SharedTokenCacheCredential */ public class SharedTokenCacheCredentialBuilder extends AadCredentialBuilderBase { - private final ClientLogger logger = new ClientLogger(SharedTokenCacheCredentialBuilder.class); private String username; - private String cacheFileName; - private Path cacheFileDirectory; - private String keychainService; - private String keychainAccount; - private String keyringName; - private KeyringItemSchema keyringItemSchema; - private String keyringItemName; - private final LinkedHashMap attributes = new LinkedHashMap<>(); // preserve order - private boolean useUnprotectedFileOnLinux = false; /** * Sets the username for the account. @@ -43,119 +27,6 @@ public SharedTokenCacheCredentialBuilder username(String username) { return this; } - /** - * Sets the location for the token cache file on Windows or Linux systems. The default - * location is {user home}/AppData/Local/.IdentityService/msal.cache on - * Windows and ~/.IdentityService/msal.cache on Linux. - * - * @param cacheFileLocation The location for the token cache file. - * - * @return The updated SharedTokenCacheCredentialBuilder object. - */ - public SharedTokenCacheCredentialBuilder cacheFileLocation(Path cacheFileLocation) { - this.cacheFileName = cacheFileLocation.getFileName().toString(); - this.cacheFileDirectory = cacheFileLocation.getParent(); - return this; - } - - /** - * Sets the service name for the Keychain item on MacOS. The default value is - * "Microsoft.Developer.IdentityService". - * - * @param serviceName The service name for the Keychain item. - * - * @return The updated SharedTokenCacheCredentialBuilder object. - */ - public SharedTokenCacheCredentialBuilder keychainService(String serviceName) { - this.keychainService = serviceName; - return this; - } - - /** - * Sets the account name for the Keychain item on MacOS. The default value is - * "MSALCache". - * - * @param accountName The account name for the Keychain item. - * - * @return The updated SharedTokenCacheCredentialBuilder object. - */ - public SharedTokenCacheCredentialBuilder keychainAccount(String accountName) { - this.keychainAccount = accountName; - return this; - } - - /** - * Sets the name of the Gnome keyring to store the cache on Gnome keyring enabled - * Linux systems. The default value is "default". - * - * @param keyringName The name of the Gnome keyring. - * - * @return The updated SharedTokenCacheCredentialBuilder object. - */ - public SharedTokenCacheCredentialBuilder keyringName(String keyringName) { - this.keyringName = keyringName; - return this; - } - - /** - * Sets the schema of the Gnome keyring to store the cache on Gnome keyring enabled - * Linux systems. The default value is KeyringItemSchema.GenericSecret. - * - * @param keyringItemSchema The schema of the Gnome keyring. - * - * @return The updated SharedTokenCacheCredentialBuilder object. - */ - public SharedTokenCacheCredentialBuilder keyringItemSchema(KeyringItemSchema keyringItemSchema) { - this.keyringItemSchema = keyringItemSchema; - return this; - } - - /** - * Sets the name of the Gnome keyring item to store the cache on Gnome keyring enabled - * Linux systems. The default value is "MSALCache". - * - * @param keyringItemName The name of the Gnome keyring item. - * - * @return The updated SharedTokenCacheCredentialBuilder object. - */ - public SharedTokenCacheCredentialBuilder keyringItemName(String keyringItemName) { - this.keyringItemName = keyringItemName; - return this; - } - - /** - * Adds an attribute to the Gnome keyring item to store the cache on Gnome keyring enabled - * Linux systems. Only 2 attributes are allowed. - * - * @param attributeName The name of the attribute. - * @param attributeValue The value of the attribute. - * - * @return The updated SharedTokenCacheCredentialBuilder object. - * @throws IllegalArgumentException if there are already 2 attributes - */ - public SharedTokenCacheCredentialBuilder addKeyringItemAttribute(String attributeName, String attributeValue) { - if (this.attributes.size() < 2) { - this.attributes.put(attributeName, attributeValue); - } else { - throw logger.logExceptionAsError(new IllegalArgumentException( - "Currently does not support more than 2 attributes for linux Keyring")); - } - return this; - } - - /** - * Sets whether to use an unprotected file specified by cacheFileLocation() instead of - * Gnome keyring on Linux. This is false by default. - * - * @param useUnprotectedFileOnLinux whether to use an unprotected file for cache storage. - * - * @return The updated SharedTokenCacheCredentialBuilder object. - */ - public SharedTokenCacheCredentialBuilder useUnprotectedFileOnLinux(boolean useUnprotectedFileOnLinux) { - this.useUnprotectedFileOnLinux = useUnprotectedFileOnLinux; - return this; - } - /** * Creates a new {@link SharedTokenCacheCredentialBuilder} with the current configurations. * @@ -163,25 +34,12 @@ public SharedTokenCacheCredentialBuilder useUnprotectedFileOnLinux(boolean useUn */ public SharedTokenCacheCredential build() { ValidationUtil.validate(getClass().getSimpleName(), new HashMap() {{ - put("cacheFileName", cacheFileName); - put("cacheFileDirectory", cacheFileDirectory); + put("cacheFileLocation", cacheFileLocation); }}); - List attributeKeyList = new ArrayList<>(attributes.keySet()); - while (attributeKeyList.size() < 2) { - attributeKeyList.add(null); - } - String key1 = attributeKeyList.get(0); - String key2 = attributeKeyList.get(1); - PersistenceSettings.Builder persistenceBuilder = PersistenceSettings.builder(cacheFileName, cacheFileDirectory) - .setLinuxUseUnprotectedFileAsCacheStorage(useUnprotectedFileOnLinux); - if (keychainService != null && keychainAccount != null) { - persistenceBuilder.setMacKeychain(keychainService, keychainAccount); - } - if (keyringName != null && keyringItemName != null && keyringItemSchema != null) { - persistenceBuilder.setLinuxKeyring(keyringName, keyringItemSchema.toString(), keyringItemName, - key1, attributes.getOrDefault(key1, null), key2, attributes.getOrDefault(key2, null)); + if (cacheFileLocation != null) { + identityClientOptions.setPersistenceSettings(cacheFileLocation, keychainService, keychainAccount, + keyringName, keyringItemSchema, keyringItemName, attributes, useUnprotectedFileOnLinux); } - return new SharedTokenCacheCredential( - username, clientId, tenantId, identityClientOptions, persistenceBuilder.build()); + return new SharedTokenCacheCredential(username, clientId, tenantId, identityClientOptions); } } diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java index 1de117cb308f5..73e0f6a364782 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java @@ -27,9 +27,12 @@ import com.microsoft.aad.msal4j.ClientCredentialParameters; import com.microsoft.aad.msal4j.ConfidentialClientApplication; import com.microsoft.aad.msal4j.DeviceCodeFlowParameters; +import com.microsoft.aad.msal4j.IAccount; +import com.microsoft.aad.msal4j.IAuthenticationResult; import com.microsoft.aad.msal4j.PublicClientApplication; import com.microsoft.aad.msal4j.SilentParameters; import com.microsoft.aad.msal4j.UserNamePasswordParameters; +import com.microsoft.aad.msal4jextensions.PersistenceTokenCacheAccessAspect; import reactor.core.publisher.Mono; import reactor.core.scheduler.Schedulers; @@ -56,6 +59,7 @@ import java.time.ZoneOffset; import java.time.format.DateTimeFormatter; import java.util.ArrayList; +import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Locale; @@ -63,7 +67,9 @@ import java.util.Random; import java.util.Scanner; import java.util.UUID; +import java.util.concurrent.CompletableFuture; import java.util.function.Consumer; +import java.util.stream.Collectors; /** * The identity client that contains APIs to retrieve access tokens @@ -140,6 +146,14 @@ public class IdentityClient { if (options.getExecutorService() != null) { publicClientApplicationBuilder.executorService(options.getExecutorService()); } + if (options.getPersistenceSettings() != null) { + try { + publicClientApplicationBuilder.setTokenCacheAccessAspect( + new PersistenceTokenCacheAccessAspect(options.getPersistenceSettings())); + } catch (IOException e) { + throw logger.logExceptionAsWarning(new IllegalStateException(e)); + } + } this.publicClientApplication = publicClientApplicationBuilder.build(); } } @@ -480,6 +494,83 @@ public Mono authenticateWithBrowserInteraction(TokenRequestContext re }); } + /** + * Gets token from shared token cache + * */ + public Mono authenticateWithSharedTokenCache(TokenRequestContext request, String username) { + String authorityUrl = options.getAuthorityHost().replaceAll("/+$", "") + "/" + tenantId + "/"; + // find if the Public Client app with the requested username exists + return Mono.fromFuture(publicClientApplication.getAccounts()) + .onErrorResume(t -> Mono.error(new ClientAuthenticationException( + "Cannot get accounts from token cache. Error: " + t.getMessage(), null))) + .flatMap(set -> { + IAccount requestedAccount; + Map accounts = new HashMap<>(); // home account id -> account + + for (IAccount cached : set) { + if (username == null || username.equals(cached.username())) { + if (!accounts.containsKey(cached.homeAccountId())) { // only put the first one + accounts.put(cached.homeAccountId(), cached); + } + } + } + + if (accounts.size() == 0) { + if (username == null) { + return Mono.error(new RuntimeException("No accounts were discovered in the shared token cache." + + " To fix, authenticate through tooling supporting azure developer sign on.")); + } else { + return Mono.error(new RuntimeException(String.format("User account '%s' was not found in the " + + "shared token cache. Discovered Accounts: [ '%s' ]", username, set.stream() + .map(IAccount::username).distinct().collect(Collectors.joining(", "))))); + } + } else if (accounts.size() > 1) { + if (username == null) { + return Mono.error(new RuntimeException("Multiple accounts were discovered in the shared token " + + "cache. To fix, set the AZURE_USERNAME and AZURE_TENANT_ID environment variable to the " + + "preferred username, or specify it when constructing SharedTokenCacheCredential.")); + } else { + return Mono.error(new RuntimeException("Multiple entries for the user account " + username + + " were found in the shared token cache. This is not currently supported by the" + + " SharedTokenCacheCredential.")); + } + } else { + requestedAccount = accounts.values().iterator().next(); + } + + // if it does, then request the token + SilentParameters params = SilentParameters.builder( + new HashSet<>(request.getScopes()), requestedAccount) + .authorityUrl(authorityUrl) + .build(); + + SilentParameters forceParams = SilentParameters.builder( + new HashSet<>(request.getScopes()), requestedAccount) + .authorityUrl(authorityUrl) + .forceRefresh(true) + .build(); + + CompletableFuture future; + try { + future = publicClientApplication.acquireTokenSilently(params); + return Mono.fromFuture(() -> future).map(result -> + new MsalToken(result, options)) + .filter(t -> !t.isExpired()) + .switchIfEmpty(Mono.defer(() -> Mono.fromFuture(() -> { + try { + return publicClientApplication.acquireTokenSilently(forceParams); + } catch (MalformedURLException e) { + throw logger.logExceptionAsWarning(new RuntimeException(e)); + } + } + ).map(result -> new MsalToken(result, options)))); + } catch (MalformedURLException e) { + e.printStackTrace(); + return Mono.error(new RuntimeException("Token was not found")); + } + }); + } + /** * Asynchronously acquire a token from the App Service Managed Service Identity endpoint. * diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClientOptions.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClientOptions.java index 66a07de85a262..32bf51d8297fc 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClientOptions.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClientOptions.java @@ -6,8 +6,14 @@ import com.azure.core.http.HttpClient; import com.azure.core.http.HttpPipeline; import com.azure.core.http.ProxyOptions; +import com.azure.identity.KeyringItemSchema; +import com.microsoft.aad.msal4jextensions.PersistenceSettings; +import java.nio.file.Path; import java.time.Duration; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.concurrent.ExecutorService; import java.util.concurrent.ForkJoinPool; @@ -27,6 +33,7 @@ public final class IdentityClientOptions { private ExecutorService executorService; private Duration tokenRefreshOffset = Duration.ofMinutes(2); private HttpClient httpClient; + private PersistenceSettings persistenceSettings; /** * Creates an instance of IdentityClientOptions with default settings. @@ -191,4 +198,35 @@ public IdentityClientOptions setHttpClient(HttpClient httpClient) { this.httpClient = httpClient; return this; } + + public PersistenceSettings getPersistenceSettings() { + return persistenceSettings; + } + + public IdentityClientOptions setPersistenceSettings(Path cacheFileLocation, String keychainService, + String keychainAccount, String keyringName, + KeyringItemSchema keyringItemSchema, String keyringItemName, + Map keyringItemAttributes, + boolean useUnprotectedFileOnLinux) { + String cacheFileName = cacheFileLocation.getFileName().toString(); + Path cacheFileDirectory = cacheFileLocation.getParent(); + List attributeKeyList = new ArrayList<>(keyringItemAttributes.keySet()); + while (attributeKeyList.size() < 2) { + attributeKeyList.add(null); + } + String key1 = attributeKeyList.get(0); + String key2 = attributeKeyList.get(1); + PersistenceSettings.Builder persistenceBuilder = PersistenceSettings.builder(cacheFileName, cacheFileDirectory) + .setLinuxUseUnprotectedFileAsCacheStorage(useUnprotectedFileOnLinux); + if (keychainService != null && keychainAccount != null) { + persistenceBuilder.setMacKeychain(keychainService, keychainAccount); + } + if (keyringName != null && keyringItemName != null && keyringItemSchema != null) { + persistenceBuilder.setLinuxKeyring(keyringName, keyringItemSchema.toString(), keyringItemName, + key1, keyringItemAttributes.getOrDefault(key1, null), + key2, keyringItemAttributes.getOrDefault(key2, null)); + } + this.persistenceSettings = persistenceBuilder.build(); + return this; + } } diff --git a/sdk/identity/perf-test/README.md b/sdk/identity/perf-test/README.md new file mode 100644 index 0000000000000..a291790c30c31 --- /dev/null +++ b/sdk/identity/perf-test/README.md @@ -0,0 +1,34 @@ +# Azure Storage Performance test client library for Java + +Represents Performance tests for Azure Storage SDK for Java. + +## Getting started + +### Prerequisites + +- Java Development Kit (JDK) with version 8 or above + +### Adding the package to your product + + +## Key concepts + + +## Examples + +## Troubleshooting + +## Next steps + +## Contributing + +If you would like to become an active contributor to this project please follow the instructions provided in [Microsoft +Azure Projects Contribution Guidelines](http://azure.github.io/guidelines.html). + +1. Fork it +1. Create your feature branch (`git checkout -b my-new-feature`) +1. Commit your changes (`git commit -am 'Add some feature'`) +1. Push to the branch (`git push origin my-new-feature`) +1. Create new Pull Request + +![Impressions](https://azure-sdk-impressions.azurewebsites.net/api/impressions/azure-sdk-for-java%2Fsdk%2Fstorage%2Fperf-test-core%2FREADME.png) diff --git a/sdk/identity/perf-test/pom.xml b/sdk/identity/perf-test/pom.xml new file mode 100644 index 0000000000000..fd079eae0f8ca --- /dev/null +++ b/sdk/identity/perf-test/pom.xml @@ -0,0 +1,68 @@ + + + + + + com.azure + azure-client-sdk-parent + 1.7.0 + ../../../pom.client.xml + + + 4.0.0 + + com.azure + azure-identity-perf + 1.0.0-beta.1 + jar + + + UTF-8 + 1.8 + 1.8 + + + + + com.azure + azure-identity + 1.1.0-beta.3 + + + com.azure + perf-test-core + 1.0.0-beta.1 + + + + + + + org.apache.maven.plugins + maven-assembly-plugin + 3.2.0 + + + package + + single + + + + + + com.azure.identity.perf.App + + + + + jar-with-dependencies + + + + + + + + diff --git a/sdk/identity/perf-test/src/main/java/com/azure/identity/ReadOnlyPersistenceTokenCacheAccessAspect.java b/sdk/identity/perf-test/src/main/java/com/azure/identity/ReadOnlyPersistenceTokenCacheAccessAspect.java new file mode 100644 index 0000000000000..1a9ddf3e6370e --- /dev/null +++ b/sdk/identity/perf-test/src/main/java/com/azure/identity/ReadOnlyPersistenceTokenCacheAccessAspect.java @@ -0,0 +1,18 @@ +package com.azure.identity; + +import com.microsoft.aad.msal4j.ITokenCacheAccessContext; +import com.microsoft.aad.msal4jextensions.PersistenceSettings; +import com.microsoft.aad.msal4jextensions.PersistenceTokenCacheAccessAspect; + +import java.io.IOException; + +public class ReadOnlyPersistenceTokenCacheAccessAspect extends PersistenceTokenCacheAccessAspect { + public ReadOnlyPersistenceTokenCacheAccessAspect(PersistenceSettings persistenceSettings) throws IOException { + super(persistenceSettings); + } + + @Override + public void afterCacheAccess(ITokenCacheAccessContext iTokenCacheAccessContext) { + // do nothing + } +} diff --git a/sdk/identity/perf-test/src/main/java/com/azure/identity/ReadOnlySharedTokenCacheCredential.java b/sdk/identity/perf-test/src/main/java/com/azure/identity/ReadOnlySharedTokenCacheCredential.java new file mode 100644 index 0000000000000..3e48e346df70b --- /dev/null +++ b/sdk/identity/perf-test/src/main/java/com/azure/identity/ReadOnlySharedTokenCacheCredential.java @@ -0,0 +1,139 @@ +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.core.exception.ClientAuthenticationException; +import com.azure.core.util.Configuration; +import com.azure.identity.implementation.IdentityClientOptions; +import com.microsoft.aad.msal4j.IAccount; +import com.microsoft.aad.msal4j.IAuthenticationResult; +import com.microsoft.aad.msal4j.PublicClientApplication; +import com.microsoft.aad.msal4j.SilentParameters; +import reactor.core.publisher.Mono; + +import java.net.MalformedURLException; +import java.time.ZoneOffset; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; + +public class ReadOnlySharedTokenCacheCredential implements TokenCredential { + private final String username; + private final String clientId; + private final String tenantId; + private final IdentityClientOptions options; + + private PublicClientApplication pubClient = null; + + /** + * Creates an instance of the Shared Token Cache Credential Provider. + * + * @param username the username of the account for the application + * @param clientId the client ID of the application + * @param identityClientOptions the options for configuring the identity client + */ + ReadOnlySharedTokenCacheCredential(String username, String clientId, String tenantId, + IdentityClientOptions identityClientOptions) { + Configuration configuration = Configuration.getGlobalConfiguration().clone(); + + if (username == null) { + this.username = configuration.get(Configuration.PROPERTY_AZURE_USERNAME); + } else { + this.username = username; + } + if (clientId == null) { + this.clientId = configuration.get(Configuration.PROPERTY_AZURE_CLIENT_ID); + } else { + this.clientId = clientId; + } + if (tenantId == null) { + this.tenantId = configuration.contains(Configuration.PROPERTY_AZURE_TENANT_ID) + ? configuration.get(Configuration.PROPERTY_AZURE_TENANT_ID) : "common"; + } else { + this.tenantId = tenantId; + } + this.options = identityClientOptions; + } + + @Override + public Mono getToken(TokenRequestContext request) { + String authorityUrl = options.getAuthorityHost().replaceAll("/+$", "") + "/" + tenantId + "/"; + // Initialize here so that the constructor doesn't throw + if (pubClient == null) { + try { + PublicClientApplication.Builder applicationBuilder = PublicClientApplication.builder(this.clientId); + if (options.getExecutorService() != null) { + applicationBuilder.executorService(options.getExecutorService()); + } + + pubClient = applicationBuilder + .authority(authorityUrl) + .setTokenCacheAccessAspect(new ReadOnlyPersistenceTokenCacheAccessAspect(options.getPersistenceSettings())) + .build(); + } catch (Throwable e) { + return Mono.error(e); + } + } + + // find if the Public Client app with the requested username exists + return Mono.fromFuture(pubClient.getAccounts()) + .onErrorResume(t -> Mono.error(new ClientAuthenticationException( + "Cannot get accounts from token cache. Error: " + t.getMessage(), null))) + .flatMap(set -> { + IAccount requestedAccount; + Map accounts = new HashMap<>(); // home account id -> account + + for (IAccount cached : set) { + if (username == null || username.equals(cached.username())) { + if (!accounts.containsKey(cached.homeAccountId())) { // only put the first one + accounts.put(cached.homeAccountId(), cached); + } + } + } + + if (accounts.size() == 0) { + if (username == null) { + return Mono.error(new RuntimeException("No accounts were discovered in the shared token cache." + + " To fix, authenticate through tooling supporting azure developer sign on.")); + } else { + return Mono.error(new RuntimeException(String.format("User account '%s' was not found in the " + + "shared token cache. Discovered Accounts: [ '%s' ]", username, set.stream() + .map(IAccount::username).distinct().collect(Collectors.joining(", "))))); + } + } else if (accounts.size() > 1) { + if (username == null) { + return Mono.error(new RuntimeException("Multiple accounts were discovered in the shared token " + + "cache. To fix, set the AZURE_USERNAME and AZURE_TENANT_ID environment variable to the " + + "preferred username, or specify it when constructing SharedTokenCacheCredential.")); + } else { + return Mono.error(new RuntimeException("Multiple entries for the user account " + username + + " were found in the shared token cache. This is not currently supported by the" + + " SharedTokenCacheCredential.")); + } + } else { + requestedAccount = accounts.values().iterator().next(); + } + + // if it does, then request the token + SilentParameters params = SilentParameters.builder( + new HashSet<>(request.getScopes()), requestedAccount) + .authorityUrl(authorityUrl) + .build(); + + CompletableFuture future; + try { + future = pubClient.acquireTokenSilently(params); + return Mono.fromFuture(() -> future).map(result -> + new AccessToken(result.accessToken(), + result.expiresOnDate().toInstant().atOffset(ZoneOffset.UTC))); + + } catch (MalformedURLException e) { + e.printStackTrace(); + return Mono.error(new RuntimeException("Token was not found")); + } + }); + } +} diff --git a/sdk/identity/perf-test/src/main/java/com/azure/identity/ReadOnlySharedTokenCacheCredentialBuilder.java b/sdk/identity/perf-test/src/main/java/com/azure/identity/ReadOnlySharedTokenCacheCredentialBuilder.java new file mode 100644 index 0000000000000..6e083fcdf358a --- /dev/null +++ b/sdk/identity/perf-test/src/main/java/com/azure/identity/ReadOnlySharedTokenCacheCredentialBuilder.java @@ -0,0 +1,45 @@ +// 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 SharedTokenCacheCredential}. + * + * @see SharedTokenCacheCredential + */ +public class ReadOnlySharedTokenCacheCredentialBuilder extends AadCredentialBuilderBase { + private String username; + + /** + * Sets the username for the account. + * + * @param username The username for the account. + * + * @return The updated SharedTokenCacheCredentialBuilder object. + */ + public ReadOnlySharedTokenCacheCredentialBuilder username(String username) { + this.username = username; + return this; + } + + /** + * Creates a new {@link ReadOnlySharedTokenCacheCredentialBuilder} with the current configurations. + * + * @return a {@link ReadOnlySharedTokenCacheCredentialBuilder} with the current configurations. + */ + public ReadOnlySharedTokenCacheCredential build() { + ValidationUtil.validate(getClass().getSimpleName(), new HashMap() {{ + put("cacheFileLocation", cacheFileLocation); + }}); + if (cacheFileLocation != null) { + identityClientOptions.setPersistenceSettings(cacheFileLocation, keychainService, keychainAccount, + keyringName, keyringItemSchema, keyringItemName, attributes, useUnprotectedFileOnLinux); + } + return new ReadOnlySharedTokenCacheCredential(username, clientId, tenantId, identityClientOptions); + } +} diff --git a/sdk/identity/perf-test/src/main/java/com/azure/identity/perf/App.java b/sdk/identity/perf-test/src/main/java/com/azure/identity/perf/App.java new file mode 100644 index 0000000000000..9a3d5a0b13a38 --- /dev/null +++ b/sdk/identity/perf-test/src/main/java/com/azure/identity/perf/App.java @@ -0,0 +1,38 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.identity.perf; + +import com.azure.perf.test.core.PerfStressProgram; + +/** + * Runs the Identity performance tests. + * + * Test scenarios: + * 1. Read cache from a single process + * 2. Read cache from multiple processes + * 3. Write cache from a single process + * 4. Write cache from multiple processes + * + *

To run from command line. Package the project into a jar with dependencies via mvn clean package. + * Then run the program via java -jar 'compiled-jar-with-dependencies-path'

+ * + *

To run from IDE, set all the required environment variables in IntelliJ via Run -> EditConfigurations section. + * Then run the App's main method via IDE.

+ */ +public class App { + public static void main(String[] args) { + Class[] testClasses; + + try { + testClasses = new Class[] { + Class.forName("com.azure.identity.perf.ReadCacheSingleProcessTest"), + Class.forName("com.azure.identity.perf.WriteCacheSingleProcessTest"), + }; + } catch (ClassNotFoundException e) { + throw new RuntimeException(e); + } + + PerfStressProgram.run(testClasses, args); + } +} diff --git a/sdk/identity/perf-test/src/main/java/com/azure/identity/perf/ReadCacheSingleProcessTest.java b/sdk/identity/perf-test/src/main/java/com/azure/identity/perf/ReadCacheSingleProcessTest.java new file mode 100644 index 0000000000000..601f27c41f75e --- /dev/null +++ b/sdk/identity/perf-test/src/main/java/com/azure/identity/perf/ReadCacheSingleProcessTest.java @@ -0,0 +1,38 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.identity.perf; + +import com.azure.identity.SharedTokenCacheCredential; +import com.azure.identity.SharedTokenCacheCredentialBuilder; +import com.azure.identity.perf.core.ServiceTest; +import com.azure.perf.test.core.SizeOptions; +import reactor.core.publisher.Mono; + +import java.nio.file.Paths; + +public class ReadCacheSingleProcessTest extends ServiceTest { + private final SharedTokenCacheCredential credential; + + public ReadCacheSingleProcessTest(SizeOptions options) { + super(options); + credential = new SharedTokenCacheCredentialBuilder() + .clientId(CLI_CLIENT_ID) + .keychainService("Microsoft.Developer.IdentityService") + .keychainAccount("MSALCache") + .cacheFileLocation(Paths.get(System.getProperty("user.home"), + ".IdentityService", "msal.cache")) + .build(); + } + + // Perform the API call to be tested here + @Override + public void run() { + credential.getToken(ARM_TOKEN_REQUEST_CONTEXT).block(); + } + + @Override + public Mono runAsync() { + return credential.getToken(ARM_TOKEN_REQUEST_CONTEXT).then(); + } +} diff --git a/sdk/identity/perf-test/src/main/java/com/azure/identity/perf/WriteCacheSingleProcessTest.java b/sdk/identity/perf-test/src/main/java/com/azure/identity/perf/WriteCacheSingleProcessTest.java new file mode 100644 index 0000000000000..3a48985f7e2b3 --- /dev/null +++ b/sdk/identity/perf-test/src/main/java/com/azure/identity/perf/WriteCacheSingleProcessTest.java @@ -0,0 +1,40 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.identity.perf; + +import com.azure.identity.SharedTokenCacheCredential; +import com.azure.identity.SharedTokenCacheCredentialBuilder; +import com.azure.identity.perf.core.ServiceTest; +import com.azure.perf.test.core.SizeOptions; +import reactor.core.publisher.Mono; + +import java.nio.file.Paths; +import java.time.Duration; + +public class WriteCacheSingleProcessTest extends ServiceTest { + private final SharedTokenCacheCredential credential; + + public WriteCacheSingleProcessTest(SizeOptions options) { + super(options); + credential = new SharedTokenCacheCredentialBuilder() + .clientId(CLI_CLIENT_ID) + .keychainService("Microsoft.Developer.IdentityService") + .keychainAccount("MSALCache") + .cacheFileLocation(Paths.get(System.getProperty("user.home"), + ".IdentityService", "msal.cache")) + .tokenRefreshOffset(Duration.ofMinutes(60)) + .build(); + } + + // Perform the API call to be tested here + @Override + public void run() { + credential.getToken(ARM_TOKEN_REQUEST_CONTEXT).block(); + } + + @Override + public Mono runAsync() { + return credential.getToken(ARM_TOKEN_REQUEST_CONTEXT).then(); + } +} diff --git a/sdk/identity/perf-test/src/main/java/com/azure/identity/perf/core/ServiceTest.java b/sdk/identity/perf-test/src/main/java/com/azure/identity/perf/core/ServiceTest.java new file mode 100644 index 0000000000000..3ab4af63e23a8 --- /dev/null +++ b/sdk/identity/perf-test/src/main/java/com/azure/identity/perf/core/ServiceTest.java @@ -0,0 +1,29 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.identity.perf.core; + +import com.azure.core.credential.TokenRequestContext; +import com.azure.perf.test.core.PerfStressOptions; +import com.azure.perf.test.core.PerfStressTest; +import reactor.core.publisher.Mono; + +public abstract class ServiceTest extends PerfStressTest { + protected static final String CLI_CLIENT_ID = "04b07795-8ddb-461a-bbee-02f9e1bf7b46"; + protected static final TokenRequestContext ARM_TOKEN_REQUEST_CONTEXT = new TokenRequestContext() + .addScopes("https://management.azure.com/.default"); + + public ServiceTest(TOptions options) { + super(options); + } + + @Override + public Mono globalSetupAsync() { + // Populate the token cache for tests + return super.globalSetupAsync() +// .then(new InteractiveBrowserCredentialBuilder().port(8765) +// .clientId(CLI_CLIENT_ID).build() +// .getToken(ARM_TOKEN_REQUEST_CONTEXT)) + .then(); + } +} diff --git a/sdk/identity/pom.service.xml b/sdk/identity/pom.service.xml index c37d14e46a057..1dfad479232bc 100644 --- a/sdk/identity/pom.service.xml +++ b/sdk/identity/pom.service.xml @@ -15,5 +15,6 @@ ../core/azure-core-http-netty ../core/azure-core-test azure-identity + perf-test diff --git a/sdk/keyvault/azure-security-keyvault-secrets/src/samples/java/com/azure/security/keyvault/secrets/PersistentTokenCacheDemo.java b/sdk/keyvault/azure-security-keyvault-secrets/src/samples/java/com/azure/security/keyvault/secrets/PersistentTokenCacheDemo.java index fcfbbacf9762e..c408eec083668 100644 --- a/sdk/keyvault/azure-security-keyvault-secrets/src/samples/java/com/azure/security/keyvault/secrets/PersistentTokenCacheDemo.java +++ b/sdk/keyvault/azure-security-keyvault-secrets/src/samples/java/com/azure/security/keyvault/secrets/PersistentTokenCacheDemo.java @@ -7,6 +7,8 @@ import com.azure.identity.DefaultAzureCredentialBuilder; import com.azure.security.keyvault.secrets.models.KeyVaultSecret; +import java.time.Duration; + /** * Sample showing how to authenticate to key vault with a shared token cache credential. */ @@ -19,7 +21,8 @@ public class PersistentTokenCacheDemo { public static void main(String[] args) { // Wrote to AZURE_USERNAME env variable - DefaultAzureCredential defaultCredential = new DefaultAzureCredentialBuilder().build(); + DefaultAzureCredential defaultCredential = new DefaultAzureCredentialBuilder() + .tokenRefreshOffset(Duration.ofMinutes(60)).build(); SecretClient client = new SecretClientBuilder() .vaultUrl("https://persistentcachedemo.vault.azure.net") From dd341e7d557ee525145f9f7b881c07709ee0d85e Mon Sep 17 00:00:00 2001 From: Jianghao Lu Date: Fri, 3 Apr 2020 14:20:52 -0700 Subject: [PATCH 10/45] more perf testing work --- .../InteractiveBrowserCredentialBuilder.java | 4 + .../implementation/IdentityClient.java | 15 +- ...OnlyPersistenceTokenCacheAccessAspect.java | 18 --- .../ReadOnlySharedTokenCacheCredential.java | 139 ------------------ ...OnlySharedTokenCacheCredentialBuilder.java | 45 ------ .../java/com/azure/identity/perf/App.java | 4 +- ...eSingleProcessTest.java => ReadCache.java} | 17 ++- ...SingleProcessTest.java => WriteCache.java} | 17 ++- .../azure/identity/perf/core/ServiceTest.java | 17 ++- 9 files changed, 60 insertions(+), 216 deletions(-) delete mode 100644 sdk/identity/perf-test/src/main/java/com/azure/identity/ReadOnlyPersistenceTokenCacheAccessAspect.java delete mode 100644 sdk/identity/perf-test/src/main/java/com/azure/identity/ReadOnlySharedTokenCacheCredential.java delete mode 100644 sdk/identity/perf-test/src/main/java/com/azure/identity/ReadOnlySharedTokenCacheCredentialBuilder.java rename sdk/identity/perf-test/src/main/java/com/azure/identity/perf/{ReadCacheSingleProcessTest.java => ReadCache.java} (63%) rename sdk/identity/perf-test/src/main/java/com/azure/identity/perf/{WriteCacheSingleProcessTest.java => WriteCache.java} (65%) diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/InteractiveBrowserCredentialBuilder.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/InteractiveBrowserCredentialBuilder.java index d0ef8e3a863f3..b94a9d8446de6 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/InteractiveBrowserCredentialBuilder.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/InteractiveBrowserCredentialBuilder.java @@ -37,6 +37,10 @@ public InteractiveBrowserCredential build() { put("clientId", clientId); put("port", port); }}); + if (cacheFileLocation != null) { + identityClientOptions.setPersistenceSettings(cacheFileLocation, keychainService, keychainAccount, + keyringName, keyringItemSchema, keyringItemName, attributes, useUnprotectedFileOnLinux); + } return new InteractiveBrowserCredential(clientId, tenantId, port, identityClientOptions); } } diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java index 73e0f6a364782..64ae007e1ddda 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java @@ -398,15 +398,28 @@ public Mono authenticateWithUsernamePassword(TokenRequestContext requ */ public Mono authenticateWithUserRefreshToken(TokenRequestContext request, MsalToken msalToken) { SilentParameters parameters; + SilentParameters forceParameters; if (msalToken.getAccount() != null) { parameters = SilentParameters.builder(new HashSet<>(request.getScopes()), msalToken.getAccount()).build(); + forceParameters = SilentParameters.builder(new HashSet<>(request.getScopes()), msalToken.getAccount()) + .forceRefresh(true).build(); } else { parameters = SilentParameters.builder(new HashSet<>(request.getScopes())).build(); + forceParameters = SilentParameters.builder(new HashSet<>(request.getScopes())).forceRefresh(true).build(); } return Mono.defer(() -> { try { return Mono.fromFuture(publicClientApplication.acquireTokenSilently(parameters)) - .map(ar -> new MsalToken(ar, options)); + .map(ar -> new MsalToken(ar, options)) + .filter(t -> !t.isExpired()) + .switchIfEmpty(Mono.defer(() -> Mono.fromFuture(() -> { + try { + return publicClientApplication.acquireTokenSilently(forceParameters); + } catch (MalformedURLException e) { + throw logger.logExceptionAsWarning(new RuntimeException(e)); + } + } + ).map(result -> new MsalToken(result, options)))); } catch (MalformedURLException e) { return Mono.error(e); } diff --git a/sdk/identity/perf-test/src/main/java/com/azure/identity/ReadOnlyPersistenceTokenCacheAccessAspect.java b/sdk/identity/perf-test/src/main/java/com/azure/identity/ReadOnlyPersistenceTokenCacheAccessAspect.java deleted file mode 100644 index 1a9ddf3e6370e..0000000000000 --- a/sdk/identity/perf-test/src/main/java/com/azure/identity/ReadOnlyPersistenceTokenCacheAccessAspect.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.azure.identity; - -import com.microsoft.aad.msal4j.ITokenCacheAccessContext; -import com.microsoft.aad.msal4jextensions.PersistenceSettings; -import com.microsoft.aad.msal4jextensions.PersistenceTokenCacheAccessAspect; - -import java.io.IOException; - -public class ReadOnlyPersistenceTokenCacheAccessAspect extends PersistenceTokenCacheAccessAspect { - public ReadOnlyPersistenceTokenCacheAccessAspect(PersistenceSettings persistenceSettings) throws IOException { - super(persistenceSettings); - } - - @Override - public void afterCacheAccess(ITokenCacheAccessContext iTokenCacheAccessContext) { - // do nothing - } -} diff --git a/sdk/identity/perf-test/src/main/java/com/azure/identity/ReadOnlySharedTokenCacheCredential.java b/sdk/identity/perf-test/src/main/java/com/azure/identity/ReadOnlySharedTokenCacheCredential.java deleted file mode 100644 index 3e48e346df70b..0000000000000 --- a/sdk/identity/perf-test/src/main/java/com/azure/identity/ReadOnlySharedTokenCacheCredential.java +++ /dev/null @@ -1,139 +0,0 @@ -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.core.exception.ClientAuthenticationException; -import com.azure.core.util.Configuration; -import com.azure.identity.implementation.IdentityClientOptions; -import com.microsoft.aad.msal4j.IAccount; -import com.microsoft.aad.msal4j.IAuthenticationResult; -import com.microsoft.aad.msal4j.PublicClientApplication; -import com.microsoft.aad.msal4j.SilentParameters; -import reactor.core.publisher.Mono; - -import java.net.MalformedURLException; -import java.time.ZoneOffset; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.concurrent.CompletableFuture; -import java.util.stream.Collectors; - -public class ReadOnlySharedTokenCacheCredential implements TokenCredential { - private final String username; - private final String clientId; - private final String tenantId; - private final IdentityClientOptions options; - - private PublicClientApplication pubClient = null; - - /** - * Creates an instance of the Shared Token Cache Credential Provider. - * - * @param username the username of the account for the application - * @param clientId the client ID of the application - * @param identityClientOptions the options for configuring the identity client - */ - ReadOnlySharedTokenCacheCredential(String username, String clientId, String tenantId, - IdentityClientOptions identityClientOptions) { - Configuration configuration = Configuration.getGlobalConfiguration().clone(); - - if (username == null) { - this.username = configuration.get(Configuration.PROPERTY_AZURE_USERNAME); - } else { - this.username = username; - } - if (clientId == null) { - this.clientId = configuration.get(Configuration.PROPERTY_AZURE_CLIENT_ID); - } else { - this.clientId = clientId; - } - if (tenantId == null) { - this.tenantId = configuration.contains(Configuration.PROPERTY_AZURE_TENANT_ID) - ? configuration.get(Configuration.PROPERTY_AZURE_TENANT_ID) : "common"; - } else { - this.tenantId = tenantId; - } - this.options = identityClientOptions; - } - - @Override - public Mono getToken(TokenRequestContext request) { - String authorityUrl = options.getAuthorityHost().replaceAll("/+$", "") + "/" + tenantId + "/"; - // Initialize here so that the constructor doesn't throw - if (pubClient == null) { - try { - PublicClientApplication.Builder applicationBuilder = PublicClientApplication.builder(this.clientId); - if (options.getExecutorService() != null) { - applicationBuilder.executorService(options.getExecutorService()); - } - - pubClient = applicationBuilder - .authority(authorityUrl) - .setTokenCacheAccessAspect(new ReadOnlyPersistenceTokenCacheAccessAspect(options.getPersistenceSettings())) - .build(); - } catch (Throwable e) { - return Mono.error(e); - } - } - - // find if the Public Client app with the requested username exists - return Mono.fromFuture(pubClient.getAccounts()) - .onErrorResume(t -> Mono.error(new ClientAuthenticationException( - "Cannot get accounts from token cache. Error: " + t.getMessage(), null))) - .flatMap(set -> { - IAccount requestedAccount; - Map accounts = new HashMap<>(); // home account id -> account - - for (IAccount cached : set) { - if (username == null || username.equals(cached.username())) { - if (!accounts.containsKey(cached.homeAccountId())) { // only put the first one - accounts.put(cached.homeAccountId(), cached); - } - } - } - - if (accounts.size() == 0) { - if (username == null) { - return Mono.error(new RuntimeException("No accounts were discovered in the shared token cache." - + " To fix, authenticate through tooling supporting azure developer sign on.")); - } else { - return Mono.error(new RuntimeException(String.format("User account '%s' was not found in the " - + "shared token cache. Discovered Accounts: [ '%s' ]", username, set.stream() - .map(IAccount::username).distinct().collect(Collectors.joining(", "))))); - } - } else if (accounts.size() > 1) { - if (username == null) { - return Mono.error(new RuntimeException("Multiple accounts were discovered in the shared token " - + "cache. To fix, set the AZURE_USERNAME and AZURE_TENANT_ID environment variable to the " - + "preferred username, or specify it when constructing SharedTokenCacheCredential.")); - } else { - return Mono.error(new RuntimeException("Multiple entries for the user account " + username - + " were found in the shared token cache. This is not currently supported by the" - + " SharedTokenCacheCredential.")); - } - } else { - requestedAccount = accounts.values().iterator().next(); - } - - // if it does, then request the token - SilentParameters params = SilentParameters.builder( - new HashSet<>(request.getScopes()), requestedAccount) - .authorityUrl(authorityUrl) - .build(); - - CompletableFuture future; - try { - future = pubClient.acquireTokenSilently(params); - return Mono.fromFuture(() -> future).map(result -> - new AccessToken(result.accessToken(), - result.expiresOnDate().toInstant().atOffset(ZoneOffset.UTC))); - - } catch (MalformedURLException e) { - e.printStackTrace(); - return Mono.error(new RuntimeException("Token was not found")); - } - }); - } -} diff --git a/sdk/identity/perf-test/src/main/java/com/azure/identity/ReadOnlySharedTokenCacheCredentialBuilder.java b/sdk/identity/perf-test/src/main/java/com/azure/identity/ReadOnlySharedTokenCacheCredentialBuilder.java deleted file mode 100644 index 6e083fcdf358a..0000000000000 --- a/sdk/identity/perf-test/src/main/java/com/azure/identity/ReadOnlySharedTokenCacheCredentialBuilder.java +++ /dev/null @@ -1,45 +0,0 @@ -// 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 SharedTokenCacheCredential}. - * - * @see SharedTokenCacheCredential - */ -public class ReadOnlySharedTokenCacheCredentialBuilder extends AadCredentialBuilderBase { - private String username; - - /** - * Sets the username for the account. - * - * @param username The username for the account. - * - * @return The updated SharedTokenCacheCredentialBuilder object. - */ - public ReadOnlySharedTokenCacheCredentialBuilder username(String username) { - this.username = username; - return this; - } - - /** - * Creates a new {@link ReadOnlySharedTokenCacheCredentialBuilder} with the current configurations. - * - * @return a {@link ReadOnlySharedTokenCacheCredentialBuilder} with the current configurations. - */ - public ReadOnlySharedTokenCacheCredential build() { - ValidationUtil.validate(getClass().getSimpleName(), new HashMap() {{ - put("cacheFileLocation", cacheFileLocation); - }}); - if (cacheFileLocation != null) { - identityClientOptions.setPersistenceSettings(cacheFileLocation, keychainService, keychainAccount, - keyringName, keyringItemSchema, keyringItemName, attributes, useUnprotectedFileOnLinux); - } - return new ReadOnlySharedTokenCacheCredential(username, clientId, tenantId, identityClientOptions); - } -} diff --git a/sdk/identity/perf-test/src/main/java/com/azure/identity/perf/App.java b/sdk/identity/perf-test/src/main/java/com/azure/identity/perf/App.java index 9a3d5a0b13a38..93f28cb15b7bd 100644 --- a/sdk/identity/perf-test/src/main/java/com/azure/identity/perf/App.java +++ b/sdk/identity/perf-test/src/main/java/com/azure/identity/perf/App.java @@ -26,8 +26,8 @@ public static void main(String[] args) { try { testClasses = new Class[] { - Class.forName("com.azure.identity.perf.ReadCacheSingleProcessTest"), - Class.forName("com.azure.identity.perf.WriteCacheSingleProcessTest"), + Class.forName("com.azure.identity.perf.ReadCache"), + Class.forName("com.azure.identity.perf.WriteCache"), }; } catch (ClassNotFoundException e) { throw new RuntimeException(e); diff --git a/sdk/identity/perf-test/src/main/java/com/azure/identity/perf/ReadCacheSingleProcessTest.java b/sdk/identity/perf-test/src/main/java/com/azure/identity/perf/ReadCache.java similarity index 63% rename from sdk/identity/perf-test/src/main/java/com/azure/identity/perf/ReadCacheSingleProcessTest.java rename to sdk/identity/perf-test/src/main/java/com/azure/identity/perf/ReadCache.java index 601f27c41f75e..9621cd0751f89 100644 --- a/sdk/identity/perf-test/src/main/java/com/azure/identity/perf/ReadCacheSingleProcessTest.java +++ b/sdk/identity/perf-test/src/main/java/com/azure/identity/perf/ReadCache.java @@ -7,21 +7,30 @@ import com.azure.identity.SharedTokenCacheCredentialBuilder; import com.azure.identity.perf.core.ServiceTest; import com.azure.perf.test.core.SizeOptions; +import com.sun.jna.Platform; import reactor.core.publisher.Mono; +import java.nio.file.Path; import java.nio.file.Paths; -public class ReadCacheSingleProcessTest extends ServiceTest { +public class ReadCache extends ServiceTest { private final SharedTokenCacheCredential credential; - public ReadCacheSingleProcessTest(SizeOptions options) { + public ReadCache(SizeOptions options) { super(options); + Path cacheFileLocation; + if (Platform.isWindows()) { + cacheFileLocation = Paths.get(System.getProperty("user.home"), + "AppData", "Local", ".IdentityService", "msal.cache"); + } else { + cacheFileLocation = Paths.get(System.getProperty("user.home"), + ".IdentityService", "msal.cache"); + } credential = new SharedTokenCacheCredentialBuilder() .clientId(CLI_CLIENT_ID) .keychainService("Microsoft.Developer.IdentityService") .keychainAccount("MSALCache") - .cacheFileLocation(Paths.get(System.getProperty("user.home"), - ".IdentityService", "msal.cache")) + .cacheFileLocation(cacheFileLocation) .build(); } diff --git a/sdk/identity/perf-test/src/main/java/com/azure/identity/perf/WriteCacheSingleProcessTest.java b/sdk/identity/perf-test/src/main/java/com/azure/identity/perf/WriteCache.java similarity index 65% rename from sdk/identity/perf-test/src/main/java/com/azure/identity/perf/WriteCacheSingleProcessTest.java rename to sdk/identity/perf-test/src/main/java/com/azure/identity/perf/WriteCache.java index 3a48985f7e2b3..59b39b462382e 100644 --- a/sdk/identity/perf-test/src/main/java/com/azure/identity/perf/WriteCacheSingleProcessTest.java +++ b/sdk/identity/perf-test/src/main/java/com/azure/identity/perf/WriteCache.java @@ -7,22 +7,31 @@ import com.azure.identity.SharedTokenCacheCredentialBuilder; import com.azure.identity.perf.core.ServiceTest; import com.azure.perf.test.core.SizeOptions; +import com.sun.jna.Platform; import reactor.core.publisher.Mono; +import java.nio.file.Path; import java.nio.file.Paths; import java.time.Duration; -public class WriteCacheSingleProcessTest extends ServiceTest { +public class WriteCache extends ServiceTest { private final SharedTokenCacheCredential credential; - public WriteCacheSingleProcessTest(SizeOptions options) { + public WriteCache(SizeOptions options) { super(options); + Path cacheFileLocation; + if (Platform.isWindows()) { + cacheFileLocation = Paths.get(System.getProperty("user.home"), + "AppData", "Local", ".IdentityService", "msal.cache"); + } else { + cacheFileLocation = Paths.get(System.getProperty("user.home"), + ".IdentityService", "msal.cache"); + } credential = new SharedTokenCacheCredentialBuilder() .clientId(CLI_CLIENT_ID) .keychainService("Microsoft.Developer.IdentityService") .keychainAccount("MSALCache") - .cacheFileLocation(Paths.get(System.getProperty("user.home"), - ".IdentityService", "msal.cache")) + .cacheFileLocation(cacheFileLocation) .tokenRefreshOffset(Duration.ofMinutes(60)) .build(); } diff --git a/sdk/identity/perf-test/src/main/java/com/azure/identity/perf/core/ServiceTest.java b/sdk/identity/perf-test/src/main/java/com/azure/identity/perf/core/ServiceTest.java index 3ab4af63e23a8..9fe84d91f6e35 100644 --- a/sdk/identity/perf-test/src/main/java/com/azure/identity/perf/core/ServiceTest.java +++ b/sdk/identity/perf-test/src/main/java/com/azure/identity/perf/core/ServiceTest.java @@ -4,15 +4,28 @@ package com.azure.identity.perf.core; import com.azure.core.credential.TokenRequestContext; +import com.azure.identity.InteractiveBrowserCredential; +import com.azure.identity.InteractiveBrowserCredentialBuilder; import com.azure.perf.test.core.PerfStressOptions; import com.azure.perf.test.core.PerfStressTest; import reactor.core.publisher.Mono; +import java.nio.file.Paths; + public abstract class ServiceTest extends PerfStressTest { protected static final String CLI_CLIENT_ID = "04b07795-8ddb-461a-bbee-02f9e1bf7b46"; protected static final TokenRequestContext ARM_TOKEN_REQUEST_CONTEXT = new TokenRequestContext() .addScopes("https://management.azure.com/.default"); + private InteractiveBrowserCredential interactiveBrowserCredential = new InteractiveBrowserCredentialBuilder() + .port(8765) + .clientId(CLI_CLIENT_ID) + .keychainService("Microsoft.Developer.IdentityService") + .keychainAccount("MSALCache") + .cacheFileLocation(Paths.get(System.getProperty("user.home"), + ".IdentityService", "msal.cache")) + .build(); + public ServiceTest(TOptions options) { super(options); } @@ -21,9 +34,7 @@ public ServiceTest(TOptions options) { public Mono globalSetupAsync() { // Populate the token cache for tests return super.globalSetupAsync() -// .then(new InteractiveBrowserCredentialBuilder().port(8765) -// .clientId(CLI_CLIENT_ID).build() -// .getToken(ARM_TOKEN_REQUEST_CONTEXT)) +// .then(interactiveBrowserCredential.getToken(ARM_TOKEN_REQUEST_CONTEXT)) .then(); } } From 7cba579bc24e6d656a48be916760d4d3a7f16f30 Mon Sep 17 00:00:00 2001 From: Jianghao Lu Date: Mon, 6 Apr 2020 16:25:10 -0700 Subject: [PATCH 11/45] Move default to identity client options --- .../identity/AadCredentialBuilderBase.java | 46 ++-- .../identity/DefaultAzureCredential.java | 22 +- .../InteractiveBrowserCredentialBuilder.java | 4 - .../SharedTokenCacheCredentialBuilder.java | 11 - .../implementation/IdentityClient.java | 2 +- .../implementation/IdentityClientOptions.java | 213 +++++++++++++++--- .../com/azure/identity/perf/ReadCache.java | 15 -- .../com/azure/identity/perf/WriteCache.java | 14 -- 8 files changed, 208 insertions(+), 119 deletions(-) diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/AadCredentialBuilderBase.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/AadCredentialBuilderBase.java index 1295c106eb9d8..efdea7add96a4 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/AadCredentialBuilderBase.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/AadCredentialBuilderBase.java @@ -3,10 +3,7 @@ package com.azure.identity; -import com.azure.core.util.logging.ClientLogger; - import java.nio.file.Path; -import java.util.LinkedHashMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.ForkJoinPool; @@ -15,17 +12,8 @@ * @param the type of the credential builder */ public abstract class AadCredentialBuilderBase> extends CredentialBuilderBase { - private final ClientLogger logger = new ClientLogger(AadCredentialBuilderBase.class); String clientId; String tenantId; - Path cacheFileLocation; - String keychainService; - String keychainAccount; - String keyringName; - KeyringItemSchema keyringItemSchema; - String keyringItemName; - final LinkedHashMap attributes = new LinkedHashMap<>(); // preserve order - boolean useUnprotectedFileOnLinux = false; /** * Specifies the Azure Active Directory endpoint to acquire tokens. @@ -95,7 +83,7 @@ public T executorService(ExecutorService executorService) { */ @SuppressWarnings("unchecked") public T cacheFileLocation(Path cacheFileLocation) { - this.cacheFileLocation = cacheFileLocation; + this.identityClientOptions.setCacheFileLocation(cacheFileLocation); return (T) this; } @@ -109,7 +97,7 @@ public T cacheFileLocation(Path cacheFileLocation) { */ @SuppressWarnings("unchecked") public T keychainService(String serviceName) { - this.keychainService = serviceName; + this.identityClientOptions.setKeychainService(serviceName); return (T) this; } @@ -123,7 +111,7 @@ public T keychainService(String serviceName) { */ @SuppressWarnings("unchecked") public T keychainAccount(String accountName) { - this.keychainAccount = accountName; + this.identityClientOptions.setKeychainAccount(accountName); return (T) this; } @@ -137,7 +125,7 @@ public T keychainAccount(String accountName) { */ @SuppressWarnings("unchecked") public T keyringName(String keyringName) { - this.keyringName = keyringName; + this.identityClientOptions.setKeyringName(keyringName); return (T) this; } @@ -151,7 +139,7 @@ public T keyringName(String keyringName) { */ @SuppressWarnings("unchecked") public T keyringItemSchema(KeyringItemSchema keyringItemSchema) { - this.keyringItemSchema = keyringItemSchema; + this.identityClientOptions.setKeyringItemSchema(keyringItemSchema); return (T) this; } @@ -165,7 +153,7 @@ public T keyringItemSchema(KeyringItemSchema keyringItemSchema) { */ @SuppressWarnings("unchecked") public T keyringItemName(String keyringItemName) { - this.keyringItemName = keyringItemName; + this.identityClientOptions.setKeyringItemName(keyringItemName); return (T) this; } @@ -181,12 +169,7 @@ public T keyringItemName(String keyringItemName) { */ @SuppressWarnings("unchecked") public T addKeyringItemAttribute(String attributeName, String attributeValue) { - if (this.attributes.size() < 2) { - this.attributes.put(attributeName, attributeValue); - } else { - throw logger.logExceptionAsError(new IllegalArgumentException( - "Currently does not support more than 2 attributes for linux Keyring")); - } + this.identityClientOptions.addKeyringItemAttribute(attributeName, attributeValue); return (T) this; } @@ -200,7 +183,20 @@ public T addKeyringItemAttribute(String attributeName, String attributeValue) { */ @SuppressWarnings("unchecked") public T useUnprotectedFileOnLinux(boolean useUnprotectedFileOnLinux) { - this.useUnprotectedFileOnLinux = useUnprotectedFileOnLinux; + this.identityClientOptions.setUseUnprotectedFileOnLinux(useUnprotectedFileOnLinux); + return (T) this; + } + + /** + * Disable using the shared token cache. + * + * @param disabled whether to disable using the shared token cache. + * + * @return The updated identity client options. + */ + @SuppressWarnings("unchecked") + public T disableSharedTokenCache(boolean disabled) { + this.identityClientOptions.disableSharedTokenCache(disabled); return (T) this; } } diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/DefaultAzureCredential.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/DefaultAzureCredential.java index 0c710f96d1a42..676cdf7ec7f7f 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/DefaultAzureCredential.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/DefaultAzureCredential.java @@ -5,13 +5,9 @@ import com.azure.core.annotation.Immutable; import com.azure.identity.implementation.IdentityClientOptions; -import com.sun.jna.Platform; -import java.nio.file.Path; -import java.nio.file.Paths; import java.util.ArrayDeque; import java.util.Arrays; -import java.util.Collections; /** * Creates a credential using environment variables or the shared token cache. It tries to create a valid credential in @@ -27,18 +23,6 @@ */ @Immutable public final class DefaultAzureCredential extends ChainedTokenCredential { - private static final Path DEFAULT_CACHE_PATH = Platform.isWindows() - ? Paths.get(System.getProperty("user.home"), "AppData", "Local", ".IdentityService", "msal.cache") - : Paths.get(System.getProperty("user.home"), ".IdentityService", "msal.cache"); - private static final String DEFAULT_KEYCHAIN_SERVICE = "Microsoft.Developer.IdentityService"; - private static final String DEFAULT_KEYCHAIN_ACCOUNT = "MSALCache"; - private static final String DEFAULT_KEYRING_NAME = "default"; - private static final KeyringItemSchema DEFAULT_KEYRING_SCHEMA = KeyringItemSchema.GENERIC_SECRET; - private static final String DEFAULT_KEYRING_ITEM_NAME = DEFAULT_KEYCHAIN_ACCOUNT; - private static final String DEFAULT_KEYRING_ATTR_NAME = "MsalClientID"; - private static final String DEFAULT_KEYRING_ATTR_VALUE = "Microsoft.Developer.IdentityService"; - - /** * Creates default DefaultAzureCredential instance to use. This will use AZURE_CLIENT_ID, * AZURE_CLIENT_SECRET, and AZURE_TENANT_ID environment variables to create a @@ -52,11 +36,7 @@ public final class DefaultAzureCredential extends ChainedTokenCredential { DefaultAzureCredential(IdentityClientOptions identityClientOptions) { super(new ArrayDeque<>(Arrays.asList(new EnvironmentCredential(identityClientOptions), new ManagedIdentityCredential(null, identityClientOptions), - new SharedTokenCacheCredential(null, "04b07795-8ddb-461a-bbee-02f9e1bf7b46", null, - identityClientOptions.setPersistenceSettings(DEFAULT_CACHE_PATH, DEFAULT_KEYCHAIN_SERVICE, - DEFAULT_KEYCHAIN_ACCOUNT, DEFAULT_KEYRING_NAME, DEFAULT_KEYRING_SCHEMA, - DEFAULT_KEYRING_ITEM_NAME, Collections.singletonMap(DEFAULT_KEYRING_ATTR_NAME, - DEFAULT_KEYRING_ATTR_VALUE), false)), + new SharedTokenCacheCredentialBuilder().clientId("04b07795-8ddb-461a-bbee-02f9e1bf7b46").build(), // TODO: Check if libsecret is installed for Linux and use unprotected file cache if not new AzureCliCredential(identityClientOptions)))); } diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/InteractiveBrowserCredentialBuilder.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/InteractiveBrowserCredentialBuilder.java index b94a9d8446de6..d0ef8e3a863f3 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/InteractiveBrowserCredentialBuilder.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/InteractiveBrowserCredentialBuilder.java @@ -37,10 +37,6 @@ public InteractiveBrowserCredential build() { put("clientId", clientId); put("port", port); }}); - if (cacheFileLocation != null) { - identityClientOptions.setPersistenceSettings(cacheFileLocation, keychainService, keychainAccount, - keyringName, keyringItemSchema, keyringItemName, attributes, useUnprotectedFileOnLinux); - } return new InteractiveBrowserCredential(clientId, tenantId, port, identityClientOptions); } } diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/SharedTokenCacheCredentialBuilder.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/SharedTokenCacheCredentialBuilder.java index 08a0ff63020a4..397be6f7e6555 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/SharedTokenCacheCredentialBuilder.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/SharedTokenCacheCredentialBuilder.java @@ -3,10 +3,6 @@ package com.azure.identity; -import com.azure.identity.implementation.util.ValidationUtil; - -import java.util.HashMap; - /** * Fluent credential builder for instantiating a {@link SharedTokenCacheCredential}. * @@ -33,13 +29,6 @@ public SharedTokenCacheCredentialBuilder username(String username) { * @return a {@link SharedTokenCacheCredentialBuilder} with the current configurations. */ public SharedTokenCacheCredential build() { - ValidationUtil.validate(getClass().getSimpleName(), new HashMap() {{ - put("cacheFileLocation", cacheFileLocation); - }}); - if (cacheFileLocation != null) { - identityClientOptions.setPersistenceSettings(cacheFileLocation, keychainService, keychainAccount, - keyringName, keyringItemSchema, keyringItemName, attributes, useUnprotectedFileOnLinux); - } return new SharedTokenCacheCredential(username, clientId, tenantId, identityClientOptions); } } diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java index 64ae007e1ddda..a1bd17e3d091b 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java @@ -146,7 +146,7 @@ public class IdentityClient { if (options.getExecutorService() != null) { publicClientApplicationBuilder.executorService(options.getExecutorService()); } - if (options.getPersistenceSettings() != null) { + if (!options.isSharedTokenCacheDisabled()) { try { publicClientApplicationBuilder.setTokenCacheAccessAspect( new PersistenceTokenCacheAccessAspect(options.getPersistenceSettings())); diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClientOptions.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClientOptions.java index 32bf51d8297fc..f7b07420e5f7d 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClientOptions.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClientOptions.java @@ -6,18 +6,22 @@ import com.azure.core.http.HttpClient; import com.azure.core.http.HttpPipeline; import com.azure.core.http.ProxyOptions; +import com.azure.core.util.logging.ClientLogger; import com.azure.identity.KeyringItemSchema; import com.microsoft.aad.msal4jextensions.PersistenceSettings; +import com.sun.jna.Platform; import java.nio.file.Path; +import java.nio.file.Paths; import java.time.Duration; -import java.util.ArrayList; +import java.util.Arrays; +import java.util.LinkedHashMap; import java.util.List; -import java.util.Map; import java.util.Objects; import java.util.concurrent.ExecutorService; import java.util.concurrent.ForkJoinPool; import java.util.function.Function; +import java.util.stream.Collectors; /** * Options to configure the IdentityClient. @@ -25,6 +29,19 @@ public final class IdentityClientOptions { private static final String DEFAULT_AUTHORITY_HOST = "https://login.microsoftonline.com/"; private static final int MAX_RETRY_DEFAULT_LIMIT = 3; + private static final String DEFAULT_CACHE_FILE_NAME = "msal.cache"; + private static final Path DEFAULT_CACHE_FILE_PATH = Platform.isWindows() + ? Paths.get(System.getProperty("user.home"), "AppData", "Local", ".IdentityService") + : Paths.get(System.getProperty("user.home"), ".IdentityService"); + private static final String DEFAULT_KEYCHAIN_SERVICE = "Microsoft.Developer.IdentityService"; + private static final String DEFAULT_KEYCHAIN_ACCOUNT = "MSALCache"; + private static final String DEFAULT_KEYRING_NAME = "default"; + private static final KeyringItemSchema DEFAULT_KEYRING_SCHEMA = KeyringItemSchema.GENERIC_SECRET; + private static final String DEFAULT_KEYRING_ITEM_NAME = DEFAULT_KEYCHAIN_ACCOUNT; + private static final String DEFAULT_KEYRING_ATTR_NAME = "MsalClientID"; + private static final String DEFAULT_KEYRING_ATTR_VALUE = "Microsoft.Developer.IdentityService"; + private static ClientLogger logger = new ClientLogger(IdentityClientOptions.class); + private String authorityHost; private int maxRetry; private Function retryTimeout; @@ -33,7 +50,16 @@ public final class IdentityClientOptions { private ExecutorService executorService; private Duration tokenRefreshOffset = Duration.ofMinutes(2); private HttpClient httpClient; - private PersistenceSettings persistenceSettings; + private Path cacheFileDirectory; + private String cacheFileName; + private String keychainService; + private String keychainAccount; + private String keyringName; + private KeyringItemSchema keyringItemSchema; + private String keyringItemName; + private final LinkedHashMap attributes; // preserve order + private boolean useUnprotectedFileOnLinux; + private boolean sharedTokenCacheDisabled; /** * Creates an instance of IdentityClientOptions with default settings. @@ -42,6 +68,16 @@ public IdentityClientOptions() { authorityHost = DEFAULT_AUTHORITY_HOST; maxRetry = MAX_RETRY_DEFAULT_LIMIT; retryTimeout = i -> Duration.ofSeconds((long) Math.pow(2, i.getSeconds() - 1)); + cacheFileDirectory = DEFAULT_CACHE_FILE_PATH; + cacheFileName = DEFAULT_CACHE_FILE_NAME; + keychainService = DEFAULT_KEYCHAIN_SERVICE; + keychainAccount = DEFAULT_KEYCHAIN_ACCOUNT; + keyringName = DEFAULT_KEYRING_NAME; + keyringItemSchema = DEFAULT_KEYRING_SCHEMA; + keyringItemName = DEFAULT_KEYRING_ITEM_NAME; + attributes = new LinkedHashMap<>(); + useUnprotectedFileOnLinux = false; + sharedTokenCacheDisabled = false; } /** @@ -199,34 +235,155 @@ public IdentityClientOptions setHttpClient(HttpClient httpClient) { return this; } - public PersistenceSettings getPersistenceSettings() { - return persistenceSettings; - } - - public IdentityClientOptions setPersistenceSettings(Path cacheFileLocation, String keychainService, - String keychainAccount, String keyringName, - KeyringItemSchema keyringItemSchema, String keyringItemName, - Map keyringItemAttributes, - boolean useUnprotectedFileOnLinux) { - String cacheFileName = cacheFileLocation.getFileName().toString(); - Path cacheFileDirectory = cacheFileLocation.getParent(); - List attributeKeyList = new ArrayList<>(keyringItemAttributes.keySet()); - while (attributeKeyList.size() < 2) { - attributeKeyList.add(null); + PersistenceSettings getPersistenceSettings() { + if (attributes.size() < 2) { + attributes.put(DEFAULT_KEYRING_ATTR_NAME, DEFAULT_KEYRING_ATTR_VALUE); } - String key1 = attributeKeyList.get(0); - String key2 = attributeKeyList.get(1); - PersistenceSettings.Builder persistenceBuilder = PersistenceSettings.builder(cacheFileName, cacheFileDirectory) - .setLinuxUseUnprotectedFileAsCacheStorage(useUnprotectedFileOnLinux); - if (keychainService != null && keychainAccount != null) { - persistenceBuilder.setMacKeychain(keychainService, keychainAccount); + List args = attributes.entrySet().stream() + .flatMap(entry -> Arrays.asList(entry.getKey(), entry.getValue()).stream()) + .collect(Collectors.toList()); + if (args.size() == 2) { + args.set(2, null); + args.set(3, null); } - if (keyringName != null && keyringItemName != null && keyringItemSchema != null) { - persistenceBuilder.setLinuxKeyring(keyringName, keyringItemSchema.toString(), keyringItemName, - key1, keyringItemAttributes.getOrDefault(key1, null), - key2, keyringItemAttributes.getOrDefault(key2, null)); + return PersistenceSettings.builder(cacheFileName, cacheFileDirectory) + .setMacKeychain(keychainService, keychainAccount) + .setLinuxKeyring(keyringName, keyringItemSchema.toString(), keyringItemName, + args.get(0), args.get(1), args.get(2), args.get(3)) + .setLinuxUseUnprotectedFileAsCacheStorage(useUnprotectedFileOnLinux) + .build(); + } + + /** + * Sets the location for the token cache file on Windows or Linux systems. The default + * location is {user home}/AppData/Local/.IdentityService/msal.cache on + * Windows and ~/.IdentityService/msal.cache on Linux. + * + * @param cacheFileLocation The location for the token cache file. + * + * @return The updated identity client options. + */ + public IdentityClientOptions setCacheFileLocation(Path cacheFileLocation) { + this.cacheFileDirectory = cacheFileLocation.getParent(); + this.cacheFileName = cacheFileLocation.getFileName().toString(); + return this; + } + + /** + * Sets the service name for the Keychain item on MacOS. The default value is + * "Microsoft.Developer.IdentityService". + * + * @param serviceName The service name for the Keychain item. + * + * @return The updated identity client options. + */ + public IdentityClientOptions setKeychainService(String serviceName) { + this.keychainService = serviceName; + return this; + } + + /** + * Sets the account name for the Keychain item on MacOS. The default value is + * "MSALCache". + * + * @param accountName The account name for the Keychain item. + * + * @return The updated identity client options. + */ + public IdentityClientOptions setKeychainAccount(String accountName) { + this.keychainAccount = accountName; + return this; + } + + /** + * Sets the name of the Gnome keyring to store the cache on Gnome keyring enabled + * Linux systems. The default value is "default". + * + * @param keyringName The name of the Gnome keyring. + * + * @return The updated identity client options. + */ + public IdentityClientOptions setKeyringName(String keyringName) { + this.keyringName = keyringName; + return this; + } + + /** + * Sets the schema of the Gnome keyring to store the cache on Gnome keyring enabled + * Linux systems. The default value is KeyringItemSchema.GenericSecret. + * + * @param keyringItemSchema The schema of the Gnome keyring. + * + * @return The updated identity client options. + */ + public IdentityClientOptions setKeyringItemSchema(KeyringItemSchema keyringItemSchema) { + this.keyringItemSchema = keyringItemSchema; + return this; + } + + /** + * Sets the name of the Gnome keyring item to store the cache on Gnome keyring enabled + * Linux systems. The default value is "MSALCache". + * + * @param keyringItemName The name of the Gnome keyring item. + * + * @return The updated identity client options. + */ + public IdentityClientOptions setKeyringItemName(String keyringItemName) { + this.keyringItemName = keyringItemName; + return this; + } + + /** + * Adds an attribute to the Gnome keyring item to store the cache on Gnome keyring enabled + * Linux systems. Only 2 attributes are allowed. + * + * @param attributeName The name of the attribute. + * @param attributeValue The value of the attribute. + * + * @return The updated identity client options. + * @throws IllegalArgumentException if there are already 2 attributes + */ + public IdentityClientOptions addKeyringItemAttribute(String attributeName, String attributeValue) { + if (this.attributes.size() < 2) { + this.attributes.put(attributeName, attributeValue); + } else { + throw logger.logExceptionAsError(new IllegalArgumentException( + "Currently does not support more than 2 attributes for linux Keyring")); } - this.persistenceSettings = persistenceBuilder.build(); + return this; + } + + /** + * Sets whether to use an unprotected file specified by cacheFileLocation() instead of + * Gnome keyring on Linux. This is false by default. + * + * @param useUnprotectedFileOnLinux whether to use an unprotected file for cache storage. + * + * @return The updated identity client options. + */ + public IdentityClientOptions setUseUnprotectedFileOnLinux(boolean useUnprotectedFileOnLinux) { + this.useUnprotectedFileOnLinux = useUnprotectedFileOnLinux; + return this; + } + + /** + * Gets if the shared token cache is disabled. + * @return if the shared token cache is disabled. + */ + public boolean isSharedTokenCacheDisabled() { + return this.sharedTokenCacheDisabled; + } + + /** + * Disable using the shared token cache. + * + * @param disabled whether to disable using the shared token cache. + * + * @return The updated identity client options. + */ + public IdentityClientOptions disableSharedTokenCache(boolean disabled) { + this.sharedTokenCacheDisabled = disabled; return this; } } diff --git a/sdk/identity/perf-test/src/main/java/com/azure/identity/perf/ReadCache.java b/sdk/identity/perf-test/src/main/java/com/azure/identity/perf/ReadCache.java index 9621cd0751f89..5ab01c3de1401 100644 --- a/sdk/identity/perf-test/src/main/java/com/azure/identity/perf/ReadCache.java +++ b/sdk/identity/perf-test/src/main/java/com/azure/identity/perf/ReadCache.java @@ -7,30 +7,15 @@ import com.azure.identity.SharedTokenCacheCredentialBuilder; import com.azure.identity.perf.core.ServiceTest; import com.azure.perf.test.core.SizeOptions; -import com.sun.jna.Platform; import reactor.core.publisher.Mono; -import java.nio.file.Path; -import java.nio.file.Paths; - public class ReadCache extends ServiceTest { private final SharedTokenCacheCredential credential; public ReadCache(SizeOptions options) { super(options); - Path cacheFileLocation; - if (Platform.isWindows()) { - cacheFileLocation = Paths.get(System.getProperty("user.home"), - "AppData", "Local", ".IdentityService", "msal.cache"); - } else { - cacheFileLocation = Paths.get(System.getProperty("user.home"), - ".IdentityService", "msal.cache"); - } credential = new SharedTokenCacheCredentialBuilder() .clientId(CLI_CLIENT_ID) - .keychainService("Microsoft.Developer.IdentityService") - .keychainAccount("MSALCache") - .cacheFileLocation(cacheFileLocation) .build(); } diff --git a/sdk/identity/perf-test/src/main/java/com/azure/identity/perf/WriteCache.java b/sdk/identity/perf-test/src/main/java/com/azure/identity/perf/WriteCache.java index 59b39b462382e..912c16f422053 100644 --- a/sdk/identity/perf-test/src/main/java/com/azure/identity/perf/WriteCache.java +++ b/sdk/identity/perf-test/src/main/java/com/azure/identity/perf/WriteCache.java @@ -7,11 +7,8 @@ import com.azure.identity.SharedTokenCacheCredentialBuilder; import com.azure.identity.perf.core.ServiceTest; import com.azure.perf.test.core.SizeOptions; -import com.sun.jna.Platform; import reactor.core.publisher.Mono; -import java.nio.file.Path; -import java.nio.file.Paths; import java.time.Duration; public class WriteCache extends ServiceTest { @@ -19,19 +16,8 @@ public class WriteCache extends ServiceTest { public WriteCache(SizeOptions options) { super(options); - Path cacheFileLocation; - if (Platform.isWindows()) { - cacheFileLocation = Paths.get(System.getProperty("user.home"), - "AppData", "Local", ".IdentityService", "msal.cache"); - } else { - cacheFileLocation = Paths.get(System.getProperty("user.home"), - ".IdentityService", "msal.cache"); - } credential = new SharedTokenCacheCredentialBuilder() .clientId(CLI_CLIENT_ID) - .keychainService("Microsoft.Developer.IdentityService") - .keychainAccount("MSALCache") - .cacheFileLocation(cacheFileLocation) .tokenRefreshOffset(Duration.ofMinutes(60)) .build(); } From 600fc6b4af77373b5bb1e56bc9e73e8329eed72c Mon Sep 17 00:00:00 2001 From: Jianghao Lu Date: Mon, 6 Apr 2020 18:43:34 -0700 Subject: [PATCH 12/45] Fix array access in identity client options --- .../azure/identity/implementation/IdentityClientOptions.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClientOptions.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClientOptions.java index f7b07420e5f7d..86bb27534b044 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClientOptions.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClientOptions.java @@ -243,8 +243,8 @@ PersistenceSettings getPersistenceSettings() { .flatMap(entry -> Arrays.asList(entry.getKey(), entry.getValue()).stream()) .collect(Collectors.toList()); if (args.size() == 2) { - args.set(2, null); - args.set(3, null); + args.add(null); + args.add(null); } return PersistenceSettings.builder(cacheFileName, cacheFileDirectory) .setMacKeychain(keychainService, keychainAccount) From 1c53b243c38cee41ccd8aa2a6a07319124fbb39e Mon Sep 17 00:00:00 2001 From: Jianghao Lu Date: Mon, 6 Apr 2020 23:49:50 -0700 Subject: [PATCH 13/45] Fix libsecret on Windows --- .../implementation/IdentityClientOptions.java | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClientOptions.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClientOptions.java index 86bb27534b044..e07c0cbd499dc 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClientOptions.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClientOptions.java @@ -246,12 +246,15 @@ PersistenceSettings getPersistenceSettings() { args.add(null); args.add(null); } - return PersistenceSettings.builder(cacheFileName, cacheFileDirectory) - .setMacKeychain(keychainService, keychainAccount) - .setLinuxKeyring(keyringName, keyringItemSchema.toString(), keyringItemName, - args.get(0), args.get(1), args.get(2), args.get(3)) - .setLinuxUseUnprotectedFileAsCacheStorage(useUnprotectedFileOnLinux) - .build(); + PersistenceSettings.Builder builder = PersistenceSettings.builder(cacheFileName, cacheFileDirectory); + if (Platform.isMac()) { + builder.setMacKeychain(keychainService, keychainAccount); + } else if (Platform.isLinux()) { + builder.setLinuxKeyring(keyringName, keyringItemSchema.toString(), keyringItemName, + args.get(0), args.get(1), args.get(2), args.get(3)) + .setLinuxUseUnprotectedFileAsCacheStorage(useUnprotectedFileOnLinux); + } + return builder.build(); } /** From 3539779b9c632bd28823c2e9752989a3517c184e Mon Sep 17 00:00:00 2001 From: Jianghao Lu Date: Tue, 7 Apr 2020 14:59:45 -0700 Subject: [PATCH 14/45] Remove shared token cache configurations --- .../identity/AadCredentialBuilderBase.java | 106 +------------- .../implementation/IdentityClientOptions.java | 137 ++---------------- .../azure/identity/perf/core/ServiceTest.java | 6 - 3 files changed, 11 insertions(+), 238 deletions(-) diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/AadCredentialBuilderBase.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/AadCredentialBuilderBase.java index efdea7add96a4..d888be261c16e 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/AadCredentialBuilderBase.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/AadCredentialBuilderBase.java @@ -3,7 +3,6 @@ package com.azure.identity; -import java.nio.file.Path; import java.util.concurrent.ExecutorService; import java.util.concurrent.ForkJoinPool; @@ -72,107 +71,6 @@ public T executorService(ExecutorService executorService) { return (T) this; } - /** - * Sets the location for the token cache file on Windows or Linux systems. The default - * location is {user home}/AppData/Local/.IdentityService/msal.cache on - * Windows and ~/.IdentityService/msal.cache on Linux. - * - * @param cacheFileLocation The location for the token cache file. - * - * @return The updated T object. - */ - @SuppressWarnings("unchecked") - public T cacheFileLocation(Path cacheFileLocation) { - this.identityClientOptions.setCacheFileLocation(cacheFileLocation); - return (T) this; - } - - /** - * Sets the service name for the Keychain item on MacOS. The default value is - * "Microsoft.Developer.IdentityService". - * - * @param serviceName The service name for the Keychain item. - * - * @return The updated T object. - */ - @SuppressWarnings("unchecked") - public T keychainService(String serviceName) { - this.identityClientOptions.setKeychainService(serviceName); - return (T) this; - } - - /** - * Sets the account name for the Keychain item on MacOS. The default value is - * "MSALCache". - * - * @param accountName The account name for the Keychain item. - * - * @return The updated T object. - */ - @SuppressWarnings("unchecked") - public T keychainAccount(String accountName) { - this.identityClientOptions.setKeychainAccount(accountName); - return (T) this; - } - - /** - * Sets the name of the Gnome keyring to store the cache on Gnome keyring enabled - * Linux systems. The default value is "default". - * - * @param keyringName The name of the Gnome keyring. - * - * @return The updated T object. - */ - @SuppressWarnings("unchecked") - public T keyringName(String keyringName) { - this.identityClientOptions.setKeyringName(keyringName); - return (T) this; - } - - /** - * Sets the schema of the Gnome keyring to store the cache on Gnome keyring enabled - * Linux systems. The default value is KeyringItemSchema.GenericSecret. - * - * @param keyringItemSchema The schema of the Gnome keyring. - * - * @return The updated T object. - */ - @SuppressWarnings("unchecked") - public T keyringItemSchema(KeyringItemSchema keyringItemSchema) { - this.identityClientOptions.setKeyringItemSchema(keyringItemSchema); - return (T) this; - } - - /** - * Sets the name of the Gnome keyring item to store the cache on Gnome keyring enabled - * Linux systems. The default value is "MSALCache". - * - * @param keyringItemName The name of the Gnome keyring item. - * - * @return The updated T object. - */ - @SuppressWarnings("unchecked") - public T keyringItemName(String keyringItemName) { - this.identityClientOptions.setKeyringItemName(keyringItemName); - return (T) this; - } - - /** - * Adds an attribute to the Gnome keyring item to store the cache on Gnome keyring enabled - * Linux systems. Only 2 attributes are allowed. - * - * @param attributeName The name of the attribute. - * @param attributeValue The value of the attribute. - * - * @return The updated T object. - * @throws IllegalArgumentException if there are already 2 attributes - */ - @SuppressWarnings("unchecked") - public T addKeyringItemAttribute(String attributeName, String attributeValue) { - this.identityClientOptions.addKeyringItemAttribute(attributeName, attributeValue); - return (T) this; - } - /** * Sets whether to use an unprotected file specified by cacheFileLocation() instead of * Gnome keyring on Linux. This is false by default. @@ -182,8 +80,8 @@ public T addKeyringItemAttribute(String attributeName, String attributeValue) { * @return The updated T object. */ @SuppressWarnings("unchecked") - public T useUnprotectedFileOnLinux(boolean useUnprotectedFileOnLinux) { - this.identityClientOptions.setUseUnprotectedFileOnLinux(useUnprotectedFileOnLinux); + public T useUnprotectedTokenCacheFileOnLinux(boolean useUnprotectedFileOnLinux) { + this.identityClientOptions.setUseUnprotectedTokenCacheFileOnLinux(useUnprotectedFileOnLinux); return (T) this; } diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClientOptions.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClientOptions.java index e07c0cbd499dc..4e6b4b326c1e7 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClientOptions.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClientOptions.java @@ -6,7 +6,6 @@ import com.azure.core.http.HttpClient; import com.azure.core.http.HttpPipeline; import com.azure.core.http.ProxyOptions; -import com.azure.core.util.logging.ClientLogger; import com.azure.identity.KeyringItemSchema; import com.microsoft.aad.msal4jextensions.PersistenceSettings; import com.sun.jna.Platform; @@ -14,14 +13,10 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.time.Duration; -import java.util.Arrays; -import java.util.LinkedHashMap; -import java.util.List; import java.util.Objects; import java.util.concurrent.ExecutorService; import java.util.concurrent.ForkJoinPool; import java.util.function.Function; -import java.util.stream.Collectors; /** * Options to configure the IdentityClient. @@ -40,7 +35,6 @@ public final class IdentityClientOptions { private static final String DEFAULT_KEYRING_ITEM_NAME = DEFAULT_KEYCHAIN_ACCOUNT; private static final String DEFAULT_KEYRING_ATTR_NAME = "MsalClientID"; private static final String DEFAULT_KEYRING_ATTR_VALUE = "Microsoft.Developer.IdentityService"; - private static ClientLogger logger = new ClientLogger(IdentityClientOptions.class); private String authorityHost; private int maxRetry; @@ -57,7 +51,7 @@ public final class IdentityClientOptions { private String keyringName; private KeyringItemSchema keyringItemSchema; private String keyringItemName; - private final LinkedHashMap attributes; // preserve order + private final String[] attributes; // preserve order private boolean useUnprotectedFileOnLinux; private boolean sharedTokenCacheDisabled; @@ -75,7 +69,7 @@ public IdentityClientOptions() { keyringName = DEFAULT_KEYRING_NAME; keyringItemSchema = DEFAULT_KEYRING_SCHEMA; keyringItemName = DEFAULT_KEYRING_ITEM_NAME; - attributes = new LinkedHashMap<>(); + attributes = new String[] { DEFAULT_KEYRING_ATTR_NAME, DEFAULT_KEYRING_ATTR_VALUE }; useUnprotectedFileOnLinux = false; sharedTokenCacheDisabled = false; } @@ -236,125 +230,12 @@ public IdentityClientOptions setHttpClient(HttpClient httpClient) { } PersistenceSettings getPersistenceSettings() { - if (attributes.size() < 2) { - attributes.put(DEFAULT_KEYRING_ATTR_NAME, DEFAULT_KEYRING_ATTR_VALUE); - } - List args = attributes.entrySet().stream() - .flatMap(entry -> Arrays.asList(entry.getKey(), entry.getValue()).stream()) - .collect(Collectors.toList()); - if (args.size() == 2) { - args.add(null); - args.add(null); - } - PersistenceSettings.Builder builder = PersistenceSettings.builder(cacheFileName, cacheFileDirectory); - if (Platform.isMac()) { - builder.setMacKeychain(keychainService, keychainAccount); - } else if (Platform.isLinux()) { - builder.setLinuxKeyring(keyringName, keyringItemSchema.toString(), keyringItemName, - args.get(0), args.get(1), args.get(2), args.get(3)) - .setLinuxUseUnprotectedFileAsCacheStorage(useUnprotectedFileOnLinux); - } - return builder.build(); - } - - /** - * Sets the location for the token cache file on Windows or Linux systems. The default - * location is {user home}/AppData/Local/.IdentityService/msal.cache on - * Windows and ~/.IdentityService/msal.cache on Linux. - * - * @param cacheFileLocation The location for the token cache file. - * - * @return The updated identity client options. - */ - public IdentityClientOptions setCacheFileLocation(Path cacheFileLocation) { - this.cacheFileDirectory = cacheFileLocation.getParent(); - this.cacheFileName = cacheFileLocation.getFileName().toString(); - return this; - } - - /** - * Sets the service name for the Keychain item on MacOS. The default value is - * "Microsoft.Developer.IdentityService". - * - * @param serviceName The service name for the Keychain item. - * - * @return The updated identity client options. - */ - public IdentityClientOptions setKeychainService(String serviceName) { - this.keychainService = serviceName; - return this; - } - - /** - * Sets the account name for the Keychain item on MacOS. The default value is - * "MSALCache". - * - * @param accountName The account name for the Keychain item. - * - * @return The updated identity client options. - */ - public IdentityClientOptions setKeychainAccount(String accountName) { - this.keychainAccount = accountName; - return this; - } - - /** - * Sets the name of the Gnome keyring to store the cache on Gnome keyring enabled - * Linux systems. The default value is "default". - * - * @param keyringName The name of the Gnome keyring. - * - * @return The updated identity client options. - */ - public IdentityClientOptions setKeyringName(String keyringName) { - this.keyringName = keyringName; - return this; - } - - /** - * Sets the schema of the Gnome keyring to store the cache on Gnome keyring enabled - * Linux systems. The default value is KeyringItemSchema.GenericSecret. - * - * @param keyringItemSchema The schema of the Gnome keyring. - * - * @return The updated identity client options. - */ - public IdentityClientOptions setKeyringItemSchema(KeyringItemSchema keyringItemSchema) { - this.keyringItemSchema = keyringItemSchema; - return this; - } - - /** - * Sets the name of the Gnome keyring item to store the cache on Gnome keyring enabled - * Linux systems. The default value is "MSALCache". - * - * @param keyringItemName The name of the Gnome keyring item. - * - * @return The updated identity client options. - */ - public IdentityClientOptions setKeyringItemName(String keyringItemName) { - this.keyringItemName = keyringItemName; - return this; - } - - /** - * Adds an attribute to the Gnome keyring item to store the cache on Gnome keyring enabled - * Linux systems. Only 2 attributes are allowed. - * - * @param attributeName The name of the attribute. - * @param attributeValue The value of the attribute. - * - * @return The updated identity client options. - * @throws IllegalArgumentException if there are already 2 attributes - */ - public IdentityClientOptions addKeyringItemAttribute(String attributeName, String attributeValue) { - if (this.attributes.size() < 2) { - this.attributes.put(attributeName, attributeValue); - } else { - throw logger.logExceptionAsError(new IllegalArgumentException( - "Currently does not support more than 2 attributes for linux Keyring")); - } - return this; + return PersistenceSettings.builder(cacheFileName, cacheFileDirectory) + .setMacKeychain(keychainService, keychainAccount) + .setLinuxKeyring(keyringName, keyringItemSchema.toString(), keyringItemName, + attributes[0], attributes[1], null, null) + .setLinuxUseUnprotectedFileAsCacheStorage(useUnprotectedFileOnLinux) + .build(); } /** @@ -365,7 +246,7 @@ public IdentityClientOptions addKeyringItemAttribute(String attributeName, Strin * * @return The updated identity client options. */ - public IdentityClientOptions setUseUnprotectedFileOnLinux(boolean useUnprotectedFileOnLinux) { + public IdentityClientOptions setUseUnprotectedTokenCacheFileOnLinux(boolean useUnprotectedFileOnLinux) { this.useUnprotectedFileOnLinux = useUnprotectedFileOnLinux; return this; } diff --git a/sdk/identity/perf-test/src/main/java/com/azure/identity/perf/core/ServiceTest.java b/sdk/identity/perf-test/src/main/java/com/azure/identity/perf/core/ServiceTest.java index 9fe84d91f6e35..a524cfdcc7bb8 100644 --- a/sdk/identity/perf-test/src/main/java/com/azure/identity/perf/core/ServiceTest.java +++ b/sdk/identity/perf-test/src/main/java/com/azure/identity/perf/core/ServiceTest.java @@ -10,8 +10,6 @@ import com.azure.perf.test.core.PerfStressTest; import reactor.core.publisher.Mono; -import java.nio.file.Paths; - public abstract class ServiceTest extends PerfStressTest { protected static final String CLI_CLIENT_ID = "04b07795-8ddb-461a-bbee-02f9e1bf7b46"; protected static final TokenRequestContext ARM_TOKEN_REQUEST_CONTEXT = new TokenRequestContext() @@ -20,10 +18,6 @@ public abstract class ServiceTest extends Pe private InteractiveBrowserCredential interactiveBrowserCredential = new InteractiveBrowserCredentialBuilder() .port(8765) .clientId(CLI_CLIENT_ID) - .keychainService("Microsoft.Developer.IdentityService") - .keychainAccount("MSALCache") - .cacheFileLocation(Paths.get(System.getProperty("user.home"), - ".IdentityService", "msal.cache")) .build(); public ServiceTest(TOptions options) { From 80bb671f72a12fa4058efde35f03a5f0fca6d304 Mon Sep 17 00:00:00 2001 From: Jianghao Lu Date: Tue, 7 Apr 2020 15:04:37 -0700 Subject: [PATCH 15/45] Clean up --- .../main/java/com/azure/identity/DefaultAzureCredential.java | 1 - .../src/main/java/com/azure/identity/perf/core/ServiceTest.java | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/DefaultAzureCredential.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/DefaultAzureCredential.java index 676cdf7ec7f7f..dbfee481d8760 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/DefaultAzureCredential.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/DefaultAzureCredential.java @@ -37,7 +37,6 @@ public final class DefaultAzureCredential extends ChainedTokenCredential { super(new ArrayDeque<>(Arrays.asList(new EnvironmentCredential(identityClientOptions), new ManagedIdentityCredential(null, identityClientOptions), new SharedTokenCacheCredentialBuilder().clientId("04b07795-8ddb-461a-bbee-02f9e1bf7b46").build(), - // TODO: Check if libsecret is installed for Linux and use unprotected file cache if not new AzureCliCredential(identityClientOptions)))); } } diff --git a/sdk/identity/perf-test/src/main/java/com/azure/identity/perf/core/ServiceTest.java b/sdk/identity/perf-test/src/main/java/com/azure/identity/perf/core/ServiceTest.java index a524cfdcc7bb8..6b2ac25f18e4c 100644 --- a/sdk/identity/perf-test/src/main/java/com/azure/identity/perf/core/ServiceTest.java +++ b/sdk/identity/perf-test/src/main/java/com/azure/identity/perf/core/ServiceTest.java @@ -28,7 +28,7 @@ public ServiceTest(TOptions options) { public Mono globalSetupAsync() { // Populate the token cache for tests return super.globalSetupAsync() -// .then(interactiveBrowserCredential.getToken(ARM_TOKEN_REQUEST_CONTEXT)) + .then(interactiveBrowserCredential.getToken(ARM_TOKEN_REQUEST_CONTEXT)) .then(); } } From a83c9ab0df31f2a9b17efbdee519ca2c03671917 Mon Sep 17 00:00:00 2001 From: Jianghao Lu Date: Tue, 7 Apr 2020 16:16:30 -0700 Subject: [PATCH 16/45] Fix tests --- .../com/azure/identity/DefaultAzureCredentialBuilder.java | 4 ++-- .../java/com/azure/identity/DefaultAzureCredentialTest.java | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/DefaultAzureCredentialBuilder.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/DefaultAzureCredentialBuilder.java index 5349b667147a7..f69e91df3326e 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/DefaultAzureCredentialBuilder.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/DefaultAzureCredentialBuilder.java @@ -120,8 +120,8 @@ private ArrayDeque getCredentialsChain() { } if (!excludeSharedTokenCacheCredential) { - output.add(new SharedTokenCacheCredential(null, null, - "04b07795-8ddb-461a-bbee-02f9e1bf7b46", identityClientOptions)); + output.add(new SharedTokenCacheCredential(null, "04b07795-8ddb-461a-bbee-02f9e1bf7b46", + null, identityClientOptions)); } if (!excludeAzureCliCredential) { diff --git a/sdk/identity/azure-identity/src/test/java/com/azure/identity/DefaultAzureCredentialTest.java b/sdk/identity/azure-identity/src/test/java/com/azure/identity/DefaultAzureCredentialTest.java index ea013330a6486..36dfe5be6ec92 100644 --- a/sdk/identity/azure-identity/src/test/java/com/azure/identity/DefaultAzureCredentialTest.java +++ b/sdk/identity/azure-identity/src/test/java/com/azure/identity/DefaultAzureCredentialTest.java @@ -24,7 +24,8 @@ @RunWith(PowerMockRunner.class) @PrepareForTest(fullyQualifiedNames = "com.azure.identity.*") -@PowerMockIgnore({"com.sun.org.apache.xerces.*", "javax.xml.*", "org.xml.*", "javax.net.ssl.*"}) +@PowerMockIgnore({"com.sun.org.apache.xerces.*", "javax.xml.*", "org.xml.*", "javax.net.ssl.*", + "io.netty.handler.ssl.*", "io.netty.buffer.*", "io.netty.channel.*"}) public class DefaultAzureCredentialTest { private final String tenantId = "contoso.com"; @@ -94,6 +95,7 @@ public void testUseAzureCliCredential() throws Exception { IdentityClient identityClient = PowerMockito.mock(IdentityClient.class); when(identityClient.authenticateWithAzureCli(request)).thenReturn(TestUtils.getMockAccessToken(token1, expiresAt)); when(identityClient.authenticateToIMDSEndpoint(request)).thenReturn(Mono.empty()); + when(identityClient.authenticateWithSharedTokenCache(request, null)).thenReturn(Mono.empty()); PowerMockito.whenNew(IdentityClient.class).withAnyArguments().thenReturn(identityClient); // test From 52e11f6e41c1d0908b4a5af476326f99611b72db Mon Sep 17 00:00:00 2001 From: Jianghao Lu Date: Tue, 7 Apr 2020 16:21:08 -0700 Subject: [PATCH 17/45] Fix versions --- eng/versioning/version_client.txt | 1 + sdk/identity/perf-test/README.md | 6 ++---- sdk/identity/perf-test/pom.xml | 4 ++-- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/eng/versioning/version_client.txt b/eng/versioning/version_client.txt index 594a29820fb23..a8fd2e63604db 100644 --- a/eng/versioning/version_client.txt +++ b/eng/versioning/version_client.txt @@ -20,6 +20,7 @@ com.azure:azure-cosmos-benchmark;4.0.1-beta.1;4.0.1-beta.1 com.azure:azure-data-appconfiguration;1.1.1;1.2.0-beta.1 com.azure:azure-e2e;1.0.0-beta.1;1.0.0-beta.1 com.azure:azure-identity;1.0.4;1.1.0-beta.3 +com.azure:azure-identity-perf;1.0.0-beta.1;1.0.0-beta.1 com.azure:azure-messaging-eventhubs;5.0.2;5.1.0-beta.1 com.azure:azure-messaging-eventhubs-checkpointstore-blob;1.0.2;1.1.0-beta.1 com.azure:azure-messaging-servicebus;7.0.0-beta.1;7.0.0-beta.2 diff --git a/sdk/identity/perf-test/README.md b/sdk/identity/perf-test/README.md index a291790c30c31..73530b2e3b06a 100644 --- a/sdk/identity/perf-test/README.md +++ b/sdk/identity/perf-test/README.md @@ -1,6 +1,6 @@ -# Azure Storage Performance test client library for Java +# Azure Identity Performance test client library for Java -Represents Performance tests for Azure Storage SDK for Java. +Represents Performance tests for Azure Identity SDK for Java. ## Getting started @@ -30,5 +30,3 @@ Azure Projects Contribution Guidelines](http://azure.github.io/guidelines.html). 1. Commit your changes (`git commit -am 'Add some feature'`) 1. Push to the branch (`git push origin my-new-feature`) 1. Create new Pull Request - -![Impressions](https://azure-sdk-impressions.azurewebsites.net/api/impressions/azure-sdk-for-java%2Fsdk%2Fstorage%2Fperf-test-core%2FREADME.png) diff --git a/sdk/identity/perf-test/pom.xml b/sdk/identity/perf-test/pom.xml index fd079eae0f8ca..689cf7829b872 100644 --- a/sdk/identity/perf-test/pom.xml +++ b/sdk/identity/perf-test/pom.xml @@ -14,7 +14,7 @@ com.azure azure-identity-perf - 1.0.0-beta.1 + 1.0.0-beta.1 jar @@ -27,7 +27,7 @@ com.azure azure-identity - 1.1.0-beta.3 + 1.1.0-beta.3 com.azure From 5a79873983b33434529421634dc46231450f44dc Mon Sep 17 00:00:00 2001 From: Jianghao Lu Date: Tue, 7 Apr 2020 16:37:49 -0700 Subject: [PATCH 18/45] Fix versions --- sdk/identity/perf-test/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/identity/perf-test/pom.xml b/sdk/identity/perf-test/pom.xml index 689cf7829b872..ca0650f96cb7a 100644 --- a/sdk/identity/perf-test/pom.xml +++ b/sdk/identity/perf-test/pom.xml @@ -27,7 +27,7 @@ com.azure azure-identity - 1.1.0-beta.3 + 1.1.0-beta.3 com.azure From bfcf3b3302fcce9049223909adc90bd233b901b7 Mon Sep 17 00:00:00 2001 From: Jianghao Lu Date: Tue, 7 Apr 2020 17:03:24 -0700 Subject: [PATCH 19/45] Add version again --- sdk/identity/azure-identity/pom.xml | 1 + .../azure/identity/ChainedTokenCredential.java | 16 +++++++++++++++- .../identity/ClientCertificateCredential.java | 17 +++++++++++++++-- .../azure/identity/ClientSecretCredential.java | 17 +++++++++++++++-- 4 files changed, 46 insertions(+), 5 deletions(-) diff --git a/sdk/identity/azure-identity/pom.xml b/sdk/identity/azure-identity/pom.xml index 355d28eb49e7f..d820fb4efdc2f 100644 --- a/sdk/identity/azure-identity/pom.xml +++ b/sdk/identity/azure-identity/pom.xml @@ -127,6 +127,7 @@ com.azure:* com.nimbusds:oauth2-oidc-sdk com.microsoft.azure:msal4j + com.microsoft.azure:msal4j-persistence-extension org.nanohttpd:nanohttpd net.java.dev.jna diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/ChainedTokenCredential.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/ChainedTokenCredential.java index 2f42e6571de91..d704482eaa21b 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/ChainedTokenCredential.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/ChainedTokenCredential.java @@ -19,7 +19,21 @@ * *

Sample: Construct a ChainedTokenCredential with silent username+password login tried first, then * interactive browser login as needed (e.g. when 2FA is turned on in the directory).

- * {@codesnippet com.azure.identity.credential.chainedtokencredential.construct} + *
+ * UsernamePasswordCredential usernamePasswordCredential = new UsernamePasswordCredentialBuilder()
+ *     .clientId(clientId)
+ *     .username(username)
+ *     .password(password)
+ *     .build();
+ * InteractiveBrowserCredential interactiveBrowserCredential = new InteractiveBrowserCredentialBuilder()
+ *     .clientId(clientId)
+ *     .port(8765)
+ *     .build();
+ * ChainedTokenCredential credential = new ChainedTokenCredentialBuilder()
+ *     .addLast(usernamePasswordCredential)
+ *     .addLast(interactiveBrowserCredential)
+ *     .build();
+ * 
*/ @Immutable public class ChainedTokenCredential implements TokenCredential { diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/ClientCertificateCredential.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/ClientCertificateCredential.java index 44dfd883582ca..56d481007ded6 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/ClientCertificateCredential.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/ClientCertificateCredential.java @@ -18,10 +18,23 @@ * An AAD credential that acquires a token with a client certificate for an AAD application. * *

Sample: Construct a simple ClientCertificateCredential

- * {@codesnippet com.azure.identity.credential.clientcertificatecredential.construct} + *
+ * ClientCertificateCredential credential1 = new ClientCertificateCredentialBuilder()
+ *     .tenantId(tenantId)
+ *     .clientId(clientId)
+ *     .pemCertificate("C:\\fakepath\\cert.pem")
+ *     .build();
+ * 
* *

Sample: Construct a ClientCertificateCredential behind a proxy

- * {@codesnippet com.azure.identity.credential.clientcertificatecredential.constructwithproxy} + *
+ * ClientCertificateCredential credential2 = new ClientCertificateCredentialBuilder()
+ *     .tenantId(tenantId)
+ *     .clientId(clientId)
+ *     .pfxCertificate("C:\\fakepath\\cert.pfx", "P{@literal @}s$w0rd")
+ *     .proxyOptions(new ProxyOptions(Type.HTTP, new InetSocketAddress("10.21.32.43", 5465)))
+ *     .build();
+ * 
*/ @Immutable public class ClientCertificateCredential implements TokenCredential { diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/ClientSecretCredential.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/ClientSecretCredential.java index 28efcb6a07497..84e3a9337cc75 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/ClientSecretCredential.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/ClientSecretCredential.java @@ -18,10 +18,23 @@ * An AAD credential that acquires a token with a client secret for an AAD application. * *

Sample: Construct a simple ClientSecretCredential

- * {@codesnippet com.azure.identity.credential.clientsecretcredential.construct} + *
+ * ClientSecretCredential credential1 = new ClientSecretCredentialBuilder()
+ *     .tenantId(tenantId)
+ *     .clientId(clientId)
+ *     .clientSecret(clientSecret)
+ *     .build();
+ * 
* *

Sample: Construct a ClientSecretCredential behind a proxy

- * {@codesnippet com.azure.identity.credential.clientsecretcredential.constructwithproxy} + *
+ * ClientSecretCredential credential2 = new ClientSecretCredentialBuilder()
+ *     .tenantId(tenantId)
+ *     .clientId(clientId)
+ *     .clientSecret(clientSecret)
+ *     .proxyOptions(new ProxyOptions(Type.HTTP, new InetSocketAddress("10.21.32.43", 5465)))
+ *     .build();
+ * 
*/ @Immutable public class ClientSecretCredential implements TokenCredential { From 3660848340c4b749d9aa07eb91e4022ad3aab0f6 Mon Sep 17 00:00:00 2001 From: Jianghao Lu Date: Wed, 8 Apr 2020 13:53:44 -0700 Subject: [PATCH 20/45] Lazy initialize pub client --- .../implementation/IdentityClient.java | 28 +++++++++++-------- .../implementation/IdentityClientTests.java | 15 ++++++---- 2 files changed, 27 insertions(+), 16 deletions(-) diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java index 81efd6b4f6a0e..7769b696ac47c 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java @@ -90,7 +90,7 @@ public class IdentityClient { private final ClientLogger logger = new ClientLogger(IdentityClient.class); private final IdentityClientOptions options; - private final PublicClientApplication publicClientApplication; + private PublicClientApplication publicClientApplication; private final String tenantId; private final String clientId; private HttpPipelineAdapter httpPipelineAdapter; @@ -112,8 +112,13 @@ public class IdentityClient { this.tenantId = tenantId; this.clientId = clientId; this.options = options; - if (clientId == null) { - this.publicClientApplication = null; + } + + private PublicClientApplication getPublicClientApplication() { + if (publicClientApplication != null) { + return publicClientApplication; + } else if (clientId == null) { + return null; } else { String authorityUrl = options.getAuthorityHost().replaceAll("/+$", "") + "/organizations/" + tenantId; PublicClientApplication.Builder publicClientApplicationBuilder = PublicClientApplication.builder(clientId); @@ -156,6 +161,7 @@ public class IdentityClient { } } this.publicClientApplication = publicClientApplicationBuilder.build(); + return this.publicClientApplication; } } @@ -387,7 +393,7 @@ public Mono authenticateWithPemCertificate(String pemCertificatePat */ public Mono authenticateWithUsernamePassword(TokenRequestContext request, String username, String password) { - return Mono.fromFuture(publicClientApplication.acquireToken( + return Mono.fromFuture(getPublicClientApplication().acquireToken( UserNamePasswordParameters.builder(new HashSet<>(request.getScopes()), username, password.toCharArray()) .build())) .map(ar -> new MsalToken(ar, options)); @@ -412,12 +418,12 @@ public Mono authenticateWithUserRefreshToken(TokenRequestContext requ } return Mono.defer(() -> { try { - return Mono.fromFuture(publicClientApplication.acquireTokenSilently(parameters)) + return Mono.fromFuture(getPublicClientApplication().acquireTokenSilently(parameters)) .map(ar -> new MsalToken(ar, options)) .filter(t -> !t.isExpired()) .switchIfEmpty(Mono.defer(() -> Mono.fromFuture(() -> { try { - return publicClientApplication.acquireTokenSilently(forceParameters); + return getPublicClientApplication().acquireTokenSilently(forceParameters); } catch (MalformedURLException e) { throw logger.logExceptionAsWarning(new RuntimeException(e)); } @@ -445,7 +451,7 @@ public Mono authenticateWithDeviceCode(TokenRequestContext request, DeviceCodeFlowParameters parameters = DeviceCodeFlowParameters.builder(new HashSet<>(request.getScopes()), dc -> deviceCodeConsumer.accept(new DeviceCodeInfo(dc.userCode(), dc.deviceCode(), dc.verificationUri(), OffsetDateTime.now().plusSeconds(dc.expiresIn()), dc.message()))).build(); - return publicClientApplication.acquireToken(parameters); + return getPublicClientApplication().acquireToken(parameters); }).map(ar -> new MsalToken(ar, options)); } @@ -459,7 +465,7 @@ public Mono authenticateWithDeviceCode(TokenRequestContext request, */ public Mono authenticateWithAuthorizationCode(TokenRequestContext request, String authorizationCode, URI redirectUrl) { - return Mono.fromFuture(() -> publicClientApplication.acquireToken( + return Mono.fromFuture(() -> getPublicClientApplication().acquireToken( AuthorizationCodeParameters.builder(authorizationCode, redirectUrl) .scopes(new HashSet<>(request.getScopes())) .build())) @@ -516,7 +522,7 @@ public Mono authenticateWithBrowserInteraction(TokenRequestContext re public Mono authenticateWithSharedTokenCache(TokenRequestContext request, String username) { String authorityUrl = options.getAuthorityHost().replaceAll("/+$", "") + "/" + tenantId + "/"; // find if the Public Client app with the requested username exists - return Mono.fromFuture(publicClientApplication.getAccounts()) + return Mono.fromFuture(getPublicClientApplication().getAccounts()) .onErrorResume(t -> Mono.error(new ClientAuthenticationException( "Cannot get accounts from token cache. Error: " + t.getMessage(), null))) .flatMap(set -> { @@ -568,13 +574,13 @@ public Mono authenticateWithSharedTokenCache(TokenRequestContext re CompletableFuture future; try { - future = publicClientApplication.acquireTokenSilently(params); + future = getPublicClientApplication().acquireTokenSilently(params); return Mono.fromFuture(() -> future).map(result -> new MsalToken(result, options)) .filter(t -> !t.isExpired()) .switchIfEmpty(Mono.defer(() -> Mono.fromFuture(() -> { try { - return publicClientApplication.acquireTokenSilently(forceParams); + return getPublicClientApplication().acquireTokenSilently(forceParams); } catch (MalformedURLException e) { throw logger.logExceptionAsWarning(new RuntimeException(e)); } diff --git a/sdk/identity/azure-identity/src/test/java/com/azure/identity/implementation/IdentityClientTests.java b/sdk/identity/azure-identity/src/test/java/com/azure/identity/implementation/IdentityClientTests.java index 1e942dbd516f7..f9c01d5d1cfe7 100644 --- a/sdk/identity/azure-identity/src/test/java/com/azure/identity/implementation/IdentityClientTests.java +++ b/sdk/identity/azure-identity/src/test/java/com/azure/identity/implementation/IdentityClientTests.java @@ -169,7 +169,8 @@ public void testValidDeviceCodeFlow() throws Exception { mockForDeviceCodeFlow(request, accessToken, expiresOn); // test - IdentityClient client = new IdentityClientBuilder().tenantId(tenantId).clientId(clientId).build(); + IdentityClientOptions options = new IdentityClientOptions().disableSharedTokenCache(true); + IdentityClient client = new IdentityClientBuilder().tenantId(tenantId).clientId(clientId).identityClientOptions(options).build(); AccessToken token = client.authenticateWithDeviceCode(request, deviceCodeChallenge -> { /* do nothing */ }).block(); Assert.assertEquals(accessToken, token.getToken()); Assert.assertEquals(expiresOn.getSecond(), token.getExpiresAt().getSecond()); @@ -237,7 +238,8 @@ public void testAuthorizationCodeFlow() throws Exception { mockForAuthorizationCodeFlow(token1, request, expiresAt); // test - IdentityClient client = new IdentityClientBuilder().tenantId(tenantId).clientId(clientId).build(); + IdentityClientOptions options = new IdentityClientOptions().disableSharedTokenCache(true); + IdentityClient client = new IdentityClientBuilder().tenantId(tenantId).clientId(clientId).identityClientOptions(options).build(); StepVerifier.create(client.authenticateWithAuthorizationCode(request, authCode1, redirectUri)) .expectNextMatches(accessToken -> token1.equals(accessToken.getToken()) && expiresAt.getSecond() == accessToken.getExpiresAt().getSecond()) @@ -256,7 +258,8 @@ public void testUserRefreshTokenflow() throws Exception { mockForUserRefreshTokenFlow(token2, request2, expiresAt); // test - IdentityClient client = new IdentityClientBuilder().tenantId(tenantId).clientId(clientId).build(); + IdentityClientOptions options = new IdentityClientOptions().disableSharedTokenCache(true); + IdentityClient client = new IdentityClientBuilder().tenantId(tenantId).clientId(clientId).identityClientOptions(options).build(); StepVerifier.create(client.authenticateWithUserRefreshToken(request2, TestUtils.getMockMsalToken(token1, expiresAt).block())) .expectNextMatches(accessToken -> token2.equals(accessToken.getToken()) && expiresAt.getSecond() == accessToken.getExpiresAt().getSecond()) @@ -276,7 +279,8 @@ public void testUsernamePasswordCodeFlow() throws Exception { mockForUsernamePasswordCodeFlow(token, request, expiresOn); // test - IdentityClient client = new IdentityClientBuilder().tenantId(tenantId).clientId(clientId).build(); + IdentityClientOptions options = new IdentityClientOptions().disableSharedTokenCache(true); + IdentityClient client = new IdentityClientBuilder().tenantId(tenantId).clientId(clientId).identityClientOptions(options).build(); StepVerifier.create(client.authenticateWithUsernamePassword(request, username, password)) .expectNextMatches(accessToken -> token.equals(accessToken.getToken()) && expiresOn.getSecond() == accessToken.getExpiresAt().getSecond()) @@ -297,7 +301,8 @@ public void testBrowserAuthenicationCodeFlow() throws Exception { mocForBrowserAuthenticationCodeFlow(); // test - IdentityClient client = new IdentityClientBuilder().tenantId(tenantId).clientId(clientId).build(); + IdentityClientOptions options = new IdentityClientOptions().disableSharedTokenCache(true); + IdentityClient client = new IdentityClientBuilder().tenantId(tenantId).clientId(clientId).identityClientOptions(options).build(); StepVerifier.create(client.authenticateWithBrowserInteraction(request, 4567)) .expectNextMatches(accessToken -> token.equals(accessToken.getToken()) && expiresOn.getSecond() == accessToken.getExpiresAt().getSecond()) From 435a4f25db9eb59dcad9c6deb5853b17836a1565 Mon Sep 17 00:00:00 2001 From: Jianghao Lu Date: Wed, 8 Apr 2020 14:25:24 -0700 Subject: [PATCH 21/45] Defer Mono.fromFuture() --- .../java/com/azure/identity/SharedTokenCacheCredential.java | 2 -- .../com/azure/identity/implementation/IdentityClient.java | 4 ++-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/SharedTokenCacheCredential.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/SharedTokenCacheCredential.java index 8663cf66b5ef6..4dd7b57c0a32a 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/SharedTokenCacheCredential.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/SharedTokenCacheCredential.java @@ -21,7 +21,6 @@ public class SharedTokenCacheCredential implements TokenCredential { private final String username; private final String clientId; private final String tenantId; - private final IdentityClientOptions options; private final IdentityClient identityClient; @@ -52,7 +51,6 @@ public class SharedTokenCacheCredential implements TokenCredential { } else { this.tenantId = tenantId; } - this.options = identityClientOptions; this.identityClient = new IdentityClientBuilder() .tenantId(this.tenantId) .clientId(this.clientId) diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java index 7769b696ac47c..685ba4bd18bb2 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java @@ -393,7 +393,7 @@ public Mono authenticateWithPemCertificate(String pemCertificatePat */ public Mono authenticateWithUsernamePassword(TokenRequestContext request, String username, String password) { - return Mono.fromFuture(getPublicClientApplication().acquireToken( + return Mono.fromFuture(() -> getPublicClientApplication().acquireToken( UserNamePasswordParameters.builder(new HashSet<>(request.getScopes()), username, password.toCharArray()) .build())) .map(ar -> new MsalToken(ar, options)); @@ -522,7 +522,7 @@ public Mono authenticateWithBrowserInteraction(TokenRequestContext re public Mono authenticateWithSharedTokenCache(TokenRequestContext request, String username) { String authorityUrl = options.getAuthorityHost().replaceAll("/+$", "") + "/" + tenantId + "/"; // find if the Public Client app with the requested username exists - return Mono.fromFuture(getPublicClientApplication().getAccounts()) + return Mono.fromFuture(() -> getPublicClientApplication().getAccounts()) .onErrorResume(t -> Mono.error(new ClientAuthenticationException( "Cannot get accounts from token cache. Error: " + t.getMessage(), null))) .flatMap(set -> { From 5ac9807992e1983c20ba5b684a67b174b439629d Mon Sep 17 00:00:00 2001 From: Jianghao Lu Date: Wed, 8 Apr 2020 15:11:25 -0700 Subject: [PATCH 22/45] Checkstyle --- .../identity/ChainedTokenCredential.java | 16 +----------- .../identity/ClientCertificateCredential.java | 17 ++---------- .../identity/ClientSecretCredential.java | 17 ++---------- .../identity/DefaultAzureCredential.java | 1 + .../implementation/IdentityClient.java | 26 ++++++++++--------- 5 files changed, 20 insertions(+), 57 deletions(-) diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/ChainedTokenCredential.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/ChainedTokenCredential.java index d704482eaa21b..2f42e6571de91 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/ChainedTokenCredential.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/ChainedTokenCredential.java @@ -19,21 +19,7 @@ * *

Sample: Construct a ChainedTokenCredential with silent username+password login tried first, then * interactive browser login as needed (e.g. when 2FA is turned on in the directory).

- *
- * UsernamePasswordCredential usernamePasswordCredential = new UsernamePasswordCredentialBuilder()
- *     .clientId(clientId)
- *     .username(username)
- *     .password(password)
- *     .build();
- * InteractiveBrowserCredential interactiveBrowserCredential = new InteractiveBrowserCredentialBuilder()
- *     .clientId(clientId)
- *     .port(8765)
- *     .build();
- * ChainedTokenCredential credential = new ChainedTokenCredentialBuilder()
- *     .addLast(usernamePasswordCredential)
- *     .addLast(interactiveBrowserCredential)
- *     .build();
- * 
+ * {@codesnippet com.azure.identity.credential.chainedtokencredential.construct} */ @Immutable public class ChainedTokenCredential implements TokenCredential { diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/ClientCertificateCredential.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/ClientCertificateCredential.java index 56d481007ded6..44dfd883582ca 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/ClientCertificateCredential.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/ClientCertificateCredential.java @@ -18,23 +18,10 @@ * An AAD credential that acquires a token with a client certificate for an AAD application. * *

Sample: Construct a simple ClientCertificateCredential

- *
- * ClientCertificateCredential credential1 = new ClientCertificateCredentialBuilder()
- *     .tenantId(tenantId)
- *     .clientId(clientId)
- *     .pemCertificate("C:\\fakepath\\cert.pem")
- *     .build();
- * 
+ * {@codesnippet com.azure.identity.credential.clientcertificatecredential.construct} * *

Sample: Construct a ClientCertificateCredential behind a proxy

- *
- * ClientCertificateCredential credential2 = new ClientCertificateCredentialBuilder()
- *     .tenantId(tenantId)
- *     .clientId(clientId)
- *     .pfxCertificate("C:\\fakepath\\cert.pfx", "P{@literal @}s$w0rd")
- *     .proxyOptions(new ProxyOptions(Type.HTTP, new InetSocketAddress("10.21.32.43", 5465)))
- *     .build();
- * 
+ * {@codesnippet com.azure.identity.credential.clientcertificatecredential.constructwithproxy} */ @Immutable public class ClientCertificateCredential implements TokenCredential { diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/ClientSecretCredential.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/ClientSecretCredential.java index 84e3a9337cc75..28efcb6a07497 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/ClientSecretCredential.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/ClientSecretCredential.java @@ -18,23 +18,10 @@ * An AAD credential that acquires a token with a client secret for an AAD application. * *

Sample: Construct a simple ClientSecretCredential

- *
- * ClientSecretCredential credential1 = new ClientSecretCredentialBuilder()
- *     .tenantId(tenantId)
- *     .clientId(clientId)
- *     .clientSecret(clientSecret)
- *     .build();
- * 
+ * {@codesnippet com.azure.identity.credential.clientsecretcredential.construct} * *

Sample: Construct a ClientSecretCredential behind a proxy

- *
- * ClientSecretCredential credential2 = new ClientSecretCredentialBuilder()
- *     .tenantId(tenantId)
- *     .clientId(clientId)
- *     .clientSecret(clientSecret)
- *     .proxyOptions(new ProxyOptions(Type.HTTP, new InetSocketAddress("10.21.32.43", 5465)))
- *     .build();
- * 
+ * {@codesnippet com.azure.identity.credential.clientsecretcredential.constructwithproxy} */ @Immutable public class ClientSecretCredential implements TokenCredential { diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/DefaultAzureCredential.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/DefaultAzureCredential.java index 9fac863a9e464..e8275e8ede9b3 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/DefaultAzureCredential.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/DefaultAzureCredential.java @@ -22,6 +22,7 @@ */ @Immutable public final class DefaultAzureCredential extends ChainedTokenCredential { + /** * Creates default DefaultAzureCredential instance to use. This will use AZURE_CLIENT_ID, * AZURE_CLIENT_SECRET, and AZURE_TENANT_ID environment variables to create a diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java index 685ba4bd18bb2..e24709e2e1231 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java @@ -422,12 +422,12 @@ public Mono authenticateWithUserRefreshToken(TokenRequestContext requ .map(ar -> new MsalToken(ar, options)) .filter(t -> !t.isExpired()) .switchIfEmpty(Mono.defer(() -> Mono.fromFuture(() -> { - try { - return getPublicClientApplication().acquireTokenSilently(forceParameters); - } catch (MalformedURLException e) { - throw logger.logExceptionAsWarning(new RuntimeException(e)); - } + try { + return getPublicClientApplication().acquireTokenSilently(forceParameters); + } catch (MalformedURLException e) { + throw logger.logExceptionAsWarning(new RuntimeException(e)); } + } ).map(result -> new MsalToken(result, options)))); } catch (MalformedURLException e) { return Mono.error(e); @@ -539,18 +539,20 @@ public Mono authenticateWithSharedTokenCache(TokenRequestContext re if (accounts.size() == 0) { if (username == null) { - return Mono.error(new RuntimeException("No accounts were discovered in the shared token cache." - + " To fix, authenticate through tooling supporting azure developer sign on.")); + return Mono.error(new RuntimeException("No accounts were discovered in the shared token " + + "cache. To fix, authenticate through tooling supporting azure developer sign " + + "on.")); } else { - return Mono.error(new RuntimeException(String.format("User account '%s' was not found in the " - + "shared token cache. Discovered Accounts: [ '%s' ]", username, set.stream() + return Mono.error(new RuntimeException(String.format("User account '%s' was not found in " + + "the shared token cache. Discovered Accounts: [ '%s' ]", username, set.stream() .map(IAccount::username).distinct().collect(Collectors.joining(", "))))); } } else if (accounts.size() > 1) { if (username == null) { - return Mono.error(new RuntimeException("Multiple accounts were discovered in the shared token " - + "cache. To fix, set the AZURE_USERNAME and AZURE_TENANT_ID environment variable to the " - + "preferred username, or specify it when constructing SharedTokenCacheCredential.")); + return Mono.error(new RuntimeException("Multiple accounts were discovered in the shared " + + "token cache. To fix, set the AZURE_USERNAME and AZURE_TENANT_ID environment " + + "variable to the preferred username, or specify it when constructing " + + "SharedTokenCacheCredential.")); } else { return Mono.error(new RuntimeException("Multiple entries for the user account " + username + " were found in the shared token cache. This is not currently supported by the" From f8245f0ddd25be0bac8b2859827a67e4cf08b2bb Mon Sep 17 00:00:00 2001 From: Jianghao Lu Date: Tue, 14 Apr 2020 18:37:01 +0000 Subject: [PATCH 23/45] Fix in perf test --- .../com/azure/identity/implementation/IdentityClient.java | 4 ++-- .../src/main/java/com/azure/identity/perf/ReadCache.java | 6 +++--- .../src/main/java/com/azure/identity/perf/WriteCache.java | 6 +++--- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java index e24709e2e1231..1742746a91f09 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java @@ -186,7 +186,7 @@ public Mono authenticateWithAzureCli(TokenRequestContext request) { } command.append(scopes); - + AccessToken token = null; BufferedReader reader = null; try { @@ -524,7 +524,7 @@ public Mono authenticateWithSharedTokenCache(TokenRequestContext re // find if the Public Client app with the requested username exists return Mono.fromFuture(() -> getPublicClientApplication().getAccounts()) .onErrorResume(t -> Mono.error(new ClientAuthenticationException( - "Cannot get accounts from token cache. Error: " + t.getMessage(), null))) + "Cannot get accounts from token cache. Error: " + t.getMessage(), null, t))) .flatMap(set -> { IAccount requestedAccount; Map accounts = new HashMap<>(); // home account id -> account diff --git a/sdk/identity/perf-test/src/main/java/com/azure/identity/perf/ReadCache.java b/sdk/identity/perf-test/src/main/java/com/azure/identity/perf/ReadCache.java index 5ab01c3de1401..e3a87ea9fe685 100644 --- a/sdk/identity/perf-test/src/main/java/com/azure/identity/perf/ReadCache.java +++ b/sdk/identity/perf-test/src/main/java/com/azure/identity/perf/ReadCache.java @@ -6,13 +6,13 @@ import com.azure.identity.SharedTokenCacheCredential; import com.azure.identity.SharedTokenCacheCredentialBuilder; import com.azure.identity.perf.core.ServiceTest; -import com.azure.perf.test.core.SizeOptions; +import com.azure.perf.test.core.PerfStressOptions; import reactor.core.publisher.Mono; -public class ReadCache extends ServiceTest { +public class ReadCache extends ServiceTest { private final SharedTokenCacheCredential credential; - public ReadCache(SizeOptions options) { + public ReadCache(PerfStressOptions options) { super(options); credential = new SharedTokenCacheCredentialBuilder() .clientId(CLI_CLIENT_ID) diff --git a/sdk/identity/perf-test/src/main/java/com/azure/identity/perf/WriteCache.java b/sdk/identity/perf-test/src/main/java/com/azure/identity/perf/WriteCache.java index 912c16f422053..edee2684493f2 100644 --- a/sdk/identity/perf-test/src/main/java/com/azure/identity/perf/WriteCache.java +++ b/sdk/identity/perf-test/src/main/java/com/azure/identity/perf/WriteCache.java @@ -6,15 +6,15 @@ import com.azure.identity.SharedTokenCacheCredential; import com.azure.identity.SharedTokenCacheCredentialBuilder; import com.azure.identity.perf.core.ServiceTest; -import com.azure.perf.test.core.SizeOptions; +import com.azure.perf.test.core.PerfStressOptions; import reactor.core.publisher.Mono; import java.time.Duration; -public class WriteCache extends ServiceTest { +public class WriteCache extends ServiceTest { private final SharedTokenCacheCredential credential; - public WriteCache(SizeOptions options) { + public WriteCache(PerfStressOptions options) { super(options); credential = new SharedTokenCacheCredentialBuilder() .clientId(CLI_CLIENT_ID) From 1472726a6612a02a1fd330071c1aaad23a903a5b Mon Sep 17 00:00:00 2001 From: Jianghao Lu Date: Tue, 21 Apr 2020 00:15:18 -0700 Subject: [PATCH 24/45] Does not throw except SharedTokenCacheCredential --- .../implementation/IdentityClient.java | 30 ++++++++++++------- sdk/identity/perf-test/pom.xml | 2 +- 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java index 86ce7da2589ec..f783e85987e76 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java @@ -34,6 +34,8 @@ import com.microsoft.aad.msal4j.SilentParameters; import com.microsoft.aad.msal4j.UserNamePasswordParameters; import com.microsoft.aad.msal4jextensions.PersistenceTokenCacheAccessAspect; +import com.microsoft.aad.msal4jextensions.persistence.linux.KeyRingAccessException; +import com.microsoft.aad.msal4jextensions.persistence.mac.KeyChainAccessException; import reactor.core.publisher.Mono; import reactor.core.scheduler.Schedulers; @@ -117,7 +119,7 @@ public class IdentityClient { this.options = options; } - private PublicClientApplication getPublicClientApplication() { + private PublicClientApplication getPublicClientApplication(boolean requirePersistence) { if (publicClientApplication != null) { return publicClientApplication; } else if (clientId == null) { @@ -159,8 +161,14 @@ private PublicClientApplication getPublicClientApplication() { try { publicClientApplicationBuilder.setTokenCacheAccessAspect( new PersistenceTokenCacheAccessAspect(options.getPersistenceSettings())); - } catch (IOException e) { - throw logger.logExceptionAsWarning(new IllegalStateException(e)); + } catch (IOException | KeyChainAccessException | KeyRingAccessException e) { + CredentialUnavailableException cue = new CredentialUnavailableException( + "Shared token cache is unavailable on this platform.", e); + if (requirePersistence) { + throw logger.logExceptionAsWarning(cue); + } else { + logger.logExceptionAsWarning(cue); + } } } this.publicClientApplication = publicClientApplicationBuilder.build(); @@ -400,7 +408,7 @@ public Mono authenticateWithPemCertificate(String pemCertificatePat */ public Mono authenticateWithUsernamePassword(TokenRequestContext request, String username, String password) { - return Mono.fromFuture(() -> getPublicClientApplication().acquireToken( + return Mono.fromFuture(() -> getPublicClientApplication(false).acquireToken( UserNamePasswordParameters.builder(new HashSet<>(request.getScopes()), username, password.toCharArray()) .build())) .map(ar -> new MsalToken(ar, options)); @@ -425,12 +433,12 @@ public Mono authenticateWithUserRefreshToken(TokenRequestContext requ } return Mono.defer(() -> { try { - return Mono.fromFuture(getPublicClientApplication().acquireTokenSilently(parameters)) + return Mono.fromFuture(getPublicClientApplication(false).acquireTokenSilently(parameters)) .map(ar -> new MsalToken(ar, options)) .filter(t -> !t.isExpired()) .switchIfEmpty(Mono.defer(() -> Mono.fromFuture(() -> { try { - return getPublicClientApplication().acquireTokenSilently(forceParameters); + return getPublicClientApplication(false).acquireTokenSilently(forceParameters); } catch (MalformedURLException e) { throw logger.logExceptionAsWarning(new RuntimeException(e)); } @@ -458,7 +466,7 @@ public Mono authenticateWithDeviceCode(TokenRequestContext request, DeviceCodeFlowParameters parameters = DeviceCodeFlowParameters.builder(new HashSet<>(request.getScopes()), dc -> deviceCodeConsumer.accept(new DeviceCodeInfo(dc.userCode(), dc.deviceCode(), dc.verificationUri(), OffsetDateTime.now().plusSeconds(dc.expiresIn()), dc.message()))).build(); - return getPublicClientApplication().acquireToken(parameters); + return getPublicClientApplication(false).acquireToken(parameters); }).map(ar -> new MsalToken(ar, options)); } @@ -472,7 +480,7 @@ public Mono authenticateWithDeviceCode(TokenRequestContext request, */ public Mono authenticateWithAuthorizationCode(TokenRequestContext request, String authorizationCode, URI redirectUrl) { - return Mono.fromFuture(() -> getPublicClientApplication().acquireToken( + return Mono.fromFuture(() -> getPublicClientApplication(false).acquireToken( AuthorizationCodeParameters.builder(authorizationCode, redirectUrl) .scopes(new HashSet<>(request.getScopes())) .build())) @@ -529,7 +537,7 @@ public Mono authenticateWithBrowserInteraction(TokenRequestContext re public Mono authenticateWithSharedTokenCache(TokenRequestContext request, String username) { String authorityUrl = options.getAuthorityHost().replaceAll("/+$", "") + "/" + tenantId + "/"; // find if the Public Client app with the requested username exists - return Mono.fromFuture(() -> getPublicClientApplication().getAccounts()) + return Mono.fromFuture(() -> getPublicClientApplication(true).getAccounts()) .onErrorResume(t -> Mono.error(new CredentialUnavailableException( "Cannot get accounts from token cache. Error: " + t.getMessage(), t))) .flatMap(set -> { @@ -583,13 +591,13 @@ public Mono authenticateWithSharedTokenCache(TokenRequestContext re CompletableFuture future; try { - future = getPublicClientApplication().acquireTokenSilently(params); + future = getPublicClientApplication(false).acquireTokenSilently(params); return Mono.fromFuture(() -> future).map(result -> new MsalToken(result, options)) .filter(t -> !t.isExpired()) .switchIfEmpty(Mono.defer(() -> Mono.fromFuture(() -> { try { - return getPublicClientApplication().acquireTokenSilently(forceParams); + return getPublicClientApplication(false).acquireTokenSilently(forceParams); } catch (MalformedURLException e) { throw logger.logExceptionAsWarning(new RuntimeException(e)); } diff --git a/sdk/identity/perf-test/pom.xml b/sdk/identity/perf-test/pom.xml index ca0650f96cb7a..a48d02f94172e 100644 --- a/sdk/identity/perf-test/pom.xml +++ b/sdk/identity/perf-test/pom.xml @@ -27,7 +27,7 @@ com.azure azure-identity - 1.1.0-beta.3 + 1.1.0-beta.4 com.azure From 1cc825445fa02cfa045a30e07ce3a61a575965cc Mon Sep 17 00:00:00 2001 From: Jianghao Lu Date: Tue, 21 Apr 2020 11:15:47 -0700 Subject: [PATCH 25/45] Fix merge error --- eng/versioning/version_client.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/eng/versioning/version_client.txt b/eng/versioning/version_client.txt index 3e5f10730ef1b..f6faeea152fb0 100644 --- a/eng/versioning/version_client.txt +++ b/eng/versioning/version_client.txt @@ -21,7 +21,6 @@ com.azure:azure-data-appconfiguration;1.1.1;1.2.0-beta.1 com.azure:azure-e2e;1.0.0-beta.1;1.0.0-beta.1 com.azure:azure-identity;1.0.5;1.1.0-beta.4 com.azure:azure-identity-perf;1.0.0-beta.1;1.0.0-beta.1 ->>>>>>> 3a255eb005748305722895bc50d3dd4ff5789bdb com.azure:azure-messaging-eventhubs;5.0.3;5.1.0-beta.1 com.azure:azure-messaging-eventhubs-checkpointstore-blob;1.0.3;1.1.0-beta.1 com.azure:azure-messaging-servicebus;7.0.0-beta.1;7.0.0-beta.2 From 6b1e50d109738ef602e5c0370617d420d503fdd9 Mon Sep 17 00:00:00 2001 From: Jianghao Lu Date: Tue, 21 Apr 2020 14:59:58 -0700 Subject: [PATCH 26/45] x-include-update --- sdk/identity/azure-identity/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/identity/azure-identity/pom.xml b/sdk/identity/azure-identity/pom.xml index b685e87837e59..f7455b82b8f00 100644 --- a/sdk/identity/azure-identity/pom.xml +++ b/sdk/identity/azure-identity/pom.xml @@ -112,7 +112,7 @@ com.azure:* com.microsoft.azure:msal4j:[1.3.0] - com.microsoft.azure:msal4j-persistence-extension:[0.1] + com.microsoft.azure:msal4j-persistence-extension:[0.1] com.nimbusds:oauth2-oidc-sdk:[6.14] net.java.dev.jna:jna-platform:[5.4.0] org.nanohttpd:nanohttpd:[2.3.1] From 3a16cadc09e7e61a66ef9bdcc1e4c8baab99a0ec Mon Sep 17 00:00:00 2001 From: Jianghao Lu Date: Tue, 21 Apr 2020 22:46:47 -0700 Subject: [PATCH 27/45] Minor change in public client creation --- .../com/azure/identity/implementation/IdentityClient.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java index f783e85987e76..80bb494c4b2fc 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java @@ -34,8 +34,6 @@ import com.microsoft.aad.msal4j.SilentParameters; import com.microsoft.aad.msal4j.UserNamePasswordParameters; import com.microsoft.aad.msal4jextensions.PersistenceTokenCacheAccessAspect; -import com.microsoft.aad.msal4jextensions.persistence.linux.KeyRingAccessException; -import com.microsoft.aad.msal4jextensions.persistence.mac.KeyChainAccessException; import reactor.core.publisher.Mono; import reactor.core.scheduler.Schedulers; @@ -161,9 +159,9 @@ private PublicClientApplication getPublicClientApplication(boolean requirePersis try { publicClientApplicationBuilder.setTokenCacheAccessAspect( new PersistenceTokenCacheAccessAspect(options.getPersistenceSettings())); - } catch (IOException | KeyChainAccessException | KeyRingAccessException e) { + } catch (Throwable t) { CredentialUnavailableException cue = new CredentialUnavailableException( - "Shared token cache is unavailable on this platform.", e); + "Shared token cache is unavailable on this platform.", t); if (requirePersistence) { throw logger.logExceptionAsWarning(cue); } else { From db0eb379f701bf774bb710128c34afca682da12a Mon Sep 17 00:00:00 2001 From: Jianghao Lu Date: Tue, 21 Apr 2020 23:39:13 -0700 Subject: [PATCH 28/45] Checkstyle --- .../implementation/IdentityClient.java | 27 ++++++++++--------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java index 80bb494c4b2fc..c27fbb653fe98 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java @@ -552,24 +552,25 @@ public Mono authenticateWithSharedTokenCache(TokenRequestContext re if (accounts.size() == 0) { if (username == null) { - return Mono.error(new CredentialUnavailableException("No accounts were discovered in the shared token " - + "cache. To fix, authenticate through tooling supporting azure developer sign " - + "on.")); + return Mono.error(new CredentialUnavailableException("No accounts were discovered in the " + + "shared token cache. To fix, authenticate through tooling supporting azure " + + "developer sign on.")); } else { - return Mono.error(new CredentialUnavailableException(String.format("User account '%s' was not found in " - + "the shared token cache. Discovered Accounts: [ '%s' ]", username, set.stream() - .map(IAccount::username).distinct().collect(Collectors.joining(", "))))); + return Mono.error(new CredentialUnavailableException(String.format("User account '%s' was " + + "not found in the shared token cache. Discovered Accounts: [ '%s' ]", username, + set.stream().map(IAccount::username).distinct() + .collect(Collectors.joining(", "))))); } } else if (accounts.size() > 1) { if (username == null) { - return Mono.error(new CredentialUnavailableException("Multiple accounts were discovered in the shared " - + "token cache. To fix, set the AZURE_USERNAME and AZURE_TENANT_ID environment " - + "variable to the preferred username, or specify it when constructing " - + "SharedTokenCacheCredential.")); + return Mono.error(new CredentialUnavailableException("Multiple accounts were discovered " + + "in the shared token cache. To fix, set the AZURE_USERNAME and AZURE_TENANT_ID " + + "environment variable to the preferred username, or specify it when " + + "constructing SharedTokenCacheCredential.")); } else { - return Mono.error(new CredentialUnavailableException("Multiple entries for the user account " + username - + " were found in the shared token cache. This is not currently supported by the" - + " SharedTokenCacheCredential.")); + return Mono.error(new CredentialUnavailableException("Multiple entries for the user " + + "account " + username + " were found in the shared token cache. This is not " + + "currently supported by the SharedTokenCacheCredential.")); } } else { requestedAccount = accounts.values().iterator().next(); From 775568f00d9d8b942c670b74412c0e0112a6d15b Mon Sep 17 00:00:00 2001 From: Jianghao Lu Date: Tue, 21 Apr 2020 23:52:05 -0700 Subject: [PATCH 29/45] Revert persistent cache demo --- .../keyvault/secrets/PersistentTokenCacheDemo.java | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/sdk/keyvault/azure-security-keyvault-secrets/src/samples/java/com/azure/security/keyvault/secrets/PersistentTokenCacheDemo.java b/sdk/keyvault/azure-security-keyvault-secrets/src/samples/java/com/azure/security/keyvault/secrets/PersistentTokenCacheDemo.java index c408eec083668..fb98d42c13f34 100644 --- a/sdk/keyvault/azure-security-keyvault-secrets/src/samples/java/com/azure/security/keyvault/secrets/PersistentTokenCacheDemo.java +++ b/sdk/keyvault/azure-security-keyvault-secrets/src/samples/java/com/azure/security/keyvault/secrets/PersistentTokenCacheDemo.java @@ -3,11 +3,9 @@ package com.azure.security.keyvault.secrets; -import com.azure.identity.DefaultAzureCredential; -import com.azure.identity.DefaultAzureCredentialBuilder; import com.azure.security.keyvault.secrets.models.KeyVaultSecret; - -import java.time.Duration; +import com.azure.identity.SharedTokenCacheCredential; +import com.azure.identity.SharedTokenCacheCredentialBuilder; /** * Sample showing how to authenticate to key vault with a shared token cache credential. @@ -21,8 +19,9 @@ public class PersistentTokenCacheDemo { public static void main(String[] args) { // Wrote to AZURE_USERNAME env variable - DefaultAzureCredential defaultCredential = new DefaultAzureCredentialBuilder() - .tokenRefreshOffset(Duration.ofMinutes(60)).build(); + SharedTokenCacheCredential defaultCredential = new SharedTokenCacheCredentialBuilder() + .clientId("04b07795-8ddb-461a-bbee-02f9e1bf7b46") + .build(); SecretClient client = new SecretClientBuilder() .vaultUrl("https://persistentcachedemo.vault.azure.net") From 006b191ee58398d9114cdab3625bd1a87ce36726 Mon Sep 17 00:00:00 2001 From: Jianghao Lu Date: Wed, 22 Apr 2020 07:26:35 +0000 Subject: [PATCH 30/45] Align Linux default settings with MSAL.NET tests --- .../src/main/java/com/azure/identity/KeyringItemSchema.java | 1 + .../azure/identity/implementation/IdentityClientOptions.java | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/KeyringItemSchema.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/KeyringItemSchema.java index 7b9006d86d225..4c1aabe33044e 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/KeyringItemSchema.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/KeyringItemSchema.java @@ -11,6 +11,7 @@ public final class KeyringItemSchema { public static final KeyringItemSchema NETWORK_PASSWORD = new KeyringItemSchema( "org.gnome.keyring.NetworkPassword"); public static final KeyringItemSchema NOTE = new KeyringItemSchema("org.gnome.keyring.Note"); + public static final KeyringItemSchema MSAL_CACHE = new KeyringItemSchema("msal.cache"); private final String value; diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClientOptions.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClientOptions.java index 6bab967e2ed34..52458ec45460c 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClientOptions.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClientOptions.java @@ -32,7 +32,7 @@ public final class IdentityClientOptions { private static final String DEFAULT_KEYCHAIN_SERVICE = "Microsoft.Developer.IdentityService"; private static final String DEFAULT_KEYCHAIN_ACCOUNT = "MSALCache"; private static final String DEFAULT_KEYRING_NAME = "default"; - private static final KeyringItemSchema DEFAULT_KEYRING_SCHEMA = KeyringItemSchema.GENERIC_SECRET; + private static final KeyringItemSchema DEFAULT_KEYRING_SCHEMA = KeyringItemSchema.MSAL_CACHE; private static final String DEFAULT_KEYRING_ITEM_NAME = DEFAULT_KEYCHAIN_ACCOUNT; private static final String DEFAULT_KEYRING_ATTR_NAME = "MsalClientID"; private static final String DEFAULT_KEYRING_ATTR_VALUE = "Microsoft.Developer.IdentityService"; @@ -195,7 +195,7 @@ public IdentityClientOptions setExecutorService(ExecutorService executorService) public ExecutorService getExecutorService() { return executorService; } - + /** * @return how long before the actual token expiry to refresh the token. */ From eb9c6150b09493f5f45eebe6e563b428401edf51 Mon Sep 17 00:00:00 2001 From: Jianghao Lu Date: Thu, 23 Apr 2020 11:07:47 -0700 Subject: [PATCH 31/45] Fix readme and use expandable enum --- .../README.md | 0 .../pom.xml | 0 .../java/com/azure/identity/perf/App.java | 0 .../com/azure/identity/perf/ReadCache.java | 0 .../com/azure/identity/perf/WriteCache.java | 0 .../azure/identity/perf/core/ServiceTest.java | 0 .../identity/AadCredentialBuilderBase.java | 4 +-- .../com/azure/identity/KeyringItemSchema.java | 35 ++++++------------- .../implementation/IdentityClient.java | 1 - sdk/identity/pom.xml | 2 +- 10 files changed, 13 insertions(+), 29 deletions(-) rename sdk/identity/{perf-test => azure-identity-perf}/README.md (100%) rename sdk/identity/{perf-test => azure-identity-perf}/pom.xml (100%) rename sdk/identity/{perf-test => azure-identity-perf}/src/main/java/com/azure/identity/perf/App.java (100%) rename sdk/identity/{perf-test => azure-identity-perf}/src/main/java/com/azure/identity/perf/ReadCache.java (100%) rename sdk/identity/{perf-test => azure-identity-perf}/src/main/java/com/azure/identity/perf/WriteCache.java (100%) rename sdk/identity/{perf-test => azure-identity-perf}/src/main/java/com/azure/identity/perf/core/ServiceTest.java (100%) diff --git a/sdk/identity/perf-test/README.md b/sdk/identity/azure-identity-perf/README.md similarity index 100% rename from sdk/identity/perf-test/README.md rename to sdk/identity/azure-identity-perf/README.md diff --git a/sdk/identity/perf-test/pom.xml b/sdk/identity/azure-identity-perf/pom.xml similarity index 100% rename from sdk/identity/perf-test/pom.xml rename to sdk/identity/azure-identity-perf/pom.xml diff --git a/sdk/identity/perf-test/src/main/java/com/azure/identity/perf/App.java b/sdk/identity/azure-identity-perf/src/main/java/com/azure/identity/perf/App.java similarity index 100% rename from sdk/identity/perf-test/src/main/java/com/azure/identity/perf/App.java rename to sdk/identity/azure-identity-perf/src/main/java/com/azure/identity/perf/App.java diff --git a/sdk/identity/perf-test/src/main/java/com/azure/identity/perf/ReadCache.java b/sdk/identity/azure-identity-perf/src/main/java/com/azure/identity/perf/ReadCache.java similarity index 100% rename from sdk/identity/perf-test/src/main/java/com/azure/identity/perf/ReadCache.java rename to sdk/identity/azure-identity-perf/src/main/java/com/azure/identity/perf/ReadCache.java diff --git a/sdk/identity/perf-test/src/main/java/com/azure/identity/perf/WriteCache.java b/sdk/identity/azure-identity-perf/src/main/java/com/azure/identity/perf/WriteCache.java similarity index 100% rename from sdk/identity/perf-test/src/main/java/com/azure/identity/perf/WriteCache.java rename to sdk/identity/azure-identity-perf/src/main/java/com/azure/identity/perf/WriteCache.java diff --git a/sdk/identity/perf-test/src/main/java/com/azure/identity/perf/core/ServiceTest.java b/sdk/identity/azure-identity-perf/src/main/java/com/azure/identity/perf/core/ServiceTest.java similarity index 100% rename from sdk/identity/perf-test/src/main/java/com/azure/identity/perf/core/ServiceTest.java rename to sdk/identity/azure-identity-perf/src/main/java/com/azure/identity/perf/core/ServiceTest.java diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/AadCredentialBuilderBase.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/AadCredentialBuilderBase.java index d888be261c16e..d6a43b06ef422 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/AadCredentialBuilderBase.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/AadCredentialBuilderBase.java @@ -77,7 +77,7 @@ public T executorService(ExecutorService executorService) { * * @param useUnprotectedFileOnLinux whether to use an unprotected file for cache storage. * - * @return The updated T object. + * @return An updated instance of this builder with the unprotected token cache setting set as specified. */ @SuppressWarnings("unchecked") public T useUnprotectedTokenCacheFileOnLinux(boolean useUnprotectedFileOnLinux) { @@ -90,7 +90,7 @@ public T useUnprotectedTokenCacheFileOnLinux(boolean useUnprotectedFileOnLinux) * * @param disabled whether to disable using the shared token cache. * - * @return The updated identity client options. + * @return An updated instance of this builder with if the shared token cache disabled specified. */ @SuppressWarnings("unchecked") public T disableSharedTokenCache(boolean disabled) { diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/KeyringItemSchema.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/KeyringItemSchema.java index 4c1aabe33044e..1df13872e4385 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/KeyringItemSchema.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/KeyringItemSchema.java @@ -3,33 +3,18 @@ package com.azure.identity; +import com.azure.core.util.ExpandableStringEnum; + /** * An expandable enum for types of item schema in a Keyring. */ -public final class KeyringItemSchema { - public static final KeyringItemSchema GENERIC_SECRET = new KeyringItemSchema("org.freedesktop.Secret.Generic"); - public static final KeyringItemSchema NETWORK_PASSWORD = new KeyringItemSchema( - "org.gnome.keyring.NetworkPassword"); - public static final KeyringItemSchema NOTE = new KeyringItemSchema("org.gnome.keyring.Note"); - public static final KeyringItemSchema MSAL_CACHE = new KeyringItemSchema("msal.cache"); - - private final String value; - - private KeyringItemSchema(String value) { - this.value = value; - } - - /** - * Parses a String into a new Keyring schema. - * @param schema the full name of the schema - * @return the KeyringItemSchema enum representing this schema - */ - public static KeyringItemSchema fromString(String schema) { - return new KeyringItemSchema(schema); - } - @Override - public String toString() { - return value; - } +public final class KeyringItemSchema extends ExpandableStringEnum { + public static final KeyringItemSchema GENERIC_SECRET = fromString("org.freedesktop.Secret.Generic", + KeyringItemSchema.class); + public static final KeyringItemSchema NETWORK_PASSWORD = fromString("org.gnome.keyring.NetworkPassword", + KeyringItemSchema.class); + public static final KeyringItemSchema NOTE = fromString("org.gnome.keyring.Note", + KeyringItemSchema.class); + public static final KeyringItemSchema MSAL_CACHE = fromString("msal.cache", KeyringItemSchema.class); } diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java index c27fbb653fe98..21e82de822a63 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java @@ -603,7 +603,6 @@ public Mono authenticateWithSharedTokenCache(TokenRequestContext re } ).map(result -> new MsalToken(result, options)))); } catch (MalformedURLException e) { - e.printStackTrace(); return Mono.error(new RuntimeException("Token was not found")); } }); diff --git a/sdk/identity/pom.xml b/sdk/identity/pom.xml index 7927146b32d5e..98648ba66804a 100644 --- a/sdk/identity/pom.xml +++ b/sdk/identity/pom.xml @@ -12,6 +12,6 @@ azure-identity - perf-test + azure-identity-perf From 233ce5cb67766680997b09cd944cda342f92a1c0 Mon Sep 17 00:00:00 2001 From: Jianghao Lu Date: Tue, 28 Apr 2020 23:24:47 -0700 Subject: [PATCH 32/45] Disable shared token cache by default --- .../identity/AadCredentialBuilderBase.java | 16 ++++----- .../com/azure/identity/KeyringItemSchema.java | 20 ----------- .../implementation/IdentityClient.java | 2 +- .../implementation/IdentityClientOptions.java | 35 +++++++++---------- .../implementation/IdentityClientTests.java | 10 +++--- 5 files changed, 31 insertions(+), 52 deletions(-) delete mode 100644 sdk/identity/azure-identity/src/main/java/com/azure/identity/KeyringItemSchema.java diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/AadCredentialBuilderBase.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/AadCredentialBuilderBase.java index d6a43b06ef422..28e174920aeba 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/AadCredentialBuilderBase.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/AadCredentialBuilderBase.java @@ -75,26 +75,26 @@ public T executorService(ExecutorService executorService) { * Sets whether to use an unprotected file specified by cacheFileLocation() instead of * Gnome keyring on Linux. This is false by default. * - * @param useUnprotectedFileOnLinux whether to use an unprotected file for cache storage. + * @param allowUnencryptedCache whether to use an unprotected file for cache storage. * * @return An updated instance of this builder with the unprotected token cache setting set as specified. */ @SuppressWarnings("unchecked") - public T useUnprotectedTokenCacheFileOnLinux(boolean useUnprotectedFileOnLinux) { - this.identityClientOptions.setUseUnprotectedTokenCacheFileOnLinux(useUnprotectedFileOnLinux); + public T allowUnencryptedCache(boolean allowUnencryptedCache) { + this.identityClientOptions.allowUnencryptedCache(allowUnencryptedCache); return (T) this; } /** - * Disable using the shared token cache. + * Sets whether to enable using the shared token cache. * - * @param disabled whether to disable using the shared token cache. + * @param enabled whether to enabled using the shared token cache. * - * @return An updated instance of this builder with if the shared token cache disabled specified. + * @return An updated instance of this builder with if the shared token cache enabled specified. */ @SuppressWarnings("unchecked") - public T disableSharedTokenCache(boolean disabled) { - this.identityClientOptions.disableSharedTokenCache(disabled); + public T enableSharedTokenCache(boolean enabled) { + this.identityClientOptions.enableSharedTokenCache(enabled); return (T) this; } } diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/KeyringItemSchema.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/KeyringItemSchema.java deleted file mode 100644 index 1df13872e4385..0000000000000 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/KeyringItemSchema.java +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.identity; - -import com.azure.core.util.ExpandableStringEnum; - -/** - * An expandable enum for types of item schema in a Keyring. - */ - -public final class KeyringItemSchema extends ExpandableStringEnum { - public static final KeyringItemSchema GENERIC_SECRET = fromString("org.freedesktop.Secret.Generic", - KeyringItemSchema.class); - public static final KeyringItemSchema NETWORK_PASSWORD = fromString("org.gnome.keyring.NetworkPassword", - KeyringItemSchema.class); - public static final KeyringItemSchema NOTE = fromString("org.gnome.keyring.Note", - KeyringItemSchema.class); - public static final KeyringItemSchema MSAL_CACHE = fromString("msal.cache", KeyringItemSchema.class); -} diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java index 21e82de822a63..277c85cd6ecda 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java @@ -155,7 +155,7 @@ private PublicClientApplication getPublicClientApplication(boolean requirePersis if (options.getExecutorService() != null) { publicClientApplicationBuilder.executorService(options.getExecutorService()); } - if (!options.isSharedTokenCacheDisabled()) { + if (!options.isSharedTokenCacheEnabled()) { try { publicClientApplicationBuilder.setTokenCacheAccessAspect( new PersistenceTokenCacheAccessAspect(options.getPersistenceSettings())); diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClientOptions.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClientOptions.java index 52458ec45460c..b71fedb80a71d 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClientOptions.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClientOptions.java @@ -7,7 +7,6 @@ import com.azure.core.http.HttpPipeline; import com.azure.core.http.ProxyOptions; import com.azure.core.util.Configuration; -import com.azure.identity.KeyringItemSchema; import com.azure.identity.KnownAuthorityHosts; import com.microsoft.aad.msal4jextensions.PersistenceSettings; import com.sun.jna.Platform; @@ -32,7 +31,7 @@ public final class IdentityClientOptions { private static final String DEFAULT_KEYCHAIN_SERVICE = "Microsoft.Developer.IdentityService"; private static final String DEFAULT_KEYCHAIN_ACCOUNT = "MSALCache"; private static final String DEFAULT_KEYRING_NAME = "default"; - private static final KeyringItemSchema DEFAULT_KEYRING_SCHEMA = KeyringItemSchema.MSAL_CACHE; + private static final String DEFAULT_KEYRING_SCHEMA = "msal.cache"; private static final String DEFAULT_KEYRING_ITEM_NAME = DEFAULT_KEYCHAIN_ACCOUNT; private static final String DEFAULT_KEYRING_ATTR_NAME = "MsalClientID"; private static final String DEFAULT_KEYRING_ATTR_VALUE = "Microsoft.Developer.IdentityService"; @@ -50,11 +49,11 @@ public final class IdentityClientOptions { private String keychainService; private String keychainAccount; private String keyringName; - private KeyringItemSchema keyringItemSchema; + private String keyringItemSchema; private String keyringItemName; private final String[] attributes; // preserve order - private boolean useUnprotectedFileOnLinux; - private boolean sharedTokenCacheDisabled; + private boolean allowUnencryptedCache; + private boolean sharedTokenCacheEnabled; /** * Creates an instance of IdentityClientOptions with default settings. @@ -72,8 +71,8 @@ public IdentityClientOptions() { keyringItemSchema = DEFAULT_KEYRING_SCHEMA; keyringItemName = DEFAULT_KEYRING_ITEM_NAME; attributes = new String[] { DEFAULT_KEYRING_ATTR_NAME, DEFAULT_KEYRING_ATTR_VALUE }; - useUnprotectedFileOnLinux = false; - sharedTokenCacheDisabled = false; + allowUnencryptedCache = false; + sharedTokenCacheEnabled = false; } /** @@ -234,9 +233,9 @@ public IdentityClientOptions setHttpClient(HttpClient httpClient) { PersistenceSettings getPersistenceSettings() { return PersistenceSettings.builder(cacheFileName, cacheFileDirectory) .setMacKeychain(keychainService, keychainAccount) - .setLinuxKeyring(keyringName, keyringItemSchema.toString(), keyringItemName, + .setLinuxKeyring(keyringName, keyringItemSchema, keyringItemName, attributes[0], attributes[1], null, null) - .setLinuxUseUnprotectedFileAsCacheStorage(useUnprotectedFileOnLinux) + .setLinuxUseUnprotectedFileAsCacheStorage(allowUnencryptedCache) .build(); } @@ -244,12 +243,12 @@ PersistenceSettings getPersistenceSettings() { * Sets whether to use an unprotected file specified by cacheFileLocation() instead of * Gnome keyring on Linux. This is false by default. * - * @param useUnprotectedFileOnLinux whether to use an unprotected file for cache storage. + * @param allowUnencryptedCache whether to use an unprotected file for cache storage. * * @return The updated identity client options. */ - public IdentityClientOptions setUseUnprotectedTokenCacheFileOnLinux(boolean useUnprotectedFileOnLinux) { - this.useUnprotectedFileOnLinux = useUnprotectedFileOnLinux; + public IdentityClientOptions allowUnencryptedCache(boolean allowUnencryptedCache) { + this.allowUnencryptedCache = allowUnencryptedCache; return this; } @@ -257,19 +256,19 @@ public IdentityClientOptions setUseUnprotectedTokenCacheFileOnLinux(boolean useU * Gets if the shared token cache is disabled. * @return if the shared token cache is disabled. */ - public boolean isSharedTokenCacheDisabled() { - return this.sharedTokenCacheDisabled; + public boolean isSharedTokenCacheEnabled() { + return this.sharedTokenCacheEnabled; } /** - * Disable using the shared token cache. + * Sets whether to enable using the shared token cache. * - * @param disabled whether to disable using the shared token cache. + * @param enabled whether to enable using the shared token cache. * * @return The updated identity client options. */ - public IdentityClientOptions disableSharedTokenCache(boolean disabled) { - this.sharedTokenCacheDisabled = disabled; + public IdentityClientOptions enableSharedTokenCache(boolean enabled) { + this.sharedTokenCacheEnabled = enabled; return this; } } diff --git a/sdk/identity/azure-identity/src/test/java/com/azure/identity/implementation/IdentityClientTests.java b/sdk/identity/azure-identity/src/test/java/com/azure/identity/implementation/IdentityClientTests.java index f9c01d5d1cfe7..b673cd2cefe2a 100644 --- a/sdk/identity/azure-identity/src/test/java/com/azure/identity/implementation/IdentityClientTests.java +++ b/sdk/identity/azure-identity/src/test/java/com/azure/identity/implementation/IdentityClientTests.java @@ -169,7 +169,7 @@ public void testValidDeviceCodeFlow() throws Exception { mockForDeviceCodeFlow(request, accessToken, expiresOn); // test - IdentityClientOptions options = new IdentityClientOptions().disableSharedTokenCache(true); + IdentityClientOptions options = new IdentityClientOptions().enableSharedTokenCache(true); IdentityClient client = new IdentityClientBuilder().tenantId(tenantId).clientId(clientId).identityClientOptions(options).build(); AccessToken token = client.authenticateWithDeviceCode(request, deviceCodeChallenge -> { /* do nothing */ }).block(); Assert.assertEquals(accessToken, token.getToken()); @@ -238,7 +238,7 @@ public void testAuthorizationCodeFlow() throws Exception { mockForAuthorizationCodeFlow(token1, request, expiresAt); // test - IdentityClientOptions options = new IdentityClientOptions().disableSharedTokenCache(true); + IdentityClientOptions options = new IdentityClientOptions().enableSharedTokenCache(true); IdentityClient client = new IdentityClientBuilder().tenantId(tenantId).clientId(clientId).identityClientOptions(options).build(); StepVerifier.create(client.authenticateWithAuthorizationCode(request, authCode1, redirectUri)) .expectNextMatches(accessToken -> token1.equals(accessToken.getToken()) @@ -258,7 +258,7 @@ public void testUserRefreshTokenflow() throws Exception { mockForUserRefreshTokenFlow(token2, request2, expiresAt); // test - IdentityClientOptions options = new IdentityClientOptions().disableSharedTokenCache(true); + IdentityClientOptions options = new IdentityClientOptions().enableSharedTokenCache(true); IdentityClient client = new IdentityClientBuilder().tenantId(tenantId).clientId(clientId).identityClientOptions(options).build(); StepVerifier.create(client.authenticateWithUserRefreshToken(request2, TestUtils.getMockMsalToken(token1, expiresAt).block())) .expectNextMatches(accessToken -> token2.equals(accessToken.getToken()) @@ -279,7 +279,7 @@ public void testUsernamePasswordCodeFlow() throws Exception { mockForUsernamePasswordCodeFlow(token, request, expiresOn); // test - IdentityClientOptions options = new IdentityClientOptions().disableSharedTokenCache(true); + IdentityClientOptions options = new IdentityClientOptions().enableSharedTokenCache(true); IdentityClient client = new IdentityClientBuilder().tenantId(tenantId).clientId(clientId).identityClientOptions(options).build(); StepVerifier.create(client.authenticateWithUsernamePassword(request, username, password)) .expectNextMatches(accessToken -> token.equals(accessToken.getToken()) @@ -301,7 +301,7 @@ public void testBrowserAuthenicationCodeFlow() throws Exception { mocForBrowserAuthenticationCodeFlow(); // test - IdentityClientOptions options = new IdentityClientOptions().disableSharedTokenCache(true); + IdentityClientOptions options = new IdentityClientOptions().enableSharedTokenCache(true); IdentityClient client = new IdentityClientBuilder().tenantId(tenantId).clientId(clientId).identityClientOptions(options).build(); StepVerifier.create(client.authenticateWithBrowserInteraction(request, 4567)) .expectNextMatches(accessToken -> token.equals(accessToken.getToken()) From a0b1725bcf66d5d879ba8a764e1281886193b101 Mon Sep 17 00:00:00 2001 From: Jianghao Lu Date: Tue, 28 Apr 2020 23:44:29 -0700 Subject: [PATCH 33/45] Allow enabling on select builders --- .../identity/AadCredentialBuilderBase.java | 27 ------------------- .../AuthorizationCodeCredentialBuilder.java | 25 +++++++++++++++++ .../identity/DeviceCodeCredentialBuilder.java | 25 +++++++++++++++++ .../InteractiveBrowserCredentialBuilder.java | 25 +++++++++++++++++ .../SharedTokenCacheCredentialBuilder.java | 13 +++++++++ .../UsernamePasswordCredentialBuilder.java | 25 +++++++++++++++++ .../implementation/IdentityClient.java | 2 +- .../implementation/IdentityClientTests.java | 10 +++---- 8 files changed, 119 insertions(+), 33 deletions(-) diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/AadCredentialBuilderBase.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/AadCredentialBuilderBase.java index 28e174920aeba..e64d14c086768 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/AadCredentialBuilderBase.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/AadCredentialBuilderBase.java @@ -70,31 +70,4 @@ public T executorService(ExecutorService executorService) { this.identityClientOptions.setExecutorService(executorService); return (T) this; } - - /** - * Sets whether to use an unprotected file specified by cacheFileLocation() instead of - * Gnome keyring on Linux. This is false by default. - * - * @param allowUnencryptedCache whether to use an unprotected file for cache storage. - * - * @return An updated instance of this builder with the unprotected token cache setting set as specified. - */ - @SuppressWarnings("unchecked") - public T allowUnencryptedCache(boolean allowUnencryptedCache) { - this.identityClientOptions.allowUnencryptedCache(allowUnencryptedCache); - return (T) this; - } - - /** - * Sets whether to enable using the shared token cache. - * - * @param enabled whether to enabled using the shared token cache. - * - * @return An updated instance of this builder with if the shared token cache enabled specified. - */ - @SuppressWarnings("unchecked") - public T enableSharedTokenCache(boolean enabled) { - this.identityClientOptions.enableSharedTokenCache(enabled); - return (T) this; - } } diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/AuthorizationCodeCredentialBuilder.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/AuthorizationCodeCredentialBuilder.java index c9a00e6c9c4d1..7f89307ac361c 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/AuthorizationCodeCredentialBuilder.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/AuthorizationCodeCredentialBuilder.java @@ -47,6 +47,31 @@ public AuthorizationCodeCredentialBuilder redirectUrl(String redirectUrl) { return this; } + /** + * Sets whether to use an unprotected file specified by cacheFileLocation() instead of + * Gnome keyring on Linux. This is false by default. + * + * @param allowUnencryptedCache whether to use an unprotected file for cache storage. + * + * @return An updated instance of this builder with the unprotected token cache setting set as specified. + */ + public AuthorizationCodeCredentialBuilder allowUnencryptedCache(boolean allowUnencryptedCache) { + this.identityClientOptions.allowUnencryptedCache(allowUnencryptedCache); + return this; + } + + /** + * Sets whether to enable using the shared token cache. + * + * @param enabled whether to enabled using the shared token cache. + * + * @return An updated instance of this builder with if the shared token cache enabled specified. + */ + public AuthorizationCodeCredentialBuilder enableSharedTokenCache(boolean enabled) { + this.identityClientOptions.enableSharedTokenCache(enabled); + return this; + } + /** * Creates a new {@link AuthorizationCodeCredential} with the current configurations. * diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/DeviceCodeCredentialBuilder.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/DeviceCodeCredentialBuilder.java index 08d74b043a1ab..e346d21928f5e 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/DeviceCodeCredentialBuilder.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/DeviceCodeCredentialBuilder.java @@ -29,6 +29,31 @@ public DeviceCodeCredentialBuilder challengeConsumer( return this; } + /** + * Sets whether to use an unprotected file specified by cacheFileLocation() instead of + * Gnome keyring on Linux. This is false by default. + * + * @param allowUnencryptedCache whether to use an unprotected file for cache storage. + * + * @return An updated instance of this builder with the unprotected token cache setting set as specified. + */ + public DeviceCodeCredentialBuilder allowUnencryptedCache(boolean allowUnencryptedCache) { + this.identityClientOptions.allowUnencryptedCache(allowUnencryptedCache); + return this; + } + + /** + * Sets whether to enable using the shared token cache. + * + * @param enabled whether to enabled using the shared token cache. + * + * @return An updated instance of this builder with if the shared token cache enabled specified. + */ + public DeviceCodeCredentialBuilder enableSharedTokenCache(boolean enabled) { + this.identityClientOptions.enableSharedTokenCache(enabled); + return this; + } + /** * Creates a new {@link DeviceCodeCredential} with the current configurations. * diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/InteractiveBrowserCredentialBuilder.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/InteractiveBrowserCredentialBuilder.java index d0ef8e3a863f3..d7625955e76c0 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/InteractiveBrowserCredentialBuilder.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/InteractiveBrowserCredentialBuilder.java @@ -27,6 +27,31 @@ public InteractiveBrowserCredentialBuilder port(int port) { return this; } + /** + * Sets whether to use an unprotected file specified by cacheFileLocation() instead of + * Gnome keyring on Linux. This is false by default. + * + * @param allowUnencryptedCache whether to use an unprotected file for cache storage. + * + * @return An updated instance of this builder with the unprotected token cache setting set as specified. + */ + public InteractiveBrowserCredentialBuilder allowUnencryptedCache(boolean allowUnencryptedCache) { + this.identityClientOptions.allowUnencryptedCache(allowUnencryptedCache); + return this; + } + + /** + * Sets whether to enable using the shared token cache. + * + * @param enabled whether to enabled using the shared token cache. + * + * @return An updated instance of this builder with if the shared token cache enabled specified. + */ + public InteractiveBrowserCredentialBuilder enableSharedTokenCache(boolean enabled) { + this.identityClientOptions.enableSharedTokenCache(enabled); + return this; + } + /** * Creates a new {@link InteractiveBrowserCredential} with the current configurations. * diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/SharedTokenCacheCredentialBuilder.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/SharedTokenCacheCredentialBuilder.java index 397be6f7e6555..4fc4d1db729e1 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/SharedTokenCacheCredentialBuilder.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/SharedTokenCacheCredentialBuilder.java @@ -23,6 +23,19 @@ public SharedTokenCacheCredentialBuilder username(String username) { return this; } + /** + * Sets whether to use an unprotected file specified by cacheFileLocation() instead of + * Gnome keyring on Linux. This is false by default. + * + * @param allowUnencryptedCache whether to use an unprotected file for cache storage. + * + * @return An updated instance of this builder with the unprotected token cache setting set as specified. + */ + public SharedTokenCacheCredentialBuilder allowUnencryptedCache(boolean allowUnencryptedCache) { + this.identityClientOptions.allowUnencryptedCache(allowUnencryptedCache); + return this; + } + /** * Creates a new {@link SharedTokenCacheCredentialBuilder} with the current configurations. * diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/UsernamePasswordCredentialBuilder.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/UsernamePasswordCredentialBuilder.java index 6ba055165c0e6..ea5957cf2c7e1 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/UsernamePasswordCredentialBuilder.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/UsernamePasswordCredentialBuilder.java @@ -36,6 +36,31 @@ public UsernamePasswordCredentialBuilder password(String password) { return this; } + /** + * Sets whether to use an unprotected file specified by cacheFileLocation() instead of + * Gnome keyring on Linux. This is false by default. + * + * @param allowUnencryptedCache whether to use an unprotected file for cache storage. + * + * @return An updated instance of this builder with the unprotected token cache setting set as specified. + */ + public UsernamePasswordCredentialBuilder allowUnencryptedCache(boolean allowUnencryptedCache) { + this.identityClientOptions.allowUnencryptedCache(allowUnencryptedCache); + return this; + } + + /** + * Sets whether to enable using the shared token cache. + * + * @param enabled whether to enabled using the shared token cache. + * + * @return An updated instance of this builder with if the shared token cache enabled specified. + */ + public UsernamePasswordCredentialBuilder enableSharedTokenCache(boolean enabled) { + this.identityClientOptions.enableSharedTokenCache(enabled); + return this; + } + /** * Creates a new {@link UsernamePasswordCredential} with the current configurations. * diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java index 277c85cd6ecda..2627eae154255 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java @@ -155,7 +155,7 @@ private PublicClientApplication getPublicClientApplication(boolean requirePersis if (options.getExecutorService() != null) { publicClientApplicationBuilder.executorService(options.getExecutorService()); } - if (!options.isSharedTokenCacheEnabled()) { + if (options.isSharedTokenCacheEnabled()) { try { publicClientApplicationBuilder.setTokenCacheAccessAspect( new PersistenceTokenCacheAccessAspect(options.getPersistenceSettings())); diff --git a/sdk/identity/azure-identity/src/test/java/com/azure/identity/implementation/IdentityClientTests.java b/sdk/identity/azure-identity/src/test/java/com/azure/identity/implementation/IdentityClientTests.java index b673cd2cefe2a..36fbd1abdfd7a 100644 --- a/sdk/identity/azure-identity/src/test/java/com/azure/identity/implementation/IdentityClientTests.java +++ b/sdk/identity/azure-identity/src/test/java/com/azure/identity/implementation/IdentityClientTests.java @@ -169,7 +169,7 @@ public void testValidDeviceCodeFlow() throws Exception { mockForDeviceCodeFlow(request, accessToken, expiresOn); // test - IdentityClientOptions options = new IdentityClientOptions().enableSharedTokenCache(true); + IdentityClientOptions options = new IdentityClientOptions(); IdentityClient client = new IdentityClientBuilder().tenantId(tenantId).clientId(clientId).identityClientOptions(options).build(); AccessToken token = client.authenticateWithDeviceCode(request, deviceCodeChallenge -> { /* do nothing */ }).block(); Assert.assertEquals(accessToken, token.getToken()); @@ -238,7 +238,7 @@ public void testAuthorizationCodeFlow() throws Exception { mockForAuthorizationCodeFlow(token1, request, expiresAt); // test - IdentityClientOptions options = new IdentityClientOptions().enableSharedTokenCache(true); + IdentityClientOptions options = new IdentityClientOptions(); IdentityClient client = new IdentityClientBuilder().tenantId(tenantId).clientId(clientId).identityClientOptions(options).build(); StepVerifier.create(client.authenticateWithAuthorizationCode(request, authCode1, redirectUri)) .expectNextMatches(accessToken -> token1.equals(accessToken.getToken()) @@ -258,7 +258,7 @@ public void testUserRefreshTokenflow() throws Exception { mockForUserRefreshTokenFlow(token2, request2, expiresAt); // test - IdentityClientOptions options = new IdentityClientOptions().enableSharedTokenCache(true); + IdentityClientOptions options = new IdentityClientOptions(); IdentityClient client = new IdentityClientBuilder().tenantId(tenantId).clientId(clientId).identityClientOptions(options).build(); StepVerifier.create(client.authenticateWithUserRefreshToken(request2, TestUtils.getMockMsalToken(token1, expiresAt).block())) .expectNextMatches(accessToken -> token2.equals(accessToken.getToken()) @@ -279,7 +279,7 @@ public void testUsernamePasswordCodeFlow() throws Exception { mockForUsernamePasswordCodeFlow(token, request, expiresOn); // test - IdentityClientOptions options = new IdentityClientOptions().enableSharedTokenCache(true); + IdentityClientOptions options = new IdentityClientOptions(); IdentityClient client = new IdentityClientBuilder().tenantId(tenantId).clientId(clientId).identityClientOptions(options).build(); StepVerifier.create(client.authenticateWithUsernamePassword(request, username, password)) .expectNextMatches(accessToken -> token.equals(accessToken.getToken()) @@ -301,7 +301,7 @@ public void testBrowserAuthenicationCodeFlow() throws Exception { mocForBrowserAuthenticationCodeFlow(); // test - IdentityClientOptions options = new IdentityClientOptions().enableSharedTokenCache(true); + IdentityClientOptions options = new IdentityClientOptions(); IdentityClient client = new IdentityClientBuilder().tenantId(tenantId).clientId(clientId).identityClientOptions(options).build(); StepVerifier.create(client.authenticateWithBrowserInteraction(request, 4567)) .expectNextMatches(accessToken -> token.equals(accessToken.getToken()) From 77c8a593e6b0ca17b71912361e2ff2c99f83cb99 Mon Sep 17 00:00:00 2001 From: Jianghao Lu Date: Wed, 29 Apr 2020 00:18:41 -0700 Subject: [PATCH 34/45] Wrap exceptions in ClientAuthenticationExceptions when Msal fails --- .../identity/AuthorizationCodeCredential.java | 2 +- .../azure/identity/DeviceCodeCredential.java | 2 +- .../InteractiveBrowserCredential.java | 2 +- .../identity/UsernamePasswordCredential.java | 2 +- .../implementation/IdentityClient.java | 62 ++++++------------- .../AuthorizationCodeCredentialTest.java | 2 +- .../identity/DeviceCodeCredentialTest.java | 2 +- .../InteractiveBrowserCredentialTest.java | 2 +- .../UsernamePasswordCredentialTest.java | 6 +- .../IdentityClientIntegrationTests.java | 8 +-- .../implementation/IdentityClientTests.java | 2 +- .../com/azure/identity/util/TestUtils.java | 11 ++++ 12 files changed, 44 insertions(+), 59 deletions(-) diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/AuthorizationCodeCredential.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/AuthorizationCodeCredential.java index 65b1fcf7ec099..f4f28cecaba5f 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/AuthorizationCodeCredential.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/AuthorizationCodeCredential.java @@ -55,7 +55,7 @@ public class AuthorizationCodeCredential implements TokenCredential { public Mono getToken(TokenRequestContext request) { return Mono.defer(() -> { if (cachedToken.get() != null) { - return identityClient.authenticateWithUserRefreshToken(request, cachedToken.get()) + return identityClient.authenticateWithMsalAccount(request, cachedToken.get().getAccount()) .onErrorResume(t -> Mono.empty()); } else { return Mono.empty(); diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/DeviceCodeCredential.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/DeviceCodeCredential.java index 638a8ce9bed96..831c1a9d13060 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/DeviceCodeCredential.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/DeviceCodeCredential.java @@ -51,7 +51,7 @@ public class DeviceCodeCredential implements TokenCredential { public Mono getToken(TokenRequestContext request) { return Mono.defer(() -> { if (cachedToken.get() != null) { - return identityClient.authenticateWithUserRefreshToken(request, cachedToken.get()) + return identityClient.authenticateWithMsalAccount(request, cachedToken.get().getAccount()) .onErrorResume(t -> Mono.empty()); } else { return Mono.empty(); diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/InteractiveBrowserCredential.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/InteractiveBrowserCredential.java index a0bce6a715e7d..ed2249c6dd209 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/InteractiveBrowserCredential.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/InteractiveBrowserCredential.java @@ -57,7 +57,7 @@ public class InteractiveBrowserCredential implements TokenCredential { public Mono getToken(TokenRequestContext request) { return Mono.defer(() -> { if (cachedToken.get() != null) { - return identityClient.authenticateWithUserRefreshToken(request, cachedToken.get()) + return identityClient.authenticateWithMsalAccount(request, cachedToken.get().getAccount()) .onErrorResume(t -> Mono.empty()); } else { return Mono.empty(); diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/UsernamePasswordCredential.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/UsernamePasswordCredential.java index 5c2f5d4e14cb3..16ad80f019e31 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/UsernamePasswordCredential.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/UsernamePasswordCredential.java @@ -59,7 +59,7 @@ public class UsernamePasswordCredential implements TokenCredential { public Mono getToken(TokenRequestContext request) { return Mono.defer(() -> { if (cachedToken.get() != null) { - return identityClient.authenticateWithUserRefreshToken(request, cachedToken.get()) + return identityClient.authenticateWithMsalAccount(request, cachedToken.get().getAccount()) .onErrorResume(t -> Mono.empty()); } else { return Mono.empty(); diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java index 2627eae154255..c8d685ea70c9b 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java @@ -29,7 +29,6 @@ import com.microsoft.aad.msal4j.ConfidentialClientApplication; import com.microsoft.aad.msal4j.DeviceCodeFlowParameters; import com.microsoft.aad.msal4j.IAccount; -import com.microsoft.aad.msal4j.IAuthenticationResult; import com.microsoft.aad.msal4j.PublicClientApplication; import com.microsoft.aad.msal4j.SilentParameters; import com.microsoft.aad.msal4j.UserNamePasswordParameters; @@ -70,7 +69,6 @@ import java.util.Random; import java.util.Scanner; import java.util.UUID; -import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; import java.util.function.Consumer; import java.util.stream.Collectors; @@ -117,7 +115,7 @@ public class IdentityClient { this.options = options; } - private PublicClientApplication getPublicClientApplication(boolean requirePersistence) { + private PublicClientApplication getPublicClientApplication(boolean sharedTokenCacheCredential) { if (publicClientApplication != null) { return publicClientApplication; } else if (clientId == null) { @@ -160,12 +158,12 @@ private PublicClientApplication getPublicClientApplication(boolean requirePersis publicClientApplicationBuilder.setTokenCacheAccessAspect( new PersistenceTokenCacheAccessAspect(options.getPersistenceSettings())); } catch (Throwable t) { - CredentialUnavailableException cue = new CredentialUnavailableException( - "Shared token cache is unavailable on this platform.", t); - if (requirePersistence) { - throw logger.logExceptionAsWarning(cue); + if (sharedTokenCacheCredential) { + throw logger.logExceptionAsError(new CredentialUnavailableException( + "Shared token cache is unavailable in this environment.", t)); } else { - logger.logExceptionAsWarning(cue); + throw logger.logExceptionAsError(new ClientAuthenticationException( + "Shared token cache is unavailable in this environment.", null, t)); } } } @@ -409,21 +407,23 @@ public Mono authenticateWithUsernamePassword(TokenRequestContext requ return Mono.fromFuture(() -> getPublicClientApplication(false).acquireToken( UserNamePasswordParameters.builder(new HashSet<>(request.getScopes()), username, password.toCharArray()) .build())) - .map(ar -> new MsalToken(ar, options)); + .onErrorResume(t -> Mono.error(new ClientAuthenticationException("Failed to acquire token with username " + + "and password", null, t))).map(ar -> new MsalToken(ar, options)); } /** * Asynchronously acquire a token from the currently logged in client. * * @param request the details of the token request + * @param account the account used to login to acquire the last token * @return a Publisher that emits an AccessToken */ - public Mono authenticateWithUserRefreshToken(TokenRequestContext request, MsalToken msalToken) { + public Mono authenticateWithMsalAccount(TokenRequestContext request, IAccount account) { SilentParameters parameters; SilentParameters forceParameters; - if (msalToken.getAccount() != null) { - parameters = SilentParameters.builder(new HashSet<>(request.getScopes()), msalToken.getAccount()).build(); - forceParameters = SilentParameters.builder(new HashSet<>(request.getScopes()), msalToken.getAccount()) + if (account != null) { + parameters = SilentParameters.builder(new HashSet<>(request.getScopes()), account).build(); + forceParameters = SilentParameters.builder(new HashSet<>(request.getScopes()), account) .forceRefresh(true).build(); } else { parameters = SilentParameters.builder(new HashSet<>(request.getScopes())).build(); @@ -465,7 +465,8 @@ public Mono authenticateWithDeviceCode(TokenRequestContext request, dc -> deviceCodeConsumer.accept(new DeviceCodeInfo(dc.userCode(), dc.deviceCode(), dc.verificationUri(), OffsetDateTime.now().plusSeconds(dc.expiresIn()), dc.message()))).build(); return getPublicClientApplication(false).acquireToken(parameters); - }).map(ar -> new MsalToken(ar, options)); + }).onErrorResume(t -> Mono.error(new ClientAuthenticationException("Failed to acquire token with device code", + null, t))).map(ar -> new MsalToken(ar, options)); } /** @@ -482,7 +483,8 @@ public Mono authenticateWithAuthorizationCode(TokenRequestContext req AuthorizationCodeParameters.builder(authorizationCode, redirectUrl) .scopes(new HashSet<>(request.getScopes())) .build())) - .map(ar -> new MsalToken(ar, options)); + .onErrorResume(t -> Mono.error(new ClientAuthenticationException("Failed to acquire token with " + + "authorization code", null, t))).map(ar -> new MsalToken(ar, options)); } /** @@ -576,35 +578,7 @@ public Mono authenticateWithSharedTokenCache(TokenRequestContext re requestedAccount = accounts.values().iterator().next(); } - // if it does, then request the token - SilentParameters params = SilentParameters.builder( - new HashSet<>(request.getScopes()), requestedAccount) - .authorityUrl(authorityUrl) - .build(); - - SilentParameters forceParams = SilentParameters.builder( - new HashSet<>(request.getScopes()), requestedAccount) - .authorityUrl(authorityUrl) - .forceRefresh(true) - .build(); - - CompletableFuture future; - try { - future = getPublicClientApplication(false).acquireTokenSilently(params); - return Mono.fromFuture(() -> future).map(result -> - new MsalToken(result, options)) - .filter(t -> !t.isExpired()) - .switchIfEmpty(Mono.defer(() -> Mono.fromFuture(() -> { - try { - return getPublicClientApplication(false).acquireTokenSilently(forceParams); - } catch (MalformedURLException e) { - throw logger.logExceptionAsWarning(new RuntimeException(e)); - } - } - ).map(result -> new MsalToken(result, options)))); - } catch (MalformedURLException e) { - return Mono.error(new RuntimeException("Token was not found")); - } + return authenticateWithMsalAccount(request, requestedAccount); }); } diff --git a/sdk/identity/azure-identity/src/test/java/com/azure/identity/AuthorizationCodeCredentialTest.java b/sdk/identity/azure-identity/src/test/java/com/azure/identity/AuthorizationCodeCredentialTest.java index 0b9305627739b..b15a28f51478e 100644 --- a/sdk/identity/azure-identity/src/test/java/com/azure/identity/AuthorizationCodeCredentialTest.java +++ b/sdk/identity/azure-identity/src/test/java/com/azure/identity/AuthorizationCodeCredentialTest.java @@ -47,7 +47,7 @@ public void testValidAuthorizationCode() throws Exception { IdentityClient identityClient = PowerMockito.mock(IdentityClient.class); when(identityClient.authenticateWithAuthorizationCode(eq(request1), eq(authCode1), eq(redirectUri))) .thenReturn(TestUtils.getMockMsalToken(token1, expiresAt)); - when(identityClient.authenticateWithUserRefreshToken(any(), any())) + when(identityClient.authenticateWithMsalAccount(any(), any())) .thenAnswer(invocation -> { TokenRequestContext argument = (TokenRequestContext) invocation.getArguments()[0]; if (argument.getScopes().size() == 1 && argument.getScopes().get(0).equals(request2.getScopes().get(0))) { diff --git a/sdk/identity/azure-identity/src/test/java/com/azure/identity/DeviceCodeCredentialTest.java b/sdk/identity/azure-identity/src/test/java/com/azure/identity/DeviceCodeCredentialTest.java index 126e3f5f1e667..8660390ff28db 100644 --- a/sdk/identity/azure-identity/src/test/java/com/azure/identity/DeviceCodeCredentialTest.java +++ b/sdk/identity/azure-identity/src/test/java/com/azure/identity/DeviceCodeCredentialTest.java @@ -46,7 +46,7 @@ public void testValidDeviceCode() throws Exception { // mock IdentityClient identityClient = PowerMockito.mock(IdentityClient.class); when(identityClient.authenticateWithDeviceCode(eq(request1), eq(consumer))).thenReturn(TestUtils.getMockMsalToken(token1, expiresAt)); - when(identityClient.authenticateWithUserRefreshToken(any(), any())) + when(identityClient.authenticateWithMsalAccount(any(), any())) .thenAnswer(invocation -> { TokenRequestContext argument = (TokenRequestContext) invocation.getArguments()[0]; if (argument.getScopes().size() == 1 && argument.getScopes().get(0).equals(request2.getScopes().get(0))) { diff --git a/sdk/identity/azure-identity/src/test/java/com/azure/identity/InteractiveBrowserCredentialTest.java b/sdk/identity/azure-identity/src/test/java/com/azure/identity/InteractiveBrowserCredentialTest.java index 5f89055c0861a..1b5e1e2a94a8d 100644 --- a/sdk/identity/azure-identity/src/test/java/com/azure/identity/InteractiveBrowserCredentialTest.java +++ b/sdk/identity/azure-identity/src/test/java/com/azure/identity/InteractiveBrowserCredentialTest.java @@ -48,7 +48,7 @@ public void testValidInteractive() throws Exception { // mock IdentityClient identityClient = PowerMockito.mock(IdentityClient.class); when(identityClient.authenticateWithBrowserInteraction(eq(request1), eq(port))).thenReturn(TestUtils.getMockMsalToken(token1, expiresAt)); - when(identityClient.authenticateWithUserRefreshToken(any(), any())) + when(identityClient.authenticateWithMsalAccount(any(), any())) .thenAnswer(invocation -> { TokenRequestContext argument = (TokenRequestContext) invocation.getArguments()[0]; if (argument.getScopes().size() == 1 && argument.getScopes().get(0).equals(request2.getScopes().get(0))) { diff --git a/sdk/identity/azure-identity/src/test/java/com/azure/identity/UsernamePasswordCredentialTest.java b/sdk/identity/azure-identity/src/test/java/com/azure/identity/UsernamePasswordCredentialTest.java index b78ef63b8412b..bd75c2307e036 100644 --- a/sdk/identity/azure-identity/src/test/java/com/azure/identity/UsernamePasswordCredentialTest.java +++ b/sdk/identity/azure-identity/src/test/java/com/azure/identity/UsernamePasswordCredentialTest.java @@ -47,7 +47,7 @@ public void testValidUserCredential() throws Exception { // mock IdentityClient identityClient = PowerMockito.mock(IdentityClient.class); when(identityClient.authenticateWithUsernamePassword(request1, username, password)).thenReturn(TestUtils.getMockMsalToken(token1, expiresAt)); - when(identityClient.authenticateWithUserRefreshToken(any(), any())) + when(identityClient.authenticateWithMsalAccount(any(), any())) .thenAnswer(invocation -> { TokenRequestContext argument = (TokenRequestContext) invocation.getArguments()[0]; if (argument.getScopes().size() == 1 && argument.getScopes().get(0).equals(request2.getScopes().get(0))) { @@ -83,7 +83,7 @@ public void testInvalidUserCredential() throws Exception { // mock IdentityClient identityClient = PowerMockito.mock(IdentityClient.class); when(identityClient.authenticateWithUsernamePassword(request, username, badPassword)).thenThrow(new MsalServiceException("bad credential", "BadCredential")); - when(identityClient.authenticateWithUserRefreshToken(any(), any())) + when(identityClient.authenticateWithMsalAccount(any(), any())) .thenAnswer(invocation -> Mono.error(new UnsupportedOperationException("nothing cached"))); PowerMockito.whenNew(IdentityClient.class).withAnyArguments().thenReturn(identityClient); @@ -107,7 +107,7 @@ public void testInvalidParameters() throws Exception { // mock IdentityClient identityClient = PowerMockito.mock(IdentityClient.class); when(identityClient.authenticateWithUsernamePassword(request, username, password)).thenReturn(TestUtils.getMockMsalToken(token1, expiresOn)); - when(identityClient.authenticateWithUserRefreshToken(any(), any())) + when(identityClient.authenticateWithMsalAccount(any(), any())) .thenAnswer(invocation -> Mono.error(new UnsupportedOperationException("nothing cached"))); PowerMockito.whenNew(IdentityClient.class).withAnyArguments().thenReturn(identityClient); diff --git a/sdk/identity/azure-identity/src/test/java/com/azure/identity/implementation/IdentityClientIntegrationTests.java b/sdk/identity/azure-identity/src/test/java/com/azure/identity/implementation/IdentityClientIntegrationTests.java index aa7fd71b7aef2..72adcc2c2f8d6 100644 --- a/sdk/identity/azure-identity/src/test/java/com/azure/identity/implementation/IdentityClientIntegrationTests.java +++ b/sdk/identity/azure-identity/src/test/java/com/azure/identity/implementation/IdentityClientIntegrationTests.java @@ -51,7 +51,7 @@ public void deviceCodeCanGetToken() { Assert.assertNotNull(token.getToken()); Assert.assertNotNull(token.getExpiresAt()); Assert.assertFalse(token.isExpired()); - token = client.authenticateWithUserRefreshToken(new TokenRequestContext().addScopes("https://vault.azure.net/.default"), token).block(); + token = client.authenticateWithMsalAccount(new TokenRequestContext().addScopes("https://vault.azure.net/.default"), token.getAccount()).block(); Assert.assertNotNull(token); Assert.assertNotNull(token.getToken()); Assert.assertNotNull(token.getExpiresAt()); @@ -66,7 +66,7 @@ public void browserCanGetToken() { Assert.assertNotNull(token.getToken()); Assert.assertNotNull(token.getExpiresAt()); Assert.assertFalse(token.isExpired()); - token = client.authenticateWithUserRefreshToken(new TokenRequestContext().addScopes("https://vault.azure.net/.default"), token).block(); + token = client.authenticateWithMsalAccount(new TokenRequestContext().addScopes("https://vault.azure.net/.default"), token.getAccount()).block(); Assert.assertNotNull(token); Assert.assertNotNull(token.getToken()); Assert.assertNotNull(token.getExpiresAt()); @@ -81,7 +81,7 @@ public void usernamePasswordCanGetToken() { Assert.assertNotNull(token.getToken()); Assert.assertNotNull(token.getExpiresAt()); Assert.assertFalse(token.isExpired()); - token = client.authenticateWithUserRefreshToken(new TokenRequestContext().addScopes("https://vault.azure.net/.default"), token).block(); + token = client.authenticateWithMsalAccount(new TokenRequestContext().addScopes("https://vault.azure.net/.default"), token.getAccount()).block(); Assert.assertNotNull(token); Assert.assertNotNull(token.getToken()); Assert.assertNotNull(token.getExpiresAt()); @@ -96,7 +96,7 @@ public void authCodeCanGetToken() throws Exception { Assert.assertNotNull(token.getToken()); Assert.assertNotNull(token.getExpiresAt()); Assert.assertFalse(token.isExpired()); - token = client.authenticateWithUserRefreshToken(new TokenRequestContext().addScopes("https://vault.azure.net/.default"), token).block(); + token = client.authenticateWithMsalAccount(new TokenRequestContext().addScopes("https://vault.azure.net/.default"), token.getAccount()).block(); Assert.assertNotNull(token); Assert.assertNotNull(token.getToken()); Assert.assertNotNull(token.getExpiresAt()); diff --git a/sdk/identity/azure-identity/src/test/java/com/azure/identity/implementation/IdentityClientTests.java b/sdk/identity/azure-identity/src/test/java/com/azure/identity/implementation/IdentityClientTests.java index 36fbd1abdfd7a..72fe05076ecc4 100644 --- a/sdk/identity/azure-identity/src/test/java/com/azure/identity/implementation/IdentityClientTests.java +++ b/sdk/identity/azure-identity/src/test/java/com/azure/identity/implementation/IdentityClientTests.java @@ -260,7 +260,7 @@ public void testUserRefreshTokenflow() throws Exception { // test IdentityClientOptions options = new IdentityClientOptions(); IdentityClient client = new IdentityClientBuilder().tenantId(tenantId).clientId(clientId).identityClientOptions(options).build(); - StepVerifier.create(client.authenticateWithUserRefreshToken(request2, TestUtils.getMockMsalToken(token1, expiresAt).block())) + StepVerifier.create(client.authenticateWithMsalAccount(request2, TestUtils.getMockMsalAccount(token1, expiresAt).block())) .expectNextMatches(accessToken -> token2.equals(accessToken.getToken()) && expiresAt.getSecond() == accessToken.getExpiresAt().getSecond()) .verifyComplete(); diff --git a/sdk/identity/azure-identity/src/test/java/com/azure/identity/util/TestUtils.java b/sdk/identity/azure-identity/src/test/java/com/azure/identity/util/TestUtils.java index a041b0977c43f..896d47c37bcd7 100644 --- a/sdk/identity/azure-identity/src/test/java/com/azure/identity/util/TestUtils.java +++ b/sdk/identity/azure-identity/src/test/java/com/azure/identity/util/TestUtils.java @@ -87,6 +87,17 @@ public static Mono getMockMsalToken(String accessToken, OffsetDateTim .map(ar -> new MsalToken(ar, new IdentityClientOptions())); } + /** + * Creates a mock {@link IAccount} instance. + * @param accessToken the access token to return + * @param expiresOn the expiration time + * @return a Mono publisher of the result + */ + public static Mono getMockMsalAccount(String accessToken, OffsetDateTime expiresOn) { + return Mono.fromFuture(getMockAuthenticationResult(accessToken, expiresOn)) + .map(IAuthenticationResult::account); + } + /** * Creates a mock {@link AccessToken} instance. * @param accessToken the access token to return From ed8e7223daf3df49e337c1e163a56ab9459cdb52 Mon Sep 17 00:00:00 2001 From: Jianghao Lu Date: Wed, 29 Apr 2020 00:46:50 -0700 Subject: [PATCH 35/45] Address spotbugs --- .../com/azure/identity/AuthorizationCodeCredentialBuilder.java | 2 +- .../java/com/azure/identity/DeviceCodeCredentialBuilder.java | 2 +- .../com/azure/identity/InteractiveBrowserCredentialBuilder.java | 2 +- .../com/azure/identity/UsernamePasswordCredentialBuilder.java | 2 +- .../java/com/azure/identity/implementation/IdentityClient.java | 1 - .../azure/identity/implementation/IdentityClientOptions.java | 2 +- 6 files changed, 5 insertions(+), 6 deletions(-) diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/AuthorizationCodeCredentialBuilder.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/AuthorizationCodeCredentialBuilder.java index 7f89307ac361c..ec8fa5b91d8af 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/AuthorizationCodeCredentialBuilder.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/AuthorizationCodeCredentialBuilder.java @@ -61,7 +61,7 @@ public AuthorizationCodeCredentialBuilder allowUnencryptedCache(boolean allowUne } /** - * Sets whether to enable using the shared token cache. + * Sets whether to enable using the shared token cache. This is disabled by default. * * @param enabled whether to enabled using the shared token cache. * diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/DeviceCodeCredentialBuilder.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/DeviceCodeCredentialBuilder.java index e346d21928f5e..e7f1ffd571edb 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/DeviceCodeCredentialBuilder.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/DeviceCodeCredentialBuilder.java @@ -43,7 +43,7 @@ public DeviceCodeCredentialBuilder allowUnencryptedCache(boolean allowUnencrypte } /** - * Sets whether to enable using the shared token cache. + * Sets whether to enable using the shared token cache. This is disabled by default. * * @param enabled whether to enabled using the shared token cache. * diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/InteractiveBrowserCredentialBuilder.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/InteractiveBrowserCredentialBuilder.java index d7625955e76c0..2d6d94adce6e5 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/InteractiveBrowserCredentialBuilder.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/InteractiveBrowserCredentialBuilder.java @@ -41,7 +41,7 @@ public InteractiveBrowserCredentialBuilder allowUnencryptedCache(boolean allowUn } /** - * Sets whether to enable using the shared token cache. + * Sets whether to enable using the shared token cache. This is disabled by default. * * @param enabled whether to enabled using the shared token cache. * diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/UsernamePasswordCredentialBuilder.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/UsernamePasswordCredentialBuilder.java index ea5957cf2c7e1..f53a17e03a3c1 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/UsernamePasswordCredentialBuilder.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/UsernamePasswordCredentialBuilder.java @@ -50,7 +50,7 @@ public UsernamePasswordCredentialBuilder allowUnencryptedCache(boolean allowUnen } /** - * Sets whether to enable using the shared token cache. + * Sets whether to enable using the shared token cache. This is disabled by default. * * @param enabled whether to enabled using the shared token cache. * diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java index c8d685ea70c9b..1cf717aab9a07 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java @@ -535,7 +535,6 @@ public Mono authenticateWithBrowserInteraction(TokenRequestContext re * Gets token from shared token cache * */ public Mono authenticateWithSharedTokenCache(TokenRequestContext request, String username) { - String authorityUrl = options.getAuthorityHost().replaceAll("/+$", "") + "/" + tenantId + "/"; // find if the Public Client app with the requested username exists return Mono.fromFuture(() -> getPublicClientApplication(true).getAccounts()) .onErrorResume(t -> Mono.error(new CredentialUnavailableException( diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClientOptions.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClientOptions.java index b71fedb80a71d..c918d560314dd 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClientOptions.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClientOptions.java @@ -261,7 +261,7 @@ public boolean isSharedTokenCacheEnabled() { } /** - * Sets whether to enable using the shared token cache. + * Sets whether to enable using the shared token cache. This is disabled by default. * * @param enabled whether to enable using the shared token cache. * From 09718dd9d337c865b7073d84002a6a496e6de77e Mon Sep 17 00:00:00 2001 From: Jianghao Lu Date: Wed, 29 Apr 2020 13:26:53 -0700 Subject: [PATCH 36/45] Clean up and address Alan's review --- .../implementation/IdentityClient.java | 74 +++++++++---------- 1 file changed, 37 insertions(+), 37 deletions(-) diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java index 1cf717aab9a07..030cb057d9258 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java @@ -33,6 +33,7 @@ import com.microsoft.aad.msal4j.SilentParameters; import com.microsoft.aad.msal4j.UserNamePasswordParameters; import com.microsoft.aad.msal4jextensions.PersistenceTokenCacheAccessAspect; +import reactor.core.Exceptions; import reactor.core.publisher.Mono; import reactor.core.scheduler.Schedulers; @@ -119,7 +120,8 @@ private PublicClientApplication getPublicClientApplication(boolean sharedTokenCa if (publicClientApplication != null) { return publicClientApplication; } else if (clientId == null) { - return null; + throw logger.logExceptionAsError(new IllegalArgumentException( + "A non-null value for client ID must be providedfor user authentication.")); } else { String authorityUrl = options.getAuthorityHost().replaceAll("/+$", "") + "/organizations/" + tenantId; PublicClientApplication.Builder publicClientApplicationBuilder = PublicClientApplication.builder(clientId); @@ -275,7 +277,6 @@ public Mono authenticateWithAzureCli(TokenRequestContext request) { return Mono.just(token); } - /** * Asynchronously acquire a token from Active Directory with a client secret. * @@ -407,8 +408,8 @@ public Mono authenticateWithUsernamePassword(TokenRequestContext requ return Mono.fromFuture(() -> getPublicClientApplication(false).acquireToken( UserNamePasswordParameters.builder(new HashSet<>(request.getScopes()), username, password.toCharArray()) .build())) - .onErrorResume(t -> Mono.error(new ClientAuthenticationException("Failed to acquire token with username " - + "and password", null, t))).map(ar -> new MsalToken(ar, options)); + .onErrorMap(t -> new ClientAuthenticationException("Failed to acquire token with username and password", + null, t)).map(ar -> new MsalToken(ar, options)); } /** @@ -419,33 +420,32 @@ public Mono authenticateWithUsernamePassword(TokenRequestContext requ * @return a Publisher that emits an AccessToken */ public Mono authenticateWithMsalAccount(TokenRequestContext request, IAccount account) { - SilentParameters parameters; - SilentParameters forceParameters; - if (account != null) { - parameters = SilentParameters.builder(new HashSet<>(request.getScopes()), account).build(); - forceParameters = SilentParameters.builder(new HashSet<>(request.getScopes()), account) - .forceRefresh(true).build(); - } else { - parameters = SilentParameters.builder(new HashSet<>(request.getScopes())).build(); - forceParameters = SilentParameters.builder(new HashSet<>(request.getScopes())).forceRefresh(true).build(); - } - return Mono.defer(() -> { - try { - return Mono.fromFuture(getPublicClientApplication(false).acquireTokenSilently(parameters)) - .map(ar -> new MsalToken(ar, options)) - .filter(t -> !t.isExpired()) - .switchIfEmpty(Mono.defer(() -> Mono.fromFuture(() -> { - try { - return getPublicClientApplication(false).acquireTokenSilently(forceParameters); - } catch (MalformedURLException e) { - throw logger.logExceptionAsWarning(new RuntimeException(e)); - } - } - ).map(result -> new MsalToken(result, options)))); - } catch (MalformedURLException e) { - return Mono.error(e); - } - }); + return Mono.defer(() -> Mono.fromFuture(() -> { + SilentParameters.SilentParametersBuilder parametersBuilder = SilentParameters.builder( + new HashSet<>(request.getScopes())); + if (account != null) { + parametersBuilder = parametersBuilder.account(account); + } + try { + return getPublicClientApplication(false) + .acquireTokenSilently(parametersBuilder.build()); + } catch (MalformedURLException e) { + throw logger.logExceptionAsError(Exceptions.propagate(e)); + } + }).map(ar -> new MsalToken(ar, options)) + .filter(t -> !t.isExpired()) + .switchIfEmpty(Mono.fromFuture(() -> { + SilentParameters.SilentParametersBuilder forceParametersBuilder = SilentParameters.builder( + new HashSet<>(request.getScopes())).forceRefresh(true); + if (account != null) { + forceParametersBuilder = forceParametersBuilder.account(account); + } + try { + return getPublicClientApplication(false).acquireTokenSilently(forceParametersBuilder.build()); + } catch (MalformedURLException e) { + throw logger.logExceptionAsError(Exceptions.propagate(e)); + } + }).map(result -> new MsalToken(result, options)))); } /** @@ -465,8 +465,8 @@ public Mono authenticateWithDeviceCode(TokenRequestContext request, dc -> deviceCodeConsumer.accept(new DeviceCodeInfo(dc.userCode(), dc.deviceCode(), dc.verificationUri(), OffsetDateTime.now().plusSeconds(dc.expiresIn()), dc.message()))).build(); return getPublicClientApplication(false).acquireToken(parameters); - }).onErrorResume(t -> Mono.error(new ClientAuthenticationException("Failed to acquire token with device code", - null, t))).map(ar -> new MsalToken(ar, options)); + }).onErrorMap(t -> new ClientAuthenticationException("Failed to acquire token with device code", null, t)) + .map(ar -> new MsalToken(ar, options)); } /** @@ -483,8 +483,8 @@ public Mono authenticateWithAuthorizationCode(TokenRequestContext req AuthorizationCodeParameters.builder(authorizationCode, redirectUrl) .scopes(new HashSet<>(request.getScopes())) .build())) - .onErrorResume(t -> Mono.error(new ClientAuthenticationException("Failed to acquire token with " - + "authorization code", null, t))).map(ar -> new MsalToken(ar, options)); + .onErrorMap(t -> new ClientAuthenticationException("Failed to acquire token with authorization code", + null, t)).map(ar -> new MsalToken(ar, options)); } /** @@ -537,8 +537,8 @@ public Mono authenticateWithBrowserInteraction(TokenRequestContext re public Mono authenticateWithSharedTokenCache(TokenRequestContext request, String username) { // find if the Public Client app with the requested username exists return Mono.fromFuture(() -> getPublicClientApplication(true).getAccounts()) - .onErrorResume(t -> Mono.error(new CredentialUnavailableException( - "Cannot get accounts from token cache. Error: " + t.getMessage(), t))) + .onErrorMap(t -> new CredentialUnavailableException( + "Cannot get accounts from token cache. Error: " + t.getMessage(), t)) .flatMap(set -> { IAccount requestedAccount; Map accounts = new HashMap<>(); // home account id -> account From 806539a60aa9bf92793a100eafbb62f414a1b60e Mon Sep 17 00:00:00 2001 From: Jianghao Lu Date: Wed, 29 Apr 2020 13:30:05 -0700 Subject: [PATCH 37/45] Merge error message --- .../azure/identity/implementation/IdentityClient.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java index 030cb057d9258..7b95c40d96f88 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java @@ -160,12 +160,11 @@ private PublicClientApplication getPublicClientApplication(boolean sharedTokenCa publicClientApplicationBuilder.setTokenCacheAccessAspect( new PersistenceTokenCacheAccessAspect(options.getPersistenceSettings())); } catch (Throwable t) { + String message = "Shared token cache is unavailable in this environment."; if (sharedTokenCacheCredential) { - throw logger.logExceptionAsError(new CredentialUnavailableException( - "Shared token cache is unavailable in this environment.", t)); + throw logger.logExceptionAsError(new CredentialUnavailableException(message, t)); } else { - throw logger.logExceptionAsError(new ClientAuthenticationException( - "Shared token cache is unavailable in this environment.", null, t)); + throw logger.logExceptionAsError(new ClientAuthenticationException(message, null, t)); } } } @@ -408,6 +407,9 @@ public Mono authenticateWithUsernamePassword(TokenRequestContext requ return Mono.fromFuture(() -> getPublicClientApplication(false).acquireToken( UserNamePasswordParameters.builder(new HashSet<>(request.getScopes()), username, password.toCharArray()) .build())) + .doFinally(s -> { + if (s.) + }) .onErrorMap(t -> new ClientAuthenticationException("Failed to acquire token with username and password", null, t)).map(ar -> new MsalToken(ar, options)); } From 066d41336c054bd7290070387a5ed46d7f157152 Mon Sep 17 00:00:00 2001 From: Jianghao Lu Date: Wed, 29 Apr 2020 13:50:09 -0700 Subject: [PATCH 38/45] CI says IdentityClient doesn't compile --- .../java/com/azure/identity/implementation/IdentityClient.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java index 7b95c40d96f88..dfd5e6423d1c3 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java @@ -407,9 +407,6 @@ public Mono authenticateWithUsernamePassword(TokenRequestContext requ return Mono.fromFuture(() -> getPublicClientApplication(false).acquireToken( UserNamePasswordParameters.builder(new HashSet<>(request.getScopes()), username, password.toCharArray()) .build())) - .doFinally(s -> { - if (s.) - }) .onErrorMap(t -> new ClientAuthenticationException("Failed to acquire token with username and password", null, t)).map(ar -> new MsalToken(ar, options)); } From 6cc8c1b60735420762714373444870c50eaed310 Mon Sep 17 00:00:00 2001 From: Jianghao Lu Date: Wed, 29 Apr 2020 16:37:03 -0700 Subject: [PATCH 39/45] Checkstyle: indentation --- .../implementation/IdentityClient.java | 50 +++++++++---------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java index dfd5e6423d1c3..cd7903cea79a9 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java @@ -420,31 +420,31 @@ public Mono authenticateWithUsernamePassword(TokenRequestContext requ */ public Mono authenticateWithMsalAccount(TokenRequestContext request, IAccount account) { return Mono.defer(() -> Mono.fromFuture(() -> { - SilentParameters.SilentParametersBuilder parametersBuilder = SilentParameters.builder( - new HashSet<>(request.getScopes())); - if (account != null) { - parametersBuilder = parametersBuilder.account(account); - } - try { - return getPublicClientApplication(false) - .acquireTokenSilently(parametersBuilder.build()); - } catch (MalformedURLException e) { - throw logger.logExceptionAsError(Exceptions.propagate(e)); - } - }).map(ar -> new MsalToken(ar, options)) - .filter(t -> !t.isExpired()) - .switchIfEmpty(Mono.fromFuture(() -> { - SilentParameters.SilentParametersBuilder forceParametersBuilder = SilentParameters.builder( - new HashSet<>(request.getScopes())).forceRefresh(true); - if (account != null) { - forceParametersBuilder = forceParametersBuilder.account(account); - } - try { - return getPublicClientApplication(false).acquireTokenSilently(forceParametersBuilder.build()); - } catch (MalformedURLException e) { - throw logger.logExceptionAsError(Exceptions.propagate(e)); - } - }).map(result -> new MsalToken(result, options)))); + SilentParameters.SilentParametersBuilder parametersBuilder = SilentParameters.builder( + new HashSet<>(request.getScopes())); + if (account != null) { + parametersBuilder = parametersBuilder.account(account); + } + try { + return getPublicClientApplication(false) + .acquireTokenSilently(parametersBuilder.build()); + } catch (MalformedURLException e) { + throw logger.logExceptionAsError(Exceptions.propagate(e)); + } + }).map(ar -> new MsalToken(ar, options)) + .filter(t -> !t.isExpired()) + .switchIfEmpty(Mono.fromFuture(() -> { + SilentParameters.SilentParametersBuilder forceParametersBuilder = SilentParameters.builder( + new HashSet<>(request.getScopes())).forceRefresh(true); + if (account != null) { + forceParametersBuilder = forceParametersBuilder.account(account); + } + try { + return getPublicClientApplication(false).acquireTokenSilently(forceParametersBuilder.build()); + } catch (MalformedURLException e) { + throw logger.logExceptionAsError(Exceptions.propagate(e)); + } + }).map(result -> new MsalToken(result, options)))); } /** From d5c466269fe95ea6a729b15cca402cef4fbe0c49 Mon Sep 17 00:00:00 2001 From: Jianghao Lu Date: Thu, 30 Apr 2020 14:27:18 -0700 Subject: [PATCH 40/45] Update shared token cache look up with master --- .../implementation/IdentityClient.java | 45 +++++++++---------- 1 file changed, 22 insertions(+), 23 deletions(-) diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java index cd7903cea79a9..a15ee5741243e 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java @@ -72,7 +72,6 @@ import java.util.UUID; import java.util.concurrent.TimeUnit; import java.util.function.Consumer; -import java.util.stream.Collectors; /** * The identity client that contains APIs to retrieve access tokens @@ -550,32 +549,32 @@ public Mono authenticateWithSharedTokenCache(TokenRequestContext re } } - if (accounts.size() == 0) { - if (username == null) { - return Mono.error(new CredentialUnavailableException("No accounts were discovered in the " - + "shared token cache. To fix, authenticate through tooling supporting azure " - + "developer sign on.")); - } else { - return Mono.error(new CredentialUnavailableException(String.format("User account '%s' was " - + "not found in the shared token cache. Discovered Accounts: [ '%s' ]", username, - set.stream().map(IAccount::username).distinct() - .collect(Collectors.joining(", "))))); - } - } else if (accounts.size() > 1) { - if (username == null) { - return Mono.error(new CredentialUnavailableException("Multiple accounts were discovered " - + "in the shared token cache. To fix, set the AZURE_USERNAME and AZURE_TENANT_ID " - + "environment variable to the preferred username, or specify it when " - + "constructing SharedTokenCacheCredential.")); + if (set.size() == 0) { + return Mono.error(new CredentialUnavailableException("SharedTokenCacheCredential " + + "authentication unavailable. No accounts were found in the cache.")); + } + + if (CoreUtils.isNullOrEmpty(username)) { + return Mono.error(new CredentialUnavailableException("SharedTokenCacheCredential " + + "authentication unavailable. Multiple accounts were found in the cache. Use " + + "username and tenant id to disambiguate.")); + } + + if (accounts.size() != 1) { + if (accounts.size() == 0) { + return Mono.error(new CredentialUnavailableException( + String.format("SharedTokenCacheCredential authentication " + + "unavailable. No account matching the specified username %s was found " + + "in the cache.", username))); } else { - return Mono.error(new CredentialUnavailableException("Multiple entries for the user " - + "account " + username + " were found in the shared token cache. This is not " - + "currently supported by the SharedTokenCacheCredential.")); + return Mono.error(new CredentialUnavailableException(String.format( + "SharedTokenCacheCredential authentication unavailable. Multiple accounts " + + "matching the specified username %s were found in the cache.", username))); } - } else { - requestedAccount = accounts.values().iterator().next(); } + requestedAccount = accounts.values().iterator().next(); + return authenticateWithMsalAccount(request, requestedAccount); }); } From 0bf3f76bb45b910ff5bd7bdca807110665c1df44 Mon Sep 17 00:00:00 2001 From: Jianghao Lu Date: Fri, 1 May 2020 01:26:11 -0700 Subject: [PATCH 41/45] Address Connie's feedback --- .../com/azure/identity/implementation/IdentityClient.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java index a15ee5741243e..f2ebbf7366708 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java @@ -120,7 +120,7 @@ private PublicClientApplication getPublicClientApplication(boolean sharedTokenCa return publicClientApplication; } else if (clientId == null) { throw logger.logExceptionAsError(new IllegalArgumentException( - "A non-null value for client ID must be providedfor user authentication.")); + "A non-null value for client ID must be provided for user authentication.")); } else { String authorityUrl = options.getAuthorityHost().replaceAll("/+$", "") + "/organizations/" + tenantId; PublicClientApplication.Builder publicClientApplicationBuilder = PublicClientApplication.builder(clientId); @@ -549,7 +549,7 @@ public Mono authenticateWithSharedTokenCache(TokenRequestContext re } } - if (set.size() == 0) { + if (set.isEmpty()) { return Mono.error(new CredentialUnavailableException("SharedTokenCacheCredential " + "authentication unavailable. No accounts were found in the cache.")); } @@ -561,7 +561,7 @@ public Mono authenticateWithSharedTokenCache(TokenRequestContext re } if (accounts.size() != 1) { - if (accounts.size() == 0) { + if (accounts.isEmpty()) { return Mono.error(new CredentialUnavailableException( String.format("SharedTokenCacheCredential authentication " + "unavailable. No account matching the specified username %s was found " From b337b9393669c0a558f3b7b09cba50882e44efb7 Mon Sep 17 00:00:00 2001 From: Vinay Gera Date: Fri, 1 May 2020 23:18:48 +0000 Subject: [PATCH 42/45] address feedback --- .../AuthorizationCodeCredentialBuilder.java | 4 ++-- .../identity/DeviceCodeCredentialBuilder.java | 4 ++-- .../InteractiveBrowserCredentialBuilder.java | 4 ++-- .../identity/SharedTokenCacheCredential.java | 19 ++++++++++++++++++- .../UsernamePasswordCredentialBuilder.java | 4 ++-- .../implementation/IdentityClient.java | 2 +- .../implementation/IdentityClientOptions.java | 2 +- 7 files changed, 28 insertions(+), 11 deletions(-) diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/AuthorizationCodeCredentialBuilder.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/AuthorizationCodeCredentialBuilder.java index ec8fa5b91d8af..c92e63932c074 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/AuthorizationCodeCredentialBuilder.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/AuthorizationCodeCredentialBuilder.java @@ -67,8 +67,8 @@ public AuthorizationCodeCredentialBuilder allowUnencryptedCache(boolean allowUne * * @return An updated instance of this builder with if the shared token cache enabled specified. */ - public AuthorizationCodeCredentialBuilder enableSharedTokenCache(boolean enabled) { - this.identityClientOptions.enableSharedTokenCache(enabled); + public AuthorizationCodeCredentialBuilder enablePersistentCache(boolean enabled) { + this.identityClientOptions.enablePersistentCache(enabled); return this; } diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/DeviceCodeCredentialBuilder.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/DeviceCodeCredentialBuilder.java index e7f1ffd571edb..0c6836f0c8851 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/DeviceCodeCredentialBuilder.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/DeviceCodeCredentialBuilder.java @@ -49,8 +49,8 @@ public DeviceCodeCredentialBuilder allowUnencryptedCache(boolean allowUnencrypte * * @return An updated instance of this builder with if the shared token cache enabled specified. */ - public DeviceCodeCredentialBuilder enableSharedTokenCache(boolean enabled) { - this.identityClientOptions.enableSharedTokenCache(enabled); + public DeviceCodeCredentialBuilder enablePersistentCache(boolean enabled) { + this.identityClientOptions.enablePersistentCache(enabled); return this; } diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/InteractiveBrowserCredentialBuilder.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/InteractiveBrowserCredentialBuilder.java index 2d6d94adce6e5..72f78d7f356f9 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/InteractiveBrowserCredentialBuilder.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/InteractiveBrowserCredentialBuilder.java @@ -47,8 +47,8 @@ public InteractiveBrowserCredentialBuilder allowUnencryptedCache(boolean allowUn * * @return An updated instance of this builder with if the shared token cache enabled specified. */ - public InteractiveBrowserCredentialBuilder enableSharedTokenCache(boolean enabled) { - this.identityClientOptions.enableSharedTokenCache(enabled); + public InteractiveBrowserCredentialBuilder enablePersistentCache(boolean enabled) { + this.identityClientOptions.enablePersistentCache(enabled); return this; } diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/SharedTokenCacheCredential.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/SharedTokenCacheCredential.java index 4dd7b57c0a32a..ecf37596b9e31 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/SharedTokenCacheCredential.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/SharedTokenCacheCredential.java @@ -10,8 +10,11 @@ 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 reactor.core.publisher.Mono; +import java.util.concurrent.atomic.AtomicReference; + /** * A credential provider that provides token credentials from the MSAL shared token cache. * Requires a username and client Id. If a username is not provided, then the @@ -21,6 +24,7 @@ public class SharedTokenCacheCredential implements TokenCredential { private final String username; private final String clientId; private final String tenantId; + private final AtomicReference cachedToken; private final IdentityClient identityClient; @@ -56,6 +60,7 @@ public class SharedTokenCacheCredential implements TokenCredential { .clientId(this.clientId) .identityClientOptions(identityClientOptions) .build(); + this.cachedToken = new AtomicReference<>(); } /** @@ -63,6 +68,18 @@ public class SharedTokenCacheCredential implements TokenCredential { * */ @Override public Mono getToken(TokenRequestContext request) { - return identityClient.authenticateWithSharedTokenCache(request, username); + return Mono.defer(() -> { + if (cachedToken.get() != null) { + return identityClient.authenticateWithMsalAccount(request, cachedToken.get().getAccount()) + .onErrorResume(t -> Mono.empty()); + } else { + return Mono.empty(); + } + }).switchIfEmpty( + Mono.defer(() -> identityClient.authenticateWithSharedTokenCache(request, username))) + .map(msalToken -> { + cachedToken.set(msalToken); + return msalToken; + }); } } diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/UsernamePasswordCredentialBuilder.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/UsernamePasswordCredentialBuilder.java index f53a17e03a3c1..d1204b6bc8f40 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/UsernamePasswordCredentialBuilder.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/UsernamePasswordCredentialBuilder.java @@ -56,8 +56,8 @@ public UsernamePasswordCredentialBuilder allowUnencryptedCache(boolean allowUnen * * @return An updated instance of this builder with if the shared token cache enabled specified. */ - public UsernamePasswordCredentialBuilder enableSharedTokenCache(boolean enabled) { - this.identityClientOptions.enableSharedTokenCache(enabled); + public UsernamePasswordCredentialBuilder enablePersistentCache(boolean enabled) { + this.identityClientOptions.enablePersistentCache(enabled); return this; } diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java index f2ebbf7366708..c42a06709b5ca 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java @@ -532,7 +532,7 @@ public Mono authenticateWithBrowserInteraction(TokenRequestContext re /** * Gets token from shared token cache * */ - public Mono authenticateWithSharedTokenCache(TokenRequestContext request, String username) { + public Mono authenticateWithSharedTokenCache(TokenRequestContext request, String username) { // find if the Public Client app with the requested username exists return Mono.fromFuture(() -> getPublicClientApplication(true).getAccounts()) .onErrorMap(t -> new CredentialUnavailableException( diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClientOptions.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClientOptions.java index c918d560314dd..fb131ba8bfce9 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClientOptions.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClientOptions.java @@ -267,7 +267,7 @@ public boolean isSharedTokenCacheEnabled() { * * @return The updated identity client options. */ - public IdentityClientOptions enableSharedTokenCache(boolean enabled) { + public IdentityClientOptions enablePersistentCache(boolean enabled) { this.sharedTokenCacheEnabled = enabled; return this; } From 25d2a63d58710d4196006095b59ce3f455efa7e4 Mon Sep 17 00:00:00 2001 From: Jianghao Lu Date: Mon, 4 May 2020 13:53:33 -0700 Subject: [PATCH 43/45] Fix shared token cache and error handling --- .../SharedTokenCacheCredentialBuilder.java | 3 +- .../implementation/IdentityClient.java | 41 +++++++++---------- .../azure-security-keyvault-secrets/pom.xml | 2 +- .../secrets/PersistentTokenCacheDemo.java | 2 +- 4 files changed, 24 insertions(+), 24 deletions(-) diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/SharedTokenCacheCredentialBuilder.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/SharedTokenCacheCredentialBuilder.java index 4fc4d1db729e1..c126756353000 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/SharedTokenCacheCredentialBuilder.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/SharedTokenCacheCredentialBuilder.java @@ -42,6 +42,7 @@ public SharedTokenCacheCredentialBuilder allowUnencryptedCache(boolean allowUnen * @return a {@link SharedTokenCacheCredentialBuilder} with the current configurations. */ public SharedTokenCacheCredential build() { - return new SharedTokenCacheCredential(username, clientId, tenantId, identityClientOptions); + return new SharedTokenCacheCredential(username, clientId, tenantId, + identityClientOptions.enablePersistentCache(true)); } } diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java index 3c5f0b0eccc4b..345303efdc8ba 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java @@ -620,6 +620,11 @@ public Mono authenticateWithSharedTokenCache(TokenRequestContext requ IAccount requestedAccount; Map accounts = new HashMap<>(); // home account id -> account + if (set.isEmpty()) { + return Mono.error(new CredentialUnavailableException("SharedTokenCacheCredential " + + "authentication unavailable. No accounts were found in the cache.")); + } + for (IAccount cached : set) { if (username == null || username.equals(cached.username())) { if (!accounts.containsKey(cached.homeAccountId())) { // only put the first one @@ -628,31 +633,25 @@ public Mono authenticateWithSharedTokenCache(TokenRequestContext requ } } - if (set.isEmpty()) { - return Mono.error(new CredentialUnavailableException("SharedTokenCacheCredential " - + "authentication unavailable. No accounts were found in the cache.")); - } - - if (CoreUtils.isNullOrEmpty(username)) { - return Mono.error(new CredentialUnavailableException("SharedTokenCacheCredential " - + "authentication unavailable. Multiple accounts were found in the cache. Use " - + "username and tenant id to disambiguate.")); - } - - if (accounts.size() != 1) { - if (accounts.isEmpty()) { - return Mono.error(new CredentialUnavailableException( - String.format("SharedTokenCacheCredential authentication " - + "unavailable. No account matching the specified username %s was found " - + "in the cache.", username))); + if (accounts.isEmpty()) { + // no more accounts after filtering, username must be set + return Mono.error(new RuntimeException(String.format("SharedTokenCacheCredential " + + "authentication unavailable. No account matching the specified username: %s was " + + "found in the cache.", username))); + } else if (accounts.size() > 1) { + if (username == null) { + return Mono.error(new RuntimeException("SharedTokenCacheCredential authentication " + + "unavailable. Multiple accounts were found in the cache. Use username and " + + "tenant id to disambiguate.")); } else { - return Mono.error(new CredentialUnavailableException(String.format( - "SharedTokenCacheCredential authentication unavailable. Multiple accounts " - + "matching the specified username %s were found in the cache.", username))); + return Mono.error(new RuntimeException(String.format("SharedTokenCacheCredential " + + "authentication unavailable. Multiple accounts matching the specified username: " + + "%s were found in the cache.", username))); } + } else { + requestedAccount = accounts.values().iterator().next(); } - requestedAccount = accounts.values().iterator().next(); return authenticateWithMsalAccount(request, requestedAccount); }); diff --git a/sdk/keyvault/azure-security-keyvault-secrets/pom.xml b/sdk/keyvault/azure-security-keyvault-secrets/pom.xml index d7322b68c1278..cae2d0d00c8fd 100644 --- a/sdk/keyvault/azure-security-keyvault-secrets/pom.xml +++ b/sdk/keyvault/azure-security-keyvault-secrets/pom.xml @@ -105,7 +105,7 @@ com.azure azure-identity - 1.0.5 + 1.1.0-beta.4 test diff --git a/sdk/keyvault/azure-security-keyvault-secrets/src/samples/java/com/azure/security/keyvault/secrets/PersistentTokenCacheDemo.java b/sdk/keyvault/azure-security-keyvault-secrets/src/samples/java/com/azure/security/keyvault/secrets/PersistentTokenCacheDemo.java index fb98d42c13f34..152f441a60e0a 100644 --- a/sdk/keyvault/azure-security-keyvault-secrets/src/samples/java/com/azure/security/keyvault/secrets/PersistentTokenCacheDemo.java +++ b/sdk/keyvault/azure-security-keyvault-secrets/src/samples/java/com/azure/security/keyvault/secrets/PersistentTokenCacheDemo.java @@ -24,7 +24,7 @@ public static void main(String[] args) { .build(); SecretClient client = new SecretClientBuilder() - .vaultUrl("https://persistentcachedemo.vault.azure.net") + .vaultUrl("https://jianghaovaultb.vault.azure.net") .credential(defaultCredential) .buildClient(); From e3a405d368d8d04df222e99b69e80f84f140100d Mon Sep 17 00:00:00 2001 From: Jianghao Lu Date: Mon, 4 May 2020 14:14:44 -0700 Subject: [PATCH 44/45] Use getPublicClientApplication() --- .../com/azure/identity/implementation/IdentityClient.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java index 345303efdc8ba..a9f08fddf7822 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java @@ -218,7 +218,7 @@ public Mono authenticateWithIntelliJ(TokenRequestContext request) { .builder(new HashSet<>(request.getScopes()), refreshToken) .build(); - return Mono.defer(() -> Mono.fromFuture(publicClientApplication.acquireToken(parameters)) + return Mono.defer(() -> Mono.fromFuture(getPublicClientApplication(false).acquireToken(parameters)) .map(ar -> new MsalToken(ar, options))); } else { @@ -542,7 +542,7 @@ public Mono authenticateWithVsCodeCredential(TokenRequestContext requ .builder(new HashSet<>(request.getScopes()), credential) .build(); - return Mono.defer(() -> Mono.fromFuture(publicClientApplication.acquireToken(parameters)) + return Mono.defer(() -> Mono.fromFuture(getPublicClientApplication(false).acquireToken(parameters)) .map(ar -> new MsalToken(ar, options))); } From 3dff1f3437575f1e9581bd42fde3c9fa9c6947df Mon Sep 17 00:00:00 2001 From: Jianghao Lu Date: Mon, 4 May 2020 14:18:02 -0700 Subject: [PATCH 45/45] Undo changes in key vault --- sdk/keyvault/azure-security-keyvault-secrets/pom.xml | 2 +- .../security/keyvault/secrets/PersistentTokenCacheDemo.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/sdk/keyvault/azure-security-keyvault-secrets/pom.xml b/sdk/keyvault/azure-security-keyvault-secrets/pom.xml index cae2d0d00c8fd..d7322b68c1278 100644 --- a/sdk/keyvault/azure-security-keyvault-secrets/pom.xml +++ b/sdk/keyvault/azure-security-keyvault-secrets/pom.xml @@ -105,7 +105,7 @@ com.azure azure-identity - 1.1.0-beta.4 + 1.0.5 test diff --git a/sdk/keyvault/azure-security-keyvault-secrets/src/samples/java/com/azure/security/keyvault/secrets/PersistentTokenCacheDemo.java b/sdk/keyvault/azure-security-keyvault-secrets/src/samples/java/com/azure/security/keyvault/secrets/PersistentTokenCacheDemo.java index 152f441a60e0a..fb98d42c13f34 100644 --- a/sdk/keyvault/azure-security-keyvault-secrets/src/samples/java/com/azure/security/keyvault/secrets/PersistentTokenCacheDemo.java +++ b/sdk/keyvault/azure-security-keyvault-secrets/src/samples/java/com/azure/security/keyvault/secrets/PersistentTokenCacheDemo.java @@ -24,7 +24,7 @@ public static void main(String[] args) { .build(); SecretClient client = new SecretClientBuilder() - .vaultUrl("https://jianghaovaultb.vault.azure.net") + .vaultUrl("https://persistentcachedemo.vault.azure.net") .credential(defaultCredential) .buildClient();