From 8efa0f96148d79a08ab04ac3a5eb35b06e077b6d Mon Sep 17 00:00:00 2001
From: Yeming Liu <11371776+isra-fel@users.noreply.github.com>
Date: Thu, 24 Mar 2022 16:38:40 +0800
Subject: [PATCH 1/9] Modified code from Microsot.Extensions.Configuration
---
.../Config/Internal/BinderOptions.cs | 28 +
.../Config/Internal/ConfigurationBinder.cs | 587 ++++++++++++++++++
.../Config/Internal/ConfigurationBuilder.cs | 72 +++
.../Internal/ConfigurationExtensions.cs | 97 +++
.../Internal/ConfigurationKeyComparer.cs | 82 +++
.../Config/Internal/ConfigurationPath.cs | 90 +++
.../Config/Internal/ConfigurationProvider.cs | 97 +++
.../Config/Internal/ConfigurationRoot.cs | 137 ++++
.../Config/Internal/ConfigurationSection.cs | 127 ++++
.../Internal/Interfaces/IConfiguration.cs | 45 ++
.../Interfaces/IConfigurationBuilder.cs | 49 ++
.../Interfaces/IConfigurationProvider.cs | 56 ++
.../Internal/Interfaces/IConfigurationRoot.cs | 38 ++
.../Interfaces/IConfigurationSection.cs | 40 ++
.../Interfaces/IConfigurationSource.cs | 29 +
.../IEnvironmentVariableProvider.cs | 28 +
.../InternalConfigurationRootExtensions.cs | 42 ++
...VariablesConfigurationBuilderExtensions.cs | 29 +
...nvironmentVariablesConfigurationOptions.cs | 27 +
...vironmentVariablesConfigurationProvider.cs | 49 ++
...EnvironmentVariablesConfigurationSource.cs | 33 +
.../Providers/JsonConfigurationExtensions.cs | 42 ++
.../Providers/JsonConfigurationFileParser.cs | 116 ++++
.../JsonStreamConfigurationProvider.cs | 37 ++
.../JsonStreamConfigurationSource.cs | 32 +
.../Providers/StreamConfigurationProvider.cs | 60 ++
.../Providers/StreamConfigurationSource.cs | 37 ++
...bleMemoryConfigurationBuilderExtensions.cs | 33 +
.../UnsettableMemoryConfigurationProvider.cs | 81 +++
.../UnsettableMemoryConfigurationSource.cs | 26 +
30 files changed, 2246 insertions(+)
create mode 100644 src/Accounts/Authentication/Config/Internal/BinderOptions.cs
create mode 100644 src/Accounts/Authentication/Config/Internal/ConfigurationBinder.cs
create mode 100644 src/Accounts/Authentication/Config/Internal/ConfigurationBuilder.cs
create mode 100644 src/Accounts/Authentication/Config/Internal/ConfigurationExtensions.cs
create mode 100644 src/Accounts/Authentication/Config/Internal/ConfigurationKeyComparer.cs
create mode 100644 src/Accounts/Authentication/Config/Internal/ConfigurationPath.cs
create mode 100644 src/Accounts/Authentication/Config/Internal/ConfigurationProvider.cs
create mode 100644 src/Accounts/Authentication/Config/Internal/ConfigurationRoot.cs
create mode 100644 src/Accounts/Authentication/Config/Internal/ConfigurationSection.cs
create mode 100644 src/Accounts/Authentication/Config/Internal/Interfaces/IConfiguration.cs
create mode 100644 src/Accounts/Authentication/Config/Internal/Interfaces/IConfigurationBuilder.cs
create mode 100644 src/Accounts/Authentication/Config/Internal/Interfaces/IConfigurationProvider.cs
create mode 100644 src/Accounts/Authentication/Config/Internal/Interfaces/IConfigurationRoot.cs
create mode 100644 src/Accounts/Authentication/Config/Internal/Interfaces/IConfigurationSection.cs
create mode 100644 src/Accounts/Authentication/Config/Internal/Interfaces/IConfigurationSource.cs
create mode 100644 src/Accounts/Authentication/Config/Internal/Interfaces/IEnvironmentVariableProvider.cs
create mode 100644 src/Accounts/Authentication/Config/Internal/InternalConfigurationRootExtensions.cs
create mode 100644 src/Accounts/Authentication/Config/Internal/Providers/EnvironmentVariablesConfigurationBuilderExtensions.cs
create mode 100644 src/Accounts/Authentication/Config/Internal/Providers/EnvironmentVariablesConfigurationOptions.cs
create mode 100644 src/Accounts/Authentication/Config/Internal/Providers/EnvironmentVariablesConfigurationProvider.cs
create mode 100644 src/Accounts/Authentication/Config/Internal/Providers/EnvironmentVariablesConfigurationSource.cs
create mode 100644 src/Accounts/Authentication/Config/Internal/Providers/JsonConfigurationExtensions.cs
create mode 100644 src/Accounts/Authentication/Config/Internal/Providers/JsonConfigurationFileParser.cs
create mode 100644 src/Accounts/Authentication/Config/Internal/Providers/JsonStreamConfigurationProvider.cs
create mode 100644 src/Accounts/Authentication/Config/Internal/Providers/JsonStreamConfigurationSource.cs
create mode 100644 src/Accounts/Authentication/Config/Internal/Providers/StreamConfigurationProvider.cs
create mode 100644 src/Accounts/Authentication/Config/Internal/Providers/StreamConfigurationSource.cs
create mode 100644 src/Accounts/Authentication/Config/Internal/Providers/UnsettableMemoryConfigurationBuilderExtensions.cs
create mode 100644 src/Accounts/Authentication/Config/Internal/Providers/UnsettableMemoryConfigurationProvider.cs
create mode 100644 src/Accounts/Authentication/Config/Internal/Providers/UnsettableMemoryConfigurationSource.cs
diff --git a/src/Accounts/Authentication/Config/Internal/BinderOptions.cs b/src/Accounts/Authentication/Config/Internal/BinderOptions.cs
new file mode 100644
index 000000000000..48ada32aa867
--- /dev/null
+++ b/src/Accounts/Authentication/Config/Internal/BinderOptions.cs
@@ -0,0 +1,28 @@
+// ----------------------------------------------------------------------------------
+//
+// Copyright Microsoft Corporation
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ----------------------------------------------------------------------------------
+
+namespace Microsoft.Azure.Commands.Common.Authentication.Config.Internal
+{
+ ///
+ /// Options class used by the .
+ ///
+ internal class BinderOptions
+ {
+ ///
+ /// When false (the default), the binder will only attempt to set public properties.
+ /// If true, the binder will attempt to set all non read-only properties.
+ ///
+ public bool BindNonPublicProperties { get; set; }
+ }
+}
diff --git a/src/Accounts/Authentication/Config/Internal/ConfigurationBinder.cs b/src/Accounts/Authentication/Config/Internal/ConfigurationBinder.cs
new file mode 100644
index 000000000000..785c7a23f60d
--- /dev/null
+++ b/src/Accounts/Authentication/Config/Internal/ConfigurationBinder.cs
@@ -0,0 +1,587 @@
+// ----------------------------------------------------------------------------------
+//
+// Copyright Microsoft Corporation
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ----------------------------------------------------------------------------------
+
+using Microsoft.Azure.Commands.Common.Authentication.Config.Internal.Interfaces;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using System.Reflection;
+
+namespace Microsoft.Azure.Commands.Common.Authentication.Config.Internal
+{
+ ///
+ /// Static helper class that allows binding strongly typed objects to configuration values.
+ ///
+ internal static class ConfigurationBinder
+ {
+ ///
+ /// Attempts to bind the configuration instance to a new instance of type T.
+ /// If this configuration section has a value, that will be used.
+ /// Otherwise binding by matching property names against configuration keys recursively.
+ ///
+ /// The type of the new instance to bind.
+ /// The configuration instance to bind.
+ /// The new instance of T if successful, default(T) otherwise.
+ public static (T, string) Get(this IConfiguration configuration)
+ => configuration.Get(_ => { });
+
+ ///
+ /// Attempts to bind the configuration instance to a new instance of type T.
+ /// If this configuration section has a value, that will be used.
+ /// Otherwise binding by matching property names against configuration keys recursively.
+ ///
+ /// The type of the new instance to bind.
+ /// The configuration instance to bind.
+ /// Configures the binder options.
+ /// The new instance of T if successful, default(T) otherwise.
+ public static (T, string) Get(this IConfiguration configuration, Action configureOptions)
+ {
+ if (configuration == null)
+ {
+ throw new ArgumentNullException(nameof(configuration));
+ }
+
+ (object result, string providerId) = configuration.Get(typeof(T), configureOptions);
+ if (result == null)
+ {
+ return (default(T), providerId);
+ }
+ return ((T)result, providerId);
+ }
+
+ ///
+ /// Attempts to bind the configuration instance to a new instance of type T.
+ /// If this configuration section has a value, that will be used.
+ /// Otherwise binding by matching property names against configuration keys recursively.
+ ///
+ /// The configuration instance to bind.
+ /// The type of the new instance to bind.
+ /// The new instance if successful, null otherwise.
+ public static (object, string) Get(this IConfiguration configuration, Type type)
+ => configuration.Get(type, _ => { });
+
+ ///
+ /// Attempts to bind the configuration instance to a new instance of type T.
+ /// If this configuration section has a value, that will be used.
+ /// Otherwise binding by matching property names against configuration keys recursively.
+ ///
+ /// The configuration instance to bind.
+ /// The type of the new instance to bind.
+ /// Configures the binder options.
+ /// The new instance if successful, null otherwise.
+ public static (object, string) Get(this IConfiguration configuration, Type type, Action configureOptions)
+ {
+ if (configuration == null)
+ {
+ throw new ArgumentNullException(nameof(configuration));
+ }
+
+ var options = new BinderOptions();
+ configureOptions?.Invoke(options);
+ object bound = BindInstance(type, instance: null, config: configuration, options: options);
+ string providerId = (configuration as IConfigurationSection).GetValueWithProviderId().Item2;
+ return (bound, providerId);
+ }
+
+ ///
+ /// Attempts to bind the given object instance to the configuration section specified by the key by matching property names against configuration keys recursively.
+ ///
+ /// The configuration instance to bind.
+ /// The key of the configuration section to bind.
+ /// The object to bind.
+ public static void Bind(this IConfiguration configuration, string key, object instance)
+ => configuration.GetSection(key).Bind(instance);
+
+ ///
+ /// Attempts to bind the given object instance to configuration values by matching property names against configuration keys recursively.
+ ///
+ /// The configuration instance to bind.
+ /// The object to bind.
+ public static void Bind(this IConfiguration configuration, object instance)
+ => configuration.Bind(instance, o => { });
+
+ ///
+ /// Attempts to bind the given object instance to configuration values by matching property names against configuration keys recursively.
+ ///
+ /// The configuration instance to bind.
+ /// The object to bind.
+ /// Configures the binder options.
+ public static void Bind(this IConfiguration configuration, object instance, Action configureOptions)
+ {
+ if (configuration == null)
+ {
+ throw new ArgumentNullException(nameof(configuration));
+ }
+
+ if (instance != null)
+ {
+ var options = new BinderOptions();
+ configureOptions?.Invoke(options);
+ BindInstance(instance.GetType(), instance, configuration, options);
+ }
+ }
+
+ ///
+ /// Extracts the value with the specified key and converts it to type T.
+ ///
+ /// The type to convert the value to.
+ /// The configuration.
+ /// The key of the configuration section's value to convert.
+ /// The converted value.
+ public static T GetValue(this IConfiguration configuration, string key)
+ {
+ return GetValue(configuration, key, default(T));
+ }
+
+ ///
+ /// Extracts the value with the specified key and converts it to type T.
+ ///
+ /// The type to convert the value to.
+ /// The configuration.
+ /// The key of the configuration section's value to convert.
+ /// The default value to use if no value is found.
+ /// The converted value.
+ public static T GetValue(this IConfiguration configuration, string key, T defaultValue)
+ {
+ return (T)GetValue(configuration, typeof(T), key, defaultValue);
+ }
+
+ ///
+ /// Extracts the value with the specified key and converts it to the specified type.
+ ///
+ /// The configuration.
+ /// The type to convert the value to.
+ /// The key of the configuration section's value to convert.
+ /// The converted value.
+ public static object GetValue(this IConfiguration configuration, Type type, string key)
+ {
+ return GetValue(configuration, type, key, defaultValue: null);
+ }
+
+ ///
+ /// Extracts the value with the specified key and converts it to the specified type.
+ ///
+ /// The configuration.
+ /// The type to convert the value to.
+ /// The key of the configuration section's value to convert.
+ /// The default value to use if no value is found.
+ /// The converted value.
+ public static object GetValue(this IConfiguration configuration, Type type, string key, object defaultValue)
+ {
+ IConfigurationSection section = configuration.GetSection(key);
+ string value = section.Value;
+ if (value != null)
+ {
+ return ConvertValue(type, value, section.Path);
+ }
+ return defaultValue;
+ }
+
+ private static void BindNonScalar(this IConfiguration configuration, object instance, BinderOptions options)
+ {
+ if (instance != null)
+ {
+ foreach (PropertyInfo property in GetAllProperties(instance.GetType().GetTypeInfo()))
+ {
+ BindProperty(property, instance, configuration, options);
+ }
+ }
+ }
+
+ private static void BindProperty(PropertyInfo property, object instance, IConfiguration config, BinderOptions options)
+ {
+ // We don't support set only, non public, or indexer properties
+ if (property.GetMethod == null ||
+ (!options.BindNonPublicProperties && !property.GetMethod.IsPublic) ||
+ property.GetMethod.GetParameters().Length > 0)
+ {
+ return;
+ }
+
+ object propertyValue = property.GetValue(instance);
+ bool hasSetter = property.SetMethod != null && (property.SetMethod.IsPublic || options.BindNonPublicProperties);
+
+ if (propertyValue == null && !hasSetter)
+ {
+ // Property doesn't have a value and we cannot set it so there is no
+ // point in going further down the graph
+ return;
+ }
+
+ propertyValue = BindInstance(property.PropertyType, propertyValue, config.GetSection(property.Name), options);
+
+ if (propertyValue != null && hasSetter)
+ {
+ property.SetValue(instance, propertyValue);
+ }
+ }
+
+ private static object BindToCollection(TypeInfo typeInfo, IConfiguration config, BinderOptions options)
+ {
+ Type type = typeof(List<>).MakeGenericType(typeInfo.GenericTypeArguments[0]);
+ object instance = Activator.CreateInstance(type);
+ BindCollection(instance, type, config, options);
+ return instance;
+ }
+
+ // Try to create an array/dictionary instance to back various collection interfaces
+ private static object AttemptBindToCollectionInterfaces(Type type, IConfiguration config, BinderOptions options)
+ {
+ TypeInfo typeInfo = type.GetTypeInfo();
+
+ if (!typeInfo.IsInterface)
+ {
+ return null;
+ }
+
+ Type collectionInterface = FindOpenGenericInterface(typeof(IReadOnlyList<>), type);
+ if (collectionInterface != null)
+ {
+ // IEnumerable is guaranteed to have exactly one parameter
+ return BindToCollection(typeInfo, config, options);
+ }
+
+ collectionInterface = FindOpenGenericInterface(typeof(IReadOnlyDictionary<,>), type);
+ if (collectionInterface != null)
+ {
+ Type dictionaryType = typeof(Dictionary<,>).MakeGenericType(typeInfo.GenericTypeArguments[0], typeInfo.GenericTypeArguments[1]);
+ object instance = Activator.CreateInstance(dictionaryType);
+ BindDictionary(instance, dictionaryType, config, options);
+ return instance;
+ }
+
+ collectionInterface = FindOpenGenericInterface(typeof(IDictionary<,>), type);
+ if (collectionInterface != null)
+ {
+ object instance = Activator.CreateInstance(typeof(Dictionary<,>).MakeGenericType(typeInfo.GenericTypeArguments[0], typeInfo.GenericTypeArguments[1]));
+ BindDictionary(instance, collectionInterface, config, options);
+ return instance;
+ }
+
+ collectionInterface = FindOpenGenericInterface(typeof(IReadOnlyCollection<>), type);
+ if (collectionInterface != null)
+ {
+ // IReadOnlyCollection is guaranteed to have exactly one parameter
+ return BindToCollection(typeInfo, config, options);
+ }
+
+ collectionInterface = FindOpenGenericInterface(typeof(ICollection<>), type);
+ if (collectionInterface != null)
+ {
+ // ICollection is guaranteed to have exactly one parameter
+ return BindToCollection(typeInfo, config, options);
+ }
+
+ collectionInterface = FindOpenGenericInterface(typeof(IEnumerable<>), type);
+ if (collectionInterface != null)
+ {
+ // IEnumerable is guaranteed to have exactly one parameter
+ return BindToCollection(typeInfo, config, options);
+ }
+
+ return null;
+ }
+
+ private static object BindInstance(Type type, object instance, IConfiguration config, BinderOptions options)
+ {
+ // if binding IConfigurationSection, break early
+ if (type == typeof(IConfigurationSection))
+ {
+ return config;
+ }
+
+ var section = config as IConfigurationSection;
+ string configValue = section?.Value;
+ object convertedValue;
+ Exception error;
+ if (configValue != null && TryConvertValue(type, configValue, section.Path, out convertedValue, out error))
+ {
+ if (error != null)
+ {
+ throw error;
+ }
+
+ // Leaf nodes are always reinitialized
+ return convertedValue;
+ }
+
+ if (config != null && config.GetChildren().Any())
+ {
+ // If we don't have an instance, try to create one
+ if (instance == null)
+ {
+ // We are already done if binding to a new collection instance worked
+ instance = AttemptBindToCollectionInterfaces(type, config, options);
+ if (instance != null)
+ {
+ return instance;
+ }
+
+ instance = CreateInstance(type);
+ }
+
+ // See if its a Dictionary
+ Type collectionInterface = FindOpenGenericInterface(typeof(IDictionary<,>), type);
+ if (collectionInterface != null)
+ {
+ BindDictionary(instance, collectionInterface, config, options);
+ }
+ else if (type.IsArray)
+ {
+ instance = BindArray((Array)instance, config, options);
+ }
+ else
+ {
+ // See if its an ICollection
+ collectionInterface = FindOpenGenericInterface(typeof(ICollection<>), type);
+ if (collectionInterface != null)
+ {
+ BindCollection(instance, collectionInterface, config, options);
+ }
+ // Something else
+ else
+ {
+ BindNonScalar(config, instance, options);
+ }
+ }
+ }
+
+ return instance;
+ }
+
+ private static object CreateInstance(Type type)
+ {
+ TypeInfo typeInfo = type.GetTypeInfo();
+
+ if (typeInfo.IsInterface || typeInfo.IsAbstract)
+ {
+ throw new InvalidOperationException($"Error: cannot activate abstract class or interface, type: {type}");
+ }
+
+ if (type.IsArray)
+ {
+ if (typeInfo.GetArrayRank() > 1)
+ {
+ throw new InvalidOperationException($"Error: multi-dimensional array is not supported, type: {type})");
+ }
+
+ return Array.CreateInstance(typeInfo.GetElementType(), 0);
+ }
+
+ if (!typeInfo.IsValueType)
+ {
+ bool hasDefaultConstructor = typeInfo.DeclaredConstructors.Any(ctor => ctor.IsPublic && ctor.GetParameters().Length == 0);
+ if (!hasDefaultConstructor)
+ {
+ throw new InvalidOperationException($"Error: missing parameterless constructor in type {type}");
+ }
+ }
+
+ try
+ {
+ return Activator.CreateInstance(type);
+ }
+ catch (Exception ex)
+ {
+ throw new InvalidOperationException($"Error: failed to activate type [{type}]. {ex.Message}", ex);
+ }
+ }
+
+ private static void BindDictionary(object dictionary, Type dictionaryType, IConfiguration config, BinderOptions options)
+ {
+ TypeInfo typeInfo = dictionaryType.GetTypeInfo();
+
+ // IDictionary is guaranteed to have exactly two parameters
+ Type keyType = typeInfo.GenericTypeArguments[0];
+ Type valueType = typeInfo.GenericTypeArguments[1];
+ bool keyTypeIsEnum = keyType.GetTypeInfo().IsEnum;
+
+ if (keyType != typeof(string) && !keyTypeIsEnum)
+ {
+ // We only support string and enum keys
+ return;
+ }
+
+ PropertyInfo setter = typeInfo.GetDeclaredProperty("Item");
+ foreach (IConfigurationSection child in config.GetChildren())
+ {
+ object item = BindInstance(
+ type: valueType,
+ instance: null,
+ config: child,
+ options: options);
+ if (item != null)
+ {
+ if (keyType == typeof(string))
+ {
+ string key = child.Key;
+ setter.SetValue(dictionary, item, new object[] { key });
+ }
+ else if (keyTypeIsEnum)
+ {
+ object key = Enum.Parse(keyType, child.Key);
+ setter.SetValue(dictionary, item, new object[] { key });
+ }
+ }
+ }
+ }
+
+ private static void BindCollection(object collection, Type collectionType, IConfiguration config, BinderOptions options)
+ {
+ TypeInfo typeInfo = collectionType.GetTypeInfo();
+
+ // ICollection is guaranteed to have exactly one parameter
+ Type itemType = typeInfo.GenericTypeArguments[0];
+ MethodInfo addMethod = typeInfo.GetDeclaredMethod("Add");
+
+ foreach (IConfigurationSection section in config.GetChildren())
+ {
+ try
+ {
+ object item = BindInstance(
+ type: itemType,
+ instance: null,
+ config: section,
+ options: options);
+ if (item != null)
+ {
+ addMethod.Invoke(collection, new[] { item });
+ }
+ }
+ catch
+ {
+ }
+ }
+ }
+
+ private static Array BindArray(Array source, IConfiguration config, BinderOptions options)
+ {
+ IConfigurationSection[] children = config.GetChildren().ToArray();
+ int arrayLength = source.Length;
+ Type elementType = source.GetType().GetElementType();
+ var newArray = Array.CreateInstance(elementType, arrayLength + children.Length);
+
+ // binding to array has to preserve already initialized arrays with values
+ if (arrayLength > 0)
+ {
+ Array.Copy(source, newArray, arrayLength);
+ }
+
+ for (int i = 0; i < children.Length; i++)
+ {
+ try
+ {
+ object item = BindInstance(
+ type: elementType,
+ instance: null,
+ config: children[i],
+ options: options);
+ if (item != null)
+ {
+ newArray.SetValue(item, arrayLength + i);
+ }
+ }
+ catch
+ {
+ }
+ }
+
+ return newArray;
+ }
+
+ private static bool TryConvertValue(Type type, string value, string path, out object result, out Exception error)
+ {
+ error = null;
+ result = null;
+ if (type == typeof(object))
+ {
+ result = value;
+ return true;
+ }
+
+ if (type.GetTypeInfo().IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>))
+ {
+ if (string.IsNullOrEmpty(value))
+ {
+ return true;
+ }
+ return TryConvertValue(Nullable.GetUnderlyingType(type), value, path, out result, out error);
+ }
+
+ TypeConverter converter = TypeDescriptor.GetConverter(type);
+ if (converter.CanConvertFrom(typeof(string)))
+ {
+ try
+ {
+ result = converter.ConvertFromInvariantString(value);
+ }
+ catch (Exception ex)
+ {
+ error = new InvalidOperationException($"Failed to convert value [{value}] to type [{type}].", ex);
+ }
+ return true;
+ }
+
+ return false;
+ }
+
+ private static object ConvertValue(Type type, string value, string path)
+ {
+ object result;
+ Exception error;
+ TryConvertValue(type, value, path, out result, out error);
+ if (error != null)
+ {
+ throw error;
+ }
+ return result;
+ }
+
+ private static Type FindOpenGenericInterface(Type expected, Type actual)
+ {
+ TypeInfo actualTypeInfo = actual.GetTypeInfo();
+ if (actualTypeInfo.IsGenericType &&
+ actual.GetGenericTypeDefinition() == expected)
+ {
+ return actual;
+ }
+
+ IEnumerable interfaces = actualTypeInfo.ImplementedInterfaces;
+ foreach (Type interfaceType in interfaces)
+ {
+ if (interfaceType.GetTypeInfo().IsGenericType &&
+ interfaceType.GetGenericTypeDefinition() == expected)
+ {
+ return interfaceType;
+ }
+ }
+ return null;
+ }
+
+ private static IEnumerable GetAllProperties(TypeInfo type)
+ {
+ var allProperties = new List();
+
+ do
+ {
+ allProperties.AddRange(type.DeclaredProperties);
+ type = type.BaseType.GetTypeInfo();
+ }
+ while (type != typeof(object).GetTypeInfo());
+
+ return allProperties;
+ }
+ }
+}
diff --git a/src/Accounts/Authentication/Config/Internal/ConfigurationBuilder.cs b/src/Accounts/Authentication/Config/Internal/ConfigurationBuilder.cs
new file mode 100644
index 000000000000..03ab860ccbe9
--- /dev/null
+++ b/src/Accounts/Authentication/Config/Internal/ConfigurationBuilder.cs
@@ -0,0 +1,72 @@
+// ----------------------------------------------------------------------------------
+//
+// Copyright Microsoft Corporation
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ----------------------------------------------------------------------------------
+
+using Microsoft.Azure.Commands.Common.Authentication.Config.Internal.Interfaces;
+using System;
+using System.Collections.Generic;
+
+namespace Microsoft.Azure.Commands.Common.Authentication.Config.Internal
+{
+ ///
+ /// Used to build key/value based configuration settings for use in an application.
+ ///
+ internal class ConfigurationBuilder : IConfigurationBuilder
+ {
+ ///
+ /// Returns the sources used to obtain configuration values.
+ ///
+ public IList Sources { get; } = new List();
+
+ private IDictionary _ids = new Dictionary();
+
+ ///
+ /// Gets a key/value collection that can be used to share data between the
+ /// and the registered s.
+ ///
+ public IDictionary Properties { get; } = new Dictionary();
+
+ ///
+ /// Adds a new configuration source.
+ ///
+ /// The configuration source to add.
+ /// The same .
+ public IConfigurationBuilder Add(string id, IConfigurationSource source)
+ {
+ if (source == null)
+ {
+ throw new ArgumentNullException(nameof(source));
+ }
+
+ Sources.Add(source);
+ _ids[source] = id;
+ return this;
+ }
+
+ ///
+ /// Builds an with keys and values from the set of providers registered in
+ /// .
+ ///
+ /// An with keys and values from the registered providers.
+ public IConfigurationRoot Build()
+ {
+ var providers = new List();
+ foreach (IConfigurationSource source in Sources)
+ {
+ IConfigurationProvider provider = source.Build(this, _ids[source]);
+ providers.Add(provider);
+ }
+ return new ConfigurationRoot(providers);
+ }
+ }
+}
diff --git a/src/Accounts/Authentication/Config/Internal/ConfigurationExtensions.cs b/src/Accounts/Authentication/Config/Internal/ConfigurationExtensions.cs
new file mode 100644
index 000000000000..54209d3526cf
--- /dev/null
+++ b/src/Accounts/Authentication/Config/Internal/ConfigurationExtensions.cs
@@ -0,0 +1,97 @@
+// ----------------------------------------------------------------------------------
+//
+// Copyright Microsoft Corporation
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ----------------------------------------------------------------------------------
+
+using Microsoft.Azure.Commands.Common.Authentication.Config.Internal.Interfaces;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Microsoft.Azure.Commands.Common.Authentication.Config.Internal
+{
+ ///
+ /// Extension methods for configuration classes./>.
+ ///
+ internal static class ConfigurationExtensions
+ {
+ ///
+ /// Adds a new configuration source.
+ ///
+ /// The to add to.
+ /// Configures the source secrets.
+ /// The .
+ public static IConfigurationBuilder Add(this IConfigurationBuilder builder, string id, Action configureSource) where TSource : IConfigurationSource, new()
+ {
+ var source = new TSource();
+ configureSource?.Invoke(source);
+ return builder.Add(id, source);
+ }
+
+ ///
+ /// Shorthand for GetSection("ConnectionStrings")[name].
+ ///
+ /// The configuration.
+ /// The connection string key.
+ /// The connection string.
+ public static string GetConnectionString(this IConfiguration configuration, string name)
+ {
+ return configuration?.GetSection("ConnectionStrings")?[name];
+ }
+
+ ///
+ /// Get the enumeration of key value pairs within the
+ ///
+ /// The to enumerate.
+ /// An enumeration of key value pairs.
+ public static IEnumerable> AsEnumerable(this IConfiguration configuration) => configuration.AsEnumerable(makePathsRelative: false);
+
+ ///
+ /// Get the enumeration of key value pairs within the
+ ///
+ /// The to enumerate.
+ /// If true, the child keys returned will have the current configuration's Path trimmed from the front.
+ /// An enumeration of key value pairs.
+ public static IEnumerable> AsEnumerable(this IConfiguration configuration, bool makePathsRelative)
+ {
+ var stack = new Stack();
+ stack.Push(configuration);
+ var rootSection = configuration as IConfigurationSection;
+ int prefixLength = (makePathsRelative && rootSection != null) ? rootSection.Path.Length + 1 : 0;
+ while (stack.Count > 0)
+ {
+ IConfiguration config = stack.Pop();
+ // Don't include the sections value if we are removing paths, since it will be an empty key
+ if (config is IConfigurationSection section && (!makePathsRelative || config != configuration))
+ {
+ yield return new KeyValuePair(section.Path.Substring(prefixLength), section.Value);
+ }
+ foreach (IConfigurationSection child in config.GetChildren())
+ {
+ stack.Push(child);
+ }
+ }
+ }
+
+ ///
+ /// Determines whether the section has a or has children
+ ///
+ public static bool Exists(this IConfigurationSection section)
+ {
+ if (section == null)
+ {
+ return false;
+ }
+ return section.Value != null || section.GetChildren().Any();
+ }
+ }
+}
diff --git a/src/Accounts/Authentication/Config/Internal/ConfigurationKeyComparer.cs b/src/Accounts/Authentication/Config/Internal/ConfigurationKeyComparer.cs
new file mode 100644
index 000000000000..7d55a591f2c6
--- /dev/null
+++ b/src/Accounts/Authentication/Config/Internal/ConfigurationKeyComparer.cs
@@ -0,0 +1,82 @@
+// ----------------------------------------------------------------------------------
+//
+// Copyright Microsoft Corporation
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ----------------------------------------------------------------------------------
+
+using System;
+using System.Collections.Generic;
+
+namespace Microsoft.Azure.Commands.Common.Authentication.Config.Internal
+{
+ internal class ConfigurationKeyComparer : IComparer
+ {
+ private static readonly string[] _keyDelimiterArray = new[] { ConfigurationPath.KeyDelimiter };
+
+ ///
+ /// The default instance.
+ ///
+ public static ConfigurationKeyComparer Instance { get; } = new ConfigurationKeyComparer();
+
+ ///
+ /// Compares two strings.
+ ///
+ /// First string.
+ /// Second string.
+ /// Less than 0 if x is less than y, 0 if x is equal to y and greater than 0 if x is greater than y.
+ public int Compare(string x, string y)
+ {
+ string[] xParts = x?.Split(_keyDelimiterArray, StringSplitOptions.RemoveEmptyEntries) ?? Array.Empty();
+ string[] yParts = y?.Split(_keyDelimiterArray, StringSplitOptions.RemoveEmptyEntries) ?? Array.Empty();
+
+ // Compare each part until we get two parts that are not equal
+ for (int i = 0; i < Math.Min(xParts.Length, yParts.Length); i++)
+ {
+ x = xParts[i];
+ y = yParts[i];
+
+ int value1 = 0;
+ int value2 = 0;
+
+ bool xIsInt = x != null && int.TryParse(x, out value1);
+ bool yIsInt = y != null && int.TryParse(y, out value2);
+
+ int result;
+
+ if (!xIsInt && !yIsInt)
+ {
+ // Both are strings
+ result = string.Compare(x, y, StringComparison.OrdinalIgnoreCase);
+ }
+ else if (xIsInt && yIsInt)
+ {
+ // Both are int
+ result = value1 - value2;
+ }
+ else
+ {
+ // Only one of them is int
+ result = xIsInt ? -1 : 1;
+ }
+
+ if (result != 0)
+ {
+ // One of them is different
+ return result;
+ }
+ }
+
+ // If we get here, the common parts are equal.
+ // If they are of the same length, then they are totally identical
+ return xParts.Length - yParts.Length;
+ }
+ }
+}
diff --git a/src/Accounts/Authentication/Config/Internal/ConfigurationPath.cs b/src/Accounts/Authentication/Config/Internal/ConfigurationPath.cs
new file mode 100644
index 000000000000..7a335573d7dd
--- /dev/null
+++ b/src/Accounts/Authentication/Config/Internal/ConfigurationPath.cs
@@ -0,0 +1,90 @@
+// ----------------------------------------------------------------------------------
+//
+// Copyright Microsoft Corporation
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ----------------------------------------------------------------------------------
+
+using System;
+using System.Collections.Generic;
+
+namespace Microsoft.Azure.Commands.Common.Authentication.Config.Internal
+{
+ ///
+ /// Utility methods and constants for manipulating Configuration paths
+ ///
+ internal static class ConfigurationPath
+ {
+ ///
+ /// The delimiter ":" used to separate individual keys in a path.
+ ///
+ public static readonly string KeyDelimiter = ":";
+
+ ///
+ /// Combines path segments into one path.
+ ///
+ /// The path segments to combine.
+ /// The combined path.
+ public static string Combine(params string[] pathSegments)
+ {
+ if (pathSegments == null)
+ {
+ throw new ArgumentNullException(nameof(pathSegments));
+ }
+ return string.Join(KeyDelimiter, pathSegments);
+ }
+
+ ///
+ /// Combines path segments into one path.
+ ///
+ /// The path segments to combine.
+ /// The combined path.
+ public static string Combine(IEnumerable pathSegments)
+ {
+ if (pathSegments == null)
+ {
+ throw new ArgumentNullException(nameof(pathSegments));
+ }
+ return string.Join(KeyDelimiter, pathSegments);
+ }
+
+ ///
+ /// Extracts the last path segment from the path.
+ ///
+ /// The path.
+ /// The last path segment of the path.
+ public static string GetSectionKey(string path)
+ {
+ if (string.IsNullOrEmpty(path))
+ {
+ return path;
+ }
+
+ int lastDelimiterIndex = path.LastIndexOf(KeyDelimiter, StringComparison.OrdinalIgnoreCase);
+ return lastDelimiterIndex == -1 ? path : path.Substring(lastDelimiterIndex + 1);
+ }
+
+ ///
+ /// Extracts the path corresponding to the parent node for a given path.
+ ///
+ /// The path.
+ /// The original path minus the last individual segment found in it. Null if the original path corresponds to a top level node.
+ public static string GetParentPath(string path)
+ {
+ if (string.IsNullOrEmpty(path))
+ {
+ return null;
+ }
+
+ int lastDelimiterIndex = path.LastIndexOf(KeyDelimiter, StringComparison.OrdinalIgnoreCase);
+ return lastDelimiterIndex == -1 ? null : path.Substring(0, lastDelimiterIndex);
+ }
+ }
+}
diff --git a/src/Accounts/Authentication/Config/Internal/ConfigurationProvider.cs b/src/Accounts/Authentication/Config/Internal/ConfigurationProvider.cs
new file mode 100644
index 000000000000..f2a5c5c7a89e
--- /dev/null
+++ b/src/Accounts/Authentication/Config/Internal/ConfigurationProvider.cs
@@ -0,0 +1,97 @@
+// ----------------------------------------------------------------------------------
+//
+// Copyright Microsoft Corporation
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ----------------------------------------------------------------------------------
+
+using Microsoft.Azure.Commands.Common.Authentication.Config.Internal.Interfaces;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Microsoft.Azure.Commands.Common.Authentication.Config.Internal
+{
+ ///
+ /// Base helper class for implementing an
+ ///
+ internal abstract class ConfigurationProvider : IConfigurationProvider
+ {
+ public string Id { get; }
+
+ ///
+ /// Initializes a new
+ ///
+ protected ConfigurationProvider(string id)
+ {
+ Id = id;
+ Data = new Dictionary(StringComparer.OrdinalIgnoreCase);
+ }
+
+ ///
+ /// The configuration key value pairs for this provider.
+ ///
+ protected IDictionary Data { get; set; }
+
+ ///
+ /// Attempts to find a value with the given key, returns true if one is found, false otherwise.
+ ///
+ /// The key to lookup.
+ /// The value found at key if one is found.
+ /// True if key has a value, false otherwise.
+ public virtual bool TryGet(string key, out string value)
+ => Data.TryGetValue(key, out value);
+
+ ///
+ /// Sets a value for a given key.
+ ///
+ /// The configuration key to set.
+ /// The value to set.
+ public virtual void Set(string key, string value)
+ => Data[key] = value;
+
+ ///
+ /// Loads (or reloads) the data for this provider.
+ ///
+ public virtual void Load()
+ { }
+
+ ///
+ /// Returns the list of keys that this provider has.
+ ///
+ /// The earlier keys that other providers contain.
+ /// The path for the parent IConfiguration.
+ /// The list of keys for this provider.
+ public virtual IEnumerable GetChildKeys(
+ IEnumerable earlierKeys,
+ string parentPath)
+ {
+ string prefix = parentPath == null ? string.Empty : parentPath + ConfigurationPath.KeyDelimiter;
+
+ return Data
+ .Where(kv => kv.Key.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
+ .Select(kv => Segment(kv.Key, prefix.Length))
+ .Concat(earlierKeys)
+ .OrderBy(k => k, ConfigurationKeyComparer.Instance);
+ }
+
+ private static string Segment(string key, int prefixLength)
+ {
+ int indexOf = key.IndexOf(ConfigurationPath.KeyDelimiter, prefixLength, StringComparison.OrdinalIgnoreCase);
+ return indexOf < 0 ? key.Substring(prefixLength) : key.Substring(prefixLength, indexOf - prefixLength);
+ }
+
+ ///
+ /// Generates a string representing this provider name and relevant details.
+ ///
+ /// The configuration name.
+ public override string ToString() => $"{GetType().Name}";
+ }
+}
diff --git a/src/Accounts/Authentication/Config/Internal/ConfigurationRoot.cs b/src/Accounts/Authentication/Config/Internal/ConfigurationRoot.cs
new file mode 100644
index 000000000000..af8cd9516bee
--- /dev/null
+++ b/src/Accounts/Authentication/Config/Internal/ConfigurationRoot.cs
@@ -0,0 +1,137 @@
+// ----------------------------------------------------------------------------------
+//
+// Copyright Microsoft Corporation
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ----------------------------------------------------------------------------------
+
+using Microsoft.Azure.Commands.Common.Authentication.Config.Internal.Interfaces;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Microsoft.Azure.Commands.Common.Authentication.Config.Internal
+{
+ ///
+ /// The root node for a configuration.
+ ///
+ internal class ConfigurationRoot : IConfigurationRoot, IDisposable
+ {
+ private readonly IList _providers;
+
+ ///
+ /// Initializes a Configuration root with a list of providers.
+ ///
+ /// The s for this configuration.
+ public ConfigurationRoot(IList providers)
+ {
+ if (providers == null)
+ {
+ throw new ArgumentNullException(nameof(providers));
+ }
+
+ _providers = providers;
+ foreach (IConfigurationProvider p in providers)
+ {
+ p.Load();
+ }
+ }
+
+ ///
+ /// The s for this configuration.
+ ///
+ public IEnumerable Providers => _providers;
+
+ ///
+ /// Gets or sets the value corresponding to a configuration key.
+ ///
+ /// The configuration key.
+ /// The configuration value.
+ public string this[string key]
+ {
+ get
+ {
+ return GetValueWithProviderId(key).Item1;
+ }
+ set
+ {
+ if (!_providers.Any())
+ {
+ throw new InvalidOperationException($"Error: none config source is registered.");
+ }
+
+ foreach (IConfigurationProvider provider in _providers)
+ {
+ provider.Set(key, value);
+ }
+ }
+ }
+
+ public (string, string) GetValueWithProviderId(string key)
+ {
+ for (int i = _providers.Count - 1; i >= 0; i--)
+ {
+ IConfigurationProvider provider = _providers[i];
+
+ if (provider.TryGet(key, out string value))
+ {
+ return (value, provider.Id);
+ }
+ }
+
+ return (null, null);
+
+ }
+
+ ///
+ /// Gets the immediate children sub-sections.
+ ///
+ /// The children.
+ public IEnumerable GetChildren() => this.GetChildrenImplementation(null);
+
+ ///
+ /// Gets a configuration sub-section with the specified key.
+ ///
+ /// The key of the configuration section.
+ /// The .
+ ///
+ /// This method will never return null. If no matching sub-section is found with the specified key,
+ /// an empty will be returned.
+ ///
+ public IConfigurationSection GetSection(string key)
+ => new ConfigurationSection(this, key);
+
+ ///
+ /// Force the configuration values to be reloaded from the underlying sources.
+ ///
+ public void Reload()
+ {
+ foreach (IConfigurationProvider provider in _providers)
+ {
+ provider.Load();
+ }
+ }
+
+ ///
+ public void Dispose()
+ {
+ // dispose providers
+ foreach (IConfigurationProvider provider in _providers)
+ {
+ (provider as IDisposable)?.Dispose();
+ }
+ }
+
+ public IConfigurationProvider GetConfigurationProvider(string id)
+ {
+ return _providers.FirstOrDefault(x => x.Id.Equals(id));
+ }
+ }
+}
diff --git a/src/Accounts/Authentication/Config/Internal/ConfigurationSection.cs b/src/Accounts/Authentication/Config/Internal/ConfigurationSection.cs
new file mode 100644
index 000000000000..045f31b468ad
--- /dev/null
+++ b/src/Accounts/Authentication/Config/Internal/ConfigurationSection.cs
@@ -0,0 +1,127 @@
+// ----------------------------------------------------------------------------------
+//
+// Copyright Microsoft Corporation
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ----------------------------------------------------------------------------------
+
+using Microsoft.Azure.Commands.Common.Authentication.Config.Internal.Interfaces;
+using System;
+using System.Collections.Generic;
+
+namespace Microsoft.Azure.Commands.Common.Authentication.Config.Internal
+{
+ ///
+ /// Represents a section of application configuration values.
+ ///
+ internal class ConfigurationSection : IConfigurationSection
+ {
+ private readonly IConfigurationRoot _root;
+ private readonly string _path;
+ private string _key;
+
+ ///
+ /// Initializes a new instance.
+ ///
+ /// The configuration root.
+ /// The path to this section.
+ public ConfigurationSection(IConfigurationRoot root, string path)
+ {
+ if (root == null)
+ {
+ throw new ArgumentNullException(nameof(root));
+ }
+
+ if (path == null)
+ {
+ throw new ArgumentNullException(nameof(path));
+ }
+
+ _root = root;
+ _path = path;
+ }
+
+ ///
+ /// Gets the full path to this section from the .
+ ///
+ public string Path => _path;
+
+ ///
+ /// Gets the key this section occupies in its parent.
+ ///
+ public string Key
+ {
+ get
+ {
+ if (_key == null)
+ {
+ // Key is calculated lazily as last portion of Path
+ _key = ConfigurationPath.GetSectionKey(_path);
+ }
+ return _key;
+ }
+ }
+
+ ///
+ /// Gets or sets the section value.
+ ///
+ public string Value
+ {
+ get
+ {
+ return _root[Path];
+ }
+ set
+ {
+ _root[Path] = value;
+ }
+ }
+
+ public (string, string) GetValueWithProviderId()
+ {
+ return _root.GetValueWithProviderId(Path);
+ }
+
+ ///
+ /// Gets or sets the value corresponding to a configuration key.
+ ///
+ /// The configuration key.
+ /// The configuration value.
+ public string this[string key]
+ {
+ get
+ {
+ return _root[ConfigurationPath.Combine(Path, key)];
+ }
+
+ set
+ {
+ _root[ConfigurationPath.Combine(Path, key)] = value;
+ }
+ }
+
+ ///
+ /// Gets a configuration sub-section with the specified key.
+ ///
+ /// The key of the configuration section.
+ /// The .
+ ///
+ /// This method will never return null. If no matching sub-section is found with the specified key,
+ /// an empty will be returned.
+ ///
+ public IConfigurationSection GetSection(string key) => _root.GetSection(ConfigurationPath.Combine(Path, key));
+
+ ///
+ /// Gets the immediate descendant configuration sub-sections.
+ ///
+ /// The configuration sub-sections.
+ public IEnumerable GetChildren() => _root.GetChildrenImplementation(Path);
+ }
+}
diff --git a/src/Accounts/Authentication/Config/Internal/Interfaces/IConfiguration.cs b/src/Accounts/Authentication/Config/Internal/Interfaces/IConfiguration.cs
new file mode 100644
index 000000000000..56821ed998ef
--- /dev/null
+++ b/src/Accounts/Authentication/Config/Internal/Interfaces/IConfiguration.cs
@@ -0,0 +1,45 @@
+// ----------------------------------------------------------------------------------
+//
+// Copyright Microsoft Corporation
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ----------------------------------------------------------------------------------
+
+using System.Collections.Generic;
+
+namespace Microsoft.Azure.Commands.Common.Authentication.Config.Internal.Interfaces
+{
+ internal interface IConfiguration
+ {
+ ///
+ /// Gets or sets a configuration value.
+ ///
+ /// The configuration key.
+ /// The configuration value.
+ string this[string key] { get; set; }
+
+ ///
+ /// Gets a configuration sub-section with the specified key.
+ ///
+ /// The key of the configuration section.
+ /// The .
+ ///
+ /// This method will never return null. If no matching sub-section is found with the specified key,
+ /// an empty will be returned.
+ ///
+ IConfigurationSection GetSection(string key);
+
+ ///
+ /// Gets the immediate descendant configuration sub-sections.
+ ///
+ /// The configuration sub-sections.
+ IEnumerable GetChildren();
+ }
+}
diff --git a/src/Accounts/Authentication/Config/Internal/Interfaces/IConfigurationBuilder.cs b/src/Accounts/Authentication/Config/Internal/Interfaces/IConfigurationBuilder.cs
new file mode 100644
index 000000000000..760ab3f997e7
--- /dev/null
+++ b/src/Accounts/Authentication/Config/Internal/Interfaces/IConfigurationBuilder.cs
@@ -0,0 +1,49 @@
+// ----------------------------------------------------------------------------------
+//
+// Copyright Microsoft Corporation
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ----------------------------------------------------------------------------------
+
+using System.Collections.Generic;
+
+namespace Microsoft.Azure.Commands.Common.Authentication.Config.Internal.Interfaces
+{
+ ///
+ /// Represents a type used to build application configuration.
+ ///
+ internal interface IConfigurationBuilder
+ {
+ ///
+ /// Gets a key/value collection that can be used to share data between the
+ /// and the registered s.
+ ///
+ IDictionary Properties { get; }
+
+ ///
+ /// Gets the sources used to obtain configuration values
+ ///
+ IList Sources { get; }
+
+ ///
+ /// Adds a new configuration source.
+ ///
+ /// The configuration source to add.
+ /// The same .
+ IConfigurationBuilder Add(string id, IConfigurationSource source);
+
+ ///
+ /// Builds an with keys and values from the set of sources registered in
+ /// .
+ ///
+ /// An with keys and values from the registered sources.
+ IConfigurationRoot Build();
+ }
+}
diff --git a/src/Accounts/Authentication/Config/Internal/Interfaces/IConfigurationProvider.cs b/src/Accounts/Authentication/Config/Internal/Interfaces/IConfigurationProvider.cs
new file mode 100644
index 000000000000..235cd11c74d4
--- /dev/null
+++ b/src/Accounts/Authentication/Config/Internal/Interfaces/IConfigurationProvider.cs
@@ -0,0 +1,56 @@
+// ----------------------------------------------------------------------------------
+//
+// Copyright Microsoft Corporation
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ----------------------------------------------------------------------------------
+
+using System.Collections.Generic;
+
+namespace Microsoft.Azure.Commands.Common.Authentication.Config.Internal.Interfaces
+{
+ ///
+ /// Provides configuration key/values for an application.
+ ///
+ internal interface IConfigurationProvider
+ {
+ string Id { get; }
+
+ ///
+ /// Tries to get a configuration value for the specified key.
+ ///
+ /// The key.
+ /// The value.
+ /// True if a value for the specified key was found, otherwise false.
+ bool TryGet(string key, out string value);
+
+ ///
+ /// Sets a configuration value for the specified key.
+ ///
+ /// The key.
+ /// The value.
+ void Set(string key, string value);
+
+ ///
+ /// Loads configuration values from the source represented by this .
+ ///
+ void Load();
+
+ ///
+ /// Returns the immediate descendant configuration keys for a given parent path based on this
+ /// s data and the set of keys returned by all the preceding
+ /// s.
+ ///
+ /// The child keys returned by the preceding providers for the same parent path.
+ /// The parent path.
+ /// The child keys.
+ IEnumerable GetChildKeys(IEnumerable earlierKeys, string parentPath);
+ }
+}
\ No newline at end of file
diff --git a/src/Accounts/Authentication/Config/Internal/Interfaces/IConfigurationRoot.cs b/src/Accounts/Authentication/Config/Internal/Interfaces/IConfigurationRoot.cs
new file mode 100644
index 000000000000..e6506849bb42
--- /dev/null
+++ b/src/Accounts/Authentication/Config/Internal/Interfaces/IConfigurationRoot.cs
@@ -0,0 +1,38 @@
+// ----------------------------------------------------------------------------------
+//
+// Copyright Microsoft Corporation
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ----------------------------------------------------------------------------------
+
+using System.Collections.Generic;
+
+namespace Microsoft.Azure.Commands.Common.Authentication.Config.Internal.Interfaces
+{
+ ///
+ /// Represents the root of an hierarchy.
+ ///
+ internal interface IConfigurationRoot : IConfiguration
+ {
+ ///
+ /// Force the configuration values to be reloaded from the underlying s.
+ ///
+ void Reload();
+
+ ///
+ /// The s for this configuration.
+ ///
+ IEnumerable Providers { get; }
+
+ IConfigurationProvider GetConfigurationProvider(string id);
+
+ (string, string) GetValueWithProviderId(string key);
+ }
+}
diff --git a/src/Accounts/Authentication/Config/Internal/Interfaces/IConfigurationSection.cs b/src/Accounts/Authentication/Config/Internal/Interfaces/IConfigurationSection.cs
new file mode 100644
index 000000000000..d829afa70178
--- /dev/null
+++ b/src/Accounts/Authentication/Config/Internal/Interfaces/IConfigurationSection.cs
@@ -0,0 +1,40 @@
+// ----------------------------------------------------------------------------------
+//
+// Copyright Microsoft Corporation
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ----------------------------------------------------------------------------------
+
+namespace Microsoft.Azure.Commands.Common.Authentication.Config.Internal.Interfaces
+{
+ internal interface IConfigurationSection : IConfiguration
+ {
+ ///
+ /// Gets the key this section occupies in its parent.
+ ///
+ string Key { get; }
+
+ ///
+ /// Gets the full path to this section within the .
+ ///
+ string Path { get; }
+
+ ///
+ /// Gets or sets the section value.
+ ///
+ string Value { get; set; }
+
+ ///
+ /// Gets the section value and the ID of the provider which provides this value.
+ ///
+ ///
+ (string, string) GetValueWithProviderId();
+ }
+}
diff --git a/src/Accounts/Authentication/Config/Internal/Interfaces/IConfigurationSource.cs b/src/Accounts/Authentication/Config/Internal/Interfaces/IConfigurationSource.cs
new file mode 100644
index 000000000000..975c175086a1
--- /dev/null
+++ b/src/Accounts/Authentication/Config/Internal/Interfaces/IConfigurationSource.cs
@@ -0,0 +1,29 @@
+// ----------------------------------------------------------------------------------
+//
+// Copyright Microsoft Corporation
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ----------------------------------------------------------------------------------
+
+namespace Microsoft.Azure.Commands.Common.Authentication.Config.Internal.Interfaces
+{
+ ///
+ /// Represents a source of configuration key/values for an application.
+ ///
+ internal interface IConfigurationSource
+ {
+ ///
+ /// Builds the for this source.
+ ///
+ /// The .
+ /// An
+ IConfigurationProvider Build(IConfigurationBuilder builder, string id);
+ }
+}
diff --git a/src/Accounts/Authentication/Config/Internal/Interfaces/IEnvironmentVariableProvider.cs b/src/Accounts/Authentication/Config/Internal/Interfaces/IEnvironmentVariableProvider.cs
new file mode 100644
index 000000000000..2cf3787f0c63
--- /dev/null
+++ b/src/Accounts/Authentication/Config/Internal/Interfaces/IEnvironmentVariableProvider.cs
@@ -0,0 +1,28 @@
+// ----------------------------------------------------------------------------------
+//
+// Copyright Microsoft Corporation
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ----------------------------------------------------------------------------------
+
+using System;
+
+namespace Microsoft.Azure.Commands.Common.Authentication.Config.Internal.Interfaces
+{
+ ///
+ /// An abstraction of the ability to get and set environment variable on various targets.
+ ///
+ internal interface IEnvironmentVariableProvider
+ {
+ string Get(string variableName, EnvironmentVariableTarget target = EnvironmentVariableTarget.Process);
+
+ void Set(string variableName, string value, EnvironmentVariableTarget target = EnvironmentVariableTarget.Process);
+ }
+}
diff --git a/src/Accounts/Authentication/Config/Internal/InternalConfigurationRootExtensions.cs b/src/Accounts/Authentication/Config/Internal/InternalConfigurationRootExtensions.cs
new file mode 100644
index 000000000000..dcefe827f1b5
--- /dev/null
+++ b/src/Accounts/Authentication/Config/Internal/InternalConfigurationRootExtensions.cs
@@ -0,0 +1,42 @@
+// ----------------------------------------------------------------------------------
+//
+// Copyright Microsoft Corporation
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ----------------------------------------------------------------------------------
+
+using Microsoft.Azure.Commands.Common.Authentication.Config.Internal.Interfaces;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Microsoft.Azure.Commands.Common.Authentication.Config.Internal
+{
+ ///
+ /// Extensions method for
+ ///
+ internal static class InternalConfigurationRootExtensions
+ {
+ ///
+ /// Gets the immediate children sub-sections of configuration root based on key.
+ ///
+ /// Configuration from which to retrieve sub-sections.
+ /// Key of a section of which children to retrieve.
+ /// Immediate children sub-sections of section specified by key.
+ internal static IEnumerable GetChildrenImplementation(this IConfigurationRoot root, string path)
+ {
+ return root.Providers
+ .Aggregate(Enumerable.Empty(),
+ (seed, source) => source.GetChildKeys(seed, path))
+ .Distinct(StringComparer.OrdinalIgnoreCase)
+ .Select(key => root.GetSection(path == null ? key : ConfigurationPath.Combine(path, key)));
+ }
+ }
+}
diff --git a/src/Accounts/Authentication/Config/Internal/Providers/EnvironmentVariablesConfigurationBuilderExtensions.cs b/src/Accounts/Authentication/Config/Internal/Providers/EnvironmentVariablesConfigurationBuilderExtensions.cs
new file mode 100644
index 000000000000..352d8d3c08f1
--- /dev/null
+++ b/src/Accounts/Authentication/Config/Internal/Providers/EnvironmentVariablesConfigurationBuilderExtensions.cs
@@ -0,0 +1,29 @@
+// ----------------------------------------------------------------------------------
+//
+// Copyright Microsoft Corporation
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ----------------------------------------------------------------------------------
+
+using Microsoft.Azure.Commands.Common.Authentication.Config.Internal.Interfaces;
+
+namespace Microsoft.Azure.Commands.Common.Authentication.Config.Internal.Providers
+{
+ internal static class EnvironmentVariablesConfigurationBuilderExtensions
+ {
+ public static IConfigurationBuilder AddEnvironmentVariables(this IConfigurationBuilder builder,
+ string providerId,
+ EnvironmentVariablesConfigurationOptions options)
+ {
+ builder.Add(providerId, new EnvironmentVariablesConfigurationSource(options));
+ return builder;
+ }
+ }
+}
diff --git a/src/Accounts/Authentication/Config/Internal/Providers/EnvironmentVariablesConfigurationOptions.cs b/src/Accounts/Authentication/Config/Internal/Providers/EnvironmentVariablesConfigurationOptions.cs
new file mode 100644
index 000000000000..47b90bb444fe
--- /dev/null
+++ b/src/Accounts/Authentication/Config/Internal/Providers/EnvironmentVariablesConfigurationOptions.cs
@@ -0,0 +1,27 @@
+// ----------------------------------------------------------------------------------
+//
+// Copyright Microsoft Corporation
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ----------------------------------------------------------------------------------
+
+using Microsoft.Azure.Commands.Common.Authentication.Config.Internal.Interfaces;
+using System;
+using System.Collections.Generic;
+
+namespace Microsoft.Azure.Commands.Common.Authentication.Config.Internal.Providers
+{
+ internal class EnvironmentVariablesConfigurationOptions
+ {
+ public IEnvironmentVariableProvider EnvironmentVariableProvider { get; set; }
+ public EnvironmentVariableTarget EnvironmentVariableTarget { get; set; }
+ public IDictionary EnvironmentVariableToKeyMap { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/Accounts/Authentication/Config/Internal/Providers/EnvironmentVariablesConfigurationProvider.cs b/src/Accounts/Authentication/Config/Internal/Providers/EnvironmentVariablesConfigurationProvider.cs
new file mode 100644
index 000000000000..ef0fcf488680
--- /dev/null
+++ b/src/Accounts/Authentication/Config/Internal/Providers/EnvironmentVariablesConfigurationProvider.cs
@@ -0,0 +1,49 @@
+// ----------------------------------------------------------------------------------
+//
+// Copyright Microsoft Corporation
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ----------------------------------------------------------------------------------
+
+using Microsoft.Azure.Commands.Common.Authentication.Config.Internal.Interfaces;
+using System;
+using System.Collections.Generic;
+
+namespace Microsoft.Azure.Commands.Common.Authentication.Config.Internal.Providers
+{
+ internal class EnvironmentVariablesConfigurationProvider : ConfigurationProvider
+ {
+ private EnvironmentVariableTarget _environmentVariableTarget;
+ private IDictionary _environmentVariableNameToKeyMapping;
+ private IEnvironmentVariableProvider _environmentVariableProvider;
+
+ public EnvironmentVariablesConfigurationProvider(string id, EnvironmentVariablesConfigurationOptions options) : base(id)
+ {
+ _environmentVariableTarget = options.EnvironmentVariableTarget;
+ _environmentVariableNameToKeyMapping = options.EnvironmentVariableToKeyMap ?? new Dictionary();
+ _environmentVariableProvider = options.EnvironmentVariableProvider;
+ }
+
+ public override void Load()
+ {
+ var data = new Dictionary(StringComparer.OrdinalIgnoreCase);
+ foreach (var i in _environmentVariableNameToKeyMapping)
+ {
+ string value = _environmentVariableProvider.Get(i.Key, _environmentVariableTarget);
+ if (!string.IsNullOrEmpty(value))
+ {
+ data[i.Value] = value;
+ }
+ }
+
+ Data = data;
+ }
+ }
+}
diff --git a/src/Accounts/Authentication/Config/Internal/Providers/EnvironmentVariablesConfigurationSource.cs b/src/Accounts/Authentication/Config/Internal/Providers/EnvironmentVariablesConfigurationSource.cs
new file mode 100644
index 000000000000..f5b59aeebcf7
--- /dev/null
+++ b/src/Accounts/Authentication/Config/Internal/Providers/EnvironmentVariablesConfigurationSource.cs
@@ -0,0 +1,33 @@
+// ----------------------------------------------------------------------------------
+//
+// Copyright Microsoft Corporation
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ----------------------------------------------------------------------------------
+
+using Microsoft.Azure.Commands.Common.Authentication.Config.Internal.Interfaces;
+
+namespace Microsoft.Azure.Commands.Common.Authentication.Config.Internal.Providers
+{
+ internal class EnvironmentVariablesConfigurationSource : IConfigurationSource
+ {
+ private EnvironmentVariablesConfigurationOptions _options;
+
+ public EnvironmentVariablesConfigurationSource(EnvironmentVariablesConfigurationOptions options)
+ {
+ _options = options;
+ }
+
+ public IConfigurationProvider Build(IConfigurationBuilder builder, string id)
+ {
+ return new EnvironmentVariablesConfigurationProvider(id, _options);
+ }
+ }
+}
diff --git a/src/Accounts/Authentication/Config/Internal/Providers/JsonConfigurationExtensions.cs b/src/Accounts/Authentication/Config/Internal/Providers/JsonConfigurationExtensions.cs
new file mode 100644
index 000000000000..81850eb386d8
--- /dev/null
+++ b/src/Accounts/Authentication/Config/Internal/Providers/JsonConfigurationExtensions.cs
@@ -0,0 +1,42 @@
+// ----------------------------------------------------------------------------------
+//
+// Copyright Microsoft Corporation
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ----------------------------------------------------------------------------------
+
+using Microsoft.Azure.Commands.Common.Authentication.Config.Internal.Interfaces;
+using System;
+using System.IO;
+
+namespace Microsoft.Azure.Commands.Common.Authentication.Config.Internal.Providers
+{
+ ///
+ /// Extension methods for adding the .
+ ///
+ internal static class JsonConfigurationExtensions
+ {
+ ///
+ /// Adds a JSON configuration source to .
+ ///
+ /// The to add to.
+ /// The to read the json configuration data from.
+ /// The .
+ public static IConfigurationBuilder AddJsonStream(this IConfigurationBuilder builder, string id, Stream stream)
+ {
+ if (builder == null)
+ {
+ throw new ArgumentNullException(nameof(builder));
+ }
+
+ return builder.Add(id, s => s.Stream = stream);
+ }
+ }
+}
diff --git a/src/Accounts/Authentication/Config/Internal/Providers/JsonConfigurationFileParser.cs b/src/Accounts/Authentication/Config/Internal/Providers/JsonConfigurationFileParser.cs
new file mode 100644
index 000000000000..b370ec2bd9fe
--- /dev/null
+++ b/src/Accounts/Authentication/Config/Internal/Providers/JsonConfigurationFileParser.cs
@@ -0,0 +1,116 @@
+// ----------------------------------------------------------------------------------
+//
+// Copyright Microsoft Corporation
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ----------------------------------------------------------------------------------
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text.Json;
+
+namespace Microsoft.Azure.Commands.Common.Authentication.Config.Internal.Providers
+{
+ internal class JsonConfigurationFileParser
+ {
+ private JsonConfigurationFileParser() { }
+
+ private readonly IDictionary _data = new SortedDictionary(StringComparer.OrdinalIgnoreCase);
+ private readonly Stack _context = new Stack();
+ private string _currentPath;
+
+ public static IDictionary Parse(Stream input)
+ => new JsonConfigurationFileParser().ParseStream(input);
+
+ private IDictionary ParseStream(Stream input)
+ {
+ _data.Clear();
+
+ var jsonDocumentOptions = new JsonDocumentOptions
+ {
+ CommentHandling = JsonCommentHandling.Skip,
+ AllowTrailingCommas = true,
+ };
+
+ using (var reader = new StreamReader(input))
+ using (JsonDocument doc = JsonDocument.Parse(reader.ReadToEnd(), jsonDocumentOptions))
+ {
+ if (doc.RootElement.ValueKind != JsonValueKind.Object)
+ {
+ throw new FormatException($"Error: unsupported JSON token [{doc.RootElement.ValueKind}]. Object kind is expected.");
+ }
+ VisitElement(doc.RootElement);
+ }
+
+ return _data;
+ }
+
+ private void VisitElement(JsonElement element)
+ {
+ foreach (JsonProperty property in element.EnumerateObject())
+ {
+ EnterContext(property.Name);
+ VisitValue(property.Value);
+ ExitContext();
+ }
+ }
+
+ private void VisitValue(JsonElement value)
+ {
+ switch (value.ValueKind)
+ {
+ case JsonValueKind.Object:
+ VisitElement(value);
+ break;
+
+ case JsonValueKind.Array:
+ int index = 0;
+ foreach (JsonElement arrayElement in value.EnumerateArray())
+ {
+ EnterContext(index.ToString());
+ VisitValue(arrayElement);
+ ExitContext();
+ index++;
+ }
+ break;
+
+ case JsonValueKind.Number:
+ case JsonValueKind.String:
+ case JsonValueKind.True:
+ case JsonValueKind.False:
+ case JsonValueKind.Null:
+ string key = _currentPath;
+ if (_data.ContainsKey(key))
+ {
+ throw new FormatException($"Error: key [{key}] is duplicated.");
+ }
+ _data[key] = value.ToString();
+ break;
+
+ default:
+ throw new FormatException($"Error: unsupported JSON token [{value.ValueKind}]");
+ }
+ }
+
+ private void EnterContext(string context)
+ {
+ _context.Push(context);
+ _currentPath = ConfigurationPath.Combine(_context.Reverse());
+ }
+
+ private void ExitContext()
+ {
+ _context.Pop();
+ _currentPath = ConfigurationPath.Combine(_context.Reverse());
+ }
+ }
+}
diff --git a/src/Accounts/Authentication/Config/Internal/Providers/JsonStreamConfigurationProvider.cs b/src/Accounts/Authentication/Config/Internal/Providers/JsonStreamConfigurationProvider.cs
new file mode 100644
index 000000000000..c7d0297bb676
--- /dev/null
+++ b/src/Accounts/Authentication/Config/Internal/Providers/JsonStreamConfigurationProvider.cs
@@ -0,0 +1,37 @@
+// ----------------------------------------------------------------------------------
+//
+// Copyright Microsoft Corporation
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ----------------------------------------------------------------------------------
+
+using System.IO;
+
+namespace Microsoft.Azure.Commands.Common.Authentication.Config.Internal.Providers
+{
+ ///
+ /// Loads configuration key/values from a json stream into a provider.
+ ///
+ internal class JsonStreamConfigurationProvider : StreamConfigurationProvider
+ {
+ public JsonStreamConfigurationProvider(JsonStreamConfigurationSource source, string id) : base(source, id)
+ {
+ }
+
+ ///
+ /// Loads json configuration key/values from a stream into a provider.
+ ///
+ /// The json to load configuration data from.
+ public override void Load(Stream stream)
+ {
+ Data = JsonConfigurationFileParser.Parse(stream);
+ }
+ }
+}
diff --git a/src/Accounts/Authentication/Config/Internal/Providers/JsonStreamConfigurationSource.cs b/src/Accounts/Authentication/Config/Internal/Providers/JsonStreamConfigurationSource.cs
new file mode 100644
index 000000000000..8e46465f1e84
--- /dev/null
+++ b/src/Accounts/Authentication/Config/Internal/Providers/JsonStreamConfigurationSource.cs
@@ -0,0 +1,32 @@
+// ----------------------------------------------------------------------------------
+//
+// Copyright Microsoft Corporation
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ----------------------------------------------------------------------------------
+
+using Microsoft.Azure.Commands.Common.Authentication.Config.Internal.Interfaces;
+
+namespace Microsoft.Azure.Commands.Common.Authentication.Config.Internal.Providers
+{
+ ///
+ /// Represents a JSON file as an .
+ ///
+ internal class JsonStreamConfigurationSource : StreamConfigurationSource
+ {
+ ///
+ /// Builds the for this source.
+ ///
+ /// The .
+ /// An
+ public override IConfigurationProvider Build(IConfigurationBuilder builder, string id)
+ => new JsonStreamConfigurationProvider(this, id);
+ }
+}
diff --git a/src/Accounts/Authentication/Config/Internal/Providers/StreamConfigurationProvider.cs b/src/Accounts/Authentication/Config/Internal/Providers/StreamConfigurationProvider.cs
new file mode 100644
index 000000000000..934541cd943d
--- /dev/null
+++ b/src/Accounts/Authentication/Config/Internal/Providers/StreamConfigurationProvider.cs
@@ -0,0 +1,60 @@
+// ----------------------------------------------------------------------------------
+//
+// Copyright Microsoft Corporation
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ----------------------------------------------------------------------------------
+
+using System;
+using System.IO;
+
+namespace Microsoft.Azure.Commands.Common.Authentication.Config.Internal.Providers
+{
+ ///
+ /// Stream based configuration provider
+ ///
+ internal abstract class StreamConfigurationProvider : ConfigurationProvider
+ {
+ ///
+ /// The source settings for this provider.
+ ///
+ public StreamConfigurationSource Source { get; }
+
+ private bool _loaded;
+
+ ///
+ /// Constructor.
+ ///
+ /// The source.
+ public StreamConfigurationProvider(StreamConfigurationSource source, string id) : base(id)
+ {
+ Source = source ?? throw new ArgumentNullException(nameof(source));
+ }
+
+ ///
+ /// Load the configuration data from the stream.
+ ///
+ /// The data stream.
+ public abstract void Load(Stream stream);
+
+ ///
+ /// Load the configuration data from the stream. Will throw after the first call.
+ ///
+ public override void Load()
+ {
+ if (_loaded)
+ {
+ throw new InvalidOperationException("StreamConfigurationProviders cannot be loaded more than once.");
+ }
+ Load(Source.Stream);
+ _loaded = true;
+ }
+ }
+}
diff --git a/src/Accounts/Authentication/Config/Internal/Providers/StreamConfigurationSource.cs b/src/Accounts/Authentication/Config/Internal/Providers/StreamConfigurationSource.cs
new file mode 100644
index 000000000000..388cae463514
--- /dev/null
+++ b/src/Accounts/Authentication/Config/Internal/Providers/StreamConfigurationSource.cs
@@ -0,0 +1,37 @@
+// ----------------------------------------------------------------------------------
+//
+// Copyright Microsoft Corporation
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ----------------------------------------------------------------------------------
+
+using Microsoft.Azure.Commands.Common.Authentication.Config.Internal.Interfaces;
+using System.IO;
+
+namespace Microsoft.Azure.Commands.Common.Authentication.Config.Internal.Providers
+{
+ ///
+ /// Stream based .
+ ///
+ internal abstract class StreamConfigurationSource : IConfigurationSource
+ {
+ ///
+ /// The stream containing the configuration data.
+ ///
+ public Stream Stream { get; set; }
+
+ ///
+ /// Builds the for this source.
+ ///
+ /// The .
+ /// An
+ public abstract IConfigurationProvider Build(IConfigurationBuilder builder, string id);
+ }
+}
diff --git a/src/Accounts/Authentication/Config/Internal/Providers/UnsettableMemoryConfigurationBuilderExtensions.cs b/src/Accounts/Authentication/Config/Internal/Providers/UnsettableMemoryConfigurationBuilderExtensions.cs
new file mode 100644
index 000000000000..dee76dc0c09e
--- /dev/null
+++ b/src/Accounts/Authentication/Config/Internal/Providers/UnsettableMemoryConfigurationBuilderExtensions.cs
@@ -0,0 +1,33 @@
+// ----------------------------------------------------------------------------------
+//
+// Copyright Microsoft Corporation
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ----------------------------------------------------------------------------------
+
+using Microsoft.Azure.Commands.Common.Authentication.Config.Internal.Interfaces;
+using System;
+
+namespace Microsoft.Azure.Commands.Common.Authentication.Config.Internal.Providers
+{
+ internal static class UnsettableMemoryConfigurationBuilderExtensions
+ {
+ public static IConfigurationBuilder AddUnsettableInMemoryCollection(this IConfigurationBuilder builder, string id)
+ {
+ if (builder == null)
+ {
+ throw new ArgumentNullException(nameof(builder));
+ }
+
+ builder.Add(id, new UnsettableMemoryConfigurationSource());
+ return builder;
+ }
+ }
+}
diff --git a/src/Accounts/Authentication/Config/Internal/Providers/UnsettableMemoryConfigurationProvider.cs b/src/Accounts/Authentication/Config/Internal/Providers/UnsettableMemoryConfigurationProvider.cs
new file mode 100644
index 000000000000..889e544bd758
--- /dev/null
+++ b/src/Accounts/Authentication/Config/Internal/Providers/UnsettableMemoryConfigurationProvider.cs
@@ -0,0 +1,81 @@
+// ----------------------------------------------------------------------------------
+//
+// Copyright Microsoft Corporation
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ----------------------------------------------------------------------------------
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using Microsoft.Azure.Commands.Common.Authentication.Config.Internal.Interfaces;
+
+namespace Microsoft.Azure.Commands.Common.Authentication.Config.Internal.Providers
+{
+ ///
+ /// In-memory implementation of
+ ///
+ internal class UnsettableMemoryConfigurationProvider : ConfigurationProvider, IEnumerable>
+ {
+ private readonly UnsettableMemoryConfigurationSource _source;
+
+ ///
+ /// Initialize a new instance from the source.
+ ///
+ /// The source settings.
+ public UnsettableMemoryConfigurationProvider(UnsettableMemoryConfigurationSource source, string id): base(id)
+ {
+ if (source == null)
+ {
+ throw new ArgumentNullException(nameof(source));
+ }
+
+ _source = source;
+ }
+
+ ///
+ /// Add a new key and value pair.
+ ///
+ /// The configuration key.
+ /// The configuration value.
+ public void Add(string key, string value)
+ {
+ Data.Add(key, value);
+ }
+
+ public void Unset(string key)
+ {
+ Data.Remove(key);
+ }
+
+ ///
+ /// Returns an enumerator that iterates through the collection.
+ ///
+ /// An enumerator that can be used to iterate through the collection.
+ public IEnumerator> GetEnumerator()
+ {
+ return Data.GetEnumerator();
+ }
+
+ ///
+ /// Returns an enumerator that iterates through the collection.
+ ///
+ /// An enumerator that can be used to iterate through the collection.
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return GetEnumerator();
+ }
+
+ public void UnsetAll()
+ {
+ Data.Clear();
+ }
+ }
+}
diff --git a/src/Accounts/Authentication/Config/Internal/Providers/UnsettableMemoryConfigurationSource.cs b/src/Accounts/Authentication/Config/Internal/Providers/UnsettableMemoryConfigurationSource.cs
new file mode 100644
index 000000000000..f4c8f8892821
--- /dev/null
+++ b/src/Accounts/Authentication/Config/Internal/Providers/UnsettableMemoryConfigurationSource.cs
@@ -0,0 +1,26 @@
+// ----------------------------------------------------------------------------------
+//
+// Copyright Microsoft Corporation
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ----------------------------------------------------------------------------------
+
+using Microsoft.Azure.Commands.Common.Authentication.Config.Internal.Interfaces;
+
+namespace Microsoft.Azure.Commands.Common.Authentication.Config.Internal.Providers
+{
+ internal class UnsettableMemoryConfigurationSource : IConfigurationSource
+ {
+ public IConfigurationProvider Build(IConfigurationBuilder builder, string id)
+ {
+ return new UnsettableMemoryConfigurationProvider(this, id);
+ }
+ }
+}
From e54b3cbec5b7088174620f26b70d15996bd379d2 Mon Sep 17 00:00:00 2001
From: Yeming Liu <11371776+isra-fel@users.noreply.github.com>
Date: Thu, 24 Mar 2022 16:40:30 +0800
Subject: [PATCH 2/9] implementation of config framework
---
.../Authentication/Authentication.csproj | 4 +
.../Authentication/AzureSessionInitializer.cs | 14 +-
.../Config/ConfigInitializer.cs | 156 ++++++
.../Authentication/Config/ConfigManager.cs | 468 ++++++++++++++++++
.../Config/Helper/AppliesToHelper.cs | 99 ++++
.../Config/Helper/ConfigPathHelper.cs | 59 +++
.../Config/Helper/ConfigScopeHelper.cs | 40 ++
.../DefaultEnvironmentVariableProvider.cs | 35 ++
.../Config/Helper/JsonConfigWriter.cs | 153 ++++++
.../Config/Models/InternalInvocationInfo.cs | 35 ++
.../Config/Models/InvocationInfoAdapter.cs | 25 +
.../Authentication/Config/Models/PSConfig.cs | 42 ++
.../Config/Models/SimpleTypedConfig.cs | 50 ++
.../Config/Models/TypedConfig.cs | 69 +++
src/Accounts/Authentication/Constants.cs | 13 +
15 files changed, 1261 insertions(+), 1 deletion(-)
create mode 100644 src/Accounts/Authentication/Config/ConfigInitializer.cs
create mode 100644 src/Accounts/Authentication/Config/ConfigManager.cs
create mode 100644 src/Accounts/Authentication/Config/Helper/AppliesToHelper.cs
create mode 100644 src/Accounts/Authentication/Config/Helper/ConfigPathHelper.cs
create mode 100644 src/Accounts/Authentication/Config/Helper/ConfigScopeHelper.cs
create mode 100644 src/Accounts/Authentication/Config/Helper/DefaultEnvironmentVariableProvider.cs
create mode 100644 src/Accounts/Authentication/Config/Helper/JsonConfigWriter.cs
create mode 100644 src/Accounts/Authentication/Config/Models/InternalInvocationInfo.cs
create mode 100644 src/Accounts/Authentication/Config/Models/InvocationInfoAdapter.cs
create mode 100644 src/Accounts/Authentication/Config/Models/PSConfig.cs
create mode 100644 src/Accounts/Authentication/Config/Models/SimpleTypedConfig.cs
create mode 100644 src/Accounts/Authentication/Config/Models/TypedConfig.cs
diff --git a/src/Accounts/Authentication/Authentication.csproj b/src/Accounts/Authentication/Authentication.csproj
index 388c5e5f8e2e..e37d65960075 100644
--- a/src/Accounts/Authentication/Authentication.csproj
+++ b/src/Accounts/Authentication/Authentication.csproj
@@ -30,4 +30,8 @@
+
+
+
+
diff --git a/src/Accounts/Authentication/AzureSessionInitializer.cs b/src/Accounts/Authentication/AzureSessionInitializer.cs
index 0d5ae62ef5ae..6d3c5c84e692 100644
--- a/src/Accounts/Authentication/AzureSessionInitializer.cs
+++ b/src/Accounts/Authentication/AzureSessionInitializer.cs
@@ -24,10 +24,11 @@
using Microsoft.Azure.Commands.Common.Authentication.Authentication.TokenCache;
using Microsoft.Azure.Commands.Common.Authentication.Factories;
using Microsoft.Azure.Commands.Common.Authentication.Properties;
-
+using Microsoft.Azure.Commands.Common.Authentication.Config;
using Newtonsoft.Json;
using TraceLevel = System.Diagnostics.TraceLevel;
+using System.Collections.Generic;
namespace Microsoft.Azure.Commands.Common.Authentication
{
@@ -244,12 +245,23 @@ static IAzureSession CreateInstance(IDataStore dataStore = null)
session.TokenCacheDirectory = autoSave.CacheDirectory;
session.TokenCacheFile = autoSave.CacheFile;
+ InitializeConfigs(session);
InitializeDataCollection(session);
session.RegisterComponent(HttpClientOperationsFactory.Name, () => HttpClientOperationsFactory.Create());
session.TokenCache = session.TokenCache ?? new AzureTokenCache();
return session;
}
+ private static void InitializeConfigs(AzureSession session)
+ {
+ var fallbackList = new List()
+ {
+ Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".Azure", "PSConfig.json"),
+ Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), ".Azure", "PSConfig.json")
+ };
+ new ConfigInitializer(fallbackList).InitializeForAzureSession(session);
+ }
+
public class AdalSession : AzureSession
{
#if !NETSTANDARD
diff --git a/src/Accounts/Authentication/Config/ConfigInitializer.cs b/src/Accounts/Authentication/Config/ConfigInitializer.cs
new file mode 100644
index 000000000000..d179bbe9fc89
--- /dev/null
+++ b/src/Accounts/Authentication/Config/ConfigInitializer.cs
@@ -0,0 +1,156 @@
+// ----------------------------------------------------------------------------------
+//
+// Copyright Microsoft Corporation
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ----------------------------------------------------------------------------------
+
+using Microsoft.Azure.Commands.Common.Authentication.Abstractions;
+using Microsoft.Azure.Commands.Common.Authentication.Config.Internal.Interfaces;
+using Microsoft.Azure.PowerShell.Common.Config;
+using Newtonsoft.Json.Linq;
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+
+namespace Microsoft.Azure.Commands.Common.Authentication.Config
+{
+ ///
+ /// Initializes the config file and config manager.
+ ///
+ internal class ConfigInitializer
+ {
+ internal IDataStore DataStore { get; set; } = new DiskDataStore();
+ private static readonly object _fsLock = new object();
+
+ internal IEnvironmentVariableProvider EnvironmentVariableProvider { get; set; } = new DefaultEnvironmentVariableProvider();
+
+ private readonly string _pathToConfigFile;
+
+ public ConfigInitializer(IEnumerable paths)
+ {
+ _ = paths ?? throw new ArgumentNullException(nameof(paths));
+ _pathToConfigFile = GetPathToConfigFile(paths);
+ }
+
+ ///
+ /// Loop through the fallback list of paths of the config file. Returns the first usable one.
+ ///
+ /// A list of paths to the config file. When one is not usable, it will fallback to the next.
+ ///
+ /// When no one in the list is usable.
+ private string GetPathToConfigFile(IEnumerable paths)
+ {
+ // find first exist path and use it
+ foreach (string path in paths)
+ {
+ if (DataStore.FileExists(path))
+ {
+ return path;
+ }
+ }
+ // if not found, use the first writable path
+ foreach (string path in paths)
+ {
+ try
+ {
+ DirectoryInfo dir = new FileInfo(path).Directory;
+ DataStore.CreateDirectory(dir.FullName); // create directory if not exists
+ using (var _ = DataStore.OpenForExclusiveWrite(path)) { }
+ return path;
+ }
+ catch (Exception)
+ {
+ continue;
+ }
+ }
+ throw new ApplicationException($"Failed to store the config file. Please make sure any one of the following paths is accessible: {string.Join(", ", paths)}");
+ }
+
+ internal IConfigManager GetConfigManager()
+ {
+ lock (_fsLock)
+ {
+ ValidateConfigFile();
+ }
+ return new ConfigManager(_pathToConfigFile, DataStore, EnvironmentVariableProvider);
+ }
+
+ private void ValidateConfigFileContent()
+ {
+ string json = DataStore.ReadFileAsText(_pathToConfigFile);
+
+ bool isValidJson = true;
+ try
+ {
+ JObject.Parse(json);
+ }
+ catch (Exception)
+ {
+ isValidJson = false;
+ }
+
+ if (string.IsNullOrEmpty(json) || !isValidJson)
+ {
+ Debug.Write($"[ConfigInitializer] Failed to parse the config file at {_pathToConfigFile}. Clearing the file.");
+ ResetConfigFileToDefault();
+ }
+ }
+
+ private void ValidateConfigFile()
+ {
+ if (!DataStore.FileExists(_pathToConfigFile))
+ {
+ ResetConfigFileToDefault();
+ }
+ else
+ {
+ ValidateConfigFileContent();
+ }
+ }
+
+ private void ResetConfigFileToDefault()
+ {
+ try
+ {
+ DataStore.WriteFile(_pathToConfigFile, @"{}");
+ }
+ catch (Exception ex)
+ {
+ // do not halt for IO exception
+ Debug.WriteLine(ex.Message);
+ }
+ }
+
+ // todo: tests initializes configs in a different way. Maybe there should be an abstraction IConfigInitializer and two concrete classes ConfigInitializer + TestConfigInitializer
+ internal void InitializeForAzureSession(AzureSession session)
+ {
+ IConfigManager configManager = GetConfigManager();
+ session.RegisterComponent(nameof(IConfigManager), () => configManager);
+ RegisterConfigs(configManager);
+ configManager.BuildConfig();
+ }
+
+ private void RegisterConfigs(IConfigManager configManager)
+ {
+ // simple configs
+ // todo: review the help messages
+ //configManager.RegisterConfig(new SimpleTypedConfig(ConfigKeys.SuppressWarningMessage, "Controls if the warning messages of upcoming breaking changes are enabled or suppressed. The messages are typically displayed when a cmdlet that will have breaking change in the future is executed.", false, BreakingChangeAttributeHelper.SUPPRESS_ERROR_OR_WARNING_MESSAGE_ENV_VARIABLE_NAME));
+ //configManager.RegisterConfig(new SimpleTypedConfig(ConfigKeys.EnableInterceptSurvey, "When enabled, a message of taking part in the survey about the user experience of Azure PowerShell will prompt at low frequency.", true, "Azure_PS_Intercept_Survey"));
+ // todo: when the input is not a valid subscription name or id. Connect-AzAccount will throw an error. Is it right?
+ //configManager.RegisterConfig(new SimpleTypedConfig(ConfigKeys.DefaultSubscriptionForLogin, "Subscription name or GUID. If defined, when logging in Azure PowerShell without specifying the subscription, this one will be used to select the default context.", string.Empty));
+ // todo: add later
+ //configManager.RegisterConfig(new RetryConfig());
+ // todo: how to migrate old config
+ //configManager.RegisterConfig(new EnableDataCollectionConfig());
+ }
+ }
+}
diff --git a/src/Accounts/Authentication/Config/ConfigManager.cs b/src/Accounts/Authentication/Config/ConfigManager.cs
new file mode 100644
index 000000000000..7b906af858a9
--- /dev/null
+++ b/src/Accounts/Authentication/Config/ConfigManager.cs
@@ -0,0 +1,468 @@
+// ----------------------------------------------------------------------------------
+//
+// Copyright Microsoft Corporation
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ----------------------------------------------------------------------------------
+
+using Microsoft.Azure.Commands.Common.Authentication.Abstractions;
+using Microsoft.Azure.Commands.Common.Authentication.Config.Internal;
+using Microsoft.Azure.Commands.Common.Authentication.Config.Internal.Interfaces;
+using Microsoft.Azure.Commands.Common.Authentication.Config.Internal.Providers;
+using Microsoft.Azure.Commands.Common.Exceptions;
+using Microsoft.Azure.Commands.ResourceManager.Common;
+using Microsoft.Azure.PowerShell.Common.Config;
+using Microsoft.WindowsAzure.Commands.Utilities.Common;
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Management.Automation;
+using System.Threading;
+
+namespace Microsoft.Azure.Commands.Common.Authentication.Config
+{
+ ///
+ /// Default implementation of , providing CRUD abilities to the configs.
+ ///
+ internal class ConfigManager : IConfigManager
+ {
+ ///
+ public string ConfigFilePath { get; private set; }
+
+ private IConfigurationRoot _root;
+ private readonly ConcurrentDictionary _configDefinitionMap = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase);
+ private IOrderedEnumerable> OrderedConfigDefinitionMap => _configDefinitionMap.OrderBy(x => x.Key);
+ private readonly ConcurrentDictionary EnvironmentVariableToKeyMap = new ConcurrentDictionary();
+ private readonly IEnvironmentVariableProvider _environmentVariableProvider;
+ private readonly IDataStore _dataStore;
+ private readonly JsonConfigWriter _jsonConfigWriter;
+ private readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim();
+
+ ///
+ /// Creates an instance of .
+ ///
+ /// Path to the config file.
+ /// Provider of file system APIs.
+ /// Provider of environment variable APIs.
+ internal ConfigManager(string configFilePath, IDataStore dataStore, IEnvironmentVariableProvider environmentVariableProvider)
+ {
+ _ = dataStore ?? throw new AzPSArgumentNullException($"{nameof(dataStore)} cannot be null.", nameof(dataStore));
+ _ = configFilePath ?? throw new AzPSArgumentNullException($"{nameof(configFilePath)} cannot be null.", nameof(configFilePath));
+ _ = environmentVariableProvider ?? throw new AzPSArgumentNullException($"{nameof(environmentVariableProvider)} cannot be null.", nameof(environmentVariableProvider));
+ ConfigFilePath = configFilePath;
+ _environmentVariableProvider = environmentVariableProvider;
+ _dataStore = dataStore;
+ _jsonConfigWriter = new JsonConfigWriter(ConfigFilePath, _dataStore);
+ }
+
+ ///
+ /// Rebuild config hierarchy and load from the providers.
+ ///
+ public void BuildConfig()
+ {
+ var builder = new ConfigurationBuilder();
+
+ if (SharedUtilities.IsWindowsPlatform())
+ {
+ // User and machine level environment variables are only on Windows
+ builder.AddEnvironmentVariables(Constants.ConfigProviderIds.MachineEnvironment, new EnvironmentVariablesConfigurationOptions()
+ {
+ EnvironmentVariableProvider = _environmentVariableProvider,
+ EnvironmentVariableTarget = EnvironmentVariableTarget.Machine,
+ EnvironmentVariableToKeyMap = EnvironmentVariableToKeyMap
+ })
+ .AddEnvironmentVariables(Constants.ConfigProviderIds.UserEnvironment, new EnvironmentVariablesConfigurationOptions()
+ {
+ EnvironmentVariableProvider = _environmentVariableProvider,
+ EnvironmentVariableTarget = EnvironmentVariableTarget.User,
+ EnvironmentVariableToKeyMap = EnvironmentVariableToKeyMap
+ });
+ }
+ builder.AddJsonStream(Constants.ConfigProviderIds.UserConfig, _dataStore.ReadFileAsStream(ConfigFilePath))
+ .AddEnvironmentVariables(Constants.ConfigProviderIds.ProcessEnvironment, new EnvironmentVariablesConfigurationOptions()
+ {
+ EnvironmentVariableProvider = _environmentVariableProvider,
+ EnvironmentVariableTarget = EnvironmentVariableTarget.Process,
+ EnvironmentVariableToKeyMap = EnvironmentVariableToKeyMap
+ })
+ .AddUnsettableInMemoryCollection(Constants.ConfigProviderIds.ProcessConfig);
+
+ _lock.EnterReadLock();
+ try
+ {
+ _root = builder.Build();
+ }
+ finally
+ {
+ _lock.ExitReadLock();
+ }
+ }
+
+ ///
+ public void RegisterConfig(ConfigDefinition config)
+ {
+ // check if key already taken
+ if (_configDefinitionMap.ContainsKey(config.Key))
+ {
+ if (_configDefinitionMap[config.Key] == config)
+ {
+ Debug.WriteLine($"Config with key [{config.Key}] was registered twice");
+ }
+ else
+ {
+ throw new AzPSArgumentException($"Duplicated config key. [{config.Key}] was already taken.", nameof(config.Key));
+ }
+ return;
+ }
+ // configure environment variable providers
+ if (!string.IsNullOrEmpty(config.EnvironmentVariableName))
+ {
+ EnvironmentVariableToKeyMap[config.EnvironmentVariableName] = ConfigPathHelper.GetPathOfConfig(config.Key);
+ }
+ _configDefinitionMap[config.Key] = config;
+ }
+
+ ///
+ public T GetConfigValue(string key, object invocation = null)
+ {
+ if (invocation != null && !(invocation is InvocationInfo))
+ {
+ throw new AzPSArgumentException($"Type error: type of {nameof(invocation)} must be {nameof(InvocationInfo)}", nameof(invocation));
+ }
+ return GetConfigValueInternal(key, new InvocationInfoAdapter((InvocationInfo)invocation));
+ }
+
+ internal T GetConfigValueInternal(string key, InternalInvocationInfo invocation) => (T)GetConfigValueInternal(key, invocation);
+
+ internal object GetConfigValueInternal(string key, InternalInvocationInfo invocation)
+ {
+ _ = key ?? throw new AzPSArgumentNullException($"{nameof(key)} cannot be null.", nameof(key));
+ if (!_configDefinitionMap.TryGetValue(key, out ConfigDefinition definition) || definition == null)
+ {
+ throw new AzPSArgumentException($"Config with key [{key}] was not registered.", nameof(key));
+ }
+
+ foreach (var path in ConfigPathHelper.EnumerateConfigPaths(key, invocation))
+ {
+ IConfigurationSection section = _root.GetSection(path);
+ if (section.Exists())
+ {
+ (object value, _) = GetConfigValueOrDefault(section, definition);
+ WriteDebug($"[ConfigManager] Got [{value}] from [{key}], Module = [{invocation?.ModuleName}], Cmdlet = [{invocation?.CmdletName}].");
+ return value;
+ }
+ }
+
+ WriteDebug($"[ConfigManager] Got nothing from [{key}], Module = [{invocation?.ModuleName}], Cmdlet = [{invocation?.CmdletName}]. Returning default value [{definition.DefaultValue}].");
+ return definition.DefaultValue;
+ }
+
+ private void WriteDebug(string message)
+ {
+ WriteMessage(message, AzureRMCmdlet.WriteDebugKey);
+ }
+
+ private void WriteMessage(string message, string eventHandlerKey)
+ {
+ try
+ {
+ if (AzureSession.Instance.TryGetComponent(eventHandlerKey, out EventHandler writeDebug))
+ {
+ writeDebug.Invoke(this, new StreamEventArgs() { Message = message });
+ }
+ }
+ catch (Exception)
+ {
+ // do not throw when session is not initialized
+ }
+ }
+
+ private void WriteWarning(string message)
+ {
+ WriteMessage(message, AzureRMCmdlet.WriteWarningKey);
+ }
+
+ ///
+ public IEnumerable ListConfigDefinitions()
+ {
+ return OrderedConfigDefinitionMap.Select(x => x.Value);
+ }
+
+ ///
+ public IEnumerable ListConfigs(ConfigFilter filter = null)
+ {
+ IList results = new List();
+
+ // include all values
+ ISet noNeedForDefault = new HashSet();
+ foreach (var appliesToSection in _root.GetChildren())
+ {
+ foreach (var configSection in appliesToSection.GetChildren())
+ {
+ string key = configSection.Key;
+ if (_configDefinitionMap.TryGetValue(key, out var configDefinition))
+ {
+ (object value, string providerId) = GetConfigValueOrDefault(configSection, configDefinition);
+ ConfigScope scope = ConfigScopeHelper.GetScopeByProviderId(providerId);
+ results.Add(new ConfigData(configDefinition, value, scope, appliesToSection.Key));
+ // if a config is already set at global level, there's no need to return its default value
+ if (string.Equals(ConfigFilter.GlobalAppliesTo, appliesToSection.Key, StringComparison.OrdinalIgnoreCase))
+ {
+ noNeedForDefault.Add(configDefinition.Key);
+ }
+ }
+ }
+ }
+
+ // include default values
+ IEnumerable keys = filter?.Keys ?? Enumerable.Empty();
+ bool isRegisteredKey(string key) => _configDefinitionMap.Keys.Contains(key, StringComparer.OrdinalIgnoreCase);
+ IEnumerable configDefinitions = keys.Any() ? keys.Where(isRegisteredKey).Select(key => _configDefinitionMap[key]) : OrderedConfigDefinitionMap.Select(x => x.Value);
+ configDefinitions.Where(x => !noNeedForDefault.Contains(x.Key)).Select(x => GetDefaultConfigData(x)).ForEach(x => results.Add(x));
+
+
+ if (keys.Any())
+ {
+ results = results.Where(x => keys.Contains(x.Definition.Key, StringComparer.OrdinalIgnoreCase)).ToList();
+ }
+
+ string appliesTo = filter?.AppliesTo;
+ if (!string.IsNullOrEmpty(appliesTo))
+ {
+ results = results.Where(x => string.Equals(appliesTo, x.AppliesTo, StringComparison.OrdinalIgnoreCase)).ToList();
+ }
+
+ return results;
+ }
+
+ ///
+ /// Get the value and the ID of the corresponding provider of the config.
+ ///
+ /// The section that stores the config.
+ /// The definition of the config.
+ /// A tuple containing the value of the config and the ID of the provider from which the value is got.
+ /// Exceptions are handled gracefully in this method.
+ private (object value, string providerId) GetConfigValueOrDefault(IConfigurationSection section, ConfigDefinition definition)
+ {
+ try
+ {
+ return section.Get(definition.ValueType);
+ }
+ catch (InvalidOperationException ex)
+ {
+ WriteWarning($"[ConfigManager] Failed to get value for [{definition.Key}]. Using the default value [{definition.DefaultValue}] instead. Error: {ex.Message}. {ex.InnerException?.Message}");
+ WriteDebug($"[ConfigManager] Exception: {ex.Message}, stack trace: \n{ex.StackTrace}");
+ return (definition.DefaultValue, Constants.ConfigProviderIds.None);
+ }
+ }
+
+ private ConfigData GetDefaultConfigData(ConfigDefinition configDefinition)
+ {
+ return new ConfigData(configDefinition,
+ configDefinition.DefaultValue,
+ ConfigScope.Default,
+ ConfigFilter.GlobalAppliesTo);
+ }
+
+ // A bulk update API is currently unnecessary as we don't expect users to do that.
+ // But if telemetry data proves it's a demanded feature, we might add it in the future.
+ // public IEnumerable UpdateConfigs(IEnumerable updateConfigOptions) => updateConfigOptions.Select(UpdateConfig);
+
+ ///
+ public ConfigData UpdateConfig(string key, object value, ConfigScope scope)
+ {
+ return UpdateConfig(new UpdateConfigOptions(key, value, scope));
+ }
+
+ ///
+ public ConfigData UpdateConfig(UpdateConfigOptions options)
+ {
+ if (options == null)
+ {
+ throw new AzPSArgumentNullException($"{nameof(options)} cannot be null when updating config.", nameof(options));
+ }
+
+ if (!_configDefinitionMap.TryGetValue(options.Key, out ConfigDefinition definition) || definition == null)
+ {
+ throw new AzPSArgumentException($"Config with key [{options.Key}] was not registered.", nameof(options.Key));
+ }
+
+ try
+ {
+ definition.Validate(options.Value);
+ }
+ catch (Exception e)
+ {
+ throw new AzPSArgumentException(e.Message, e);
+ }
+
+ if (AppliesToHelper.TryParseAppliesTo(options.AppliesTo, out var appliesTo) && !definition.CanApplyTo.Contains(appliesTo))
+ {
+ throw new AzPSArgumentException($"[{options.AppliesTo}] is not a valid value for AppliesTo - it doesn't match any of ({AppliesToHelper.FormatOptions(definition.CanApplyTo)}).", nameof(options.AppliesTo));
+ }
+
+ definition.Apply(options.Value);
+
+ string path = ConfigPathHelper.GetPathOfConfig(options.Key, options.AppliesTo);
+
+ switch (options.Scope)
+ {
+ case ConfigScope.Process:
+ SetProcessLevelConfig(path, options.Value);
+ break;
+ case ConfigScope.CurrentUser:
+ SetUserLevelConfig(path, options.Value);
+ break;
+ }
+
+ WriteDebug($"[ConfigManager] Updated [{options.Key}] to [{options.Value}]. Scope = [{options.Scope}], AppliesTo = [{options.AppliesTo}]");
+
+ return new ConfigData(definition, options.Value, options.Scope, options.AppliesTo);
+ }
+
+ private void SetProcessLevelConfig(string path, object value)
+ {
+ GetProcessLevelConfigProvider().Set(path, value.ToString());
+ }
+
+ private UnsettableMemoryConfigurationProvider GetProcessLevelConfigProvider()
+ {
+ return _root.GetConfigurationProvider(Constants.ConfigProviderIds.ProcessConfig) as UnsettableMemoryConfigurationProvider;
+ }
+
+ private void SetUserLevelConfig(string path, object value)
+ {
+ _lock.EnterWriteLock();
+ try
+ {
+ _jsonConfigWriter.Update(path, value);
+ }
+ finally
+ {
+ _lock.ExitWriteLock();
+ }
+ BuildConfig(); // reload the config values
+ }
+
+ ///
+ public void ClearConfig(string key, ConfigScope scope) => ClearConfig(new ClearConfigOptions(key, scope));
+
+ ///
+ public void ClearConfig(ClearConfigOptions options)
+ {
+ _ = options ?? throw new AzPSArgumentNullException($"{nameof(options)} cannot be null.", nameof(options));
+
+ bool clearAll = string.IsNullOrEmpty(options.Key);
+
+ if (clearAll)
+ {
+ ClearAllConfigs(options);
+ }
+ else
+ {
+ ClearConfigByKey(options);
+ }
+ }
+
+ private void ClearAllConfigs(ClearConfigOptions options)
+ {
+ switch (options.Scope)
+ {
+ case ConfigScope.Process:
+ ClearProcessLevelAllConfigs(options);
+ break;
+ case ConfigScope.CurrentUser:
+ ClearUserLevelAllConfigs(options);
+ break;
+ default:
+ throw new AzPSArgumentException($"[{options.Scope}] is not a valid scope when clearing configs.", nameof(options.Scope));
+ }
+ WriteDebug($"[ConfigManager] Cleared all the configs. Scope = [{options.Scope}].");
+ }
+
+ private void ClearProcessLevelAllConfigs(ClearConfigOptions options)
+ {
+ var configProvider = GetProcessLevelConfigProvider();
+ if (string.IsNullOrEmpty(options.AppliesTo))
+ {
+ configProvider.UnsetAll();
+ }
+ else
+ {
+ foreach (var key in _configDefinitionMap.Keys)
+ {
+ configProvider.Unset(ConfigPathHelper.GetPathOfConfig(key, options.AppliesTo));
+ }
+ }
+ }
+
+ private void ClearUserLevelAllConfigs(ClearConfigOptions options)
+ {
+ _lock.EnterWriteLock();
+ try
+ {
+ if (string.IsNullOrEmpty(options.AppliesTo))
+ {
+ _jsonConfigWriter.ClearAll();
+ }
+ else
+ {
+ foreach (var key in _configDefinitionMap.Keys)
+ {
+ _jsonConfigWriter.Clear(ConfigPathHelper.GetPathOfConfig(key, options.AppliesTo));
+ }
+ }
+ }
+ finally
+ {
+ _lock.ExitWriteLock();
+ }
+ BuildConfig();
+ }
+
+ private void ClearConfigByKey(ClearConfigOptions options)
+ {
+ if (!_configDefinitionMap.TryGetValue(options.Key, out ConfigDefinition definition))
+ {
+ throw new AzPSArgumentException($"Config with key [{options.Key}] was not registered.", nameof(options.Key));
+ }
+
+ string path = ConfigPathHelper.GetPathOfConfig(definition.Key, options.AppliesTo);
+
+ switch (options.Scope)
+ {
+ case ConfigScope.Process:
+ GetProcessLevelConfigProvider().Unset(path);
+ break;
+ case ConfigScope.CurrentUser:
+ ClearUserLevelConfigByKey(path);
+ break;
+ }
+
+ WriteDebug($"[ConfigManager] Cleared [{options.Key}]. Scope = [{options.Scope}], AppliesTo = [{options.AppliesTo}]");
+ }
+
+ private void ClearUserLevelConfigByKey(string key)
+ {
+ _lock.EnterWriteLock();
+ try
+ {
+ _jsonConfigWriter.Clear(key);
+ }
+ finally
+ {
+ _lock.ExitWriteLock();
+ }
+ BuildConfig();
+ }
+ }
+}
diff --git a/src/Accounts/Authentication/Config/Helper/AppliesToHelper.cs b/src/Accounts/Authentication/Config/Helper/AppliesToHelper.cs
new file mode 100644
index 000000000000..32902f509faa
--- /dev/null
+++ b/src/Accounts/Authentication/Config/Helper/AppliesToHelper.cs
@@ -0,0 +1,99 @@
+// ----------------------------------------------------------------------------------
+//
+// Copyright Microsoft Corporation
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ----------------------------------------------------------------------------------
+
+using Microsoft.Azure.PowerShell.Common.Config;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Text.RegularExpressions;
+
+namespace Microsoft.Azure.Commands.Common.Authentication.Config
+{
+ ///
+ /// Helper class to deal with AppliesTo (how large is the scope that the config affects Azure PowerShell).
+ ///
+ public static class AppliesToHelper
+ {
+ internal static readonly Regex ModulePattern = new Regex(@"^az\.[a-z]+$", RegexOptions.IgnoreCase);
+ internal static readonly Regex CmdletPattern = new Regex(@"^[a-z]+-[a-z]+$", RegexOptions.IgnoreCase);
+ internal static readonly Regex ModuleOrCmdletPattern = new Regex(@"^az\.[a-z]+$|^[a-z]+-[a-z]+$", RegexOptions.IgnoreCase);
+
+ ///
+ /// Tries to parse a user-input text to an enum.
+ ///
+ /// Input from user.
+ /// Result if successful.
+ /// True if parsed successfully.
+ public static bool TryParseAppliesTo(string text, out AppliesTo appliesTo)
+ {
+ if (string.IsNullOrEmpty(text) || string.Equals(ConfigFilter.GlobalAppliesTo, text, StringComparison.OrdinalIgnoreCase))
+ {
+ appliesTo = AppliesTo.Az;
+ return true;
+ }
+
+ if (ModulePattern.IsMatch(text))
+ {
+ appliesTo = AppliesTo.Module;
+ return true;
+ }
+
+ if (CmdletPattern.IsMatch(text))
+ {
+ appliesTo = AppliesTo.Cmdlet;
+ return true;
+ }
+
+ appliesTo = AppliesTo.Az;
+ return false;
+ }
+
+ ///
+ /// Gets a comma-divided string for human-readable description of the AppliesTo options.
+ ///
+ /// Options of AppliesTo.
+ /// The formated string.
+ internal static string FormatOptions(IReadOnlyCollection options)
+ {
+ if (options == null || !options.Any())
+ {
+ throw new ArgumentException($"Make sure the config definition has a non-empty {nameof(ConfigDefinition.CanApplyTo)}.", nameof(options));
+ }
+ var sb = new StringBuilder();
+ bool isFirst = true;
+ foreach (var option in options)
+ {
+ if (!isFirst)
+ {
+ sb.Append(", ");
+ isFirst = false;
+ }
+ switch (option)
+ {
+ case AppliesTo.Az:
+ sb.Append(ConfigFilter.GlobalAppliesTo);
+ break;
+ case AppliesTo.Cmdlet:
+ sb.Append("name of a cmdlet");
+ break;
+ case AppliesTo.Module:
+ sb.Append("name of a module");
+ break;
+ }
+ }
+ return sb.ToString();
+ }
+ }
+}
diff --git a/src/Accounts/Authentication/Config/Helper/ConfigPathHelper.cs b/src/Accounts/Authentication/Config/Helper/ConfigPathHelper.cs
new file mode 100644
index 000000000000..73b2b4b5dbe3
--- /dev/null
+++ b/src/Accounts/Authentication/Config/Helper/ConfigPathHelper.cs
@@ -0,0 +1,59 @@
+// ----------------------------------------------------------------------------------
+//
+// Copyright Microsoft Corporation
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ----------------------------------------------------------------------------------
+
+using Microsoft.Azure.Commands.Common.Authentication.Config.Internal;
+using Microsoft.Azure.PowerShell.Common.Config;
+using System.Collections.Generic;
+
+namespace Microsoft.Azure.Commands.Common.Authentication.Config
+{
+ ///
+ /// Helper class to deal with the full path where configs are stored.
+ ///
+ internal static class ConfigPathHelper
+ {
+ ///
+ /// Gets a list of paths to check when getting a config value by key and invocation info.
+ ///
+ /// The key in the config definition.
+ /// Command invocation info, containing command name and module name.
+ public static IEnumerable EnumerateConfigPaths(string key, InternalInvocationInfo invocation = null)
+ {
+ if (!string.IsNullOrEmpty(invocation?.CmdletName))
+ {
+ yield return GetPathOfConfig(key, invocation.CmdletName);
+ }
+ if (!string.IsNullOrEmpty(invocation?.ModuleName))
+ {
+ yield return GetPathOfConfig(key, invocation.ModuleName);
+ }
+ yield return GetPathOfConfig(key);
+ }
+
+ ///
+ /// Get the path (full key) of a config by its key and what it applies to.
+ ///
+ ///
+ /// Global appliesTo by default.
+ ///
+ internal static string GetPathOfConfig(string key, string appliesTo = null)
+ {
+ if (string.IsNullOrEmpty(appliesTo))
+ {
+ appliesTo = ConfigFilter.GlobalAppliesTo;
+ }
+ return appliesTo + ConfigurationPath.KeyDelimiter + key;
+ }
+ }
+}
diff --git a/src/Accounts/Authentication/Config/Helper/ConfigScopeHelper.cs b/src/Accounts/Authentication/Config/Helper/ConfigScopeHelper.cs
new file mode 100644
index 000000000000..16165b01cbf2
--- /dev/null
+++ b/src/Accounts/Authentication/Config/Helper/ConfigScopeHelper.cs
@@ -0,0 +1,40 @@
+// ----------------------------------------------------------------------------------
+//
+// Copyright Microsoft Corporation
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ----------------------------------------------------------------------------------
+
+using Microsoft.Azure.Commands.Common.Exceptions;
+using Microsoft.Azure.PowerShell.Common.Config;
+
+namespace Microsoft.Azure.Commands.Common.Authentication.Config
+{
+ internal static class ConfigScopeHelper
+ {
+ public static ConfigScope GetScopeByProviderId(string id)
+ {
+ switch (id)
+ {
+ case Constants.ConfigProviderIds.MachineEnvironment:
+ case Constants.ConfigProviderIds.UserEnvironment:
+ case Constants.ConfigProviderIds.UserConfig:
+ return ConfigScope.CurrentUser;
+ case Constants.ConfigProviderIds.ProcessEnvironment:
+ case Constants.ConfigProviderIds.ProcessConfig:
+ return ConfigScope.Process;
+ case Constants.ConfigProviderIds.None:
+ return ConfigScope.Default;
+ default:
+ throw new AzPSArgumentOutOfRangeException($"Unexpected provider ID [{id}]. See {nameof(Constants.ConfigProviderIds)} class for all valid IDs.", nameof(id));
+ }
+ }
+ }
+}
diff --git a/src/Accounts/Authentication/Config/Helper/DefaultEnvironmentVariableProvider.cs b/src/Accounts/Authentication/Config/Helper/DefaultEnvironmentVariableProvider.cs
new file mode 100644
index 000000000000..f93c1f373940
--- /dev/null
+++ b/src/Accounts/Authentication/Config/Helper/DefaultEnvironmentVariableProvider.cs
@@ -0,0 +1,35 @@
+// ----------------------------------------------------------------------------------
+//
+// Copyright Microsoft Corporation
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ----------------------------------------------------------------------------------
+
+using Microsoft.Azure.Commands.Common.Authentication.Config.Internal.Interfaces;
+using System;
+
+namespace Microsoft.Azure.Commands.Common.Authentication.Config
+{
+ ///
+ /// Default implementation of that utilizes the API.
+ ///
+ internal class DefaultEnvironmentVariableProvider : IEnvironmentVariableProvider
+ {
+ public string Get(string variableName, EnvironmentVariableTarget target = EnvironmentVariableTarget.Process)
+ {
+ return Environment.GetEnvironmentVariable(variableName, target);
+ }
+
+ public void Set(string variableName, string value, EnvironmentVariableTarget target = EnvironmentVariableTarget.Process)
+ {
+ Environment.SetEnvironmentVariable(variableName, value, target);
+ }
+ }
+}
diff --git a/src/Accounts/Authentication/Config/Helper/JsonConfigWriter.cs b/src/Accounts/Authentication/Config/Helper/JsonConfigWriter.cs
new file mode 100644
index 000000000000..841bb0abede5
--- /dev/null
+++ b/src/Accounts/Authentication/Config/Helper/JsonConfigWriter.cs
@@ -0,0 +1,153 @@
+// ----------------------------------------------------------------------------------
+//
+// Copyright Microsoft Corporation
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ----------------------------------------------------------------------------------
+
+using Microsoft.Azure.Commands.Common.Authentication.Config.Internal;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+using System;
+using System.IO;
+
+namespace Microsoft.Azure.Commands.Common.Authentication.Config
+{
+ ///
+ /// Helper for updating the config JSON file.
+ ///
+ internal class JsonConfigWriter
+ {
+ private readonly string _jsonConfigPath;
+ private readonly IDataStore _dataStore;
+
+ public JsonConfigWriter(string jsonConfigPath, IDataStore dataStore)
+ {
+ _jsonConfigPath = jsonConfigPath;
+ _dataStore = dataStore;
+ }
+
+ ///
+ /// Update a config value.
+ ///
+ /// The full path of the config.
+ /// The value to update.
+ internal void Update(string key, object value) => TryUpdate(key, true, (JObject parent, string propertyName) =>
+ {
+ var prop = parent.Property(propertyName);
+
+ if (prop == null)
+ {
+ prop = new JProperty(propertyName, value);
+
+ parent.Add(prop);
+ }
+ else
+ {
+ prop.Value = IsMultiContent(value) ? new JArray(value) : JToken.FromObject(value);
+ }
+ });
+
+ private bool IsMultiContent(object value)
+ {
+ return value is Array;
+ }
+
+ ///
+ /// Locates the node by key in the JSON object, and performs a general update (add, modify or remove a property).
+ ///
+ /// The full path to the config.
+ /// Whether to create the JSON node when part of the path is missing.
+ /// The concrete action to perform. First argument is the parent node in the JSON object, second is the name of the property to update.
+ /// Whether the update is successful.
+ private bool TryUpdate(string key, bool createWhenNotExist, Action updateAction)
+ {
+ string json = _dataStore.ReadFileAsText(_jsonConfigPath);
+ JObject root = JObject.Parse(json);
+
+ string[] segments = key.Split(ConfigurationPath.KeyDelimiter.ToCharArray());
+ JObject parent = LocateParentNode(root, segments, createWhenNotExist);
+ if (parent == null)
+ {
+ return false;
+ }
+
+ string propertyName = segments[segments.Length - 1];
+
+ updateAction(parent, propertyName);
+
+ // hack: to avoid last version of the config remaining in the file, empty it first
+ _dataStore.WriteFile(_jsonConfigPath, string.Empty);
+
+ JsonSerializer serializer = new JsonSerializer
+ {
+ Formatting = Formatting.Indented
+ };
+ using (Stream fs = _dataStore.OpenForExclusiveWrite(_jsonConfigPath))
+ using (StreamWriter sw = new StreamWriter(fs))
+ using (var writer = new JsonTextWriter(sw) { Indentation = 4 })
+ {
+ serializer.Serialize(writer, root);
+ }
+
+ return true;
+ }
+
+ private static JObject LocateParentNode(JObject root, string[] segments, bool createWhenNotExist)
+ {
+ JObject node = root;
+ for (int i = 0; i < segments.Length - 1; ++i)
+ {
+ string segment = segments[i];
+ // JObject.TryGetValue() supports case insensitivity
+ // otherwise we might get duplicated keys with different casing in the config file
+ if (node.TryGetValue(segment, StringComparison.OrdinalIgnoreCase, out JToken match))
+ {
+ node = (JObject)match;
+ }
+ else
+ {
+ if (createWhenNotExist)
+ {
+ node[segment] = new JObject();
+ node = (JObject)node[segment];
+ }
+ else
+ {
+ return null;
+ }
+ }
+ }
+
+ return node;
+ }
+
+ ///
+ /// Clear a config by key.
+ ///
+ /// The full path to the config.
+ internal void Clear(string key) => TryUpdate(key, false, (parent, propertyName) =>
+ {
+ if (parent.Property(propertyName) != null)
+ {
+ parent.Remove(propertyName);
+ }
+ // if the config is never set, there's no need to clear.
+ });
+
+ ///
+ /// Clear all the configs.
+ ///
+ internal void ClearAll()
+ {
+ _dataStore.WriteFile(_jsonConfigPath, @"{}");
+ }
+ }
+}
diff --git a/src/Accounts/Authentication/Config/Models/InternalInvocationInfo.cs b/src/Accounts/Authentication/Config/Models/InternalInvocationInfo.cs
new file mode 100644
index 000000000000..fe4fdae78343
--- /dev/null
+++ b/src/Accounts/Authentication/Config/Models/InternalInvocationInfo.cs
@@ -0,0 +1,35 @@
+// ----------------------------------------------------------------------------------
+//
+// Copyright Microsoft Corporation
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ----------------------------------------------------------------------------------
+
+namespace Microsoft.Azure.Commands.Common.Authentication.Config
+{
+ ///
+ /// Abstraction for the PS InvocationInfo.
+ ///
+ internal class InternalInvocationInfo
+ {
+ public InternalInvocationInfo() : this(null, null)
+ {
+ }
+
+ public InternalInvocationInfo(string moduleName, string cmdletName)
+ {
+ CmdletName = cmdletName;
+ ModuleName = moduleName;
+ }
+
+ public string ModuleName { get; set; } = null;
+ public string CmdletName { get; set; } = null;
+ }
+}
\ No newline at end of file
diff --git a/src/Accounts/Authentication/Config/Models/InvocationInfoAdapter.cs b/src/Accounts/Authentication/Config/Models/InvocationInfoAdapter.cs
new file mode 100644
index 000000000000..b304ed5c2879
--- /dev/null
+++ b/src/Accounts/Authentication/Config/Models/InvocationInfoAdapter.cs
@@ -0,0 +1,25 @@
+// ----------------------------------------------------------------------------------
+//
+// Copyright Microsoft Corporation
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ----------------------------------------------------------------------------------
+
+using System.Management.Automation;
+
+namespace Microsoft.Azure.Commands.Common.Authentication.Config
+{
+ internal class InvocationInfoAdapter : InternalInvocationInfo
+ {
+ public InvocationInfoAdapter(InvocationInfo invocationInfo) : base(
+ invocationInfo?.MyCommand?.ModuleName, invocationInfo?.MyCommand?.Name)
+ { }
+ }
+}
diff --git a/src/Accounts/Authentication/Config/Models/PSConfig.cs b/src/Accounts/Authentication/Config/Models/PSConfig.cs
new file mode 100644
index 000000000000..782608543971
--- /dev/null
+++ b/src/Accounts/Authentication/Config/Models/PSConfig.cs
@@ -0,0 +1,42 @@
+// ----------------------------------------------------------------------------------
+//
+// Copyright Microsoft Corporation
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ----------------------------------------------------------------------------------
+
+using Microsoft.Azure.PowerShell.Common.Config;
+
+///
+/// The output model of config-related cmdlets.
+///
+namespace Microsoft.Azure.Commands.Common.Authentication.Config
+{
+ public class PSConfig
+ {
+ public string Key { get; }
+ public object Value { get; }
+ public ConfigScope Scope { get; } = ConfigScope.CurrentUser;
+ public string AppliesTo { get; }
+ public string HelpMessage { get; }
+ public object DefaultValue { get; }
+ public PSConfig(ConfigData config)
+ {
+ Value = config.Value;
+ Scope = config.Scope;
+ AppliesTo = config.AppliesTo;
+
+ var def = config.Definition;
+ Key = def.Key;
+ HelpMessage = def.HelpMessage;
+ DefaultValue = def.DefaultValue;
+ }
+ }
+}
diff --git a/src/Accounts/Authentication/Config/Models/SimpleTypedConfig.cs b/src/Accounts/Authentication/Config/Models/SimpleTypedConfig.cs
new file mode 100644
index 000000000000..8300fe8b445e
--- /dev/null
+++ b/src/Accounts/Authentication/Config/Models/SimpleTypedConfig.cs
@@ -0,0 +1,50 @@
+// ----------------------------------------------------------------------------------
+//
+// Copyright Microsoft Corporation
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ----------------------------------------------------------------------------------
+
+using Microsoft.Azure.PowerShell.Common.Config;
+using System.Collections.Generic;
+
+namespace Microsoft.Azure.Commands.Common.Authentication.Config
+{
+ ///
+ /// Represents a simple typed config. For complex configs please define your own type inheriting or .
+ ///
+ /// Type of the config value.
+ internal class SimpleTypedConfig : TypedConfig
+ {
+ private readonly string _key;
+ private readonly string _helpMessage;
+ private readonly TValue _defaultValue;
+ private readonly string _environmentVariable;
+ private readonly IReadOnlyCollection _canApplyTo = null;
+
+ public SimpleTypedConfig(string key, string helpMessage, TValue defaultValue, string environmentVariable = null, IReadOnlyCollection canApplyTo = null)
+ {
+ _key = key;
+ _helpMessage = helpMessage;
+ _defaultValue = defaultValue;
+ _environmentVariable = environmentVariable;
+ _canApplyTo = canApplyTo;
+ }
+
+ public override string Key => _key;
+ public override string HelpMessage => _helpMessage;
+ public override object DefaultValue => _defaultValue;
+ public override string EnvironmentVariableName => _environmentVariable;
+ public override IReadOnlyCollection CanApplyTo
+ {
+ get { return _canApplyTo ?? base.CanApplyTo; }
+ }
+ }
+}
diff --git a/src/Accounts/Authentication/Config/Models/TypedConfig.cs b/src/Accounts/Authentication/Config/Models/TypedConfig.cs
new file mode 100644
index 000000000000..8c67463f1e71
--- /dev/null
+++ b/src/Accounts/Authentication/Config/Models/TypedConfig.cs
@@ -0,0 +1,69 @@
+// ----------------------------------------------------------------------------------
+//
+// Copyright Microsoft Corporation
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ----------------------------------------------------------------------------------
+
+using Microsoft.Azure.PowerShell.Common.Config;
+using System;
+
+namespace Microsoft.Azure.Commands.Common.Authentication.Config
+{
+ ///
+ /// Base class for configs that have a typed value.
+ ///
+ /// The type of the config value.
+ internal abstract class TypedConfig : ConfigDefinition
+ {
+ protected TypedConfig()
+ {
+ }
+
+ public TValue TypedDefaultValue => (TValue)DefaultValue;
+
+ ///
+ /// Validates if the input value is type .
+ ///
+ /// The value to check.
+ /// Throws when the value in another type.
+ public override void Validate(object value)
+ {
+ base.Validate(value);
+ if (!(value is TValue))
+ {
+ throw new ArgumentException($"Unexpected value type [{value.GetType()}]. The value of config [{Key}] should be of type [{ValueType}]", nameof(value));
+ }
+ }
+
+ ///
+ /// Performs side effects before applying the config.
+ ///
+ /// The value to be applied to this typed config.
+ ///
+ /// This method is sealed.
+ /// Derived types should override .
+ ///
+ public override sealed void Apply(object value)
+ {
+ base.Apply(value);
+ ApplyTyped((TValue)value);
+ }
+
+ ///
+ /// Generic version of .
+ /// Override in child classes to perform side effects before applying the config value.
+ ///
+ /// The value to be applied to this typed config, cast to the correct type.
+ protected virtual void ApplyTyped(TValue value) { }
+
+ public override Type ValueType => typeof(TValue);
+ }
+}
diff --git a/src/Accounts/Authentication/Constants.cs b/src/Accounts/Authentication/Constants.cs
index d4f0256257a7..7b8654fbcb54 100644
--- a/src/Accounts/Authentication/Constants.cs
+++ b/src/Accounts/Authentication/Constants.cs
@@ -24,5 +24,18 @@ public static class Constants
public const string MicrosoftGraphAccessToken = "MicrosoftGraphAccessToken";
public const string DefaultValue = "Default";
+
+ public class ConfigProviderIds
+ {
+ public const string MachineEnvironment = "Environment (Machine)";
+ public const string UserEnvironment = "Environment (User)";
+ public const string ProcessEnvironment = "Environment (Process)";
+ public const string UserConfig = "Config (User)";
+ public const string ProcessConfig = "Config (Process)";
+ ///
+ /// Represents that the value is not in any providers.
+ ///
+ public const string None = "None";
+ }
}
}
From 3f59938167f26f711ce37a768eeb02b0b976d7ca Mon Sep 17 00:00:00 2001
From: Yeming Liu <11371776+isra-fel@users.noreply.github.com>
Date: Thu, 24 Mar 2022 16:41:12 +0800
Subject: [PATCH 3/9] cmdlet related
---
src/Accounts/Accounts/Accounts.format.ps1xml | 57 +++-
src/Accounts/Accounts/Az.Accounts.psd1 | 2 +-
.../Accounts/Config/ClearConfigCommand.cs | 110 ++++++++
.../Accounts/Config/ConfigCommandBase.cs | 98 +++++++
.../Accounts/Config/GetConfigCommand.cs | 66 +++++
.../Accounts/Config/UpdateConfigCommand.cs | 75 ++++++
src/Accounts/Accounts/help/Az.Accounts.md | 9 +
src/Accounts/Accounts/help/Clear-AzConfig.md | 251 ++++++++++++++++++
src/Accounts/Accounts/help/Get-AzConfig.md | 168 ++++++++++++
src/Accounts/Accounts/help/Update-AzConfig.md | 199 ++++++++++++++
10 files changed, 1033 insertions(+), 2 deletions(-)
create mode 100644 src/Accounts/Accounts/Config/ClearConfigCommand.cs
create mode 100644 src/Accounts/Accounts/Config/ConfigCommandBase.cs
create mode 100644 src/Accounts/Accounts/Config/GetConfigCommand.cs
create mode 100644 src/Accounts/Accounts/Config/UpdateConfigCommand.cs
create mode 100644 src/Accounts/Accounts/help/Clear-AzConfig.md
create mode 100644 src/Accounts/Accounts/help/Get-AzConfig.md
create mode 100644 src/Accounts/Accounts/help/Update-AzConfig.md
diff --git a/src/Accounts/Accounts/Accounts.format.ps1xml b/src/Accounts/Accounts/Accounts.format.ps1xml
index 818c9fc643a1..b4f4f5ef762a 100644
--- a/src/Accounts/Accounts/Accounts.format.ps1xml
+++ b/src/Accounts/Accounts/Accounts.format.ps1xml
@@ -276,6 +276,61 @@
-
+
+ Microsoft.Azure.Commands.Common.Authentication.Config.PSConfig
+
+ Microsoft.Azure.Commands.Common.Authentication.Config.PSConfig
+
+
+
+
+ Left
+
+
+
+ Left
+
+
+
+ Left
+
+
+
+ Left
+
+
+
+ Left
+
+
+
+
+
+
+
+ Left
+ Key
+
+
+ Left
+ Value
+
+
+ Left
+ AppliesTo
+
+
+ Left
+ Scope
+
+
+ Left
+ HelpMessage
+
+
+
+
+
+
diff --git a/src/Accounts/Accounts/Az.Accounts.psd1 b/src/Accounts/Accounts/Az.Accounts.psd1
index 224a2daa0d83..e3dd8a869176 100644
--- a/src/Accounts/Accounts/Az.Accounts.psd1
+++ b/src/Accounts/Accounts/Az.Accounts.psd1
@@ -108,7 +108,7 @@ CmdletsToExport = 'Disable-AzDataCollection', 'Disable-AzContextAutosave',
'Set-AzDefault', 'Get-AzDefault', 'Clear-AzDefault',
'Register-AzModule', 'Enable-AzureRmAlias', 'Disable-AzureRmAlias',
'Uninstall-AzureRm', 'Invoke-AzRestMethod', 'Get-AzAccessToken',
- 'Open-AzSurveyLink'
+ 'Open-AzSurveyLink', 'Get-AzConfig', 'Update-AzConfig', 'Clear-AzConfig'
# Variables to export from this module
# VariablesToExport = @()
diff --git a/src/Accounts/Accounts/Config/ClearConfigCommand.cs b/src/Accounts/Accounts/Config/ClearConfigCommand.cs
new file mode 100644
index 000000000000..e5ba6206b004
--- /dev/null
+++ b/src/Accounts/Accounts/Config/ClearConfigCommand.cs
@@ -0,0 +1,110 @@
+// ----------------------------------------------------------------------------------
+//
+// Copyright Microsoft Corporation
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ----------------------------------------------------------------------------------
+
+using Microsoft.Azure.PowerShell.Common.Config;
+using Microsoft.WindowsAzure.Commands.Utilities.Common;
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Linq;
+using System.Management.Automation;
+
+namespace Microsoft.Azure.Commands.Common.Authentication.Config
+{
+ [Cmdlet("Clear", "AzConfig", SupportsShouldProcess = true)]
+ [OutputType(typeof(bool))]
+ public class ClearConfigCommand : ConfigCommandBase, IDynamicParameters
+ {
+ private const string ClearByKey = "ClearByKey";
+ private const string ClearAll = "ClearAll";
+
+ private const string ProcessMessage = "Clear the configs that apply to \"{0}\" by the following keys: {1}.";
+
+ private string ContinueMessage => $"Clear all the configs that apply to \"{AppliesTo}\" in scope {Scope}?";
+ private string ProcessTarget => $"Configs in scope {Scope}";
+
+ [Parameter(ParameterSetName = ClearAll, Mandatory = true, HelpMessage = "Clear all configs.")]
+ public SwitchParameter All { get; set; }
+
+ [Parameter(ParameterSetName = ClearAll, HelpMessage = "Do not ask for confirmation when clearing all configs.")]
+ public SwitchParameter Force { get; set; }
+
+ [Parameter(HelpMessage = "Returns true if cmdlet executes correctly.")]
+ public SwitchParameter PassThru { get; set; }
+
+ public new object GetDynamicParameters()
+ {
+ return GetDynamicParameters((ConfigDefinition config) =>
+ new RuntimeDefinedParameter(
+ config.Key,
+ typeof(SwitchParameter),
+ new Collection() {
+ new ParameterAttribute {
+ ParameterSetName = ClearByKey,
+ HelpMessage = config.HelpMessage
+ }
+ }));
+ }
+
+ public override void ExecuteCmdlet()
+ {
+ switch (ParameterSetName)
+ {
+ case ClearByKey:
+ ClearConfigByKey();
+ break;
+ case ClearAll:
+ ClearAllConfigs();
+ break;
+ }
+ if (PassThru)
+ {
+ WriteObject(true);
+ }
+ }
+
+ private void ClearConfigByKey()
+ {
+ IEnumerable configKeysFromInput = GetConfigsSpecifiedByUser().Where(x => (bool)x.Value).Select(x => x.Key);
+ if (!configKeysFromInput.Any())
+ {
+ WriteWarning($"Please specify the key(s) of the configs to clear. Run `help {MyInvocation.MyCommand.Name}` for more information.");
+ return;
+ }
+ base.ConfirmAction(
+ string.Format(ProcessMessage, AppliesTo, string.Join(", ", configKeysFromInput)),
+ ProcessTarget,
+ () => configKeysFromInput.ForEach(ClearConfigByKey));
+ }
+
+ private void ClearConfigByKey(string key)
+ {
+ ConfigManager.ClearConfig(new ClearConfigOptions(key, Scope)
+ {
+ AppliesTo = AppliesTo
+ });
+ }
+
+ private void ClearAllConfigs()
+ {
+ ConfirmAction(Force, ContinueMessage, ContinueMessage, ProcessTarget, () =>
+ {
+ ConfigManager.ClearConfig(new ClearConfigOptions(null, Scope)
+ {
+ AppliesTo = AppliesTo
+ });
+ });
+ }
+ }
+}
diff --git a/src/Accounts/Accounts/Config/ConfigCommandBase.cs b/src/Accounts/Accounts/Config/ConfigCommandBase.cs
new file mode 100644
index 000000000000..dc5b43c06b66
--- /dev/null
+++ b/src/Accounts/Accounts/Config/ConfigCommandBase.cs
@@ -0,0 +1,98 @@
+// ----------------------------------------------------------------------------------
+//
+// Copyright Microsoft Corporation
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ----------------------------------------------------------------------------------
+
+using Microsoft.Azure.Commands.Common.Exceptions;
+using Microsoft.Azure.Commands.ResourceManager.Common;
+using Microsoft.Azure.PowerShell.Common.Config;
+using Microsoft.WindowsAzure.Commands.Common.CustomAttributes;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Management.Automation;
+
+namespace Microsoft.Azure.Commands.Common.Authentication.Config
+{
+ [CmdletPreview("The cmdlet group \"AzConfig\" is in preview. Feedback is welcome: https://github.com/Azure/azure-powershell/discussions")]
+ public abstract class ConfigCommandBase : AzureRMCmdlet
+ {
+ private readonly RuntimeDefinedParameterDictionary _dynamicParameters = new RuntimeDefinedParameterDictionary();
+
+ protected IConfigManager ConfigManager { get; }
+ protected IEnumerable ConfigDefinitions
+ {
+ get
+ {
+ if (_configDefinitions == null)
+ {
+ _configDefinitions = ConfigManager.ListConfigDefinitions();
+ }
+ return _configDefinitions;
+ }
+ }
+ private IEnumerable _configDefinitions;
+
+ public ConfigCommandBase() : base()
+ {
+ if (!AzureSession.Instance.TryGetComponent(nameof(IConfigManager), out var configManager))
+ {
+ throw new AzPSApplicationException($"Unexpected error: {nameof(IConfigManager)} has not been registered to the current session.");
+ }
+ ConfigManager = configManager;
+ }
+
+ [Parameter(HelpMessage = "Specifies what part of Azure PowerShell the config applies to. Possible values are:\n- \"" + ConfigFilter.GlobalAppliesTo + "\": the config applies to all modules and cmdlets of Azure PowerShell. \n- Module name: the config applies to a certain module of Azure PowerShell. For example, \"Az.Storage\".\n- Cmdlet name: the config applies to a certain cmdlet of Azure PowerShell. For example, \"Get-AzKeyVault\".\nIf not specified, when getting configs, output will be all of the above; when updating, it defaults to \"" + ConfigFilter.GlobalAppliesTo + "\"; when clearing, configs applying to any targets are cleared.")]
+ [ValidateNotNullOrEmpty]
+ public string AppliesTo { get; set; }
+
+ [Parameter(HelpMessage = "Determines the scope of config changes, for example, whether changes apply only to the current process, or to all sessions started by this user. By default it is CurrentUser.")]
+ public ConfigScope Scope { get; set; } = ConfigScope.CurrentUser;
+
+ protected override void BeginProcessing()
+ {
+ base.BeginProcessing();
+ ValidateParameters();
+ }
+
+ protected virtual void ValidateParameters()
+ {
+ if (!AppliesToHelper.TryParseAppliesTo(AppliesTo, out _))
+ {
+ throw new AzPSArgumentException($"{nameof(AppliesTo)} must be a valid module name, a cmdlet name, or \"{ConfigFilter.GlobalAppliesTo}\"", nameof(AppliesTo));
+ }
+ }
+
+ protected object GetDynamicParameters(Func mapConfigToParameter)
+ {
+ _dynamicParameters.Clear();
+ foreach (var config in ConfigDefinitions)
+ {
+ _dynamicParameters.Add(config.Key, mapConfigToParameter(config));
+ }
+ return _dynamicParameters;
+ }
+
+ ///
+ /// Gets the dynamic parameters and their values if specified.
+ ///
+ ///
+ protected IEnumerable<(string Key, object Value)> GetConfigsSpecifiedByUser()
+ {
+ var configs = new Dictionary();
+ foreach (var param in _dynamicParameters.Values.Where(p => p.IsSet))
+ {
+ yield return (param.Name, param.Value);
+ }
+ }
+ }
+}
diff --git a/src/Accounts/Accounts/Config/GetConfigCommand.cs b/src/Accounts/Accounts/Config/GetConfigCommand.cs
new file mode 100644
index 000000000000..9f0147e88e1d
--- /dev/null
+++ b/src/Accounts/Accounts/Config/GetConfigCommand.cs
@@ -0,0 +1,66 @@
+// ----------------------------------------------------------------------------------
+//
+// Copyright Microsoft Corporation
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ----------------------------------------------------------------------------------
+
+using Microsoft.Azure.Commands.ResourceManager.Common;
+using Microsoft.Azure.PowerShell.Common.Config;
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Linq;
+using System.Management.Automation;
+
+namespace Microsoft.Azure.Commands.Common.Authentication.Config
+{
+ [Cmdlet(VerbsCommon.Get, AzureRMConstants.AzureRMPrefix + "Config")]
+ [OutputType(typeof(PSConfig))]
+ public class GetConfigCommand : ConfigCommandBase, IDynamicParameters
+ {
+ public GetConfigCommand() : base()
+ {
+ }
+
+ public new object GetDynamicParameters()
+ {
+ return GetDynamicParameters((ConfigDefinition config) =>
+ new RuntimeDefinedParameter(
+ config.Key,
+ typeof(SwitchParameter),
+ new Collection() {
+ new ParameterAttribute {
+ HelpMessage = config.HelpMessage
+ }
+ }));
+ }
+
+ public override void ExecuteCmdlet()
+ {
+ ConfigFilter filter = CreateConfigFilter();
+
+ IEnumerable configs = ConfigManager.ListConfigs(filter);
+ WriteObject(configs.Select(x => new PSConfig(x)), true);
+ }
+
+ private ConfigFilter CreateConfigFilter()
+ {
+ ConfigFilter filter = new ConfigFilter() { AppliesTo = AppliesTo };
+ IEnumerable configKeysFromInput = GetConfigsSpecifiedByUser().Where(x => (bool)x.Value).Select(x => x.Key);
+ if (configKeysFromInput.Any())
+ {
+ filter.Keys = configKeysFromInput;
+ }
+
+ return filter;
+ }
+ }
+}
diff --git a/src/Accounts/Accounts/Config/UpdateConfigCommand.cs b/src/Accounts/Accounts/Config/UpdateConfigCommand.cs
new file mode 100644
index 000000000000..83fb44f9801d
--- /dev/null
+++ b/src/Accounts/Accounts/Config/UpdateConfigCommand.cs
@@ -0,0 +1,75 @@
+// ----------------------------------------------------------------------------------
+//
+// Copyright Microsoft Corporation
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ----------------------------------------------------------------------------------
+
+using Microsoft.Azure.PowerShell.Common.Config;
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Linq;
+using System.Management.Automation;
+
+namespace Microsoft.Azure.Commands.Common.Authentication.Config
+{
+ [Cmdlet("Update", "AzConfig", SupportsShouldProcess = true)]
+ [OutputType(typeof(PSConfig))]
+ public class UpdateConfigCommand : ConfigCommandBase, IDynamicParameters
+ {
+ private const string ProcessMessage = "Update the configs that apply to \"{0}\" by the following keys: {1}.";
+ private string ProcessTarget => $"Configs in scope {Scope}";
+
+ public new object GetDynamicParameters() => GetDynamicParameters(
+ (ConfigDefinition config) => new RuntimeDefinedParameter(
+ config.Key, config.ValueType,
+ new Collection() { new ParameterAttribute {
+ HelpMessage = config.HelpMessage,
+ ValueFromPipelineByPropertyName = true
+ } }
+ ));
+
+ protected override void BeginProcessing()
+ {
+ base.BeginProcessing();
+ if (AppliesTo == null)
+ {
+ AppliesTo = ConfigFilter.GlobalAppliesTo;
+ }
+ }
+
+ public override void ExecuteCmdlet()
+ {
+ var configsFromInput = GetConfigsSpecifiedByUser();
+ if (!configsFromInput.Any())
+ {
+ WriteWarning($"Please specify the key(s) of the configs to update. Run `help {MyInvocation.MyCommand.Name}` for more information.");
+ return;
+ }
+ base.ConfirmAction(
+ string.Format(ProcessMessage, AppliesTo, string.Join(", ", configsFromInput.Select(x => x.Key))),
+ ProcessTarget,
+ () => UpdateConfigs(configsFromInput));
+ }
+
+ private void UpdateConfigs(IEnumerable<(string, object)> configsToUpdate)
+ {
+ foreach ((string key, object value) in configsToUpdate)
+ {
+ ConfigData updated = ConfigManager.UpdateConfig(new UpdateConfigOptions(key, value, Scope)
+ {
+ AppliesTo = AppliesTo
+ });
+ WriteObject(new PSConfig(updated));
+ }
+ }
+ }
+}
diff --git a/src/Accounts/Accounts/help/Az.Accounts.md b/src/Accounts/Accounts/help/Az.Accounts.md
index c05e40b851cb..351244e89071 100644
--- a/src/Accounts/Accounts/help/Az.Accounts.md
+++ b/src/Accounts/Accounts/help/Az.Accounts.md
@@ -14,6 +14,9 @@ Manages credentials and common configuration for all Azure modules.
### [Add-AzEnvironment](Add-AzEnvironment.md)
Adds endpoints and metadata for an instance of Azure Resource Manager.
+### [Clear-AzConfig](Clear-AzConfig.md)
+Clears the values of configs that are set by the user.
+
### [Clear-AzContext](Clear-AzContext.md)
Remove all Azure credentials, account, and subscription information.
@@ -56,6 +59,9 @@ Enables AzureRm prefix aliases for Az modules.
### [Get-AzAccessToken](Get-AzAccessToken.md)
Get raw access token. When using -ResourceUrl, please make sure the value does match current Azure environment. You may refer to the value of `(Get-AzContext).Environment`.
+### [Get-AzConfig](Get-AzConfig.md)
+Gets the configs of Azure PowerShell.
+
### [Get-AzContext](Get-AzContext.md)
Gets the metadata used to authenticate Azure Resource Manager requests.
@@ -120,3 +126,6 @@ Sets properties for an Azure environment.
### [Uninstall-AzureRm](Uninstall-AzureRm.md)
Removes all AzureRm modules from a machine.
+### [Update-AzConfig](Update-AzConfig.md)
+Updates the configs of Azure PowerShell.
+
diff --git a/src/Accounts/Accounts/help/Clear-AzConfig.md b/src/Accounts/Accounts/help/Clear-AzConfig.md
new file mode 100644
index 000000000000..55ea44291f4c
--- /dev/null
+++ b/src/Accounts/Accounts/help/Clear-AzConfig.md
@@ -0,0 +1,251 @@
+---
+external help file: Microsoft.Azure.PowerShell.Cmdlets.Accounts.dll-Help.xml
+Module Name: Az.Accounts
+online version: https://docs.microsoft.com/powershell/module/az.accounts/clear-azconfig
+schema: 2.0.0
+---
+
+# Clear-AzConfig
+
+## SYNOPSIS
+Clears the values of configs that are set by the user.
+
+## SYNTAX
+
+### ClearAll
+```
+Clear-AzConfig [-All] [-Force] [-PassThru] [-AppliesTo ] [-Scope ]
+ [-DefaultProfile ] [-WhatIf] [-Confirm] []
+```
+
+### ClearByKey
+```
+Clear-AzConfig [-PassThru] [-AppliesTo ] [-Scope ]
+ [-DefaultProfile ] [-WhatIf] [-Confirm] [-DefaultSubscriptionForLogin]
+ [-EnableDataCollection] [-EnableInterceptSurvey] [-SuppressWarningMessage] []
+```
+
+## DESCRIPTION
+{{ Fill in the Description }}
+
+## EXAMPLES
+
+### Example 1
+```powershell
+Clear-AzConfig -Todo
+```
+
+```output
+Todo
+```
+
+Todo
+
+## PARAMETERS
+
+### -All
+Clear all configs.
+
+```yaml
+Type: System.Management.Automation.SwitchParameter
+Parameter Sets: ClearAll
+Aliases:
+
+Required: True
+Position: Named
+Default value: None
+Accept pipeline input: False
+Accept wildcard characters: False
+```
+
+### -AppliesTo
+Specifies what part of Azure PowerShell the config applies to.
+Possible values are:
+- "Az": the config applies to all modules and cmdlets of Azure PowerShell.
+- Module name: the config applies to a certain module of Azure PowerShell.
+For example, "Az.Storage".
+- Cmdlet name: the config applies to a certain cmdlet of Azure PowerShell.
+For example, "Get-AzKeyVault".
+If not specified, when getting configs, output will be all of the above; when updating or clearing configs, it defaults to "Az"
+
+```yaml
+Type: System.String
+Parameter Sets: (All)
+Aliases:
+
+Required: False
+Position: Named
+Default value: None
+Accept pipeline input: False
+Accept wildcard characters: False
+```
+
+### -DefaultProfile
+The credentials, account, tenant, and subscription used for communication with Azure.
+
+```yaml
+Type: Microsoft.Azure.Commands.Common.Authentication.Abstractions.Core.IAzureContextContainer
+Parameter Sets: (All)
+Aliases: AzContext, AzureRmContext, AzureCredential
+
+Required: False
+Position: Named
+Default value: None
+Accept pipeline input: False
+Accept wildcard characters: False
+```
+
+### -DefaultSubscriptionForLogin
+Subscription name or GUID.
+If defined, when logging in Azure PowerShell without specifying the subscription, this one will be used to select the default context.
+
+```yaml
+Type: System.Management.Automation.SwitchParameter
+Parameter Sets: ClearByKey
+Aliases:
+
+Required: False
+Position: Named
+Default value: None
+Accept pipeline input: False
+Accept wildcard characters: False
+```
+
+### -EnableDataCollection
+todo
+
+```yaml
+Type: System.Management.Automation.SwitchParameter
+Parameter Sets: ClearByKey
+Aliases:
+
+Required: False
+Position: Named
+Default value: None
+Accept pipeline input: False
+Accept wildcard characters: False
+```
+
+### -EnableInterceptSurvey
+When enabled, a message of taking part in the survey about the user experience of Azure PowerShell will prompt at low frequency.
+
+```yaml
+Type: System.Management.Automation.SwitchParameter
+Parameter Sets: ClearByKey
+Aliases:
+
+Required: False
+Position: Named
+Default value: None
+Accept pipeline input: False
+Accept wildcard characters: False
+```
+
+### -Force
+Do not ask for confirmation when clearing all configs.
+
+```yaml
+Type: System.Management.Automation.SwitchParameter
+Parameter Sets: ClearAll
+Aliases:
+
+Required: False
+Position: Named
+Default value: None
+Accept pipeline input: False
+Accept wildcard characters: False
+```
+
+### -PassThru
+Returns true if cmdlet executes correctly.
+
+```yaml
+Type: System.Management.Automation.SwitchParameter
+Parameter Sets: (All)
+Aliases:
+
+Required: False
+Position: Named
+Default value: None
+Accept pipeline input: False
+Accept wildcard characters: False
+```
+
+### -Scope
+Determines the scope of config changes, for example, whether changes apply only to the current process, or to all sessions started by this user.
+By default it is CurrentUser.
+
+```yaml
+Type: Microsoft.Azure.PowerShell.Common.Config.ConfigScope
+Parameter Sets: (All)
+Aliases:
+Accepted values: CurrentUser, Process, Default
+
+Required: False
+Position: Named
+Default value: None
+Accept pipeline input: False
+Accept wildcard characters: False
+```
+
+### -SuppressWarningMessage
+Controls if the warning messages of upcoming breaking changes are enabled or suppressed.
+The messages are typically displayed when a cmdlet that will have breaking change in the future is executed.
+
+```yaml
+Type: System.Management.Automation.SwitchParameter
+Parameter Sets: ClearByKey
+Aliases:
+
+Required: False
+Position: Named
+Default value: None
+Accept pipeline input: False
+Accept wildcard characters: False
+```
+
+### -Confirm
+Prompts you for confirmation before running the cmdlet.
+
+```yaml
+Type: System.Management.Automation.SwitchParameter
+Parameter Sets: (All)
+Aliases: cf
+
+Required: False
+Position: Named
+Default value: None
+Accept pipeline input: False
+Accept wildcard characters: False
+```
+
+### -WhatIf
+Shows what would happen if the cmdlet runs.
+The cmdlet is not run.
+
+```yaml
+Type: System.Management.Automation.SwitchParameter
+Parameter Sets: (All)
+Aliases: wi
+
+Required: False
+Position: Named
+Default value: None
+Accept pipeline input: False
+Accept wildcard characters: False
+```
+
+### CommonParameters
+This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216).
+
+## INPUTS
+
+### None
+
+## OUTPUTS
+
+### System.Boolean
+
+## NOTES
+
+## RELATED LINKS
diff --git a/src/Accounts/Accounts/help/Get-AzConfig.md b/src/Accounts/Accounts/help/Get-AzConfig.md
new file mode 100644
index 000000000000..f51d073b6ea4
--- /dev/null
+++ b/src/Accounts/Accounts/help/Get-AzConfig.md
@@ -0,0 +1,168 @@
+---
+external help file: Microsoft.Azure.PowerShell.Cmdlets.Accounts.dll-Help.xml
+Module Name: Az.Accounts
+online version: https://docs.microsoft.com/powershell/module/az.accounts/get-azconfig
+schema: 2.0.0
+---
+
+# Get-AzConfig
+
+## SYNOPSIS
+Gets the configs of Azure PowerShell.
+
+## SYNTAX
+
+```
+Get-AzConfig [-AppliesTo ] [-Scope ] [-DefaultProfile ]
+ [-DefaultSubscriptionForLogin] [-EnableDataCollection] [-EnableInterceptSurvey] [-SuppressWarningMessage]
+ []
+```
+
+## DESCRIPTION
+{{ Fill in the Description }}
+
+## EXAMPLES
+
+### Example 1
+```powershell
+Get-AzConfig
+```
+
+```output
+Todo
+```
+
+Todo
+
+## PARAMETERS
+
+### -AppliesTo
+Specifies what part of Azure PowerShell the config applies to.
+Possible values are:
+- "Az": the config applies to all modules and cmdlets of Azure PowerShell.
+- Module name: the config applies to a certain module of Azure PowerShell.
+For example, "Az.Storage".
+- Cmdlet name: the config applies to a certain cmdlet of Azure PowerShell.
+For example, "Get-AzKeyVault".
+If not specified, when getting configs, output will be all of the above; when updating or clearing configs, it defaults to "Az"
+
+```yaml
+Type: System.String
+Parameter Sets: (All)
+Aliases:
+
+Required: False
+Position: Named
+Default value: None
+Accept pipeline input: False
+Accept wildcard characters: False
+```
+
+### -DefaultProfile
+The credentials, account, tenant, and subscription used for communication with Azure.
+
+```yaml
+Type: Microsoft.Azure.Commands.Common.Authentication.Abstractions.Core.IAzureContextContainer
+Parameter Sets: (All)
+Aliases: AzContext, AzureRmContext, AzureCredential
+
+Required: False
+Position: Named
+Default value: None
+Accept pipeline input: False
+Accept wildcard characters: False
+```
+
+### -DefaultSubscriptionForLogin
+Subscription name or GUID.
+If defined, when logging in Azure PowerShell without specifying the subscription, this one will be used to select the default context.
+
+```yaml
+Type: System.Management.Automation.SwitchParameter
+Parameter Sets: (All)
+Aliases:
+
+Required: False
+Position: Named
+Default value: None
+Accept pipeline input: False
+Accept wildcard characters: False
+```
+
+### -EnableDataCollection
+todo
+
+```yaml
+Type: System.Management.Automation.SwitchParameter
+Parameter Sets: (All)
+Aliases:
+
+Required: False
+Position: Named
+Default value: None
+Accept pipeline input: False
+Accept wildcard characters: False
+```
+
+### -EnableInterceptSurvey
+When enabled, a message of taking part in the survey about the user experience of Azure PowerShell will prompt at low frequency.
+
+```yaml
+Type: System.Management.Automation.SwitchParameter
+Parameter Sets: (All)
+Aliases:
+
+Required: False
+Position: Named
+Default value: None
+Accept pipeline input: False
+Accept wildcard characters: False
+```
+
+### -Scope
+Determines the scope of config changes, for example, whether changes apply only to the current process, or to all sessions started by this user.
+By default it is CurrentUser.
+
+```yaml
+Type: Microsoft.Azure.PowerShell.Common.Config.ConfigScope
+Parameter Sets: (All)
+Aliases:
+Accepted values: CurrentUser, Process, Default
+
+Required: False
+Position: Named
+Default value: None
+Accept pipeline input: False
+Accept wildcard characters: False
+```
+
+### -SuppressWarningMessage
+Controls if the warning messages of upcoming breaking changes are enabled or suppressed.
+The messages are typically displayed when a cmdlet that will have breaking change in the future is executed.
+
+```yaml
+Type: System.Management.Automation.SwitchParameter
+Parameter Sets: (All)
+Aliases:
+
+Required: False
+Position: Named
+Default value: None
+Accept pipeline input: False
+Accept wildcard characters: False
+```
+
+### CommonParameters
+This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216).
+
+## INPUTS
+
+### None
+
+## OUTPUTS
+
+### Microsoft.Azure.Commands.Common.Authentication.Config.PSConfig
+
+## NOTES
+
+## RELATED LINKS
diff --git a/src/Accounts/Accounts/help/Update-AzConfig.md b/src/Accounts/Accounts/help/Update-AzConfig.md
new file mode 100644
index 000000000000..a67982f7051f
--- /dev/null
+++ b/src/Accounts/Accounts/help/Update-AzConfig.md
@@ -0,0 +1,199 @@
+---
+external help file: Microsoft.Azure.PowerShell.Cmdlets.Accounts.dll-Help.xml
+Module Name: Az.Accounts
+online version: https://docs.microsoft.com/powershell/module/az.accounts/update-azconfig
+schema: 2.0.0
+---
+
+# Update-AzConfig
+
+## SYNOPSIS
+Updates the configs of Azure PowerShell.
+
+## SYNTAX
+
+```
+Update-AzConfig [-AppliesTo ] [-Scope ] [-DefaultProfile ]
+ [-WhatIf] [-Confirm] [-DefaultSubscriptionForLogin ] [-EnableDataCollection ]
+ [-EnableInterceptSurvey ] [-SuppressWarningMessage ] []
+```
+
+## DESCRIPTION
+{{ Fill in the Description }}
+
+## EXAMPLES
+
+### Example 1
+```powershell
+Update-AzConfig -Todo $true
+```
+
+```output
+Todo
+```
+
+Todo
+
+## PARAMETERS
+
+### -AppliesTo
+Specifies what part of Azure PowerShell the config applies to.
+Possible values are:
+- "Az": the config applies to all modules and cmdlets of Azure PowerShell.
+- Module name: the config applies to a certain module of Azure PowerShell.
+For example, "Az.Storage".
+- Cmdlet name: the config applies to a certain cmdlet of Azure PowerShell.
+For example, "Get-AzKeyVault".
+If not specified, when getting configs, output will be all of the above; when updating or clearing configs, it defaults to "Az"
+
+```yaml
+Type: System.String
+Parameter Sets: (All)
+Aliases:
+
+Required: False
+Position: Named
+Default value: None
+Accept pipeline input: False
+Accept wildcard characters: False
+```
+
+### -DefaultProfile
+The credentials, account, tenant, and subscription used for communication with Azure.
+
+```yaml
+Type: Microsoft.Azure.Commands.Common.Authentication.Abstractions.Core.IAzureContextContainer
+Parameter Sets: (All)
+Aliases: AzContext, AzureRmContext, AzureCredential
+
+Required: False
+Position: Named
+Default value: None
+Accept pipeline input: False
+Accept wildcard characters: False
+```
+
+### -DefaultSubscriptionForLogin
+Subscription name or GUID.
+If defined, when logging in Azure PowerShell without specifying the subscription, this one will be used to select the default context.
+
+```yaml
+Type: System.String
+Parameter Sets: (All)
+Aliases:
+
+Required: False
+Position: Named
+Default value: None
+Accept pipeline input: True (ByPropertyName)
+Accept wildcard characters: False
+```
+
+### -EnableDataCollection
+todo
+
+```yaml
+Type: System.Boolean
+Parameter Sets: (All)
+Aliases:
+
+Required: False
+Position: Named
+Default value: None
+Accept pipeline input: True (ByPropertyName)
+Accept wildcard characters: False
+```
+
+### -EnableInterceptSurvey
+When enabled, a message of taking part in the survey about the user experience of Azure PowerShell will prompt at low frequency.
+
+```yaml
+Type: System.Boolean
+Parameter Sets: (All)
+Aliases:
+
+Required: False
+Position: Named
+Default value: None
+Accept pipeline input: True (ByPropertyName)
+Accept wildcard characters: False
+```
+
+### -Scope
+Determines the scope of config changes, for example, whether changes apply only to the current process, or to all sessions started by this user.
+By default it is CurrentUser.
+
+```yaml
+Type: Microsoft.Azure.PowerShell.Common.Config.ConfigScope
+Parameter Sets: (All)
+Aliases:
+Accepted values: CurrentUser, Process, Default
+
+Required: False
+Position: Named
+Default value: None
+Accept pipeline input: False
+Accept wildcard characters: False
+```
+
+### -SuppressWarningMessage
+Controls if the warning messages of upcoming breaking changes are enabled or suppressed.
+The messages are typically displayed when a cmdlet that will have breaking change in the future is executed.
+
+```yaml
+Type: System.Boolean
+Parameter Sets: (All)
+Aliases:
+
+Required: False
+Position: Named
+Default value: None
+Accept pipeline input: True (ByPropertyName)
+Accept wildcard characters: False
+```
+
+### -Confirm
+Prompts you for confirmation before running the cmdlet.
+
+```yaml
+Type: System.Management.Automation.SwitchParameter
+Parameter Sets: (All)
+Aliases: cf
+
+Required: False
+Position: Named
+Default value: None
+Accept pipeline input: False
+Accept wildcard characters: False
+```
+
+### -WhatIf
+Shows what would happen if the cmdlet runs.
+The cmdlet is not run.
+
+```yaml
+Type: System.Management.Automation.SwitchParameter
+Parameter Sets: (All)
+Aliases: wi
+
+Required: False
+Position: Named
+Default value: None
+Accept pipeline input: False
+Accept wildcard characters: False
+```
+
+### CommonParameters
+This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216).
+
+## INPUTS
+
+### None
+
+## OUTPUTS
+
+### Microsoft.Azure.Commands.Common.Authentication.Config.PSConfig
+
+## NOTES
+
+## RELATED LINKS
From c1e967f8c7c465c6215031faea471824c1bd8ec3 Mon Sep 17 00:00:00 2001
From: Yeming Liu <11371776+isra-fel@users.noreply.github.com>
Date: Thu, 24 Mar 2022 16:41:24 +0800
Subject: [PATCH 4/9] test related
---
.../Accounts.Test/Mocks/MockDataStore.cs | 3 +-
src/Accounts/Accounts/Directory.Build.targets | 5 +-
.../ConfigTests/ClearConfigTests.cs | 214 +++++++++++
.../ConfigTests/ConfigDefinitionTests.cs | 60 ++++
.../ConfigTests/ConfigTestsBase.cs | 75 ++++
.../ConfigTests/GetConfigTests.cs | 339 ++++++++++++++++++
.../ConfigTests/PriorityTests.cs | 85 +++++
.../ConfigTests/RegisterConfigTests.cs | 106 ++++++
.../ConfigTests/UpdateConfigTests.cs | 186 ++++++++++
.../Mocks/MockDataStore.cs | 3 +-
.../Mocks/MockEnvironmentVariableProvider.cs | 53 +++
.../Authentication/Properties/AssemblyInfo.cs | 3 +
12 files changed, 1128 insertions(+), 4 deletions(-)
create mode 100644 src/Accounts/Authentication.Test/ConfigTests/ClearConfigTests.cs
create mode 100644 src/Accounts/Authentication.Test/ConfigTests/ConfigDefinitionTests.cs
create mode 100644 src/Accounts/Authentication.Test/ConfigTests/ConfigTestsBase.cs
create mode 100644 src/Accounts/Authentication.Test/ConfigTests/GetConfigTests.cs
create mode 100644 src/Accounts/Authentication.Test/ConfigTests/PriorityTests.cs
create mode 100644 src/Accounts/Authentication.Test/ConfigTests/RegisterConfigTests.cs
create mode 100644 src/Accounts/Authentication.Test/ConfigTests/UpdateConfigTests.cs
create mode 100644 src/Accounts/Authentication.Test/Mocks/MockEnvironmentVariableProvider.cs
diff --git a/src/Accounts/Accounts.Test/Mocks/MockDataStore.cs b/src/Accounts/Accounts.Test/Mocks/MockDataStore.cs
index 5f03997356c5..cfed5e22eb64 100644
--- a/src/Accounts/Accounts.Test/Mocks/MockDataStore.cs
+++ b/src/Accounts/Accounts.Test/Mocks/MockDataStore.cs
@@ -382,7 +382,8 @@ public Stream OpenForExclusiveWrite(string path)
() =>
{
writeLocks[path] = false;
- virtualStore[path] = Encoding.UTF8.GetString(buffer);
+ // trim \0 otherwise json fails to parse
+ virtualStore[path] = Encoding.UTF8.GetString(buffer).TrimEnd('\0');
}
);
}
diff --git a/src/Accounts/Accounts/Directory.Build.targets b/src/Accounts/Accounts/Directory.Build.targets
index fa3748d4339a..278dd06f9a08 100644
--- a/src/Accounts/Accounts/Directory.Build.targets
+++ b/src/Accounts/Accounts/Directory.Build.targets
@@ -1,9 +1,10 @@
-
+
+
-
+
diff --git a/src/Accounts/Authentication.Test/ConfigTests/ClearConfigTests.cs b/src/Accounts/Authentication.Test/ConfigTests/ClearConfigTests.cs
new file mode 100644
index 000000000000..b9fbba072cf6
--- /dev/null
+++ b/src/Accounts/Authentication.Test/ConfigTests/ClearConfigTests.cs
@@ -0,0 +1,214 @@
+// ----------------------------------------------------------------------------------
+//
+// Copyright Microsoft Corporation
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ----------------------------------------------------------------------------------
+
+using Microsoft.Azure.Commands.Common.Exceptions;
+using Microsoft.Azure.Commands.Common.Authentication.Config;
+using Microsoft.Azure.PowerShell.Common.Config;
+using Microsoft.Rest.ClientRuntime.Azure.TestFramework;
+using Microsoft.VisualStudio.TestPlatform.ObjectModel;
+using Xunit;
+
+namespace Microsoft.Azure.Authentication.Test.Config
+{
+ public class ClearConfigTests : ConfigTestsBase
+ {
+ [Fact]
+ [Trait(TestTraits.AcceptanceType, TestTraits.CheckIn)]
+ public void CanClearSingleConfig()
+ {
+ string key = "FalseByDefault";
+ IConfigManager icm = GetConfigManager(new SimpleTypedConfig(key, "{help message}", false));
+ Assert.False(icm.GetConfigValue(key));
+
+ icm.UpdateConfig(new UpdateConfigOptions(key, true, ConfigScope.Process));
+ Assert.True(icm.GetConfigValue(key));
+
+ icm.ClearConfig(new ClearConfigOptions(key, ConfigScope.Process));
+ Assert.False(icm.GetConfigValue(key));
+ }
+
+ [Fact]
+ [Trait(TestTraits.AcceptanceType, TestTraits.CheckIn)]
+ public void CannotClearUnknownConfig()
+ {
+ IConfigManager configurationManager = GetConfigManager();
+
+ Assert.Throws(() =>
+ {
+ configurationManager.ClearConfig(new ClearConfigOptions("NeverRegistered", ConfigScope.CurrentUser));
+ });
+ }
+
+ [Fact]
+ [Trait(TestTraits.AcceptanceType, TestTraits.CheckIn)]
+ public void ShouldNotThrowToClearConfigNeverSet()
+ {
+ string key1 = "key1";
+ var config1 = new SimpleTypedConfig(key1, "{help message}", false);
+ string key2 = "key2";
+ var config2 = new SimpleTypedConfig(key2, "{help message}", false);
+ IConfigManager icm = GetConfigManager(config1, config2);
+
+ icm.ClearConfig(key1, ConfigScope.CurrentUser);
+ icm.ClearConfig(key2, ConfigScope.Process);
+ icm.ClearConfig(null, ConfigScope.CurrentUser);
+ icm.ClearConfig(null, ConfigScope.Process);
+ icm.ClearConfig(new ClearConfigOptions(null, ConfigScope.CurrentUser)
+ {
+ AppliesTo = null
+ });
+ icm.ClearConfig(new ClearConfigOptions(null, ConfigScope.Process)
+ {
+ AppliesTo = null
+ });
+ icm.ClearConfig(new ClearConfigOptions(null, ConfigScope.CurrentUser)
+ {
+ AppliesTo = "Az.Accounts"
+ });
+ icm.ClearConfig(new ClearConfigOptions(null, ConfigScope.Process)
+ {
+ AppliesTo = "Az.Accounts"
+ });
+ }
+
+ [Fact]
+ [Trait(TestTraits.AcceptanceType, TestTraits.CheckIn)]
+ public void CanClearSingleConfigInJson()
+ {
+ IConfigManager icm = GetConfigManager();
+ string key = "DisableSomething";
+ icm.RegisterConfig(new SimpleTypedConfig(key, "{help message}", false));
+ icm.BuildConfig();
+
+ Assert.False(icm.GetConfigValue(key));
+
+ icm.UpdateConfig(new UpdateConfigOptions(key, true, ConfigScope.CurrentUser));
+ Assert.True(icm.GetConfigValue(key));
+
+ icm.ClearConfig(new ClearConfigOptions(key, ConfigScope.CurrentUser));
+ Assert.False(icm.GetConfigValue(key));
+ }
+
+ [Fact]
+ [Trait(TestTraits.AcceptanceType, TestTraits.CheckIn)]
+ public void CanClearAllConfigsInJson()
+ {
+ string key1 = "key1";
+ var config1 = new SimpleTypedConfig(key1, "{help message}", false);
+ string key2 = "key2";
+ var config2 = new SimpleTypedConfig(key2, "{help message}", false);
+ ConfigManager cm = GetConfigManager(config1, config2) as ConfigManager;
+
+ Assert.False(cm.GetConfigValue(key1));
+ Assert.False(cm.GetConfigValue(key2));
+
+ // Scenario 1: update the configs, applying to Az
+ cm.UpdateConfig(new UpdateConfigOptions(key1, true, ConfigScope.CurrentUser));
+ Assert.True(cm.GetConfigValue(key1));
+ cm.UpdateConfig(new UpdateConfigOptions(key2, true, ConfigScope.CurrentUser));
+ Assert.True(cm.GetConfigValue(key2));
+
+ // clear all configs by specifying `null` as the key, applying to Az
+ cm.ClearConfig(null, ConfigScope.CurrentUser);
+ Assert.False(cm.GetConfigValue(key1));
+ Assert.False(cm.GetConfigValue(key2));
+
+ // Scenario 2: update the configs, applying to Az.Accounts
+ cm.UpdateConfig(new UpdateConfigOptions(key1, true, ConfigScope.CurrentUser) { AppliesTo = "Az.Accounts" });
+ Assert.True(cm.GetConfigValueInternal(key1, new InternalInvocationInfo() { ModuleName = "Az.Accounts" }));
+ cm.UpdateConfig(new UpdateConfigOptions(key2, true, ConfigScope.CurrentUser) { AppliesTo = "Az.Accounts" });
+ Assert.True(cm.GetConfigValueInternal(key2, new InternalInvocationInfo() { ModuleName = "Az.Accounts" }));
+
+ // clear all configs, applying to Az.Accounts
+ cm.ClearConfig(new ClearConfigOptions(null, ConfigScope.CurrentUser) { AppliesTo = "Az.Accounts" });
+ Assert.False(cm.GetConfigValueInternal(key1, new InternalInvocationInfo() { ModuleName = "Az.Accounts" }));
+ Assert.False(cm.GetConfigValueInternal(key2, new InternalInvocationInfo() { ModuleName = "Az.Accounts" }));
+
+ // Scenario 3: update the configs, applying differently
+ cm.UpdateConfig(new UpdateConfigOptions(key1, true, ConfigScope.CurrentUser) { AppliesTo = "Az.Accounts" });
+ Assert.True(cm.GetConfigValueInternal(key1, new InternalInvocationInfo() { ModuleName = "Az.Accounts" }));
+ cm.UpdateConfig(new UpdateConfigOptions(key2, true, ConfigScope.CurrentUser) { AppliesTo = "Az.KeyVault" });
+ Assert.True(cm.GetConfigValueInternal(key2, new InternalInvocationInfo() { ModuleName = "Az.KeyVault" }));
+
+ // clear all configs, applying anything
+ cm.ClearConfig(null, ConfigScope.CurrentUser);
+ Assert.False(cm.GetConfigValueInternal(key1, new InternalInvocationInfo() { ModuleName = "Az.Accounts" }));
+ Assert.False(cm.GetConfigValueInternal(key2, new InternalInvocationInfo() { ModuleName = "Az.KeyVault" }));
+ }
+
+ [Fact]
+ [Trait(TestTraits.AcceptanceType, TestTraits.CheckIn)]
+ public void ShouldNotThrowWhenClearConfigNeverSet()
+ {
+ string key = "DisableSomething";
+ var config = new SimpleTypedConfig(key, "{help message}", false);
+ IConfigManager icm = GetConfigManager(config);
+
+ icm.ClearConfig(key, ConfigScope.CurrentUser);
+ }
+
+ [Fact]
+ [Trait(TestTraits.AcceptanceType, TestTraits.CheckIn)]
+ public void CanClearByScope()
+ {
+ const string boolKey = "BoolKey";
+ var boolConfig = new SimpleTypedConfig(boolKey, "", false);
+ const string intKey = "intKey";
+ var intConfig = new SimpleTypedConfig(intKey, "", 0);
+ var icm = GetConfigManager(boolConfig, intConfig);
+
+ icm.UpdateConfig(new UpdateConfigOptions(boolKey, true, ConfigScope.CurrentUser));
+ icm.UpdateConfig(new UpdateConfigOptions(intKey, 10, ConfigScope.CurrentUser));
+ icm.UpdateConfig(new UpdateConfigOptions(boolKey, true, ConfigScope.Process));
+ icm.UpdateConfig(new UpdateConfigOptions(intKey, 10, ConfigScope.Process));
+
+ icm.ClearConfig(new ClearConfigOptions(boolKey, ConfigScope.Process));
+ icm.ClearConfig(new ClearConfigOptions(intKey, ConfigScope.Process));
+
+ foreach (var configData in icm.ListConfigs())
+ {
+ Assert.NotEqual(ConfigScope.Process, configData.Scope);
+ }
+
+ icm.ClearConfig(boolKey, ConfigScope.CurrentUser);
+ icm.ClearConfig(intKey, ConfigScope.CurrentUser);
+
+ foreach (var configData in icm.ListConfigs())
+ {
+ Assert.NotEqual(ConfigScope.CurrentUser, configData.Scope);
+ }
+ }
+
+ [Fact]
+ [Trait(TestTraits.AcceptanceType, TestTraits.CheckIn)]
+ public void AppliesToShouldDefaultToAz()
+ {
+ const string boolKey = "BoolKey";
+ var boolConfig = new SimpleTypedConfig(boolKey, "", false);
+ var icm = GetConfigManager(boolConfig);
+
+ const string appliesTo = "Az.A";
+ icm.UpdateConfig(new UpdateConfigOptions(boolKey, true, ConfigScope.CurrentUser)
+ {
+ AppliesTo = appliesTo
+ });
+
+ icm.ClearConfig(boolKey, ConfigScope.CurrentUser);
+ Assert.Single(icm.ListConfigs(new ConfigFilter() { Keys = new string[] { boolKey }, AppliesTo = appliesTo }));
+
+ icm.ClearConfig(new ClearConfigOptions(boolKey, ConfigScope.CurrentUser) { AppliesTo = appliesTo });
+ Assert.Empty(icm.ListConfigs(new ConfigFilter() { Keys = new string[] { boolKey }, AppliesTo = appliesTo }));
+ }
+ }
+}
diff --git a/src/Accounts/Authentication.Test/ConfigTests/ConfigDefinitionTests.cs b/src/Accounts/Authentication.Test/ConfigTests/ConfigDefinitionTests.cs
new file mode 100644
index 000000000000..b451c1bea556
--- /dev/null
+++ b/src/Accounts/Authentication.Test/ConfigTests/ConfigDefinitionTests.cs
@@ -0,0 +1,60 @@
+// ----------------------------------------------------------------------------------
+//
+// Copyright Microsoft Corporation
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ----------------------------------------------------------------------------------
+
+using Microsoft.Azure.Commands.Common.Exceptions;
+using Microsoft.Azure.Commands.Common.Authentication.Config;
+using Microsoft.Rest.ClientRuntime.Azure.TestFramework;
+using Microsoft.WindowsAzure.Commands.Common;
+using System;
+using System.Linq;
+using Xunit;
+using Microsoft.Azure.PowerShell.Common.Config;
+
+namespace Microsoft.Azure.Authentication.Test.Config
+{
+ public class ConfigDefinitionTests : ConfigTestsBase
+ {
+ [Fact]
+ [Trait(TestTraits.AcceptanceType, TestTraits.CheckIn)]
+ public void CanValidateInput() {
+ const string boolKey = "BoolKey";
+ var boolConfig = new SimpleTypedConfig(boolKey, "", false);
+ var rangedIntConfig = new RangedConfig();
+ var icm = GetConfigManagerWithInitState(null, null, boolConfig, rangedIntConfig);
+
+ Assert.Throws(() => { icm.UpdateConfig(boolKey, 0, ConfigScope.CurrentUser); });
+ Assert.Throws(() => { icm.UpdateConfig(rangedIntConfig.Key, true, ConfigScope.CurrentUser); });
+ Assert.Throws(() => { icm.UpdateConfig(rangedIntConfig.Key, -1, ConfigScope.CurrentUser); });
+ }
+
+ private class RangedConfig : TypedConfig
+ {
+ public override object DefaultValue => 0;
+
+ public override string Key => "RangedKey";
+
+ public override string HelpMessage => "";
+
+ public override void Validate(object value)
+ {
+ base.Validate(value);
+ int valueAsInt = (int)value;
+ if (valueAsInt < 0 || valueAsInt > 100)
+ {
+ throw new ArgumentOutOfRangeException($"The value of config {Key} must be in between 0 and 100.");
+ }
+ }
+ }
+ }
+}
diff --git a/src/Accounts/Authentication.Test/ConfigTests/ConfigTestsBase.cs b/src/Accounts/Authentication.Test/ConfigTests/ConfigTestsBase.cs
new file mode 100644
index 000000000000..771641cee939
--- /dev/null
+++ b/src/Accounts/Authentication.Test/ConfigTests/ConfigTestsBase.cs
@@ -0,0 +1,75 @@
+// ----------------------------------------------------------------------------------
+//
+// Copyright Microsoft Corporation
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ----------------------------------------------------------------------------------
+
+using Microsoft.Azure.Commands.Common.Authentication.Config;
+using Microsoft.Azure.PowerShell.Authentication.Test.Mocks;
+using Microsoft.Azure.PowerShell.Common.Config;
+using Microsoft.WindowsAzure.Commands.Common.Test.Mocks;
+using System;
+using System.Collections.Generic;
+using System.IO;
+
+namespace Microsoft.Azure.Authentication.Test.Config
+{
+ public class ConfigTestsBase
+ {
+ private readonly Action _noopFileWriter = (x, y) => { };
+ private readonly Action _noopEnvVarWriter = (x) => { };
+
+ ///
+ /// Initializes and returns an with the specified configs registered.
+ ///
+ /// Definitions of configs to be registered to the config manager.
+ /// A config manager ready to use.
+ protected IConfigManager GetConfigManager(params ConfigDefinition[] config) => GetConfigManagerWithInitState(null, null, config);
+
+ ///
+ /// Initializes and returns an with the specified configs registered with initial state.
+ ///
+ /// An action to set up the config file before config manager initializes.
+ /// An action to set up the environments before config manager initializes.
+ /// Definitions of configs to be registered to the config manager.
+ /// A config manager with initial state, ready to use.
+ protected IConfigManager GetConfigManagerWithInitState(Action configFileWriter, Action envVarWriter, params ConfigDefinition[] config)
+ {
+ if (configFileWriter == null)
+ {
+ configFileWriter = _noopFileWriter;
+ }
+
+ if (envVarWriter == null)
+ {
+ envVarWriter = _noopEnvVarWriter;
+ }
+
+ string configPath = Path.GetRandomFileName();
+ var mockDataStore = new MockDataStore();
+ configFileWriter(mockDataStore, configPath);
+ var environmentVariables = new MockEnvironmentVariableProvider();
+ envVarWriter(environmentVariables);
+ ConfigInitializer ci = new ConfigInitializer(new List() { configPath })
+ {
+ DataStore = mockDataStore,
+ EnvironmentVariableProvider = environmentVariables
+ };
+ IConfigManager icm = ci.GetConfigManager();
+ foreach (var configDefinition in config)
+ {
+ icm.RegisterConfig(configDefinition);
+ }
+ icm.BuildConfig();
+ return icm;
+ }
+ }
+}
diff --git a/src/Accounts/Authentication.Test/ConfigTests/GetConfigTests.cs b/src/Accounts/Authentication.Test/ConfigTests/GetConfigTests.cs
new file mode 100644
index 000000000000..a1366a550dac
--- /dev/null
+++ b/src/Accounts/Authentication.Test/ConfigTests/GetConfigTests.cs
@@ -0,0 +1,339 @@
+// ----------------------------------------------------------------------------------
+//
+// Copyright Microsoft Corporation
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ----------------------------------------------------------------------------------
+
+using Microsoft.Azure.Commands.Common.Exceptions;
+using Microsoft.Azure.Commands.Common.Authentication.Config;
+using Microsoft.Azure.PowerShell.Common.Config;
+using Microsoft.Rest.ClientRuntime.Azure.TestFramework;
+using System.Linq;
+using Xunit;
+
+namespace Microsoft.Azure.Authentication.Test.Config
+{
+ public class GetConfigTests : ConfigTestsBase
+ {
+ [Fact]
+ [Trait(TestTraits.AcceptanceType, TestTraits.CheckIn)]
+ public void CanGetAppliesTo()
+ {
+ const string key = "EnableTelemetry";
+ var def = new SimpleTypedConfig(
+ key,
+ "Enable telemetry",
+ true);
+ IConfigManager icm = GetConfigManager(def);
+
+ var config = icm.ListConfigs().Single();
+ Assert.NotNull(config);
+ Assert.Equal(key, config.Definition.Key);
+
+ icm.UpdateConfig(new UpdateConfigOptions(key, false, ConfigScope.CurrentUser));
+ config = icm.ListConfigs().Single();
+ Assert.Equal(ConfigFilter.GlobalAppliesTo, config.AppliesTo);
+
+ icm.UpdateConfig(new UpdateConfigOptions(key, false, ConfigScope.CurrentUser) { AppliesTo = "Az.KeyVault" });
+ config = icm.ListConfigs(new ConfigFilter() { AppliesTo = "Az.KeyVault" }).Single();
+ Assert.Equal("Az.KeyVault", config.AppliesTo);
+
+ icm.UpdateConfig(new UpdateConfigOptions(key, false, ConfigScope.CurrentUser) { AppliesTo = "Get-AzKeyVault" });
+ config = icm.ListConfigs(new ConfigFilter { AppliesTo = "Get-AzKeyVault" }).Single();
+ Assert.Equal("Get-AzKeyVault", config.AppliesTo);
+
+ }
+
+ [Fact]
+ [Trait(TestTraits.AcceptanceType, TestTraits.CheckIn)]
+ public void ShouldReturnEmptyWhenFilterIsWrong()
+ {
+ const string key = "EnableTelemetry";
+ var config = new SimpleTypedConfig(
+ key,
+ "Enable telemetry",
+ true);
+ var icm = GetConfigManager(config);
+ Assert.NotEmpty(icm.ListConfigs());
+ Assert.NotEmpty(icm.ListConfigs(null));
+ Assert.Empty(icm.ListConfigs(new ConfigFilter() { Keys = new string[] { "Never Exist" } }));
+ Assert.Empty(icm.ListConfigs(new ConfigFilter() { AppliesTo = "xxx" }));
+
+ }
+
+ [Fact]
+ [Trait(TestTraits.AcceptanceType, TestTraits.CheckIn)]
+ public void CanGetAndListRegisteredConfigs()
+ {
+ const string key1 = "EnableTelemetry";
+ var config1 = new SimpleTypedConfig(
+ key1,
+ "Enable telemetry",
+ true);
+ TestConfig config2 = new TestConfig();
+ IConfigManager configurationManager = GetConfigManager(config1, config2);
+
+ var listResult = configurationManager.ListConfigs();
+ Assert.Equal(2, listResult.Count());
+
+ ConfigData configData = listResult.Where(x => x.Definition.Key == key1).Single();
+ Assert.Equal(true, configData.Value);
+ Assert.True(configurationManager.GetConfigValue(key1));
+
+ ConfigData tempConfigResult = listResult.Where(x => x.Definition.Key == config2.Key).Single();
+ Assert.Equal(config2.DefaultValue, tempConfigResult.Value);
+ Assert.Equal(config2.HelpMessage, tempConfigResult.Definition.HelpMessage);
+ Assert.Equal(config2.DefaultValue, configurationManager.GetConfigValue(config2.Key));
+ }
+
+ [Fact]
+ [Trait(TestTraits.AcceptanceType, TestTraits.CheckIn)]
+ public void CanUpdateAndList()
+ {
+ IConfigManager configurationManager = GetConfigManager();
+ const string key = "EnableTelemetry";
+ configurationManager.RegisterConfig(
+ new SimpleTypedConfig(
+ key,
+ "Enable telemetry",
+ true));
+ configurationManager.BuildConfig();
+ var updatedConfig = configurationManager.UpdateConfig(new UpdateConfigOptions(key, false, ConfigScope.Process));
+ Assert.Equal(key, updatedConfig.Definition.Key);
+ Assert.False((bool)updatedConfig.Value);
+ Assert.False(configurationManager.GetConfigValue(key));
+ }
+
+ [Fact]
+ [Trait(TestTraits.AcceptanceType, TestTraits.CheckIn)]
+ public void CanGetFromEnvironmentVar()
+ {
+ const string key = "FromEnv";
+ const string envKey = "ENV_VAR_FOR_CONFIG";
+ var config = new SimpleTypedConfig(key, "", -1, envKey);
+ const int value = 20;
+
+ var configurationManager = GetConfigManagerWithInitState(null, (envVar) => { envVar.Set(envKey, value.ToString()); }, config);
+
+ Assert.Equal(value, configurationManager.GetConfigValue(key));
+ }
+
+ [Fact]
+ [Trait(TestTraits.AcceptanceType, TestTraits.CheckIn)]
+ public void ShouldNotThrowWhenEnvVarIsWrong()
+ {
+ const string key = "FromEnv";
+ const string envKey = "ENV_VAR_FOR_CONFIG";
+ const int defaultValue = -1;
+ var config = new SimpleTypedConfig(key, "", defaultValue, envKey);
+ const bool valueWithWrongType = true;
+ var configurationManager = GetConfigManagerWithInitState(null, envVar =>
+ {
+ envVar.Set(envKey, valueWithWrongType.ToString());
+ }, config);
+
+ Assert.Equal(defaultValue, configurationManager.GetConfigValue(key));
+ }
+
+ private class TestConfig : TypedConfig
+ {
+ public override object DefaultValue => -1;
+
+ public override string Key => "TempConfig";
+
+ public override string HelpMessage => "temp config";
+ }
+
+ [Fact]
+ [Trait(TestTraits.AcceptanceType, TestTraits.CheckIn)]
+ public void CanGetFromJson()
+ {
+ var config1 = new SimpleTypedConfig("Retry", "", -1);
+ var config2 = new SimpleTypedConfig("Array", "", null);
+ IConfigManager icm = GetConfigManagerWithInitState((dataStore, path) =>
+ {
+ dataStore.WriteFile(path,
+@"{
+ ""Az"": {
+ ""Retry"": 100
+ },
+ ""Az.KeyVault"": {
+ ""Array"": [""a"",""b""]
+ },
+ ""Get-AzKeyVault"": {
+ ""Array"": [""k"",""v""]
+ }
+}");
+ }, null, config1, config2);
+ ConfigManager cm = icm as ConfigManager;
+ Assert.Equal(100, cm.GetConfigValue("Retry"));
+ Assert.Equal(new string[] { "a", "b" }, cm.GetConfigValueInternal("Array", new InternalInvocationInfo() { ModuleName = "Az.KeyVault" }));
+ Assert.Equal(new string[] { "k", "v" }, cm.GetConfigValueInternal("Array", new InternalInvocationInfo() { ModuleName = "Az.KeyVault", CmdletName = "Get-AzKeyVault" }));
+ }
+
+ [Fact]
+ [Trait(TestTraits.AcceptanceType, TestTraits.CheckIn)]
+ public void ThrowWhenGetUnknownConfig()
+ {
+ IConfigManager entry = GetConfigManager();
+ entry.BuildConfig();
+
+ Assert.Throws(() => { entry.GetConfigValue(null); });
+ Assert.Throws(() => { entry.GetConfigValue(""); });
+
+ const string key = "KeyThatIsNotRegistered";
+ Assert.Throws(() => { entry.GetConfigValue(key); });
+ }
+
+ [Fact]
+ [Trait(TestTraits.AcceptanceType, TestTraits.CheckIn)]
+ public void CanFilterByKeyAndAppliesTo()
+ {
+ const string key = "key";
+ var config = new SimpleTypedConfig(key, "", true);
+ var icm = GetConfigManager(config);
+ const string module = "Az.KeyVault";
+ icm.UpdateConfig(new UpdateConfigOptions(key, false, ConfigScope.CurrentUser) { AppliesTo = module });
+ Assert.Single(icm.ListConfigs(new ConfigFilter() { Keys = new[] { key }, AppliesTo = module }));
+ }
+
+ [Fact]
+ [Trait(TestTraits.AcceptanceType, TestTraits.CheckIn)]
+ public void CanFilterByKey()
+ {
+ const string key = "key";
+ var config = new SimpleTypedConfig(key, "", true);
+ var icm = GetConfigManager(config);
+ const string module = "Az.KeyVault";
+ icm.UpdateConfig(new UpdateConfigOptions(key, false, ConfigScope.CurrentUser) { AppliesTo = module });
+ var listResults = icm.ListConfigs(new ConfigFilter() { Keys = new[] { key } });
+ Assert.Equal(2, listResults.Count());
+ }
+
+ [Fact]
+ [Trait(TestTraits.AcceptanceType, TestTraits.CheckIn)]
+ public void CanFilterByAppliesTo()
+ {
+ const string key1 = "key";
+ var config1 = new SimpleTypedConfig(key1, "", true);
+ const string key2 = "key2";
+ var config2 = new SimpleTypedConfig(key2, "", true);
+ var icm = GetConfigManager(config1, config2);
+
+ const string module = "Az.KeyVault";
+ icm.UpdateConfig(new UpdateConfigOptions(key1, false, ConfigScope.CurrentUser) { AppliesTo = module });
+ icm.UpdateConfig(new UpdateConfigOptions(key2, false, ConfigScope.CurrentUser) { AppliesTo = module });
+
+ var listResults = icm.ListConfigs(new ConfigFilter() { AppliesTo = module });
+ Assert.Equal(2, listResults.Count());
+
+ listResults = icm.ListConfigs(new ConfigFilter() { AppliesTo = ConfigFilter.GlobalAppliesTo });
+ Assert.Equal(2, listResults.Count());
+ }
+
+ [Fact]
+ [Trait(TestTraits.AcceptanceType, TestTraits.CheckIn)]
+ public void CanFilterByNoFilter()
+ {
+ const string key1 = "key";
+ var config1 = new SimpleTypedConfig(key1, "", true);
+ const string key2 = "key2";
+ var config2 = new SimpleTypedConfig(key2, "", true);
+ var icm = GetConfigManager(config1, config2);
+
+ const string module = "Az.KeyVault";
+ icm.UpdateConfig(new UpdateConfigOptions(key1, false, ConfigScope.CurrentUser) { AppliesTo = module });
+ icm.UpdateConfig(new UpdateConfigOptions(key2, false, ConfigScope.CurrentUser) { AppliesTo = module });
+ var listResults = icm.ListConfigs();
+ Assert.Equal(4, listResults.Count()); // default*2, module*2
+ }
+
+ [Fact]
+ [Trait(TestTraits.AcceptanceType, TestTraits.CheckIn)]
+ public void CanListDefinitions()
+ {
+ const string key1 = "key";
+ var config1 = new SimpleTypedConfig(key1, "", true);
+ const string key2 = "key2";
+ var config2 = new SimpleTypedConfig(key2, "", true);
+ var config3 = new TestConfig();
+ var icm = GetConfigManager(config1, config2, config3);
+
+ Assert.Equal(3, icm.ListConfigDefinitions().Count());
+
+ const string module = "Az.KeyVault";
+ icm.UpdateConfig(new UpdateConfigOptions(key1, false, ConfigScope.CurrentUser) { AppliesTo = module });
+ Assert.Equal(3, icm.ListConfigDefinitions().Count());
+ }
+
+ [Fact]
+ [Trait(TestTraits.AcceptanceType, TestTraits.CheckIn)]
+ public void CanGetScope()
+ {
+ const string key1 = "key";
+ var config1 = new SimpleTypedConfig(key1, "", true);
+ var config2 = new TestConfig();
+ var icm = GetConfigManager(config1, config2);
+
+ var listResults = icm.ListConfigs();
+ foreach (var config in listResults)
+ {
+ Assert.Equal(ConfigScope.Default, config.Scope);
+ }
+
+ var updated = icm.UpdateConfig(new UpdateConfigOptions(key1, false, ConfigScope.CurrentUser));
+ Assert.Equal(ConfigScope.CurrentUser, updated.Scope);
+
+ updated = icm.UpdateConfig(new UpdateConfigOptions(key1, true, ConfigScope.Process));
+ Assert.Equal(ConfigScope.Process, updated.Scope);
+
+ icm.ClearConfig(new ClearConfigOptions(key1, ConfigScope.Process));
+ updated = icm.ListConfigs(new ConfigFilter() { Keys = new string[] { key1 } }).Single();
+ Assert.Equal(ConfigScope.CurrentUser, updated.Scope);
+ }
+
+ [Fact]
+ [Trait(TestTraits.AcceptanceType, TestTraits.CheckIn)]
+ public void AppliesToShouldBeCaseInsensitive()
+ {
+ const string key = "key";
+ var config = new SimpleTypedConfig(key, "", 0);
+ var icm = GetConfigManager(config);
+
+ icm.UpdateConfig(new UpdateConfigOptions(key, 1, ConfigScope.CurrentUser) { AppliesTo = "az.abc" });
+ Assert.Equal(1, icm.ListConfigs(new ConfigFilter() { Keys = new[] { key }, AppliesTo = "az.abc" }).Single().Value);
+ Assert.Equal(1, icm.ListConfigs(new ConfigFilter() { Keys = new[] { key }, AppliesTo = "Az.Abc" }).Single().Value);
+ }
+
+ [Fact]
+ [Trait(TestTraits.AcceptanceType, TestTraits.CheckIn)]
+ public void ListDefinitionsShouldBeDictOrder()
+ {
+ const string key1 = "key1";
+ var config1 = new SimpleTypedConfig(key1, "", 0);
+ const string key2 = "key2";
+ var config2 = new SimpleTypedConfig(key2, "", 0);
+ const string key3 = "key3";
+ var config3 = new SimpleTypedConfig(key3, "", 0);
+ // register using wrong order
+ var icm = GetConfigManager(config2, config1, config3);
+
+ for (int i = 0; i != 10; ++i)
+ {
+ var definitions = icm.ListConfigDefinitions();
+ // expect return with dict order
+ Assert.Equal(key1, definitions.ElementAt(0).Key);
+ Assert.Equal(key2, definitions.ElementAt(1).Key);
+ Assert.Equal(key3, definitions.ElementAt(2).Key);
+ }
+ }
+ }
+}
diff --git a/src/Accounts/Authentication.Test/ConfigTests/PriorityTests.cs b/src/Accounts/Authentication.Test/ConfigTests/PriorityTests.cs
new file mode 100644
index 000000000000..eb5972d0f571
--- /dev/null
+++ b/src/Accounts/Authentication.Test/ConfigTests/PriorityTests.cs
@@ -0,0 +1,85 @@
+// ----------------------------------------------------------------------------------
+//
+// Copyright Microsoft Corporation
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ----------------------------------------------------------------------------------
+
+using Microsoft.Azure.Commands.Common.Authentication.Config;
+using Microsoft.Azure.PowerShell.Common.Config;
+using Microsoft.Rest.ClientRuntime.Azure.TestFramework;
+using Xunit;
+
+namespace Microsoft.Azure.Authentication.Test.Config
+{
+ public class PriorityTests : ConfigTestsBase
+ {
+ [Fact]
+ [Trait(TestTraits.AcceptanceType, TestTraits.CheckIn)]
+ public void UserConfigHigherThanSystemUserEnv()
+ {
+ const string retryKey = "Retry";
+ const string envName = "ENV_FOR_RETRY";
+ var config = new SimpleTypedConfig(retryKey, "", -1, envName);
+ IConfigManager icm = GetConfigManagerWithInitState((dataStore, path) =>
+ {
+ dataStore.WriteFile(path,
+@"{
+ ""Az"": {
+ ""Retry"": 100
+ }
+}");
+ }, envVar =>
+ {
+ envVar.Set(envName, "10", System.EnvironmentVariableTarget.User);
+ }, config);
+ Assert.Equal(100, icm.GetConfigValue(retryKey));
+ }
+
+ [Fact]
+ [Trait(TestTraits.AcceptanceType, TestTraits.CheckIn)]
+ public void ProcessEnvHigherThanUserConfig()
+ {
+
+ const string retryKey = "Retry";
+ const string envName = "ENV_FOR_RETRY";
+ var config = new SimpleTypedConfig(retryKey, "", -1, envName);
+ IConfigManager icm = GetConfigManagerWithInitState((dataStore, path) =>
+ {
+ dataStore.WriteFile(path,
+@"{
+ ""Az"": {
+ ""Retry"": 100
+ }
+}");
+ }, envVar =>
+ {
+ envVar.Set(envName, "10", System.EnvironmentVariableTarget.Process);
+ }, config);
+ Assert.Equal(10, icm.GetConfigValue(retryKey));
+ }
+
+ [Fact]
+ [Trait(TestTraits.AcceptanceType, TestTraits.CheckIn)]
+ public void ProcessConfigHigherThanProcessEnv()
+ {
+ const string retryKey = "Retry";
+ const string envName = "ENV_FOR_RETRY";
+ var config = new SimpleTypedConfig(retryKey, "", -1, envName);
+ IConfigManager icm = GetConfigManagerWithInitState(null, envVar =>
+ {
+ envVar.Set(envName, "10", System.EnvironmentVariableTarget.Process);
+ }, config);
+
+ icm.UpdateConfig(new UpdateConfigOptions(retryKey, 100, ConfigScope.Process));
+ Assert.Equal(100, icm.GetConfigValue(retryKey));
+ }
+ }
+}
diff --git a/src/Accounts/Authentication.Test/ConfigTests/RegisterConfigTests.cs b/src/Accounts/Authentication.Test/ConfigTests/RegisterConfigTests.cs
new file mode 100644
index 000000000000..ec436c41afe5
--- /dev/null
+++ b/src/Accounts/Authentication.Test/ConfigTests/RegisterConfigTests.cs
@@ -0,0 +1,106 @@
+// ----------------------------------------------------------------------------------
+//
+// Copyright Microsoft Corporation
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ----------------------------------------------------------------------------------
+
+using Microsoft.Azure.Commands.Common.Exceptions;
+using Microsoft.Azure.Commands.Common.Authentication.Config;
+using Microsoft.Azure.PowerShell.Common.Config;
+using Microsoft.Rest.ClientRuntime.Azure.TestFramework;
+using System;
+using System.Collections.Generic;
+using Xunit;
+
+namespace Microsoft.Azure.Authentication.Test.Config
+{
+ public class RegisterConfigTests : ConfigTestsBase
+ {
+ [Fact]
+ [Trait(TestTraits.AcceptanceType, TestTraits.CheckIn)]
+ public void CannotRegisterSameKeyTwice()
+ {
+ IConfigManager entry = GetConfigManager();
+ const string key = "CannotRegisterTwice";
+ entry.RegisterConfig(new SimpleTypedConfig(key, "", -1));
+ Assert.Throws(() =>
+ {
+ entry.RegisterConfig(new SimpleTypedConfig