Beispiel #1
0
        /// <summary>
        /// Initializes a new instance of the <see cref="AdalCache"/> class.
        /// </summary>
        /// <param name="storage">Adal cache storage</param>
        /// <param name="logger">Logger</param>
        /// <param name="lockRetryDelay">Delay in ms between retries if cache lock is contended</param>
        /// <param name="lockRetryCount">Number of retries if cache lock is contended</param>
        public AdalCache(AdalCacheStorage storage, TraceSource logger, int lockRetryDelay, int lockRetryCount)
        {
            _logger             = logger == null ? s_staticLogger.Value : new TraceSourceLogger(logger);
            _store              = storage ?? throw new ArgumentNullException(nameof(storage));
            _lockFileRetryCount = lockRetryCount;
            _lockFileRetryDelay = lockRetryDelay;

            AfterAccess  = AfterAccessNotification;
            BeforeAccess = BeforeAccessNotification;

            _logger.LogInformation($"Initializing adal cache");

            byte[] data = _store.ReadData();

            _logger.LogInformation($"Read '{data?.Length}' bytes from storage");

            if (data != null && data.Length > 0)
            {
                try
                {
                    _logger.LogInformation($"Deserializing data into memory");
                    DeserializeAdalV3(data);
                }
                catch (Exception e)
                {
                    _logger.LogInformation($"An exception was encountered while deserializing the data during initialization of {nameof(AdalCache)} : {e}");
                    DeserializeAdalV3(null);
                    _store.Clear();
                }
            }

            _logger.LogInformation($"Done initializing");
        }
Beispiel #2
0
        public void CacheStorageCanHandleMultipleExceptionsWhenReading()
        {
            // Arrange
            var stringListener = new TraceStringListener();
            var cacheAccessor  = Substitute.For <ICacheAccessor>();
            var exception      = new InvalidOperationException("some error");

            cacheAccessor.Read().Throws(exception);
            cacheAccessor.When((x) => x.Clear()).Do(x => throw exception);
            _logger.Listeners.Add(stringListener);
            var actualLogger = new TraceSourceLogger(_logger);
            var storage      = new Storage(s_storageCreationProperties, cacheAccessor, actualLogger);

            // Act
            byte[] result = null;
            try
            {
                result = storage.ReadData();
            }
            catch (Exception ex)
            {
                // ignore
                Assert.IsTrue(ex is InvalidOperationException);
            }

            // Assert
            Assert.IsTrue(stringListener.CurrentLog.Contains("TestSource Error"));
            Assert.IsTrue(stringListener.CurrentLog.Contains("InvalidOperationException"));
            Assert.IsTrue(stringListener.CurrentLog.Contains("some error"));
        }
Beispiel #3
0
        internal static void TryProcessFile(Action action, TraceSourceLogger logger)
        {
            for (int tryCount = 0; tryCount <= FileLockRetryCount; tryCount++)
            {
                try
                {
                    action.Invoke();
                    return;
                }
                catch (Exception e)
                {
                    Thread.Sleep(TimeSpan.FromMilliseconds(FileLockRetryWaitInMs));



                    if (tryCount == FileLockRetryCount)
                    {
                        logger.LogError($"An exception was encountered while processing the cache file ex:'{e}'");
                    }
                    else
                    {
                        logger.LogWarning($"An exception was encountered while processing the cache file. Operation will be retried. Ex:'{e}'");
                    }
                }
            }
        }
        /// <summary>
        /// An internal constructor allowing unit tests to data explicitly rather than initializing here.
        /// </summary>
        /// <param name="userTokenCache">The token cache to synchronize with the backing store</param>
        /// <param name="store">The backing store to use.</param>
        /// <param name="logger">Passing null uses the default logger</param>
        internal MsalCacheHelper(ITokenCache userTokenCache, MsalCacheStorage store, TraceSource logger = null)
        {
            _logger    = logger == null ? s_staticLogger.Value : new TraceSourceLogger(logger);
            CacheStore = store;
            _storageCreationProperties = store.StorageCreationProperties;

            RegisterCache(userTokenCache);
        }
Beispiel #5
0
 internal /* internal for test, otherwise private */ MsalCacheStorage(
     StorageCreationProperties creationProperties,
     ICacheAccessor cacheAccessor,
     TraceSourceLogger logger)
 {
     StorageCreationProperties = creationProperties;
     _logger       = logger;
     CacheAccessor = cacheAccessor;
     _logger.LogInformation($"Initialized '{nameof(MsalCacheStorage)}'");
 }
        public DpApiEncryptedFileAccessor(string cacheFilePath, TraceSourceLogger logger)
        {
            if (string.IsNullOrEmpty(cacheFilePath))
            {
                throw new ArgumentNullException(nameof(cacheFilePath));
            }

            _cacheFilePath           = cacheFilePath;
            _logger                  = logger ?? throw new ArgumentNullException(nameof(logger));
            _unencryptedFileAccessor = new FileAccessor(_cacheFilePath, _logger);
        }
Beispiel #7
0
        /// <summary>
        /// Gets the current set of accounts in the cache by creating a new public client, and
        /// deserializing the cache into a temporary object.
        /// </summary>
        private static async Task <HashSet <string> > GetAccountIdentifiersNoLockAsync(  // executed in a cross plat lock context
            StorageCreationProperties storageCreationProperties,
            TraceSourceLogger logger)
        {
            var accountIdentifiers = new HashSet <string>();

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

                pca.UserTokenCache.SetBeforeAccess((args) =>
                {
                    Storage tempCache = null;
                    try
                    {
                        tempCache = Storage.Create(storageCreationProperties, s_staticLogger.Value.Source);
                        // We're using ReadData here so that decryption is handled within the store.
                        byte[] data = null;
                        try
                        {
                            data = tempCache.ReadData();
                        }
                        catch
                        {
                            // ignore read failures, we will try again
                        }

                        if (data != null)
                        {
                            args.TokenCache.DeserializeMsalV3(data);
                        }
                    }
                    catch (Exception e)
                    {
                        logger.LogError("An error occured while reading the token cache: " + e);
                        logger.LogError("Deleting the token cache as it might be corrupt.");
                        tempCache.Clear(ignoreExceptions: true);
                    }
                });

                var accounts = await pca.GetAccountsAsync().ConfigureAwait(false);

                foreach (var account in accounts)
                {
                    accountIdentifiers.Add(account.HomeAccountId.Identifier);
                }
            }

            return(accountIdentifiers);
        }
Beispiel #8
0
        internal static void WriteDataToFile(string filePath, byte[] data, TraceSourceLogger logger)
        {
            EnsureParentDirectoryExists(filePath, logger);

            logger.LogInformation($"Writing cache file");

            TryProcessFile(() =>
            {
                File.WriteAllBytes(filePath, data);
            }, logger);
        }
Beispiel #9
0
        private static void EnsureParentDirectoryExists(string filePath, TraceSourceLogger logger)
        {
            string directoryForCacheFile = Path.GetDirectoryName(filePath);

            if (!Directory.Exists(directoryForCacheFile))
            {
                string directory = Path.GetDirectoryName(filePath);
                logger.LogInformation($"Creating directory '{directory}'");
                Directory.CreateDirectory(directory);
            }
        }
        /// <summary>
        /// Initializes a new instance of the <see cref="Storage"/> class.
        /// The actual cache reading and writing is OS specific:
        /// <list type="bullet">
        /// <item>
        ///     <term>Windows</term>
        ///     <description>DPAPI encrypted file on behalf of the user. </description>
        /// </item>
        /// <item>
        ///     <term>Mac</term>
        ///     <description>Cache is stored in KeyChain.  </description>
        /// </item>
        /// <item>
        ///     <term>Linux</term>
        ///     <description>Cache is stored in Gnome KeyRing - https://developer.gnome.org/libsecret/0.18/  </description>
        /// </item>
        /// </list>
        /// </summary>
        /// <param name="creationProperties">Properties for creating the cache storage on disk</param>
        /// <param name="logger">logger</param>
        /// <returns></returns>
        public static Storage Create(StorageCreationProperties creationProperties, TraceSource logger = null)
        {
            TraceSourceLogger actualLogger = logger == null ? s_staticLogger.Value : new TraceSourceLogger(logger);

            ICacheAccessor cacheAccessor;

            if (creationProperties.UseUnencryptedFallback)
            {
                cacheAccessor = new FileAccessor(creationProperties.CacheFilePath, setOwnerOnlyPermissions: true, logger: actualLogger);
            }
            else
            {
                if (SharedUtilities.IsWindowsPlatform())
                {
                    cacheAccessor = new DpApiEncryptedFileAccessor(creationProperties.CacheFilePath, logger: actualLogger);
                }
                else if (SharedUtilities.IsMacPlatform())
                {
                    cacheAccessor = new MacKeychainAccessor(
                        creationProperties.CacheFilePath,
                        creationProperties.MacKeyChainServiceName,
                        creationProperties.MacKeyChainAccountName,
                        actualLogger);
                }
                else if (SharedUtilities.IsLinuxPlatform())
                {
                    if (creationProperties.UseLinuxUnencryptedFallback)
                    {
                        cacheAccessor = new FileAccessor(creationProperties.CacheFilePath, setOwnerOnlyPermissions: true, actualLogger);
                    }
                    else
                    {
                        cacheAccessor = new LinuxKeyringAccessor(
                            creationProperties.CacheFilePath,
                            creationProperties.KeyringCollection,
                            creationProperties.KeyringSchemaName,
                            creationProperties.KeyringSecretLabel,
                            creationProperties.KeyringAttribute1.Key,
                            creationProperties.KeyringAttribute1.Value,
                            creationProperties.KeyringAttribute2.Key,
                            creationProperties.KeyringAttribute2.Value,
                            actualLogger);
                    }
                }
                else
                {
                    throw new PlatformNotSupportedException();
                }
            }

            return(new Storage(creationProperties, cacheAccessor, actualLogger));
        }
Beispiel #11
0
        public void ReadCanThrowExceptions()
        {
            // Arrange
            var actualLogger  = new TraceSourceLogger(_logger);
            var cacheAccessor = NSubstitute.Substitute.For <ICacheAccessor>();

            cacheAccessor.Read().Throws(new InvalidOperationException());
            var storage = new Storage(s_storageCreationProperties, cacheAccessor, actualLogger);

            // Assert
            AssertException.Throws <InvalidOperationException>(
                () => storage.ReadData());
        }
Beispiel #12
0
        public void WriteCanThrowExceptions()
        {
            // Arrange
            var actualLogger  = new TraceSourceLogger(_logger);
            var cacheAccessor = NSubstitute.Substitute.For <ICacheAccessor>();

            cacheAccessor.WhenForAnyArgs(c => c.Write(null)).Throw(new InvalidOperationException());
            var storage = new Storage(s_storageCreationProperties, cacheAccessor, actualLogger);

            // Assert
            AssertException.Throws <InvalidOperationException>(
                () => storage.WriteData(new byte[0]));
        }
        /// <summary>
        /// Creates a new instance of this class.
        /// </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="knownAccountIds">The set of known accounts</param>
        /// <param name="cacheWatcher">Watcher for the cache file, to enable sending updated events</param>
        private MsalCacheHelper(
            StorageCreationProperties storageCreationProperties,
            TraceSource logger,
            HashSet <string> knownAccountIds,
            FileSystemWatcher cacheWatcher)
        {
            _logger = logger == null ? s_staticLogger.Value : new TraceSourceLogger(logger);
            _storageCreationProperties = storageCreationProperties;
            CacheStore       = MsalCacheStorage.Create(_storageCreationProperties, _logger.Source);
            _knownAccountIds = knownAccountIds;

            _cacheWatcher          = cacheWatcher;
            _cacheWatcher.Changed += OnCacheFileChangedAsync;
            _cacheWatcher.Deleted += OnCacheFileChangedAsync;
        }
        public void CacheStorageReadCanHandleReadingNull()
        {
            // Arrange
            var cacheAccessor = NSubstitute.Substitute.For <ICacheAccessor>();

            cacheAccessor.Read().Returns((byte[])null);

            var actualLogger = new TraceSourceLogger(_logger);
            var storage      = new MsalCacheStorage(s_storageCreationProperties, cacheAccessor, actualLogger);

            // Act
            byte[] result = storage.ReadData();

            // Assert
            Assert.AreEqual(0, result.Length);
        }
        public void ClearCanThrowExceptions()
        {
            // Arrange
            var actualLogger  = new TraceSourceLogger(_logger);
            var cacheAccessor = NSubstitute.Substitute.For <ICacheAccessor>();

            cacheAccessor.WhenForAnyArgs(c => c.Clear()).Throw(new InvalidOperationException());
            var storage = new MsalCacheStorage(s_storageCreationProperties, cacheAccessor, actualLogger);

            // Act
            storage.Clear();

            // Assert
            AssertException.Throws <InvalidOperationException>(
                () => storage.Clear(ignoreExceptions: false));
        }
Beispiel #16
0
 public OculiServiceLogger()
 {
     this.fileSystem  = (IFileSystem) new FileSystem();
     this.traceSource = new TraceSource("OculiService", SourceLevels.All);
     this.logger      = new TraceSourceLogger(this.traceSource);
     this.eventLog    = EventLogExtensions.CreateEventLog("Application", ".", "Oculi Service");
     if (EventLog.SourceExists("Oculi Service"))
     {
         EventLog.DeleteEventSource("Oculi Service");
     }
     EventLog.CreateEventSource(new EventSourceCreationData("Oculi Service", "Application")
     {
         //MessageResourceFile = PathHelpers.GetFullPath("DoubleTake.ManagementService.EventMessages.dll", RelativeFolder.Application)
     });
     this.ConfigureListeners();
     Tracer.ConfigurationChanged += new EventHandler(this.Tracer_ConfigurationChanged);
 }
Beispiel #17
0
        public void VerifyPersistenceThrowsIfDataReadIsEmpty()
        {
            // Arrange
            var actualLogger  = new TraceSourceLogger(_logger);
            var cacheAccessor = Substitute.For <ICacheAccessor>();

            cacheAccessor.CreateForPersistenceValidation().Returns(cacheAccessor);
            var storage = new Storage(s_storageCreationProperties, cacheAccessor, actualLogger);


            // Act
            var ex = AssertException.Throws <MsalCachePersistenceException>(
                () => storage.VerifyPersistence());

            // Assert
            Assert.IsNull(ex.InnerException); // no more details available
        }
Beispiel #18
0
        public LinuxKeyringAccessor(
            string cacheFilePath,
            string keyringCollection,
            string keyringSchemaName,
            string keyringSecretLabel,
            string attributeKey1,
            string attributeValue1,
            string attributeKey2,
            string attributeValue2,
            TraceSourceLogger logger)
        {
            if (string.IsNullOrWhiteSpace(cacheFilePath))
            {
                throw new ArgumentNullException(nameof(cacheFilePath));
            }

            if (string.IsNullOrWhiteSpace(attributeKey1))
            {
                throw new ArgumentNullException(nameof(attributeKey1));
            }

            if (string.IsNullOrWhiteSpace(attributeValue1))
            {
                throw new ArgumentNullException(nameof(attributeValue1));
            }

            if (string.IsNullOrWhiteSpace(attributeKey2))
            {
                throw new ArgumentNullException(nameof(attributeKey2));
            }

            if (string.IsNullOrWhiteSpace(attributeValue2))
            {
                throw new ArgumentNullException(nameof(attributeValue2));
            }

            _cacheFilePath      = cacheFilePath;
            _keyringCollection  = keyringCollection;
            _keyringSchemaName  = keyringSchemaName;
            _keyringSecretLabel = keyringSecretLabel;
            _attributeKey1      = attributeKey1;
            _attributeValue1    = attributeValue1;
            _attributeKey2      = attributeKey2;
            _attributeValue2    = attributeValue2;
            _logger             = logger ?? throw new ArgumentNullException(nameof(logger));
        }
        public void ReadCanThrowExceptions()
        {
            // Arrange
            var actualLogger  = new TraceSourceLogger(_logger);
            var cacheAccessor = NSubstitute.Substitute.For <ICacheAccessor>();

            cacheAccessor.Read().Throws(new InvalidOperationException());
            var storage = new MsalCacheStorage(s_storageCreationProperties, cacheAccessor, actualLogger);

            // Act
            byte[] result = storage.ReadData();
            Assert.AreEqual(0, result.Length);

            // Assert
            AssertException.Throws <InvalidOperationException>(
                () => storage.ReadData(ignoreExceptions: false));
        }
        public void CacheStorageReadCanHandleExceptionsWhenReading()
        {
            // Arrange
            var cacheAccessor = NSubstitute.Substitute.For <ICacheAccessor>();
            var exception     = new InvalidOperationException();

            cacheAccessor.Read().Throws(exception);

            var actualLogger = new TraceSourceLogger(_logger);
            var storage      = new MsalCacheStorage(s_storageCreationProperties, cacheAccessor, actualLogger);

            // Act
            byte[] result = storage.ReadData();

            // Assert
            Assert.AreEqual(0, result.Length);
        }
Beispiel #21
0
        /// <summary>
        /// Changes the LastWriteTime of the file, without actually writing anything to it.
        /// </summary>
        /// <remarks>
        /// Creates the file if it does not exist.
        /// This operation will enable a <see cref="FileSystemWatcher"/> to fire.
        /// </remarks>
        internal static void TouchFile(string filePath, TraceSourceLogger logger)
        {
            EnsureParentDirectoryExists(filePath, logger);
            logger.LogInformation($"Touching file...");

            TryProcessFile(() =>
            {
                if (!File.Exists(filePath))
                {
                    logger.LogInformation($"File {filePath} does not exist. Creating it..");

                    var fs = File.Create(filePath);
                    fs.Dispose();
                }

                File.SetLastWriteTimeUtc(filePath, DateTime.UtcNow);
            }, logger);
        }
        internal static void WriteDataToFile(string filePath, byte[] data, TraceSourceLogger logger)
        {
            string directoryForCacheFile = Path.GetDirectoryName(filePath);

            if (!Directory.Exists(directoryForCacheFile))
            {
                string directory = Path.GetDirectoryName(filePath);
                logger.LogInformation($"Creating directory '{directory}'");
                Directory.CreateDirectory(directory);
            }

            logger.LogInformation($"Cache file directory exists. '{Directory.Exists(directoryForCacheFile)}' now writing cache file");

            TryProcessFile(() =>
            {
                File.WriteAllBytes(filePath, data);
            }, logger);
        }
        public void VerifyPersistenceThrowsIfDataReadIsDiffrentFromDataWritten()
        {
            // Arrange
            var actualLogger  = new TraceSourceLogger(_logger);
            var cacheAccessor = Substitute.For <ICacheAccessor>();

            cacheAccessor.CreateForPersistenceValidation().Returns(cacheAccessor);
            var storage = new MsalCacheStorage(s_storageCreationProperties, cacheAccessor, actualLogger);

            cacheAccessor.Read().Returns(Encoding.UTF8.GetBytes("other_dummy_data"));

            // Act
            var ex = AssertException.Throws <MsalCachePersistenceException>(
                () => storage.VerifyPersistence());

            // Assert
            Assert.IsNull(ex.InnerException); // no more details available
        }
Beispiel #24
0
        public void VerifyPersistenceThrowsInnerExceptions()
        {
            // Arrange
            var actualLogger  = new TraceSourceLogger(_logger);
            var cacheAccessor = Substitute.For <ICacheAccessor>();

            cacheAccessor.CreateForPersistenceValidation().Returns(cacheAccessor);
            var exception = new InvalidOperationException("some error");
            var storage   = new Storage(s_storageCreationProperties, cacheAccessor, actualLogger);

            cacheAccessor.Read().Throws(exception);

            // Act
            var ex = AssertException.Throws <MsalCachePersistenceException>(
                () => storage.VerifyPersistence());

            // Assert
            Assert.AreEqual(ex.InnerException, exception);
        }
        internal static void TryProcessFile(Action action, TraceSourceLogger logger)
        {
            for (int tryCount = 0; tryCount <= FileLockRetryCount; tryCount++)
            {
                try
                {
                    action.Invoke();
                    return;
                }
                catch (Exception e)
                {
                    Thread.Sleep(TimeSpan.FromMilliseconds(FileLockRetryWaitInMs));

                    if (tryCount == FileLockRetryCount)
                    {
                        logger.LogError($"An exception was encountered while processing the cache file from the {nameof(MsalCacheStorage)} ex:'{e}'");
                    }
                }
            }
        }
Beispiel #26
0
        internal static void DeleteCacheFile(string filePath, TraceSourceLogger logger)
        {
            bool cacheFileExists = File.Exists(filePath);

            logger.LogInformation($"DeleteCacheFile Cache file exists '{cacheFileExists}'");

            TryProcessFile(() =>
            {
                logger.LogInformation("Before deleting the cache file");
                try
                {
                    File.Delete(filePath);
                }
                catch (Exception e)
                {
                    logger.LogError($"Problem deleting the cache file '{e}'");
                }

                logger.LogInformation($"After deleting the cache file.");
            }, logger);
        }
Beispiel #27
0
        public MacKeychainAccessor(string cacheFilePath, string keyChainServiceName, string keyChainAccountName, TraceSourceLogger logger)
        {
            if (string.IsNullOrWhiteSpace(cacheFilePath))
            {
                throw new ArgumentNullException(nameof(cacheFilePath));
            }

            if (string.IsNullOrWhiteSpace(keyChainServiceName))
            {
                throw new ArgumentNullException(nameof(keyChainServiceName));
            }

            if (string.IsNullOrWhiteSpace(keyChainAccountName))
            {
                throw new ArgumentNullException(nameof(keyChainAccountName));
            }

            _cacheFilePath       = cacheFilePath;
            _keyChainServiceName = keyChainServiceName;
            _keyChainAccountName = keyChainAccountName;
            _logger = logger ?? throw new ArgumentNullException(nameof(logger));
        }
        /// <summary>
        /// Gets the current set of accounts in the cache by creating a new public client, and
        /// deserializing the cache into a temporary object.
        /// </summary>
        private static async Task <HashSet <string> > GetAccountIdentifiersAsync(
            StorageCreationProperties storageCreationProperties,
            TraceSourceLogger logger)
        {
            var accountIdentifiers = new HashSet <string>();

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

                pca.UserTokenCache.SetBeforeAccess((args) =>
                {
                    MsalCacheStorage tempCache = null;
                    try
                    {
                        tempCache = MsalCacheStorage.Create(storageCreationProperties, s_staticLogger.Value.Source);
                        // We're using ReadData here so that decryption is handled within the store.
                        var data = tempCache.ReadData();
                        args.TokenCache.DeserializeMsalV3(data);
                    }
                    catch (Exception e)
                    {
                        logger.LogError("An error occured while reading the token cache: " + e);
                        logger.LogError("Deleting the token cache as it might be corrupt.");
                        tempCache.Clear();
                    }
                });

                var accounts = await pca.GetAccountsAsync().ConfigureAwait(false);

                foreach (var account in accounts)
                {
                    accountIdentifiers.Add(account.HomeAccountId.Identifier);
                }
            }

            return(accountIdentifiers);
        }
Beispiel #29
0
        public void VerifyPersistenceHappyPath()
        {
            // Arrange
            byte[] dummyData     = Encoding.UTF8.GetBytes(Storage.PersistenceValidationDummyData);
            var    actualLogger  = new TraceSourceLogger(_logger);
            var    cacheAccessor = Substitute.For <ICacheAccessor>();

            cacheAccessor.CreateForPersistenceValidation().Returns(cacheAccessor);
            var storage = new Storage(s_storageCreationProperties, cacheAccessor, actualLogger);

            cacheAccessor.Read().Returns(dummyData);

            // Act
            storage.VerifyPersistence();

            // Assert
            Received.InOrder(() => {
                cacheAccessor.CreateForPersistenceValidation();
                cacheAccessor.Write(Arg.Any <byte[]>());
                cacheAccessor.Read();
                cacheAccessor.Clear();
            });
        }
 internal FileAccessor(string cacheFilePath, bool setOwnerOnlyPermissions, TraceSourceLogger logger)
 {
     _cacheFilePath          = cacheFilePath;
     _setOwnerOnlyPermission = setOwnerOnlyPermissions;
     _logger = logger ?? throw new ArgumentNullException(nameof(logger));
 }