| | 1 | | // Ignore Spelling: Auth Thumbprint |
| | 2 | |
|
| | 3 | | using System.Data.Common; |
| | 4 | |
|
| | 5 | | namespace Configuration.Helper; |
| | 6 | |
|
| | 7 | | /// <summary> |
| | 8 | | /// Provides a simple way to create and manage the contents of connection |
| | 9 | | /// strings used for a Microsoft Dataverse connection.</summary> |
| | 10 | | /// <remarks> |
| | 11 | | /// See<a href="https://learn.microsoft.com/en-us/power-apps/developer/data-platform/xrm-tooling/use-connection-strings- |
| | 12 | | /// Use connection strings in XRM tooling to connect to Microsoft Dataverse</a> and |
| | 13 | | /// <a href="https://learn.microsoft.com/en-us/dynamics365/customerengagement/on-premises/developer/xrm-tooling/use-conn |
| | 14 | | /// Use connection strings in XRM tooling</a> for more information. |
| | 15 | | /// </remarks> |
| | 16 | | public class WebConnectionStringBuilder : DbConnectionStringBuilder |
| | 17 | | { |
| | 18 | | #region Key Aliases |
| | 19 | |
|
| | 20 | | private static class Alias |
| | 21 | | { |
| 1 | 22 | | internal static readonly string[] url = { @"Url", "ServiceUri", "Service Uri", "Server" }; |
| 1 | 23 | | internal static readonly string[] userName = { "UserName", "User Name", "UserId", " User Id" }; |
| 1 | 24 | | internal static readonly string[] password = { "Password" }; |
| 1 | 25 | | internal static readonly string[] homeRealmUri = { "HomeRealmUri", "Home Realm Uri" }; |
| 1 | 26 | | internal static readonly string[] authType = { "AuthType", "AuthenticationType" }; |
| 1 | 27 | | internal static readonly string[] requireNewInstance = { "RequireNewInstance" }; |
| 1 | 28 | | internal static readonly string[] clientId = { "ClientId", "AppId", "ApplicationId" }; |
| 1 | 29 | | internal static readonly string[] clientSecret = { "ClientSecret", "Secret" }; |
| 1 | 30 | | internal static readonly string[] redirectUri = { "RedirectUri", "ReplyUrl" }; |
| 1 | 31 | | internal static readonly string[] tokenCacheStorePath = { "TokenCacheStorePath" }; |
| 1 | 32 | | internal static readonly string[] loginPrompt = { "LoginPrompt" }; |
| 1 | 33 | | internal static readonly string[] storeName = { "StoreName", "CertificateStoreName" }; |
| 1 | 34 | | internal static readonly string[] thumbprint = { @"Thumbprint", "CertThumbprint" }; |
| 1 | 35 | | internal static readonly string[] skipDiscovery = { "SkipDiscovery" }; |
| 1 | 36 | | internal static readonly string[] integratedSecurity = { "Integrated Security" }; |
| 1 | 37 | | internal static readonly string[] domain = { "Domain" }; |
| | 38 | | } |
| | 39 | |
|
| | 40 | | #endregion |
| | 41 | |
|
| | 42 | | #region Authentication Types |
| | 43 | |
|
| | 44 | | /// <summary>Dynamics 365 Web service authentication types.</summary> |
| | 45 | | /// <remarks> |
| | 46 | | /// Only OAuth, Certificate, ClientSecret and Office365 are permitted values |
| | 47 | | /// for Dataverse environments.<br/> |
| | 48 | | /// See<a href="https://learn.microsoft.com/en-us/powerapps/developer/data-platform/authentication" target="_blank"> |
| | 49 | | /// Authenticate with Microsoft Dataverse web services</a> |
| | 50 | | /// for more information. |
| | 51 | | /// </remarks> |
| | 52 | | public enum AuthType |
| | 53 | | { |
| | 54 | | /// <summary>Open standard authorization protocol for access delegation.</summary> |
| | 55 | | OAuth, |
| | 56 | | /// <summary>Client secrets to enable server-to-server authentication scenarios.</summary> |
| | 57 | | ClientSecret, |
| | 58 | | /// <summary>Certificates to enable server-to-server authentication scenarios.</summary> |
| | 59 | | Certificate, |
| | 60 | | /// <summary>Use of the WS-Trust authentication security protocol is no longer recommended</summary> |
| | 61 | | Office365, |
| | 62 | | /// <summary>Dynamics 365 On-premises Active Directory authentication.</summary> |
| | 63 | | AD, |
| | 64 | | /// <summary>Dynamics 365 Internet-facing deployment authentication.</summary> |
| | 65 | | IFD |
| | 66 | | } |
| | 67 | |
|
| | 68 | | #endregion |
| | 69 | |
|
| | 70 | | #region Login Prompt Types |
| | 71 | |
|
| | 72 | | /// <summary>Dynamics 365 Web service login types.</summary> |
| | 73 | | /// <remarks>The item with a value of zero is the default.</remarks> |
| | 74 | | public enum LoginPromptType |
| | 75 | | { |
| | 76 | | /// <summary>Does not prompt the user to specify credentials.</summary> |
| | 77 | | Never = 0, |
| | 78 | | /// <summary>Always prompts the user to specify credentials.</summary> |
| | 79 | | Always, |
| | 80 | | /// <summary>Allows the user to select in the login control interface whether to display the prompt or not.</summary |
| | 81 | | Auto |
| | 82 | | } |
| | 83 | |
|
| | 84 | | #endregion |
| | 85 | |
|
| | 86 | | #region Properties |
| | 87 | |
|
| | 88 | | /// <summary> |
| | 89 | | /// Gets or sets the URL to the Dataverse environment. The URL can use |
| | 90 | | /// HTTP or HTTPS protocol, and the port is optional. |
| | 91 | | /// </summary> |
| | 92 | | public string Url |
| | 93 | | { |
| | 94 | | get |
| 12 | 95 | | { |
| | 96 | | const string lastChar = @"/"; |
| 12 | 97 | | string value = GetProperty( Alias.url ); |
| | 98 | | // Make sure the URL ends with a delimiting character |
| 12 | 99 | | if( value.Length > 0 & !value.EndsWith( lastChar ) & !value.EndsWith( '\\' ) ) |
| 6 | 100 | | { |
| 6 | 101 | | return value + lastChar; |
| | 102 | | } |
| 6 | 103 | | return value; |
| 12 | 104 | | } |
| 36 | 105 | | set { SetProperty( value, Alias.url ); } |
| | 106 | | } |
| | 107 | |
|
| | 108 | | /// <summary>Gets or sets the user name associated with the credentials.</summary> |
| | 109 | | public string UserName |
| | 110 | | { |
| 18 | 111 | | get { return GetProperty( Alias.userName ); } |
| 9 | 112 | | set { SetProperty( value, Alias.userName ); } |
| | 113 | | } |
| | 114 | |
|
| | 115 | | /// <summary>Gets or sets the password for the user name associated with the credentials.</summary> |
| | 116 | | public string Password |
| | 117 | | { |
| 18 | 118 | | get { return GetProperty( Alias.password ); } |
| 9 | 119 | | set { SetProperty( value, Alias.password ); } |
| | 120 | | } |
| | 121 | |
|
| | 122 | | /// <summary>Gets or sets the Home Realm Uniform Resource Identifier.</summary> |
| | 123 | | /// <remarks> |
| | 124 | | /// Set this property to a non-null value when a second AD FS instance is configured as an |
| | 125 | | /// identity provider to the AD FS instance that Dynamics 365 has been configured with |
| | 126 | | /// for claims authentication. The parameter value is the URI of the WS-Trust metadata |
| | 127 | | /// endpoint of the second AD FS instance. |
| | 128 | | /// </remarks> |
| | 129 | | public string HomeRealmUri |
| | 130 | | { |
| 18 | 131 | | get { return GetProperty( Alias.homeRealmUri ); } |
| 3 | 132 | | set { SetProperty( value, Alias.homeRealmUri ); } |
| | 133 | | } |
| | 134 | |
|
| | 135 | | /// <summary>Gets or sets the authentication type to connect to Dataverse environment.</summary> |
| | 136 | | public AuthType AuthenticationType |
| | 137 | | { |
| 18 | 138 | | get { return ConvertToAuthType( GetProperty( Alias.authType ) ); } |
| 18 | 139 | | set { SetProperty( value.ToString(), Alias.authType ); } |
| | 140 | | } |
| | 141 | |
|
| | 142 | | /// <summary> |
| | 143 | | /// Gets or sets whether to reuse an existing connection if recalled |
| | 144 | | /// while the connection is still active. |
| | 145 | | /// </summary> |
| | 146 | | /// <remarks>The default is false.</remarks> |
| | 147 | | public bool RequireNewInstance |
| | 148 | | { |
| 18 | 149 | | get { return ConvertToBool( GetProperty( Alias.requireNewInstance ) ); } |
| 3 | 150 | | set { SetProperty( value.ToString().ToLower(), Alias.requireNewInstance ); } |
| | 151 | | } |
| | 152 | |
|
| | 153 | | /// <summary> |
| | 154 | | /// Gets or sets the ClientID assigned when the registered application |
| | 155 | | /// in Azure Active Directory or Active Directory Federation Services (AD FS). |
| | 156 | | /// </summary> |
| | 157 | | public string ClientId |
| | 158 | | { |
| 18 | 159 | | get { return GetProperty( Alias.clientId ); } |
| 24 | 160 | | set { SetProperty( value, Alias.clientId ); } |
| | 161 | | } |
| | 162 | |
|
| | 163 | | /// <summary>Gets or sets the secret when authentication type is set to ClientSecret.</summary> |
| | 164 | | public string ClientSecret |
| | 165 | | { |
| 18 | 166 | | get { return GetProperty( Alias.clientSecret ); } |
| 3 | 167 | | set { SetProperty( value, Alias.clientSecret ); } |
| | 168 | | } |
| | 169 | |
|
| | 170 | | /// <summary> |
| | 171 | | /// Gets or sets the redirect URI of the application registered in Azure |
| | 172 | | /// Active Directory or Active Directory Federation Services (AD FS). |
| | 173 | | /// </summary> |
| | 174 | | public string RedirectUri |
| | 175 | | { |
| 18 | 176 | | get { return GetProperty( Alias.redirectUri ); } |
| 3 | 177 | | set { SetProperty( value, Alias.redirectUri ); } |
| | 178 | | } |
| | 179 | |
|
| | 180 | | /// <summary> |
| | 181 | | /// Gets or sets the full path to the location where the user token cache should be stored. |
| | 182 | | /// </summary> |
| | 183 | | /// <remarks> |
| | 184 | | /// Required only with a web service configured for OAuth authentication. |
| | 185 | | /// </remarks> |
| | 186 | | public string TokenCacheStorePath |
| | 187 | | { |
| 18 | 188 | | get { return GetProperty( Alias.tokenCacheStorePath ); } |
| 3 | 189 | | set { SetProperty( value, Alias.tokenCacheStorePath ); } |
| | 190 | | } |
| | 191 | |
|
| | 192 | | /// <summary> |
| | 193 | | /// Gets or sets whether the user is prompted for credentials if the credentials are not supplied. |
| | 194 | | /// </summary> |
| | 195 | | /// <remarks> |
| | 196 | | /// Required only with a web service configured for OAuth authentication. |
| | 197 | | /// </remarks> |
| | 198 | | public LoginPromptType LoginPrompt |
| | 199 | | { |
| 18 | 200 | | get { return ConvertToLoginPromptType( GetProperty( Alias.loginPrompt ) ); } |
| 3 | 201 | | set { SetProperty( value.ToString(), Alias.loginPrompt ); } |
| | 202 | | } |
| | 203 | |
|
| | 204 | | /// <summary> |
| | 205 | | /// Gets or sets the store name where the certificate identified by |
| | 206 | | /// thumb-print can be found. When set, Thumb-print is required. |
| | 207 | | /// </summary> |
| | 208 | | public string StoreName |
| | 209 | | { |
| 18 | 210 | | get { return GetProperty( Alias.storeName ); } |
| 3 | 211 | | set { SetProperty( value, Alias.storeName ); } |
| | 212 | | } |
| | 213 | |
|
| | 214 | | /// <summary> |
| | 215 | | /// Gets or sets the thumb-print of the certificate to be utilized during |
| | 216 | | /// an S2S connection. When set, AppID is required and UserID and Password |
| | 217 | | /// values are ignored. |
| | 218 | | /// </summary> |
| | 219 | | public string Thumbprint |
| | 220 | | { |
| 18 | 221 | | get { return GetProperty( Alias.thumbprint ); } |
| 3 | 222 | | set { SetProperty( value, Alias.thumbprint ); } |
| | 223 | | } |
| | 224 | |
|
| | 225 | | /// <summary> |
| | 226 | | /// Gets or sets whether to call instance discovery to determine the connection |
| | 227 | | /// URI for a given instance. |
| | 228 | | /// As of NuGet release Microsoft.CrmSdk.XrmTooling.CoreAssembly Version 9.0.2.7 |
| | 229 | | /// </summary> |
| | 230 | | public bool SkipDiscovery |
| | 231 | | { |
| 18 | 232 | | get { return ConvertToBool( GetProperty( Alias.skipDiscovery ) ); } |
| 3 | 233 | | set { SetProperty( value.ToString().ToLower(), Alias.skipDiscovery ); } |
| | 234 | | } |
| | 235 | |
|
| | 236 | | /// <summary> |
| | 237 | | /// Gets or sets whether to use current windows credentials to attempt to create |
| | 238 | | /// a token for the instances. |
| | 239 | | /// As of NuGet release Microsoft.CrmSdk.XrmTooling.CoreAssembly Version 9.1.0.21 |
| | 240 | | /// </summary> |
| | 241 | | public bool IntegratedSecurity |
| | 242 | | { |
| 18 | 243 | | get { return ConvertToBool( GetProperty( Alias.integratedSecurity ) ); } |
| 3 | 244 | | set { SetProperty( value.ToString().ToLower(), Alias.integratedSecurity ); } |
| | 245 | | } |
| | 246 | |
|
| | 247 | | /// <summary>Gets or sets the domain that will verify user credentials.</summary> |
| | 248 | | public string Domain |
| | 249 | | { |
| 18 | 250 | | get { return GetProperty( GetProperty( Alias.domain ) ); } |
| 3 | 251 | | set { SetProperty( value, Alias.domain ); } |
| | 252 | | } |
| | 253 | |
|
| | 254 | | #endregion |
| | 255 | |
|
| | 256 | | #region Constructors |
| | 257 | |
|
| | 258 | | /// <summary>Initializes a new instance of the WebConnectionStringBuilder class.</summary> |
| 6 | 259 | | public WebConnectionStringBuilder() |
| 12 | 260 | | { } |
| | 261 | |
|
| | 262 | | /// <summary> |
| | 263 | | /// Initializes a new instance of the WebConnectionStringBuilder class using a supplied connection string. |
| | 264 | | /// </summary> |
| | 265 | | /// <param name="connectionString">The basis for the object's internal connection information. |
| | 266 | | /// Parsed into name/value pairs.</param> |
| 6 | 267 | | public WebConnectionStringBuilder( string connectionString ) |
| 6 | 268 | | { |
| 6 | 269 | | if( !string.IsNullOrWhiteSpace( connectionString ) ) |
| 6 | 270 | | { |
| 6 | 271 | | ConnectionString = connectionString.Trim(); |
| 6 | 272 | | } |
| 6 | 273 | | } |
| | 274 | |
|
| | 275 | | #endregion |
| | 276 | |
|
| | 277 | | #region Private Methods |
| | 278 | |
|
| | 279 | | /// <summary>Get the value for a property key.</summary> |
| | 280 | | /// <param name="keyAliases">Collection of keywords.</param> |
| | 281 | | /// <returns>An empty string is returned if the key is not found.</returns> |
| | 282 | | private string GetProperty( params string[] keyAliases ) |
| 108 | 283 | | { |
| 660 | 284 | | foreach( string alias in keyAliases ) |
| 174 | 285 | | { |
| 174 | 286 | | if( TryGetValue( alias, out object? value ) ) |
| 60 | 287 | | { string? wrk = value.ToString(); if( wrk is not null ) { return wrk.Trim(); } } |
| 162 | 288 | | } |
| | 289 | |
|
| 96 | 290 | | return string.Empty; |
| 108 | 291 | | } |
| | 292 | |
|
| | 293 | | /// <summary>Sets the value for a property key.</summary> |
| | 294 | | /// <param name="val">Value to set.</param> |
| | 295 | | /// <param name="keyAliases">Collection of keywords.</param> |
| | 296 | | private void SetProperty( string val, params string[] keyAliases ) |
| 43 | 297 | | { |
| 43 | 298 | | val = val.Trim(); |
| | 299 | |
|
| | 300 | | // If value is empty string then remove the parameter |
| 43 | 301 | | if( val.Length == 0 ) |
| 6 | 302 | | { |
| 6 | 303 | | RemoveValue( keyAliases ); |
| 6 | 304 | | } |
| | 305 | | else |
| 37 | 306 | | { |
| | 307 | | // Set the new value |
| 37 | 308 | | SetValue( val, keyAliases ); |
| 37 | 309 | | } |
| 43 | 310 | | } |
| | 311 | |
|
| | 312 | | /// <summary>Removes a parameter from the connection string.</summary> |
| | 313 | | /// <param name="keyAliases">Collection of keyword aliases.</param> |
| | 314 | | /// <returns> |
| | 315 | | /// The value of the key before it is removed is returned. |
| | 316 | | /// If the key is not found an empty string is returned. |
| | 317 | | /// </returns> |
| | 318 | | private string RemoveValue( params string[] keyAliases ) |
| 6 | 319 | | { |
| 6 | 320 | | string? retValue = null; |
| 54 | 321 | | foreach( string alias in keyAliases ) |
| 18 | 322 | | { |
| 18 | 323 | | if( TryGetValue( alias, out object? value ) ) |
| 6 | 324 | | { |
| 6 | 325 | | if( null == retValue ) |
| 6 | 326 | | { |
| 6 | 327 | | retValue = value.ToString(); |
| 6 | 328 | | } |
| 6 | 329 | | Remove( alias ); |
| 6 | 330 | | } |
| 18 | 331 | | } |
| | 332 | |
|
| 6 | 333 | | return retValue ?? string.Empty; |
| 6 | 334 | | } |
| | 335 | |
|
| | 336 | | /// <summary>Set the value for a parameter key.</summary> |
| | 337 | | /// <param name="newValue">New value</param> |
| | 338 | | /// <param name="keyAliases">Collection of keywords.</param> |
| | 339 | | private void SetValue( string newValue, params string[] keyAliases ) |
| 37 | 340 | | { |
| 263 | 341 | | foreach( string alias in keyAliases ) |
| 79 | 342 | | { |
| 97 | 343 | | if( TryGetValue( alias, out _ ) ) { this[alias] = newValue; return; } |
| 73 | 344 | | } |
| | 345 | |
|
| 31 | 346 | | this[keyAliases[0]] = newValue; |
| 37 | 347 | | } |
| | 348 | |
|
| | 349 | | /// <summary>Converts a string to a boolean.</summary> |
| | 350 | | /// <param name="valBool">Boolean string.</param> |
| | 351 | | /// <param name="dftValue">Default value. False is assumed if a value is not provided.</param> |
| | 352 | | /// <returns>The default is returned if the string is not a valid boolean.</returns> |
| | 353 | | private static bool ConvertToBool( string valBool, bool dftValue = false ) |
| 18 | 354 | | { |
| 18 | 355 | | return bool.TryParse( valBool, out bool retValue ) ? retValue : dftValue; |
| 18 | 356 | | } |
| | 357 | |
|
| | 358 | | /// <summary>Converts a string to a Dynamics 365 Web service authentication type.</summary> |
| | 359 | | /// <param name="authType">Dynamics 365 Web service authentication type string.</param> |
| | 360 | | /// <returns>The default value of AD is returned if the string is not an authentication type.</returns> |
| | 361 | | private static AuthType ConvertToAuthType( string authType ) |
| 6 | 362 | | { |
| 6 | 363 | | _ = Enum.TryParse( authType.Trim(), out AuthType retValue ); |
| 6 | 364 | | return retValue; |
| 6 | 365 | | } |
| | 366 | |
|
| | 367 | | /// <summary>Converts a string to a Dynamics 365 Web service login type.</summary> |
| | 368 | | /// <param name="loginType">Dynamics 365 Web login type string.</param> |
| | 369 | | /// <returns>The default value of Never is returned if the string is not a login type.</returns> |
| | 370 | | private static LoginPromptType ConvertToLoginPromptType( string loginType ) |
| 6 | 371 | | { |
| 6 | 372 | | _ = Enum.TryParse( loginType.Trim(), out LoginPromptType retValue ); |
| 6 | 373 | | return retValue; |
| 6 | 374 | | } |
| | 375 | |
|
| | 376 | | #endregion |
| | 377 | | } |