Skip to content

Commit 59821ed

Browse files
authored
Add TokenCacheSerializer (Azure#18601)
1 parent e542d6b commit 59821ed

File tree

7 files changed

+415
-104
lines changed

7 files changed

+415
-104
lines changed

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

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -229,10 +229,13 @@ public partial class TokenCache
229229
{
230230
public TokenCache() { }
231231
public event System.Func<Azure.Identity.TokenCacheUpdatedArgs, System.Threading.Tasks.Task> Updated { add { } remove { } }
232+
}
233+
public static partial class TokenCacheSerializer
234+
{
232235
public static Azure.Identity.TokenCache Deserialize(System.IO.Stream stream, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
233236
public static System.Threading.Tasks.Task<Azure.Identity.TokenCache> DeserializeAsync(System.IO.Stream stream, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
234-
public void Serialize(System.IO.Stream stream, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { }
235-
public System.Threading.Tasks.Task SerializeAsync(System.IO.Stream stream, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
237+
public static void Serialize(Azure.Identity.TokenCache tokenCache, System.IO.Stream stream, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { }
238+
public static System.Threading.Tasks.Task SerializeAsync(Azure.Identity.TokenCache tokenCache, System.IO.Stream stream, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
236239
}
237240
public partial class TokenCacheUpdatedArgs
238241
{

sdk/identity/Azure.Identity/samples/TokenCache.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ private const string TOKEN_CACHE_PATH = "./tokencache.bin";
6060
```C# Snippet:Identity_TokenCache_CustomPersistence_Write
6161
using (var cacheStream = new FileStream(TOKEN_CACHE_PATH, FileMode.Create, FileAccess.Write))
6262
{
63-
await tokenCache.SerializeAsync(cacheStream);
63+
await TokenCacheSerializer.SerializeAsync(tokenCache, cacheStream);
6464
}
6565
```
6666

@@ -71,7 +71,7 @@ TokenCache tokenCache;
7171

7272
using (var cacheStream = new FileStream(TOKEN_CACHE_PATH, FileMode.OpenOrCreate, FileAccess.Read))
7373
{
74-
tokenCache = await TokenCache.DeserializeAsync(cacheStream);
74+
tokenCache = await TokenCacheSerializer.DeserializeAsync(cacheStream);
7575
}
7676
```
7777

@@ -84,7 +84,7 @@ public static async Task<TokenCache> ReadTokenCacheAsync()
8484

8585
using (var cacheStream = new FileStream(TOKEN_CACHE_PATH, FileMode.OpenOrCreate, FileAccess.Read))
8686
{
87-
tokenCache = await TokenCache.DeserializeAsync(cacheStream);
87+
tokenCache = await TokenCacheSerializer.DeserializeAsync(cacheStream);
8888
tokenCache.Updated += WriteCacheOnUpdateAsync;
8989
}
9090

@@ -95,7 +95,7 @@ public static async Task WriteCacheOnUpdateAsync(TokenCacheUpdatedArgs args)
9595
{
9696
using (var cacheStream = new FileStream(TOKEN_CACHE_PATH, FileMode.Create, FileAccess.Write))
9797
{
98-
await args.Cache.SerializeAsync(cacheStream);
98+
await TokenCacheSerializer.SerializeAsync(args.Cache, cacheStream);
9999
}
100100
}
101101

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

Lines changed: 18 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,15 @@ namespace Azure.Identity
1919
public class TokenCache
2020
#pragma warning restore CA1001 // Types that own disposable fields should be disposable
2121
{
22-
private SemaphoreSlim _lock = new SemaphoreSlim(1,1);
23-
private byte[] _data;
22+
private readonly SemaphoreSlim _lock = new SemaphoreSlim(1, 1);
2423
private DateTimeOffset _lastUpdated;
2524
private ConditionalWeakTable<object, CacheTimestamp> _cacheAccessMap;
25+
internal Func<IPublicClientApplication> _publicClientApplicationFactory;
26+
27+
/// <summary>
28+
/// The internal state of the cache.
29+
/// </summary>
30+
internal byte[] Data { get; private set; }
2631

2732
private class CacheTimestamp
2833
{
@@ -33,9 +38,10 @@ public CacheTimestamp()
3338
Update();
3439
}
3540

36-
public void Update()
41+
public DateTimeOffset Update()
3742
{
3843
_timestamp = DateTimeOffset.UtcNow;
44+
return _timestamp;
3945
}
4046

4147
public DateTimeOffset Value { get { return _timestamp; } }
@@ -49,98 +55,19 @@ public TokenCache()
4955
{
5056
}
5157

52-
internal TokenCache(byte[] data)
58+
internal TokenCache(byte[] data, Func<IPublicClientApplication> publicApplicationFactory = null)
5359
{
54-
_data = data;
60+
Data = data;
5561
_lastUpdated = DateTimeOffset.UtcNow;
5662
_cacheAccessMap = new ConditionalWeakTable<object, CacheTimestamp>();
63+
_publicClientApplicationFactory = publicApplicationFactory ?? new (() => PublicClientApplicationBuilder.Create(Guid.NewGuid().ToString()).Build());
5764
}
5865

5966
/// <summary>
6067
/// An event notifying the subscriber that the underlying <see cref="TokenCache"/> has been updated. This event can be handled to persist the updated cache data.
6168
/// </summary>
6269
public event Func<TokenCacheUpdatedArgs, Task> Updated;
6370

64-
/// <summary>
65-
/// Serializes the <see cref="TokenCache"/> to the specified <see cref="Stream"/>.
66-
/// </summary>
67-
/// <param name="stream">The <see cref="Stream"/> which the serialized <see cref="TokenCache"/> will be written to.</param>
68-
/// <param name="cancellationToken">A <see cref="CancellationToken"/> controlling the request lifetime.</param>
69-
public void Serialize(Stream stream, CancellationToken cancellationToken = default)
70-
{
71-
if (stream is null)
72-
throw new ArgumentNullException(nameof(stream));
73-
74-
SerializeAsync(stream, false, cancellationToken).EnsureCompleted();
75-
}
76-
77-
/// <summary>
78-
/// Serializes the <see cref="TokenCache"/> to the specified <see cref="Stream"/>.
79-
/// </summary>
80-
/// <param name="stream">The <see cref="Stream"/> to which the serialized <see cref="TokenCache"/> will be written.</param>
81-
/// <param name="cancellationToken">A <see cref="CancellationToken"/> controlling the request lifetime.</param>
82-
public async Task SerializeAsync(Stream stream, CancellationToken cancellationToken = default)
83-
{
84-
if (stream is null)
85-
throw new ArgumentNullException(nameof(stream));
86-
87-
await SerializeAsync(stream, true, cancellationToken).ConfigureAwait(false);
88-
}
89-
90-
/// <summary>
91-
/// Deserializes the <see cref="TokenCache"/> from the specified <see cref="Stream"/>.
92-
/// </summary>
93-
/// <param name="stream">The <see cref="Stream"/> from which the serialized <see cref="TokenCache"/> will be read.</param>
94-
/// <param name="cancellationToken">A <see cref="CancellationToken"/> controlling the request lifetime.</param>
95-
public static TokenCache Deserialize(Stream stream, CancellationToken cancellationToken = default)
96-
{
97-
if (stream is null)
98-
throw new ArgumentNullException(nameof(stream));
99-
100-
return DeserializeAsync(stream, false, cancellationToken).EnsureCompleted();
101-
}
102-
103-
/// <summary>
104-
/// Deserializes the <see cref="TokenCache"/> from the specified <see cref="Stream"/>.
105-
/// </summary>
106-
/// <param name="stream">The <see cref="Stream"/> from which the serialized <see cref="TokenCache"/> will be read.</param>
107-
/// <param name="cancellationToken">A <see cref="CancellationToken"/> controlling the request lifetime.</param>
108-
public static async Task<TokenCache> DeserializeAsync(Stream stream, CancellationToken cancellationToken = default)
109-
{
110-
if (stream is null)
111-
throw new ArgumentNullException(nameof(stream));
112-
113-
return await DeserializeAsync(stream, true, cancellationToken).ConfigureAwait(false);
114-
}
115-
116-
private async Task SerializeAsync(Stream stream, bool async, CancellationToken cancellationToken)
117-
{
118-
if (async)
119-
{
120-
await stream.WriteAsync(_data, 0, _data.Length, cancellationToken).ConfigureAwait(false);
121-
}
122-
else
123-
{
124-
stream.Write(_data, 0, _data.Length);
125-
}
126-
}
127-
128-
private static async Task<TokenCache> DeserializeAsync(Stream stream, bool async, CancellationToken cancellationToken)
129-
{
130-
var data = new byte[stream.Length - stream.Position];
131-
132-
if (async)
133-
{
134-
await stream.ReadAsync(data, 0, data.Length, cancellationToken).ConfigureAwait(false);
135-
}
136-
else
137-
{
138-
stream.Read(data, 0, data.Length);
139-
}
140-
141-
return new TokenCache(data);
142-
}
143-
14471
internal virtual async Task RegisterCache(bool async, ITokenCache tokenCache, CancellationToken cancellationToken)
14572
{
14673
if (async)
@@ -175,7 +102,7 @@ private async Task OnBeforeCacheAccessAsync(TokenCacheNotificationArgs args)
175102

176103
try
177104
{
178-
args.TokenCache.DeserializeMsalV3(_data, true);
105+
args.TokenCache.DeserializeMsalV3(Data, true);
179106

180107
_cacheAccessMap.GetOrCreateValue(args.TokenCache).Update();
181108
}
@@ -201,16 +128,14 @@ private async Task UpdateCacheDataAsync(ITokenCacheSerializer tokenCache)
201128
{
202129
if (!_cacheAccessMap.TryGetValue(tokenCache, out CacheTimestamp lastRead) || lastRead.Value < _lastUpdated)
203130
{
204-
_data = await MergeCacheData(_data, tokenCache.SerializeMsalV3()).ConfigureAwait(false);
131+
Data = await MergeCacheData(Data, tokenCache.SerializeMsalV3()).ConfigureAwait(false);
205132
}
206133
else
207134
{
208-
_data = tokenCache.SerializeMsalV3();
135+
Data = tokenCache.SerializeMsalV3();
209136
}
210137

211-
_cacheAccessMap.GetOrCreateValue(tokenCache).Update();
212-
213-
_lastUpdated = DateTime.UtcNow;
138+
_lastUpdated = _cacheAccessMap.GetOrCreateValue(tokenCache).Update();
214139
}
215140
finally
216141
{
@@ -226,11 +151,11 @@ private async Task UpdateCacheDataAsync(ITokenCacheSerializer tokenCache)
226151
}
227152
}
228153

229-
private static async Task<byte[]> MergeCacheData(byte[] cacheA, byte[] cacheB)
154+
private async Task<byte[]> MergeCacheData(byte[] cacheA, byte[] cacheB)
230155
{
231156
byte[] merged = null;
232157

233-
var client = PublicClientApplicationBuilder.Create(Guid.NewGuid().ToString()).Build();
158+
IPublicClientApplication client = _publicClientApplicationFactory();
234159

235160
client.UserTokenCache.SetBeforeAccess(args => args.TokenCache.DeserializeMsalV3(cacheA));
236161

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
using System;
5+
using System.IO;
6+
using System.Threading;
7+
using System.Threading.Tasks;
8+
using Azure.Core.Pipeline;
9+
using Azure.Core;
10+
11+
namespace Azure.Identity
12+
{
13+
/// <summary>
14+
/// A cache for Tokens.
15+
/// </summary>
16+
public static class TokenCacheSerializer
17+
{
18+
/// <summary>
19+
/// Serializes the <see cref="TokenCache"/> to the specified <see cref="Stream"/>.
20+
/// </summary>
21+
/// <param name="tokenCache">The <see cref="TokenCache"/> to serialize.</param>
22+
/// <param name="stream">The <see cref="Stream"/> which the serialized <see cref="TokenCache"/> will be written to.</param>
23+
/// <param name="cancellationToken">A <see cref="CancellationToken"/> controlling the request lifetime.</param>
24+
public static void Serialize(TokenCache tokenCache, Stream stream, CancellationToken cancellationToken = default)
25+
{
26+
Argument.AssertNotNull(tokenCache, nameof(tokenCache));
27+
Argument.AssertNotNull(stream, nameof(stream));
28+
29+
SerializeAsync(tokenCache, stream, false, cancellationToken).EnsureCompleted();
30+
}
31+
32+
/// <summary>
33+
/// Serializes the <see cref="TokenCache"/> to the specified <see cref="Stream"/>.
34+
/// </summary>
35+
/// <param name="tokenCache">The <see cref="TokenCache"/> to serialize.</param>
36+
/// <param name="stream">The <see cref="Stream"/> to which the serialized <see cref="TokenCache"/> will be written.</param>
37+
/// <param name="cancellationToken">A <see cref="CancellationToken"/> controlling the request lifetime.</param>
38+
public static async Task SerializeAsync(TokenCache tokenCache, Stream stream, CancellationToken cancellationToken = default)
39+
{
40+
Argument.AssertNotNull(tokenCache, nameof(tokenCache));
41+
Argument.AssertNotNull(stream, nameof(stream));
42+
43+
await SerializeAsync(tokenCache, stream, true, cancellationToken).ConfigureAwait(false);
44+
}
45+
46+
/// <summary>
47+
/// Deserializes the <see cref="TokenCache"/> from the specified <see cref="Stream"/>.
48+
/// </summary>
49+
/// <param name="stream">The <see cref="Stream"/> from which the serialized <see cref="TokenCache"/> will be read.</param>
50+
/// <param name="cancellationToken">A <see cref="CancellationToken"/> controlling the request lifetime.</param>
51+
public static TokenCache Deserialize(Stream stream, CancellationToken cancellationToken = default)
52+
{
53+
Argument.AssertNotNull(stream, nameof(stream));
54+
55+
return DeserializeAsync(stream, false, cancellationToken).EnsureCompleted();
56+
}
57+
58+
/// <summary>
59+
/// Deserializes the <see cref="TokenCache"/> from the specified <see cref="Stream"/>.
60+
/// </summary>
61+
/// <param name="stream">The <see cref="Stream"/> from which the serialized <see cref="TokenCache"/> will be read.</param>
62+
/// <param name="cancellationToken">A <see cref="CancellationToken"/> controlling the request lifetime.</param>
63+
public static async Task<TokenCache> DeserializeAsync(Stream stream, CancellationToken cancellationToken = default)
64+
{
65+
Argument.AssertNotNull(stream, nameof(stream));
66+
67+
return await DeserializeAsync(stream, true, cancellationToken).ConfigureAwait(false);
68+
}
69+
70+
private static async Task SerializeAsync(TokenCache tokenCache, Stream stream, bool async, CancellationToken cancellationToken)
71+
{
72+
if (async)
73+
{
74+
await stream.WriteAsync(tokenCache.Data, 0, tokenCache.Data.Length, cancellationToken).ConfigureAwait(false);
75+
}
76+
else
77+
{
78+
stream.Write(tokenCache.Data, 0, tokenCache.Data.Length);
79+
}
80+
}
81+
82+
private static async Task<TokenCache> DeserializeAsync(Stream stream, bool async, CancellationToken cancellationToken)
83+
{
84+
var data = new byte[stream.Length - stream.Position];
85+
86+
if (async)
87+
{
88+
await stream.ReadAsync(data, 0, data.Length, cancellationToken).ConfigureAwait(false);
89+
}
90+
else
91+
{
92+
stream.Read(data, 0, data.Length);
93+
}
94+
95+
return new TokenCache(data);
96+
}
97+
}
98+
}

0 commit comments

Comments
 (0)