private void EnsureOptions(AzureAppConfigurationOptions azconfigOptions) { if (azconfigOptions == null) { throw new ArgumentNullException(nameof(azconfigOptions)); } if (azconfigOptions.ConnectionString == null) { throw new InvalidOperationException("An Azure App Configuration connection string is required."); } OfflineFileCacheOptions options = _options ?? new OfflineFileCacheOptions(); if ((options.Key == null) || (options.SignKey == null) || (options.IV == null)) { byte[] secret = Convert.FromBase64String(ConnectionStringParser.Parse(azconfigOptions.ConnectionString, "Secret")); using (SHA256 sha256 = SHA256.Create()) { byte[] hash = sha256.ComputeHash(secret); options.Key = options.Key ?? hash; options.SignKey = options.SignKey ?? hash; options.IV = options.IV ?? hash.Take(16).ToArray(); } } if (string.IsNullOrEmpty(_scopeToken)) { // // The default scope token is the configuration store endpoint combined with all of the key-value filters string endpoint = ConnectionStringParser.Parse(azconfigOptions.ConnectionString, "Endpoint"); if (string.IsNullOrWhiteSpace(endpoint)) { throw new InvalidOperationException("Invalid connection string format."); } var sb = new StringBuilder($"{endpoint}\0"); foreach (var selector in azconfigOptions.KeyValueSelectors) { sb.Append($"{selector.KeyFilter}\0{selector.LabelFilter}\0"); } _scopeToken = sb.ToString(); } _options = options; }
/// <summary> /// A cache used for storing Azure App Configuration data using the file system. /// Supports encryption of the stored data. /// </summary> /// <param name="options"> /// Options dictating the behavior of the offline cache. /// If the options are null or the encryption keys are omitted, they will be derived from the store's connection string. /// <see cref="OfflineFileCacheOptions.Path"/> is required unless the application is running inside of an Azure App Service instance, in which case it can be populated automatically. /// </param> public OfflineFileCache(OfflineFileCacheOptions options = null) { OfflineFileCacheOptions opts = options ?? new OfflineFileCacheOptions(); // If the user does not specify the cache path, we will try to use the default path // For the moment, default path is only supported when running inside Azure App Service if (opts.Path == null) { // Generate default cache file name under $home/data/azureAppConfigCache/app{instance}-{hash}.json string homePath = Environment.GetEnvironmentVariable("HOME"); if (Directory.Exists(homePath)) { string dataPath = Path.Combine(homePath, "data"); if (Directory.Exists(dataPath)) { string cachePath = Path.Combine(dataPath, "azureAppConfigCache"); if (!Directory.Exists(cachePath)) { Directory.CreateDirectory(cachePath); } string websiteName = Environment.GetEnvironmentVariable("WEBSITE_SITE_NAME"); if (websiteName != null) { byte[] hash = new byte[0]; using (var sha = SHA1.Create()) { hash = sha.ComputeHash(Encoding.UTF8.GetBytes(websiteName)); } // The instance count will help prevent multiple providers from overwriting each other's cache file Interlocked.Increment(ref instance); opts.Path = Path.Combine(cachePath, $"app{instance}-{BitConverter.ToString(hash).Replace("-", String.Empty)}.json"); } } } if (opts.Path == null) { throw new ArgumentNullException($"{nameof(OfflineFileCacheOptions)}.{nameof(OfflineFileCacheOptions.Path)}", "Default cache path is only supported when running inside of an Azure App Service."); } } ValidateCachePath(opts.Path); _localCachePath = opts.Path; _options = opts; }