Skip to content
This repository has been archived by the owner on Jan 23, 2023. It is now read-only.
/ corefx Public archive

Commit

Permalink
Add tests and other fixes (#14956)
Browse files Browse the repository at this point in the history
* Add tests and other fixes

- Ports the majority of Mono Configuration tests (all relevant, all pass)
- Add a number of new tests
- Expand the implicit machine config
- Make check for default machine config explicit
- Fix file move helper to move over existing
- Add Dpapi protected section provider
- Port the RSA protected section provider

* Address feedback & fix issues

- Tweak the headers for the Mono tests
- Fix a Unix path manipulation problem in ClientConfigPaths

* Tweak tests

* Test tweak

* Address feedback
  • Loading branch information
JeremyKuhne authored Jan 9, 2017
1 parent 5e438d4 commit 7d0bf61
Show file tree
Hide file tree
Showing 47 changed files with 6,524 additions and 115 deletions.
2 changes: 2 additions & 0 deletions src/System.Configuration/src/System.Configuration.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Windows_Release|AnyCPU'" />
<ItemGroup>
<Compile Include="System\Configuration\DictionarySectionHandler.cs" />
<Compile Include="System\Configuration\DpapiProtectedConfigurationProvider.cs" />
<Compile Include="System\Configuration\IConfigurationSystem.cs" />
<Compile Include="System\Configuration\IPersistComponentSettings.cs" />
<Compile Include="System\Configuration\ApplicationSettingsBase.cs" />
Expand Down Expand Up @@ -174,6 +175,7 @@
<Compile Include="System\Configuration\ReadOnlyNameValueCollection.cs" />
<Compile Include="System\Configuration\RegexStringValidator.cs" />
<Compile Include="System\Configuration\RegexStringValidatorAttribute.cs" />
<Compile Include="System\Configuration\RsaProtectedConfigurationProvider.cs" />
<Compile Include="System\Configuration\RuntimeConfigurationRecord.cs" />
<Compile Include="System\Configuration\SafeBitVector32.cs" />
<Compile Include="System\Configuration\SchemeSettingElement.cs" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,6 @@ internal class ClientConfigPaths

private const string ConfigExtension = ".config";
private const int MaxLengthToUse = 25;
private const string FileUriLocal = "file:///";
private const string FileUriUnc = "file://";
private const string FileUri = "file:";
private const string HttpUri = "http://";

private const string StrongNameDesc = "StrongName";
Expand All @@ -35,67 +32,47 @@ private ClientConfigPaths(string exePath, bool includeUserConfig)
_includesUserConfig = includeUserConfig;

Assembly exeAssembly = null;
string applicationUri;
string applicationFilename = null;

// get the assembly and applicationUri for the file
if (exePath == null)
if (exePath != null)
{
// Exe path was specified, use it
ApplicationUri = Path.GetFullPath(exePath);
if (!File.Exists(ApplicationUri))
{
throw ExceptionUtil.ParameterInvalid(nameof(exePath));
}

applicationFilename = ApplicationUri;
}
else
{
// Now figure out the application path.
// Exe path wasn't specified, get it from the entry assembly
exeAssembly = Assembly.GetEntryAssembly();

if (exeAssembly == null)
throw new PlatformNotSupportedException();

HasEntryAssembly = true;
applicationUri = exeAssembly.CodeBase;

bool isFile = false;

if (StringUtil.StartsWithOrdinalIgnoreCase(applicationUri, FileUriLocal))
{
// If it is a local file URI, convert it to its filename, without invoking Uri class.
// example: "file:///C:/WINNT/Microsoft.NET/Framework/v2.0.x86fre/csc.exe"
isFile = true;
applicationUri = applicationUri.Substring(FileUriLocal.Length);
}
else
{
// If it is a UNC file URI, convert it to its filename, without invoking Uri class.
// example: "file://server/share/csc.exe"
if (StringUtil.StartsWithOrdinalIgnoreCase(applicationUri, FileUriUnc))
{
isFile = true;
applicationUri = applicationUri.Substring(FileUri.Length);
}
}

if (isFile)
// The original NetFX (desktop) code tried to get the local path without using Uri.
// If we ever find a need to do this again be careful with the logic. "file:///" is
// used for local paths and "file://" for UNCs. Simply removing the prefix will make
// local paths relative on Unix (e.g. "file:///home" will become "home" instead of
// "/home").
Uri uri = new Uri(exeAssembly.CodeBase);
if (uri.IsFile)
{
applicationUri = applicationUri.Replace('/', '\\');
applicationFilename = applicationUri;
ApplicationUri = uri.LocalPath;
applicationFilename = uri.LocalPath;
}
else
{
applicationUri = exeAssembly.EscapedCodeBase;
}
}
else
{
applicationUri = Path.GetFullPath(exePath);
if (!File.Exists(applicationUri))
{
throw ExceptionUtil.ParameterInvalid(nameof(exePath));
ApplicationUri = exeAssembly.EscapedCodeBase;
}

applicationFilename = applicationUri;
}

// Fallback if we haven't set the app config file path yet.
if (ApplicationConfigUri == null) ApplicationConfigUri = applicationUri + ConfigExtension;

// Set application path
ApplicationUri = applicationUri;
ApplicationConfigUri = ApplicationUri + ConfigExtension;

// In the case when exePath was explicitly supplied, we will not be able to
// construct user.config paths, so quit here.
Expand All @@ -108,20 +85,24 @@ private ClientConfigPaths(string exePath, bool includeUserConfig)
SetNamesAndVersion(applicationFilename, exeAssembly, isHttp);
if (isHttp) return;

// Create a directory suffix for local and roaming config of three parts:

// (1) Company name
string part1 = Validate(_companyName, limitSize: true);
string validAppDomainName = Validate(AppDomain.CurrentDomain.FriendlyName, limitSize: true);

// (2) Domain or product name & a application urit hash
string namePrefix = Validate(AppDomain.CurrentDomain.FriendlyName, limitSize: true);
if (string.IsNullOrEmpty(namePrefix))
namePrefix = Validate(ProductName, limitSize: true);
string applicationUriLower = !string.IsNullOrEmpty(ApplicationUri)
? ApplicationUri.ToLower(CultureInfo.InvariantCulture)
: null;
string namePrefix = !string.IsNullOrEmpty(validAppDomainName)
? validAppDomainName
: Validate(ProductName, limitSize: true);
string hashSuffix = GetTypeAndHashSuffix(applicationUriLower);

string part2 = !string.IsNullOrEmpty(namePrefix) && !string.IsNullOrEmpty(hashSuffix)
? namePrefix + hashSuffix
: null;

// (3) The product version
string part3 = Validate(ProductVersion, limitSize: false);

string dirSuffix = CombineIfValid(CombineIfValid(part1, part2), part3);
Expand Down Expand Up @@ -255,19 +236,16 @@ private void SetNamesAndVersion(string applicationFilename, Assembly exeAssembly
object[] attrs = exeAssembly.GetCustomAttributes(typeof(AssemblyCompanyAttribute), false);
if ((attrs != null) && (attrs.Length > 0))
{
_companyName = ((AssemblyCompanyAttribute)attrs[0]).Company;
_companyName = _companyName?.Trim();
_companyName = ((AssemblyCompanyAttribute)attrs[0]).Company?.Trim();
}

attrs = exeAssembly.GetCustomAttributes(typeof(AssemblyProductAttribute), false);
if ((attrs != null) && (attrs.Length > 0))
{
ProductName = ((AssemblyProductAttribute)attrs[0]).Product;
ProductName = ProductName?.Trim();
ProductName = ((AssemblyProductAttribute)attrs[0]).Product?.Trim();
}

ProductVersion = exeAssembly.GetName().Version.ToString();
ProductVersion = ProductVersion?.Trim();
ProductVersion = exeAssembly.GetName().Version.ToString().Trim();
}

// If we couldn't get custom attributes, fall back on the entry type namespace
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,10 @@ public ConfigurationFileMap(string machineConfigFilename)
{
if (string.IsNullOrEmpty(machineConfigFilename))
throw new ArgumentNullException(nameof(machineConfigFilename));

if (!File.Exists(machineConfigFilename))
{
throw new ArgumentException(string.Format(SR.Machine_config_file_not_found, machineConfigFilename),
nameof(machineConfigFilename));
}

MachineConfigFilename = machineConfigFilename;
}
Expand All @@ -57,5 +56,7 @@ private static string GetFilenameFromMachineConfigFilePath()
{
return ClientConfigurationHost.MachineConfigFilePath;
}

internal bool IsMachinePathDefault => _getFilenameThunk == GetFilenameFromMachineConfigFilePath;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System.Collections.Specialized;
using System.Security.Cryptography;
using System.Text;
using System.Xml;

namespace System.Configuration
{
public sealed class DpapiProtectedConfigurationProvider : ProtectedConfigurationProvider
{
private const int CRYPTPROTECT_UI_FORBIDDEN = 0x1;
private const int CRYPTPROTECT_LOCAL_MACHINE = 0x4;
private bool _useMachineProtection = true;
private string _keyEntropy;

public override XmlNode Decrypt(XmlNode encryptedNode)
{
if (encryptedNode.NodeType != XmlNodeType.Element ||
encryptedNode.Name != "EncryptedData")
{
throw new ConfigurationErrorsException(SR.DPAPI_bad_data);
}

XmlNode cipherNode = TraverseToChild(encryptedNode, "CipherData", false);
if (cipherNode == null)
throw new ConfigurationErrorsException(SR.DPAPI_bad_data);

XmlNode cipherValue = TraverseToChild(cipherNode, "CipherValue", true);
if (cipherValue == null)
throw new ConfigurationErrorsException(SR.DPAPI_bad_data);

string encText = cipherValue.InnerText;
if (encText == null)
throw new ConfigurationErrorsException(SR.DPAPI_bad_data);

string decText = DecryptText(encText);
XmlDocument xmlDocument = new XmlDocument();
xmlDocument.PreserveWhitespace = true;
xmlDocument.LoadXml(decText);
return xmlDocument.DocumentElement;
}

public override XmlNode Encrypt(XmlNode node)
{
string text = node.OuterXml;
string encText = EncryptText(text);
string pre = @"<EncryptedData><CipherData><CipherValue>";
string post = @"</CipherValue></CipherData></EncryptedData>";
string xmlText = pre + encText + post;

XmlDocument xmlDocument = new XmlDocument();
xmlDocument.PreserveWhitespace = true;
xmlDocument.LoadXml(xmlText);
return xmlDocument.DocumentElement;
}

private string EncryptText(string clearText)
{
if (clearText == null || clearText.Length < 1)
return clearText;

byte[] inputData = PrepareDataBlob(clearText);
byte[] entropyData = PrepareDataBlob(_keyEntropy);

byte[] encryptedData = ProtectedData.Protect(
userData: inputData,
optionalEntropy: entropyData,
scope: UseMachineProtection ? DataProtectionScope.LocalMachine : DataProtectionScope.CurrentUser);

return Convert.ToBase64String(encryptedData);
}

private string DecryptText(string encText)
{
if (encText == null || encText.Length < 1)
return encText;

byte[] inputData = Convert.FromBase64String(encText);
byte[] entropyData = PrepareDataBlob(_keyEntropy);

byte[] decryptedData = ProtectedData.Unprotect(
encryptedData: inputData,
optionalEntropy: entropyData,
scope: UseMachineProtection ? DataProtectionScope.LocalMachine : DataProtectionScope.CurrentUser);

return Encoding.Unicode.GetString(decryptedData);
}

public bool UseMachineProtection { get { return _useMachineProtection; } }

public override void Initialize(string name, NameValueCollection configurationValues)
{
base.Initialize(name, configurationValues);
_useMachineProtection = GetBooleanValue(configurationValues, "useMachineProtection", true);
_keyEntropy = configurationValues["keyEntropy"];
configurationValues.Remove("keyEntropy");
if (configurationValues.Count > 0)
throw new ConfigurationErrorsException(string.Format(SR.Unrecognized_initialization_value, configurationValues.GetKey(0)));
}

private static XmlNode TraverseToChild(XmlNode node, string name, bool onlyChild)
{
foreach (XmlNode child in node.ChildNodes)
{
if (child.NodeType != XmlNodeType.Element)
continue;
if (child.Name == name)
return child; // found it!
if (onlyChild)
return null;
}

return null;
}

private static byte[] PrepareDataBlob(string s)
{
return (s != null) ? Encoding.Unicode.GetBytes(s) : Array.Empty<byte>();
}

private static bool GetBooleanValue(NameValueCollection configurationValues, string valueName, bool defaultValue)
{
string s = configurationValues[valueName];
if (s == null)
return defaultValue;
configurationValues.Remove(valueName);
if (s == "true")
return true;
if (s == "false")
return false;
throw new ConfigurationErrorsException(string.Format(SR.Config_invalid_boolean_attribute, valueName));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public override string GetStreamName(string configPath)
string name = base.GetStreamName(configPath);

if (ConfigPathUtility.GetName(configPath) == ClientConfigurationHost.MachineConfigName
&& !string.Equals(_fileMap?.MachineConfigFilename, name))
&& (_fileMap?.IsMachinePathDefault ?? true))
{
// The machine config was asked for and wasn't explicitly
// specified, stash the "default" machine.config path
Expand All @@ -66,7 +66,22 @@ public override Stream OpenStreamForRead(string streamName)
@"<configuration>
<configSections>
<section name='appSettings' type='System.Configuration.AppSettingsSection, System.Configuration' restartOnExternalChanges='false' requirePermission='false'/>
<section name='connectionStrings' type='System.Configuration.ConnectionStringsSection, System.Configuration' requirePermission='false'/>
<section name='mscorlib' type='System.Configuration.IgnoreSection, System.Configuration' allowLocation='false'/>
<section name='runtime' type='System.Configuration.IgnoreSection, System.Configuration' allowLocation='false'/>
<section name='assemblyBinding' type='System.Configuration.IgnoreSection, System.Configuration' allowLocation='false'/>
<section name='satelliteassemblies' type='System.Configuration.IgnoreSection, System.Configuration' allowLocation='false'/>
<section name='startup' type='System.Configuration.IgnoreSection, System.Configuration' allowLocation='false'/>
</configSections>
<configProtectedData defaultProvider='RsaProtectedConfigurationProvider'>
<providers>
<add name = 'RsaProtectedConfigurationProvider' type='System.Configuration.RsaProtectedConfigurationProvider,System.Configuration' description='Uses RsaCryptoServiceProvider to encrypt and decrypt' keyContainerName='NetFrameworkConfigurationKey' cspProviderName='' useMachineContainer='true' useOAEP='false'/>
<add name = 'DataProtectionConfigurationProvider' type='System.Configuration.DpapiProtectedConfigurationProvider,System.Configuration' description='Uses CryptProtectData and CryptUnProtectData Windows APIs to encrypt and decrypt' useMachineProtection='true' keyEntropy=''/>
</providers>
</configProtectedData>
<connectionStrings>
<add name = 'LocalSqlServer' connectionString='data source=.\SQLEXPRESS;Integrated Security=SSPI;AttachDBFilename=|DataDirectory|aspnetdb.mdf;User Instance=true' providerName='System.Data.SqlClient'/>
</connectionStrings>
</configuration>";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -173,12 +173,19 @@ private void ReplaceFile(string source, string target)
}
}

// Attempt to move a file from one location to another
// Attempt to move a file from one location to another, overwriting if needed
private bool AttemptMove(string source, string target)
{
try
{
File.Replace(source, target, null);
if (File.Exists(target))
{
File.Replace(source, target, null);
}
else
{
File.Move(source, target);
}
return true;
}
catch
Expand Down
Loading

0 comments on commit 7d0bf61

Please sign in to comment.