From ba035689ae34b04f3eaa0d0cde1843fba53867ef Mon Sep 17 00:00:00 2001
From: pmaytak <34331512+pmaytak@users.noreply.github.com>
Date: Fri, 1 Jul 2022 01:19:18 -0700
Subject: [PATCH 1/8] Add IIdentityCache as default cache. Add user-provided
IIdentityCache option. Add CompositeCache as an implementation.
---
LibsAndSamples.sln | 33 +++++
src/client/Abstractions/CacheEntryOptions.cs | 131 ++++++++++++++++
.../CacheEntryOptionsExtensions.cs | 120 +++++++++++++++
.../Abstractions/DateTimeOffsetExtensions.cs | 36 +++++
src/client/Abstractions/ICacheEntry.cs | 36 +++++
src/client/Abstractions/ICacheObject.cs | 25 ++++
src/client/Abstractions/IIdentityCache.cs | 140 ++++++++++++++++++
.../Abstractions/Implementation/CacheEntry.cs | 111 ++++++++++++++
src/client/Abstractions/InternalsVisibleTo.cs | 6 +
...tity.ServiceEssentials.Abstractions.csproj | 15 ++
.../AppConfig/CacheOptions.cs | 29 ++++
.../Cache/CacheSessionManager.cs | 8 +-
.../Cache/Prototype/DefaultInMemoryCache.cs | 66 +++++++++
.../Cache/Prototype/IdentityCacheWrapper.cs | 34 +++++
.../ClientApplicationBase.cs | 15 +-
.../ConfidentialClientApplication.cs | 4 +-
.../ITokenCacheInternal.cs | 2 +-
.../Microsoft.Identity.Client.csproj | 9 +-
.../TokenCache.ITokenCacheInternal.cs | 95 ++++++++----
.../Microsoft.Identity.Client/TokenCache.cs | 26 +++-
.../CacheTests/UnifiedCacheTests.cs | 2 +-
.../Net5TestApp/CompositeCacheAdapter.cs | 76 ++++++++++
tests/devapps/Net5TestApp/Net5TestApp.csproj | 15 +-
tests/devapps/Net5TestApp/Program.cs | 47 +++---
24 files changed, 1012 insertions(+), 69 deletions(-)
create mode 100644 src/client/Abstractions/CacheEntryOptions.cs
create mode 100644 src/client/Abstractions/CacheEntryOptionsExtensions.cs
create mode 100644 src/client/Abstractions/DateTimeOffsetExtensions.cs
create mode 100644 src/client/Abstractions/ICacheEntry.cs
create mode 100644 src/client/Abstractions/ICacheObject.cs
create mode 100644 src/client/Abstractions/IIdentityCache.cs
create mode 100644 src/client/Abstractions/Implementation/CacheEntry.cs
create mode 100644 src/client/Abstractions/InternalsVisibleTo.cs
create mode 100644 src/client/Abstractions/Microsoft.Identity.ServiceEssentials.Abstractions.csproj
create mode 100644 src/client/Microsoft.Identity.Client/Cache/Prototype/DefaultInMemoryCache.cs
create mode 100644 src/client/Microsoft.Identity.Client/Cache/Prototype/IdentityCacheWrapper.cs
create mode 100644 tests/devapps/Net5TestApp/CompositeCacheAdapter.cs
diff --git a/LibsAndSamples.sln b/LibsAndSamples.sln
index 27f1ced788..d77b384f91 100644
--- a/LibsAndSamples.sln
+++ b/LibsAndSamples.sln
@@ -5,6 +5,9 @@ MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{9B0B5396-4D95-4C15-82ED-DC22B5A3123F}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Identity.Client", "src\client\Microsoft.Identity.Client\Microsoft.Identity.Client.csproj", "{60117A9B-4BB8-472E-BFFF-52CBF67CA95A}"
+ ProjectSection(ProjectDependencies) = postProject
+ {57AB6744-A9D0-47BD-AEDF-D1B17639996D} = {57AB6744-A9D0-47BD-AEDF-D1B17639996D}
+ EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "devapps", "devapps", "{34BE693E-3496-45A4-B1D2-D3A0E068EEDB}"
ProjectSection(SolutionItems) = preProject
@@ -185,6 +188,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Intune-xamarin-Android", "t
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Identity.Client.Broker", "src\client\Microsoft.Identity.Client.Broker\Microsoft.Identity.Client.Broker.csproj", "{6839F934-45F0-4026-8AF3-C3AEFB7D48A9}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Identity.ServiceEssentials.Abstractions", "src\client\Abstractions\Microsoft.Identity.ServiceEssentials.Abstractions.csproj", "{57AB6744-A9D0-47BD-AEDF-D1B17639996D}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -1584,6 +1589,34 @@ Global
{6839F934-45F0-4026-8AF3-C3AEFB7D48A9}.Release|x64.Build.0 = Release|Any CPU
{6839F934-45F0-4026-8AF3-C3AEFB7D48A9}.Release|x86.ActiveCfg = Release|Any CPU
{6839F934-45F0-4026-8AF3-C3AEFB7D48A9}.Release|x86.Build.0 = Release|Any CPU
+ {57AB6744-A9D0-47BD-AEDF-D1B17639996D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {57AB6744-A9D0-47BD-AEDF-D1B17639996D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {57AB6744-A9D0-47BD-AEDF-D1B17639996D}.Debug|ARM.ActiveCfg = Debug|Any CPU
+ {57AB6744-A9D0-47BD-AEDF-D1B17639996D}.Debug|ARM.Build.0 = Debug|Any CPU
+ {57AB6744-A9D0-47BD-AEDF-D1B17639996D}.Debug|ARM64.ActiveCfg = Debug|Any CPU
+ {57AB6744-A9D0-47BD-AEDF-D1B17639996D}.Debug|ARM64.Build.0 = Debug|Any CPU
+ {57AB6744-A9D0-47BD-AEDF-D1B17639996D}.Debug|iPhone.ActiveCfg = Debug|Any CPU
+ {57AB6744-A9D0-47BD-AEDF-D1B17639996D}.Debug|iPhone.Build.0 = Debug|Any CPU
+ {57AB6744-A9D0-47BD-AEDF-D1B17639996D}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
+ {57AB6744-A9D0-47BD-AEDF-D1B17639996D}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
+ {57AB6744-A9D0-47BD-AEDF-D1B17639996D}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {57AB6744-A9D0-47BD-AEDF-D1B17639996D}.Debug|x64.Build.0 = Debug|Any CPU
+ {57AB6744-A9D0-47BD-AEDF-D1B17639996D}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {57AB6744-A9D0-47BD-AEDF-D1B17639996D}.Debug|x86.Build.0 = Debug|Any CPU
+ {57AB6744-A9D0-47BD-AEDF-D1B17639996D}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {57AB6744-A9D0-47BD-AEDF-D1B17639996D}.Release|Any CPU.Build.0 = Release|Any CPU
+ {57AB6744-A9D0-47BD-AEDF-D1B17639996D}.Release|ARM.ActiveCfg = Release|Any CPU
+ {57AB6744-A9D0-47BD-AEDF-D1B17639996D}.Release|ARM.Build.0 = Release|Any CPU
+ {57AB6744-A9D0-47BD-AEDF-D1B17639996D}.Release|ARM64.ActiveCfg = Release|Any CPU
+ {57AB6744-A9D0-47BD-AEDF-D1B17639996D}.Release|ARM64.Build.0 = Release|Any CPU
+ {57AB6744-A9D0-47BD-AEDF-D1B17639996D}.Release|iPhone.ActiveCfg = Release|Any CPU
+ {57AB6744-A9D0-47BD-AEDF-D1B17639996D}.Release|iPhone.Build.0 = Release|Any CPU
+ {57AB6744-A9D0-47BD-AEDF-D1B17639996D}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
+ {57AB6744-A9D0-47BD-AEDF-D1B17639996D}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
+ {57AB6744-A9D0-47BD-AEDF-D1B17639996D}.Release|x64.ActiveCfg = Release|Any CPU
+ {57AB6744-A9D0-47BD-AEDF-D1B17639996D}.Release|x64.Build.0 = Release|Any CPU
+ {57AB6744-A9D0-47BD-AEDF-D1B17639996D}.Release|x86.ActiveCfg = Release|Any CPU
+ {57AB6744-A9D0-47BD-AEDF-D1B17639996D}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/src/client/Abstractions/CacheEntryOptions.cs b/src/client/Abstractions/CacheEntryOptions.cs
new file mode 100644
index 0000000000..26594e7bd9
--- /dev/null
+++ b/src/client/Abstractions/CacheEntryOptions.cs
@@ -0,0 +1,131 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using System;
+using System.ComponentModel;
+
+namespace Microsoft.Identity.ServiceEssentials
+{
+ ///
+ /// Represents the cache options applied to an .
+ ///
+ public class CacheEntryOptions
+ {
+ private TimeSpan _timeToExpire = TimeSpan.FromHours(1);
+ private TimeSpan _timeToRefresh = TimeSpan.MaxValue;
+ private TimeSpan _timeToRemove = TimeSpan.FromHours(1);
+
+ ///
+ /// Logical time-to-live for the , relative to time now.
+ ///
+ public TimeSpan TimeToExpire
+ {
+ get => _timeToExpire;
+ set
+ {
+ if (value <= TimeSpan.Zero)
+ throw new ArgumentOutOfRangeException(nameof(value));
+ _timeToExpire = value;
+ }
+ }
+
+ ///
+ /// Emergency time-to-live for the cache item, relative to time now.
+ /// Represents the actual expiration time for the in a cache storage and can be used as a 'last-known-good'
+ /// value in scenarios when primary data source is not available.
+ ///
+ ///
+ /// Defaults to .
+ ///
+ public TimeSpan TimeToRemove
+ {
+ get => _timeToRemove;
+ set
+ {
+ if (value <= TimeSpan.Zero)
+ throw new ArgumentOutOfRangeException(nameof(value));
+ _timeToRemove = value;
+ }
+ }
+
+ ///
+ /// Refresh time-to-live for the , relative to time now.
+ /// This value can be used in proactive refresh scenarios.
+ ///
+ ///
+ /// for no refresh (default).
+ ///
+ public TimeSpan TimeToRefresh
+ {
+ get => _timeToRefresh;
+ set
+ {
+ if (value <= TimeSpan.Zero)
+ throw new ArgumentOutOfRangeException(nameof(value));
+ _timeToRefresh = value;
+ }
+ }
+
+ ///
+ /// Flag that indicates whether the should be stored only in a local cache.
+ ///
+ public bool StoreToLocalCacheOnly { get; set; }
+
+ ///
+ /// Value that can be used to randomize spans in .
+ ///
+ ///
+ /// Negative value will be used to randomize spans to a maximum of -,
+ /// while positive value will be used to randomize spans to a maximum of +-.
+ ///
+ public int JitterInSeconds { get; set; }
+
+ ///
+ /// Should be used for deserialization only.
+ ///
+ ///
+ /// will be thrown if System.Text.Json is used to
+ /// serialize this class on targets below .NET Core 3.0, if public parameterless constructor is not defined.
+ /// https://docs.microsoft.com/en-us/dotnet/core/compatibility/serialization/5.0/non-public-parameterless-constructors-not-used-for-deserialization
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ [Obsolete("This constructor is for serialization")]
+ public CacheEntryOptions()
+ {
+ }
+
+ ///
+ /// Creates a new instance of the .
+ ///
+ /// Logical time-to-live of the cache item, relative to now.
+ ///
+ /// is set to by default.
+ /// is set to by default.
+ /// is set to 0 by default.
+ ///
+ public CacheEntryOptions(TimeSpan timeToExpire)
+ : this (timeToExpire, 0)
+ { }
+
+ ///
+ /// Creates a new instance of the .
+ ///
+ /// Logical time-to-live of the cache item, relative to now.
+ ///
+ /// Negative value will be used to randomize spans to a maximum of -,
+ /// while positive value will be used to randomize spans to a maximum of +-.
+ ///
+ ///
+ /// is set to by default.
+ /// is set to by default.
+ ///
+ public CacheEntryOptions(TimeSpan timeToExpire, int jitterInSeconds)
+ {
+ TimeToExpire = timeToExpire;
+ TimeToRemove = timeToExpire;
+ TimeToRefresh = TimeSpan.MaxValue;
+ StoreToLocalCacheOnly = false;
+ JitterInSeconds = jitterInSeconds;
+ }
+ }
+}
diff --git a/src/client/Abstractions/CacheEntryOptionsExtensions.cs b/src/client/Abstractions/CacheEntryOptionsExtensions.cs
new file mode 100644
index 0000000000..25cfbbaa17
--- /dev/null
+++ b/src/client/Abstractions/CacheEntryOptionsExtensions.cs
@@ -0,0 +1,120 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using System;
+
+namespace Microsoft.Identity.ServiceEssentials
+{
+ // CAN be made internal and moved to implementation
+
+ ///
+ /// extension methods.
+ ///
+ public static class CacheEntryOptionsExtensions
+ {
+ private static readonly Random _random = new Random();
+
+ ///
+ /// Returns of
+ /// randomized by
+ ///
+ ///
+ /// instance that contains
+ /// and .
+ ///
+ ///
+ /// of
+ /// randomized by .
+ ///
+ ///
+ /// Jitter should be applied where possible, to avoid the thundering herd problem.
+ ///
+ public static TimeSpan GetTimeToRefreshWithJitter(this CacheEntryOptions cacheEntryOptions)
+ {
+ _ = cacheEntryOptions ?? throw new ArgumentNullException(nameof(cacheEntryOptions));
+
+ if (cacheEntryOptions.TimeToRefresh == TimeSpan.MaxValue)
+ return cacheEntryOptions.TimeToRefresh;
+
+ var randomSpan = GetRandomSpan(cacheEntryOptions.JitterInSeconds);
+ return cacheEntryOptions.TimeToRefresh.AddOrCap(randomSpan);
+ }
+
+ ///
+ /// Returns of
+ /// randomized by
+ ///
+ ///
+ /// instance that contains
+ /// and .
+ ///
+ ///
+ /// of
+ /// randomized by .
+ ///
+ ///
+ /// Jitter should be applied where possible, to avoid the thundering herd problem.
+ ///
+ public static TimeSpan GetTimeToRemoveWithJitter(this CacheEntryOptions cacheEntryOptions)
+ {
+ _ = cacheEntryOptions ?? throw new ArgumentNullException(nameof(cacheEntryOptions));
+
+ if (cacheEntryOptions.TimeToRemove == TimeSpan.MaxValue)
+ return cacheEntryOptions.TimeToRefresh;
+
+ var randomSpan = GetRandomSpan(cacheEntryOptions.JitterInSeconds);
+ return cacheEntryOptions.TimeToRemove.AddOrCap(randomSpan);
+ }
+
+ ///
+ /// Returns of
+ /// randomized by
+ ///
+ ///
+ /// instance that contains
+ /// and .
+ ///
+ ///
+ /// of
+ /// randomized by .
+ ///
+ ///
+ /// Jitter should be applied where possible, to avoid the thundering herd problem.
+ ///
+ public static TimeSpan GetTimeToExpireWithJitter(this CacheEntryOptions cacheEntryOptions)
+ {
+ _ = cacheEntryOptions ?? throw new ArgumentNullException(nameof(cacheEntryOptions));
+
+ if (cacheEntryOptions.TimeToExpire == TimeSpan.MaxValue)
+ return cacheEntryOptions.TimeToExpire;
+
+ var randomSpan = GetRandomSpan(cacheEntryOptions.JitterInSeconds);
+ return cacheEntryOptions.TimeToExpire.AddOrCap(randomSpan);
+ }
+
+ private static TimeSpan GetRandomSpan(int jitterSpanInSeconds)
+ {
+ if (jitterSpanInSeconds == 0)
+ return TimeSpan.Zero;
+ else if (jitterSpanInSeconds < 0)
+ return TimeSpan.FromSeconds((long)((_random.NextDouble()) * jitterSpanInSeconds));
+ else
+ return TimeSpan.FromSeconds((long)((_random.NextDouble() * 2.0 - 1.0) * jitterSpanInSeconds));
+ }
+
+ private static TimeSpan AddOrCap(this TimeSpan timeSpan1, TimeSpan timeSpan2)
+ {
+ if (timeSpan1 == TimeSpan.MaxValue || timeSpan2 == TimeSpan.MaxValue)
+ return TimeSpan.MaxValue;
+
+ // checking only if timeSpan == TimeSpan.MaxValue is unsufficient
+ // as jitter might be applied to the timeSpan.
+ // based on: https://referencesource.microsoft.com/#mscorlib/system/timespan.cs,92
+ long result = timeSpan1.Ticks + timeSpan2.Ticks;
+ if ((timeSpan1.Ticks >> 63 == timeSpan2.Ticks >> 63) && (timeSpan1.Ticks >> 63 != result >> 63))
+ return TimeSpan.MaxValue;
+ else
+ return timeSpan1.Add(timeSpan2);
+ }
+ }
+}
diff --git a/src/client/Abstractions/DateTimeOffsetExtensions.cs b/src/client/Abstractions/DateTimeOffsetExtensions.cs
new file mode 100644
index 0000000000..e7e8ac1e11
--- /dev/null
+++ b/src/client/Abstractions/DateTimeOffsetExtensions.cs
@@ -0,0 +1,36 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using System;
+
+namespace Microsoft.Identity.ServiceEssentials
+{
+ // CAN be made internal and moved to implementation
+
+ ///
+ /// extension methods.
+ ///
+ public static class DateTimeOffsetExtensions
+ {
+ ///
+ /// Adds to .
+ /// If sum of and
+ /// exeeds , the resulting ,
+ /// result will be set to to .
+ ///
+ public static DateTimeOffset AddOrCap(this DateTimeOffset dateTime, TimeSpan timeSpan)
+ {
+ if (dateTime == DateTimeOffset.MaxValue || timeSpan == TimeSpan.MaxValue)
+ return DateTimeOffset.MaxValue;
+
+ // checking only if timeSpan == TimeSpan.MaxValue is unsufficient
+ // as jitter might be applied to the timeSpan.
+ // based on: https://referencesource.microsoft.com/#mscorlib/system/timespan.cs,92
+ long result = dateTime.UtcTicks + timeSpan.Ticks;
+ if ((dateTime.UtcTicks >> 63 == timeSpan.Ticks >> 63) && (dateTime.UtcTicks >> 63 != result >> 63))
+ return DateTimeOffset.MaxValue;
+ else
+ return dateTime.Add(timeSpan);
+ }
+ }
+}
diff --git a/src/client/Abstractions/ICacheEntry.cs b/src/client/Abstractions/ICacheEntry.cs
new file mode 100644
index 0000000000..6edc993b98
--- /dev/null
+++ b/src/client/Abstractions/ICacheEntry.cs
@@ -0,0 +1,36 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+namespace Microsoft.Identity.ServiceEssentials
+{
+ ///
+ /// Represents a cache item.
+ ///
+ public interface ICacheEntry
+ {
+ ///
+ /// Gets the value in cache.
+ ///
+ public T Value { get; }
+
+ ///
+ /// Checks if is found in a cache
+ /// and if it is still within its time-to-live.
+ ///
+ ///
+ /// if the is found and
+ /// is still within its time-to-live, otherwise.
+ ///
+ bool IsValid();
+
+ ///
+ /// Checks if is found in a cache
+ /// and can be used as a last-known-good value.
+ ///
+ ///
+ /// if the is found and
+ /// can be used as a last-known-good value, otherwise.
+ ///
+ bool IsValidAsLastKnownGood();
+ }
+}
diff --git a/src/client/Abstractions/ICacheObject.cs b/src/client/Abstractions/ICacheObject.cs
new file mode 100644
index 0000000000..187f33a96c
--- /dev/null
+++ b/src/client/Abstractions/ICacheObject.cs
@@ -0,0 +1,25 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using System;
+
+namespace Microsoft.Identity.ServiceEssentials
+{
+ ///
+ /// Represents an object that can be serialized and deserialized.
+ ///
+ public interface ICacheObject : IEquatable
+ {
+ ///
+ /// Serializes an object into a string.
+ ///
+ /// The serialized value.
+ string Serialize();
+
+ ///
+ /// Deserializes the .
+ ///
+ /// The serialized representation of the object.
+ void Deserialize(string serializedValue);
+ }
+}
diff --git a/src/client/Abstractions/IIdentityCache.cs b/src/client/Abstractions/IIdentityCache.cs
new file mode 100644
index 0000000000..54781ee367
--- /dev/null
+++ b/src/client/Abstractions/IIdentityCache.cs
@@ -0,0 +1,140 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Microsoft.Identity.ServiceEssentials
+{
+ ///
+ /// Represents a cache.
+ ///
+ public interface IIdentityCache
+ {
+ ///
+ /// Gets a with the given key.
+ ///
+ /// The category of the key.
+ /// The key for lookup in cache.
+ ///
+ /// Optional. The used to propagate notifications that the operation should be canceled.
+ ///
+ ///
+ /// Async task that returns the .
+ ///
+ ///
+ /// Caller should utilize to verify that
+ /// is found and withing its time-to-live.
+ ///
+ Task> GetAsync(
+ string category, string key, CancellationToken cancellationToken = default);
+
+ ///
+ /// Gets a with the given key.
+ ///
+ /// The category of the key.
+ /// The key for lookup in cache.
+ ///
+ /// Optional. The used to propagate notifications that the operation should be canceled.
+ ///
+ ///
+ /// Async task that returns the .
+ ///
+ ///
+ /// Caller should utilize to verify that
+ /// is found and withing its time-to-live.
+ /// ///
+ Task> GetAsync(
+ string category, string key, CancellationToken cancellationToken = default);
+
+ ///
+ /// Gets a with the given key.
+ /// If is valid, but should be refreshed,
+ /// will be invoked on a background thread.
+ /// If is not valid, will be invoked
+ /// and resulting will be stored to cache and returned.
+ ///
+ /// The category of the key.
+ /// The key for lookup in cache.
+ ///
+ ///
+ ///
+ /// Optional. The used to propagate notifications that the operation should be canceled.
+ ///
+ ///
+ /// Async task that returns the .
+ ///
+ ///
+ /// Caller should utilize to verify that
+ /// is found and withing its time-to-live.
+ ///
+ Task> GetWithRefreshFunctionAsync(
+ string category, string key, CacheEntryOptions cacheEntryOptions, Func> refreshFunction, CancellationToken cancellationToken = default)
+ where T : ICacheObject, new();
+
+ ///
+ /// Gets a with the given key.
+ /// If is valid, but should be refreshed,
+ /// will be invoked on a background thread.
+ /// If is not valid, will be invoked
+ /// and resulting will be stored to cache and returned.
+ ///
+ /// The category of the key.
+ /// The key for lookup in cache.
+ ///
+ ///
+ ///
+ /// Optional. The used to propagate notifications that the operation should be canceled.
+ ///
+ ///
+ /// Async task that returns the .
+ ///
+ ///
+ /// Caller should utilize to verify that
+ /// is found and withing its time-to-live.
+ /// ///
+ Task> GetWithRefreshFunctionAsync(
+ string category, string key, CacheEntryOptions cacheEntryOptions, Func> refreshFunction, CancellationToken cancellationToken = default);
+
+ ///
+ /// Sets the to the cache.
+ ///
+ /// The category of the key.
+ /// The key for lookup in cache.
+ /// The value to be cached.
+ /// Options applied when creating the .
+ ///
+ /// Optional. The used to propagate notifications that the operation should be canceled.
+ ///
+ /// Async.
+ Task SetAsync(
+ string category, string key, T value, CacheEntryOptions cacheEntryOptions, CancellationToken cancellationToken = default);
+
+ ///
+ /// Sets the to the cache.
+ ///
+ /// The category of the key.
+ /// The key for lookup in cache.
+ /// The value to be cached.
+ /// Options applied when creating the .
+ ///
+ /// Optional. The used to propagate notifications that the operation should be canceled.
+ ///
+ /// Async.
+ Task SetAsync(
+ string category, string key, string value, CacheEntryOptions cacheEntryOptions, CancellationToken cancellationToken = default);
+
+ ///
+ /// Removes an item from the cache with the given key.
+ ///
+ /// The category of the key.
+ /// The key for lookup in cache.
+ ///
+ /// Optional. The used to propagate notifications that the operation should be canceled.
+ ///
+ /// Async.
+ Task RemoveAsync(
+ string category, string key, CancellationToken cancellationToken = default);
+ }
+}
diff --git a/src/client/Abstractions/Implementation/CacheEntry.cs b/src/client/Abstractions/Implementation/CacheEntry.cs
new file mode 100644
index 0000000000..b9fe589898
--- /dev/null
+++ b/src/client/Abstractions/Implementation/CacheEntry.cs
@@ -0,0 +1,111 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using System;
+
+namespace Microsoft.Identity.ServiceEssentials
+{
+ ///
+ internal readonly struct CacheEntry : ICacheEntry, IEquatable>
+ {
+ ///
+ /// Gets the cache entry when this is non existing.
+ ///
+ public static CacheEntry NonExisting => new CacheEntry();
+
+ ///
+ public T Value { get; }
+
+ public DateTimeOffset ExpirationTime { get; }
+
+ public DateTimeOffset LastKnowGoodTime { get; }
+
+ public DateTimeOffset RefreshTime { get; }
+
+ public CacheEntry(T value, DateTimeOffset expirationTime, DateTimeOffset lastKnowGoodTime, DateTimeOffset refreshTime)
+ {
+ Value = value ?? throw new ArgumentNullException(nameof(value));
+ ExpirationTime = expirationTime;
+ LastKnowGoodTime = lastKnowGoodTime;
+ RefreshTime = refreshTime;
+ }
+
+ ///
+ public bool IsValid()
+ {
+ return DateTimeOffset.UtcNow < ExpirationTime;
+ }
+
+ ///
+ public bool IsValidAsLastKnownGood()
+ {
+ return DateTimeOffset.UtcNow < LastKnowGoodTime;
+ }
+
+ ///
+ public bool NeedsRefresh()
+ {
+ return DateTimeOffset.UtcNow >= RefreshTime;
+ }
+
+ ///
+ /// Test for equality.
+ ///
+ /// Left hand side.
+ /// Right hand side.
+ /// true if the current object is equal to the other parameter; otherwise, false.
+ public static bool operator ==(CacheEntry lhs, CacheEntry rhs)
+ {
+ return lhs.Equals(rhs);
+ }
+
+ ///
+ /// Test for inequality.
+ ///
+ /// Left hand side.
+ /// Right hand side.
+ /// true if the current object is not equal to the other parameter; otherwise, false.
+ public static bool operator !=(CacheEntry lhs, CacheEntry rhs)
+ {
+ return !lhs.Equals(rhs);
+ }
+
+ ///
+ /// Generate hashcode.
+ ///
+ /// returns hashcode.
+ public override int GetHashCode()
+ {
+ return (Value?.GetHashCode() ?? 0) ^
+ (ExpirationTime.GetHashCode()) ^
+ (RefreshTime.GetHashCode()) ^
+ (LastKnowGoodTime.GetHashCode());
+ }
+
+ ///
+ /// Checks equality.
+ ///
+ /// Object to compare.
+ /// if equal or not.
+ public override bool Equals(object obj)
+ {
+ if (obj is CacheEntry cacheEntry)
+ return Equals(cacheEntry);
+
+ return false;
+ }
+
+ ///
+ /// Checks equality.
+ ///
+ /// Object to compare.
+ /// if equal or not.
+ public bool Equals(CacheEntry other)
+ {
+ return Equals(Value, other.Value) &&
+ ExpirationTime == other.ExpirationTime &&
+ RefreshTime == other.RefreshTime &&
+ LastKnowGoodTime == other.LastKnowGoodTime;
+ }
+ }
+}
diff --git a/src/client/Abstractions/InternalsVisibleTo.cs b/src/client/Abstractions/InternalsVisibleTo.cs
new file mode 100644
index 0000000000..1233089c53
--- /dev/null
+++ b/src/client/Abstractions/InternalsVisibleTo.cs
@@ -0,0 +1,6 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using System.Runtime.CompilerServices;
+
+[assembly: InternalsVisibleTo("Microsoft.Identity.Client, PublicKey=00240000048000009400000006020000002400005253413100040000010001002D96616729B54F6D013D71559A017F50AA4861487226C523959D1579B93F3FDF71C08B980FD3130062B03D3DE115C4B84E7AC46AEF5E192A40E7457D5F3A08F66CEAB71143807F2C3CB0DA5E23B38F0559769978406F6E5D30CEADD7985FC73A5A609A8B74A1DF0A29399074A003A226C943D480FEC96DBEC7106A87896539AD")]
diff --git a/src/client/Abstractions/Microsoft.Identity.ServiceEssentials.Abstractions.csproj b/src/client/Abstractions/Microsoft.Identity.ServiceEssentials.Abstractions.csproj
new file mode 100644
index 0000000000..1dd08abb1b
--- /dev/null
+++ b/src/client/Abstractions/Microsoft.Identity.ServiceEssentials.Abstractions.csproj
@@ -0,0 +1,15 @@
+
+
+ $(MiseVersion)
+ MISE Abstractions project that contains base components and interfaces.
+ MISE;Pipeline;Abstractions;Host;ServiceEssentials
+ Microsoft.Identity.ServiceEssentials
+ netstandard2.0
+ 9
+
+
+
+
+
+
+
diff --git a/src/client/Microsoft.Identity.Client/AppConfig/CacheOptions.cs b/src/client/Microsoft.Identity.Client/AppConfig/CacheOptions.cs
index 6b5d873327..b63011adf0 100644
--- a/src/client/Microsoft.Identity.Client/AppConfig/CacheOptions.cs
+++ b/src/client/Microsoft.Identity.Client/AppConfig/CacheOptions.cs
@@ -1,6 +1,8 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
+using Microsoft.Identity.ServiceEssentials;
+
namespace Microsoft.Identity.Client
{
///
@@ -39,6 +41,24 @@ public CacheOptions(bool useSharedCache)
UseSharedCache = useSharedCache;
}
+ ///
+ ///
+ ///
+ ///
+ public CacheOptions(IIdentityCache identityCache)
+ {
+ IdentityCache = identityCache;
+ }
+
+ ///
+ ///
+ ///
+ ///
+ public CacheOptions(int sizeLimit)
+ {
+ SizeLimit = sizeLimit;
+ }
+
///
/// Share the cache between all ClientApplication objects. The cache becomes static. Defaults to false.
///
@@ -50,5 +70,14 @@ public CacheOptions(bool useSharedCache)
///
public bool UseSharedCache { get; set; }
+ ///
+ /// User-provided instance of IIdentityCache
+ ///
+ public IIdentityCache IdentityCache { get; }
+
+ ///
+ /// Max count of items in the default in-memory cache with eviction
+ ///
+ public int SizeLimit { get; }
}
}
diff --git a/src/client/Microsoft.Identity.Client/Cache/CacheSessionManager.cs b/src/client/Microsoft.Identity.Client/Cache/CacheSessionManager.cs
index 3849d6f677..350ff15661 100644
--- a/src/client/Microsoft.Identity.Client/Cache/CacheSessionManager.cs
+++ b/src/client/Microsoft.Identity.Client/Cache/CacheSessionManager.cs
@@ -58,7 +58,7 @@ public async Task GetAccountAssociatedWithAccessTokenAsync(MsalAccessTo
public async Task GetIdTokenCacheItemAsync(MsalAccessTokenCacheItem accessTokenCacheItem)
{
await RefreshCacheForReadOperationsAsync().ConfigureAwait(false);
- return TokenCacheInternal.GetIdTokenCacheItem(accessTokenCacheItem);
+ return await TokenCacheInternal.GetIdTokenCacheItemAsync(accessTokenCacheItem).ConfigureAwait(false);
}
public async Task FindFamilyRefreshTokenAsync(string familyId)
@@ -118,15 +118,15 @@ private async Task RefreshCacheForReadOperationsAsync()
var args = new TokenCacheNotificationArgs(
TokenCacheInternal,
_requestParams.AppConfig.ClientId,
- _requestParams.Account,
+ _requestParams.Account,
hasStateChanged: false,
isApplicationCache: TokenCacheInternal.IsApplicationCache,
suggestedCacheKey: key,
hasTokens: TokenCacheInternal.HasTokensNoLocks(),
cancellationToken: _requestParams.RequestContext.UserCancellationToken,
suggestedCacheExpiry: null,
- correlationId: _requestParams.RequestContext.CorrelationId,
- requestScopes: _requestParams.Scope,
+ correlationId: _requestParams.RequestContext.CorrelationId,
+ requestScopes: _requestParams.Scope,
requestTenantId: _requestParams.AuthorityManager.OriginalAuthority.TenantId);
stopwatch.Start();
diff --git a/src/client/Microsoft.Identity.Client/Cache/Prototype/DefaultInMemoryCache.cs b/src/client/Microsoft.Identity.Client/Cache/Prototype/DefaultInMemoryCache.cs
new file mode 100644
index 0000000000..e0365783d9
--- /dev/null
+++ b/src/client/Microsoft.Identity.Client/Cache/Prototype/DefaultInMemoryCache.cs
@@ -0,0 +1,66 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Caching.Memory;
+using Microsoft.Identity.ServiceEssentials;
+
+namespace Microsoft.Identity.Client.Cache.Prototype
+{
+ internal class DefaultInMemoryCache : IIdentityCache
+ {
+ private readonly MemoryCache _memoryCache;
+
+ public DefaultInMemoryCache(CacheOptions cacheOptions)
+ {
+ _memoryCache = new MemoryCache(new MemoryCacheOptions() { SizeLimit = cacheOptions?.SizeLimit ?? 1000 });
+ }
+
+ public Task> GetAsync(string category, string key, CancellationToken cancellationToken = default)
+ {
+ ICacheEntry result = null;
+ _memoryCache?.TryGetValue(key, out result);
+ return Task.FromResult(result);
+ }
+
+ public Task SetAsync(string category, string key, T value, CacheEntryOptions cacheEntryOptions, CancellationToken cancellationToken = default)
+ {
+ var cacheEntry = new CacheEntry(value, DateTimeOffset.UtcNow, DateTimeOffset.UtcNow, DateTimeOffset.UtcNow);
+ var memoryCacheOptions = new MemoryCacheEntryOptions()
+ {
+ AbsoluteExpiration = DateTimeOffset.UtcNow.Add(cacheEntryOptions.TimeToExpire),
+ Size = 1
+ };
+ _memoryCache.Set(key, cacheEntry, memoryCacheOptions);
+ return Task.CompletedTask;
+ }
+
+ #region Not Implemented
+ public Task> GetAsync(string category, string key, CancellationToken cancellationToken = default)
+ {
+ throw new NotImplementedException();
+ }
+
+ public Task> GetWithRefreshFunctionAsync(string category, string key, CacheEntryOptions cacheEntryOptions, Func> refreshFunction, CancellationToken cancellationToken = default) where T : ICacheObject, new()
+ {
+ throw new NotImplementedException();
+ }
+
+ public Task> GetWithRefreshFunctionAsync(string category, string key, CacheEntryOptions cacheEntryOptions, Func> refreshFunction, CancellationToken cancellationToken = default)
+ {
+ throw new NotImplementedException();
+ }
+ public Task RemoveAsync(string category, string key, CancellationToken cancellationToken = default)
+ {
+ throw new NotImplementedException();
+ }
+
+ public Task SetAsync(string category, string key, string value, CacheEntryOptions cacheEntryOptions, CancellationToken cancellationToken = default)
+ {
+ throw new NotImplementedException();
+ }
+ #endregion
+ }
+}
diff --git a/src/client/Microsoft.Identity.Client/Cache/Prototype/IdentityCacheWrapper.cs b/src/client/Microsoft.Identity.Client/Cache/Prototype/IdentityCacheWrapper.cs
new file mode 100644
index 0000000000..92815fb377
--- /dev/null
+++ b/src/client/Microsoft.Identity.Client/Cache/Prototype/IdentityCacheWrapper.cs
@@ -0,0 +1,34 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using System;
+using System.Threading.Tasks;
+using Microsoft.Identity.ServiceEssentials;
+
+namespace Microsoft.Identity.Client.Cache.Prototype
+{
+ internal class IdentityCacheWrapper
+ {
+ internal static IIdentityCache s_iIdentityCache { get; set; }
+
+ internal IdentityCacheWrapper(CacheOptions cacheOptions)
+ {
+ // Set (or overwrite) cache to user-specified implementation, otherwise set to default implementation, if not already set.
+ s_iIdentityCache = cacheOptions?.IdentityCache ?? s_iIdentityCache ?? CreateDefaultCache(cacheOptions);
+ }
+
+ private IIdentityCache CreateDefaultCache(CacheOptions cacheOptions) => new DefaultInMemoryCache(cacheOptions);
+
+ internal async Task GetAsync(string key)
+ {
+ var entry = await s_iIdentityCache.GetAsync(string.Empty, key).ConfigureAwait(false);
+ return entry == null ? default : entry.Value;
+ }
+
+ internal async Task SetAsync(string key, T value, DateTimeOffset? cacheExpiry)
+ {
+ TimeSpan timeToExpire = cacheExpiry.HasValue ? cacheExpiry.Value - DateTimeOffset.UtcNow : TimeSpan.FromHours(1);
+ await s_iIdentityCache.SetAsync(string.Empty, key, value, new CacheEntryOptions(timeToExpire)).ConfigureAwait(false);
+ }
+ }
+}
diff --git a/src/client/Microsoft.Identity.Client/ClientApplicationBase.cs b/src/client/Microsoft.Identity.Client/ClientApplicationBase.cs
index cc41792069..44eabdf2a0 100644
--- a/src/client/Microsoft.Identity.Client/ClientApplicationBase.cs
+++ b/src/client/Microsoft.Identity.Client/ClientApplicationBase.cs
@@ -10,13 +10,14 @@
using Microsoft.Identity.Client.ApiConfig.Parameters;
using Microsoft.Identity.Client.Cache;
using Microsoft.Identity.Client.Cache.CacheImpl;
+using Microsoft.Identity.Client.Cache.Prototype;
using Microsoft.Identity.Client.Core;
using Microsoft.Identity.Client.Internal;
using Microsoft.Identity.Client.Internal.Requests;
using Microsoft.Identity.Client.TelemetryCore.Internal.Events;
using Microsoft.Identity.Client.Utils;
using static Microsoft.Identity.Client.TelemetryCore.Internal.Events.ApiEvent;
-
+
namespace Microsoft.Identity.Client
{
///
@@ -62,18 +63,26 @@ public abstract partial class ClientApplicationBase : IClientApplicationBase
internal ITokenCacheInternal UserTokenCacheInternal { get; }
+ ///
+ /// TokenCache instance for implementation of IIdentityCache
+ ///
+ internal IdentityCacheWrapper IdentityCacheWrapper { get; }
+
internal ClientApplicationBase(ApplicationConfiguration config)
{
ServiceBundle = Internal.ServiceBundle.Create(config);
ICacheSerializationProvider defaultCacheSerialization = ServiceBundle.PlatformProxy.CreateTokenCacheBlobStorage();
+ // For this prototype, legacy cache serialization is disregarded, use user-provided or default IIdentityCacheImplementation.
+ IdentityCacheWrapper = new IdentityCacheWrapper(config.AccessorOptions);
+
if (config.UserTokenLegacyCachePersistenceForTest != null)
{
- UserTokenCacheInternal = new TokenCache(ServiceBundle, config.UserTokenLegacyCachePersistenceForTest, false, defaultCacheSerialization);
+ UserTokenCacheInternal = new TokenCache(ServiceBundle, config.UserTokenLegacyCachePersistenceForTest, false, defaultCacheSerialization, identityCacheWrapper: IdentityCacheWrapper);
}
else
{
- UserTokenCacheInternal = config.UserTokenCacheInternalForTest ?? new TokenCache(ServiceBundle, false, defaultCacheSerialization);
+ UserTokenCacheInternal = config.UserTokenCacheInternalForTest ?? new TokenCache(ServiceBundle, false, defaultCacheSerialization, identityCacheWrapper: IdentityCacheWrapper);
}
}
diff --git a/src/client/Microsoft.Identity.Client/ConfidentialClientApplication.cs b/src/client/Microsoft.Identity.Client/ConfidentialClientApplication.cs
index 3bd44e711a..e9444c7e1e 100644
--- a/src/client/Microsoft.Identity.Client/ConfidentialClientApplication.cs
+++ b/src/client/Microsoft.Identity.Client/ConfidentialClientApplication.cs
@@ -44,9 +44,9 @@ internal ConfidentialClientApplication(
{
GuardMobileFrameworks();
- AppTokenCacheInternal = configuration.AppTokenCacheInternalForTest ?? new TokenCache(ServiceBundle, true);
+ AppTokenCacheInternal = configuration.AppTokenCacheInternalForTest ?? new TokenCache(ServiceBundle, true, identityCacheWrapper: IdentityCacheWrapper);
Certificate = configuration.ClientCredentialCertificate;
-
+
this.ServiceBundle.ApplicationLogger.Verbose($"ConfidentialClientApplication {configuration.GetHashCode()} created");
}
diff --git a/src/client/Microsoft.Identity.Client/ITokenCacheInternal.cs b/src/client/Microsoft.Identity.Client/ITokenCacheInternal.cs
index e8937f5d7d..ee6793eabf 100644
--- a/src/client/Microsoft.Identity.Client/ITokenCacheInternal.cs
+++ b/src/client/Microsoft.Identity.Client/ITokenCacheInternal.cs
@@ -27,7 +27,7 @@ Task> SaveTokenRe
MsalTokenResponse response);
Task FindAccessTokenAsync(AuthenticationRequestParameters requestParams);
- MsalIdTokenCacheItem GetIdTokenCacheItem(MsalAccessTokenCacheItem msalAccessTokenCacheItem);
+ Task GetIdTokenCacheItemAsync(MsalAccessTokenCacheItem msalAccessTokenCacheItem);
///
/// Returns a RT for the request. If familyId is specified, it tries to return the FRT.
diff --git a/src/client/Microsoft.Identity.Client/Microsoft.Identity.Client.csproj b/src/client/Microsoft.Identity.Client/Microsoft.Identity.Client.csproj
index 259cb3d5c3..e6fe8ed00c 100644
--- a/src/client/Microsoft.Identity.Client/Microsoft.Identity.Client.csproj
+++ b/src/client/Microsoft.Identity.Client/Microsoft.Identity.Client.csproj
@@ -9,12 +9,12 @@
- net45
+
@@ -33,7 +33,6 @@
4.7.1
$(MsalClientSemVer)
-
true
@@ -297,4 +296,8 @@
+
+
+
+
diff --git a/src/client/Microsoft.Identity.Client/TokenCache.ITokenCacheInternal.cs b/src/client/Microsoft.Identity.Client/TokenCache.ITokenCacheInternal.cs
index c268373f97..580d9a1ba6 100644
--- a/src/client/Microsoft.Identity.Client/TokenCache.ITokenCacheInternal.cs
+++ b/src/client/Microsoft.Identity.Client/TokenCache.ITokenCacheInternal.cs
@@ -4,7 +4,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
-using System.Diagnostics.Tracing;
using System.Globalization;
using System.Linq;
using System.Text;
@@ -19,6 +18,7 @@
using Microsoft.Identity.Client.Internal;
using Microsoft.Identity.Client.Internal.Requests;
using Microsoft.Identity.Client.OAuth2;
+using Microsoft.Identity.Client.PlatformsCommon.Factories;
using Microsoft.Identity.Client.TelemetryCore.Internal.Events;
using Microsoft.Identity.Client.Utils;
@@ -165,7 +165,7 @@ async Task> IToke
hasTokens: tokenCacheInternal.HasTokensNoLocks(),
suggestedCacheExpiry: null,
cancellationToken: requestParams.RequestContext.UserCancellationToken,
- correlationId: requestParams.RequestContext.CorrelationId,
+ correlationId: requestParams.RequestContext.CorrelationId,
requestScopes: requestParams.Scope,
requestTenantId: requestParams.AuthorityManager.OriginalAuthority.TenantId);
@@ -176,6 +176,8 @@ async Task> IToke
requestParams.RequestContext.ApiEvent.DurationInCacheInMs += sw.ElapsedMilliseconds;
}
+ var accessor = await GetOrCreateAccessorAsync(suggestedWebCacheKey).ConfigureAwait(false);
+
// Don't cache PoP access tokens from broker
if (msalAccessTokenCacheItem != null && !(response.TokenSource == TokenSource.Broker && response.TokenType == Constants.PoPAuthHeaderPrefix))
{
@@ -186,9 +188,10 @@ async Task> IToke
tenantId,
msalAccessTokenCacheItem.ScopeSet,
msalAccessTokenCacheItem.HomeAccountId,
- msalAccessTokenCacheItem.TokenType);
+ msalAccessTokenCacheItem.TokenType,
+ accessor);
- Accessor.SaveAccessToken(msalAccessTokenCacheItem);
+ accessor.SaveAccessToken(msalAccessTokenCacheItem);
}
if (idToken != null)
@@ -196,20 +199,27 @@ async Task> IToke
logger.Info("Saving Id Token and Account in cache ...");
Accessor.SaveIdToken(msalIdTokenCacheItem);
MergeWamAccountIds(msalAccountCacheItem);
- Accessor.SaveAccount(msalAccountCacheItem);
+ accessor.SaveAccount(msalAccountCacheItem);
}
// if server returns the refresh token back, save it in the cache.
if (msalRefreshTokenCacheItem != null)
{
logger.Info("Saving RT in cache...");
- Accessor.SaveRefreshToken(msalRefreshTokenCacheItem);
+ accessor.SaveRefreshToken(msalRefreshTokenCacheItem);
}
UpdateAppMetadata(
requestParams.AppConfig.ClientId,
instanceDiscoveryMetadata.PreferredCache,
- response.FamilyId);
+ response.FamilyId,
+ accessor);
+
+ if (!((ITokenCacheInternal)this).IsAppSubscribedToSerializationEvents())
+ {
+ DateTimeOffset? cacheExpiry = CalculateSuggestedCacheExpiry(accessor, logger);
+ await IdentityCacheWrapper.SetAsync(suggestedWebCacheKey, accessor, cacheExpiry).ConfigureAwait(false);
+ }
SaveToLegacyAdalCache(
requestParams,
@@ -235,7 +245,7 @@ async Task> IToke
hasTokens: tokenCacheInternal.HasTokensNoLocks(),
suggestedCacheExpiry: cacheExpiry,
cancellationToken: requestParams.RequestContext.UserCancellationToken,
- correlationId: requestParams.RequestContext.CorrelationId,
+ correlationId: requestParams.RequestContext.CorrelationId,
requestScopes: requestParams.Scope,
requestTenantId: requestParams.AuthorityManager.OriginalAuthority.TenantId);
@@ -265,7 +275,6 @@ async Task> IToke
//This will run on a background thread to mitigate this.
private void DumpCacheToLogs(AuthenticationRequestParameters requestParameters)
{
-
if (requestParameters.RequestContext.Logger.IsLoggingEnabled(LogLevel.Verbose))
{
var accessTokenCacheItems = Accessor.GetAllAccessTokens();
@@ -388,9 +397,9 @@ private void MergeWamAccountIds(MsalAccountCacheItem msalAccountCacheItem)
var existingWamAccountIds = existingAccount?.WamAccountIds;
msalAccountCacheItem.WamAccountIds.MergeDifferentEntries(existingWamAccountIds);
}
-#endregion
+ #endregion
-#region FindAccessToken
+ #region FindAccessToken
///
/// IMPORTANT: this class is performance critical; any changes must be benchmarked using Microsoft.Identity.Test.Performance.
/// More information about how to test and what data to look for is in https://aka.ms/msal-net-performance-testing.
@@ -414,7 +423,7 @@ async Task ITokenCacheInternal.FindAccessTokenAsync(
string partitionKey = CacheKeyFactory.GetKeyFromRequest(requestParams);
Debug.Assert(partitionKey != null || !requestParams.IsConfidentialClient, "On confidential client, cache must be partitioned.");
- var accessTokens = Accessor.GetAllAccessTokens(partitionKey, logger);
+ var accessTokens = (await GetOrCreateAccessorAsync(partitionKey).ConfigureAwait(false)).GetAllAccessTokens(partitionKey, logger);
requestParams.RequestContext.Logger.Always($"[FindAccessTokenAsync] Discovered {accessTokens.Count} access tokens in cache using partition key: {partitionKey}");
@@ -693,7 +702,7 @@ private MsalAccessTokenCacheItem FilterTokensByPopKeyId(MsalAccessTokenCacheItem
requestKid));
return null;
}
-#endregion
+ #endregion
private void FilterTokensByClientId(List tokenCacheItems) where T : MsalCredentialCacheItemBase
{
@@ -742,7 +751,7 @@ async Task ITokenCacheInternal.FindRefreshTokenAsync(
return null;
var requestKey = CacheKeyFactory.GetKeyFromRequest(requestParams);
- var refreshTokens = Accessor.GetAllRefreshTokens(requestKey);
+ var refreshTokens = (await GetOrCreateAccessorAsync(requestKey).ConfigureAwait(false)).GetAllRefreshTokens(requestKey);
requestParams.RequestContext.Logger.Always($"[FindRefreshTokenAsync] Discovered {refreshTokens.Count} refresh tokens in cache using key: {requestKey}");
if (refreshTokens.Count != 0)
@@ -1050,9 +1059,9 @@ private void UpdateWithAdalAccountsWithoutClientInfo(
}
}
- MsalIdTokenCacheItem ITokenCacheInternal.GetIdTokenCacheItem(MsalAccessTokenCacheItem msalAccessTokenCacheItem)
+ async Task ITokenCacheInternal.GetIdTokenCacheItemAsync(MsalAccessTokenCacheItem msalAccessTokenCacheItem)
{
- var idToken = Accessor.GetIdToken(msalAccessTokenCacheItem);
+ var idToken = (await GetOrCreateAccessorAsync(CacheKeyFactory.GetIdTokenKeyFromCachedItem(msalAccessTokenCacheItem)).ConfigureAwait(false)).GetIdToken(msalAccessTokenCacheItem);
return idToken;
}
@@ -1067,7 +1076,7 @@ private async Task> GetTenantProfilesAsync(
Debug.Assert(homeAccountId != null);
- var idTokenCacheItems = Accessor.GetAllIdTokens(homeAccountId);
+ var idTokenCacheItems = (await GetOrCreateAccessorAsync(homeAccountId).ConfigureAwait(false)).GetAllIdTokens(homeAccountId);
FilterTokensByClientId(idTokenCacheItems);
if (!requestParameters.AppConfig.MultiCloudSupportEnabled)
@@ -1104,7 +1113,7 @@ async Task ITokenCacheInternal.GetAccountAssociatedWithAccessTokenAsync
var tenantProfiles = await GetTenantProfilesAsync(requestParameters, msalAccessTokenCacheItem.HomeAccountId).ConfigureAwait(false);
- var accountCacheItem = Accessor.GetAccount(
+ var accountCacheItem = (await GetOrCreateAccessorAsync(CacheKeyFactory.GetIdTokenKeyFromCachedItem(msalAccessTokenCacheItem)).ConfigureAwait(false)).GetAccount(
new MsalAccountCacheKey(
msalAccessTokenCacheItem.Environment,
msalAccessTokenCacheItem.TenantId,
@@ -1148,13 +1157,13 @@ async Task ITokenCacheInternal.RemoveAccountAsync(IAccount account, Authenticati
correlationId: requestParameters.RequestContext.CorrelationId,
requestScopes: requestParameters.Scope,
requestTenantId: requestParameters.AuthorityManager.OriginalAuthority.TenantId);
-
+
await tokenCacheInternal.OnBeforeAccessAsync(args).ConfigureAwait(false);
await tokenCacheInternal.OnBeforeWriteAsync(args).ConfigureAwait(false);
}
- RemoveAccountInternal(account, requestParameters.RequestContext);
+ RemoveAccountInternalAsync(account, requestParameters.RequestContext);
if (IsLegacyAdalCacheEnabled(requestParameters))
{
CacheFallbackOperations.RemoveAdalUser(
@@ -1203,7 +1212,7 @@ bool ITokenCacheInternal.HasTokensNoLocks()
return Accessor.HasAccessOrRefreshTokens();
}
- internal /* internal for test only */ void RemoveAccountInternal(IAccount account, RequestContext requestContext)
+ internal /* internal for test only */ async Task RemoveAccountInternalAsync(IAccount account, RequestContext requestContext)
{
if (account.HomeAccountId == null)
{
@@ -1213,7 +1222,9 @@ bool ITokenCacheInternal.HasTokensNoLocks()
string partitionKey = account.HomeAccountId.Identifier;
- var refreshTokens = Accessor.GetAllRefreshTokens(partitionKey);
+ var accessor = await GetOrCreateAccessorAsync(partitionKey).ConfigureAwait(false);
+
+ var refreshTokens = accessor.GetAllRefreshTokens(partitionKey);
refreshTokens.RemoveAll(item => !item.HomeAccountId.Equals(account.HomeAccountId.Identifier, StringComparison.OrdinalIgnoreCase));
// To maintain backward compatibility with other MSALs, filter all credentials by clientID if
@@ -1228,12 +1239,12 @@ bool ITokenCacheInternal.HasTokensNoLocks()
foreach (MsalRefreshTokenCacheItem refreshTokenCacheItem in refreshTokens)
{
- Accessor.DeleteRefreshToken(refreshTokenCacheItem);
+ accessor.DeleteRefreshToken(refreshTokenCacheItem);
}
requestContext.Logger.Info($"Deleted {refreshTokens.Count} refresh tokens.");
- var accessTokens = Accessor.GetAllAccessTokens(partitionKey);
+ var accessTokens = accessor.GetAllAccessTokens(partitionKey);
accessTokens.RemoveAll(item => !item.HomeAccountId.Equals(account.HomeAccountId.Identifier, StringComparison.OrdinalIgnoreCase));
if (filterByClientId)
{
@@ -1242,12 +1253,12 @@ bool ITokenCacheInternal.HasTokensNoLocks()
foreach (MsalAccessTokenCacheItem accessTokenCacheItem in accessTokens)
{
- Accessor.DeleteAccessToken(accessTokenCacheItem);
+ accessor.DeleteAccessToken(accessTokenCacheItem);
}
requestContext.Logger.Info($"Deleted {accessTokens.Count} access tokens.");
- var idTokens = Accessor.GetAllIdTokens(partitionKey);
+ var idTokens = accessor.GetAllIdTokens(partitionKey);
idTokens.RemoveAll(item => !item.HomeAccountId.Equals(account.HomeAccountId.Identifier, StringComparison.OrdinalIgnoreCase));
if (filterByClientId)
{
@@ -1256,18 +1267,44 @@ bool ITokenCacheInternal.HasTokensNoLocks()
foreach (MsalIdTokenCacheItem idTokenCacheItem in idTokens)
{
- Accessor.DeleteIdToken(idTokenCacheItem);
+ accessor.DeleteIdToken(idTokenCacheItem);
}
requestContext.Logger.Info($"Deleted {idTokens.Count} ID tokens.");
- var accounts = Accessor.GetAllAccounts(partitionKey);
+ var accounts = accessor.GetAllAccounts(partitionKey);
accounts.RemoveAll(item => !(item.HomeAccountId.Equals(account.HomeAccountId.Identifier, StringComparison.OrdinalIgnoreCase) &&
item.PreferredUsername.Equals(account.Username, StringComparison.OrdinalIgnoreCase)));
foreach (MsalAccountCacheItem accountCacheItem in accounts)
{
- Accessor.DeleteAccount(accountCacheItem);
+ accessor.DeleteAccount(accountCacheItem);
+ }
+
+ if (!((ITokenCacheInternal)this).IsAppSubscribedToSerializationEvents())
+ {
+ DateTimeOffset? cacheExpiry = CalculateSuggestedCacheExpiry(accessor, requestContext.Logger);
+ await IdentityCacheWrapper.SetAsync(partitionKey, accessor, cacheExpiry).ConfigureAwait(false);
+ }
+ }
+
+ private async Task GetOrCreateAccessorAsync(string partitionKey)
+ {
+ // If user set up legacy cache serialization, then use old accessor instance (it would have been populated with tokens)
+ // Otherwise, use IIdentityCache instance, either the user-provided or default.
+ if (((ITokenCacheInternal)this).IsAppSubscribedToSerializationEvents())
+ {
+ return Accessor;
+ }
+ else
+ {
+ var cachedAccessor = await IdentityCacheWrapper.GetAsync(partitionKey).ConfigureAwait(false);
+ if (cachedAccessor == null)
+ {
+ var proxy = ServiceBundle?.PlatformProxy ?? PlatformProxyFactory.CreatePlatformProxy(null);
+ cachedAccessor = proxy.CreateTokenCacheAccessor(ServiceBundle.Config.AccessorOptions, IsAppTokenCache);
+ }
+ return cachedAccessor;
}
}
}
diff --git a/src/client/Microsoft.Identity.Client/TokenCache.cs b/src/client/Microsoft.Identity.Client/TokenCache.cs
index d224edae69..9d61c49fab 100644
--- a/src/client/Microsoft.Identity.Client/TokenCache.cs
+++ b/src/client/Microsoft.Identity.Client/TokenCache.cs
@@ -10,6 +10,7 @@
using Microsoft.Identity.Client.Cache;
using Microsoft.Identity.Client.Cache.CacheImpl;
using Microsoft.Identity.Client.Cache.Items;
+using Microsoft.Identity.Client.Cache.Prototype;
using Microsoft.Identity.Client.Core;
using Microsoft.Identity.Client.Internal;
using Microsoft.Identity.Client.Internal.Requests;
@@ -39,6 +40,9 @@ public sealed partial class TokenCache : ITokenCacheInternal
internal ITokenCacheAccessor Accessor { get; set; }
+
+ internal IdentityCacheWrapper IdentityCacheWrapper { get; set; }
+
internal IServiceBundle ServiceBundle { get; }
internal ILegacyCachePersistence LegacyCachePersistence { get; set; }
@@ -69,7 +73,11 @@ public TokenCache() : this((IServiceBundle)null, false, null)
{
}
- internal TokenCache(IServiceBundle serviceBundle, bool isApplicationTokenCache, ICacheSerializationProvider optionalDefaultSerializer = null)
+ internal TokenCache(
+ IServiceBundle serviceBundle,
+ bool isApplicationTokenCache,
+ ICacheSerializationProvider optionalDefaultSerializer = null,
+ IdentityCacheWrapper identityCacheWrapper = null)
{
if (serviceBundle == null)
throw new ArgumentNullException(nameof(serviceBundle));
@@ -94,6 +102,8 @@ internal TokenCache(IServiceBundle serviceBundle, bool isApplicationTokenCache,
// Must happen last, this code can access things like _accessor and such above.
ServiceBundle = serviceBundle;
+
+ IdentityCacheWrapper = identityCacheWrapper;
}
///
@@ -103,8 +113,9 @@ internal TokenCache(
IServiceBundle serviceBundle,
ILegacyCachePersistence legacyCachePersistenceForTest,
bool isApplicationTokenCache,
- ICacheSerializationProvider optionalDefaultCacheSerializer = null)
- : this(serviceBundle, isApplicationTokenCache, optionalDefaultCacheSerializer)
+ ICacheSerializationProvider optionalDefaultCacheSerializer = null,
+ IdentityCacheWrapper identityCacheWrapper = null)
+ : this(serviceBundle, isApplicationTokenCache, optionalDefaultCacheSerializer, identityCacheWrapper)
{
LegacyCachePersistence = legacyCachePersistenceForTest;
}
@@ -118,12 +129,12 @@ public void SetIosKeychainSecurityGroup(string securityGroup)
#endif
}
- private void UpdateAppMetadata(string clientId, string environment, string familyId)
+ private void UpdateAppMetadata(string clientId, string environment, string familyId, ITokenCacheAccessor accessor)
{
if (_featureFlags.IsFociEnabled)
{
var metadataCacheItem = new MsalAppMetadataCacheItem(clientId, environment, familyId);
- Accessor.SaveAppMetadata(metadataCacheItem);
+ accessor.SaveAppMetadata(metadataCacheItem);
}
}
@@ -138,7 +149,8 @@ private void DeleteAccessTokensWithIntersectingScopes(
string tenantId,
HashSet scopeSet,
string homeAccountId,
- string tokenType)
+ string tokenType,
+ ITokenCacheAccessor accessor = null)
{
if (requestParams.RequestContext.Logger.IsLoggingEnabled(LogLevel.Info))
{
@@ -151,7 +163,7 @@ private void DeleteAccessTokensWithIntersectingScopes(
var partitionKeyFromResponse = CacheKeyFactory.GetInternalPartitionKeyFromResponse(requestParams, homeAccountId);
Debug.Assert(partitionKeyFromResponse != null || !requestParams.IsConfidentialClient, "On confidential client, cache must be partitioned.");
- foreach (var accessToken in Accessor.GetAllAccessTokens(partitionKeyFromResponse))
+ foreach (var accessToken in (accessor ?? Accessor).GetAllAccessTokens(partitionKeyFromResponse))
{
if (accessToken.ClientId.Equals(ClientId, StringComparison.OrdinalIgnoreCase) &&
environmentAliases.Contains(accessToken.Environment) &&
diff --git a/tests/Microsoft.Identity.Test.Unit/CacheTests/UnifiedCacheTests.cs b/tests/Microsoft.Identity.Test.Unit/CacheTests/UnifiedCacheTests.cs
index c9f1f33a41..a86d743641 100644
--- a/tests/Microsoft.Identity.Test.Unit/CacheTests/UnifiedCacheTests.cs
+++ b/tests/Microsoft.Identity.Test.Unit/CacheTests/UnifiedCacheTests.cs
@@ -75,7 +75,7 @@ public void UnifiedCache_MsalStoresToAndReadRtFromAdalCache()
var accounts = app.UserTokenCacheInternal.GetAccountsAsync(reqParams).Result;
foreach (IAccount account in accounts)
{
- (app.UserTokenCacheInternal as TokenCache).RemoveAccountInternal(account, requestContext);
+ (app.UserTokenCacheInternal as TokenCache).RemoveAccountInternalAsync(account, requestContext);
}
Assert.AreEqual(0, httpManager.QueueSize);
diff --git a/tests/devapps/Net5TestApp/CompositeCacheAdapter.cs b/tests/devapps/Net5TestApp/CompositeCacheAdapter.cs
new file mode 100644
index 0000000000..a16187a604
--- /dev/null
+++ b/tests/devapps/Net5TestApp/CompositeCacheAdapter.cs
@@ -0,0 +1,76 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using CompositeCache;
+using Microsoft.Identity.ServiceEssentials;
+
+namespace Net5TestApp
+{
+ public class CompositeCacheAdapter : IIdentityCache
+ {
+ private MemCacheProvider
public static class DateTimeOffsetExtensions
{
+ private static readonly Random _random = new Random();
+
///
- /// Adds to .
- /// If sum of and
- /// exeeds , the resulting ,
- /// result will be set to to .
///
- public static DateTimeOffset AddOrCap(this DateTimeOffset dateTime, TimeSpan timeSpan)
+ public static DateTimeOffset AddOrCap(this DateTimeOffset dateTime, int seconds)
{
- if (dateTime == DateTimeOffset.MaxValue || timeSpan == TimeSpan.MaxValue)
+ var timeSpan = GetRandomSpan(seconds);
+
+ if (dateTime == DateTimeOffset.MaxValue)
return DateTimeOffset.MaxValue;
+ // safeguard
// checking only if timeSpan == TimeSpan.MaxValue is unsufficient
// as jitter might be applied to the timeSpan.
// based on: https://referencesource.microsoft.com/#mscorlib/system/timespan.cs,92
@@ -32,5 +33,15 @@ public static DateTimeOffset AddOrCap(this DateTimeOffset dateTime, TimeSpan tim
else
return dateTime.Add(timeSpan);
}
+
+ private static TimeSpan GetRandomSpan(int jitterSpanInSeconds)
+ {
+ if (jitterSpanInSeconds == 0)
+ return TimeSpan.Zero;
+ else if (jitterSpanInSeconds < 0)
+ return TimeSpan.FromSeconds((long)((_random.NextDouble()) * jitterSpanInSeconds));
+ else
+ return TimeSpan.FromSeconds((long)((_random.NextDouble() * 2.0 - 1.0) * jitterSpanInSeconds));
+ }
}
}
diff --git a/src/client/Abstractions/DistributedCacheEntry.cs b/src/client/Abstractions/DistributedCacheEntry.cs
new file mode 100644
index 0000000000..ee728bc458
--- /dev/null
+++ b/src/client/Abstractions/DistributedCacheEntry.cs
@@ -0,0 +1,40 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using System;
+
+namespace Microsoft.Identity.ServiceEssentials
+{
+ internal class DistributedCacheEntry
+ {
+ public DistributedCacheEntry() { }
+
+ public T Value { get; set; }
+
+ ///
+ ///
+ public DateTimeOffset ExpirationTimeUTC { get; set; }
+
+ ///
+ ///
+ public DateTimeOffset RefreshTimeUTC { get; set; }
+
+ public int MaxCategoryCount { get; set; }
+
+ public int JitterInSeconds { get; set; }
+
+ public void Deserialize(string serializedValue)
+ {
+ _ = serializedValue;
+ _ = MaxCategoryCount;
+ // todo
+ }
+
+ public string Serialize()
+ {
+ _ = MaxCategoryCount;
+ // todo
+ return string.Empty;
+ }
+ }
+}
diff --git a/src/client/Abstractions/ICacheEntry.cs b/src/client/Abstractions/ICacheEntry.cs
deleted file mode 100644
index 6edc993b98..0000000000
--- a/src/client/Abstractions/ICacheEntry.cs
+++ /dev/null
@@ -1,36 +0,0 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
-// Licensed under the MIT License.
-
-namespace Microsoft.Identity.ServiceEssentials
-{
- ///
- /// Represents a cache item.
- ///
- public interface ICacheEntry
- {
- ///
- /// Gets the value in cache.
- ///
- public T Value { get; }
-
- ///
- /// Checks if is found in a cache
- /// and if it is still within its time-to-live.
- ///
- ///
- /// if the is found and
- /// is still within its time-to-live, otherwise.
- ///
- bool IsValid();
-
- ///
- /// Checks if is found in a cache
- /// and can be used as a last-known-good value.
- ///
- ///
- /// if the is found and
- /// can be used as a last-known-good value, otherwise.
- ///
- bool IsValidAsLastKnownGood();
- }
-}
diff --git a/src/client/Abstractions/ICacheObject.cs b/src/client/Abstractions/ICacheObject.cs
index 187f33a96c..02dc38cf55 100644
--- a/src/client/Abstractions/ICacheObject.cs
+++ b/src/client/Abstractions/ICacheObject.cs
@@ -8,7 +8,7 @@ namespace Microsoft.Identity.ServiceEssentials
///
/// Represents an object that can be serialized and deserialized.
///
- public interface ICacheObject : IEquatable
+ public interface ICacheObject
{
///
/// Serializes an object into a string.
diff --git a/src/client/Abstractions/IIdentityCache.cs b/src/client/Abstractions/IIdentityCache.cs
index 54781ee367..8ce323546a 100644
--- a/src/client/Abstractions/IIdentityCache.cs
+++ b/src/client/Abstractions/IIdentityCache.cs
@@ -1,7 +1,6 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
-using System;
using System.Threading;
using System.Threading.Tasks;
@@ -13,7 +12,7 @@ namespace Microsoft.Identity.ServiceEssentials
public interface IIdentityCache
{
///
- /// Gets a with the given key.
+ /// Gets a with the given key.
///
/// The category of the key.
/// The key for lookup in cache.
@@ -21,14 +20,13 @@ public interface IIdentityCache
/// Optional. The used to propagate notifications that the operation should be canceled.
///
///
- /// Async task that returns the .
+ /// Async task that returns the .
///
///
- /// Caller should utilize to verify that
- /// is found and withing its time-to-live.
///
- Task> GetAsync(
- string category, string key, CancellationToken cancellationToken = default);
+ Task> GetAsync(
+ string category, string key, CancellationToken cancellationToken = default)
+ where T : ICacheObject;
///
/// Gets a with the given key.
@@ -39,64 +37,12 @@ Task> GetAsync(
/// Optional. The used to propagate notifications that the operation should be canceled.
///
///
- /// Async task that returns the .
///
///
- /// Caller should utilize to verify that
- /// is found and withing its time-to-live.
/// ///
- Task> GetAsync(
+ Task> GetAsync(
string category, string key, CancellationToken cancellationToken = default);
- ///
- /// Gets a with the given key.
- /// If is valid, but should be refreshed,
- /// will be invoked on a background thread.
- /// If is not valid, will be invoked
- /// and resulting will be stored to cache and returned.
- ///
- /// The category of the key.
- /// The key for lookup in cache.
- ///
- ///
- ///
- /// Optional. The used to propagate notifications that the operation should be canceled.
- ///
- ///
- /// Async task that returns the .
- ///
- ///
- /// Caller should utilize to verify that
- /// is found and withing its time-to-live.
- ///
- Task> GetWithRefreshFunctionAsync(
- string category, string key, CacheEntryOptions cacheEntryOptions, Func> refreshFunction, CancellationToken cancellationToken = default)
- where T : ICacheObject, new();
-
- ///
- /// Gets a with the given key.
- /// If is valid, but should be refreshed,
- /// will be invoked on a background thread.
- /// If is not valid, will be invoked
- /// and resulting will be stored to cache and returned.
- ///
- /// The category of the key.
- /// The key for lookup in cache.
- ///
- ///
- ///
- /// Optional. The used to propagate notifications that the operation should be canceled.
- ///
- ///
- /// Async task that returns the .
- ///
- ///
- /// Caller should utilize to verify that
- /// is found and withing its time-to-live.
- /// ///
- Task> GetWithRefreshFunctionAsync(
- string category, string key, CacheEntryOptions cacheEntryOptions, Func> refreshFunction, CancellationToken cancellationToken = default);
-
///
/// Sets the to the cache.
///
@@ -109,7 +55,8 @@ Task> GetWithRefreshFunctionAsync(
///
/// Async.
Task SetAsync(
- string category, string key, T value, CacheEntryOptions cacheEntryOptions, CancellationToken cancellationToken = default);
+ string category, string key, T value, CacheEntryOptions cacheEntryOptions, CancellationToken cancellationToken = default)
+ where T : ICacheObject;
///
/// Sets the to the cache.
diff --git a/src/client/Abstractions/Implementation/CacheEntry.cs b/src/client/Abstractions/Implementation/CacheEntry.cs
deleted file mode 100644
index b9fe589898..0000000000
--- a/src/client/Abstractions/Implementation/CacheEntry.cs
+++ /dev/null
@@ -1,111 +0,0 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
-// Licensed under the MIT License.
-
-using System;
-
-namespace Microsoft.Identity.ServiceEssentials
-{
- ///
- internal readonly struct CacheEntry : ICacheEntry, IEquatable>
- {
- ///
- /// Gets the cache entry when this is non existing.
- ///
- public static CacheEntry NonExisting => new CacheEntry();
-
- ///
- public T Value { get; }
-
- public DateTimeOffset ExpirationTime { get; }
-
- public DateTimeOffset LastKnowGoodTime { get; }
-
- public DateTimeOffset RefreshTime { get; }
-
- public CacheEntry(T value, DateTimeOffset expirationTime, DateTimeOffset lastKnowGoodTime, DateTimeOffset refreshTime)
- {
- Value = value ?? throw new ArgumentNullException(nameof(value));
- ExpirationTime = expirationTime;
- LastKnowGoodTime = lastKnowGoodTime;
- RefreshTime = refreshTime;
- }
-
- ///
- public bool IsValid()
- {
- return DateTimeOffset.UtcNow < ExpirationTime;
- }
-
- ///
- public bool IsValidAsLastKnownGood()
- {
- return DateTimeOffset.UtcNow < LastKnowGoodTime;
- }
-
- ///
- public bool NeedsRefresh()
- {
- return DateTimeOffset.UtcNow >= RefreshTime;
- }
-
- ///
- /// Test for equality.
- ///
- /// Left hand side.
- /// Right hand side.
- /// true if the current object is equal to the other parameter; otherwise, false.
- public static bool operator ==(CacheEntry lhs, CacheEntry rhs)
- {
- return lhs.Equals(rhs);
- }
-
- ///
- /// Test for inequality.
- ///
- /// Left hand side.
- /// Right hand side.
- /// true if the current object is not equal to the other parameter; otherwise, false.
- public static bool operator !=(CacheEntry lhs, CacheEntry rhs)
- {
- return !lhs.Equals(rhs);
- }
-
- ///
- /// Generate hashcode.
- ///
- /// returns hashcode.
- public override int GetHashCode()
- {
- return (Value?.GetHashCode() ?? 0) ^
- (ExpirationTime.GetHashCode()) ^
- (RefreshTime.GetHashCode()) ^
- (LastKnowGoodTime.GetHashCode());
- }
-
- ///
- /// Checks equality.
- ///
- /// Object to compare.
- /// if equal or not.
- public override bool Equals(object obj)
- {
- if (obj is CacheEntry cacheEntry)
- return Equals(cacheEntry);
-
- return false;
- }
-
- ///
- /// Checks equality.
- ///
- /// Object to compare.
- /// if equal or not.
- public bool Equals(CacheEntry other)
- {
- return Equals(Value, other.Value) &&
- ExpirationTime == other.ExpirationTime &&
- RefreshTime == other.RefreshTime &&
- LastKnowGoodTime == other.LastKnowGoodTime;
- }
- }
-}
diff --git a/src/client/Abstractions/Implementation/TestIdentityCache.cs b/src/client/Abstractions/Implementation/TestIdentityCache.cs
new file mode 100644
index 0000000000..c42cdb919c
--- /dev/null
+++ b/src/client/Abstractions/Implementation/TestIdentityCache.cs
@@ -0,0 +1,221 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Caching.Distributed;
+using Microsoft.Extensions.Caching.Memory;
+using Microsoft.Extensions.Options;
+
+namespace Microsoft.Identity.ServiceEssentials.Implementation
+{
+ ///
+ /// Starting a sample...
+ ///
+ public class TestIdentityCache : IIdentityCache, IDisposable
+ {
+ private bool disposed;
+
+ private readonly Dictionary memoryCaches
+ = new Dictionary(StringComparer.OrdinalIgnoreCase);
+
+ private readonly IDistributedCache _distributedCache;
+
+ // also takes IIdentityLogger, ITelemetryClient
+ // when IDistributedCache is present, takes also IEncryptionProvider
+
+ ///
+ ///
+ ///
+ ///
+ public TestIdentityCache(InMemoryCacheOptions inMemoryCacheOptions)
+ {
+ _ = inMemoryCacheOptions ?? throw new ArgumentNullException(nameof(inMemoryCacheOptions));
+
+ foreach (var option in inMemoryCacheOptions.CategoryOptions)
+ {
+ memoryCaches[option.Key] = new MemoryCache(option.Value);
+ }
+ }
+
+ ///
+ ///
+ ///
+ public TestIdentityCache(IOptions inMemoryCacheOptions)
+ {
+ _ = inMemoryCacheOptions ?? throw new ArgumentNullException(nameof(inMemoryCacheOptions));
+
+ foreach (var option in inMemoryCacheOptions.Value.CategoryOptions)
+ {
+ memoryCaches[option.Key] = new MemoryCache(option.Value);
+ }
+ }
+
+ ///
+ ///
+ ///
+ ///
+ public TestIdentityCache(IOptions inMemoryCacheOptions, IDistributedCache distributedCache)
+ {
+ _ = inMemoryCacheOptions ?? throw new ArgumentNullException(nameof(inMemoryCacheOptions));
+ _distributedCache = distributedCache ?? throw new ArgumentNullException(nameof(distributedCache));
+
+ foreach (var option in inMemoryCacheOptions.Value.CategoryOptions)
+ {
+ memoryCaches[option.Key] = new MemoryCache(option.Value);
+ }
+ }
+
+ ///
+ public async Task> GetAsync(string category, string key, CancellationToken cancellationToken = default) where T : ICacheObject
+ {
+ return await GetAsyncInternalAsync(category, key, cancellationToken).ConfigureAwait(false);
+ }
+
+ ///
+ public async Task> GetAsync(string category, string key, CancellationToken cancellationToken = default)
+ {
+ return await GetAsyncInternalAsync(category, key, cancellationToken).ConfigureAwait(false);
+ }
+
+ private async Task> GetAsyncInternalAsync(string category, string key, CancellationToken cancellationToken)
+ {
+ var cache = GetMemoryCache(category);
+
+ CacheEntry result = null;
+ if (cache?.TryGetValue(key, out result) == true)
+ return result;
+ else if (_distributedCache != null)
+ {
+ var l2CacheValue = await _distributedCache.GetStringAsync(key, cancellationToken).ConfigureAwait(false);
+ if (l2CacheValue == null)
+ return null;
+
+ // todo: decrypt
+
+ DistributedCacheEntry entry = new DistributedCacheEntry();
+ entry.Deserialize(l2CacheValue);
+
+ // propagate to L1
+ SetToMemoryCacheInternal(category, key, entry.Value, new CacheEntryOptions(entry.ExpirationTimeUTC, entry.RefreshTimeUTC, entry.MaxCategoryCount) { JitterInSeconds = entry.JitterInSeconds });
+
+ if (cache?.TryGetValue(key, out result) == true)
+ return result;
+ else
+ return null;
+ }
+
+ return null;
+ }
+
+ ///
+ public async Task RemoveAsync(string category, string key, CancellationToken cancellationToken = default)
+ {
+ var cache = GetMemoryCache(category);
+ cache?.Remove(key);
+ if (_distributedCache != null)
+ await _distributedCache.RemoveAsync(key, cancellationToken).ConfigureAwait(false);
+ }
+
+ ///
+ public async Task SetAsync(string category, string key, T value, CacheEntryOptions cacheEntryOptions, CancellationToken cancellationToken = default) where T : ICacheObject
+ {
+ await SetAsyncInternalAsync(category, key, value, cacheEntryOptions, cancellationToken).ConfigureAwait(false);
+ }
+
+ ///
+ public async Task SetAsync(string category, string key, string value, CacheEntryOptions cacheEntryOptions, CancellationToken cancellationToken = default)
+ {
+ await SetAsyncInternalAsync(category, key, value, cacheEntryOptions, cancellationToken).ConfigureAwait(false);
+ }
+
+ ///
+ public async Task SetAsyncInternalAsync(string category, string key, T value, CacheEntryOptions cacheEntryOptions, CancellationToken cancellationToken)
+ {
+ _ = cacheEntryOptions ?? throw new ArgumentNullException(nameof(cacheEntryOptions));
+ SetToMemoryCacheInternal(category, key, value, cacheEntryOptions);
+
+ if (_distributedCache != null)
+ {
+ // set to L2 too
+ var distributedCacheEntry = new DistributedCacheEntry()
+ {
+ Value = value,
+ ExpirationTimeUTC = cacheEntryOptions.ExpirationTimeUTC,
+ RefreshTimeUTC = cacheEntryOptions.RefreshTimeUTC,
+ MaxCategoryCount = cacheEntryOptions.MaxCategoryCount,
+ JitterInSeconds = cacheEntryOptions.JitterInSeconds
+ };
+ string serializedCacheEntry = distributedCacheEntry.Serialize();
+ // todo: encrypt
+ await _distributedCache.SetStringAsync(key, serializedCacheEntry, cancellationToken).ConfigureAwait(false);
+ }
+ }
+
+ internal void SetToMemoryCacheInternal(string category, string key, T value, CacheEntryOptions cacheEntryOptions)
+ {
+ var cache = GetOrCreateMemoryCache(category, cacheEntryOptions);
+
+ // apply jitter
+ var expirationTime = cacheEntryOptions.ExpirationTimeUTC.AddOrCap(cacheEntryOptions.JitterInSeconds);
+ var refreshTime = cacheEntryOptions.RefreshTimeUTC.AddOrCap(cacheEntryOptions.JitterInSeconds);
+
+ var cacheEntry = new CacheEntry(value, expirationTime, refreshTime);
+ var memoryCacheOptions = new MemoryCacheEntryOptions()
+ {
+ AbsoluteExpiration = expirationTime,
+ Size = 1
+ };
+ cache?.Set(key, cacheEntry, memoryCacheOptions);
+ }
+
+ private MemoryCache GetMemoryCache(string category)
+ {
+ if (memoryCaches.TryGetValue(category, out var cache))
+ return cache;
+
+
+ return null;
+ }
+
+ private MemoryCache GetOrCreateMemoryCache(string category, CacheEntryOptions cacheEntryOptions)
+ {
+ if (memoryCaches.TryGetValue(category, out var cache))
+ return cache;
+
+ memoryCaches[category] = new MemoryCache(new MemoryCacheOptions() { SizeLimit = cacheEntryOptions.MaxCategoryCount });
+
+ return memoryCaches[category];
+ }
+
+ ///
+ public void Dispose()
+ {
+ // Do not change this code. Put cleanup code in Dispose(bool disposing) below.
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ ///
+ /// Dispose pattern.
+ ///
+ /// Whether this is called by user code.
+ protected virtual void Dispose(bool disposing)
+ {
+ if (!disposed)
+ {
+ if (disposing)
+ {
+ foreach (var memoryCache in memoryCaches)
+ {
+ memoryCache.Value.Dispose();
+ }
+ }
+
+ disposed = true;
+ }
+ }
+ }
+}
diff --git a/src/client/Abstractions/InMemoryCacheOptions.cs b/src/client/Abstractions/InMemoryCacheOptions.cs
new file mode 100644
index 0000000000..54ba87d463
--- /dev/null
+++ b/src/client/Abstractions/InMemoryCacheOptions.cs
@@ -0,0 +1,28 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+
+using System;
+using System.Collections.Generic;
+using Microsoft.Extensions.Caching.Memory;
+using Microsoft.Extensions.Options;
+
+namespace Microsoft.Identity.ServiceEssentials
+{
+ ///
+ ///
+ public class InMemoryCacheOptions : IOptions
+ {
+ ///
+ /// category settings
+ ///
+#pragma warning disable CA2227 // Collection properties should be read only
+ public IDictionary CategoryOptions { get; set; } = new Dictionary();
+#pragma warning restore CA2227 // Collection properties should be read only
+
+ ///
+ ///
+ ///
+ InMemoryCacheOptions IOptions.Value => this;
+ }
+}
diff --git a/src/client/Microsoft.Identity.Client/Cache/ITokenCacheAccessor.cs b/src/client/Microsoft.Identity.Client/Cache/ITokenCacheAccessor.cs
index 557a18877f..a5e7cb6a9d 100644
--- a/src/client/Microsoft.Identity.Client/Cache/ITokenCacheAccessor.cs
+++ b/src/client/Microsoft.Identity.Client/Cache/ITokenCacheAccessor.cs
@@ -5,10 +5,11 @@
using Microsoft.Identity.Client.Cache.Items;
using Microsoft.Identity.Client.Cache.Keys;
using Microsoft.Identity.Client.Core;
+using Microsoft.Identity.ServiceEssentials;
namespace Microsoft.Identity.Client.Cache
{
- internal interface ITokenCacheAccessor
+ internal interface ITokenCacheAccessor : ICacheObject
{
void SaveAccessToken(MsalAccessTokenCacheItem item);
diff --git a/src/client/Microsoft.Identity.Client/Cache/Prototype/DefaultInMemoryCache.cs b/src/client/Microsoft.Identity.Client/Cache/Prototype/DefaultInMemoryCache.cs
index e0365783d9..de78e930b5 100644
--- a/src/client/Microsoft.Identity.Client/Cache/Prototype/DefaultInMemoryCache.cs
+++ b/src/client/Microsoft.Identity.Client/Cache/Prototype/DefaultInMemoryCache.cs
@@ -18,19 +18,19 @@ public DefaultInMemoryCache(CacheOptions cacheOptions)
_memoryCache = new MemoryCache(new MemoryCacheOptions() { SizeLimit = cacheOptions?.SizeLimit ?? 1000 });
}
- public Task> GetAsync(string category, string key, CancellationToken cancellationToken = default)
+ public Task> GetAsync(string category, string key, CancellationToken cancellationToken = default) where T : ICacheObject
{
- ICacheEntry result = null;
+ CacheEntry result = null;
_memoryCache?.TryGetValue(key, out result);
return Task.FromResult(result);
}
- public Task SetAsync(string category, string key, T value, CacheEntryOptions cacheEntryOptions, CancellationToken cancellationToken = default)
+ public Task SetAsync(string category, string key, T value, CacheEntryOptions cacheEntryOptions, CancellationToken cancellationToken = default) where T : ICacheObject
{
- var cacheEntry = new CacheEntry(value, DateTimeOffset.UtcNow, DateTimeOffset.UtcNow, DateTimeOffset.UtcNow);
+ var cacheEntry = new CacheEntry(value, cacheEntryOptions.ExpirationTimeUTC, cacheEntryOptions.RefreshTimeUTC);
var memoryCacheOptions = new MemoryCacheEntryOptions()
{
- AbsoluteExpiration = DateTimeOffset.UtcNow.Add(cacheEntryOptions.TimeToExpire),
+ AbsoluteExpiration = cacheEntryOptions.ExpirationTimeUTC,
Size = 1
};
_memoryCache.Set(key, cacheEntry, memoryCacheOptions);
@@ -38,21 +38,11 @@ public Task SetAsync(string category, string key, T value, CacheEntryOptions
}
#region Not Implemented
- public Task> GetAsync(string category, string key, CancellationToken cancellationToken = default)
- {
- throw new NotImplementedException();
- }
-
- public Task> GetWithRefreshFunctionAsync(string category, string key, CacheEntryOptions cacheEntryOptions, Func> refreshFunction, CancellationToken cancellationToken = default) where T : ICacheObject, new()
- {
- throw new NotImplementedException();
- }
-
- public Task> GetWithRefreshFunctionAsync(string category, string key, CacheEntryOptions cacheEntryOptions, Func> refreshFunction, CancellationToken cancellationToken = default)
+ public Task RemoveAsync(string category, string key, CancellationToken cancellationToken = default)
{
throw new NotImplementedException();
}
- public Task RemoveAsync(string category, string key, CancellationToken cancellationToken = default)
+ public Task> GetAsync(string category, string key, CancellationToken cancellationToken = default)
{
throw new NotImplementedException();
}
diff --git a/src/client/Microsoft.Identity.Client/Cache/Prototype/IdentityCacheWrapper.cs b/src/client/Microsoft.Identity.Client/Cache/Prototype/IdentityCacheWrapper.cs
index 92815fb377..ad98a28981 100644
--- a/src/client/Microsoft.Identity.Client/Cache/Prototype/IdentityCacheWrapper.cs
+++ b/src/client/Microsoft.Identity.Client/Cache/Prototype/IdentityCacheWrapper.cs
@@ -2,33 +2,66 @@
// Licensed under the MIT License.
using System;
+using System.Collections.Generic;
using System.Threading.Tasks;
+using Microsoft.Extensions.Caching.Memory;
using Microsoft.Identity.ServiceEssentials;
+using Microsoft.Identity.ServiceEssentials.Implementation;
namespace Microsoft.Identity.Client.Cache.Prototype
{
internal class IdentityCacheWrapper
{
- internal static IIdentityCache s_iIdentityCache { get; set; }
+ private static CacheOptions s_cacheOptions;
+ private readonly IIdentityCache _identityCache;
+ private static readonly Lazy s_defaultIIdentityCache = new Lazy(
+ () => CreateDefaultCache());
+ // This cache instance (whether provided by the user or default one) will only ever be called/used if cache serialization is not enabled.
+ // There are three options for this cache: user-provided, static default, non-static default.
+ // User-provided cache takes precedence.
+ // Default cache is created lazily (since it's possible that token cache serialization is enabled)
internal IdentityCacheWrapper(CacheOptions cacheOptions)
{
+ s_cacheOptions = cacheOptions;
// Set (or overwrite) cache to user-specified implementation, otherwise set to default implementation, if not already set.
- s_iIdentityCache = cacheOptions?.IdentityCache ?? s_iIdentityCache ?? CreateDefaultCache(cacheOptions);
+
+ if (cacheOptions.IdentityCache != null)
+ {
+ _identityCache = cacheOptions?.IdentityCache;
+ }
+ else if (cacheOptions.UseSharedCache)
+ {
+ _identityCache = s_defaultIIdentityCache.Value;
+ }
+ else
+ {
+ _identityCache = CreateDefaultCache();
+ }
}
- private IIdentityCache CreateDefaultCache(CacheOptions cacheOptions) => new DefaultInMemoryCache(cacheOptions);
+ private static IIdentityCache CreateDefaultCache()
+ {
+ var memoryCachesOptions = new InMemoryCacheOptions()
+ {
+ CategoryOptions = new Dictionary()
+ {
+ { "tokens", new MemoryCacheOptions() {SizeLimit = s_cacheOptions.SizeLimit} }
+ }
+ };
+
+ return new TestIdentityCache(memoryCachesOptions);
+ }
- internal async Task GetAsync(string key)
+ internal async Task GetAsync(string key) where T : ICacheObject
{
- var entry = await s_iIdentityCache.GetAsync(string.Empty, key).ConfigureAwait(false);
+ var entry = await _identityCache.GetAsync(string.Empty, key).ConfigureAwait(false);
return entry == null ? default : entry.Value;
}
- internal async Task SetAsync(string key, T value, DateTimeOffset? cacheExpiry)
+ internal async Task SetAsync(string key, T value, DateTimeOffset? cacheExpiry) where T : ICacheObject
{
- TimeSpan timeToExpire = cacheExpiry.HasValue ? cacheExpiry.Value - DateTimeOffset.UtcNow : TimeSpan.FromHours(1);
- await s_iIdentityCache.SetAsync(string.Empty, key, value, new CacheEntryOptions(timeToExpire)).ConfigureAwait(false);
+ await _identityCache.SetAsync(string.Empty, key, value, new CacheEntryOptions(cacheExpiry ?? DateTimeOffset.UtcNow, 1)).ConfigureAwait(false);
}
}
}
diff --git a/src/client/Microsoft.Identity.Client/ClientApplicationBase.cs b/src/client/Microsoft.Identity.Client/ClientApplicationBase.cs
index 44eabdf2a0..d312a53279 100644
--- a/src/client/Microsoft.Identity.Client/ClientApplicationBase.cs
+++ b/src/client/Microsoft.Identity.Client/ClientApplicationBase.cs
@@ -74,7 +74,7 @@ internal ClientApplicationBase(ApplicationConfiguration config)
ICacheSerializationProvider defaultCacheSerialization = ServiceBundle.PlatformProxy.CreateTokenCacheBlobStorage();
// For this prototype, legacy cache serialization is disregarded, use user-provided or default IIdentityCacheImplementation.
- IdentityCacheWrapper = new IdentityCacheWrapper(config.AccessorOptions);
+ IdentityCacheWrapper = new IdentityCacheWrapper(config.AccessorOptions ?? new CacheOptions());
if (config.UserTokenLegacyCachePersistenceForTest != null)
{
diff --git a/src/client/Microsoft.Identity.Client/PlatformsCommon/Shared/InMemoryPartitionedAppTokenCacheAccessor.cs b/src/client/Microsoft.Identity.Client/PlatformsCommon/Shared/InMemoryPartitionedAppTokenCacheAccessor.cs
index 56e9337a0c..d98f666a7a 100644
--- a/src/client/Microsoft.Identity.Client/PlatformsCommon/Shared/InMemoryPartitionedAppTokenCacheAccessor.cs
+++ b/src/client/Microsoft.Identity.Client/PlatformsCommon/Shared/InMemoryPartitionedAppTokenCacheAccessor.cs
@@ -228,5 +228,15 @@ public virtual bool HasAccessOrRefreshTokens()
{
return AccessTokenCacheDictionary.Any(partition => partition.Value.Any(token => !token.Value.IsExpiredWithBuffer()));
}
+
+ public string Serialize()
+ {
+ throw new NotImplementedException();
+ }
+
+ public void Deserialize(string serializedValue)
+ {
+ throw new NotImplementedException();
+ }
}
}
diff --git a/src/client/Microsoft.Identity.Client/PlatformsCommon/Shared/InMemoryPartitionedUserTokenCacheAccessor.cs b/src/client/Microsoft.Identity.Client/PlatformsCommon/Shared/InMemoryPartitionedUserTokenCacheAccessor.cs
index bb72ebd4d4..e412577f4f 100644
--- a/src/client/Microsoft.Identity.Client/PlatformsCommon/Shared/InMemoryPartitionedUserTokenCacheAccessor.cs
+++ b/src/client/Microsoft.Identity.Client/PlatformsCommon/Shared/InMemoryPartitionedUserTokenCacheAccessor.cs
@@ -295,5 +295,15 @@ public virtual bool HasAccessOrRefreshTokens()
return RefreshTokenCacheDictionary.Any(partition => partition.Value.Count > 0) ||
AccessTokenCacheDictionary.Any(partition => partition.Value.Any(token => !token.Value.IsExpiredWithBuffer()));
}
+
+ public string Serialize()
+ {
+ throw new NotImplementedException();
+ }
+
+ public void Deserialize(string serializedValue)
+ {
+ throw new NotImplementedException();
+ }
}
}
diff --git a/src/client/Microsoft.Identity.Client/TokenCache.ITokenCacheInternal.cs b/src/client/Microsoft.Identity.Client/TokenCache.ITokenCacheInternal.cs
index 580d9a1ba6..48a91210c9 100644
--- a/src/client/Microsoft.Identity.Client/TokenCache.ITokenCacheInternal.cs
+++ b/src/client/Microsoft.Identity.Client/TokenCache.ITokenCacheInternal.cs
@@ -1288,6 +1288,8 @@ bool ITokenCacheInternal.HasTokensNoLocks()
}
}
+ // Cache setup is validated to be mutually exclusive -
+ // Token cache serialization is allowed only when WithCacheOptions is not used.
private async Task GetOrCreateAccessorAsync(string partitionKey)
{
// If user set up legacy cache serialization, then use old accessor instance (it would have been populated with tokens)
diff --git a/tests/devapps/Net5TestApp/CompositeCacheAdapter.cs b/tests/devapps/Net5TestApp/CompositeCacheAdapter.cs
index a16187a604..e6c6529cdb 100644
--- a/tests/devapps/Net5TestApp/CompositeCacheAdapter.cs
+++ b/tests/devapps/Net5TestApp/CompositeCacheAdapter.cs
@@ -11,38 +11,32 @@ namespace Net5TestApp
{
public class CompositeCacheAdapter : IIdentityCache
{
- private MemCacheProvider _cache = new();
+ private readonly MemCacheProvider _cache = new();
- public async Task> GetAsync(string category, string key, CancellationToken cancellationToken = default)
+ public async Task> GetAsync(string category, string key, CancellationToken cancellationToken = default) where T : ICacheObject
{
- var cachedResult = await _cache.GetAsync(key).ConfigureAwait(false);
- return cachedResult != null ? new MsalCacheEntry((T)cachedResult.Value) : null;
+ var compositeCacheEntry = await _cache.GetAsync(key).ConfigureAwait(false);
+ return compositeCacheEntry != null ?
+ new Microsoft.Identity.ServiceEssentials.CacheEntry(
+ (T)compositeCacheEntry.Value,
+ compositeCacheEntry.Expiration,
+ compositeCacheEntry.Refresh) :
+ null;
}
- public async Task SetAsync(string category, string key, T value, CacheEntryOptions cacheEntryOptions, CancellationToken cancellationToken = default)
+ public async Task SetAsync(string category, string key, T value, CacheEntryOptions cacheEntryOptions, CancellationToken cancellationToken = default) where T : ICacheObject
{
- var expirationDate = DateTimeOffset.UtcNow.Add(cacheEntryOptions.TimeToExpire);
- var cacheEntry = new CacheEntry(key, value, expirationDate, expirationDate, false);
+ var cacheEntry = new CompositeCache.CacheEntry(key, value, cacheEntryOptions.ExpirationTimeUTC, cacheEntryOptions.ExpirationTimeUTC, false);
await _cache.SetAsync(cacheEntry).ConfigureAwait(false);
- }
- #region Not Implemented
- public Task> GetAsync(string category, string key, CancellationToken cancellationToken = default)
- {
- throw new NotImplementedException();
}
- public Task> GetWithRefreshFunctionAsync(string category, string key, CacheEntryOptions cacheEntryOptions, Func> refreshFunction, CancellationToken cancellationToken = default) where T : ICacheObject, new()
- {
- throw new NotImplementedException();
- }
-
- public Task> GetWithRefreshFunctionAsync(string category, string key, CacheEntryOptions cacheEntryOptions, Func> refreshFunction, CancellationToken cancellationToken = default)
+ #region Not Implemented
+ public Task RemoveAsync(string category, string key, CancellationToken cancellationToken = default)
{
throw new NotImplementedException();
}
-
- public Task RemoveAsync(string category, string key, CancellationToken cancellationToken = default)
+ public Task> GetAsync(string category, string key, CancellationToken cancellationToken = default)
{
throw new NotImplementedException();
}
@@ -53,24 +47,4 @@ public Task SetAsync(string category, string key, string value, CacheEntryOption
}
#endregion
}
-
- public class MsalCacheEntry : ICacheEntry
- {
- public MsalCacheEntry(T value)
- {
- Value = value;
- }
-
- public T Value { get; }
-
- public bool IsValid()
- {
- throw new NotImplementedException();
- }
-
- public bool IsValidAsLastKnownGood()
- {
- throw new NotImplementedException();
- }
- }
}
From 59d77da9649e99e3f98016e03d720ec3cea77adb Mon Sep 17 00:00:00 2001
From: pmaytak <34331512+pmaytak@users.noreply.github.com>
Date: Fri, 29 Jul 2022 12:28:13 -0700
Subject: [PATCH 3/8] Implement new IdentityCache changes and import cache
projects.
---
LibsAndSamples.sln | 91 +++++---
src/client/Abstractions/CacheEntry.cs | 37 ---
src/client/Abstractions/CacheEntryOptions.cs | 54 -----
.../Abstractions/DateTimeOffsetExtensions.cs | 47 ----
.../Abstractions/DistributedCacheEntry.cs | 40 ----
src/client/Abstractions/ICacheObject.cs | 25 --
src/client/Abstractions/IIdentityCache.cs | 87 -------
.../Implementation/TestIdentityCache.cs | 221 ------------------
.../Abstractions/InMemoryCacheOptions.cs | 28 ---
src/client/Abstractions/InternalsVisibleTo.cs | 6 -
...tity.ServiceEssentials.Abstractions.csproj | 15 --
.../Cache/Prototype/DefaultInMemoryCache.cs | 15 +-
.../Cache/Prototype/IdentityCacheWrapper.cs | 22 +-
.../ClientApplicationBase.cs | 2 +-
.../Microsoft.Identity.Client.csproj | 5 +-
...nMemoryPartitionedAppTokenCacheAccessor.cs | 4 +-
...MemoryPartitionedUserTokenCacheAccessor.cs | 4 +-
.../Net5TestApp/CompositeCacheAdapter.cs | 11 +-
18 files changed, 98 insertions(+), 616 deletions(-)
delete mode 100644 src/client/Abstractions/CacheEntry.cs
delete mode 100644 src/client/Abstractions/CacheEntryOptions.cs
delete mode 100644 src/client/Abstractions/DateTimeOffsetExtensions.cs
delete mode 100644 src/client/Abstractions/DistributedCacheEntry.cs
delete mode 100644 src/client/Abstractions/ICacheObject.cs
delete mode 100644 src/client/Abstractions/IIdentityCache.cs
delete mode 100644 src/client/Abstractions/Implementation/TestIdentityCache.cs
delete mode 100644 src/client/Abstractions/InMemoryCacheOptions.cs
delete mode 100644 src/client/Abstractions/InternalsVisibleTo.cs
delete mode 100644 src/client/Abstractions/Microsoft.Identity.ServiceEssentials.Abstractions.csproj
diff --git a/LibsAndSamples.sln b/LibsAndSamples.sln
index d77b384f91..6c5d38907f 100644
--- a/LibsAndSamples.sln
+++ b/LibsAndSamples.sln
@@ -6,7 +6,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{9B0B5396
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Identity.Client", "src\client\Microsoft.Identity.Client\Microsoft.Identity.Client.csproj", "{60117A9B-4BB8-472E-BFFF-52CBF67CA95A}"
ProjectSection(ProjectDependencies) = postProject
- {57AB6744-A9D0-47BD-AEDF-D1B17639996D} = {57AB6744-A9D0-47BD-AEDF-D1B17639996D}
+ {C300F77C-C8B6-4A10-BBF8-29C96497FB51} = {C300F77C-C8B6-4A10-BBF8-29C96497FB51}
+ {EFEF5FA8-0FDA-4B4D-BF57-C65BBC54DAC9} = {EFEF5FA8-0FDA-4B4D-BF57-C65BBC54DAC9}
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "devapps", "devapps", "{34BE693E-3496-45A4-B1D2-D3A0E068EEDB}"
@@ -188,7 +189,9 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Intune-xamarin-Android", "t
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Identity.Client.Broker", "src\client\Microsoft.Identity.Client.Broker\Microsoft.Identity.Client.Broker.csproj", "{6839F934-45F0-4026-8AF3-C3AEFB7D48A9}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Identity.ServiceEssentials.Abstractions", "src\client\Abstractions\Microsoft.Identity.ServiceEssentials.Abstractions.csproj", "{57AB6744-A9D0-47BD-AEDF-D1B17639996D}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Identity.ServiceEssentials.Abstractions", "..\MISE\src\Abstractions\Microsoft.Identity.ServiceEssentials.Abstractions.csproj", "{C300F77C-C8B6-4A10-BBF8-29C96497FB51}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Identity.ServiceEssentials.IdentityCache", "..\MISE\src\IdentityCache\Microsoft.Identity.ServiceEssentials.IdentityCache.csproj", "{EFEF5FA8-0FDA-4B4D-BF57-C65BBC54DAC9}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -1589,34 +1592,62 @@ Global
{6839F934-45F0-4026-8AF3-C3AEFB7D48A9}.Release|x64.Build.0 = Release|Any CPU
{6839F934-45F0-4026-8AF3-C3AEFB7D48A9}.Release|x86.ActiveCfg = Release|Any CPU
{6839F934-45F0-4026-8AF3-C3AEFB7D48A9}.Release|x86.Build.0 = Release|Any CPU
- {57AB6744-A9D0-47BD-AEDF-D1B17639996D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {57AB6744-A9D0-47BD-AEDF-D1B17639996D}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {57AB6744-A9D0-47BD-AEDF-D1B17639996D}.Debug|ARM.ActiveCfg = Debug|Any CPU
- {57AB6744-A9D0-47BD-AEDF-D1B17639996D}.Debug|ARM.Build.0 = Debug|Any CPU
- {57AB6744-A9D0-47BD-AEDF-D1B17639996D}.Debug|ARM64.ActiveCfg = Debug|Any CPU
- {57AB6744-A9D0-47BD-AEDF-D1B17639996D}.Debug|ARM64.Build.0 = Debug|Any CPU
- {57AB6744-A9D0-47BD-AEDF-D1B17639996D}.Debug|iPhone.ActiveCfg = Debug|Any CPU
- {57AB6744-A9D0-47BD-AEDF-D1B17639996D}.Debug|iPhone.Build.0 = Debug|Any CPU
- {57AB6744-A9D0-47BD-AEDF-D1B17639996D}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
- {57AB6744-A9D0-47BD-AEDF-D1B17639996D}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
- {57AB6744-A9D0-47BD-AEDF-D1B17639996D}.Debug|x64.ActiveCfg = Debug|Any CPU
- {57AB6744-A9D0-47BD-AEDF-D1B17639996D}.Debug|x64.Build.0 = Debug|Any CPU
- {57AB6744-A9D0-47BD-AEDF-D1B17639996D}.Debug|x86.ActiveCfg = Debug|Any CPU
- {57AB6744-A9D0-47BD-AEDF-D1B17639996D}.Debug|x86.Build.0 = Debug|Any CPU
- {57AB6744-A9D0-47BD-AEDF-D1B17639996D}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {57AB6744-A9D0-47BD-AEDF-D1B17639996D}.Release|Any CPU.Build.0 = Release|Any CPU
- {57AB6744-A9D0-47BD-AEDF-D1B17639996D}.Release|ARM.ActiveCfg = Release|Any CPU
- {57AB6744-A9D0-47BD-AEDF-D1B17639996D}.Release|ARM.Build.0 = Release|Any CPU
- {57AB6744-A9D0-47BD-AEDF-D1B17639996D}.Release|ARM64.ActiveCfg = Release|Any CPU
- {57AB6744-A9D0-47BD-AEDF-D1B17639996D}.Release|ARM64.Build.0 = Release|Any CPU
- {57AB6744-A9D0-47BD-AEDF-D1B17639996D}.Release|iPhone.ActiveCfg = Release|Any CPU
- {57AB6744-A9D0-47BD-AEDF-D1B17639996D}.Release|iPhone.Build.0 = Release|Any CPU
- {57AB6744-A9D0-47BD-AEDF-D1B17639996D}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
- {57AB6744-A9D0-47BD-AEDF-D1B17639996D}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
- {57AB6744-A9D0-47BD-AEDF-D1B17639996D}.Release|x64.ActiveCfg = Release|Any CPU
- {57AB6744-A9D0-47BD-AEDF-D1B17639996D}.Release|x64.Build.0 = Release|Any CPU
- {57AB6744-A9D0-47BD-AEDF-D1B17639996D}.Release|x86.ActiveCfg = Release|Any CPU
- {57AB6744-A9D0-47BD-AEDF-D1B17639996D}.Release|x86.Build.0 = Release|Any CPU
+ {C300F77C-C8B6-4A10-BBF8-29C96497FB51}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {C300F77C-C8B6-4A10-BBF8-29C96497FB51}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {C300F77C-C8B6-4A10-BBF8-29C96497FB51}.Debug|ARM.ActiveCfg = Debug|Any CPU
+ {C300F77C-C8B6-4A10-BBF8-29C96497FB51}.Debug|ARM.Build.0 = Debug|Any CPU
+ {C300F77C-C8B6-4A10-BBF8-29C96497FB51}.Debug|ARM64.ActiveCfg = Debug|Any CPU
+ {C300F77C-C8B6-4A10-BBF8-29C96497FB51}.Debug|ARM64.Build.0 = Debug|Any CPU
+ {C300F77C-C8B6-4A10-BBF8-29C96497FB51}.Debug|iPhone.ActiveCfg = Debug|Any CPU
+ {C300F77C-C8B6-4A10-BBF8-29C96497FB51}.Debug|iPhone.Build.0 = Debug|Any CPU
+ {C300F77C-C8B6-4A10-BBF8-29C96497FB51}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
+ {C300F77C-C8B6-4A10-BBF8-29C96497FB51}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
+ {C300F77C-C8B6-4A10-BBF8-29C96497FB51}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {C300F77C-C8B6-4A10-BBF8-29C96497FB51}.Debug|x64.Build.0 = Debug|Any CPU
+ {C300F77C-C8B6-4A10-BBF8-29C96497FB51}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {C300F77C-C8B6-4A10-BBF8-29C96497FB51}.Debug|x86.Build.0 = Debug|Any CPU
+ {C300F77C-C8B6-4A10-BBF8-29C96497FB51}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {C300F77C-C8B6-4A10-BBF8-29C96497FB51}.Release|Any CPU.Build.0 = Release|Any CPU
+ {C300F77C-C8B6-4A10-BBF8-29C96497FB51}.Release|ARM.ActiveCfg = Release|Any CPU
+ {C300F77C-C8B6-4A10-BBF8-29C96497FB51}.Release|ARM.Build.0 = Release|Any CPU
+ {C300F77C-C8B6-4A10-BBF8-29C96497FB51}.Release|ARM64.ActiveCfg = Release|Any CPU
+ {C300F77C-C8B6-4A10-BBF8-29C96497FB51}.Release|ARM64.Build.0 = Release|Any CPU
+ {C300F77C-C8B6-4A10-BBF8-29C96497FB51}.Release|iPhone.ActiveCfg = Release|Any CPU
+ {C300F77C-C8B6-4A10-BBF8-29C96497FB51}.Release|iPhone.Build.0 = Release|Any CPU
+ {C300F77C-C8B6-4A10-BBF8-29C96497FB51}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
+ {C300F77C-C8B6-4A10-BBF8-29C96497FB51}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
+ {C300F77C-C8B6-4A10-BBF8-29C96497FB51}.Release|x64.ActiveCfg = Release|Any CPU
+ {C300F77C-C8B6-4A10-BBF8-29C96497FB51}.Release|x64.Build.0 = Release|Any CPU
+ {C300F77C-C8B6-4A10-BBF8-29C96497FB51}.Release|x86.ActiveCfg = Release|Any CPU
+ {C300F77C-C8B6-4A10-BBF8-29C96497FB51}.Release|x86.Build.0 = Release|Any CPU
+ {EFEF5FA8-0FDA-4B4D-BF57-C65BBC54DAC9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {EFEF5FA8-0FDA-4B4D-BF57-C65BBC54DAC9}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {EFEF5FA8-0FDA-4B4D-BF57-C65BBC54DAC9}.Debug|ARM.ActiveCfg = Debug|Any CPU
+ {EFEF5FA8-0FDA-4B4D-BF57-C65BBC54DAC9}.Debug|ARM.Build.0 = Debug|Any CPU
+ {EFEF5FA8-0FDA-4B4D-BF57-C65BBC54DAC9}.Debug|ARM64.ActiveCfg = Debug|Any CPU
+ {EFEF5FA8-0FDA-4B4D-BF57-C65BBC54DAC9}.Debug|ARM64.Build.0 = Debug|Any CPU
+ {EFEF5FA8-0FDA-4B4D-BF57-C65BBC54DAC9}.Debug|iPhone.ActiveCfg = Debug|Any CPU
+ {EFEF5FA8-0FDA-4B4D-BF57-C65BBC54DAC9}.Debug|iPhone.Build.0 = Debug|Any CPU
+ {EFEF5FA8-0FDA-4B4D-BF57-C65BBC54DAC9}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
+ {EFEF5FA8-0FDA-4B4D-BF57-C65BBC54DAC9}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
+ {EFEF5FA8-0FDA-4B4D-BF57-C65BBC54DAC9}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {EFEF5FA8-0FDA-4B4D-BF57-C65BBC54DAC9}.Debug|x64.Build.0 = Debug|Any CPU
+ {EFEF5FA8-0FDA-4B4D-BF57-C65BBC54DAC9}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {EFEF5FA8-0FDA-4B4D-BF57-C65BBC54DAC9}.Debug|x86.Build.0 = Debug|Any CPU
+ {EFEF5FA8-0FDA-4B4D-BF57-C65BBC54DAC9}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {EFEF5FA8-0FDA-4B4D-BF57-C65BBC54DAC9}.Release|Any CPU.Build.0 = Release|Any CPU
+ {EFEF5FA8-0FDA-4B4D-BF57-C65BBC54DAC9}.Release|ARM.ActiveCfg = Release|Any CPU
+ {EFEF5FA8-0FDA-4B4D-BF57-C65BBC54DAC9}.Release|ARM.Build.0 = Release|Any CPU
+ {EFEF5FA8-0FDA-4B4D-BF57-C65BBC54DAC9}.Release|ARM64.ActiveCfg = Release|Any CPU
+ {EFEF5FA8-0FDA-4B4D-BF57-C65BBC54DAC9}.Release|ARM64.Build.0 = Release|Any CPU
+ {EFEF5FA8-0FDA-4B4D-BF57-C65BBC54DAC9}.Release|iPhone.ActiveCfg = Release|Any CPU
+ {EFEF5FA8-0FDA-4B4D-BF57-C65BBC54DAC9}.Release|iPhone.Build.0 = Release|Any CPU
+ {EFEF5FA8-0FDA-4B4D-BF57-C65BBC54DAC9}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
+ {EFEF5FA8-0FDA-4B4D-BF57-C65BBC54DAC9}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
+ {EFEF5FA8-0FDA-4B4D-BF57-C65BBC54DAC9}.Release|x64.ActiveCfg = Release|Any CPU
+ {EFEF5FA8-0FDA-4B4D-BF57-C65BBC54DAC9}.Release|x64.Build.0 = Release|Any CPU
+ {EFEF5FA8-0FDA-4B4D-BF57-C65BBC54DAC9}.Release|x86.ActiveCfg = Release|Any CPU
+ {EFEF5FA8-0FDA-4B4D-BF57-C65BBC54DAC9}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/src/client/Abstractions/CacheEntry.cs b/src/client/Abstractions/CacheEntry.cs
deleted file mode 100644
index a936eb7f2f..0000000000
--- a/src/client/Abstractions/CacheEntry.cs
+++ /dev/null
@@ -1,37 +0,0 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
-// Licensed under the MIT License.
-
-using System;
-
-namespace Microsoft.Identity.ServiceEssentials
-{
- ///
- /// Represents a cache item. Can grow if needed.
- ///
- public class CacheEntry
- {
- ///
- /// Gets the value in cache.
- ///
- public T Value { get; }
-
- ///
- /// TODO: backpropagate ?? max category count? internal DistributedCacheEntry to carry details (we need it, not anyone else)
- ///
- public DateTimeOffset ExpirationTimeUTC { get; }
-
- ///
- ///
- public DateTimeOffset RefreshTimeUTC { get; }
-
- ///
- ///
- ///
- public CacheEntry(T value, DateTimeOffset expirationTimeUTC, DateTimeOffset refreshTimeUTC)
- {
- Value = value;
- ExpirationTimeUTC = expirationTimeUTC;
- RefreshTimeUTC = refreshTimeUTC;
- }
- }
-}
diff --git a/src/client/Abstractions/CacheEntryOptions.cs b/src/client/Abstractions/CacheEntryOptions.cs
deleted file mode 100644
index 207ba39c63..0000000000
--- a/src/client/Abstractions/CacheEntryOptions.cs
+++ /dev/null
@@ -1,54 +0,0 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
-// Licensed under the MIT License.
-
-using System;
-
-namespace Microsoft.Identity.ServiceEssentials
-{
- ///
- /// .
- ///
- public class CacheEntryOptions
- {
- ///
- ///
- public DateTimeOffset ExpirationTimeUTC { get; }
-
- ///
- ///
- public DateTimeOffset RefreshTimeUTC { get; }
-
- ///
- ///
- public bool StoreToLocalCacheOnly { get; set; }
-
- ///
- /// Value that can be used to randomize spans in .
- ///
- ///
- /// Negative value will be used to randomize spans to a maximum of -,
- /// while positive value will be used to randomize spans to a maximum of +-.
- ///
- public int JitterInSeconds { get; set; }
-
- ///
- /// If default was not provided for a category.
- ///
- public int MaxCategoryCount { get; set; }
-
- ///
- ///
- public CacheEntryOptions(DateTimeOffset expirationTimeUTC, int maxCategoryCount) : this(expirationTimeUTC, DateTimeOffset.MaxValue, maxCategoryCount)
- {
- }
-
- ///
- ///
- public CacheEntryOptions(DateTimeOffset expirationTimeUTC, DateTimeOffset refreshTimeUTC, int maxCategoryCount)
- {
- ExpirationTimeUTC = expirationTimeUTC;
- MaxCategoryCount = maxCategoryCount;
- RefreshTimeUTC = refreshTimeUTC;
- }
- }
-}
diff --git a/src/client/Abstractions/DateTimeOffsetExtensions.cs b/src/client/Abstractions/DateTimeOffsetExtensions.cs
deleted file mode 100644
index ee7c57aeeb..0000000000
--- a/src/client/Abstractions/DateTimeOffsetExtensions.cs
+++ /dev/null
@@ -1,47 +0,0 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
-// Licensed under the MIT License.
-
-using System;
-
-namespace Microsoft.Identity.ServiceEssentials
-{
- // CAN be made internal and moved to implementation
-
- ///
- /// extension methods.
- ///
- public static class DateTimeOffsetExtensions
- {
- private static readonly Random _random = new Random();
-
- ///
- ///
- public static DateTimeOffset AddOrCap(this DateTimeOffset dateTime, int seconds)
- {
- var timeSpan = GetRandomSpan(seconds);
-
- if (dateTime == DateTimeOffset.MaxValue)
- return DateTimeOffset.MaxValue;
-
- // safeguard
- // checking only if timeSpan == TimeSpan.MaxValue is unsufficient
- // as jitter might be applied to the timeSpan.
- // based on: https://referencesource.microsoft.com/#mscorlib/system/timespan.cs,92
- long result = dateTime.UtcTicks + timeSpan.Ticks;
- if ((dateTime.UtcTicks >> 63 == timeSpan.Ticks >> 63) && (dateTime.UtcTicks >> 63 != result >> 63))
- return DateTimeOffset.MaxValue;
- else
- return dateTime.Add(timeSpan);
- }
-
- private static TimeSpan GetRandomSpan(int jitterSpanInSeconds)
- {
- if (jitterSpanInSeconds == 0)
- return TimeSpan.Zero;
- else if (jitterSpanInSeconds < 0)
- return TimeSpan.FromSeconds((long)((_random.NextDouble()) * jitterSpanInSeconds));
- else
- return TimeSpan.FromSeconds((long)((_random.NextDouble() * 2.0 - 1.0) * jitterSpanInSeconds));
- }
- }
-}
diff --git a/src/client/Abstractions/DistributedCacheEntry.cs b/src/client/Abstractions/DistributedCacheEntry.cs
deleted file mode 100644
index ee728bc458..0000000000
--- a/src/client/Abstractions/DistributedCacheEntry.cs
+++ /dev/null
@@ -1,40 +0,0 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
-// Licensed under the MIT License.
-
-using System;
-
-namespace Microsoft.Identity.ServiceEssentials
-{
- internal class DistributedCacheEntry
- {
- public DistributedCacheEntry() { }
-
- public T Value { get; set; }
-
- ///
- ///
- public DateTimeOffset ExpirationTimeUTC { get; set; }
-
- ///
- ///
- public DateTimeOffset RefreshTimeUTC { get; set; }
-
- public int MaxCategoryCount { get; set; }
-
- public int JitterInSeconds { get; set; }
-
- public void Deserialize(string serializedValue)
- {
- _ = serializedValue;
- _ = MaxCategoryCount;
- // todo
- }
-
- public string Serialize()
- {
- _ = MaxCategoryCount;
- // todo
- return string.Empty;
- }
- }
-}
diff --git a/src/client/Abstractions/ICacheObject.cs b/src/client/Abstractions/ICacheObject.cs
deleted file mode 100644
index 02dc38cf55..0000000000
--- a/src/client/Abstractions/ICacheObject.cs
+++ /dev/null
@@ -1,25 +0,0 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
-// Licensed under the MIT License.
-
-using System;
-
-namespace Microsoft.Identity.ServiceEssentials
-{
- ///
- /// Represents an object that can be serialized and deserialized.
- ///
- public interface ICacheObject
- {
- ///
- /// Serializes an object into a string.
- ///
- /// The serialized value.
- string Serialize();
-
- ///
- /// Deserializes the .
- ///
- /// The serialized representation of the object.
- void Deserialize(string serializedValue);
- }
-}
diff --git a/src/client/Abstractions/IIdentityCache.cs b/src/client/Abstractions/IIdentityCache.cs
deleted file mode 100644
index 8ce323546a..0000000000
--- a/src/client/Abstractions/IIdentityCache.cs
+++ /dev/null
@@ -1,87 +0,0 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
-// Licensed under the MIT License.
-
-using System.Threading;
-using System.Threading.Tasks;
-
-namespace Microsoft.Identity.ServiceEssentials
-{
- ///
- /// Represents a cache.
- ///
- public interface IIdentityCache
- {
- ///
- /// Gets a with the given key.
- ///
- /// The category of the key.
- /// The key for lookup in cache.
- ///
- /// Optional. The used to propagate notifications that the operation should be canceled.
- ///
- ///
- /// Async task that returns the .
- ///
- ///
- ///
- Task> GetAsync(
- string category, string key, CancellationToken cancellationToken = default)
- where T : ICacheObject;
-
- ///
- /// Gets a with the given key.
- ///
- /// The category of the key.
- /// The key for lookup in cache.
- ///
- /// Optional. The used to propagate notifications that the operation should be canceled.
- ///
- ///
- ///
- ///
- /// ///
- Task> GetAsync(
- string category, string key, CancellationToken cancellationToken = default);
-
- ///
- /// Sets the to the cache.
- ///
- /// The category of the key.
- /// The key for lookup in cache.
- /// The value to be cached.
- /// Options applied when creating the .
- ///
- /// Optional. The used to propagate notifications that the operation should be canceled.
- ///
- /// Async.
- Task SetAsync(
- string category, string key, T value, CacheEntryOptions cacheEntryOptions, CancellationToken cancellationToken = default)
- where T : ICacheObject;
-
- ///
- /// Sets the to the cache.
- ///
- /// The category of the key.
- /// The key for lookup in cache.
- /// The value to be cached.
- /// Options applied when creating the .
- ///
- /// Optional. The used to propagate notifications that the operation should be canceled.
- ///
- /// Async.
- Task SetAsync(
- string category, string key, string value, CacheEntryOptions cacheEntryOptions, CancellationToken cancellationToken = default);
-
- ///
- /// Removes an item from the cache with the given key.
- ///
- /// The category of the key.
- /// The key for lookup in cache.
- ///
- /// Optional. The used to propagate notifications that the operation should be canceled.
- ///
- /// Async.
- Task RemoveAsync(
- string category, string key, CancellationToken cancellationToken = default);
- }
-}
diff --git a/src/client/Abstractions/Implementation/TestIdentityCache.cs b/src/client/Abstractions/Implementation/TestIdentityCache.cs
deleted file mode 100644
index c42cdb919c..0000000000
--- a/src/client/Abstractions/Implementation/TestIdentityCache.cs
+++ /dev/null
@@ -1,221 +0,0 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
-// Licensed under the MIT License.
-
-using System;
-using System.Collections.Generic;
-using System.Threading;
-using System.Threading.Tasks;
-using Microsoft.Extensions.Caching.Distributed;
-using Microsoft.Extensions.Caching.Memory;
-using Microsoft.Extensions.Options;
-
-namespace Microsoft.Identity.ServiceEssentials.Implementation
-{
- ///
- /// Starting a sample...
- ///
- public class TestIdentityCache : IIdentityCache, IDisposable
- {
- private bool disposed;
-
- private readonly Dictionary memoryCaches
- = new Dictionary(StringComparer.OrdinalIgnoreCase);
-
- private readonly IDistributedCache _distributedCache;
-
- // also takes IIdentityLogger, ITelemetryClient
- // when IDistributedCache is present, takes also IEncryptionProvider
-
- ///
- ///
- ///
- ///
- public TestIdentityCache(InMemoryCacheOptions inMemoryCacheOptions)
- {
- _ = inMemoryCacheOptions ?? throw new ArgumentNullException(nameof(inMemoryCacheOptions));
-
- foreach (var option in inMemoryCacheOptions.CategoryOptions)
- {
- memoryCaches[option.Key] = new MemoryCache(option.Value);
- }
- }
-
- ///
- ///
- ///
- public TestIdentityCache(IOptions inMemoryCacheOptions)
- {
- _ = inMemoryCacheOptions ?? throw new ArgumentNullException(nameof(inMemoryCacheOptions));
-
- foreach (var option in inMemoryCacheOptions.Value.CategoryOptions)
- {
- memoryCaches[option.Key] = new MemoryCache(option.Value);
- }
- }
-
- ///
- ///
- ///
- ///
- public TestIdentityCache(IOptions inMemoryCacheOptions, IDistributedCache distributedCache)
- {
- _ = inMemoryCacheOptions ?? throw new ArgumentNullException(nameof(inMemoryCacheOptions));
- _distributedCache = distributedCache ?? throw new ArgumentNullException(nameof(distributedCache));
-
- foreach (var option in inMemoryCacheOptions.Value.CategoryOptions)
- {
- memoryCaches[option.Key] = new MemoryCache(option.Value);
- }
- }
-
- ///
- public async Task> GetAsync(string category, string key, CancellationToken cancellationToken = default) where T : ICacheObject
- {
- return await GetAsyncInternalAsync(category, key, cancellationToken).ConfigureAwait(false);
- }
-
- ///
- public async Task> GetAsync(string category, string key, CancellationToken cancellationToken = default)
- {
- return await GetAsyncInternalAsync(category, key, cancellationToken).ConfigureAwait(false);
- }
-
- private async Task> GetAsyncInternalAsync(string category, string key, CancellationToken cancellationToken)
- {
- var cache = GetMemoryCache(category);
-
- CacheEntry result = null;
- if (cache?.TryGetValue(key, out result) == true)
- return result;
- else if (_distributedCache != null)
- {
- var l2CacheValue = await _distributedCache.GetStringAsync(key, cancellationToken).ConfigureAwait(false);
- if (l2CacheValue == null)
- return null;
-
- // todo: decrypt
-
- DistributedCacheEntry entry = new DistributedCacheEntry();
- entry.Deserialize(l2CacheValue);
-
- // propagate to L1
- SetToMemoryCacheInternal(category, key, entry.Value, new CacheEntryOptions(entry.ExpirationTimeUTC, entry.RefreshTimeUTC, entry.MaxCategoryCount) { JitterInSeconds = entry.JitterInSeconds });
-
- if (cache?.TryGetValue(key, out result) == true)
- return result;
- else
- return null;
- }
-
- return null;
- }
-
- ///
- public async Task RemoveAsync(string category, string key, CancellationToken cancellationToken = default)
- {
- var cache = GetMemoryCache(category);
- cache?.Remove(key);
- if (_distributedCache != null)
- await _distributedCache.RemoveAsync(key, cancellationToken).ConfigureAwait(false);
- }
-
- ///
- public async Task SetAsync(string category, string key, T value, CacheEntryOptions cacheEntryOptions, CancellationToken cancellationToken = default) where T : ICacheObject
- {
- await SetAsyncInternalAsync(category, key, value, cacheEntryOptions, cancellationToken).ConfigureAwait(false);
- }
-
- ///
- public async Task SetAsync(string category, string key, string value, CacheEntryOptions cacheEntryOptions, CancellationToken cancellationToken = default)
- {
- await SetAsyncInternalAsync(category, key, value, cacheEntryOptions, cancellationToken).ConfigureAwait(false);
- }
-
- ///
- public async Task SetAsyncInternalAsync(string category, string key, T value, CacheEntryOptions cacheEntryOptions, CancellationToken cancellationToken)
- {
- _ = cacheEntryOptions ?? throw new ArgumentNullException(nameof(cacheEntryOptions));
- SetToMemoryCacheInternal(category, key, value, cacheEntryOptions);
-
- if (_distributedCache != null)
- {
- // set to L2 too
- var distributedCacheEntry = new DistributedCacheEntry()
- {
- Value = value,
- ExpirationTimeUTC = cacheEntryOptions.ExpirationTimeUTC,
- RefreshTimeUTC = cacheEntryOptions.RefreshTimeUTC,
- MaxCategoryCount = cacheEntryOptions.MaxCategoryCount,
- JitterInSeconds = cacheEntryOptions.JitterInSeconds
- };
- string serializedCacheEntry = distributedCacheEntry.Serialize();
- // todo: encrypt
- await _distributedCache.SetStringAsync(key, serializedCacheEntry, cancellationToken).ConfigureAwait(false);
- }
- }
-
- internal void SetToMemoryCacheInternal(string category, string key, T value, CacheEntryOptions cacheEntryOptions)
- {
- var cache = GetOrCreateMemoryCache(category, cacheEntryOptions);
-
- // apply jitter
- var expirationTime = cacheEntryOptions.ExpirationTimeUTC.AddOrCap(cacheEntryOptions.JitterInSeconds);
- var refreshTime = cacheEntryOptions.RefreshTimeUTC.AddOrCap(cacheEntryOptions.JitterInSeconds);
-
- var cacheEntry = new CacheEntry(value, expirationTime, refreshTime);
- var memoryCacheOptions = new MemoryCacheEntryOptions()
- {
- AbsoluteExpiration = expirationTime,
- Size = 1
- };
- cache?.Set(key, cacheEntry, memoryCacheOptions);
- }
-
- private MemoryCache GetMemoryCache(string category)
- {
- if (memoryCaches.TryGetValue(category, out var cache))
- return cache;
-
-
- return null;
- }
-
- private MemoryCache GetOrCreateMemoryCache(string category, CacheEntryOptions cacheEntryOptions)
- {
- if (memoryCaches.TryGetValue(category, out var cache))
- return cache;
-
- memoryCaches[category] = new MemoryCache(new MemoryCacheOptions() { SizeLimit = cacheEntryOptions.MaxCategoryCount });
-
- return memoryCaches[category];
- }
-
- ///
- public void Dispose()
- {
- // Do not change this code. Put cleanup code in Dispose(bool disposing) below.
- Dispose(true);
- GC.SuppressFinalize(this);
- }
-
- ///
- /// Dispose pattern.
- ///
- /// Whether this is called by user code.
- protected virtual void Dispose(bool disposing)
- {
- if (!disposed)
- {
- if (disposing)
- {
- foreach (var memoryCache in memoryCaches)
- {
- memoryCache.Value.Dispose();
- }
- }
-
- disposed = true;
- }
- }
- }
-}
diff --git a/src/client/Abstractions/InMemoryCacheOptions.cs b/src/client/Abstractions/InMemoryCacheOptions.cs
deleted file mode 100644
index 54ba87d463..0000000000
--- a/src/client/Abstractions/InMemoryCacheOptions.cs
+++ /dev/null
@@ -1,28 +0,0 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
-// Licensed under the MIT License.
-
-
-using System;
-using System.Collections.Generic;
-using Microsoft.Extensions.Caching.Memory;
-using Microsoft.Extensions.Options;
-
-namespace Microsoft.Identity.ServiceEssentials
-{
- ///
- ///
- public class InMemoryCacheOptions : IOptions
- {
- ///
- /// category settings
- ///
-#pragma warning disable CA2227 // Collection properties should be read only
- public IDictionary CategoryOptions { get; set; } = new Dictionary();
-#pragma warning restore CA2227 // Collection properties should be read only
-
- ///
- ///
- ///
- InMemoryCacheOptions IOptions.Value => this;
- }
-}
diff --git a/src/client/Abstractions/InternalsVisibleTo.cs b/src/client/Abstractions/InternalsVisibleTo.cs
deleted file mode 100644
index 1233089c53..0000000000
--- a/src/client/Abstractions/InternalsVisibleTo.cs
+++ /dev/null
@@ -1,6 +0,0 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
-// Licensed under the MIT License.
-
-using System.Runtime.CompilerServices;
-
-[assembly: InternalsVisibleTo("Microsoft.Identity.Client, PublicKey=00240000048000009400000006020000002400005253413100040000010001002D96616729B54F6D013D71559A017F50AA4861487226C523959D1579B93F3FDF71C08B980FD3130062B03D3DE115C4B84E7AC46AEF5E192A40E7457D5F3A08F66CEAB71143807F2C3CB0DA5E23B38F0559769978406F6E5D30CEADD7985FC73A5A609A8B74A1DF0A29399074A003A226C943D480FEC96DBEC7106A87896539AD")]
diff --git a/src/client/Abstractions/Microsoft.Identity.ServiceEssentials.Abstractions.csproj b/src/client/Abstractions/Microsoft.Identity.ServiceEssentials.Abstractions.csproj
deleted file mode 100644
index 1dd08abb1b..0000000000
--- a/src/client/Abstractions/Microsoft.Identity.ServiceEssentials.Abstractions.csproj
+++ /dev/null
@@ -1,15 +0,0 @@
-
-
- $(MiseVersion)
- MISE Abstractions project that contains base components and interfaces.
- MISE;Pipeline;Abstractions;Host;ServiceEssentials
- Microsoft.Identity.ServiceEssentials
- netstandard2.0
- 9
-
-
-
-
-
-
-
diff --git a/src/client/Microsoft.Identity.Client/Cache/Prototype/DefaultInMemoryCache.cs b/src/client/Microsoft.Identity.Client/Cache/Prototype/DefaultInMemoryCache.cs
index de78e930b5..64bcd96fa6 100644
--- a/src/client/Microsoft.Identity.Client/Cache/Prototype/DefaultInMemoryCache.cs
+++ b/src/client/Microsoft.Identity.Client/Cache/Prototype/DefaultInMemoryCache.cs
@@ -18,23 +18,24 @@ public DefaultInMemoryCache(CacheOptions cacheOptions)
_memoryCache = new MemoryCache(new MemoryCacheOptions() { SizeLimit = cacheOptions?.SizeLimit ?? 1000 });
}
- public Task> GetAsync(string category, string key, CancellationToken cancellationToken = default) where T : ICacheObject
+ public Task> GetAsync(string category, string key, CancellationToken cancellationToken = default)
+ where T : ICacheObject
{
CacheEntry result = null;
_memoryCache?.TryGetValue(key, out result);
return Task.FromResult(result);
}
- public Task SetAsync(string category, string key, T value, CacheEntryOptions cacheEntryOptions, CancellationToken cancellationToken = default) where T : ICacheObject
+ public Task> SetAsync(string category, string key, T value, CacheEntryOptions cacheEntryOptions, CancellationToken cancellationToken = default)
+ where T : ICacheObject
{
- var cacheEntry = new CacheEntry(value, cacheEntryOptions.ExpirationTimeUTC, cacheEntryOptions.RefreshTimeUTC);
+ var cacheEntry = new CacheEntry(value, DateTimeOffset.UtcNow.Add(cacheEntryOptions.ExpirationTimeRelativeToNow), DateTimeOffset.UtcNow.Add(cacheEntryOptions.RefreshTimeRelativeToNow));
var memoryCacheOptions = new MemoryCacheEntryOptions()
{
- AbsoluteExpiration = cacheEntryOptions.ExpirationTimeUTC,
+ AbsoluteExpiration = cacheEntry.ExpirationTimeUTC,
Size = 1
};
- _memoryCache.Set(key, cacheEntry, memoryCacheOptions);
- return Task.CompletedTask;
+ return Task.FromResult(_memoryCache.Set(key, cacheEntry, memoryCacheOptions));
}
#region Not Implemented
@@ -47,7 +48,7 @@ public Task> GetAsync(string category, string key, Cancellati
throw new NotImplementedException();
}
- public Task SetAsync(string category, string key, string value, CacheEntryOptions cacheEntryOptions, CancellationToken cancellationToken = default)
+ public Task> SetAsync(string category, string key, string value, CacheEntryOptions cacheEntryOptions, CancellationToken cancellationToken = default)
{
throw new NotImplementedException();
}
diff --git a/src/client/Microsoft.Identity.Client/Cache/Prototype/IdentityCacheWrapper.cs b/src/client/Microsoft.Identity.Client/Cache/Prototype/IdentityCacheWrapper.cs
index ad98a28981..6b8bc78ef7 100644
--- a/src/client/Microsoft.Identity.Client/Cache/Prototype/IdentityCacheWrapper.cs
+++ b/src/client/Microsoft.Identity.Client/Cache/Prototype/IdentityCacheWrapper.cs
@@ -4,9 +4,9 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
-using Microsoft.Extensions.Caching.Memory;
using Microsoft.Identity.ServiceEssentials;
-using Microsoft.Identity.ServiceEssentials.Implementation;
+using Microsoft.Identity.ServiceEssentials.IdentityCache;
+using Microsoft.IdentityModel.Abstractions;
namespace Microsoft.Identity.Client.Cache.Prototype
{
@@ -14,16 +14,19 @@ internal class IdentityCacheWrapper
{
private static CacheOptions s_cacheOptions;
private readonly IIdentityCache _identityCache;
+ private static IIdentityLogger _identityLogger;
private static readonly Lazy s_defaultIIdentityCache = new Lazy(
() => CreateDefaultCache());
+ private const string CategoryName = "tokens";
// This cache instance (whether provided by the user or default one) will only ever be called/used if cache serialization is not enabled.
// There are three options for this cache: user-provided, static default, non-static default.
// User-provided cache takes precedence.
// Default cache is created lazily (since it's possible that token cache serialization is enabled)
- internal IdentityCacheWrapper(CacheOptions cacheOptions)
+ internal IdentityCacheWrapper(CacheOptions cacheOptions, IIdentityLogger identityLogger)
{
s_cacheOptions = cacheOptions;
+ _identityLogger = identityLogger;
// Set (or overwrite) cache to user-specified implementation, otherwise set to default implementation, if not already set.
if (cacheOptions.IdentityCache != null)
@@ -42,26 +45,27 @@ internal IdentityCacheWrapper(CacheOptions cacheOptions)
private static IIdentityCache CreateDefaultCache()
{
- var memoryCachesOptions = new InMemoryCacheOptions()
+ var memoryCacheOptions = new InMemoryCacheOptions()
{
- CategoryOptions = new Dictionary()
+ MaxNumberOfItemsForCategory = new Dictionary()
{
- { "tokens", new MemoryCacheOptions() {SizeLimit = s_cacheOptions.SizeLimit} }
+ { CategoryName, s_cacheOptions.SizeLimit },
}
};
- return new TestIdentityCache(memoryCachesOptions);
+ return new IdentityCachePrototype(memoryCacheOptions, _identityLogger, null);
}
internal async Task GetAsync(string key) where T : ICacheObject
{
- var entry = await _identityCache.GetAsync(string.Empty, key).ConfigureAwait(false);
+ var entry = await _identityCache.GetAsync(CategoryName, key).ConfigureAwait(false);
return entry == null ? default : entry.Value;
}
internal async Task SetAsync(string key, T value, DateTimeOffset? cacheExpiry) where T : ICacheObject
{
- await _identityCache.SetAsync(string.Empty, key, value, new CacheEntryOptions(cacheExpiry ?? DateTimeOffset.UtcNow, 1)).ConfigureAwait(false);
+ TimeSpan expirationTimeRelativeToNow = cacheExpiry.HasValue ? cacheExpiry.Value - DateTimeOffset.UtcNow : TimeSpan.FromHours(1);
+ await _identityCache.SetAsync(CategoryName, key, value, new CacheEntryOptions(expirationTimeRelativeToNow, 1)).ConfigureAwait(false);
}
}
}
diff --git a/src/client/Microsoft.Identity.Client/ClientApplicationBase.cs b/src/client/Microsoft.Identity.Client/ClientApplicationBase.cs
index d312a53279..eb9692f828 100644
--- a/src/client/Microsoft.Identity.Client/ClientApplicationBase.cs
+++ b/src/client/Microsoft.Identity.Client/ClientApplicationBase.cs
@@ -74,7 +74,7 @@ internal ClientApplicationBase(ApplicationConfiguration config)
ICacheSerializationProvider defaultCacheSerialization = ServiceBundle.PlatformProxy.CreateTokenCacheBlobStorage();
// For this prototype, legacy cache serialization is disregarded, use user-provided or default IIdentityCacheImplementation.
- IdentityCacheWrapper = new IdentityCacheWrapper(config.AccessorOptions ?? new CacheOptions());
+ IdentityCacheWrapper = new IdentityCacheWrapper(config.AccessorOptions ?? new CacheOptions(), ServiceBundle.Config.IdentityLogger);
if (config.UserTokenLegacyCachePersistenceForTest != null)
{
diff --git a/src/client/Microsoft.Identity.Client/Microsoft.Identity.Client.csproj b/src/client/Microsoft.Identity.Client/Microsoft.Identity.Client.csproj
index e6fe8ed00c..ea3eee01f4 100644
--- a/src/client/Microsoft.Identity.Client/Microsoft.Identity.Client.csproj
+++ b/src/client/Microsoft.Identity.Client/Microsoft.Identity.Client.csproj
@@ -294,10 +294,11 @@
-
+
-
+
+
diff --git a/src/client/Microsoft.Identity.Client/PlatformsCommon/Shared/InMemoryPartitionedAppTokenCacheAccessor.cs b/src/client/Microsoft.Identity.Client/PlatformsCommon/Shared/InMemoryPartitionedAppTokenCacheAccessor.cs
index d98f666a7a..e4b24c714e 100644
--- a/src/client/Microsoft.Identity.Client/PlatformsCommon/Shared/InMemoryPartitionedAppTokenCacheAccessor.cs
+++ b/src/client/Microsoft.Identity.Client/PlatformsCommon/Shared/InMemoryPartitionedAppTokenCacheAccessor.cs
@@ -229,12 +229,12 @@ public virtual bool HasAccessOrRefreshTokens()
return AccessTokenCacheDictionary.Any(partition => partition.Value.Any(token => !token.Value.IsExpiredWithBuffer()));
}
- public string Serialize()
+ public byte[] Serialize()
{
throw new NotImplementedException();
}
- public void Deserialize(string serializedValue)
+ public void Deserialize(byte[] serializedValue)
{
throw new NotImplementedException();
}
diff --git a/src/client/Microsoft.Identity.Client/PlatformsCommon/Shared/InMemoryPartitionedUserTokenCacheAccessor.cs b/src/client/Microsoft.Identity.Client/PlatformsCommon/Shared/InMemoryPartitionedUserTokenCacheAccessor.cs
index e412577f4f..43d21ec023 100644
--- a/src/client/Microsoft.Identity.Client/PlatformsCommon/Shared/InMemoryPartitionedUserTokenCacheAccessor.cs
+++ b/src/client/Microsoft.Identity.Client/PlatformsCommon/Shared/InMemoryPartitionedUserTokenCacheAccessor.cs
@@ -296,12 +296,12 @@ public virtual bool HasAccessOrRefreshTokens()
AccessTokenCacheDictionary.Any(partition => partition.Value.Any(token => !token.Value.IsExpiredWithBuffer()));
}
- public string Serialize()
+ public byte[] Serialize()
{
throw new NotImplementedException();
}
- public void Deserialize(string serializedValue)
+ public void Deserialize(byte[] serializedValue)
{
throw new NotImplementedException();
}
diff --git a/tests/devapps/Net5TestApp/CompositeCacheAdapter.cs b/tests/devapps/Net5TestApp/CompositeCacheAdapter.cs
index e6c6529cdb..948f8f6e79 100644
--- a/tests/devapps/Net5TestApp/CompositeCacheAdapter.cs
+++ b/tests/devapps/Net5TestApp/CompositeCacheAdapter.cs
@@ -4,7 +4,6 @@
using System;
using System.Threading;
using System.Threading.Tasks;
-using CompositeCache;
using Microsoft.Identity.ServiceEssentials;
namespace Net5TestApp
@@ -24,11 +23,17 @@ public class CompositeCacheAdapter : IIdentityCache
null;
}
- public async Task SetAsync(string category, string key, T value, CacheEntryOptions cacheEntryOptions, CancellationToken cancellationToken = default) where T : ICacheObject
+ public async Task> SetAsync(string category, string key, T value, CacheEntryOptions cacheEntryOptions, CancellationToken cancellationToken = default) where T : ICacheObject
{
- var cacheEntry = new CompositeCache.CacheEntry(key, value, cacheEntryOptions.ExpirationTimeUTC, cacheEntryOptions.ExpirationTimeUTC, false);
+ var cacheEntry = new CompositeCache.CacheEntry(
+ key,
+ value,
+ DateTimeOffset.UtcNow.Add(cacheEntryOptions.ExpirationTimeRelativeToNow),
+ DateTimeOffset.UtcNow.Add(cacheEntryOptions.ExpirationTimeRelativeToNow),
+ false);
await _cache.SetAsync(cacheEntry).ConfigureAwait(false);
+ return new CacheEntry(value, DateTimeOffset.UtcNow.Add(cacheEntryOptions.ExpirationTimeRelativeToNow), DateTimeOffset.UtcNow.Add(cacheEntryOptions.RefreshTimeRelativeToNow));
}
#region Not Implemented
From f1a226a5b4c1b266eef21cdd9b89e5a64815aae9 Mon Sep 17 00:00:00 2001
From: pmaytak <34331512+pmaytak@users.noreply.github.com>
Date: Mon, 3 Oct 2022 23:47:20 -0700
Subject: [PATCH 4/8] Typo.
---
tests/devapps/Net5TestApp/Program.cs | 9 ++++++---
1 file changed, 6 insertions(+), 3 deletions(-)
diff --git a/tests/devapps/Net5TestApp/Program.cs b/tests/devapps/Net5TestApp/Program.cs
index 8e1cee81be..ef94efad23 100644
--- a/tests/devapps/Net5TestApp/Program.cs
+++ b/tests/devapps/Net5TestApp/Program.cs
@@ -1,4 +1,7 @@
-using System;
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using System;
using System.Threading.Tasks;
using Microsoft.Identity.Client;
using Microsoft.Identity.Test.LabInfrastructure;
@@ -7,8 +10,8 @@ namespace Net5TestApp
{
class Program
{
- private const string clientIdCCA = "16dab2ba-145d-4b1b-8569-bf4b9aed4dc8";
- private const string thumbprint = "4E87313FD450985A10BC0F14A292859F2DCD6CD3";
+ private const string clientIdCCA = "";
+ private const string thumbprint = "";
private static readonly string authorityA = $"https://login.microsoftonline.com/organizations";
private const string scopeGraphDefault = "https://graph.microsoft.com//.default";
From 8a3fd9061367979f2dbb4d117c804cdcf869ef0e Mon Sep 17 00:00:00 2001
From: pmaytak <34331512+pmaytak@users.noreply.github.com>
Date: Thu, 6 Oct 2022 23:13:09 -0700
Subject: [PATCH 5/8] Update the integration with common cache to store
concrete ITokenCacheAccessor types.
---
.../Cache/Prototype/DefaultInMemoryCache.cs | 6 +-
.../Cache/Prototype/IdentityCacheWrapper.cs | 4 +-
...nMemoryPartitionedAppTokenCacheAccessor.cs | 8 ++-
...MemoryPartitionedUserTokenCacheAccessor.cs | 8 ++-
.../TokenCache.ITokenCacheInternal.cs | 62 ++++++++++++++-----
5 files changed, 68 insertions(+), 20 deletions(-)
diff --git a/src/client/Microsoft.Identity.Client/Cache/Prototype/DefaultInMemoryCache.cs b/src/client/Microsoft.Identity.Client/Cache/Prototype/DefaultInMemoryCache.cs
index 64bcd96fa6..f68e7999b9 100644
--- a/src/client/Microsoft.Identity.Client/Cache/Prototype/DefaultInMemoryCache.cs
+++ b/src/client/Microsoft.Identity.Client/Cache/Prototype/DefaultInMemoryCache.cs
@@ -1,6 +1,7 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
+#if DO_NOT_COMPILE
using System;
using System.Threading;
using System.Threading.Tasks;
@@ -38,7 +39,7 @@ public Task> SetAsync(string category, string key, T value, Cac
return Task.FromResult(_memoryCache.Set(key, cacheEntry, memoryCacheOptions));
}
- #region Not Implemented
+#region Not Implemented
public Task RemoveAsync(string category, string key, CancellationToken cancellationToken = default)
{
throw new NotImplementedException();
@@ -52,6 +53,7 @@ public Task> SetAsync(string category, string key, string val
{
throw new NotImplementedException();
}
- #endregion
+#endregion
}
}
+#endif
diff --git a/src/client/Microsoft.Identity.Client/Cache/Prototype/IdentityCacheWrapper.cs b/src/client/Microsoft.Identity.Client/Cache/Prototype/IdentityCacheWrapper.cs
index 6b8bc78ef7..0372c57723 100644
--- a/src/client/Microsoft.Identity.Client/Cache/Prototype/IdentityCacheWrapper.cs
+++ b/src/client/Microsoft.Identity.Client/Cache/Prototype/IdentityCacheWrapper.cs
@@ -56,13 +56,13 @@ private static IIdentityCache CreateDefaultCache()
return new IdentityCachePrototype(memoryCacheOptions, _identityLogger, null);
}
- internal async Task GetAsync(string key) where T : ICacheObject
+ internal async Task GetAsync(string key) where T : ICacheObject, new()
{
var entry = await _identityCache.GetAsync(CategoryName, key).ConfigureAwait(false);
return entry == null ? default : entry.Value;
}
- internal async Task SetAsync(string key, T value, DateTimeOffset? cacheExpiry) where T : ICacheObject
+ internal async Task SetAsync(string key, T value, DateTimeOffset? cacheExpiry) where T : ICacheObject, new()
{
TimeSpan expirationTimeRelativeToNow = cacheExpiry.HasValue ? cacheExpiry.Value - DateTimeOffset.UtcNow : TimeSpan.FromHours(1);
await _identityCache.SetAsync(CategoryName, key, value, new CacheEntryOptions(expirationTimeRelativeToNow, 1)).ConfigureAwait(false);
diff --git a/src/client/Microsoft.Identity.Client/PlatformsCommon/Shared/InMemoryPartitionedAppTokenCacheAccessor.cs b/src/client/Microsoft.Identity.Client/PlatformsCommon/Shared/InMemoryPartitionedAppTokenCacheAccessor.cs
index edfb195eef..9ee802741d 100644
--- a/src/client/Microsoft.Identity.Client/PlatformsCommon/Shared/InMemoryPartitionedAppTokenCacheAccessor.cs
+++ b/src/client/Microsoft.Identity.Client/PlatformsCommon/Shared/InMemoryPartitionedAppTokenCacheAccessor.cs
@@ -10,6 +10,7 @@
using Microsoft.Identity.Client.Cache.Keys;
using Microsoft.Identity.Client.Core;
using Microsoft.Identity.Client.Utils;
+using Microsoft.Identity.ServiceEssentials;
namespace Microsoft.Identity.Client.PlatformsCommon.Shared
{
@@ -19,7 +20,7 @@ namespace Microsoft.Identity.Client.PlatformsCommon.Shared
/// App metadata collection is not partitioned.
/// Refresh token, ID token, and account related methods are no-op.
///
- internal class InMemoryPartitionedAppTokenCacheAccessor : ITokenCacheAccessor
+ internal class InMemoryPartitionedAppTokenCacheAccessor : ITokenCacheAccessor, ICacheObject
{
// perf: do not use ConcurrentDictionary.Values as it takes a lock
// internal for test only
@@ -35,6 +36,11 @@ internal class InMemoryPartitionedAppTokenCacheAccessor : ITokenCacheAccessor
protected readonly ILoggerAdapter _logger;
private readonly CacheOptions _tokenCacheAccessorOptions;
+ public InMemoryPartitionedAppTokenCacheAccessor()
+ {
+
+ }
+
public InMemoryPartitionedAppTokenCacheAccessor(
ILoggerAdapter logger,
CacheOptions tokenCacheAccessorOptions)
diff --git a/src/client/Microsoft.Identity.Client/PlatformsCommon/Shared/InMemoryPartitionedUserTokenCacheAccessor.cs b/src/client/Microsoft.Identity.Client/PlatformsCommon/Shared/InMemoryPartitionedUserTokenCacheAccessor.cs
index 76f5bfb4a7..2728457247 100644
--- a/src/client/Microsoft.Identity.Client/PlatformsCommon/Shared/InMemoryPartitionedUserTokenCacheAccessor.cs
+++ b/src/client/Microsoft.Identity.Client/PlatformsCommon/Shared/InMemoryPartitionedUserTokenCacheAccessor.cs
@@ -10,6 +10,7 @@
using Microsoft.Identity.Client.Cache.Keys;
using Microsoft.Identity.Client.Core;
using Microsoft.Identity.Client.Utils;
+using Microsoft.Identity.ServiceEssentials;
namespace Microsoft.Identity.Client.PlatformsCommon.Shared
{
@@ -19,7 +20,7 @@ namespace Microsoft.Identity.Client.PlatformsCommon.Shared
/// Partitions the ID token and account collections by home account ID.
/// App metadata collection is not partitioned.
///
- internal class InMemoryPartitionedUserTokenCacheAccessor : ITokenCacheAccessor
+ internal class InMemoryPartitionedUserTokenCacheAccessor : ITokenCacheAccessor, ICacheObject
{
// perf: do not use ConcurrentDictionary.Values as it takes a lock
// internal for test only
@@ -44,6 +45,11 @@ internal class InMemoryPartitionedUserTokenCacheAccessor : ITokenCacheAccessor
protected readonly ILoggerAdapter _logger;
private readonly CacheOptions _tokenCacheAccessorOptions;
+ public InMemoryPartitionedUserTokenCacheAccessor()
+ {
+
+ }
+
public InMemoryPartitionedUserTokenCacheAccessor(ILoggerAdapter logger, CacheOptions tokenCacheAccessorOptions)
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
diff --git a/src/client/Microsoft.Identity.Client/TokenCache.ITokenCacheInternal.cs b/src/client/Microsoft.Identity.Client/TokenCache.ITokenCacheInternal.cs
index 1ef53364ac..061f2ed1b6 100644
--- a/src/client/Microsoft.Identity.Client/TokenCache.ITokenCacheInternal.cs
+++ b/src/client/Microsoft.Identity.Client/TokenCache.ITokenCacheInternal.cs
@@ -19,6 +19,7 @@
using Microsoft.Identity.Client.Internal.Requests;
using Microsoft.Identity.Client.OAuth2;
using Microsoft.Identity.Client.PlatformsCommon.Factories;
+using Microsoft.Identity.Client.PlatformsCommon.Shared;
using Microsoft.Identity.Client.TelemetryCore.Internal.Events;
using Microsoft.Identity.Client.Utils;
@@ -187,7 +188,7 @@ async Task> IToke
requestParams.RequestContext.ApiEvent.DurationInCacheInMs += sw.ElapsedMilliseconds;
}
- var accessor = await GetOrCreateAccessorAsync(suggestedWebCacheKey).ConfigureAwait(false);
+ var accessor = await GetOrCreateAccessorAsync(suggestedWebCacheKey, requestParams).ConfigureAwait(false);
// Don't cache PoP access tokens from broker
if (msalAccessTokenCacheItem != null && !(response.TokenSource == TokenSource.Broker && response.TokenType == Constants.PoPAuthHeaderPrefix))
@@ -229,7 +230,14 @@ async Task> IToke
if (!((ITokenCacheInternal)this).IsAppSubscribedToSerializationEvents())
{
DateTimeOffset? cacheExpiry = CalculateSuggestedCacheExpiry(accessor, logger);
- await IdentityCacheWrapper.SetAsync(suggestedWebCacheKey, accessor, cacheExpiry).ConfigureAwait(false);
+ if (accessor is InMemoryPartitionedAppTokenCacheAccessor)
+ {
+ await IdentityCacheWrapper.SetAsync(suggestedWebCacheKey, (InMemoryPartitionedAppTokenCacheAccessor)accessor, cacheExpiry).ConfigureAwait(false);
+ }
+ else if (accessor is InMemoryPartitionedUserTokenCacheAccessor)
+ {
+ await IdentityCacheWrapper.SetAsync(suggestedWebCacheKey, (InMemoryPartitionedUserTokenCacheAccessor)accessor, cacheExpiry).ConfigureAwait(false);
+ }
}
SaveToLegacyAdalCache(
@@ -395,7 +403,9 @@ private void SaveToLegacyAdalCache(
// do not suggest an expiration date from the past or within 5 min, as tokens will not be usable anyway
// and HasTokens will be set to false, letting implementers know to delete the cache node
if (cacheExpiry < DateTimeOffset.UtcNow + Constants.AccessTokenExpirationBuffer)
+ {
return null;
+ }
return cacheExpiry;
}
@@ -435,7 +445,7 @@ async Task ITokenCacheInternal.FindAccessTokenAsync(
string partitionKey = CacheKeyFactory.GetKeyFromRequest(requestParams);
Debug.Assert(partitionKey != null || !requestParams.IsConfidentialClient, "On confidential client, cache must be partitioned.");
- var accessTokens = (await GetOrCreateAccessorAsync(partitionKey).ConfigureAwait(false)).GetAllAccessTokens(partitionKey, logger);
+ var accessTokens = (await GetOrCreateAccessorAsync(partitionKey, requestParams).ConfigureAwait(false)).GetAllAccessTokens(partitionKey, logger);
requestParams.RequestContext.Logger.Always($"[FindAccessTokenAsync] Discovered {accessTokens.Count} access tokens in cache using partition key: {partitionKey}");
@@ -492,7 +502,8 @@ private static void FilterTokensByScopes(
!OAuth2Value.ReservedScopes.Contains(s));
tokenCacheItems.FilterWithLogging(
- item => {
+ item =>
+ {
bool accepted = ScopeHelper.ScopeContains(item.ScopeSet, requestScopes);
if (logger.IsLoggingEnabled(LogLevel.Verbose))
@@ -761,10 +772,12 @@ async Task ITokenCacheInternal.FindRefreshTokenAsync(
string familyId)
{
if (requestParams.Authority == null)
+ {
return null;
+ }
var requestKey = CacheKeyFactory.GetKeyFromRequest(requestParams);
- var refreshTokens = (await GetOrCreateAccessorAsync(requestKey).ConfigureAwait(false)).GetAllRefreshTokens(requestKey);
+ var refreshTokens = (await GetOrCreateAccessorAsync(requestKey, requestParams).ConfigureAwait(false)).GetAllRefreshTokens(requestKey);
requestParams.RequestContext.Logger.Always($"[FindRefreshTokenAsync] Discovered {refreshTokens.Count} refresh tokens in cache using key: {requestKey}");
if (refreshTokens.Count != 0)
@@ -922,7 +935,9 @@ async Task> ITokenCacheInternal.GetAccountsAsync(Authentic
}
if (logger.IsLoggingEnabled(LogLevel.Verbose))
+ {
logger.Verbose($"[GetAccounts] Found {refreshTokenCacheItems.Count} RTs and {accountCacheItems.Count} accounts in MSAL cache. ");
+ }
// Multi-cloud support - must filter by environment.
ISet allEnvironmentsInCache = new HashSet(
@@ -955,7 +970,9 @@ async Task> ITokenCacheInternal.GetAccountsAsync(Authentic
}
if (logger.IsLoggingEnabled(LogLevel.Verbose))
+ {
logger.Verbose($"[GetAccounts] Found {refreshTokenCacheItems.Count} RTs and {accountCacheItems.Count} accounts in MSAL cache after environment filtering. ");
+ }
IDictionary clientInfoToAccountMap = new Dictionary();
foreach (MsalRefreshTokenCacheItem rtItem in refreshTokenCacheItems)
@@ -1029,7 +1046,9 @@ async Task> ITokenCacheInternal.GetAccountsAsync(Authentic
StringComparison.OrdinalIgnoreCase)).ToList();
if (logger.IsLoggingEnabled(LogLevel.Verbose))
+ {
logger.Verbose($"Filtered by home account id. Remaining accounts {accounts.Count()} ");
+ }
}
return accounts;
@@ -1074,7 +1093,7 @@ private void UpdateWithAdalAccountsWithoutClientInfo(
async Task ITokenCacheInternal.GetIdTokenCacheItemAsync(MsalAccessTokenCacheItem msalAccessTokenCacheItem)
{
- var idToken = (await GetOrCreateAccessorAsync(CacheKeyFactory.GetIdTokenKeyFromCachedItem(msalAccessTokenCacheItem)).ConfigureAwait(false)).GetIdToken(msalAccessTokenCacheItem);
+ var idToken = (await GetOrCreateAccessorAsync(CacheKeyFactory.GetIdTokenKeyFromCachedItem(msalAccessTokenCacheItem), null).ConfigureAwait(false)).GetIdToken(msalAccessTokenCacheItem);
return idToken;
}
@@ -1089,7 +1108,7 @@ private async Task> GetTenantProfilesAsync(
Debug.Assert(homeAccountId != null);
- var idTokenCacheItems = (await GetOrCreateAccessorAsync(homeAccountId).ConfigureAwait(false)).GetAllIdTokens(homeAccountId);
+ var idTokenCacheItems = (await GetOrCreateAccessorAsync(homeAccountId, requestParameters).ConfigureAwait(false)).GetAllIdTokens(homeAccountId);
FilterTokensByClientId(idTokenCacheItems);
if (!requestParameters.AppConfig.MultiCloudSupportEnabled)
@@ -1126,7 +1145,7 @@ async Task ITokenCacheInternal.GetAccountAssociatedWithAccessTokenAsync
var tenantProfiles = await GetTenantProfilesAsync(requestParameters, msalAccessTokenCacheItem.HomeAccountId).ConfigureAwait(false);
- var accountCacheItem = (await GetOrCreateAccessorAsync(CacheKeyFactory.GetIdTokenKeyFromCachedItem(msalAccessTokenCacheItem)).ConfigureAwait(false)).GetAccount(
+ var accountCacheItem = (await GetOrCreateAccessorAsync(CacheKeyFactory.GetIdTokenKeyFromCachedItem(msalAccessTokenCacheItem), requestParameters).ConfigureAwait(false)).GetAccount(
new MsalAccountCacheKey(
msalAccessTokenCacheItem.Environment,
msalAccessTokenCacheItem.TenantId,
@@ -1173,7 +1192,6 @@ async Task ITokenCacheInternal.RemoveAccountAsync(IAccount account, Authenticati
identityLogger: requestParameters.RequestContext.Logger.IdentityLogger,
piiLoggingEnabled: requestParameters.RequestContext.Logger.PiiLoggingEnabled);
-
await tokenCacheInternal.OnBeforeAccessAsync(args).ConfigureAwait(false);
await tokenCacheInternal.OnBeforeWriteAsync(args).ConfigureAwait(false);
}
@@ -1209,7 +1227,6 @@ async Task ITokenCacheInternal.RemoveAccountAsync(IAccount account, Authenticati
identityLogger: requestParameters.RequestContext.Logger.IdentityLogger,
piiLoggingEnabled: requestParameters.RequestContext.Logger.PiiLoggingEnabled);
-
await tokenCacheInternal.OnAfterAccessAsync(args).ConfigureAwait(false);
}
}
@@ -1239,7 +1256,7 @@ bool ITokenCacheInternal.HasTokensNoLocks()
string partitionKey = account.HomeAccountId.Identifier;
- var accessor = await GetOrCreateAccessorAsync(partitionKey).ConfigureAwait(false);
+ var accessor = await GetOrCreateAccessorAsync(partitionKey, null).ConfigureAwait(false);
var refreshTokens = accessor.GetAllRefreshTokens(partitionKey);
refreshTokens.RemoveAll(item => !item.HomeAccountId.Equals(account.HomeAccountId.Identifier, StringComparison.OrdinalIgnoreCase));
@@ -1301,13 +1318,20 @@ bool ITokenCacheInternal.HasTokensNoLocks()
if (!((ITokenCacheInternal)this).IsAppSubscribedToSerializationEvents())
{
DateTimeOffset? cacheExpiry = CalculateSuggestedCacheExpiry(accessor, requestContext.Logger);
- await IdentityCacheWrapper.SetAsync(partitionKey, accessor, cacheExpiry).ConfigureAwait(false);
+ if (accessor is InMemoryPartitionedAppTokenCacheAccessor)
+ {
+ await IdentityCacheWrapper.SetAsync(partitionKey, (InMemoryPartitionedAppTokenCacheAccessor)accessor, cacheExpiry).ConfigureAwait(false);
+ }
+ else if (accessor is InMemoryPartitionedUserTokenCacheAccessor)
+ {
+ await IdentityCacheWrapper.SetAsync(partitionKey, (InMemoryPartitionedUserTokenCacheAccessor)accessor, cacheExpiry).ConfigureAwait(false);
+ }
}
}
// Cache setup is validated to be mutually exclusive -
// Token cache serialization is allowed only when WithCacheOptions is not used.
- private async Task GetOrCreateAccessorAsync(string partitionKey)
+ private async Task GetOrCreateAccessorAsync(string partitionKey, AuthenticationRequestParameters requestParams)
{
// If user set up legacy cache serialization, then use old accessor instance (it would have been populated with tokens)
// Otherwise, use IIdentityCache instance, either the user-provided or default.
@@ -1317,7 +1341,17 @@ private async Task GetOrCreateAccessorAsync(string partitio
}
else
{
- var cachedAccessor = await IdentityCacheWrapper.GetAsync(partitionKey).ConfigureAwait(false);
+ ITokenCacheAccessor cachedAccessor = null;
+
+ if (requestParams != null && requestParams.IsClientCredentialRequest)
+ {
+ cachedAccessor = await IdentityCacheWrapper.GetAsync(partitionKey).ConfigureAwait(false);
+ }
+ else
+ {
+ cachedAccessor = await IdentityCacheWrapper.GetAsync(partitionKey).ConfigureAwait(false);
+ }
+
if (cachedAccessor == null)
{
var proxy = ServiceBundle?.PlatformProxy ?? PlatformProxyFactory.CreatePlatformProxy(null);
From 1357510322456237015bcfa91841156f063c49bd Mon Sep 17 00:00:00 2001
From: pmaytak <34331512+pmaytak@users.noreply.github.com>
Date: Fri, 7 Oct 2022 00:04:27 -0700
Subject: [PATCH 6/8] Add reference to common cache packages.
---
.../Microsoft.Identity.Client.csproj | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/src/client/Microsoft.Identity.Client/Microsoft.Identity.Client.csproj b/src/client/Microsoft.Identity.Client/Microsoft.Identity.Client.csproj
index 8428bb012c..95773acde4 100644
--- a/src/client/Microsoft.Identity.Client/Microsoft.Identity.Client.csproj
+++ b/src/client/Microsoft.Identity.Client/Microsoft.Identity.Client.csproj
@@ -324,6 +324,8 @@
-
+
+
+
From 81e5aaee80aaca3bf29a0bc930352136014758c1 Mon Sep 17 00:00:00 2001
From: pmaytak <34331512+pmaytak@users.noreply.github.com>
Date: Mon, 10 Oct 2022 00:25:14 -0700
Subject: [PATCH 7/8] Update OBO and client perf tests.
---
.../TokenCache.ITokenCacheInternal.cs | 21 ++++++-----
.../AcquireTokenForClientCacheTests.cs | 29 ++++++++++++---
.../AcquireTokenForOboCacheTests.cs | 36 +++++++++++++++----
3 files changed, 64 insertions(+), 22 deletions(-)
diff --git a/src/client/Microsoft.Identity.Client/TokenCache.ITokenCacheInternal.cs b/src/client/Microsoft.Identity.Client/TokenCache.ITokenCacheInternal.cs
index 061f2ed1b6..79f8da9437 100644
--- a/src/client/Microsoft.Identity.Client/TokenCache.ITokenCacheInternal.cs
+++ b/src/client/Microsoft.Identity.Client/TokenCache.ITokenCacheInternal.cs
@@ -188,7 +188,7 @@ async Task> IToke
requestParams.RequestContext.ApiEvent.DurationInCacheInMs += sw.ElapsedMilliseconds;
}
- var accessor = await GetOrCreateAccessorAsync(suggestedWebCacheKey, requestParams).ConfigureAwait(false);
+ var accessor = await GetOrCreateAccessorAsync(suggestedWebCacheKey).ConfigureAwait(false);
// Don't cache PoP access tokens from broker
if (msalAccessTokenCacheItem != null && !(response.TokenSource == TokenSource.Broker && response.TokenType == Constants.PoPAuthHeaderPrefix))
@@ -445,7 +445,7 @@ async Task ITokenCacheInternal.FindAccessTokenAsync(
string partitionKey = CacheKeyFactory.GetKeyFromRequest(requestParams);
Debug.Assert(partitionKey != null || !requestParams.IsConfidentialClient, "On confidential client, cache must be partitioned.");
- var accessTokens = (await GetOrCreateAccessorAsync(partitionKey, requestParams).ConfigureAwait(false)).GetAllAccessTokens(partitionKey, logger);
+ var accessTokens = (await GetOrCreateAccessorAsync(partitionKey).ConfigureAwait(false)).GetAllAccessTokens(partitionKey, logger);
requestParams.RequestContext.Logger.Always($"[FindAccessTokenAsync] Discovered {accessTokens.Count} access tokens in cache using partition key: {partitionKey}");
@@ -777,7 +777,7 @@ async Task ITokenCacheInternal.FindRefreshTokenAsync(
}
var requestKey = CacheKeyFactory.GetKeyFromRequest(requestParams);
- var refreshTokens = (await GetOrCreateAccessorAsync(requestKey, requestParams).ConfigureAwait(false)).GetAllRefreshTokens(requestKey);
+ var refreshTokens = (await GetOrCreateAccessorAsync(requestKey).ConfigureAwait(false)).GetAllRefreshTokens(requestKey);
requestParams.RequestContext.Logger.Always($"[FindRefreshTokenAsync] Discovered {refreshTokens.Count} refresh tokens in cache using key: {requestKey}");
if (refreshTokens.Count != 0)
@@ -1093,7 +1093,7 @@ private void UpdateWithAdalAccountsWithoutClientInfo(
async Task ITokenCacheInternal.GetIdTokenCacheItemAsync(MsalAccessTokenCacheItem msalAccessTokenCacheItem)
{
- var idToken = (await GetOrCreateAccessorAsync(CacheKeyFactory.GetIdTokenKeyFromCachedItem(msalAccessTokenCacheItem), null).ConfigureAwait(false)).GetIdToken(msalAccessTokenCacheItem);
+ var idToken = (await GetOrCreateAccessorAsync(CacheKeyFactory.GetIdTokenKeyFromCachedItem(msalAccessTokenCacheItem)).ConfigureAwait(false)).GetIdToken(msalAccessTokenCacheItem);
return idToken;
}
@@ -1108,7 +1108,7 @@ private async Task> GetTenantProfilesAsync(
Debug.Assert(homeAccountId != null);
- var idTokenCacheItems = (await GetOrCreateAccessorAsync(homeAccountId, requestParameters).ConfigureAwait(false)).GetAllIdTokens(homeAccountId);
+ var idTokenCacheItems = (await GetOrCreateAccessorAsync(homeAccountId).ConfigureAwait(false)).GetAllIdTokens(homeAccountId);
FilterTokensByClientId(idTokenCacheItems);
if (!requestParameters.AppConfig.MultiCloudSupportEnabled)
@@ -1145,7 +1145,7 @@ async Task ITokenCacheInternal.GetAccountAssociatedWithAccessTokenAsync
var tenantProfiles = await GetTenantProfilesAsync(requestParameters, msalAccessTokenCacheItem.HomeAccountId).ConfigureAwait(false);
- var accountCacheItem = (await GetOrCreateAccessorAsync(CacheKeyFactory.GetIdTokenKeyFromCachedItem(msalAccessTokenCacheItem), requestParameters).ConfigureAwait(false)).GetAccount(
+ var accountCacheItem = (await GetOrCreateAccessorAsync(CacheKeyFactory.GetIdTokenKeyFromCachedItem(msalAccessTokenCacheItem)).ConfigureAwait(false)).GetAccount(
new MsalAccountCacheKey(
msalAccessTokenCacheItem.Environment,
msalAccessTokenCacheItem.TenantId,
@@ -1256,7 +1256,7 @@ bool ITokenCacheInternal.HasTokensNoLocks()
string partitionKey = account.HomeAccountId.Identifier;
- var accessor = await GetOrCreateAccessorAsync(partitionKey, null).ConfigureAwait(false);
+ var accessor = await GetOrCreateAccessorAsync(partitionKey).ConfigureAwait(false);
var refreshTokens = accessor.GetAllRefreshTokens(partitionKey);
refreshTokens.RemoveAll(item => !item.HomeAccountId.Equals(account.HomeAccountId.Identifier, StringComparison.OrdinalIgnoreCase));
@@ -1331,7 +1331,7 @@ bool ITokenCacheInternal.HasTokensNoLocks()
// Cache setup is validated to be mutually exclusive -
// Token cache serialization is allowed only when WithCacheOptions is not used.
- private async Task GetOrCreateAccessorAsync(string partitionKey, AuthenticationRequestParameters requestParams)
+ internal async Task GetOrCreateAccessorAsync(string partitionKey)
{
// If user set up legacy cache serialization, then use old accessor instance (it would have been populated with tokens)
// Otherwise, use IIdentityCache instance, either the user-provided or default.
@@ -1341,9 +1341,8 @@ private async Task GetOrCreateAccessorAsync(string partitio
}
else
{
- ITokenCacheAccessor cachedAccessor = null;
-
- if (requestParams != null && requestParams.IsClientCredentialRequest)
+ ITokenCacheAccessor cachedAccessor;
+ if (IsAppTokenCache)
{
cachedAccessor = await IdentityCacheWrapper.GetAsync(partitionKey).ConfigureAwait(false);
}
diff --git a/tests/Microsoft.Identity.Test.Performance/AcquireTokenForClientCacheTests.cs b/tests/Microsoft.Identity.Test.Performance/AcquireTokenForClientCacheTests.cs
index 03a0b4afee..d150d8ed1e 100644
--- a/tests/Microsoft.Identity.Test.Performance/AcquireTokenForClientCacheTests.cs
+++ b/tests/Microsoft.Identity.Test.Performance/AcquireTokenForClientCacheTests.cs
@@ -8,6 +8,7 @@
using Microsoft.Identity.Client;
using Microsoft.Identity.Client.Cache;
using Microsoft.Identity.Client.Cache.Items;
+using Microsoft.Identity.Client.PlatformsCommon.Shared;
using Microsoft.Identity.Test.Common.Core.Mocks;
using Microsoft.Identity.Test.Performance.Helpers;
using Microsoft.Identity.Test.Unit;
@@ -54,12 +55,18 @@ public class AcquireTokenForClientCacheTests
[GlobalSetup]
public async Task GlobalSetupAsync()
{
- _cca = ConfidentialClientApplicationBuilder
+ var builder = ConfidentialClientApplicationBuilder
.Create(TestConstants.ClientId)
.WithRedirectUri(TestConstants.RedirectUri)
.WithClientSecret(TestConstants.ClientSecret)
- .WithLegacyCacheCompatibility(false)
- .BuildConcrete();
+ .WithLegacyCacheCompatibility(false);
+
+ if (!EnableCacheSerialization)
+ {
+ builder.WithCacheOptions(new CacheOptions(CacheSize.TotalTenants));
+ }
+
+ _cca = builder.BuildConcrete();
if (EnableCacheSerialization)
{
@@ -102,6 +109,16 @@ private async Task PopulateAppCacheAsync(ConfidentialClientApplication cca, int
{
string key = CacheKeyFactory.GetClientCredentialKey(_cca.AppConfig.ClientId, $"{_tenantPrefix}{tenant}", "");
+ ITokenCacheAccessor accessor;
+ if (enableCacheSerialization)
+ {
+ accessor = cca.AppTokenCacheInternal.Accessor;
+ }
+ else
+ {
+ accessor = await (cca.AppTokenCache as TokenCache).GetOrCreateAccessorAsync(key).ConfigureAwait(false);
+ }
+
for (int token = 0; token < tokensPerTenant; token++)
{
MsalAccessTokenCacheItem atItem = TokenCacheHelper.CreateAccessTokenItem(
@@ -109,7 +126,7 @@ private async Task PopulateAppCacheAsync(ConfidentialClientApplication cca, int
tenant: $"{_tenantPrefix}{tenant}",
accessToken: TestConstants.AppAccessToken);
- cca.AppTokenCacheInternal.Accessor.SaveAccessToken(atItem);
+ accessor.SaveAccessToken(atItem);
}
if (enableCacheSerialization)
@@ -127,6 +144,10 @@ private async Task PopulateAppCacheAsync(ConfidentialClientApplication cca, int
await cca.AppTokenCacheInternal.OnAfterAccessAsync(args).ConfigureAwait(false);
cca.AppTokenCacheInternal.Accessor.Clear();
}
+ else
+ {
+ await (cca.AppTokenCache as TokenCache).IdentityCacheWrapper.SetAsync(key, (InMemoryPartitionedAppTokenCacheAccessor)accessor, null).ConfigureAwait(false);
+ }
}
}
}
diff --git a/tests/Microsoft.Identity.Test.Performance/AcquireTokenForOboCacheTests.cs b/tests/Microsoft.Identity.Test.Performance/AcquireTokenForOboCacheTests.cs
index c0a951ce91..d8be1ae28e 100644
--- a/tests/Microsoft.Identity.Test.Performance/AcquireTokenForOboCacheTests.cs
+++ b/tests/Microsoft.Identity.Test.Performance/AcquireTokenForOboCacheTests.cs
@@ -6,7 +6,9 @@
using System.Threading.Tasks;
using BenchmarkDotNet.Attributes;
using Microsoft.Identity.Client;
+using Microsoft.Identity.Client.Cache;
using Microsoft.Identity.Client.Cache.Items;
+using Microsoft.Identity.Client.PlatformsCommon.Shared;
using Microsoft.Identity.Test.Common.Core.Mocks;
using Microsoft.Identity.Test.Performance.Helpers;
using Microsoft.Identity.Test.Unit;
@@ -60,12 +62,18 @@ public class AcquireTokenForOboCacheTests
[GlobalSetup]
public async Task GlobalSetupAsync()
{
- _cca = ConfidentialClientApplicationBuilder
+ var builder = ConfidentialClientApplicationBuilder
.Create(TestConstants.ClientId)
.WithRedirectUri(TestConstants.RedirectUri)
.WithClientSecret(TestConstants.ClientSecret)
- .WithLegacyCacheCompatibility(false)
- .BuildConcrete();
+ .WithLegacyCacheCompatibility(false);
+
+ if (!EnableCacheSerialization)
+ {
+ builder.WithCacheOptions(new CacheOptions(CacheSize.TotalUsers));
+ }
+
+ _cca = builder.BuildConcrete();
if (EnableCacheSerialization)
{
@@ -105,6 +113,16 @@ private async Task PopulateUserCacheAsync(int totalUsers, int tokensPerUser, boo
string userAssertionHash = new UserAssertion($"{TestConstants.DefaultAccessToken}{user}").AssertionHash;
string homeAccountId = $"{user}.{_tenantPrefix}";
+ ITokenCacheAccessor accessor;
+ if (enableCacheSerialization)
+ {
+ accessor = _cca.UserTokenCacheInternal.Accessor;
+ }
+ else
+ {
+ accessor = await (_cca.UserTokenCache as TokenCache).GetOrCreateAccessorAsync(userAssertionHash).ConfigureAwait(false);
+ }
+
for (int token = 0; token < tokensPerUser; token++)
{
string tenant = IsMultiTenant ? $"{_tenantPrefix}{token}" : _tenantPrefix;
@@ -116,22 +134,22 @@ private async Task PopulateUserCacheAsync(int totalUsers, int tokensPerUser, boo
homeAccountId,
oboCacheKey: userAssertionHash,
accessToken: TestConstants.UserAccessToken);
- _cca.UserTokenCacheInternal.Accessor.SaveAccessToken(atItem);
+ accessor.SaveAccessToken(atItem);
MsalRefreshTokenCacheItem rtItem = TokenCacheHelper.CreateRefreshTokenItem(
userAssertionHash,
homeAccountId,
refreshToken: TestConstants.RefreshToken);
- _cca.UserTokenCacheInternal.Accessor.SaveRefreshToken(rtItem);
+ accessor.SaveRefreshToken(rtItem);
MsalIdTokenCacheItem idtItem = TokenCacheHelper.CreateIdTokenCacheItem(
tenant,
homeAccountId,
uid: user.ToString());
- _cca.UserTokenCacheInternal.Accessor.SaveIdToken(idtItem);
+ accessor.SaveIdToken(idtItem);
MsalAccountCacheItem accItem = TokenCacheHelper.CreateAccountItem(tenant, homeAccountId);
- _cca.UserTokenCacheInternal.Accessor.SaveAccount(accItem);
+ accessor.SaveAccount(accItem);
}
if (enableCacheSerialization)
@@ -149,6 +167,10 @@ private async Task PopulateUserCacheAsync(int totalUsers, int tokensPerUser, boo
await _cca.UserTokenCacheInternal.OnAfterAccessAsync(args).ConfigureAwait(false);
_cca.UserTokenCacheInternal.Accessor.Clear();
}
+ else
+ {
+ await (_cca.UserTokenCache as TokenCache).IdentityCacheWrapper.SetAsync(userAssertionHash, (InMemoryPartitionedUserTokenCacheAccessor)accessor, null).ConfigureAwait(false);
+ }
}
}
}
From 939ca1f9f7bb03d5baccf55b486939b43738acae Mon Sep 17 00:00:00 2001
From: pmaytak <34331512+pmaytak@users.noreply.github.com>
Date: Mon, 10 Oct 2022 14:47:37 -0700
Subject: [PATCH 8/8] Add two size limits, app and user tokens, for two
categories in IIdentityCache.
---
.../AppConfig/CacheOptions.cs | 20 +++++++----
.../Cache/Prototype/IdentityCacheWrapper.cs | 34 +++++++++++++++----
.../TokenCache.ITokenCacheInternal.cs | 12 +++----
.../AcquireTokenForClientCacheTests.cs | 4 +--
.../AcquireTokenForOboCacheTests.cs | 4 +--
5 files changed, 52 insertions(+), 22 deletions(-)
diff --git a/src/client/Microsoft.Identity.Client/AppConfig/CacheOptions.cs b/src/client/Microsoft.Identity.Client/AppConfig/CacheOptions.cs
index b63011adf0..2fcbc6780d 100644
--- a/src/client/Microsoft.Identity.Client/AppConfig/CacheOptions.cs
+++ b/src/client/Microsoft.Identity.Client/AppConfig/CacheOptions.cs
@@ -1,6 +1,7 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
+using System;
using Microsoft.Identity.ServiceEssentials;
namespace Microsoft.Identity.Client
@@ -47,16 +48,18 @@ public CacheOptions(bool useSharedCache)
///
public CacheOptions(IIdentityCache identityCache)
{
- IdentityCache = identityCache;
+ IdentityCache = identityCache ?? throw new ArgumentNullException(nameof(identityCache));
}
///
///
///
- ///
- public CacheOptions(int sizeLimit)
+ ///
+ ///
+ public CacheOptions(int appTokenCacheSizeLimit, int userTokenCacheSizeLimit)
{
- SizeLimit = sizeLimit;
+ AppTokenCacheSizeLimit = appTokenCacheSizeLimit > 0 ? appTokenCacheSizeLimit : 0;
+ UserTokenCacheSizeLimit = userTokenCacheSizeLimit > 0 ? userTokenCacheSizeLimit : 0;
}
///
@@ -76,8 +79,13 @@ public CacheOptions(int sizeLimit)
public IIdentityCache IdentityCache { get; }
///
- /// Max count of items in the default in-memory cache with eviction
+ /// Max count of cache items (by tenant for client credential flows) in the default in-memory cache with eviction
+ ///
+ public int AppTokenCacheSizeLimit { get; }
+
+ ///
+ /// Max count of cache items (by incoming token assertion cache for OBO and home account ID for other user flows) in the default in-memory cache with eviction
///
- public int SizeLimit { get; }
+ public int UserTokenCacheSizeLimit { get; }
}
}
diff --git a/src/client/Microsoft.Identity.Client/Cache/Prototype/IdentityCacheWrapper.cs b/src/client/Microsoft.Identity.Client/Cache/Prototype/IdentityCacheWrapper.cs
index 0372c57723..42b46dc4b3 100644
--- a/src/client/Microsoft.Identity.Client/Cache/Prototype/IdentityCacheWrapper.cs
+++ b/src/client/Microsoft.Identity.Client/Cache/Prototype/IdentityCacheWrapper.cs
@@ -17,7 +17,8 @@ internal class IdentityCacheWrapper
private static IIdentityLogger _identityLogger;
private static readonly Lazy s_defaultIIdentityCache = new Lazy(
() => CreateDefaultCache());
- private const string CategoryName = "tokens";
+ private const string AppTokensCategory = "app_tokens";
+ private const string UserTokensCategory = "user_tokens";
// This cache instance (whether provided by the user or default one) will only ever be called/used if cache serialization is not enabled.
// There are three options for this cache: user-provided, static default, non-static default.
@@ -49,23 +50,44 @@ private static IIdentityCache CreateDefaultCache()
{
MaxNumberOfItemsForCategory = new Dictionary()
{
- { CategoryName, s_cacheOptions.SizeLimit },
+ { AppTokensCategory, s_cacheOptions.AppTokenCacheSizeLimit },
+ { UserTokensCategory, s_cacheOptions.UserTokenCacheSizeLimit },
}
};
return new IdentityCachePrototype(memoryCacheOptions, _identityLogger, null);
}
- internal async Task GetAsync(string key) where T : ICacheObject, new()
+ internal async Task GetAppCacheAsync(string key) where T : ICacheObject, new()
{
- var entry = await _identityCache.GetAsync(CategoryName, key).ConfigureAwait(false);
+ return await GetAsync(AppTokensCategory, key).ConfigureAwait(false);
+ }
+
+ internal async Task GetUserCacheAsync(string key) where T : ICacheObject, new()
+ {
+ return await GetAsync(UserTokensCategory, key).ConfigureAwait(false);
+ }
+
+ private async Task GetAsync(string category, string key) where T : ICacheObject, new()
+ {
+ var entry = await _identityCache.GetAsync(category, key).ConfigureAwait(false);
return entry == null ? default : entry.Value;
}
- internal async Task SetAsync(string key, T value, DateTimeOffset? cacheExpiry) where T : ICacheObject, new()
+ internal async Task SetAppCacheAsync(string key, T value, DateTimeOffset? cacheExpiry) where T : ICacheObject, new()
+ {
+ await SetAsync(AppTokensCategory, key, value, cacheExpiry).ConfigureAwait(false);
+ }
+
+ internal async Task SetUserCacheAsync(string key, T value, DateTimeOffset? cacheExpiry) where T : ICacheObject, new()
+ {
+ await SetAsync(UserTokensCategory, key, value, cacheExpiry).ConfigureAwait(false);
+ }
+
+ private async Task SetAsync(string category, string key, T value, DateTimeOffset? cacheExpiry) where T : ICacheObject, new()
{
TimeSpan expirationTimeRelativeToNow = cacheExpiry.HasValue ? cacheExpiry.Value - DateTimeOffset.UtcNow : TimeSpan.FromHours(1);
- await _identityCache.SetAsync(CategoryName, key, value, new CacheEntryOptions(expirationTimeRelativeToNow, 1)).ConfigureAwait(false);
+ await _identityCache.SetAsync(category, key, value, new CacheEntryOptions(expirationTimeRelativeToNow, 1)).ConfigureAwait(false);
}
}
}
diff --git a/src/client/Microsoft.Identity.Client/TokenCache.ITokenCacheInternal.cs b/src/client/Microsoft.Identity.Client/TokenCache.ITokenCacheInternal.cs
index 79f8da9437..98633a567b 100644
--- a/src/client/Microsoft.Identity.Client/TokenCache.ITokenCacheInternal.cs
+++ b/src/client/Microsoft.Identity.Client/TokenCache.ITokenCacheInternal.cs
@@ -232,11 +232,11 @@ async Task> IToke
DateTimeOffset? cacheExpiry = CalculateSuggestedCacheExpiry(accessor, logger);
if (accessor is InMemoryPartitionedAppTokenCacheAccessor)
{
- await IdentityCacheWrapper.SetAsync(suggestedWebCacheKey, (InMemoryPartitionedAppTokenCacheAccessor)accessor, cacheExpiry).ConfigureAwait(false);
+ await IdentityCacheWrapper.SetAppCacheAsync(suggestedWebCacheKey, (InMemoryPartitionedAppTokenCacheAccessor)accessor, cacheExpiry).ConfigureAwait(false);
}
else if (accessor is InMemoryPartitionedUserTokenCacheAccessor)
{
- await IdentityCacheWrapper.SetAsync(suggestedWebCacheKey, (InMemoryPartitionedUserTokenCacheAccessor)accessor, cacheExpiry).ConfigureAwait(false);
+ await IdentityCacheWrapper.SetUserCacheAsync(suggestedWebCacheKey, (InMemoryPartitionedUserTokenCacheAccessor)accessor, cacheExpiry).ConfigureAwait(false);
}
}
@@ -1320,11 +1320,11 @@ bool ITokenCacheInternal.HasTokensNoLocks()
DateTimeOffset? cacheExpiry = CalculateSuggestedCacheExpiry(accessor, requestContext.Logger);
if (accessor is InMemoryPartitionedAppTokenCacheAccessor)
{
- await IdentityCacheWrapper.SetAsync(partitionKey, (InMemoryPartitionedAppTokenCacheAccessor)accessor, cacheExpiry).ConfigureAwait(false);
+ await IdentityCacheWrapper.SetAppCacheAsync(partitionKey, (InMemoryPartitionedAppTokenCacheAccessor)accessor, cacheExpiry).ConfigureAwait(false);
}
else if (accessor is InMemoryPartitionedUserTokenCacheAccessor)
{
- await IdentityCacheWrapper.SetAsync(partitionKey, (InMemoryPartitionedUserTokenCacheAccessor)accessor, cacheExpiry).ConfigureAwait(false);
+ await IdentityCacheWrapper.SetUserCacheAsync(partitionKey, (InMemoryPartitionedUserTokenCacheAccessor)accessor, cacheExpiry).ConfigureAwait(false);
}
}
}
@@ -1344,11 +1344,11 @@ internal async Task GetOrCreateAccessorAsync(string partiti
ITokenCacheAccessor cachedAccessor;
if (IsAppTokenCache)
{
- cachedAccessor = await IdentityCacheWrapper.GetAsync(partitionKey).ConfigureAwait(false);
+ cachedAccessor = await IdentityCacheWrapper.GetAppCacheAsync(partitionKey).ConfigureAwait(false);
}
else
{
- cachedAccessor = await IdentityCacheWrapper.GetAsync(partitionKey).ConfigureAwait(false);
+ cachedAccessor = await IdentityCacheWrapper.GetUserCacheAsync(partitionKey).ConfigureAwait(false);
}
if (cachedAccessor == null)
diff --git a/tests/Microsoft.Identity.Test.Performance/AcquireTokenForClientCacheTests.cs b/tests/Microsoft.Identity.Test.Performance/AcquireTokenForClientCacheTests.cs
index d150d8ed1e..a9a6e0f6e2 100644
--- a/tests/Microsoft.Identity.Test.Performance/AcquireTokenForClientCacheTests.cs
+++ b/tests/Microsoft.Identity.Test.Performance/AcquireTokenForClientCacheTests.cs
@@ -63,7 +63,7 @@ public async Task GlobalSetupAsync()
if (!EnableCacheSerialization)
{
- builder.WithCacheOptions(new CacheOptions(CacheSize.TotalTenants));
+ builder.WithCacheOptions(new CacheOptions(CacheSize.TotalTenants, 0));
}
_cca = builder.BuildConcrete();
@@ -146,7 +146,7 @@ private async Task PopulateAppCacheAsync(ConfidentialClientApplication cca, int
}
else
{
- await (cca.AppTokenCache as TokenCache).IdentityCacheWrapper.SetAsync(key, (InMemoryPartitionedAppTokenCacheAccessor)accessor, null).ConfigureAwait(false);
+ await (cca.AppTokenCache as TokenCache).IdentityCacheWrapper.SetAppCacheAsync(key, (InMemoryPartitionedAppTokenCacheAccessor)accessor, null).ConfigureAwait(false);
}
}
}
diff --git a/tests/Microsoft.Identity.Test.Performance/AcquireTokenForOboCacheTests.cs b/tests/Microsoft.Identity.Test.Performance/AcquireTokenForOboCacheTests.cs
index d8be1ae28e..a6092b04de 100644
--- a/tests/Microsoft.Identity.Test.Performance/AcquireTokenForOboCacheTests.cs
+++ b/tests/Microsoft.Identity.Test.Performance/AcquireTokenForOboCacheTests.cs
@@ -70,7 +70,7 @@ public async Task GlobalSetupAsync()
if (!EnableCacheSerialization)
{
- builder.WithCacheOptions(new CacheOptions(CacheSize.TotalUsers));
+ builder.WithCacheOptions(new CacheOptions(0, CacheSize.TotalUsers));
}
_cca = builder.BuildConcrete();
@@ -169,7 +169,7 @@ private async Task PopulateUserCacheAsync(int totalUsers, int tokensPerUser, boo
}
else
{
- await (_cca.UserTokenCache as TokenCache).IdentityCacheWrapper.SetAsync(userAssertionHash, (InMemoryPartitionedUserTokenCacheAccessor)accessor, null).ConfigureAwait(false);
+ await (_cca.UserTokenCache as TokenCache).IdentityCacheWrapper.SetUserCacheAsync(userAssertionHash, (InMemoryPartitionedUserTokenCacheAccessor)accessor, null).ConfigureAwait(false);
}
}
}