CrispyWaffle.Configuration
CrispyWaffle.Configuration holds the connection settings that the connector packages (Redis, CouchDB, Elasticsearch, RabbitMQ, SMTP) take. A Connection stores a host, a port and a set of credentials. When it is serialized, the password is encrypted, so you can keep connections in configuration files without writing passwords in plain text.
Use this package whenever you create a connector from one of the other CrispyWaffle packages. You can also use it for your own connection settings.
| Package | CrispyWaffle.Configuration |
| Namespace | CrispyWaffle.Configuration |
| Install | dotnet add package CrispyWaffle.Configuration |
| Depends on | CrispyWaffle |
How it works
Connection implements IConnection, and its Credentials property holds an IConnectionCredential. The default constructor sets Credentials to a new Credentials instance, so you can use object-initializer syntax such as Credentials = { Username = "...", Password = "..." } without creating the credentials object yourself.
Connectors read Host, Port, Credentials.Username and Credentials.Password from the connection in their constructors. They don't keep the connection after that, so changing a Connection after a connector has been created has no effect on that connector.
Password encryption
Credentials has two password properties:
Passwordholds the plain-text value. It has[XmlIgnore]and bothJsonIgnoreattributes (Newtonsoft and System.Text.Json), so serializers never write it.PasswordInternalis the value that gets serialized, under the namePassword(XML element, NewtonsoftJsonPropertyand System.Text.JsonJsonPropertyName).
When PasswordInternal is read, the plain-text password is encrypted with Security.Encrypt and a 32-character MD5 hash of the encrypted text is appended. When the password is empty or whitespace, the getter returns an empty string.
When PasswordInternal is assigned, the setter does the reverse:
- If the last 32 characters match the MD5 hash of the rest of the value, the rest is decrypted and stored as the plain-text password.
- If they don't match, or the value is 32 characters or shorter, or decryption throws, the value is stored as the plain-text password.
Because of this fallback, a configuration file can hold either an encrypted password or a plain-text one.
The encryption keys come from an ISecureCredentialProvider that Credentials resolves through ServiceLocator.Resolve<ISecureCredentialProvider>() every time PasswordInternal is read or assigned. Register one before you serialize or deserialize credentials that have a password. The same keys must be used on both sides.
Connection has the [Serializer] attribute, which uses XML as the default format. The Name property is written as an XML attribute, and the credentials are written through the hidden CredentialsInternal property as a Credentials element.
Getting started
using CrispyWaffle.Composition;
using CrispyWaffle.Configuration;
// Register the keys used to encrypt and decrypt serialized passwords.
ServiceLocator.Register<ISecureCredentialProvider>(
() =>
new SecureCredentialProvider
{
PasswordHash = "Cr1$PTVV@FE13",
SaltKey = "y48H85nH21",
IVKey = "HZEM7|Ne2YGS/F41",
},
Lifetime.Singleton
);
// Describe a connection. Credentials is created by the constructor.
var connection = new Connection
{
Name = "Redis",
Host = "localhost",
Port = 6379,
Credentials = { Username = "default", Password = "myP@ssw0rd" },
};
Pass the connection to a connector constructor, for example new RedisConnector(connection, serializer). See CrispyWaffle.Redis, CrispyWaffle.CouchDB and CrispyWaffle.ElasticSearch.
Reference
Connection
public sealed class Connection : IConnection is the default implementation of IConnection, with XML serialization support.
| Member | Description |
|---|---|
Connection() |
Constructor. Sets Credentials to a new Credentials instance. |
Name |
Connection name or identifier. Serialized as the XML attribute Name. |
Host |
Host name or address. Some connectors expect the URL scheme here (see the CouchDB page). |
Port |
Port number. |
Credentials |
The IConnectionCredential used to authenticate. Has [XmlIgnore]. |
CredentialsInternal |
Infrastructure property, hidden from IntelliSense. Casts Credentials to Credentials so the XML serializer can write it as the Credentials element. Reading it throws InvalidCastException if Credentials holds a different IConnectionCredential implementation. Don't use it directly. |
IConnection
public interface IConnection is the contract that every connector constructor accepts.
| Member | Description |
|---|---|
Name |
Connection name or identifier. |
Host |
Host name or address. |
Port |
Port number. |
Credentials |
Credentials used to authenticate. |
IConnectionCredential
public interface IConnectionCredential is a username and password pair.
| Member | Description |
|---|---|
Username |
User name. |
Password |
Plain-text password. |
ISecureCredentialProvider
public interface ISecureCredentialProvider supplies the keys that Credentials uses to encrypt and decrypt serialized passwords. Credentials resolves it from ServiceLocator.
| Member | Description |
|---|---|
PasswordHash |
Password used to derive the encryption key. |
SaltKey |
Salt used to derive the encryption key. |
IVKey |
Initialization vector. |
Credentials
public sealed class Credentials : IConnectionCredential, INotifyPropertyChanged is the default credential implementation. It encrypts the password when serialized.
| Member | Description |
|---|---|
Username |
User name. Raises PropertyChanged when set. |
Password |
Plain-text password. Never serialized. Raises PropertyChanged when set. |
PasswordInternal |
Infrastructure property, hidden from IntelliSense, serialized as Password. The getter returns the encrypted password followed by its MD5 hash, or an empty string when there is no password. The setter decrypts a value in that format, or stores any other value as plain text. Both resolve ISecureCredentialProvider from ServiceLocator when there is a value to process. Raises PropertyChanged with the name PasswordInternal. |
PropertyChanged |
Event raised when Username, Password or PasswordInternal is set. |
SecureCredentialProvider
public class SecureCredentialProvider : ISecureCredentialProvider is a plain implementation of ISecureCredentialProvider with settable properties.
| Member | Description |
|---|---|
PasswordHash |
Password used to derive the encryption key. |
SaltKey |
Salt used to derive the encryption key. |
IVKey |
Initialization vector. |
ConnectionNameAttribute
public sealed class ConnectionNameAttribute : Attribute is a marker that records which named connection a connector class expects. You can apply it to a class more than once.
The connectors in the CrispyWaffle packages use it to declare their expected names: "Redis", "RedisMaster" and "RedisSlave", "CouchDB", "ElasticSearch", "RabbitMQ" and "SMTP". No code in the CrispyWaffle packages reads the attribute. It's metadata for your own configuration-loading code.
| Member | Description |
|---|---|
ConnectionNameAttribute(string name) |
Constructor. Sets Name. |
Name |
The connection name. |
Order |
Ordering hint for when a class needs more than one connection, such as MasterSlaveConfiguration. Defaults to 0. |
Examples
Round-trip credentials through JSON
The serialized output contains the encrypted password under Password. Assigning that value to PasswordInternal restores the plain-text password.
using CrispyWaffle.Configuration;
using CrispyWaffle.Serialization;
using CrispyWaffle.Utilities;
// Requires an ISecureCredentialProvider registered in ServiceLocator.
var credential = new Credentials { Password = "EchoFoxPapa" };
var json = (string)credential.GetCustomSerializer(SerializerFormat.Json);
dynamic deserialized = SerializerFactory
.GetCustomSerializer<DynamicSerialization>(SerializerFormat.Json)
.Deserialize(json);
var restored = new Credentials { PasswordInternal = (string)deserialized.Password };
// restored.Password == "EchoFoxPapa"
Mark a custom connector with its connection name
using CrispyWaffle.Configuration;
[ConnectionName("Billing")]
public sealed class BillingApiConnector
{
public BillingApiConnector(IConnection connection)
{
BaseUrl = $"https://{connection.Host}:{connection.Port}";
ApiUser = connection.Credentials.Username;
}
public string BaseUrl { get; }
public string ApiUser { get; }
}