diff --git a/Fededim.Extensions.Configuration.Protected.ConsoleTest/Fededim.Extensions.Configuration.Protected.ConsoleTest.csproj b/Fededim.Extensions.Configuration.Protected.ConsoleTest/Fededim.Extensions.Configuration.Protected.ConsoleTest.csproj index 39be376..cc5131c 100644 --- a/Fededim.Extensions.Configuration.Protected.ConsoleTest/Fededim.Extensions.Configuration.Protected.ConsoleTest.csproj +++ b/Fededim.Extensions.Configuration.Protected.ConsoleTest/Fededim.Extensions.Configuration.Protected.ConsoleTest.csproj @@ -6,9 +6,9 @@ - - - + + + diff --git a/Fededim.Extensions.Configuration.Protected.ConsoleTest/Program.cs b/Fededim.Extensions.Configuration.Protected.ConsoleTest/Program.cs index 5ee4de5..d86c05e 100644 --- a/Fededim.Extensions.Configuration.Protected.ConsoleTest/Program.cs +++ b/Fededim.Extensions.Configuration.Protected.ConsoleTest/Program.cs @@ -153,7 +153,7 @@ public static void Main(String[] args) { // updates inside appsettings..json the property "Int": , --> "Int": "Protected:{}," var environmentAppSettings = File.ReadAllText($"appsettings.{Environment.GetEnvironmentVariable("DOTNETCORE_ENVIRONMENT")}.json"); - environmentAppSettings = new Regex("\"Int\":.+?,").Replace(environmentAppSettings, $"\"Int\": \"{standardProtectConfigurationData.ProtectConfigurationValue($"Protect:{{{new Random().Next(0, 1000000)}}}")}\","); + environmentAppSettings = new Regex("\"Int\":.+?,").Replace(environmentAppSettings, $"\"Int\": \"{standardProtectConfigurationData.ProtectConfigurationValue($"\"Int\"", $"Protect:{{{new Random().Next(0, 1000000)}}}")}\","); File.WriteAllText($"appsettings.{Environment.GetEnvironmentVariable("DOTNETCORE_ENVIRONMENT")}.json", environmentAppSettings); // wait 5 seconds for the reload to take place, please check on this breakpoint that the value of "Int" property has changed in appSettings class and it is the same of appSettingsReloaded diff --git a/Fededim.Extensions.Configuration.Protected.DataProtectionAPI/DataProtectionAPIProtectConfigurationData.cs b/Fededim.Extensions.Configuration.Protected.DataProtectionAPI/DataProtectionAPIProtectConfigurationData.cs index d46ae48..e35f8dd 100644 --- a/Fededim.Extensions.Configuration.Protected.DataProtectionAPI/DataProtectionAPIProtectConfigurationData.cs +++ b/Fededim.Extensions.Configuration.Protected.DataProtectionAPI/DataProtectionAPIProtectConfigurationData.cs @@ -1,8 +1,6 @@ using Microsoft.AspNetCore.DataProtection; using Microsoft.Extensions.DependencyInjection; using System; -using System.Linq; -using System.Security.Cryptography; using System.Text.RegularExpressions; namespace Fededim.Extensions.Configuration.Protected.DataProtectionAPI @@ -11,12 +9,12 @@ namespace Fededim.Extensions.Configuration.Protected.DataProtectionAPI /// DataProtectionAPIProtectConfigurationData is a custom data structure which stores all Microsoft Data Protection Api configuration options needed by ProtectedConfigurationBuilder and ProtectConfigurationProvider /// public class DataProtectionAPIProtectConfigurationData : IProtectProviderConfigurationData - { + { /// /// The basic purpose string common to all purposes /// public const String DataProtectionAPIProtectConfigurationPurpose = "ProtectedConfigurationBuilder"; - + /// /// A purpose string based on a key number /// @@ -53,8 +51,9 @@ public static String DataProtectionAPIProtectConfigurationStringPurpose(String p /// - key number purpose set to 1 /// /// a service provider configured with Data Protection API, which must resolve the interface + /// if either dataProtectionServiceProvider or dataProtectionConfigureAction are null or not well configured public DataProtectionAPIProtectConfigurationData(IServiceProvider dataProtectionServiceProvider) - : this(null, null,null, dataProtectionServiceProvider, null, 1) + : this(null, null, null, dataProtectionServiceProvider, null, 1) { } @@ -68,9 +67,9 @@ public DataProtectionAPIProtectConfigurationData(IServiceProvider dataProtection /// a regular expression which captures the data to be encrypted in a named group called protectData /// a regular expression which captures the data to be decrypted in a named group called protectedData /// a string replacement expression which captures the substitution which must be applied for transforming unencrypted tokenization into an encrypted tokenization - + /// if either dataProtectionServiceProvider or dataProtectionConfigureAction are null or not well configured public DataProtectionAPIProtectConfigurationData(IServiceProvider dataProtectionServiceProvider, int keyNumber, String protectRegexString = null, String protectedRegexString = null, String protectedReplaceString = null) - : this(protectRegexString,protectedRegexString, protectedReplaceString, dataProtectionServiceProvider, null, keyNumber) + : this(protectRegexString, protectedRegexString, protectedReplaceString, dataProtectionServiceProvider, null, keyNumber) { } @@ -85,6 +84,7 @@ public DataProtectionAPIProtectConfigurationData(IServiceProvider dataProtection /// a regular expression which captures the data to be encrypted in a named group called protectData /// a regular expression which captures the data to be decrypted in a named group called protectedData /// a string replacement expression which captures the substitution which must be applied for transforming unencrypted tokenization into an encrypted tokenization + /// if either dataProtectionServiceProvider or dataProtectionConfigureAction are null or not well configured public DataProtectionAPIProtectConfigurationData(IServiceProvider dataProtectionServiceProvider, String purposeString, String protectRegexString = null, String protectedRegexString = null, String protectedReplaceString = null) : this(protectRegexString, protectedRegexString, protectedReplaceString, dataProtectionServiceProvider, null, purposeString) { @@ -99,6 +99,7 @@ public DataProtectionAPIProtectConfigurationData(IServiceProvider dataProtection /// - key number purpose set to 1 /// /// a configure action to setup the Data Protection API + /// if either dataProtectionServiceProvider or dataProtectionConfigureAction are null or not well configured public DataProtectionAPIProtectConfigurationData(Action dataProtectionConfigureAction) : this(null, null, null, null, dataProtectionConfigureAction, 1) { @@ -115,7 +116,7 @@ public DataProtectionAPIProtectConfigurationData(Action /// a regular expression which captures the data to be encrypted in a named group called protectData /// a regular expression which captures the data to be decrypted in a named group called protectedData /// a string replacement expression which captures the substitution which must be applied for transforming unencrypted tokenization into an encrypted tokenization - + /// if either dataProtectionServiceProvider or dataProtectionConfigureAction are null or not well configured public DataProtectionAPIProtectConfigurationData(Action dataProtectionConfigureAction, int keyNumber, String protectRegexString = null, String protectedRegexString = null, String protectedReplaceString = null) : this(protectRegexString, protectedRegexString, protectedReplaceString, null, dataProtectionConfigureAction, keyNumber) { @@ -132,6 +133,7 @@ public DataProtectionAPIProtectConfigurationData(Action /// a regular expression which captures the data to be encrypted in a named group called protectData /// a regular expression which captures the data to be decrypted in a named group called protectedData /// a string replacement expression which captures the substitution which must be applied for transforming unencrypted tokenization into an encrypted tokenization + /// if either dataProtectionServiceProvider or dataProtectionConfigureAction are null or not well configured public DataProtectionAPIProtectConfigurationData(Action dataProtectionConfigureAction, String purposeString, String protectRegexString = null, String protectedRegexString = null, String protectedReplaceString = null) : this(protectRegexString, protectedRegexString, protectedReplaceString, null, dataProtectionConfigureAction, purposeString) { @@ -149,7 +151,7 @@ public DataProtectionAPIProtectConfigurationData(Action /// a service provider configured with Data Protection API, this parameters is mutually exclusive to dataProtectionConfigureAction /// a configure action to setup the Data Protection API, this parameters is mutually exclusive to dataProtectionServiceProvider /// a number specifying the index of the key to use - /// if dataProtectionServiceProvider or dataProtectionServiceProvider is null or not well configured + /// if either dataProtectionServiceProvider or dataProtectionConfigureAction are null or not well configured public DataProtectionAPIProtectConfigurationData(String protectRegexString = null, String protectedRegexString = null, String protectedReplaceString = null, IServiceProvider dataProtectionServiceProvider = null, Action dataProtectionConfigureAction = null, int keyNumber = 1) : this(protectRegexString, protectedRegexString, protectedReplaceString, dataProtectionServiceProvider, dataProtectionConfigureAction, DataProtectionAPIProtectConfigurationKeyNumberToString(keyNumber)) { @@ -167,7 +169,7 @@ public DataProtectionAPIProtectConfigurationData(String protectRegexString = nul /// a service provider configured with Data Protection API, this parameters is mutually exclusive to dataProtectionConfigureAction /// a configure action to setup the Data Protection API, this parameters is mutually exclusive to dataProtectionServiceProvider /// a string used to derive the encryption key - /// if dataProtectionServiceProvider or dataProtectionServiceProvider is null or not well configured + /// if either dataProtectionServiceProvider or dataProtectionConfigureAction are null or not well configured public DataProtectionAPIProtectConfigurationData(String protectRegexString = null, String protectedRegexString = null, String protectedReplaceString = null, IServiceProvider dataProtectionServiceProvider = null, Action dataProtectionConfigureAction = null, String purposeString = null) { // check that at least one parameter is not null @@ -201,5 +203,4 @@ public DataProtectionAPIProtectConfigurationData(String protectRegexString = nul CheckConfigurationIsValid(); } } - } diff --git a/Fededim.Extensions.Configuration.Protected.DataProtectionAPI/DataProtectionAPIProtectProvider.cs b/Fededim.Extensions.Configuration.Protected.DataProtectionAPI/DataProtectionAPIProtectProvider.cs index 84af5aa..a454c08 100644 --- a/Fededim.Extensions.Configuration.Protected.DataProtectionAPI/DataProtectionAPIProtectProvider.cs +++ b/Fededim.Extensions.Configuration.Protected.DataProtectionAPI/DataProtectionAPIProtectProvider.cs @@ -23,9 +23,10 @@ public DataProtectionAPIProtectProvider(IDataProtector dataProtector) /// /// This methods create a new for supporting per configuration value encryption subkey (e.g. "subpurposes") /// + /// the configuration key containing the encryption subkey /// the per configuration value encryption subkey /// a derived based on the parameter - public IProtectProvider CreateNewProviderFromSubkey(String subkey) + public IProtectProvider CreateNewProviderFromSubkey(String key, String subkey) { return new DataProtectionAPIProtectProvider(DataProtector.CreateProtector(subkey)); } @@ -34,9 +35,10 @@ public IProtectProvider CreateNewProviderFromSubkey(String subkey) /// /// This method decrypts an encrypted string /// + /// the configuration key which needs to be decrypted /// the encrypted string to be decrypted /// the decrypted string - public String Decrypt(String encryptedValue) + public String Decrypt(String key, String encryptedValue) { return DataProtector.Unprotect(encryptedValue); } @@ -45,9 +47,10 @@ public String Decrypt(String encryptedValue) /// /// This method encrypts a plain-text string /// + /// the configuration key which needs to be encrypted /// the plain-text string to be encrypted /// the encrypted string - public String Encrypt(String plainTextValue) + public String Encrypt(String key, String plainTextValue) { return DataProtector.Protect(plainTextValue); } diff --git a/Fededim.Extensions.Configuration.Protected.DataProtectionAPI/Fededim.Extensions.Configuration.Protected.DataProtectionAPI.csproj b/Fededim.Extensions.Configuration.Protected.DataProtectionAPI/Fededim.Extensions.Configuration.Protected.DataProtectionAPI.csproj index 131bab4..b48ad0a 100644 --- a/Fededim.Extensions.Configuration.Protected.DataProtectionAPI/Fededim.Extensions.Configuration.Protected.DataProtectionAPI.csproj +++ b/Fededim.Extensions.Configuration.Protected.DataProtectionAPI/Fededim.Extensions.Configuration.Protected.DataProtectionAPI.csproj @@ -5,7 +5,7 @@ disable true - 1.0.7 + 1.0.8 Federico Di Marco <fededim@gmail.com> Fededim.Extensions.Configuration.Protected.DataProtectionAPI is the standard Microsoft DataProtectionAPI provider for the encryption/decryption of configuration values using Fededim.Extensions.Configuration.Protected. $([System.DateTime]::UtcNow.Year) @@ -51,13 +51,16 @@ - Dependency: requires at least Fededim.Extensions.Configuration.Protected version 1.0.19 - Update all Nuget packages to latest version - Updated project to net8.0 due to incoming net6.0 EOL + + v1.0.8 + - Dependency: requires at least Fededim.Extensions.Configuration.Protected version 1.0.20 - - - + + + diff --git a/Fededim.Extensions.Configuration.Protected.DataProtectionAPI/docs/README.md b/Fededim.Extensions.Configuration.Protected.DataProtectionAPI/docs/README.md index 641f209..307334f 100644 --- a/Fededim.Extensions.Configuration.Protected.DataProtectionAPI/docs/README.md +++ b/Fededim.Extensions.Configuration.Protected.DataProtectionAPI/docs/README.md @@ -228,6 +228,9 @@ v1.0.7 - Update all Nuget packages to latest version - Updated project to net8.0 due to incoming net6.0 EOL +v1.0.8 +- Dependency: requires at least Fededim.Extensions.Configuration.Protected version 1.0.20 + # Detailed guide You can find a [detailed article on CodeProject](https://www.codeproject.com/Articles/5374311/Fededim-Extensions-Configuration-Protected-the-ult) explaning the origin, how to use it and the main point of the implementation. diff --git a/Fededim.Extensions.Configuration.Protected.DataProtectionAPITest/DataProtectionAPIProtectProviderTest.cs b/Fededim.Extensions.Configuration.Protected.DataProtectionAPITest/DataProtectionAPIProtectProviderTest.cs index e53344f..f5fb6e6 100644 --- a/Fededim.Extensions.Configuration.Protected.DataProtectionAPITest/DataProtectionAPIProtectProviderTest.cs +++ b/Fededim.Extensions.Configuration.Protected.DataProtectionAPITest/DataProtectionAPIProtectProviderTest.cs @@ -9,10 +9,14 @@ namespace Fededim.Extensions.Configuration.Protected.DataProtectionAPITest { - - public class DataProtectionAPIProtectProviderTest : ProtectedConfigurationBuilderTest, IClassFixture + public abstract class DataProtectionAPIProtectProviderBaseTest : ProtectedConfigurationBuilderTest, IClassFixture { - private static void ConfigureDataProtection(IDataProtectionBuilder builder) + public DataProtectionAPIProtectProviderBaseTest(ProtectedConfigurationBuilderTestFixture context, ITestOutputHelper testOutputHelper, IProtectProviderConfigurationData protectProviderConfigurationData) + : base(context, testOutputHelper, protectProviderConfigurationData) + { + } + + protected static void ConfigureDataProtection(IDataProtectionBuilder builder) { builder.UseCryptographicAlgorithms(new AuthenticatedEncryptorConfiguration { @@ -24,12 +28,6 @@ private static void ConfigureDataProtection(IDataProtectionBuilder builder) - public DataProtectionAPIProtectProviderTest(ProtectedConfigurationBuilderTestFixture context, ITestOutputHelper testOutputHelper) :base(context, testOutputHelper,new DataProtectionAPIProtectConfigurationData(ConfigureDataProtection)) - { - - } - - protected override string TrimRegexCharsFromSubpurpose(string subpurpose) { return subpurpose.Replace(":", "*").Replace("}", "|"); @@ -41,4 +39,20 @@ protected override string TrimRegexCharsFromProtectData(string value) return value.Replace("}", "|"); } } + + public class DataProtectionAPIProtectProviderTest : DataProtectionAPIProtectProviderBaseTest + { + public DataProtectionAPIProtectProviderTest(ProtectedConfigurationBuilderTestFixture context, ITestOutputHelper testOutputHelper) : base(context, testOutputHelper, new DataProtectionAPIProtectConfigurationData(ConfigureDataProtection)) + { + } + } + + + + public class ReverseChainedDataProtectionAPIProtectProviderTest : DataProtectionAPIProtectProviderBaseTest + { + public ReverseChainedDataProtectionAPIProtectProviderTest(ProtectedConfigurationBuilderTestFixture context, ITestOutputHelper testOutputHelper) : base(context, testOutputHelper, new DataProtectionAPIProtectConfigurationData(ConfigureDataProtection).Chain(pp => new ReverseChainedProtectProvider(pp))) + { + } + } } diff --git a/Fededim.Extensions.Configuration.Protected.DataProtectionAPITest/Fededim.Extensions.Configuration.Protected.DataProtectionAPITest.csproj b/Fededim.Extensions.Configuration.Protected.DataProtectionAPITest/Fededim.Extensions.Configuration.Protected.DataProtectionAPITest.csproj index afbca68..9373fdd 100644 --- a/Fededim.Extensions.Configuration.Protected.DataProtectionAPITest/Fededim.Extensions.Configuration.Protected.DataProtectionAPITest.csproj +++ b/Fededim.Extensions.Configuration.Protected.DataProtectionAPITest/Fededim.Extensions.Configuration.Protected.DataProtectionAPITest.csproj @@ -12,12 +12,12 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/Fededim.Extensions.Configuration.Protected.DataProtectionAPITest/PassthroughProtectProviderTest.cs b/Fededim.Extensions.Configuration.Protected.DataProtectionAPITest/PassthroughProtectProviderTest.cs new file mode 100644 index 0000000..c93ebea --- /dev/null +++ b/Fededim.Extensions.Configuration.Protected.DataProtectionAPITest/PassthroughProtectProviderTest.cs @@ -0,0 +1,42 @@ +using Xunit.Abstractions; +using Xunit; + +namespace Fededim.Extensions.Configuration.Protected.DataProtectionAPITest +{ + + public abstract class PassthroughProtectProviderBaseTest : ProtectedConfigurationBuilderTest, IClassFixture + { + public PassthroughProtectProviderBaseTest(ProtectedConfigurationBuilderTestFixture context, ITestOutputHelper testOutputHelper, IProtectProviderConfigurationData protectProviderConfigurationData) + : base(context, testOutputHelper, protectProviderConfigurationData) + { + } + + + protected override string TrimRegexCharsFromSubpurpose(string subpurpose) + { + return subpurpose.Replace(":", "*").Replace("}", "|"); + } + + + protected override string TrimRegexCharsFromProtectData(string value) + { + return value.Replace("}", "|"); + } + } + + + public class PassthroughProtectProviderTest : DataProtectionAPIProtectProviderBaseTest + { + public PassthroughProtectProviderTest(ProtectedConfigurationBuilderTestFixture context, ITestOutputHelper testOutputHelper) : base(context, testOutputHelper, PassthroughProtectConfigurationData.CreateInstance) + { + } + } + + + public class ReverseChainedPassthroughProtectProviderTest : DataProtectionAPIProtectProviderBaseTest + { + public ReverseChainedPassthroughProtectProviderTest(ProtectedConfigurationBuilderTestFixture context, ITestOutputHelper testOutputHelper) : base(context, testOutputHelper, PassthroughProtectConfigurationData.CreateInstance.Chain(pp => new ReverseChainedProtectProvider(pp))) + { + } + } +} diff --git a/Fededim.Extensions.Configuration.Protected.DataProtectionAPITest/ProtectedConfigurationBuilderTest.cs b/Fededim.Extensions.Configuration.Protected.DataProtectionAPITest/ProtectedConfigurationBuilderTest.cs index 2566863..2b8f3b6 100644 --- a/Fededim.Extensions.Configuration.Protected.DataProtectionAPITest/ProtectedConfigurationBuilderTest.cs +++ b/Fededim.Extensions.Configuration.Protected.DataProtectionAPITest/ProtectedConfigurationBuilderTest.cs @@ -13,6 +13,7 @@ using Xunit.Abstractions; using System.Xml; using System.Xml.Linq; +using System.Threading; namespace Fededim.Extensions.Configuration.Protected.DataProtectionAPITest { @@ -32,7 +33,7 @@ public enum DataTypes { Null, WhitespaceString, String, Integer, Double, Boolean /// public class ProtectedConfigurationBuilderTestFixture : IDisposable { - public ProtectedConfigurationBuilderTestFixture() + static ProtectedConfigurationBuilderTestFixture() { // cleans existing XML, JSON and BAK files Directory.EnumerateFiles(".", "random_*.json").ToList().ForEach(f => File.Delete(f)); @@ -54,10 +55,30 @@ public abstract class ProtectedConfigurationBuilderTest const int SUBPURPOSEMAXLENGTH = 8; - protected String RANDOMJSONFILENAME => $"random_{Random.Next()}.json"; - protected String RANDOMXMLFILENAME => $"random_{Random.Next()}.xml"; + /// + /// Necessary when tests are executed in parallel + /// + /// + protected static Guid ProcessSafeRandomId() + { + var mutex = new Mutex(false, "ProtectedConfigurationBuilderTestMutex"); + + try + { + mutex.WaitOne(); + return Guid.NewGuid(); + } + finally + { + mutex.ReleaseMutex(); + } + } + - Random Random { get; } = new Random(); + protected static String RANDOMJSONFILENAME => $"random_{ProcessSafeRandomId()}.json"; + protected static String RANDOMXMLFILENAME => $"random_{ProcessSafeRandomId()}.xml"; + + protected Random Random { get; } = new Random(ProcessSafeRandomId().GetHashCode()); protected DataTypes[] DataTypesValues = (DataTypes[])Enum.GetValues(typeof(DataTypes)); protected LevelMove[] LevelMoveValues = (LevelMove[])Enum.GetValues(typeof(LevelMove)); @@ -78,10 +99,6 @@ public ProtectedConfigurationBuilderTest(ProtectedConfigurationBuilderTestFixtur JsonSerializerOptions = new JsonSerializerOptions { WriteIndented = true, TypeInfoResolver = new DefaultJsonTypeInfoResolver() }; } - protected ProtectedConfigurationBuilderTest() - { - } - /// /// Generates a random ascii string (valid characters from 32 to 126) @@ -120,7 +137,7 @@ protected Int64 NextInt64(Int64 minValue, Int64 maxValue) /// an array of datatypes to restrict the requested random value datatype, if null it picks any available ones. /// tuple with a random datatype and a random value /// - protected (DataTypes DataType, object Value) GenerateRandomValue(DataTypes[] requiredDataTypes=null) + protected (DataTypes DataType, object Value) GenerateRandomValue(DataTypes[] requiredDataTypes = null) { DataTypes dataType; if (requiredDataTypes == null) @@ -162,25 +179,25 @@ protected Int64 NextInt64(Int64 minValue, Int64 maxValue) case DataTypes.BooleanArray: var booleanArray = new Boolean?[Random.Next(0, ARRAYMAXLENGTH)]; for (int i = 0; i < booleanArray.Length; i++) - booleanArray[i] = (Boolean?) GenerateRandomValue(new[] { DataTypes.Boolean, DataTypes.Boolean, DataTypes.Boolean, DataTypes.Null }).Value; + booleanArray[i] = (Boolean?)GenerateRandomValue(new[] { DataTypes.Boolean, DataTypes.Boolean, DataTypes.Boolean, DataTypes.Null }).Value; return (dataType, booleanArray); case DataTypes.IntegerArray: var integerArray = new Int64?[Random.Next(0, ARRAYMAXLENGTH)]; for (int i = 0; i < integerArray.Length; i++) - integerArray[i] = (Int64?) GenerateRandomValue(new[] { DataTypes.Integer, DataTypes.Integer, DataTypes.Integer, DataTypes.Null }).Value; + integerArray[i] = (Int64?)GenerateRandomValue(new[] { DataTypes.Integer, DataTypes.Integer, DataTypes.Integer, DataTypes.Null }).Value; return (dataType, integerArray); case DataTypes.DateTimeOffsetArray: var dateTimeArray = new DateTimeOffset?[Random.Next(0, ARRAYMAXLENGTH)]; for (int i = 0; i < dateTimeArray.Length; i++) - dateTimeArray[i] = (DateTimeOffset?) GenerateRandomValue(new[] { DataTypes.DateTimeOffset, DataTypes.DateTimeOffset, DataTypes.DateTimeOffset, DataTypes.Null }).Value; + dateTimeArray[i] = (DateTimeOffset?)GenerateRandomValue(new[] { DataTypes.DateTimeOffset, DataTypes.DateTimeOffset, DataTypes.DateTimeOffset, DataTypes.Null }).Value; return (dataType, dateTimeArray); case DataTypes.DoubleArray: var doubleArray = new Double?[Random.Next(0, ARRAYMAXLENGTH)]; for (int i = 0; i < doubleArray.Length; i++) - doubleArray[i] = (Double?) GenerateRandomValue(new[] { DataTypes.Double, DataTypes.Double, DataTypes.Double, DataTypes.Null }).Value; + doubleArray[i] = (Double?)GenerateRandomValue(new[] { DataTypes.Double, DataTypes.Double, DataTypes.Double, DataTypes.Null }).Value; return (dataType, doubleArray); default: @@ -190,7 +207,7 @@ protected Int64 NextInt64(Int64 minValue, Int64 maxValue) - protected int CheckConfigurationEntriesAreEqual(IConfigurationRoot configurationRoot, bool skipMissingNullKeys=false) + protected int CheckConfigurationEntriesAreEqual(IConfigurationRoot configurationRoot, bool skipMissingNullKeys = false) { int numEntries = 0; @@ -312,8 +329,8 @@ protected String CreateJsonProtectValue(string subPurpose, DataTypes dataType, o /// /// generates a random hierarchical JSON file with random NUMENTRIES in both datatype and value /// - /// the generated filename and the number of the entries (variable due to the generation of the random number of element in arrays) - protected (String FileName, int NumEntries) GenerateRandomJsonFile() + /// the generated filename and the number of the values (variable due to the generation of the random number of element in arrays) + protected (String FileName, int NumEntries, int NumValues) GenerateRandomJsonFile() { String subPurpose; var fileName = RANDOMJSONFILENAME; @@ -325,7 +342,7 @@ protected String CreateJsonProtectValue(string subPurpose, DataTypes dataType, o var currentNode = JSONObject; int level = 1; - int numEntries = 0; + int numValues = 0; for (int i = 0; i < NUMENTRIES; i++) { @@ -346,7 +363,8 @@ protected String CreateJsonProtectValue(string subPurpose, DataTypes dataType, o subPurpose = (Random.Next() % 4 == 0) ? TrimRegexCharsFromSubpurpose(GenerateRandomString(SUBPURPOSEMAXLENGTH)) : null; currentNode[entryKey + "Plaintext"] = JsonValue.Create(entryValue.Value); currentNode[entryKey + "Encrypted"] = JsonValue.Create(CreateJsonProtectValue(subPurpose, entryValue.DataType, entryValue.Value)); - numEntries += 1; + + numValues += 1; break; case DataTypes.StringArray: @@ -360,7 +378,8 @@ protected String CreateJsonProtectValue(string subPurpose, DataTypes dataType, o subPurpose = (Random.Next() % 4 == 0) ? TrimRegexCharsFromSubpurpose(GenerateRandomString(SUBPURPOSEMAXLENGTH)) : null; return JsonValue.Create(CreateJsonProtectValue(subPurpose, entryValue.DataType, obj)); }).ToArray()); - numEntries += Math.Max(((Array)entryValue.Value).Length,1); + + numValues += Math.Max(((Array)entryValue.Value).Length, 1); // note some ConfigurationProviders do not load all values (e.g. XML does not load empty / null elements like , see break; } @@ -386,7 +405,7 @@ protected String CreateJsonProtectValue(string subPurpose, DataTypes dataType, o var fileInfo = new FileInfo(fileName); TestOutputHelper.WriteLine($"{DateTime.Now}: File location file://{Path.GetFullPath(fileName).Replace("\\", "/")}"); - return (fileName, numEntries); + return (fileName, NUMENTRIES, numValues); } @@ -407,12 +426,12 @@ public void RandomJsonFileTest(bool useJsonWithCommentFileProcessor) // genererates a JSON file var result = GenerateRandomJsonFile(); - stopwatch.Step($"Generated random JSON file ({result.NumEntries} entries size {new FileInfo(result.FileName).Length / 1024} KB)"); + stopwatch.Step($"Generated random JSON file (NumEntries {result.NumEntries} NumValues {result.NumValues} size {new FileInfo(result.FileName).Length / 1024} KB)"); // Encrypts the JSON file - Assert.True(ProtectProviderConfigurationData.ProtectFiles(".")?.Any()); + Assert.True(ProtectProviderConfigurationData.ProtectFile(result.FileName)); - stopwatch.Step($"Encrypted random JSON file ({result.NumEntries} entries size {new FileInfo(result.FileName).Length / 1024} KB)"); + stopwatch.Step($"Encrypted random JSON file (NumEntries {result.NumEntries} NumValues {result.NumValues} size {new FileInfo(result.FileName).Length / 1024} KB)"); // Reads the encrypted JSON file and checks that all encrypted entries do not match DefaultProtectRegexString var encryptedJsonDocument = JsonSerializer.Deserialize>(File.ReadAllText(result.FileName), JsonSerializerOptions); @@ -559,7 +578,7 @@ public bool AddXmlNode(XElement currentNode, string name, DataTypes dataType, ob /// - Trim whitespace from any string which goes into an element /// /// the generated filename and the number of the entries (variable due to the generation of the random number of element in arrays) - protected (String FileName, int NumEntries) GenerateRandomXmlFile() + protected (String FileName, int NumEntries, int NumValues) GenerateRandomXmlFile() { String subPurpose; var fileName = RANDOMXMLFILENAME; @@ -572,7 +591,7 @@ public bool AddXmlNode(XElement currentNode, string name, DataTypes dataType, ob var currentNode = xDocument.Root; int level = 1; - int numEntries = 0; + int numValues = 0; for (int i = 0; i < NUMENTRIES; i++) { @@ -598,7 +617,7 @@ public bool AddXmlNode(XElement currentNode, string name, DataTypes dataType, ob AddXmlNode(currentNode, entryKey + "Plaintext", entryValue.DataType, entryValue.Value); AddXmlNode(currentNode, entryKey + "Encrypted", entryValue.DataType, CreateXmlProtectValue(subPurpose, entryValue.DataType, entryValue.Value)); - numEntries += 1; + numValues += 1; break; case DataTypes.StringArray: @@ -618,7 +637,7 @@ public bool AddXmlNode(XElement currentNode, string name, DataTypes dataType, ob }).ToArray(); currentNode.Add(arrayElement); - numEntries += ((Array)entryValue.Value).Length; // note IConfigurationBuilder.AddXmlFile does not load empty XML elements like , see + numValues += Math.Max(((Array)entryValue.Value).Length, 1); // note some ConfigurationProviders do not load all values (e.g. XML does not load empty / null elements like , see break; } @@ -643,7 +662,7 @@ public bool AddXmlNode(XElement currentNode, string name, DataTypes dataType, ob var fileInfo = new FileInfo(fileName); TestOutputHelper.WriteLine($"{DateTime.Now}: File location file://{Path.GetFullPath(fileName).Replace("\\", "/")}"); - return (fileName, numEntries); + return (fileName, NUMENTRIES, numValues); } @@ -660,12 +679,12 @@ public void RandomXmlFileTest() // genererates a XML file var result = GenerateRandomXmlFile(); - stopwatch.Step($"Generated random XML file ({result.NumEntries} entries size {new FileInfo(result.FileName).Length / 1024} KB)"); + stopwatch.Step($"Generated random XML file (NumEntries {result.NumEntries} NumValues {result.NumValues} size {new FileInfo(result.FileName).Length / 1024} KB)"); // Encrypts the XML file - Assert.True(ProtectProviderConfigurationData.ProtectFiles(".", searchPattern: "*.xml")?.Any()); - - stopwatch.Step($"Encrypted random XML file ({result.NumEntries} entries size {new FileInfo(result.FileName).Length / 1024} KB)"); + Assert.True(ProtectProviderConfigurationData.ProtectFile(result.FileName)); + + stopwatch.Step($"Encrypted random XML file (NumEntries {result.NumEntries} NumValues {result.NumValues} size {new FileInfo(result.FileName).Length / 1024} KB)"); // Reads the encrypted XML file and checks that all encrypted entries do not match DefaultProtectRegexString var encryptedXmlDocument = XDocument.Load(result.FileName); @@ -691,7 +710,7 @@ public void RandomXmlFileTest() stopwatch.Step("Loaded and decrypted random XML file with ProtectedConfigurationBuilder"); // Foreach xxx_Plaintext key check that its value is equal to xxx_Encrypted, here we pass true to skipMissingNullKeys because of the previous comment about empty XML elements - var checkedEntries = CheckConfigurationEntriesAreEqual(configuration,true); + var checkedEntries = CheckConfigurationEntriesAreEqual(configuration, true); stopwatch.Step($"Checked that {checkedEntries} entries are equal"); } @@ -724,19 +743,21 @@ protected String CreateStringProtectValue(string subPurpose, DataTypes dataType, /// /// Generates random environment variables with (2*NUMENTRIES) in both datatype and value. Environment variables can only contain string and they aren't hierarchical. /// - protected void GenerateRandomEnvironmentVariables() + /// the number of entries generated + protected (int NumEntries, int NumValues) GenerateRandomEnvironmentVariables(EnvironmentVariableTarget environmentVariableTarget) { String subPurpose; - var fileName = RANDOMXMLFILENAME; + + var numberOfEntries = Math.Min(NUMENTRIES, 2000); + var numValues = 0; TestOutputHelper.WriteLine($"{DateTime.Now}: Generating a random environment variables {NUMENTRIES} keys, autogenerated strings max length {STRINGMAXLENGTH} and autogenerated array max length {ARRAYMAXLENGTH}..."); // Generate random environments variables - // - for (int i = 0; i < Math.Min(NUMENTRIES,2000); i++) + for (int i = 0; i < numberOfEntries; i++) { var entryValue = GenerateRandomValue(); - var entryKey = $"Entry_{i + 1}_{entryValue.DataType}_"; + var entryKey = $"TID_{Thread.CurrentThread.ManagedThreadId}_Entry_{i + 1}_{entryValue.DataType}_"; switch (entryValue.DataType) { @@ -749,8 +770,10 @@ protected void GenerateRandomEnvironmentVariables() case DataTypes.DateTimeOffset: subPurpose = (Random.Next() % 4 == 0) ? TrimRegexCharsFromSubpurpose(GenerateRandomString(SUBPURPOSEMAXLENGTH)) : null; - Environment.SetEnvironmentVariable(entryKey + "Plaintext", entryValue.Value?.ToString(), EnvironmentVariableTarget.Process); - Environment.SetEnvironmentVariable(entryKey + "Encrypted", CreateStringProtectValue(subPurpose, entryValue.DataType, entryValue.Value), EnvironmentVariableTarget.Process); + Environment.SetEnvironmentVariable(entryKey + "Plaintext", entryValue.Value?.ToString(), environmentVariableTarget); + Environment.SetEnvironmentVariable(entryKey + "Encrypted", CreateStringProtectValue(subPurpose, entryValue.DataType, entryValue.Value), environmentVariableTarget); + + numValues += 1; break; case DataTypes.StringArray: @@ -763,8 +786,10 @@ protected void GenerateRandomEnvironmentVariables() var firstValue = ((Array)entryValue.Value).Cast()?.FirstOrDefault()?.ToString(); - Environment.SetEnvironmentVariable(entryKey + "Plaintext", firstValue, EnvironmentVariableTarget.Process); - Environment.SetEnvironmentVariable(entryKey + "Encrypted", CreateStringProtectValue(subPurpose, entryValue.DataType, firstValue), EnvironmentVariableTarget.Process); + Environment.SetEnvironmentVariable(entryKey + "Plaintext", firstValue, environmentVariableTarget); + Environment.SetEnvironmentVariable(entryKey + "Encrypted", CreateStringProtectValue(subPurpose, entryValue.DataType, firstValue), environmentVariableTarget); + + numValues += Math.Max(((Array)entryValue.Value).Length, 1); // note some ConfigurationProviders do not load all values (e.g. XML does not load empty / null elements like , see break; default: @@ -773,11 +798,14 @@ protected void GenerateRandomEnvironmentVariables() } TestOutputHelper.WriteLine($"{DateTime.Now}: Starting test..."); + + return (numberOfEntries, numValues); } /// /// Generates random process environment variables with entries, encrypts them, loads them into IConfigurationRoot using a ProtectedConfigurationBuilder and tests that all decrypted key are equal to plaintext ones. + /// Note that environment variables are not thread-safe so in order to avoid issues when tests are running in parallel each entry is prefix with TID_{Thread.CurrentThread.ManagedThreadId} /// /// [Fact] @@ -786,12 +814,12 @@ public void RandomEnvironmentVariablesTest() var stopwatch = new ExtendedStopwatch(start: true, testOutputHelper: TestOutputHelper); // genererates random environment variables - GenerateRandomEnvironmentVariables(); + var result = GenerateRandomEnvironmentVariables(EnvironmentVariableTarget.Process); - stopwatch.Step($"Generated random environment variables (note that Windows has a maximum size of 32KB for all environment variables, so not all {NUMENTRIES} keys could be created)"); + stopwatch.Step($"Generated random environment variables (NumEntries {result.NumEntries} NumValues {result.NumValues} TID {Thread.CurrentThread.ManagedThreadId}, note that Windows has a maximum size of 32KB for all environment variables, so not all {NUMENTRIES} keys could be created)"); // Encrypts the environment variables - ProtectProviderConfigurationData.ProtectEnvironmentVariables(EnvironmentVariableTarget.Process); + ProtectProviderConfigurationData.ProtectEnvironmentVariables(EnvironmentVariableTarget.Process, $"TID_{Thread.CurrentThread.ManagedThreadId}"); stopwatch.Step("Encrypted random environment variables"); @@ -799,7 +827,7 @@ public void RandomEnvironmentVariablesTest() var environmentVariables = Environment.GetEnvironmentVariables(EnvironmentVariableTarget.Process); foreach (String key in environmentVariables.Keys) { - if (key.Contains("_Encrypted") && environmentVariables[key] != null) + if (key.StartsWith($"TID_{Thread.CurrentThread.ManagedThreadId}") && key.Contains("_Encrypted") && environmentVariables[key] != null) if (ProtectProviderConfigurationData.ProtectRegex.IsMatch(environmentVariables[key].ToString())) throw new InvalidDataException($"Found an invalid un-encrypted environment variable {key} Value {environmentVariables[key]}!"); } @@ -807,7 +835,7 @@ public void RandomEnvironmentVariablesTest() stopwatch.Step("Checked that all random environment variables are encrypted"); // Loads the XML with ProtectedConfigurationBuilder - var configuration = new ProtectedConfigurationBuilder(ProtectProviderConfigurationData).AddEnvironmentVariables().Build(); + var configuration = new ProtectedConfigurationBuilder(ProtectProviderConfigurationData).AddEnvironmentVariables($"TID_{Thread.CurrentThread.ManagedThreadId}").Build(); stopwatch.Step("Loaded and decrypted random environment variables with ProtectedConfigurationBuilder"); @@ -815,6 +843,13 @@ public void RandomEnvironmentVariablesTest() var checkedEntries = CheckConfigurationEntriesAreEqual(configuration); stopwatch.Step($"Checked that {checkedEntries} entries are equal"); + + // Clean generated environment variables in order to speed up, they are shared between threads + foreach (String key in environmentVariables.Keys) + { + if (key.StartsWith($"TID_{Thread.CurrentThread.ManagedThreadId}")) + Environment.SetEnvironmentVariable(key, null); + } } #endregion @@ -822,16 +857,18 @@ public void RandomEnvironmentVariablesTest() - #region "In-Memory dictionary Tests and helper methods" + #region "In-Memory dictionary Tests and helper methods" - /// - /// Generates a random in-memory dictionary with random 2*NUMENTRIES in both datatype and value. In-memory dictionary can only contain string and it isn't hierarchical. - /// - /// the generated in-memory dictionary - protected Dictionary GenerateRandomInMemoryDictionary() + /// + /// Generates a random in-memory dictionary with random 2*NUMENTRIES in both datatype and value. In-memory dictionary can only contain string and it isn't hierarchical. + /// + /// the generated in-memory dictionary + protected (Dictionary InMemoryDictionary, int NumEntries, int NumValues) GenerateRandomInMemoryDictionary() { String subPurpose; + int numValues = 0; + TestOutputHelper.WriteLine($"{DateTime.Now}: Generating a random in-memory dictionary with {NUMENTRIES} keys, autogenerated strings max length {STRINGMAXLENGTH} and autogenerated array max length {ARRAYMAXLENGTH}..."); var dictionary = new Dictionary(); @@ -856,6 +893,8 @@ protected Dictionary GenerateRandomInMemoryDictionary() currentNode[entryKey + "Plaintext"] = entryValue.Value?.ToString(); currentNode[entryKey + "Encrypted"] = CreateStringProtectValue(subPurpose, entryValue.DataType, entryValue.Value); + + numValues += 1; break; case DataTypes.StringArray: @@ -870,6 +909,8 @@ protected Dictionary GenerateRandomInMemoryDictionary() currentNode[entryKey + "Plaintext"] = firstValue; currentNode[entryKey + "Encrypted"] = CreateStringProtectValue(subPurpose, entryValue.DataType, firstValue); + + numValues += Math.Max(((Array)entryValue.Value).Length, 1); // note some ConfigurationProviders do not load all values (e.g. XML does not load empty / null elements like , see break; } } @@ -877,7 +918,7 @@ protected Dictionary GenerateRandomInMemoryDictionary() TestOutputHelper.WriteLine($"{DateTime.Now}: Starting test..."); - return dictionary; + return (dictionary, NUMENTRIES, numValues); } @@ -891,18 +932,18 @@ public void RandomInMemoryDictionaryTest() var stopwatch = new ExtendedStopwatch(start: true, testOutputHelper: TestOutputHelper); // genererates random in-memory dictionary - var dictionary = GenerateRandomInMemoryDictionary(); + var result = GenerateRandomInMemoryDictionary(); - stopwatch.Step("Generated random in-memory dictionary"); + stopwatch.Step($"Generated random in-memory dictionary (NumEntries {result.NumEntries} NumValues {result.NumValues})"); // Encrypts the in-memory dictionary - ProtectProviderConfigurationData.ProtectConfigurationValue(dictionary); + ProtectProviderConfigurationData.ProtectConfigurationValue(result.InMemoryDictionary); stopwatch.Step("Encrypted random in-memory dictionary"); // Checks that all entries of the in-memory dictioary are encrypted, e.g. do not match DefaultProtectRegexString - foreach (var entry in dictionary) + foreach (var entry in result.InMemoryDictionary) { if (entry.Key.Contains("_Encrypted") && entry.Value != null) if (ProtectProviderConfigurationData.ProtectRegex.IsMatch(entry.Value)) @@ -912,7 +953,7 @@ public void RandomInMemoryDictionaryTest() stopwatch.Step("Checked that all random in-memory dictionary is encrypted"); // Loads the in-memory dictionary with ProtectedConfigurationBuilder - var configuration = new ProtectedConfigurationBuilder(ProtectProviderConfigurationData).AddInMemoryCollection(dictionary).Build(); + var configuration = new ProtectedConfigurationBuilder(ProtectProviderConfigurationData).AddInMemoryCollection(result.InMemoryDictionary).Build(); stopwatch.Step("Loaded and decrypted random in-memory dictionary with ProtectedConfigurationBuilder"); @@ -934,12 +975,14 @@ public void RandomInMemoryDictionaryTest() /// Generates command line arguments with random (4*NUMENTRIES) in both datatype and value. Command line arguments can only contain string and they aren't hierarchical. /// /// the generated arguments array - protected String[] GenerateRandomCommandLine() + protected (String[] CommandLine, int NumEntries, int NumValues) GenerateRandomCommandLine() { String subPurpose; - TestOutputHelper.WriteLine($"{DateTime.Now}: Generating a random command line arguments with {NUMENTRIES} keys, autogenerated strings max length {STRINGMAXLENGTH} and autogenerated array max length {ARRAYMAXLENGTH}..."); + int numValues = 0; + TestOutputHelper.WriteLine($"{DateTime.Now}: Generating a random command line arguments with {NUMENTRIES} keys, autogenerated strings max length {STRINGMAXLENGTH} and autogenerated array max length {ARRAYMAXLENGTH}..."); + var args = new String[4 * NUMENTRIES]; for (int i = 0; i < NUMENTRIES; i++) @@ -962,6 +1005,8 @@ protected String[] GenerateRandomCommandLine() args[4 * i + 1] = entryValue.Value?.ToString(); args[4 * i + 2] = $"--{entryKey}Encrypted"; args[4 * i + 3] = CreateStringProtectValue(subPurpose, entryValue.DataType, entryValue.Value); + + numValues += 1; break; case DataTypes.StringArray: @@ -978,6 +1023,8 @@ protected String[] GenerateRandomCommandLine() args[4 * i + 1] = firstValue; args[4 * i + 2] = $"--{entryKey}Encrypted"; args[4 * i + 3] = CreateStringProtectValue(subPurpose, entryValue.DataType, firstValue); + + numValues += Math.Max(((Array)entryValue.Value).Length, 1); // note some ConfigurationProviders do not load all values (e.g. XML does not load empty / null elements like , see break; } } @@ -985,7 +1032,7 @@ protected String[] GenerateRandomCommandLine() TestOutputHelper.WriteLine($"{DateTime.Now}: Starting test..."); - return args; + return (args, NUMENTRIES, numValues); } @@ -999,12 +1046,12 @@ public void RandomCommandLineTest() var stopwatch = new ExtendedStopwatch(start: true, testOutputHelper: TestOutputHelper); // genererates random command line arguments - var args = GenerateRandomCommandLine(); + var result = GenerateRandomCommandLine(); - stopwatch.Step("Generated random comand line args"); + stopwatch.Step($"Generated random comand line args (NumEntries {result.NumEntries} NumValues {result.NumValues})"); // Encrypts the command line arguments - var encryptedArgs = ProtectProviderConfigurationData.ProtectConfigurationValue(args); + var encryptedArgs = ProtectProviderConfigurationData.ProtectConfigurationValue(result.CommandLine); stopwatch.Step("Encrypted random comand line args"); diff --git a/Fededim.Extensions.Configuration.Protected.DataProtectionAPITest/ReverseChainedProtectProvider.cs b/Fededim.Extensions.Configuration.Protected.DataProtectionAPITest/ReverseChainedProtectProvider.cs new file mode 100644 index 0000000..5c8b949 --- /dev/null +++ b/Fededim.Extensions.Configuration.Protected.DataProtectionAPITest/ReverseChainedProtectProvider.cs @@ -0,0 +1,48 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Fededim.Extensions.Configuration.Protected.DataProtectionAPITest +{ + public class ReverseChainedProtectProvider : IProtectProvider + { + public IProtectProvider ProtectProvider { get; protected set; } + + + public ReverseChainedProtectProvider(IProtectProvider protectProvider) + { + ProtectProvider = protectProvider; + } + + + protected string Reverse(string text) + { + if (text == null) + return null; + + char[] array = text.ToCharArray(); + Array.Reverse(array); + return new String(array); + } + + + public IProtectProvider CreateNewProviderFromSubkey(string key, string subkey) + { + return new ReverseChainedProtectProvider(ProtectProvider.CreateNewProviderFromSubkey(key, subkey)); + } + + + public string Decrypt(string key, string encryptedValue) + { + return ProtectProvider.Decrypt(key, Reverse(encryptedValue)); + } + + + public string Encrypt(string key, string plainTextValue) + { + return Reverse(ProtectProvider.Encrypt(key, plainTextValue)); + } + } +} diff --git a/Fededim.Extensions.Configuration.Protected/ConfigurationBuilderExtensions.cs b/Fededim.Extensions.Configuration.Protected/ConfigurationBuilderExtensions.cs index 820f197..c274c06 100644 --- a/Fededim.Extensions.Configuration.Protected/ConfigurationBuilderExtensions.cs +++ b/Fededim.Extensions.Configuration.Protected/ConfigurationBuilderExtensions.cs @@ -4,6 +4,7 @@ using System.IO; using System.Linq; using System.Text.RegularExpressions; +using System.Threading; namespace Fededim.Extensions.Configuration.Protected { @@ -71,38 +72,68 @@ public static void UseJsonWithCommentsProtectFileOption() /// public static IList ProtectFiles(this IProtectProviderConfigurationData protectProviderConfigurationData, String path, String searchPattern = "*.json", SearchOption searchOption = SearchOption.TopDirectoryOnly, bool backupOriginalFile = true) { - protectProviderConfigurationData.CheckConfigurationIsValid(); - var result = new List(); foreach (var f in Directory.EnumerateFiles(path, searchPattern, searchOption)) { - var fileContent = File.ReadAllText(f); - var replacedContent = fileContent; + if (protectProviderConfigurationData.ProtectFile(f, backupOriginalFile)) + result.Add(f); + } - foreach (var protectFileOption in ProtectFilesOptions) - if (protectFileOption.FilenameRegex.Match(f).Success) - { - replacedContent = protectFileOption.ProtectFileProcessor.ProtectFile(fileContent, protectProviderConfigurationData.ProtectRegex, (value) => ProtectConfigurationValue(protectProviderConfigurationData, value)); - break; - } + return result; + } - if (replacedContent != fileContent) - { - if (backupOriginalFile) - File.Copy(f, f + ".bak", true); - File.WriteAllText(f, replacedContent); - result.Add(f); + + /// + /// Encrypts a single file using the specified + /// + /// an IProtectProviderConfigurationData interface obtained from a one of the supported providers + /// the filename to encrypt + /// boolean which indicates whether to make a backupof original file with extension .bak + /// true if filename has been successfully encrypted, false otherwise + /// + public static bool ProtectFile(this IProtectProviderConfigurationData protectProviderConfigurationData, String filename, bool backupOriginalFile = true) + { + protectProviderConfigurationData.CheckConfigurationIsValid(); + + var fileContent = File.ReadAllText(filename); + var replacedContent = fileContent; + + foreach (var protectFileOption in ProtectFilesOptions) + if (protectFileOption.FilenameRegex.Match(filename).Success) + { + replacedContent = protectFileOption.ProtectFileProcessor.ProtectFile(fileContent, protectProviderConfigurationData.ProtectRegex, (key, value) => ProtectConfigurationValue(protectProviderConfigurationData, key, value)); + break; } + + if (replacedContent != fileContent) + { + if (backupOriginalFile) + File.Copy(filename, filename + ".bak", true); + + File.WriteAllText(filename, replacedContent); + + return true; } - return result; + return false; } + /// + /// Encrypts the String value using the specified + /// + /// an IProtectProviderConfigurationData interface obtained from a one of the supported providers + /// a String literal which needs to be encrypted + /// the encrypted configuration value + /// + public static String ProtectConfigurationValue(this IProtectProviderConfigurationData protectProviderConfigurationData, String value) + { + return ProtectConfigurationValue(protectProviderConfigurationData, null, value); + } @@ -110,12 +141,13 @@ public static IList ProtectFiles(this IProtectProviderConfigurationData /// Encrypts the String value using the specified /// /// an IProtectProviderConfigurationData interface obtained from a one of the supported providers + /// the configuration key which needs to be encrypted /// a String literal which needs to be encrypted /// the encrypted configuration value /// - public static String ProtectConfigurationValue(this IProtectProviderConfigurationData protectProviderConfigurationData, String value) + public static String ProtectConfigurationValue(this IProtectProviderConfigurationData protectProviderConfigurationData, String key, String value) { - return ProtectConfigurationValueInternal(protectProviderConfigurationData, value); + return ProtectConfigurationValueInternal(protectProviderConfigurationData, key, value); } @@ -126,7 +158,7 @@ public static String ProtectConfigurationValue(this IProtectProviderConfiguratio /// an IProtectProviderConfigurationData interface obtained from a one of the supported providers /// a String literal which needs to be encrypted /// - private static String ProtectConfigurationValueInternal(IProtectProviderConfigurationData protectProviderConfigurationData, String value) + private static String ProtectConfigurationValueInternal(IProtectProviderConfigurationData protectProviderConfigurationData, String key, String value) { if (value == null) return null; @@ -140,9 +172,15 @@ private static String ProtectConfigurationValueInternal(IProtectProviderConfigur var protectProvider = protectProviderConfigurationData.ProtectProvider; if (subPurposePresent) - protectProvider = protectProviderConfigurationData.ProtectProvider.CreateNewProviderFromSubkey(me.Groups["subPurpose"].Value); + protectProvider = protectProviderConfigurationData.ProtectProvider.CreateNewProviderFromSubkey(key, me.Groups["subPurpose"].Value); + + var encryptedValue = protectProvider.Encrypt(key, me.Groups["protectData"].Value); - return protectProviderConfigurationData.ProtectedReplaceString.Replace("${subPurposePattern}", subPurposePresent ? me.Groups["subPurposePattern"].Value : String.Empty).Replace("${protectedData}", protectProvider.Encrypt(me.Groups["protectData"].Value)); + // if the encryption function returns null or empty store the original value unencrypted + if (String.IsNullOrEmpty(encryptedValue)) + return me.Groups["protectData"].Value; + else + return protectProviderConfigurationData.ProtectedReplaceString.Replace("${subPurposePattern}", subPurposePresent ? me.Groups["subPurposePattern"].Value : String.Empty).Replace("${protectedData}", encryptedValue); }); } @@ -157,7 +195,7 @@ public static void ProtectConfigurationValue(this IProtectProviderConfigurationD { if (initialData != null) foreach (var key in initialData.Keys.ToList()) - initialData[key] = protectProviderConfigurationData.ProtectConfigurationValue(initialData[key]); + initialData[key] = protectProviderConfigurationData.ProtectConfigurationValue(key, initialData[key]); } @@ -170,7 +208,7 @@ public static void ProtectConfigurationValue(this IProtectProviderConfigurationD /// a newer encrypted IEnumerable public static IEnumerable ProtectConfigurationValue(this IProtectProviderConfigurationData protectProviderConfigurationData, IEnumerable arguments) { - return arguments?.Select(argument => protectProviderConfigurationData.ProtectConfigurationValue(argument)); + return arguments?.Select(argument => protectProviderConfigurationData.ProtectConfigurationValue(String.Empty, argument)); } @@ -183,7 +221,7 @@ public static IEnumerable ProtectConfigurationValue(this IProtectProvide /// a newer encrypted String[] array public static String[] ProtectConfigurationValue(this IProtectProviderConfigurationData protectProviderConfigurationData, String[] arguments) { - return arguments?.Select(argument => protectProviderConfigurationData.ProtectConfigurationValue(argument)).ToArray(); + return arguments?.Select(argument => protectProviderConfigurationData.ProtectConfigurationValue(String.Empty, argument)).ToArray(); } @@ -193,12 +231,13 @@ public static String[] ProtectConfigurationValue(this IProtectProviderConfigurat /// /// an IProtectProviderConfigurationData interface obtained from a one of the supported providers /// a target EnvironmentVariableTarget (e.g. User, Machine, Process) - public static void ProtectEnvironmentVariables(this IProtectProviderConfigurationData protectProviderConfigurationData, EnvironmentVariableTarget environmentTarget = EnvironmentVariableTarget.User) + public static void ProtectEnvironmentVariables(this IProtectProviderConfigurationData protectProviderConfigurationData, EnvironmentVariableTarget environmentTarget = EnvironmentVariableTarget.Process, String prefix = null) { var environmentVariables = Environment.GetEnvironmentVariables(environmentTarget); foreach (String key in environmentVariables.Keys) - Environment.SetEnvironmentVariable(key, protectProviderConfigurationData.ProtectConfigurationValue(environmentVariables[key].ToString()),environmentTarget); + if (String.IsNullOrEmpty(prefix) || key.StartsWith(prefix)) + Environment.SetEnvironmentVariable(key, protectProviderConfigurationData.ProtectConfigurationValue(key, environmentVariables[key].ToString()), environmentTarget); } diff --git a/Fededim.Extensions.Configuration.Protected/Fededim.Extensions.Configuration.Protected.csproj b/Fededim.Extensions.Configuration.Protected/Fededim.Extensions.Configuration.Protected.csproj index ff0e410..8365961 100644 --- a/Fededim.Extensions.Configuration.Protected/Fededim.Extensions.Configuration.Protected.csproj +++ b/Fededim.Extensions.Configuration.Protected/Fededim.Extensions.Configuration.Protected.csproj @@ -5,7 +5,7 @@ disable true - 1.0.19 + 1.0.20 Federico Di Marco <fededim@gmail.com> Fededim.Extensions.Configuration.Protected is an improved ConfigurationBuilder which allows partial or full encryption of configuration values stored inside any possible ConfigurationSource and fully integrated in the ASP.NET Core architecture. Basically, it implements a custom ConfigurationBuilder and a custom ConfigurationProvider defining a custom tokenization tag which whenever found decrypts the enclosed encrypted data using a provider implementing a standard interface IProtectProvider. $([System.DateTime]::UtcNow.Year) @@ -96,13 +96,21 @@ - Security fix: updated System.Text.Json to 8.0.5 in order to fix the security issue CVE-2024-43485 - Updated project to net8.0 due to incoming net6.0 EOL - Removed obsolete projects Fededim.Extensions.Configuration.ProtectedJson and Fededim.Extensions.Configuration.ProtectedJson.ConsoleTest from solution + + v1.0.20 + - Improved IProtectProvider interface by including also the key being encrypted / decrypted + - Implemented a PassthroughProtectProvider and PassthroughProtectConfigurationData which does not encrypt / decrypt anything, useful for development and testing + - Implemented a chain function in order to customize the behaviour of a IProtectProvider (e.g. try / catch to skip decryption exceptions, etc.) + - Implemented ProtectFile method in ConfigurationBuilderExtensions to encrypt just a single file (it was missing) + - Improved and added new tests for the chain function and the PassthroughProtectProvider, implemented a ProcessSafeRandomId for running tests in parallel + - Update all NuGet packages to the latest version - - + + diff --git a/Fededim.Extensions.Configuration.Protected/PassthroughProtectConfigurationData.cs b/Fededim.Extensions.Configuration.Protected/PassthroughProtectConfigurationData.cs new file mode 100644 index 0000000..a13f8e8 --- /dev/null +++ b/Fededim.Extensions.Configuration.Protected/PassthroughProtectConfigurationData.cs @@ -0,0 +1,25 @@ +using System; + +namespace Fededim.Extensions.Configuration.Protected +{ + /// + /// PassthroughProtectConfigurationData is the ProtectProviderConfigurationData class for the PassthroughProtectProvider + /// + public class PassthroughProtectConfigurationData : ProtectProviderConfigurationData + { + public static PassthroughProtectConfigurationData CreateInstance => new PassthroughProtectConfigurationData(); + + /// + /// Main constructor for PassthroughProtectConfigurationData + /// + /// a regular expression which captures the data to be encrypted in a named group called protectData + /// a regular expression which captures the data to be decrypted in a named group called protectedData + /// a string replacement expression which captures the substitution which must be applied for transforming unencrypted tokenization into an encrypted tokenization + /// if one of the input parameters is not well configured + public PassthroughProtectConfigurationData(String protectRegexString = null, String protectedRegexString = null, String protectedReplaceString = null) + : base(protectRegexString, protectedRegexString, protectedReplaceString, new PassthroughProtectProvider()) + { + + } + } +} diff --git a/Fededim.Extensions.Configuration.Protected/PassthroughProtectProvider.cs b/Fededim.Extensions.Configuration.Protected/PassthroughProtectProvider.cs new file mode 100644 index 0000000..2dd6bb0 --- /dev/null +++ b/Fededim.Extensions.Configuration.Protected/PassthroughProtectProvider.cs @@ -0,0 +1,54 @@ +using System; + +namespace Fededim.Extensions.Configuration.Protected +{ + /// + /// A passthrough protect provider for Fededim.Extensions.Configuration.Protected, implementing the interface. + /// It does not encrypt or decrypt any entries, it leaves all the entries untouched, it can be used for development purposes. + /// + public class PassthroughProtectProvider : IProtectProvider + { + /// + /// The main constructor + /// + public PassthroughProtectProvider() + { + } + + /// + /// This methods create a new for supporting per configuration value encryption subkey (e.g. "subpurposes") + /// + /// the configuration key containing the encryption subkey + /// the per configuration value encryption subkey + /// a derived based on the parameter + public IProtectProvider CreateNewProviderFromSubkey(String key, String subkey) + { + return this; + } + + + /// + /// This method decrypts an encrypted string + /// + /// the configuration key which needs to be decrypted + /// the encrypted string to be decrypted + /// the decrypted string + public String Decrypt(String key, String encryptedValue) + { + return encryptedValue; + } + + + /// + /// This method encrypts a plain-text string + /// + /// the configuration key which needs to be encrypted + /// the plain-text string to be encrypted + /// the encrypted string + public String Encrypt(String key, String plainTextValue) + { + return plainTextValue; + } + } +} + diff --git a/Fededim.Extensions.Configuration.Protected/ProtectFileProcessors.cs b/Fededim.Extensions.Configuration.Protected/ProtectFileProcessors.cs index b3e09bb..eda1b9f 100644 --- a/Fededim.Extensions.Configuration.Protected/ProtectFileProcessors.cs +++ b/Fededim.Extensions.Configuration.Protected/ProtectFileProcessors.cs @@ -21,7 +21,7 @@ public interface IProtectFileProcessor /// This is the configured protected regex which must be matched in file values in order to choose whether to encrypt or not the data. /// This is the protect function taking the plaintext data as input and producing encrypted base64 data as output /// the encrypted re-encoded file as a string - String ProtectFile(String rawFileText, Regex protectRegex, Func protectFunction); + String ProtectFile(String rawFileText, Regex protectRegex, Func protectFunction); } @@ -65,9 +65,9 @@ public class RawProtectFileProcessor : IProtectFileProcessor /// This is the configured protected regex which must be matched in file values in order to choose whether to encrypt or not the data. /// This is the protect function taking the plaintext data as input and producing encrypted base64 data as output /// the encrypted re-encoded file as a string - public virtual String ProtectFile(String rawFileText, Regex protectRegex, Func ProtectFunction) + public virtual String ProtectFile(String rawFileText, Regex protectRegex, Func ProtectFunction) { - rawFileText = ProtectFunction(rawFileText); + rawFileText = ProtectFunction(String.Empty,rawFileText); return rawFileText; } @@ -110,7 +110,7 @@ public JsonProtectFileProcessor(JsonSerializerOptions jsonSerializerOptions = nu /// This is the configured protected regex which must be matched in file values in order to choose whether to encrypt or not the data. /// This is the protect function taking the plaintext data as input and producing encrypted base64 data as output /// the encrypted re-encoded file as a string - public virtual String ProtectFile(String rawFileText, Regex protectRegex, Func protectFunction) + public virtual String ProtectFile(String rawFileText, Regex protectRegex, Func protectFunction) { // Loads the JSON file var document = JsonNode.Parse(rawFileText, JsonNodeOptions, JsonDocumentOptions); @@ -130,11 +130,11 @@ public virtual String ProtectFile(String rawFileText, Regex protectRegex, FuncThis is the configured protected regex which must be matched in file values in order to choose whether to encrypt or not the data. /// This is the protect function taking the plaintext data as input and producing encrypted base64 data as output /// list of all string nodes - protected virtual List ExtractAllStringNodes(JsonNode node, Regex protectRegex, Func protectFunction) + protected virtual List ExtractAllStringNodes(JsonNode node, Regex protectRegex, Func protectFunction) { var result = new List(); @@ -213,7 +213,7 @@ public JsonWithCommentsProtectFileProcessor(JsonSerializerOptions jsonSerializer /// This is the configured protected regex which must be matched in file values in order to choose whether to encrypt or not the data. /// This is the protect function taking the plaintext data as input and producing encrypted base64 data as output /// the encrypted re-encoded file as a string - public virtual String ProtectFile(String rawFileText, Regex protectRegex, Func protectFunction) + public virtual String ProtectFile(String rawFileText, Regex protectRegex, Func protectFunction) { return protectRegex.Replace(rawFileText, me => { @@ -223,7 +223,7 @@ public virtual String ProtectFile(String rawFileText, Regex protectRegex, FuncThis is the configured protected regex which must be matched in file values in order to choose whether to encrypt or not the data. /// This is the protect function taking the plaintext data as input and producing encrypted base64 data as output /// the encrypted re-encoded file as a string - public virtual String ProtectFile(String rawFileText, Regex protectRegex, Func protectFunction) + public virtual String ProtectFile(String rawFileText, Regex protectRegex, Func protectFunction) { // Loads the XML File var document = XDocument.Parse(rawFileText, LoadOptions); @@ -292,7 +292,7 @@ public virtual String ProtectFile(String rawFileText, Regex protectRegex, FuncThis is the configured protected regex which must be matched in file values in order to choose whether to encrypt or not the data. /// This is the protect function taking the plaintext data as input and producing encrypted base64 data as output /// list of all string nodes - protected virtual void ProtectXmlNodes(XElement element, Regex protectRegex, Func protectFunction) + protected virtual void ProtectXmlNodes(XElement element, Regex protectRegex, Func protectFunction) { String value; @@ -301,7 +301,7 @@ protected virtual void ProtectXmlNodes(XElement element, Regex protectRegex, Fun { value = attribute.Value; if (protectRegex.IsMatch(value)) - attribute.Value = protectFunction(value); + attribute.Value = protectFunction(attribute.Name.LocalName, value); } if (element.HasElements) @@ -315,7 +315,7 @@ protected virtual void ProtectXmlNodes(XElement element, Regex protectRegex, Fun // protects element value if it has no children elements value = element.Value; if (protectRegex.IsMatch(value)) - element.Value = protectFunction(value); + element.Value = protectFunction(element.Name.LocalName, value); } } } diff --git a/Fededim.Extensions.Configuration.Protected/ProtectProviderConfigurationData.cs b/Fededim.Extensions.Configuration.Protected/ProtectProviderConfigurationData.cs index 94d3baf..cd8db0e 100644 --- a/Fededim.Extensions.Configuration.Protected/ProtectProviderConfigurationData.cs +++ b/Fededim.Extensions.Configuration.Protected/ProtectProviderConfigurationData.cs @@ -12,25 +12,28 @@ public interface IProtectProvider /// /// This method encrypts a plain-text string /// + /// the configuration key to be encrypted, it could be empty string in some cases /// the plain-text string to be encrypted - /// the encrypted string - String Encrypt(String plainTextValue); + /// the encrypted string or null if you do not want to encrypt it + String Encrypt(String key, String plainTextValue); /// /// This method decrypts an encrypted string /// + /// the configuration key to be decrypted /// the encrypted string to be decrypted /// the decrypted string - String Decrypt(String encryptedValue); + String Decrypt(String key, String encryptedValue); /// /// This methods create a new for supporting per configuration value encryption subkey (e.g. "subpurposes") /// + /// the configuration key to be encrypted, it could be empty string in some cases /// the per configuration value encryption subkey /// a derived based on the parameter - IProtectProvider CreateNewProviderFromSubkey(string subkey); + IProtectProvider CreateNewProviderFromSubkey(String key, String subkey); } @@ -87,6 +90,18 @@ public virtual void CheckConfigurationIsValid() if (ProtectProvider == null) throw new ArgumentException("ProtectProvider must not be null!", nameof(ProtectProvider)); } + + + /// + /// A helper overridable method which allows you to chain multiple IProtectProvider + /// + /// a chain function which creates a new IProtectProvider reusing an existing IProtectProvider + /// a new IProtectProviderConfigurationData + public virtual IProtectProviderConfigurationData Chain(Func chainFunction) + { + return new ProtectProviderConfigurationData(ProtectRegex.ToString(), ProtectedRegex.ToString(), ProtectedReplaceString, chainFunction(ProtectProvider)); + } + } @@ -125,7 +140,7 @@ public ProtectProviderConfigurationData(String protectRegexString, String protec /// /// the global configuration data /// the local configuration data - /// + /// a new IProtectProviderConfigurationData public static IProtectProviderConfigurationData Merge(IProtectProviderConfigurationData global, IProtectProviderConfigurationData local) { if (local == null) diff --git a/Fededim.Extensions.Configuration.Protected/ProtectedConfigurationProvider.cs b/Fededim.Extensions.Configuration.Protected/ProtectedConfigurationProvider.cs index 73f5c5c..4a9614f 100644 --- a/Fededim.Extensions.Configuration.Protected/ProtectedConfigurationProvider.cs +++ b/Fededim.Extensions.Configuration.Protected/ProtectedConfigurationProvider.cs @@ -144,9 +144,9 @@ protected void DecryptChildKeys(String parentPath = null) IProtectProvider protectProvider = ProtectProviderConfigurationData.ProtectProvider; if (subPurposePresent) - protectProvider = protectProvider.CreateNewProviderFromSubkey(me.Groups["subPurpose"].Value); + protectProvider = protectProvider.CreateNewProviderFromSubkey(key, me.Groups["subPurpose"].Value); - return protectProvider.Decrypt(me.Groups["protectedData"].Value); + return protectProvider.Decrypt(key, me.Groups["protectedData"].Value); })); } } @@ -166,9 +166,9 @@ protected void DecryptChildKeys(String parentPath = null) IProtectProvider protectProvider = ProtectProviderConfigurationData.ProtectProvider; if (subPurposePresent) - protectProvider = protectProvider.CreateNewProviderFromSubkey(me.Groups["subPurpose"].Value); + protectProvider = protectProvider.CreateNewProviderFromSubkey(fullKey, me.Groups["subPurpose"].Value); - return protectProvider.Decrypt(me.Groups["protectedData"].Value); + return protectProvider.Decrypt(fullKey, me.Groups["protectedData"].Value); })); } else DecryptChildKeys(fullKey); @@ -220,8 +220,8 @@ public IEnumerable GetChildKeys(IEnumerable earlierKeys, String /// /// Calls the underlying provider Set method /// - /// - /// + /// the configuration key to update + /// the value to be set in the configuration key public void Set(String key, String value) { Provider.Set(key, value); @@ -232,8 +232,8 @@ public void Set(String key, String value) /// /// Calls the underlying provider TryGet method /// - /// - /// + /// the configuration key to retrieve + /// the retrieved value of the configuration key /// True if a value for the specified key was found, otherwise false. public bool TryGet(String key, out String value) { diff --git a/Fededim.Extensions.Configuration.Protected/docs/README.md b/Fededim.Extensions.Configuration.Protected/docs/README.md index 6fba4e2..b7e022d 100644 --- a/Fededim.Extensions.Configuration.Protected/docs/README.md +++ b/Fededim.Extensions.Configuration.Protected/docs/README.md @@ -282,6 +282,14 @@ v1.0.19 - Updated project to net8.0 due to incoming net6.0 EOL - Removed obsolete projects Fededim.Extensions.Configuration.ProtectedJson and Fededim.Extensions.Configuration.ProtectedJson.ConsoleTest from solution +v1.0.20 +- Improved IProtectProvider interface by including also the key being encrypted / decrypted +- Implemented a PassthroughProtectProvider and PassthroughProtectConfigurationData which does not encrypt / decrypt anything, useful for development and testing +- Implemented a chain function in order to customize the behaviour of a IProtectProvider (e.g. try / catch to skip decryption exceptions, etc.) +- Implemented ProtectFile method in ConfigurationBuilderExtensions to encrypt just a single file (it was missing) +- Improved and added new tests for the chain function and the PassthroughProtectProvider, implemented a ProcessSafeRandomId for running tests in parallel +- Update all NuGet packages to the latest version + # Detailed guide You can find a [detailed article on CodeProject](https://www.codeproject.com/Articles/5374311/Fededim-Extensions-Configuration-Protected-the-ult) explaning the origin, how to use it and the main point of the implementation. diff --git a/misc/last_build_artifacts/Cobertura.xml b/misc/last_build_artifacts/Cobertura.xml index 09e1fc1..3cc8707 100644 --- a/misc/last_build_artifacts/Cobertura.xml +++ b/misc/last_build_artifacts/Cobertura.xml @@ -1,233 +1,269 @@ - + D:\a\Fededim.Extensions.Configuration.Protected\Fededim.Extensions.Configuration.Protected\ - + - + - + - - - + + + - + - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + - + - + - + - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + - - - - + + + + - + - + - + - - - - + + + + + - - - - + + + + - - - - - - + + + + + + - - - - - - + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - + - + - + - + - - - - - - - - - - + + + + + + + + - + + + + + + + + - - - - - - - - - - - - - - + + + + + + + + + + + + - + + + + @@ -247,7 +283,7 @@ - + @@ -262,7 +298,7 @@ - + @@ -335,31 +371,31 @@ - + - + - + - - - - - - - - - - - - - + + + + + + + + + + + + + - + @@ -374,40 +410,92 @@ - - + + - - - - - - - - - - - - - + + + + + + + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - + @@ -417,28 +505,28 @@ - + - + - - + + - - - - - + + + + + - - + + @@ -450,124 +538,124 @@ - - + + - - - - - + + + + + - - - - - - - + + + + + + + - - + + - - - - - - - + + + + + + + - - + + - - + + - + - + - + - + - - - - - - - - - - - - - + + + + + + + + + + + + + - - - + + + - - - + + + - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + @@ -575,29 +663,29 @@ - + - + - + - + - - - + + + @@ -624,11 +712,11 @@ - - - - - + + + + + @@ -638,54 +726,54 @@ - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + - - - + + + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + @@ -693,21 +781,21 @@ - + - + - - - - - + + + + + @@ -720,12 +808,12 @@ - + - + @@ -738,61 +826,61 @@ - - + + - + - - - - - - + + + + + + - + - - - - - - - - - + + + + + + + + + - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + - + @@ -808,39 +896,39 @@ - + - + - + - - - - + + + + - - - + + + - + - - - - - - - - - - - + + + + + + + + + + + @@ -853,31 +941,31 @@ - - + + - - - - + + + + - - - - - - - - - - - - - - + + + + + + + + + + + + + + @@ -888,31 +976,31 @@ - + - - - + + + - + - - + + - - + + @@ -923,129 +1011,129 @@ - - + + - - + + - + - - + + - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + - - - - - - - - - + + + + + + + + + - - - - - + + + + - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + - + - + - + - + - + - + - + - - - + + + - - - - - - - + + + + + + + diff --git a/misc/last_build_artifacts/Fededim.Extensions.Configuration.Protected.DataProataProtectionAPIProtectConfigurationData.html b/misc/last_build_artifacts/Fededim.Extensions.Configuration.Protected.DataProataProtectionAPIProtectConfigurationData.html index 9a52adb..2bd4e2a 100644 --- a/misc/last_build_artifacts/Fededim.Extensions.Configuration.Protected.DataProataProtectionAPIProtectConfigurationData.html +++ b/misc/last_build_artifacts/Fededim.Extensions.Configuration.Protected.DataProataProtectionAPIProtectConfigurationData.html @@ -29,7 +29,7 @@

< Summary - Code Coverage and Repor Tag: -66_11262204043 +70_12224880118 @@ -57,7 +57,7 @@

< Summary - Code Coverage and Repor Total lines: -205 +206 Line coverage: @@ -111,17 +111,17 @@

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage -DataProtectionAPIProtectConfigurationKeyNumberPurpose(...)100%210% -DataProtectionAPIProtectConfigurationStringPurpose(...)50%2.15266.66% -DataProtectionAPIProtectConfigurationKeyNumberToString(...)100%11100% -.ctor(...)100%210% -.ctor(...)100%210% +DataProtectionAPIProtectConfigurationKeyNumberPurpose(...)100%210% +DataProtectionAPIProtectConfigurationStringPurpose(...)50%2.15266.66% +DataProtectionAPIProtectConfigurationKeyNumberToString(...)100%11100% +.ctor(...)100%210% +.ctor(...)100%210% .ctor(...)100%210% -.ctor(...)100%11100% -.ctor(...)100%210% -.ctor(...)100%210% -.ctor(...)100%11100% -.ctor(...)62.5%18.811677.77% +.ctor(...)100%11100% +.ctor(...)100%210% +.ctor(...)100%210% +.ctor(...)100%11100% +.ctor(...)62.5%18.811677.77% @@ -134,90 +134,90 @@

 1using Microsoft.AspNetCore.DataProtection;  2using Microsoft.Extensions.DependencyInjection;  3using System;4using System.Linq;5using System.Security.Cryptography;6using System.Text.RegularExpressions;78namespace Fededim.Extensions.Configuration.Protected.DataProtectionAPI9{10    /// <summary>11    /// DataProtectionAPIProtectConfigurationData is a custom data structure which stores all Microsoft Data Protection 12    /// </summary>13    public class DataProtectionAPIProtectConfigurationData : IProtectProviderConfigurationData14    {15        /// <summary>16        /// The basic purpose string common to all purposes17        /// </summary>18        public const String DataProtectionAPIProtectConfigurationPurpose = "ProtectedConfigurationBuilder";1920        /// <summary>21        /// A purpose string based on a key number22        /// </summary>23        /// <param name="keyNumber">a key number used to derive the encryption key</param>24        /// <returns>a purpose string</returns> - 025        public static String DataProtectionAPIProtectConfigurationKeyNumberPurpose(int keyNumber) => DataProtectionAPIPr2627        /// <summary>28        /// A purpose string based on a custom string29        /// </summary>30        /// <param name="purpose">a string used to derive the encryption key</param>31        /// <returns>a purpose string</returns>32        public static String DataProtectionAPIProtectConfigurationStringPurpose(String purpose)33        { - 1234            if (String.IsNullOrEmpty(purpose)) - 035                return DataProtectionAPIProtectConfigurationPurpose;36 - 1237            return $"{DataProtectionAPIProtectConfigurationPurpose}.{purpose}";38        }3940        /// <summary>41        /// internal function for mapping key number into a string42        /// </summary>43        /// <param name="keyNumber">a key number used to derive the encryption key</param>44        /// <returns>a string containing the key number</returns> - 1245        internal static String DataProtectionAPIProtectConfigurationKeyNumberToString(int keyNumber) => $"Key{keyNumber}4using System.Text.RegularExpressions;56namespace Fededim.Extensions.Configuration.Protected.DataProtectionAPI7{8    /// <summary>9    /// DataProtectionAPIProtectConfigurationData is a custom data structure which stores all Microsoft Data Protection 10    /// </summary>11    public class DataProtectionAPIProtectConfigurationData : IProtectProviderConfigurationData12    {13        /// <summary>14        /// The basic purpose string common to all purposes15        /// </summary>16        public const String DataProtectionAPIProtectConfigurationPurpose = "ProtectedConfigurationBuilder";1718        /// <summary>19        /// A purpose string based on a key number20        /// </summary>21        /// <param name="keyNumber">a key number used to derive the encryption key</param>22        /// <returns>a purpose string</returns> + 023        public static String DataProtectionAPIProtectConfigurationKeyNumberPurpose(int keyNumber) => DataProtectionAPIPr2425        /// <summary>26        /// A purpose string based on a custom string27        /// </summary>28        /// <param name="purpose">a string used to derive the encryption key</param>29        /// <returns>a purpose string</returns>30        public static String DataProtectionAPIProtectConfigurationStringPurpose(String purpose)31        { + 2432            if (String.IsNullOrEmpty(purpose)) + 033                return DataProtectionAPIProtectConfigurationPurpose;34 + 2435            return $"{DataProtectionAPIProtectConfigurationPurpose}.{purpose}";36        }3738        /// <summary>39        /// internal function for mapping key number into a string40        /// </summary>41        /// <param name="keyNumber">a key number used to derive the encryption key</param>42        /// <returns>a string containing the key number</returns> + 2443        internal static String DataProtectionAPIProtectConfigurationKeyNumberToString(int keyNumber) => $"Key{keyNumber}4445  46  47484950        /// <summary>51        /// Creates a standard DataProtection API configuration using the specified <see cref="dataProtectionServiceProv52        /// - default tokenization (e.g. <see cref="IProtectProviderConfigurationData.DefaultProtectRegexString"/>, <see53        /// - key number purpose set to 154        /// </summary>55        /// <param name="dataProtectionServiceProvider">a service provider configured with Data Protection API, which mu56        public DataProtectionAPIProtectConfigurationData(IServiceProvider dataProtectionServiceProvider) - 057            : this(null, null,null, dataProtectionServiceProvider, null, 1)58        { - 059        }48        /// <summary>49        /// Creates a standard DataProtection API configuration using the specified <see cref="dataProtectionServiceProv50        /// - default tokenization (e.g. <see cref="IProtectProviderConfigurationData.DefaultProtectRegexString"/>, <see51        /// - key number purpose set to 152        /// </summary>53        /// <param name="dataProtectionServiceProvider">a service provider configured with Data Protection API, which mu54        /// <exception cref="ArgumentException">if either dataProtectionServiceProvider or dataProtectionConfigureAction55        public DataProtectionAPIProtectConfigurationData(IServiceProvider dataProtectionServiceProvider) + 056            : this(null, null, null, dataProtectionServiceProvider, null, 1)57        { + 058        }59  606162        /// <summary>63        /// Creates a DataProtection API configuration using the specified <see cref="dataProtectionServiceProvider"/> a64        /// - default tokenization (e.g. <see cref="IProtectProviderConfigurationData.DefaultProtectRegexString"/>, <see65        /// </summary>66        /// <param name="dataProtectionServiceProvider">a service provider configured with Data Protection API, which mu67        /// <param name="keyNumber">a number specifying the index of the key to use</param>68        /// <param name="protectRegexString">a regular expression which captures the data to be encrypted in a named gro69        /// <param name="protectedRegexString">a regular expression which captures the data to be decrypted in a named g70        /// <param name="protectedReplaceString">a string replacement expression which captures the substitution which m7172        public DataProtectionAPIProtectConfigurationData(IServiceProvider dataProtectionServiceProvider, int keyNumber,  - 073            : this(protectRegexString,protectedRegexString, protectedReplaceString, dataProtectionServiceProvider, null,74        { - 075        }61        /// <summary>62        /// Creates a DataProtection API configuration using the specified <see cref="dataProtectionServiceProvider"/> a63        /// - default tokenization (e.g. <see cref="IProtectProviderConfigurationData.DefaultProtectRegexString"/>, <see64        /// </summary>65        /// <param name="dataProtectionServiceProvider">a service provider configured with Data Protection API, which mu66        /// <param name="keyNumber">a number specifying the index of the key to use</param>67        /// <param name="protectRegexString">a regular expression which captures the data to be encrypted in a named gro68        /// <param name="protectedRegexString">a regular expression which captures the data to be decrypted in a named g69        /// <param name="protectedReplaceString">a string replacement expression which captures the substitution which m70        /// <exception cref="ArgumentException">if either dataProtectionServiceProvider or dataProtectionConfigureAction71        public DataProtectionAPIProtectConfigurationData(IServiceProvider dataProtectionServiceProvider, int keyNumber,  + 072            : this(protectRegexString, protectedRegexString, protectedReplaceString, dataProtectionServiceProvider, null73        { + 074        }75  76  777879        /// <summary>80        /// Creates a DataProtection API configuration using the specified <see cref="dataProtectionServiceProvider"/> a81        /// - default tokenization (e.g. <see cref="IProtectProviderConfigurationData.DefaultProtectRegexString"/>, <see82        /// </summary>83        /// <param name="dataProtectionServiceProvider">a service provider configured with Data Protection API, which mu84        /// <param name="purposeString">a string used to derive the encryption key</param>85        /// <param name="protectRegexString">a regular expression which captures the data to be encrypted in a named gro86        /// <param name="protectedRegexString">a regular expression which captures the data to be decrypted in a named g87        /// <param name="protectedReplaceString">a string replacement expression which captures the substitution which m78        /// <summary>79        /// Creates a DataProtection API configuration using the specified <see cref="dataProtectionServiceProvider"/> a80        /// - default tokenization (e.g. <see cref="IProtectProviderConfigurationData.DefaultProtectRegexString"/>, <see81        /// </summary>82        /// <param name="dataProtectionServiceProvider">a service provider configured with Data Protection API, which mu83        /// <param name="purposeString">a string used to derive the encryption key</param>84        /// <param name="protectRegexString">a regular expression which captures the data to be encrypted in a named gro85        /// <param name="protectedRegexString">a regular expression which captures the data to be decrypted in a named g86        /// <param name="protectedReplaceString">a string replacement expression which captures the substitution which m87        /// <exception cref="ArgumentException">if either dataProtectionServiceProvider or dataProtectionConfigureAction  88        public DataProtectionAPIProtectConfigurationData(IServiceProvider dataProtectionServiceProvider, String purposeS  089            : this(protectRegexString, protectedRegexString, protectedReplaceString, dataProtectionServiceProvider, null  90        { @@ -232,128 +232,129 @@

 99        /// - key number purpose set to 1  100        /// </summary>  101        /// <param name="dataProtectionConfigureAction">a configure action to setup the Data Protection API</param>102        public DataProtectionAPIProtectConfigurationData(Action<IDataProtectionBuilder> dataProtectionConfigureAction) - 12103            : this(null, null, null, null, dataProtectionConfigureAction, 1)104        { - 12105        }106102        /// <exception cref="ArgumentException">if either dataProtectionServiceProvider or dataProtectionConfigureAction103        public DataProtectionAPIProtectConfigurationData(Action<IDataProtectionBuilder> dataProtectionConfigureAction) + 24104            : this(null, null, null, null, dataProtectionConfigureAction, 1)105        { + 24106        }  107  108109        /// <summary>110        /// Creates a DataProtection API configuration using the specified <see cref="dataProtectionConfigureAction"/> a111        /// - default tokenization (e.g. <see cref="IProtectProviderConfigurationData.DefaultProtectRegexString"/>, <see112        /// </summary>113        /// <param name="dataProtectionConfigureAction">a configure action to setup the Data Protection API</param>114        /// <param name="keyNumber">a number specifying the index of the key to use</param>115        /// <param name="protectRegexString">a regular expression which captures the data to be encrypted in a named gro116        /// <param name="protectedRegexString">a regular expression which captures the data to be decrypted in a named g117        /// <param name="protectedReplaceString">a string replacement expression which captures the substitution which m118119        public DataProtectionAPIProtectConfigurationData(Action<IDataProtectionBuilder> dataProtectionConfigureAction, i - 0120            : this(protectRegexString, protectedRegexString, protectedReplaceString, null, dataProtectionConfigureAction121        { - 0122        }123109110        /// <summary>111        /// Creates a DataProtection API configuration using the specified <see cref="dataProtectionConfigureAction"/> a112        /// - default tokenization (e.g. <see cref="IProtectProviderConfigurationData.DefaultProtectRegexString"/>, <see113        /// </summary>114        /// <param name="dataProtectionConfigureAction">a configure action to setup the Data Protection API</param>115        /// <param name="keyNumber">a number specifying the index of the key to use</param>116        /// <param name="protectRegexString">a regular expression which captures the data to be encrypted in a named gro117        /// <param name="protectedRegexString">a regular expression which captures the data to be decrypted in a named g118        /// <param name="protectedReplaceString">a string replacement expression which captures the substitution which m119        /// <exception cref="ArgumentException">if either dataProtectionServiceProvider or dataProtectionConfigureAction120        public DataProtectionAPIProtectConfigurationData(Action<IDataProtectionBuilder> dataProtectionConfigureAction, i + 0121            : this(protectRegexString, protectedRegexString, protectedReplaceString, null, dataProtectionConfigureAction122        { + 0123        }  124  125126        /// <summary>127        /// Creates a DataProtection API configuration using the specified <see cref="dataProtectionServiceProvider"/> a128        /// - default tokenization (e.g. <see cref="IProtectProviderConfigurationData.DefaultProtectRegexString"/>, <see129        /// </summary>130        /// <param name="dataProtectionConfigureAction">a configure action to setup the Data Protection API</param>131        /// <param name="purposeString">a string used to derive the encryption key</param>132        /// <param name="protectRegexString">a regular expression which captures the data to be encrypted in a named gro133        /// <param name="protectedRegexString">a regular expression which captures the data to be decrypted in a named g134        /// <param name="protectedReplaceString">a string replacement expression which captures the substitution which m135        public DataProtectionAPIProtectConfigurationData(Action<IDataProtectionBuilder> dataProtectionConfigureAction, S - 0136            : this(protectRegexString, protectedRegexString, protectedReplaceString, null, dataProtectionConfigureAction137        { - 0138        }139140126127        /// <summary>128        /// Creates a DataProtection API configuration using the specified <see cref="dataProtectionServiceProvider"/> a129        /// - default tokenization (e.g. <see cref="IProtectProviderConfigurationData.DefaultProtectRegexString"/>, <see130        /// </summary>131        /// <param name="dataProtectionConfigureAction">a configure action to setup the Data Protection API</param>132        /// <param name="purposeString">a string used to derive the encryption key</param>133        /// <param name="protectRegexString">a regular expression which captures the data to be encrypted in a named gro134        /// <param name="protectedRegexString">a regular expression which captures the data to be decrypted in a named g135        /// <param name="protectedReplaceString">a string replacement expression which captures the substitution which m136        /// <exception cref="ArgumentException">if either dataProtectionServiceProvider or dataProtectionConfigureAction137        public DataProtectionAPIProtectConfigurationData(Action<IDataProtectionBuilder> dataProtectionConfigureAction, S + 0138            : this(protectRegexString, protectedRegexString, protectedReplaceString, null, dataProtectionConfigureAction139        { + 0140        }  141  142143        /// <summary>144        /// Main constructor for DataProtectionAPIProtectConfigurationData using a key number145        /// </summary>146        /// <param name="protectRegexString">a regular expression which captures the data to be encrypted in a named gro147        /// <param name="protectedRegexString">a regular expression which captures the data to be decrypted in a named g148        /// <param name="protectedReplaceString">a string replacement expression which captures the substitution which m149        /// <param name="dataProtectionServiceProvider">a service provider configured with Data Protection API, this par150        /// <param name="dataProtectionConfigureAction">a configure action to setup the Data Protection API, this parame151        /// <param name="keyNumber">a number specifying the index of the key to use</param>152        /// <exception cref="ArgumentException">if dataProtectionServiceProvider or dataProtectionServiceProvider is nul153        public DataProtectionAPIProtectConfigurationData(String protectRegexString = null, String protectedRegexString = - 12154            : this(protectRegexString, protectedRegexString, protectedReplaceString, dataProtectionServiceProvider, data155        {156 - 12157        }143144145        /// <summary>146        /// Main constructor for DataProtectionAPIProtectConfigurationData using a key number147        /// </summary>148        /// <param name="protectRegexString">a regular expression which captures the data to be encrypted in a named gro149        /// <param name="protectedRegexString">a regular expression which captures the data to be decrypted in a named g150        /// <param name="protectedReplaceString">a string replacement expression which captures the substitution which m151        /// <param name="dataProtectionServiceProvider">a service provider configured with Data Protection API, this par152        /// <param name="dataProtectionConfigureAction">a configure action to setup the Data Protection API, this parame153        /// <param name="keyNumber">a number specifying the index of the key to use</param>154        /// <exception cref="ArgumentException">if either dataProtectionServiceProvider or dataProtectionConfigureAction155        public DataProtectionAPIProtectConfigurationData(String protectRegexString = null, String protectedRegexString = + 24156            : this(protectRegexString, protectedRegexString, protectedReplaceString, dataProtectionServiceProvider, data157        {  158159 + 24159        }  160161        /// <summary>162        /// Main constructor for DataProtectionAPIProtectConfigurationData using a purpose string163        /// </summary>164        /// <param name="protectRegexString">a regular expression which captures the data to be encrypted in a named gro165        /// <param name="protectedRegexString">a regular expression which captures the data to be decrypted in a named g166        /// <param name="protectedReplaceString">a string replacement expression which captures the substitution which m167        /// <param name="dataProtectionServiceProvider">a service provider configured with Data Protection API, this par168        /// <param name="dataProtectionConfigureAction">a configure action to setup the Data Protection API, this parame169        /// <param name="purposeString">a string used to derive the encryption key</param>170        /// <exception cref="ArgumentException">if dataProtectionServiceProvider or dataProtectionServiceProvider is nul - 12171        public DataProtectionAPIProtectConfigurationData(String protectRegexString = null, String protectedRegexString =172        {173            // check that at least one parameter is not null - 12174            if (dataProtectionServiceProvider == null && dataProtectionConfigureAction == null) - 0175                throw new ArgumentException("Either dataProtectionServiceProvider or dataProtectionConfigureAction must 176177            // if dataProtectionServiceProvider is null and we pass a dataProtectionConfigureAction configure a new serv - 12178            if (dataProtectionServiceProvider == null && dataProtectionConfigureAction != null)179            { - 12180                var services = new ServiceCollection(); - 12181                dataProtectionConfigureAction(services.AddDataProtection()); - 12182                dataProtectionServiceProvider = services.BuildServiceProvider();183            }184185            // check that dataProtectionServiceProvider resolves the IDataProtector - 12186            var dataProtect = dataProtectionServiceProvider.GetRequiredService<IDataProtectionProvider>().CreateProtecto - 12187            if (dataProtect == null) - 0188                throw new ArgumentException("Either dataProtectionServiceProvider or dataProtectionConfigureAction must 189190            // sets the abstract class base properties and calls CheckConfigurationIsValid - 12191            if (!String.IsNullOrEmpty(protectRegexString)) - 0192                ProtectRegex = new Regex(protectRegexString);193 - 12194            if (!String.IsNullOrEmpty(protectedRegexString)) - 0195                ProtectedRegex = new Regex(protectedRegexString);196 - 12197            ProtectedReplaceString = protectedReplaceString;161162163        /// <summary>164        /// Main constructor for DataProtectionAPIProtectConfigurationData using a purpose string165        /// </summary>166        /// <param name="protectRegexString">a regular expression which captures the data to be encrypted in a named gro167        /// <param name="protectedRegexString">a regular expression which captures the data to be decrypted in a named g168        /// <param name="protectedReplaceString">a string replacement expression which captures the substitution which m169        /// <param name="dataProtectionServiceProvider">a service provider configured with Data Protection API, this par170        /// <param name="dataProtectionConfigureAction">a configure action to setup the Data Protection API, this parame171        /// <param name="purposeString">a string used to derive the encryption key</param>172        /// <exception cref="ArgumentException">if either dataProtectionServiceProvider or dataProtectionConfigureAction + 24173        public DataProtectionAPIProtectConfigurationData(String protectRegexString = null, String protectedRegexString =174        {175            // check that at least one parameter is not null + 24176            if (dataProtectionServiceProvider == null && dataProtectionConfigureAction == null) + 0177                throw new ArgumentException("Either dataProtectionServiceProvider or dataProtectionConfigureAction must 178179            // if dataProtectionServiceProvider is null and we pass a dataProtectionConfigureAction configure a new serv + 24180            if (dataProtectionServiceProvider == null && dataProtectionConfigureAction != null)181            { + 24182                var services = new ServiceCollection(); + 24183                dataProtectionConfigureAction(services.AddDataProtection()); + 24184                dataProtectionServiceProvider = services.BuildServiceProvider();185            }186187            // check that dataProtectionServiceProvider resolves the IDataProtector + 24188            var dataProtect = dataProtectionServiceProvider.GetRequiredService<IDataProtectionProvider>().CreateProtecto + 24189            if (dataProtect == null) + 0190                throw new ArgumentException("Either dataProtectionServiceProvider or dataProtectionConfigureAction must 191192            // sets the abstract class base properties and calls CheckConfigurationIsValid + 24193            if (!String.IsNullOrEmpty(protectRegexString)) + 0194                ProtectRegex = new Regex(protectRegexString);195 + 24196            if (!String.IsNullOrEmpty(protectedRegexString)) + 0197                ProtectedRegex = new Regex(protectedRegexString);  198 - 12199            ProtectProvider = new DataProtectionAPIProtectProvider(dataProtect); + 24199            ProtectedReplaceString = protectedReplaceString;  200 - 12201            CheckConfigurationIsValid(); - 12202        }203    }204205} + 24201            ProtectProvider = new DataProtectionAPIProtectProvider(dataProtect);202 + 24203            CheckConfigurationIsValid(); + 24204        }205    }206} - +

Methods/Properties

-DataProtectionAPIProtectConfigurationKeyNumberPurpose(System.Int32)
-DataProtectionAPIProtectConfigurationStringPurpose(System.String)
-DataProtectionAPIProtectConfigurationKeyNumberToString(System.Int32)
-.ctor(System.IServiceProvider)
-.ctor(System.IServiceProvider,System.Int32,System.String,System.String,System.String)
+DataProtectionAPIProtectConfigurationKeyNumberPurpose(System.Int32)
+DataProtectionAPIProtectConfigurationStringPurpose(System.String)
+DataProtectionAPIProtectConfigurationKeyNumberToString(System.Int32)
+.ctor(System.IServiceProvider)
+.ctor(System.IServiceProvider,System.Int32,System.String,System.String,System.String)
.ctor(System.IServiceProvider,System.String,System.String,System.String,System.String)
-.ctor(System.Action`1<Microsoft.AspNetCore.DataProtection.IDataProtectionBuilder>)
-.ctor(System.Action`1<Microsoft.AspNetCore.DataProtection.IDataProtectionBuilder>,System.Int32,System.String,System.String,System.String)
-.ctor(System.Action`1<Microsoft.AspNetCore.DataProtection.IDataProtectionBuilder>,System.String,System.String,System.String,System.String)
-.ctor(System.String,System.String,System.String,System.IServiceProvider,System.Action`1<Microsoft.AspNetCore.DataProtection.IDataProtectionBuilder>,System.Int32)
-.ctor(System.String,System.String,System.String,System.IServiceProvider,System.Action`1<Microsoft.AspNetCore.DataProtection.IDataProtectionBuilder>,System.String)
+.ctor(System.Action`1<Microsoft.AspNetCore.DataProtection.IDataProtectionBuilder>)
+.ctor(System.Action`1<Microsoft.AspNetCore.DataProtection.IDataProtectionBuilder>,System.Int32,System.String,System.String,System.String)
+.ctor(System.Action`1<Microsoft.AspNetCore.DataProtection.IDataProtectionBuilder>,System.String,System.String,System.String,System.String)
+.ctor(System.String,System.String,System.String,System.IServiceProvider,System.Action`1<Microsoft.AspNetCore.DataProtection.IDataProtectionBuilder>,System.Int32)
+.ctor(System.String,System.String,System.String,System.IServiceProvider,System.Action`1<Microsoft.AspNetCore.DataProtection.IDataProtectionBuilder>,System.String)

+ \ No newline at end of file diff --git a/misc/last_build_artifacts/Fededim.Extensions.Configuration.Protected_PassthroughProtectProvider.html b/misc/last_build_artifacts/Fededim.Extensions.Configuration.Protected_PassthroughProtectProvider.html new file mode 100644 index 0000000..b99fd43 --- /dev/null +++ b/misc/last_build_artifacts/Fededim.Extensions.Configuration.Protected_PassthroughProtectProvider.html @@ -0,0 +1,238 @@ + + + + + + + +Fededim.Extensions.Configuration.Protected.PassthroughProtectProvider - Code Coverage and Reports - Coverage Report + +
+

< Summary - Code Coverage and Reports

+
+
+
Information
+
+
+ + + + + + + + + + + + + + + + + +
Class:Fededim.Extensions.Configuration.Protected.PassthroughProtectProvider
Assembly:Fededim.Extensions.Configuration.Protected
File(s):D:\a\Fededim.Extensions.Configuration.Protected\Fededim.Extensions.Configuration.Protected\Fededim.Extensions.Configuration.Protected\PassthroughProtectProvider.cs
Tag:70_12224880118
+
+
+
+
+
+
+
Line coverage
+
+
100%
+
+ + + + + + + + + + + + + + + + + + + + + +
Covered lines:5
Uncovered lines:0
Coverable lines:5
Total lines:54
Line coverage:100%
+
+
+
+
+
Branch coverage
+
+
N/A
+
+ + + + + + + + + + + + + +
Covered branches:0
Total branches:0
Branch coverage:N/A
+
+
+
+
+
Method coverage
+
+
+

Feature is only available for sponsors

+Upgrade to PRO version +
+
+
+
+

Metrics

+
+ +++++++ + + + + + + + +
MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor()100%11100%
CreateNewProviderFromSubkey(...)100%11100%
Decrypt(...)100%11100%
Encrypt(...)100%11100%
+
+

File(s)

+

D:\a\Fededim.Extensions.Configuration.Protected\Fededim.Extensions.Configuration.Protected\Fededim.Extensions.Configuration.Protected\PassthroughProtectProvider.cs

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
#LineLine coverage
 1using System;
 2
 3namespace Fededim.Extensions.Configuration.Protected
 4{
 5    /// <summary>
 6    /// A passthrough protect provider for Fededim.Extensions.Configuration.Protected, implementing the <see cref="IProt
 7    /// It does not encrypt or decrypt any entries, it leaves all the entries untouched, it can be used for development 
 8    /// </summary>
 9    public class PassthroughProtectProvider : IProtectProvider
 10    {
 11        /// <summary>
 12        /// The main constructor
 13        /// </summary>
 2414        public PassthroughProtectProvider()
 15        {
 2416        }
 17
 18        /// <summary>
 19        /// This methods create a new <see cref="IProtectProvider"/> for supporting per configuration value encryption s
 20        /// </summary>
 21        /// <param name="key">the configuration key containing the encryption subkey</param>
 22        /// <param name="subkey">the per configuration value encryption subkey</param>
 23        /// <returns>a derived <see cref="IProtectProvider"/> based on the <see cref="subkey"/> parameter</returns>
 24        public IProtectProvider CreateNewProviderFromSubkey(String key, String subkey)
 25        {
 151668126            return this;
 27        }
 28
 29
 30        /// <summary>
 31        /// This method decrypts an encrypted string
 32        /// </summary>
 33        /// <param name="key">the configuration key which needs to be decrypted</param>
 34        /// <param name="encryptedValue">the encrypted string to be decrypted</param>
 35        /// <returns>the decrypted string</returns>
 36        public String Decrypt(String key, String encryptedValue)
 37        {
 303651638            return encryptedValue;
 39        }
 40
 41
 42        /// <summary>
 43        /// This method encrypts a plain-text string
 44        /// </summary>
 45        /// <param name="key">the configuration key which needs to be encrypted</param>
 46        /// <param name="plainTextValue">the plain-text string to be encrypted</param>
 47        /// <returns>the encrypted string</returns>
 48        public String Encrypt(String key, String plainTextValue)
 49        {
 304322550            return plainTextValue;
 51        }
 52    }
 53}
 54
+
+
+
+ + \ No newline at end of file diff --git a/misc/last_build_artifacts/Fededim.Extensions.Configuration.Protected_ProtectFileOptions.html b/misc/last_build_artifacts/Fededim.Extensions.Configuration.Protected_ProtectFileOptions.html index 3fc3b56..53ebecf 100644 --- a/misc/last_build_artifacts/Fededim.Extensions.Configuration.Protected_ProtectFileOptions.html +++ b/misc/last_build_artifacts/Fededim.Extensions.Configuration.Protected_ProtectFileOptions.html @@ -29,7 +29,7 @@

< Summary - Code Coverage and Repor Tag: -66_11262204043 +70_12224880118 @@ -146,7 +146,7 @@

 21        /// <param name="protectRegex">This is the configured protected regex which must be matched in file values in or  22        /// <param name="protectFunction">This is the protect function taking the plaintext data as input and producing   23        /// <returns>the encrypted re-encoded file as a string</returns>24        String ProtectFile(String rawFileText, Regex protectRegex, Func<String, String> protectFunction);24        String ProtectFile(String rawFileText, Regex protectRegex, Func<String, String, String> protectFunction);  25    }  26  27 @@ -161,12 +161,12 @@

 36        /// <summary>  37        /// Specifies the regex on the filename which if matched applies the associated FileProcessorFunction  38        /// </summary> - 2239        public Regex FilenameRegex { get; private set; } + 5839        public Regex FilenameRegex { get; private set; }  40  41        /// <summary>  42        /// Specifies the ProtectFileProcessor class implementing the <see cref="IProtectFileProcessor"/> interface used  43        /// </summary> - 2044        public IProtectFileProcessor ProtectFileProcessor { get; private set; } + 3244        public IProtectFileProcessor ProtectFileProcessor { get; private set; }  45  46  847        public ProtectFileOptions(Regex filenameRegex, IProtectFileProcessor protectFileProcessor) @@ -190,9 +190,9 @@

 65        /// <param name="protectRegex">This is the configured protected regex which must be matched in file values in or  66        /// <param name="protectFunction">This is the protect function taking the plaintext data as input and producing   67        /// <returns>the encrypted re-encoded file as a string</returns>68        public virtual String ProtectFile(String rawFileText, Regex protectRegex, Func<String, String> ProtectFunction)68        public virtual String ProtectFile(String rawFileText, Regex protectRegex, Func<String, String, String> ProtectFu  69        {70            rawFileText = ProtectFunction(rawFileText);70            rawFileText = ProtectFunction(String.Empty,rawFileText);  71  72            return rawFileText;  73        } @@ -235,7 +235,7 @@

 110        /// <param name="protectRegex">This is the configured protected regex which must be matched in file values in or  111        /// <param name="protectFunction">This is the protect function taking the plaintext data as input and producing   112        /// <returns>the encrypted re-encoded file as a string</returns>113        public virtual String ProtectFile(String rawFileText, Regex protectRegex, Func<String, String> protectFunction)113        public virtual String ProtectFile(String rawFileText, Regex protectRegex, Func<String,String, String> protectFun  114        {  115            // Loads the JSON file  116            var document = JsonNode.Parse(rawFileText, JsonNodeOptions, JsonDocumentOptions); @@ -255,11 +255,11 @@

 130                    // to change the actual value you have to differentiate if the parent node is a JSON object or a JSO  131                    if (parentType == JsonValueKind.Object)  132                    {133                        parent[node.GetPropertyName()] = protectFunction(value);133                        parent[node.GetPropertyName()] = protectFunction(node.GetPropertyName(), value);  134                    }  135                    else if (parentType == JsonValueKind.Array)  136                    {137                        parent[node.GetElementIndex()] = protectFunction(value);137                        parent[node.GetElementIndex()] = protectFunction(parent.GetPropertyName(), value);  138                    }  139                }  140            } @@ -277,7 +277,7 @@

 152        /// <param name="protectRegex">This is the configured protected regex which must be matched in file values in or  153        /// <param name="protectFunction">This is the protect function taking the plaintext data as input and producing   154        /// <returns>list of all string nodes</returns>155        protected virtual List<JsonNode> ExtractAllStringNodes(JsonNode node, Regex protectRegex, Func<String, String> p155        protected virtual List<JsonNode> ExtractAllStringNodes(JsonNode node, Regex protectRegex, Func<String, String, S  156        {  157            var result = new List<JsonNode>();  158 @@ -338,7 +338,7 @@

 213        /// <param name="protectRegex">This is the configured protected regex which must be matched in file values in or  214        /// <param name="protectFunction">This is the protect function taking the plaintext data as input and producing   215        /// <returns>the encrypted re-encoded file as a string</returns>216        public virtual String ProtectFile(String rawFileText, Regex protectRegex, Func<String, String> protectFunction)216        public virtual String ProtectFile(String rawFileText, Regex protectRegex, Func<String, String, String> protectFu  217        {  218            return protectRegex.Replace(rawFileText, me =>  219            { @@ -348,7 +348,7 @@

 223  224                if (utf8JsonReader.Read())  225                {226                    utf8JsonWriter.WriteStringValue(protectFunction(utf8JsonReader.GetString()));226                    utf8JsonWriter.WriteStringValue(protectFunction(String.Empty, utf8JsonReader.GetString()));  227                    utf8JsonWriter.Flush();  228                    return Encoding.UTF8.GetString(reencodedJsonMemoryStream.ToArray()).Replace("\"",String.Empty);  229                } @@ -389,7 +389,7 @@

 264        /// <param name="protectRegex">This is the configured protected regex which must be matched in file values in or  265        /// <param name="protectFunction">This is the protect function taking the plaintext data as input and producing   266        /// <returns>the encrypted re-encoded file as a string</returns>267        public virtual String ProtectFile(String rawFileText, Regex protectRegex, Func<String, String> protectFunction)267        public virtual String ProtectFile(String rawFileText, Regex protectRegex, Func<String, String, String> protectFu  268        {  269            // Loads the XML File  270            var document = XDocument.Parse(rawFileText, LoadOptions); @@ -417,7 +417,7 @@

 292        /// <param name="protectRegex">This is the configured protected regex which must be matched in file values in or  293        /// <param name="protectFunction">This is the protect function taking the plaintext data as input and producing   294        /// <returns>list of all string nodes</returns>295        protected virtual void ProtectXmlNodes(XElement element, Regex protectRegex, Func<string, string> protectFunctio295        protected virtual void ProtectXmlNodes(XElement element, Regex protectRegex, Func<String, String, String> protec  296        {  297            String value;  298 @@ -426,7 +426,7 @@

 301            {  302                value = attribute.Value;  303                if (protectRegex.IsMatch(value))304                    attribute.Value = protectFunction(value);304                    attribute.Value = protectFunction(attribute.Name.LocalName, value);  305            }  306  307            if (element.HasElements) @@ -440,7 +440,7 @@

 315                // protects element value if it has no children elements  316                value = element.Value;  317                if (protectRegex.IsMatch(value))318                    element.Value = protectFunction(value);318                    element.Value = protectFunction(element.Name.LocalName, value);  319            }  320        }  321    } @@ -448,7 +448,7 @@

Generated by: ReportGenerator 5.3.8.0
10/9/2024 - 8:05:55 PM
GitHub | reportgenerator.io +

Methods/Properties

diff --git a/misc/last_build_artifacts/Fededim.Extensions.Configuration.Protected_ProtectProviderConfigurationData.html b/misc/last_build_artifacts/Fededim.Extensions.Configuration.Protected_ProtectProviderConfigurationData.html index 412b9e3..e3b2c1c 100644 --- a/misc/last_build_artifacts/Fededim.Extensions.Configuration.Protected_ProtectProviderConfigurationData.html +++ b/misc/last_build_artifacts/Fededim.Extensions.Configuration.Protected_ProtectProviderConfigurationData.html @@ -29,7 +29,7 @@

< Summary - Code Coverage and Repor Tag: -66_11262204043 +70_12224880118

@@ -40,16 +40,16 @@

< Summary - Code Coverage and Repor
Line coverage
-
0%
+
60%
- + - + @@ -57,11 +57,11 @@

< Summary - Code Coverage and Repor

- + - +
Covered lines:09
Uncovered lines:156
Coverable lines:
Total lines:144159
Line coverage:0%60%
@@ -70,12 +70,12 @@

< Summary - Code Coverage and Repor
Branch coverage
-
0%
+
16%
- + @@ -83,7 +83,7 @@

< Summary - Code Coverage and Repor

- +
Covered branches:04
Total branches:
Branch coverage:0%16.6%
@@ -111,8 +111,8 @@

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage -.ctor(...)0%2040% -Merge(...)0%420200% +.ctor(...)100%44100% +Merge(...)0%420200%
@@ -136,145 +136,160 @@

 12        /// <summary>  13        /// This method encrypts a plain-text string  14        /// </summary>15        /// <param name="plainTextValue">the plain-text string to be encrypted</param>16        /// <returns>the encrypted string</returns>17        String Encrypt(String plainTextValue);1815        /// <param name="key">the configuration key to be encrypted, it could be empty string in some cases</param>16        /// <param name="plainTextValue">the plain-text string to be encrypted</param>17        /// <returns>the encrypted string or null if you do not want to encrypt it</returns>18        String Encrypt(String key, String plainTextValue);  1920        /// <summary>21        /// This method decrypts an encrypted string22        /// </summary>23        /// <param name="encryptedValue">the encrypted string to be decrypted</param>24        /// <returns>the decrypted string</returns>25        String Decrypt(String encryptedValue);262728        /// <summary>29        /// This methods create a new <see cref="IProtectProvider"/> for supporting per configuration value encryption s30        /// </summary>31        /// <param name="subkey">the per configuration value encryption subkey</param>32        /// <returns>a derived <see cref="IProtectProvider"/> based on the <see cref="subkey"/> parameter</returns>33        IProtectProvider CreateNewProviderFromSubkey(string subkey);34    }35363738    /// <summary>39    /// an abstract class for specifying the configuration data of the encryption/decryption provider40    /// </summary>41    public abstract class IProtectProviderConfigurationData42    {43        public const String DefaultProtectRegexString = "Protect(?<subPurposePattern>(:{(?<subPurpose>[^:}]+)})?):{(?<pr44        public const String DefaultProtectedRegexString = "Protected(?<subPurposePattern>(:{(?<subPurpose>[^:}]+)})?):{(45        public const String DefaultProtectedReplaceString = "Protected${subPurposePattern}:{${protectedData}}";4647        /// <summary>48        /// The actual provider performing the encryption/decryption, <see cref="IProtectProvider"/> interface49        /// </summary>50        public IProtectProvider ProtectProvider { get; protected set; }5152        /// <summary>53        /// a regular expression which captures the data to be decrypted in a named group called protectData54        /// </summary>55        public Regex ProtectedRegex { get; protected set; }565758        /// <summary>59        /// a regular expression which captures the data to be encrypted in a named group called protectData60        /// </summary>61        public Regex ProtectRegex { get; protected set; }626364        /// <summary>65        /// a string replacement expression which captures the substitution which must be applied for transforming unenc66        /// </summary>67        public String ProtectedReplaceString { get; protected set; }686970        /// <summary>71        /// A helper overridable method for checking that the configuation data is valid (e.g. ProtectProvider is not nu72        /// </summary>73        public virtual void CheckConfigurationIsValid()74        {75            ProtectRegex = ProtectRegex ?? new Regex(DefaultProtectRegexString);76            if (!ProtectRegex.GetGroupNames().Contains("protectData"))77                throw new ArgumentException("ProtectRegex must contain a group named protectedData!", nameof(ProtectRege7879            ProtectedRegex = ProtectedRegex ?? new Regex(DefaultProtectedRegexString);80            if (!ProtectedRegex.GetGroupNames().Contains("protectedData"))81                throw new ArgumentException("ProtectedRegex must contain a group named protectedData!", nameof(Protected8283            ProtectedReplaceString = !String.IsNullOrEmpty(ProtectedReplaceString) ? ProtectedReplaceString : DefaultPro84            if (!ProtectedReplaceString.Contains("${protectedData}"))85                throw new ArgumentException("ProtectedReplaceString must contain ${protectedData}!", nameof(ProtectedRep8687            if (ProtectProvider == null)88                throw new ArgumentException("ProtectProvider must not be null!", nameof(ProtectProvider));89        }90    }91922021        /// <summary>22        /// This method decrypts an encrypted string23        /// </summary>24        /// <param name="key">the configuration key to be decrypted</param>25        /// <param name="encryptedValue">the encrypted string to be decrypted</param>26        /// <returns>the decrypted string</returns>27        String Decrypt(String key, String encryptedValue);282930        /// <summary>31        /// This methods create a new <see cref="IProtectProvider"/> for supporting per configuration value encryption s32        /// </summary>33        /// <param name="key">the configuration key to be encrypted, it could be empty string in some cases</param>34        /// <param name="subkey">the per configuration value encryption subkey</param>35        /// <returns>a derived <see cref="IProtectProvider"/> based on the <see cref="subkey"/> parameter</returns>36        IProtectProvider CreateNewProviderFromSubkey(String key, String subkey);37    }38394041    /// <summary>42    /// an abstract class for specifying the configuration data of the encryption/decryption provider43    /// </summary>44    public abstract class IProtectProviderConfigurationData45    {46        public const String DefaultProtectRegexString = "Protect(?<subPurposePattern>(:{(?<subPurpose>[^:}]+)})?):{(?<pr47        public const String DefaultProtectedRegexString = "Protected(?<subPurposePattern>(:{(?<subPurpose>[^:}]+)})?):{(48        public const String DefaultProtectedReplaceString = "Protected${subPurposePattern}:{${protectedData}}";4950        /// <summary>51        /// The actual provider performing the encryption/decryption, <see cref="IProtectProvider"/> interface52        /// </summary>53        public IProtectProvider ProtectProvider { get; protected set; }5455        /// <summary>56        /// a regular expression which captures the data to be decrypted in a named group called protectData57        /// </summary>58        public Regex ProtectedRegex { get; protected set; }596061        /// <summary>62        /// a regular expression which captures the data to be encrypted in a named group called protectData63        /// </summary>64        public Regex ProtectRegex { get; protected set; }656667        /// <summary>68        /// a string replacement expression which captures the substitution which must be applied for transforming unenc69        /// </summary>70        public String ProtectedReplaceString { get; protected set; }717273        /// <summary>74        /// A helper overridable method for checking that the configuation data is valid (e.g. ProtectProvider is not nu75        /// </summary>76        public virtual void CheckConfigurationIsValid()77        {78            ProtectRegex = ProtectRegex ?? new Regex(DefaultProtectRegexString);79            if (!ProtectRegex.GetGroupNames().Contains("protectData"))80                throw new ArgumentException("ProtectRegex must contain a group named protectedData!", nameof(ProtectRege8182            ProtectedRegex = ProtectedRegex ?? new Regex(DefaultProtectedRegexString);83            if (!ProtectedRegex.GetGroupNames().Contains("protectedData"))84                throw new ArgumentException("ProtectedRegex must contain a group named protectedData!", nameof(Protected8586            ProtectedReplaceString = !String.IsNullOrEmpty(ProtectedReplaceString) ? ProtectedReplaceString : DefaultPro87            if (!ProtectedReplaceString.Contains("${protectedData}"))88                throw new ArgumentException("ProtectedReplaceString must contain ${protectedData}!", nameof(ProtectedRep8990            if (ProtectProvider == null)91                throw new ArgumentException("ProtectProvider must not be null!", nameof(ProtectProvider));92        }  9394    /// <summary>95    /// ProtectedConfigurationData is a custom data structure which stores all configuration options needed by Protected96    /// </summary>97    public class ProtectProviderConfigurationData : IProtectProviderConfigurationData98    {99        /// <summary>100        /// Main constructor101        /// </summary>102        /// <param name="protectRegexString">a regular expression which captures the data to be encrypted in a named gro103        /// <param name="protectedRegexString">a regular expression which captures the data to be decrypted in a named g104        /// <param name="protectedReplaceString">a string replacement expression which captures the substitution which m105        /// <param name="protectProvider">an IProtectProvider interface obtained from a one of the supported providers</106        /// <exception cref="ArgumentException">thrown if the Regex does not containg a group named protectedData</excep - 0107        public ProtectProviderConfigurationData(String protectRegexString, String protectedRegexString, String protected108        { - 0109            if (!String.IsNullOrEmpty(protectRegexString)) - 0110                ProtectRegex = new Regex(protectRegexString);111 - 0112            if (!String.IsNullOrEmpty(protectedRegexString)) - 0113                ProtectedRegex = new Regex(protectedRegexString);114 - 0115            ProtectedReplaceString = protectedReplaceString; - 0116            ProtectProvider = protectProvider;117118            // check resulting configuration is valid, if it is not valid we raise an exception in order to be notified  - 0119            CheckConfigurationIsValid(); - 0120        }121122123        /// <summary>124        /// A static helper method which calculates the merge of the global and local protected configuration data. The 125        /// </summary>126        /// <param name="global">the global configuration data</param>127        /// <param name="local">the local configuration data</param>128        /// <returns></returns>129        public static IProtectProviderConfigurationData Merge(IProtectProviderConfigurationData global, IProtectProvider130        { - 0131            if (local == null) - 0132                return global;133 - 0134            if (global == null) - 0135                return local;9495        /// <summary>96        /// A helper overridable method which allows you to chain multiple IProtectProvider97        /// </summary>98        /// <param name="chainFunction">a chain function which creates a new IProtectProvider reusing an existing IProte99        /// <returns>a new IProtectProviderConfigurationData</returns>100        public virtual IProtectProviderConfigurationData Chain(Func<IProtectProvider,IProtectProvider> chainFunction)101        {102            return new ProtectProviderConfigurationData(ProtectRegex.ToString(), ProtectedRegex.ToString(), ProtectedRep103        }104105    }106107108109    /// <summary>110    /// ProtectedConfigurationData is a custom data structure which stores all configuration options needed by Protected111    /// </summary>112    public class ProtectProviderConfigurationData : IProtectProviderConfigurationData113    {114        /// <summary>115        /// Main constructor116        /// </summary>117        /// <param name="protectRegexString">a regular expression which captures the data to be encrypted in a named gro118        /// <param name="protectedRegexString">a regular expression which captures the data to be decrypted in a named g119        /// <param name="protectedReplaceString">a string replacement expression which captures the substitution which m120        /// <param name="protectProvider">an IProtectProvider interface obtained from a one of the supported providers</121        /// <exception cref="ArgumentException">thrown if the Regex does not containg a group named protectedData</excep + 48122        public ProtectProviderConfigurationData(String protectRegexString, String protectedRegexString, String protected123        { + 48124            if (!String.IsNullOrEmpty(protectRegexString)) + 24125                ProtectRegex = new Regex(protectRegexString);126 + 48127            if (!String.IsNullOrEmpty(protectedRegexString)) + 24128                ProtectedRegex = new Regex(protectedRegexString);129 + 48130            ProtectedReplaceString = protectedReplaceString; + 48131            ProtectProvider = protectProvider;132133            // check resulting configuration is valid, if it is not valid we raise an exception in order to be notified  + 48134            CheckConfigurationIsValid(); + 48135        }  136137            // perform merge - 0138            var result = new ProtectProviderConfigurationData(local.ProtectRegex?.ToString() ?? global.ProtectRegex?.ToS139 - 0140            return result;141        }142    }143144}137138        /// <summary>139        /// A static helper method which calculates the merge of the global and local protected configuration data. The 140        /// </summary>141        /// <param name="global">the global configuration data</param>142        /// <param name="local">the local configuration data</param>143        /// <returns>a new IProtectProviderConfigurationData</returns>144        public static IProtectProviderConfigurationData Merge(IProtectProviderConfigurationData global, IProtectProvider145        { + 0146            if (local == null) + 0147                return global;148 + 0149            if (global == null) + 0150                return local;151152            // perform merge + 0153            var result = new ProtectProviderConfigurationData(local.ProtectRegex?.ToString() ?? global.ProtectRegex?.ToS154 + 0155            return result;156        }157    }158159}

-

+