I've written something similar once, as an example for a C# course. In my opinion it mainly demonstrates how awful the .NET configuration subsystem is, although the code does work. I've not adapted it to your settings, as it's fairly easy to introduce a mistake and so far the SO editor does not validate posted code samples ;)
First, the configuration section declaration:
<configSections>
<section name="passwordSafe"
type="Codeworks.PasswordSafe.Model.Configuration.PasswordSafeSection, Codeworks.PasswordSafe.Model, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
</configSections>
<passwordSafe hashAlgorithm="SHA256">
<users>
<user name="mm" password="Jok2eyBcFs4y7UIAlCuLix4mLfxw2byfvHfElpmk8d8=" />
<user name="joe" password="Jok2eyBcFs4y7UIAlCuLix4mLfxw2byfvHfElpmk8d8=" />
</users>
</passwordSafe>
To match the above snippet we first need the configuration section:
public class PasswordSafeSection : ConfigurationSection
{
#region Static Accessors
/// <summary>
/// Gets the configuration section using the default element name.
/// </summary>
public static PasswordSafeSection GetSection()
{
return GetSection( "passwordSafe" );
}
/// <summary>
/// Gets the configuration section using the specified element name.
/// </summary>
public static PasswordSafeSection GetSection( string sectionName )
{
PasswordSafeSection section = ConfigurationManager.GetSection( sectionName ) as PasswordSafeSection;
if( section == null )
{
string message = string.Format( "The specified configuration section (<{0}>) was not found.", sectionName );
throw new ConfigurationErrorsException( message );
}
return section;
}
#endregion
#region Configuration Properties
[ConfigurationProperty( "hashAlgorithm" )]
public string HashAlgorithm
{
get { return (string) this[ "hashAlgorithm" ]; }
set { this[ "hashAlgorithm" ] = value; }
}
[ConfigurationProperty( "users", IsDefaultCollection=true )]
public UserElementCollection Users
{
get { return (UserElementCollection) this[ "users" ]; }
set { this[ "users" ] = value; }
}
public override bool IsReadOnly()
{
return false;
}
#endregion
}
We are using a custom element collection, so let's declare that too:
[ConfigurationCollection( typeof(UserElement), CollectionType = ConfigurationElementCollectionType.BasicMap )]
public class UserElementCollection : ConfigurationElementCollection
{
protected override ConfigurationElement CreateNewElement()
{
return new UserElement();
}
protected override string ElementName
{
get { return "user"; }
}
public override ConfigurationElementCollectionType CollectionType
{
get { return ConfigurationElementCollectionType.BasicMap; }
}
public override bool IsReadOnly()
{
return false;
}
#region Indexers
public UserElement this[ int index ]
{
get { return BaseGet( index ) as UserElement; }
set
{
if( BaseGet( index ) != null )
{
BaseRemoveAt( index );
}
BaseAdd( index, value );
}
}
public new UserElement this[ string name ]
{
get { return BaseGet( name ) as UserElement; }
}
#endregion
#region Lookup Methods
protected override object GetElementKey( ConfigurationElement element )
{
UserElement user = element as UserElement;
return user != null ? user.UserName : "error";
}
public string GetKey( int index )
{
return (string) BaseGetKey( index );
}
#endregion
#region Add/Remove/Clear Methods
public void Add( UserElement item )
{
BaseAdd( item );
}
public void Remove( string name )
{
BaseRemove( name );
}
public void Remove( UserElement item )
{
BaseRemove( GetElementKey( item ) );
}
public void RemoveAt( int index )
{
BaseRemoveAt( index );
}
public void Clear()
{
BaseClear();
}
#endregion
}
And finally we need to declare the custom element used in the element collection:
public class UserElement : ConfigurationElement
{
#region Constructors
public UserElement()
{
}
public UserElement( string userName, string passwordHash )
{
UserName = userName;
PasswordHash = passwordHash;
}
#endregion
#region Configuration Properties
[ConfigurationProperty( "name", IsKey = true )]
public string UserName
{
get { return (string) this[ "name" ]; }
set { this[ "name" ] = value; }
}
[ConfigurationProperty( "password", IsRequired = true )]
public string PasswordHash
{
get { return (string) this[ "password" ]; }
set { this[ "password" ] = value; }
}
public override bool IsReadOnly()
{
return false;
}
#endregion
}
Now, having all this in place we're ready to access the configuration file. I'm using a Configurator helper class to make this slightly less cumbersome:
public static class Configurator
{
#region AppSettings Helpers
public static int SplashScreenDisplayTime
{
get { return Convert.ToInt32( ConfigurationManager.AppSettings[ "splash.display.msecs" ] ); }
}
#endregion
#region User Helpers
public static bool TryGetUserPasswordHash( string userName, out string passwordHash )
{
UserElement user = GetUser( userName );
passwordHash = user != null ? user.PasswordHash : null;
return ! string.IsNullOrEmpty( passwordHash );
}
private static UserElement GetUser( string userName )
{
SystemConfiguration config = GetConfiguration( ConfigurationUserLevel.PerUserRoamingAndLocal );
PasswordSafeSection section = config.Sections[ "passwordSafe" ] as PasswordSafeSection;
return section.Users[ userName ];
}
public static void AddUser( string userName, string passwordHash, string encryptionKey )
{
SystemConfiguration config = GetConfiguration( ConfigurationUserLevel.PerUserRoamingAndLocal );
PasswordSafeSection section = config.Sections[ "passwordSafe" ] as PasswordSafeSection;
UserElement user = section.Users[ userName ];
if( user == null )
{
user = new UserElement( userName, passwordHash, encryptionKey );
section.Users.Add( user );
config.Save( ConfigurationSaveMode.Modified );
}
}
public static void RemoveUser( string userName )
{
SystemConfiguration config = GetConfiguration( ConfigurationUserLevel.PerUserRoamingAndLocal );
PasswordSafeSection section = config.Sections[ "passwordSafe" ] as PasswordSafeSection;
section.Users.Remove( userName );
config.Save( ConfigurationSaveMode.Modified );
}
public static void UpdateUser( string userName, string passwordHash )
{
SystemConfiguration config = GetConfiguration( ConfigurationUserLevel.PerUserRoamingAndLocal );
PasswordSafeSection section = config.Sections[ "passwordSafe" ] as PasswordSafeSection;
UserElement user = section.Users[ userName ];
if( user != null )
{
user.PasswordHash = passwordHash;
config.Save( ConfigurationSaveMode.Modified );
}
}
#endregion
#region Configuration Helpers
private static SystemConfiguration GetConfiguration( ConfigurationUserLevel userLevel )
{
SystemConfiguration config = InitializeConfiguration( userLevel );
return config;
}
private static SystemConfiguration InitializeConfiguration( ConfigurationUserLevel userLevel )
{
SystemConfiguration config = ConfigurationManager.OpenExeConfiguration( userLevel );
PasswordSafeSection section = config.Sections[ "passwordSafe" ] as PasswordSafeSection;
if( section == null )
{
section = new PasswordSafeSection();
section.SectionInformation.AllowExeDefinition = ConfigurationAllowExeDefinition.MachineToLocalUser;
section.SectionInformation.ForceSave = true;
config.Sections.Add( "passwordSafe", section );
config.Save( ConfigurationSaveMode.Full );
}
return config;
}
#endregion
}
Hope this helps.