Skip to content

Commit f48c411

Browse files
authored
Add tests to PersistentTokenCache (Azure#18887)
* Add tests to PersistentTokenCache
1 parent ced985d commit f48c411

File tree

4 files changed

+348
-63
lines changed

4 files changed

+348
-63
lines changed

sdk/identity/Azure.Identity/api/Azure.Identity.netstandard2.0.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -196,8 +196,7 @@ public ManagedIdentityCredential(string clientId = null, Azure.Identity.TokenCre
196196
}
197197
public partial class PersistentTokenCache : Azure.Identity.TokenCache
198198
{
199-
public PersistentTokenCache(Azure.Identity.PersistentTokenCacheOptions options) { }
200-
public PersistentTokenCache(bool allowUnencryptedStorage = true) { }
199+
public PersistentTokenCache(Azure.Identity.PersistentTokenCacheOptions options = null) { }
201200
}
202201
public partial class PersistentTokenCacheOptions
203202
{
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
using System.Diagnostics;
5+
using System.Threading.Tasks;
6+
using Microsoft.Identity.Client;
7+
using Microsoft.Identity.Client.Extensions.Msal;
8+
9+
namespace Azure.Identity
10+
{
11+
internal class MsalCacheHelperWrapper
12+
{
13+
private MsalCacheHelper _helper;
14+
15+
/// <summary>
16+
/// Default Constructor.
17+
/// </summary>
18+
public MsalCacheHelperWrapper()
19+
{
20+
}
21+
22+
/// <summary>
23+
/// Creates a new instance of Microsoft.Identity.Client.Extensions.Msal.MsalCacheHelper.
24+
/// To configure MSAL to use this cache persistence, call Microsoft.Identity.Client.Extensions.Msal.MsalCacheHelper.RegisterCache(Microsoft.Identity.Client.ITokenCache)
25+
/// </summary>
26+
/// <param name="storageCreationProperties"></param>
27+
/// <param name="logger">Passing null uses a default logger</param>
28+
/// <returns>A new instance of Microsoft.Identity.Client.Extensions.Msal.MsalCacheHelper.</returns>
29+
public virtual async Task InitializeAsync(StorageCreationProperties storageCreationProperties, TraceSource logger = null)
30+
{
31+
_helper = await MsalCacheHelper.CreateAsync(storageCreationProperties, logger).ConfigureAwait(false);
32+
}
33+
34+
/// <summary>
35+
/// Performs a write -> read -> clear using the underlying persistence mechanism
36+
/// and throws an Microsoft.Identity.Client.Extensions.Msal.MsalCachePersistenceException
37+
/// if something goes wrong.
38+
/// </summary>
39+
/// <remarks>
40+
/// Does not overwrite the token cache. Should never fail on Windows and Mac where
41+
/// the cache accessors are guaranteed to exist by the OS.
42+
/// </remarks>
43+
public virtual void VerifyPersistence()
44+
{
45+
_helper.VerifyPersistence();
46+
}
47+
48+
/// <summary>
49+
/// Registers a token cache to synchronize with on disk storage.
50+
/// </summary>
51+
/// <param name="tokenCache"></param>
52+
public virtual void RegisterCache(ITokenCache tokenCache)
53+
{
54+
_helper.RegisterCache(tokenCache);
55+
}
56+
57+
/// <summary>
58+
/// Unregisters a token cache so it no longer synchronizes with on disk storage.
59+
/// </summary>
60+
/// <param name="tokenCache"></param>
61+
public virtual void UnregisterCache(ITokenCache tokenCache)
62+
{
63+
_helper.UnregisterCache(tokenCache);
64+
}
65+
66+
/// <summary>
67+
/// Clears the token store.
68+
/// </summary>
69+
public virtual void Clear()
70+
{
71+
_helper.Clear();
72+
}
73+
74+
/// <summary>
75+
/// Extracts the token cache data from the persistent store
76+
/// </summary>
77+
/// <remarks>
78+
/// This method should be used with care. The data returned is unencrypted.
79+
/// </remarks>
80+
/// <returns>UTF-8 byte array of the unencrypted token cache</returns>
81+
public virtual byte[] LoadUnencryptedTokenCache()
82+
{
83+
return _helper.LoadUnencryptedTokenCache();
84+
}
85+
86+
/// <summary>
87+
/// Saves an unencrypted, UTF-8 encoded byte array representing an MSAL token cache.
88+
/// The save operation will persist the data in a secure location, as configured
89+
/// in Microsoft.Identity.Client.Extensions.Msal.StorageCreationProperties
90+
/// </summary>
91+
public virtual void SaveUnencryptedTokenCache(byte[] tokenCache)
92+
{
93+
_helper.SaveUnencryptedTokenCache(tokenCache);
94+
}
95+
}
96+
}

sdk/identity/Azure.Identity/src/PersistentTokenCache.cs

Lines changed: 40 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -16,44 +16,49 @@ public class PersistentTokenCache : TokenCache
1616
{
1717
// we are creating the MsalCacheHelper with a random guid based clientId to work around issue https://github.com/AzureAD/microsoft-authentication-extensions-for-dotnet/issues/98
1818
// This does not impact the functionality of the cacheHelper as the ClientId is only used to iterate accounts in the cache not for authentication purposes.
19-
private static readonly string s_msalCacheClientId = Guid.NewGuid().ToString();
20-
21-
private static AsyncLockWithValue<MsalCacheHelper> cacheHelperLock = new AsyncLockWithValue<MsalCacheHelper>();
22-
private static AsyncLockWithValue<MsalCacheHelper> s_ProtectedCacheHelperLock = new AsyncLockWithValue<MsalCacheHelper>();
23-
private static AsyncLockWithValue<MsalCacheHelper> s_FallbackCacheHelperLock = new AsyncLockWithValue<MsalCacheHelper>();
19+
internal static readonly string s_msalCacheClientId = Guid.NewGuid().ToString();
2420
private readonly bool _allowUnencryptedStorage;
2521
private readonly string _name;
26-
27-
/// <summary>
28-
/// Creates a new instance of <see cref="PersistentTokenCache"/>.
29-
/// </summary>
30-
/// <param name="allowUnencryptedStorage"></param>
31-
public PersistentTokenCache(bool allowUnencryptedStorage = true)
32-
{
33-
_allowUnencryptedStorage = allowUnencryptedStorage;
34-
}
22+
private static AsyncLockWithValue<MsalCacheHelperWrapper> cacheHelperLock = new AsyncLockWithValue<MsalCacheHelperWrapper>();
23+
private readonly MsalCacheHelperWrapper _cacheHelperWrapper;
3524

3625
/// <summary>
3726
/// Creates a new instance of <see cref="PersistentTokenCache"/> with the specified options.
3827
/// </summary>
3928
/// <param name="options">Options controlling the storage of the <see cref="PersistentTokenCache"/>.</param>
40-
public PersistentTokenCache(PersistentTokenCacheOptions options)
29+
public PersistentTokenCache(PersistentTokenCacheOptions options = null)
4130
{
4231
_allowUnencryptedStorage = options?.AllowUnencryptedStorage ?? false;
32+
_name = options?.Name ?? Constants.DefaultMsalTokenCacheName;
33+
_cacheHelperWrapper = new MsalCacheHelperWrapper();
34+
}
4335

44-
_name = options?.Name;
36+
internal PersistentTokenCache(PersistentTokenCacheOptions options, MsalCacheHelperWrapper cacheHelperWrapper)
37+
{
38+
_allowUnencryptedStorage = options?.AllowUnencryptedStorage ?? false;
39+
_name = options?.Name ?? Constants.DefaultMsalTokenCacheName;
40+
_cacheHelperWrapper = cacheHelperWrapper;
4541
}
4642

4743
internal override async Task RegisterCache(bool async, ITokenCache tokenCache, CancellationToken cancellationToken)
4844
{
49-
MsalCacheHelper cacheHelper = await GetCacheHelperAsync(async, cancellationToken).ConfigureAwait(false);
45+
MsalCacheHelperWrapper cacheHelper = await GetCacheHelperAsync(async, cancellationToken).ConfigureAwait(false);
5046

5147
cacheHelper.RegisterCache(tokenCache);
5248

5349
await base.RegisterCache(async, tokenCache, cancellationToken).ConfigureAwait(false);
5450
}
5551

56-
private async Task<MsalCacheHelper> GetCacheHelperAsync(bool async, CancellationToken cancellationToken)
52+
/// <summary>
53+
/// Resets the <see cref="cacheHelperLock"/> so that tests can validate multiple calls to <see cref="RegisterCache"/>
54+
/// This should only be used for testing.
55+
/// </summary>
56+
internal static void ResetWrapperCache()
57+
{
58+
cacheHelperLock = new AsyncLockWithValue<MsalCacheHelperWrapper>();
59+
}
60+
61+
private async Task<MsalCacheHelperWrapper> GetCacheHelperAsync(bool async, CancellationToken cancellationToken)
5762
{
5863
using var asyncLock = await cacheHelperLock.GetLockOrValueAsync(async, cancellationToken).ConfigureAwait(false);
5964

@@ -62,19 +67,19 @@ private async Task<MsalCacheHelper> GetCacheHelperAsync(bool async, Cancellation
6267
return asyncLock.Value;
6368
}
6469

65-
MsalCacheHelper cacheHelper;
70+
MsalCacheHelperWrapper cacheHelper;
6671

6772
try
6873
{
69-
cacheHelper = string.IsNullOrEmpty(_name) ? await GetProtectedCacheHelperAsync(async, cancellationToken).ConfigureAwait(false) : await GetProtectedCacheHelperAsync(async, _name).ConfigureAwait(false);
74+
cacheHelper = await GetProtectedCacheHelperAsync(async, _name).ConfigureAwait(false);
7075

7176
cacheHelper.VerifyPersistence();
7277
}
7378
catch (MsalCachePersistenceException)
7479
{
7580
if (_allowUnencryptedStorage)
7681
{
77-
cacheHelper = string.IsNullOrEmpty(_name) ? await GetFallbackCacheHelperAsync(async, cancellationToken).ConfigureAwait(false) : await GetFallbackCacheHelperAsync(async, _name).ConfigureAwait(false);
82+
cacheHelper = await GetFallbackCacheHelperAsync(async, _name).ConfigureAwait(false);
7883

7984
cacheHelper.VerifyPersistence();
8085
}
@@ -89,69 +94,43 @@ private async Task<MsalCacheHelper> GetCacheHelperAsync(bool async, Cancellation
8994
return cacheHelper;
9095
}
9196

92-
private static async Task<MsalCacheHelper> GetProtectedCacheHelperAsync(bool async, CancellationToken cancellationToken)
93-
{
94-
using var asyncLock = await s_ProtectedCacheHelperLock.GetLockOrValueAsync(async, cancellationToken).ConfigureAwait(false);
95-
96-
if (asyncLock.HasValue)
97-
{
98-
return asyncLock.Value;
99-
}
100-
101-
MsalCacheHelper cacheHelper = await GetProtectedCacheHelperAsync(async, Constants.DefaultMsalTokenCacheName).ConfigureAwait(false);
102-
103-
asyncLock.SetValue(cacheHelper);
104-
105-
return cacheHelper;
106-
}
107-
108-
private static async Task<MsalCacheHelper> GetProtectedCacheHelperAsync(bool async, string name)
97+
private async Task<MsalCacheHelperWrapper> GetProtectedCacheHelperAsync(bool async, string name)
10998
{
11099
StorageCreationProperties storageProperties = new StorageCreationPropertiesBuilder(name, Constants.DefaultMsalTokenCacheDirectory, s_msalCacheClientId)
111100
.WithMacKeyChain(Constants.DefaultMsalTokenCacheKeychainService, name)
112101
.WithLinuxKeyring(Constants.DefaultMsalTokenCacheKeyringSchema, Constants.DefaultMsalTokenCacheKeyringCollection, name, Constants.DefaultMsaltokenCacheKeyringAttribute1, Constants.DefaultMsaltokenCacheKeyringAttribute2)
113102
.Build();
114103

115-
MsalCacheHelper cacheHelper = await CreateCacheHelper(async, storageProperties).ConfigureAwait(false);
116-
117-
return cacheHelper;
118-
}
119-
120-
private static async Task<MsalCacheHelper> GetFallbackCacheHelperAsync(bool async, CancellationToken cancellationToken)
121-
{
122-
using var asyncLock = await s_FallbackCacheHelperLock.GetLockOrValueAsync(async, cancellationToken).ConfigureAwait(false);
123-
124-
if (asyncLock.HasValue)
125-
{
126-
return asyncLock.Value;
127-
}
128-
129-
MsalCacheHelper cacheHelper = await GetFallbackCacheHelperAsync(async, Constants.DefaultMsalTokenCacheName).ConfigureAwait(false);
130-
131-
asyncLock.SetValue(cacheHelper);
104+
MsalCacheHelperWrapper cacheHelper = await InitializeCacheHelper(async, storageProperties).ConfigureAwait(false);
132105

133106
return cacheHelper;
134107
}
135108

136-
private static async Task<MsalCacheHelper> GetFallbackCacheHelperAsync(bool async, string name)
109+
private async Task<MsalCacheHelperWrapper> GetFallbackCacheHelperAsync(bool async, string name = Constants.DefaultMsalTokenCacheName)
137110
{
138111
StorageCreationProperties storageProperties = new StorageCreationPropertiesBuilder(name, Constants.DefaultMsalTokenCacheDirectory, s_msalCacheClientId)
139112
.WithMacKeyChain(Constants.DefaultMsalTokenCacheKeychainService, name)
140113
.WithLinuxUnprotectedFile()
141114
.Build();
142115

143-
MsalCacheHelper cacheHelper = await CreateCacheHelper(async, storageProperties).ConfigureAwait(false);
116+
MsalCacheHelperWrapper cacheHelper = await InitializeCacheHelper(async, storageProperties).ConfigureAwait(false);
144117

145118
return cacheHelper;
146119
}
147120

148-
private static async Task<MsalCacheHelper> CreateCacheHelper(bool async, StorageCreationProperties storageProperties)
121+
private async Task<MsalCacheHelperWrapper> InitializeCacheHelper(bool async, StorageCreationProperties storageProperties)
149122
{
150-
return async
151-
? await MsalCacheHelper.CreateAsync(storageProperties).ConfigureAwait(false)
123+
if (async)
124+
{
125+
await _cacheHelperWrapper.InitializeAsync(storageProperties).ConfigureAwait(false);
126+
}
127+
else
128+
{
152129
#pragma warning disable AZC0102 // Do not use GetAwaiter().GetResult(). Use the TaskExtensions.EnsureCompleted() extension method instead.
153-
: MsalCacheHelper.CreateAsync(storageProperties).GetAwaiter().GetResult();
130+
_cacheHelperWrapper.InitializeAsync(storageProperties).GetAwaiter().GetResult();
154131
#pragma warning restore AZC0102 // Do not use GetAwaiter().GetResult(). Use the TaskExtensions.EnsureCompleted() extension method instead.
132+
}
133+
return _cacheHelperWrapper;
155134
}
156135
}
157136
}

0 commit comments

Comments
 (0)