sdk/eventhub/Microsoft.Azure.EventHubs/src/Primitives/EventHubsConnectionStringBuilder.cs (227 lines of code) (raw):
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace Microsoft.Azure.EventHubs
{
using System;
using System.Text;
using Microsoft.Azure.EventHubs.Primitives;
/// <summary>
/// Supported transport types
/// </summary>
public enum TransportType
{
/// <summary>
/// AMQP over the default TCP transport protocol
/// </summary>
Amqp,
/// <summary>
/// AMQP over the Web Sockets transport protocol
/// </summary>
AmqpWebSockets
}
/// <summary>
/// EventHubsConnectionStringBuilder can be used to construct a connection string which can establish communication with Event Hubs entities.
/// It can also be used to perform basic validation on an existing connection string.
/// <para/>
/// A connection string is basically a string consisted of key-value pair separated by ";".
/// Basic format is "<key>=<value>[;<key>=<value>]" where supported key name are as follow:
/// <para/> Endpoint - the URL that contains the Event Hubs namespace
/// <para/> EntityPath - the path to the Event Hub entity
/// <para/> SharedAccessKeyName - the key name to the corresponding shared access policy rule for the namespace, or entity.
/// <para/> SharedAccessKey - the key for the corresponding shared access policy rule of the namespace or entity.
/// </summary>
/// <example>
/// Sample code:
/// <code>
/// var connectionStringBuiler = new EventHubsConnectionStringBuilder(
/// "amqps://EventHubsNamespaceName.servicebus.windows.net",
/// "EventHubsEntityName", // Event Hub Name
/// "SharedAccessSignatureKeyName",
/// "SharedAccessSignatureKey");
/// string connectionString = connectionStringBuilder.ToString();
/// </code>
/// </example>
public class EventHubsConnectionStringBuilder
{
const char KeyValueSeparator = '=';
const char KeyValuePairDelimiter = ';';
static readonly string EndpointScheme = "amqps";
static readonly string EndpointConfigName = "Endpoint";
static readonly string SharedAccessKeyNameConfigName = "SharedAccessKeyName";
static readonly string SharedAccessKeyConfigName = "SharedAccessKey";
static readonly string EntityPathConfigName = "EntityPath";
static readonly string OperationTimeoutConfigName = "OperationTimeout";
static readonly string TransportTypeConfigName = "TransportType";
static readonly string SharedAccessSignatureConfigName = "SharedAccessSignature";
static readonly string AuthenticationConfigName = "Authentication";
/// <summary>
/// Build a connection string consumable by <see cref="EventHubClient.CreateFromConnectionString(string)"/>
/// </summary>
/// <param name="endpointAddress">Fully qualified domain name for Event Hubs. Most likely, {yournamespace}.servicebus.windows.net</param>
/// <param name="entityPath">Entity path or Event Hub name.</param>
/// <param name="sharedAccessKeyName">Shared Access Key name</param>
/// <param name="sharedAccessKey">Shared Access Key</param>
public EventHubsConnectionStringBuilder(
Uri endpointAddress,
string entityPath,
string sharedAccessKeyName,
string sharedAccessKey)
: this (endpointAddress, entityPath, sharedAccessKeyName, sharedAccessKey, ClientConstants.DefaultOperationTimeout)
{
}
/// <summary>
/// Build a connection string consumable by <see cref="EventHubClient.CreateFromConnectionString(string)"/>
/// </summary>
/// <param name="endpointAddress">Fully qualified domain name for Event Hubs. Most likely, {yournamespace}.servicebus.windows.net</param>
/// <param name="entityPath">Entity path or Event Hub name.</param>
/// <param name="sharedAccessKeyName">Shared Access Key name</param>
/// <param name="sharedAccessKey">Shared Access Key</param>
/// <param name="operationTimeout">Operation timeout for Event Hubs operations</param>
public EventHubsConnectionStringBuilder(
Uri endpointAddress,
string entityPath,
string sharedAccessKeyName,
string sharedAccessKey,
TimeSpan operationTimeout)
: this(endpointAddress, entityPath, operationTimeout)
{
Guard.ArgumentNotNullOrWhiteSpace(nameof(sharedAccessKey), sharedAccessKey);
Guard.ArgumentNotNullOrWhiteSpace(nameof(sharedAccessKeyName), sharedAccessKeyName);
this.SasKey = sharedAccessKey;
this.SasKeyName = sharedAccessKeyName;
}
/// <summary>
/// Build a connection string consumable by <see cref="EventHubClient.CreateFromConnectionString(string)"/>
/// </summary>
/// <param name="endpointAddress">Fully qualified domain name for Event Hubs. Most likely, {yournamespace}.servicebus.windows.net</param>
/// <param name="entityPath">Entity path or Event Hub name.</param>
/// <param name="sharedAccessSignature">Shared Access Signature</param>
/// <param name="operationTimeout">Operation timeout for Event Hubs operations</param>
public EventHubsConnectionStringBuilder(
Uri endpointAddress,
string entityPath,
string sharedAccessSignature,
TimeSpan operationTimeout)
: this(endpointAddress, entityPath, operationTimeout)
{
Guard.ArgumentNotNullOrWhiteSpace(nameof(sharedAccessSignature), sharedAccessSignature);
this.SharedAccessSignature = sharedAccessSignature;
}
/// <summary>
/// ConnectionString format:
/// Endpoint=sb://namespace_DNS_Name;EntityPath=EVENT_HUB_NAME;SharedAccessKeyName=SHARED_ACCESS_KEY_NAME;SharedAccessKey=SHARED_ACCESS_KEY
/// </summary>
/// <param name="connectionString">Event Hubs ConnectionString</param>
public EventHubsConnectionStringBuilder(string connectionString)
{
Guard.ArgumentNotNullOrWhiteSpace(nameof(connectionString), connectionString);
// Assign default values.
this.OperationTimeout = ClientConstants.DefaultOperationTimeout;
this.TransportType = TransportType.Amqp;
// Parse the connection string now and override default values if any provided.
this.ParseConnectionString(connectionString);
}
internal EventHubsConnectionStringBuilder(
Uri endpointAddress,
string entityPath,
TimeSpan operationTimeout,
TransportType transportType = TransportType.Amqp)
{
Guard.ArgumentNotNull(nameof(endpointAddress), endpointAddress);
Guard.ArgumentNotNullOrWhiteSpace(nameof(entityPath), entityPath);
// Replace the scheme. We cannot really make sure that user passed an amps:// scheme to us.
var uriBuilder = new UriBuilder(endpointAddress.AbsoluteUri)
{
Scheme = EndpointScheme
};
this.Endpoint = uriBuilder.Uri;
this.EntityPath = entityPath;
this.OperationTimeout = operationTimeout;
this.TransportType = transportType;
}
/// <summary>
/// Gets or sets the Event Hubs endpoint.
/// </summary>
public Uri Endpoint { get; set; }
/// <summary>
/// Get the shared access policy key value from the connection string
/// </summary>
/// <value>Shared Access Signature key</value>
public string SasKey { get; set; }
/// <summary>
/// Get the shared access policy owner name from the connection string
/// </summary>
public string SasKeyName { get; set; }
/// <summary>
/// Gets or sets the SAS access token.
/// </summary>
/// <value>Shared Access Signature</value>
public string SharedAccessSignature { get; set; }
/// <summary>
/// Get the entity path value from the connection string
/// </summary>
public string EntityPath { get; set; }
/// <summary>
/// OperationTimeout is applied in erroneous situations to notify the caller about the relevant <see cref="EventHubsException"/>
/// </summary>
public TimeSpan OperationTimeout { get; set; }
/// <summary>
/// Transport type for the client connection.
/// Avaiable options are Amqp and AmqpWebSockets.
/// Defaults to Amqp if not specified.
/// </summary>
public TransportType TransportType { get; set; }
/// <summary>
/// Enables Azure Active Directory Managed Identity authentication when set to 'Managed Identity'
/// </summary>
public string Authentication { get; set; }
/// <summary>
/// Creates a cloned object of the current <see cref="EventHubsConnectionStringBuilder"/>.
/// </summary>
/// <returns>A new <see cref="EventHubsConnectionStringBuilder"/></returns>
public EventHubsConnectionStringBuilder Clone()
{
var clone = new EventHubsConnectionStringBuilder(this.ToString());
clone.OperationTimeout = this.OperationTimeout;
return clone;
}
/// <summary>
/// Returns an interoperable connection string that can be used to connect to Event Hubs Namespace
/// </summary>
/// <returns>the connection string</returns>
public override string ToString()
{
this.Validate();
var connectionStringBuilder = new StringBuilder();
if (this.Endpoint != null)
{
connectionStringBuilder.Append($"{EndpointConfigName}{KeyValueSeparator}{this.Endpoint}{KeyValuePairDelimiter}");
}
if (!string.IsNullOrWhiteSpace(this.EntityPath))
{
connectionStringBuilder.Append($"{EntityPathConfigName}{KeyValueSeparator}{this.EntityPath}{KeyValuePairDelimiter}");
}
if (!string.IsNullOrWhiteSpace(this.SasKeyName))
{
connectionStringBuilder.Append($"{SharedAccessKeyNameConfigName}{KeyValueSeparator}{this.SasKeyName}{KeyValuePairDelimiter}");
}
if (!string.IsNullOrWhiteSpace(this.SasKey))
{
connectionStringBuilder.Append($"{SharedAccessKeyConfigName}{KeyValueSeparator}{this.SasKey}{KeyValuePairDelimiter}");
}
if (!string.IsNullOrWhiteSpace(this.SharedAccessSignature))
{
connectionStringBuilder.Append($"{SharedAccessSignatureConfigName}{KeyValueSeparator}{this.SharedAccessSignature}{KeyValuePairDelimiter}");
}
if (this.OperationTimeout != ClientConstants.DefaultOperationTimeout)
{
connectionStringBuilder.Append($"{OperationTimeoutConfigName}{KeyValueSeparator}{this.OperationTimeout}{KeyValuePairDelimiter}");
}
if (this.TransportType != ClientConstants.DefaultTransportType)
{
connectionStringBuilder.Append($"{TransportTypeConfigName}{KeyValueSeparator}{this.TransportType}{KeyValuePairDelimiter}");
}
if (!string.IsNullOrWhiteSpace(this.Authentication))
{
connectionStringBuilder.Append($"{AuthenticationConfigName}{KeyValueSeparator}{this.Authentication}{KeyValuePairDelimiter}");
}
return connectionStringBuilder.ToString();
}
void Validate()
{
if (this.Endpoint == null)
{
throw Fx.Exception.ArgumentNullOrWhiteSpace(EndpointConfigName);
}
// if one supplied sharedAccessKeyName, they need to supply sharedAccesssecret, and vise versa
// if SharedAccessSignature is specified, SasKey or SasKeyName should not be specified
var hasSasKeyName = !string.IsNullOrWhiteSpace(this.SasKeyName);
var hasSasKey = !string.IsNullOrWhiteSpace(this.SasKey);
var hasSharedAccessSignature = !string.IsNullOrWhiteSpace(this.SharedAccessSignature);
if (hasSharedAccessSignature)
{
if (hasSasKeyName)
{
throw Fx.Exception.Argument(
string.Format("{0},{1}", SharedAccessSignatureConfigName, SharedAccessKeyNameConfigName),
Resources.AuthKeyShouldBeAlone.FormatForUser(SharedAccessSignatureConfigName, SharedAccessKeyNameConfigName));
}
if (hasSasKey)
{
throw Fx.Exception.Argument(
string.Format("{0},{1}", SharedAccessSignatureConfigName, SharedAccessKeyConfigName),
Resources.AuthKeyShouldBeAlone.FormatForUser(SharedAccessSignatureConfigName, SharedAccessKeyConfigName));
}
}
if (hasSasKeyName && !hasSasKey || !hasSasKeyName && hasSasKey)
{
throw Fx.Exception.Argument(
string.Format("{0},{1}", SharedAccessKeyNameConfigName, SharedAccessKeyConfigName),
Resources.ArgumentInvalidCombination.FormatForUser(SharedAccessKeyNameConfigName, SharedAccessKeyConfigName));
}
var hasAuthentication = !string.IsNullOrWhiteSpace(this.Authentication);
if (hasAuthentication && hasSharedAccessSignature)
{
throw Fx.Exception.Argument(
string.Format("{0},{1}", AuthenticationConfigName, SharedAccessSignatureConfigName),
Resources.AuthKeyShouldBeAlone.FormatForUser(AuthenticationConfigName, SharedAccessSignatureConfigName));
}
if (hasAuthentication && hasSasKeyName)
{
throw Fx.Exception.Argument(
string.Format("{0},{1}", AuthenticationConfigName, SharedAccessKeyNameConfigName),
Resources.AuthKeyShouldBeAlone.FormatForUser(AuthenticationConfigName, SharedAccessKeyNameConfigName));
}
}
void ParseConnectionString(string connectionString)
{
// First split based on ';'
string[] keyValuePairs = connectionString.Split(new[] { KeyValuePairDelimiter }, StringSplitOptions.RemoveEmptyEntries);
foreach (var keyValuePair in keyValuePairs)
{
// Now split based on the _first_ '='
string[] keyAndValue = keyValuePair.Split(new[] { KeyValueSeparator }, 2);
string key = keyAndValue[0];
if (keyAndValue.Length != 2)
{
throw Fx.Exception.Argument(nameof(connectionString), $"Value for the connection string parameter name '{key}' was not found.");
}
string value = keyAndValue[1];
if (key.Equals(EndpointConfigName, StringComparison.OrdinalIgnoreCase))
{
this.Endpoint = new Uri(value);
}
else if (key.Equals(EntityPathConfigName, StringComparison.OrdinalIgnoreCase))
{
this.EntityPath = value;
}
else if (key.Equals(SharedAccessKeyNameConfigName, StringComparison.OrdinalIgnoreCase))
{
this.SasKeyName = value;
}
else if (key.Equals(SharedAccessKeyConfigName, StringComparison.OrdinalIgnoreCase))
{
this.SasKey = value;
}
else if (key.Equals(SharedAccessSignatureConfigName, StringComparison.OrdinalIgnoreCase))
{
this.SharedAccessSignature = value;
}
else if (key.Equals(OperationTimeoutConfigName, StringComparison.OrdinalIgnoreCase))
{
this.OperationTimeout = TimeSpan.Parse(value);
}
else if (key.Equals(TransportTypeConfigName, StringComparison.OrdinalIgnoreCase))
{
this.TransportType = (TransportType)Enum.Parse(typeof(TransportType), value);
}
else if (key.Equals(AuthenticationConfigName, StringComparison.OrdinalIgnoreCase))
{
this.Authentication = value;
}
else
{
throw Fx.Exception.Argument(nameof(connectionString), $"Illegal connection string parameter name '{key}'");
}
}
}
}
}