Skip to content
This repository has been archived by the owner on Sep 7, 2023. It is now read-only.

Fix for #98 - cache cannot be used with ADFS #130

Merged
merged 3 commits into from
Feb 17, 2021
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 2 additions & 4 deletions sample/ManualTestApp/ExampleUsage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,7 @@ private static StorageCreationProperties ConfigureSecureStorage(bool usePlaintex
{
return new StorageCreationPropertiesBuilder(
Config.CacheFileName,
Config.CacheDir,
Config.ClientId)
Config.CacheDir)
.WithLinuxKeyring(
Config.LinuxKeyRingSchema,
Config.LinuxKeyRingCollection,
Expand All @@ -96,8 +95,7 @@ private static StorageCreationProperties ConfigureSecureStorage(bool usePlaintex

return new StorageCreationPropertiesBuilder(
Config.CacheFileName,
Config.CacheDir,
Config.ClientId)
Config.CacheDir)
.WithLinuxUnprotectedFile()
.WithMacKeyChain(
Config.KeyChainServiceName,
Expand Down
4 changes: 2 additions & 2 deletions sample/ManualTestApp/ManualTestApp.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
<OutputType>Exe</OutputType>
<IsPackable>false</IsPackable>

<TargetFrameworks Condition="$([MSBuild]::IsOsPlatform('Windows'))">netcoreapp3.0;net472</TargetFrameworks>
<TargetFrameworks Condition="!$([MSBuild]::IsOsPlatform('Windows'))">netcoreapp3.0</TargetFrameworks>
<TargetFrameworks Condition="$([MSBuild]::IsOsPlatform('Windows'))">netcoreapp3.1;net472</TargetFrameworks>
<TargetFrameworks Condition="!$([MSBuild]::IsOsPlatform('Windows'))">netcoreapp3.1</TargetFrameworks>

</PropertyGroup>

Expand Down
14 changes: 7 additions & 7 deletions sample/ManualTestApp/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ static async Task Main(string[] args)
var cacheHelper = await CreateCacheHelperAsync().ConfigureAwait(false);
cacheHelper.RegisterCache(pca.UserTokenCache);

// The token cache helper provides a high level event that informs apps about added / removed accounts.
cacheHelper.CacheChanged += (s, e) =>
// Advanced scenario for when 2 or more apps share the same cache
cacheHelper.CacheChanged += (s, e) => // this event is very expensive perf wise
{
Console.BackgroundColor = ConsoleColor.DarkCyan;
Console.WriteLine($"Cache Changed, Added: {e.AccountsAdded.Count()} Removed: {e.AccountsRemoved.Count()}");
Expand Down Expand Up @@ -276,8 +276,7 @@ private static async Task<MsalCacheHelper> CreateCacheHelperAsync()
{
storageProperties = new StorageCreationPropertiesBuilder(
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Example usage is here - you can see that Client_ID is no longer a mandatory param. If people need CacheChanged event, they need to configure both "client_id" and "authority". If they don't, they'll get a good error message when subscribing to CacheChanged

Config.CacheFileName,
Config.CacheDir,
Config.ClientId)
Config.CacheDir)
.WithLinuxKeyring(
Config.LinuxKeyRingSchema,
Config.LinuxKeyRingCollection,
Expand All @@ -287,6 +286,9 @@ private static async Task<MsalCacheHelper> CreateCacheHelperAsync()
.WithMacKeyChain(
Config.KeyChainServiceName,
Config.KeyChainAccountName)
.WithCacheChangedEvent( // do NOT use unless really necessary, high perf penalty!
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CacheChanged event is scoped to this (client_id, authority) pair. There isn't (currently) a way to get events about other pairs, except by creating different instances of the MsalCacheHelper object.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it mean that you won't have changes in the FOCI ?

Config.ClientId,
Config.Authority)
.Build();

var cacheHelper = await MsalCacheHelper.CreateAsync(
Expand All @@ -305,8 +307,7 @@ private static async Task<MsalCacheHelper> CreateCacheHelperAsync()
storageProperties =
new StorageCreationPropertiesBuilder(
Config.CacheFileName,
Config.CacheDir,
Config.ClientId)
Config.CacheDir)
.WithLinuxUnprotectedFile()
.WithMacKeyChain(
Config.KeyChainServiceName,
Expand All @@ -318,7 +319,6 @@ private static async Task<MsalCacheHelper> CreateCacheHelperAsync()

return cacheHelper;
}

}

private static IPublicClientApplication CreatePublicClient(string authority)
Expand Down
104 changes: 69 additions & 35 deletions src/Microsoft.Identity.Client.Extensions.Msal/MsalCacheHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -68,15 +68,32 @@ public class MsalCacheHelper
/// <summary>
/// Watches a filesystem location in order to fire events when the cache on disk is changed. Internal for testing.
/// </summary>
internal readonly FileSystemWatcher _cacheWatcher;
private readonly FileSystemWatcher _cacheWatcher;

private EventHandler<CacheChangedEventArgs> _cacheChangedEventHandler;
/// <summary>
/// Allows clients to listen for cache updates originating from disk.
/// </summary>
/// <remarks>
/// This event does not fire when the application is built against Mone framework (e.g. Xamarin.Mac), but it does fire on .Net Core on all 3 operating systems.
/// </remarks>
public event EventHandler<CacheChangedEventArgs> CacheChanged;
public event EventHandler<CacheChangedEventArgs> CacheChanged
{
add
{
if (!_storageCreationProperties.IsCacheEventConfigured)
{
throw new InvalidOperationException(
"To use this event, please configure the clientId and the authority " +
"using StorageCreationPropertiesBuilder.WithCacheChangedEvent");
}
_cacheChangedEventHandler += value;
}
remove
{
_cacheChangedEventHandler -= value;
}
}

/// <summary>
/// Contains a reference to all caches currently registered to synchronize with this MsalCacheHelper, along with
Expand All @@ -93,9 +110,14 @@ public class MsalCacheHelper
TraceSourceLogger logger)
{
var accountIdentifiers = new HashSet<string>();
if (File.Exists(storageCreationProperties.CacheFilePath))

if (storageCreationProperties.IsCacheEventConfigured &&
File.Exists(storageCreationProperties.CacheFilePath))
{
var pca = PublicClientApplicationBuilder.Create(storageCreationProperties.ClientId).Build();
var pca = PublicClientApplicationBuilder
.Create(storageCreationProperties.ClientId)
.WithAuthority(storageCreationProperties.Authority)
.Build();

pca.UserTokenCache.SetBeforeAccess((args) =>
{
Expand Down Expand Up @@ -136,7 +158,7 @@ public class MsalCacheHelper
private MsalCacheHelper(
StorageCreationProperties storageCreationProperties,
TraceSource logger,
HashSet<string> knownAccountIds,
HashSet<string> knownAccountIds, // only used for CacheChangedEvent
FileSystemWatcher cacheWatcher)
{
_logger = logger == null ? s_staticLogger.Value : new TraceSourceLogger(logger);
Expand All @@ -145,44 +167,46 @@ private MsalCacheHelper(
_knownAccountIds = knownAccountIds;

_cacheWatcher = cacheWatcher;
_cacheWatcher.Changed += OnCacheFileChangedAsync;
_cacheWatcher.Deleted += OnCacheFileChangedAsync;
if (_cacheWatcher != null)
{
_cacheWatcher.Changed += OnCacheFileChangedAsync;
_cacheWatcher.Deleted += OnCacheFileChangedAsync;
}
}

private async void OnCacheFileChangedAsync(object sender, FileSystemEventArgs args)
{
// avoid the high cost of computing the added / removed accounts if nobody listens to this
if (CacheChanged == null)
if (_cacheChangedEventHandler?.GetInvocationList() != null &&
_cacheChangedEventHandler?.GetInvocationList().Length > 0)
{
return;
}
try
{
IEnumerable<string> added;
IEnumerable<string> removed;

try
{
IEnumerable<string> added;
IEnumerable<string> removed;
using (CreateCrossPlatLock(_storageCreationProperties))
{
var currentAccountIds = await GetAccountIdentifiersNoLockAsync(_storageCreationProperties, _logger).ConfigureAwait(false);

using (CreateCrossPlatLock(_storageCreationProperties))
{
var currentAccountIds = await GetAccountIdentifiersNoLockAsync(_storageCreationProperties, _logger).ConfigureAwait(false);
var intersect = currentAccountIds.Intersect(_knownAccountIds);
removed = _knownAccountIds.Except(intersect);
added = currentAccountIds.Except(intersect);

var intersect = currentAccountIds.Intersect(_knownAccountIds);
removed = _knownAccountIds.Except(intersect);
added = currentAccountIds.Except(intersect);
_knownAccountIds = currentAccountIds;
}

_knownAccountIds = currentAccountIds;
if (added.Any() || removed.Any())
{
_cacheChangedEventHandler.Invoke(sender, new CacheChangedEventArgs(added, removed));
}
}

if (added.Any() || removed.Any())
catch (Exception e)
{
CacheChanged.Invoke(sender, new CacheChangedEventArgs(added, removed));
// Never let this throw, just log errors
_logger.LogWarning($"Exception within File Watcher : {e}");
}
}
catch (Exception e)
{
// Never let this throw, just log errors
_logger.LogWarning($"Exception within File Watcher : {e}");
}
}

/// <summary>
Expand All @@ -206,7 +230,7 @@ internal MsalCacheHelper(ITokenCache userTokenCache, MsalCacheStorage store, Tra
/// Creates a new instance of <see cref="MsalCacheHelper"/>. To configure MSAL to use this cache persistence, call <see cref="RegisterCache(ITokenCache)"/>
/// </summary>
/// <param name="storageCreationProperties">Properties to use when creating storage on disk.</param>
/// <param name="logger">Passing null uses a default logger</param>
/// <param name="logger">Passing null uses the default TraceSource logger. See https://github.com/AzureAD/microsoft-authentication-extensions-for-dotnet/wiki/Logging for details.</param>
/// <returns>A new instance of <see cref="MsalCacheHelper"/>.</returns>
public static async Task<MsalCacheHelper> CreateAsync(StorageCreationProperties storageCreationProperties, TraceSource logger = null)
{
Expand All @@ -219,17 +243,27 @@ public static async Task<MsalCacheHelper> CreateAsync(StorageCreationProperties
using (CreateCrossPlatLock(storageCreationProperties))
{
// Cache the list of accounts

var ts = logger == null ? s_staticLogger.Value : new TraceSourceLogger(logger);
var accountIdentifiers = await GetAccountIdentifiersNoLockAsync(storageCreationProperties, ts).ConfigureAwait(false);
var cacheWatcher = new FileSystemWatcher(storageCreationProperties.CacheDirectory, storageCreationProperties.CacheFileName);

HashSet<string> accountIdentifiers = null;
FileSystemWatcher cacheWatcher = null;

if (storageCreationProperties.IsCacheEventConfigured)
{
accountIdentifiers = await GetAccountIdentifiersNoLockAsync(storageCreationProperties, ts)
.ConfigureAwait(false);
cacheWatcher = new FileSystemWatcher(
storageCreationProperties.CacheDirectory,
storageCreationProperties.CacheFileName);
}

var helper = new MsalCacheHelper(storageCreationProperties, logger, accountIdentifiers, cacheWatcher);

try
{
if (!SharedUtilities.IsMonoPlatform())
if (!SharedUtilities.IsMonoPlatform() && storageCreationProperties.IsCacheEventConfigured)
{
cacheWatcher.EnableRaisingEvents = true;
cacheWatcher.EnableRaisingEvents = true;
}
}
catch (PlatformNotSupportedException)
Expand Down
4 changes: 3 additions & 1 deletion src/Shared/EnvUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,11 @@ namespace Microsoft.Identity.Client.Extensions.Msal
internal static class EnvUtils
{
internal const string TraceLevelEnvVarName = "IDENTITYEXTENSIONTRACELEVEL";
private const string DefaultTraceSource = "Microsoft.Identity.Client.Extensions.TraceSource";

internal static TraceSource GetNewTraceSource(string sourceName)
{
sourceName = sourceName ?? DefaultTraceSource;
#if DEBUG
var level = SourceLevels.Verbose;
#else
Expand All @@ -29,7 +31,7 @@ internal static TraceSource GetNewTraceSource(string sourceName)
level = result;
}

return new TraceSource("Microsoft.Identity.Client.Extensions.TraceSource", level);
return new TraceSource(sourceName, level);
}
}
}
32 changes: 25 additions & 7 deletions src/Shared/StorageCreationProperties.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ namespace Microsoft.Identity.Client.Extensions.Msal
namespace Microsoft.Identity.Client.Extensions.Web
#endif
{
/// <summary>
/// An immutable class containing information required to instantiate storage objects for MSAL caches in various platforms.
/// </summary>
public class StorageCreationProperties
/// <summary>
/// An immutable class containing information required to instantiate storage objects for MSAL caches in various platforms.
/// </summary>
public class StorageCreationProperties
{
/// <summary>
/// This constructor is intentionally internal. To get one of these objects use <see cref="StorageCreationPropertiesBuilder.Build"/>.
Expand All @@ -33,9 +33,10 @@ internal StorageCreationProperties(
string keyringSecretLabel,
KeyValuePair<string, string> keyringAttribute1,
KeyValuePair<string, string> keyringAttribute2,
string clientId,
int lockRetryDelay,
int lockRetryCount)
int lockRetryCount,
string clientId,
string authority)
{
CacheFileName = cacheFileName;
CacheDirectory = cacheDirectory;
Expand All @@ -53,6 +54,7 @@ internal StorageCreationProperties(
KeyringAttribute2 = keyringAttribute2;

ClientId = clientId;
Authority = authority;
LockRetryDelay = lockRetryDelay;
LockRetryCount = lockRetryCount;
}
Expand Down Expand Up @@ -123,8 +125,24 @@ internal StorageCreationProperties(
public readonly int LockRetryCount;

/// <summary>
/// The client id
/// The client id.
/// </summary>
/// <remarks> Only required for the MsalCacheHelper.CacheChanged event</remarks>
public string ClientId { get; }

/// <summary>
/// The authority
/// </summary>
/// <remarks> Only required for the MsalCacheHelper.CacheChanged event</remarks>
public string Authority { get; }

internal bool IsCacheEventConfigured
{
get
{
return !string.IsNullOrEmpty(ClientId) &&
!string.IsNullOrEmpty(Authority);
}
}
}
}
Loading