From 920182b99120dfe3ad60d9c81fc22d5ae472aed2 Mon Sep 17 00:00:00 2001 From: Release-Agent <> Date: Mon, 8 Jan 2024 21:27:47 +0000 Subject: [PATCH] '' --- .../Client/Auth/AuthProcessor.cs | 6 +- .../Client/Auth/OnPremises_Auth.cs | 14 ++-- .../TokenCache/FileBackedTokenCacheHints.cs | 13 +-- .../Client/ConnectionService.cs | 71 +++++++--------- .../Client/DataverseTelemetryBehaviors.cs | 4 +- .../Client/Model/ConfigurationOptions.cs | 6 +- .../DataverseClient/Client/ServiceClient.cs | 46 +++++++---- .../DataverseConnectionStringProcessor.cs | 3 +- .../DataverseClient/Client/Utils/Utils.cs | 15 ++++ .../ConnectControl/AdvancedOptions.xaml | 37 ++++++++- .../ConnectControl/ConnectionManager.cs | 82 +++++++++++-------- .../ConnectControl/ServerLoginControl.xaml | 2 +- .../ConnectControl/ServerLoginControl.xaml.cs | 22 ++++- .../ServiceClientTests.cs | 61 +++++++++++++- .../CdsClient_Core_Tests/appsettings.json | 2 +- ...Platform.Dataverse.Client.ReleaseNotes.txt | 8 ++ 16 files changed, 265 insertions(+), 127 deletions(-) diff --git a/src/GeneralTools/DataverseClient/Client/Auth/AuthProcessor.cs b/src/GeneralTools/DataverseClient/Client/Auth/AuthProcessor.cs index 499aec6..8c44f61 100644 --- a/src/GeneralTools/DataverseClient/Client/Auth/AuthProcessor.cs +++ b/src/GeneralTools/DataverseClient/Client/Auth/AuthProcessor.cs @@ -327,15 +327,13 @@ internal async static Task ObtainAccessTokenAsync( case PromptBehavior.Auto: break; case PromptBehavior.Always: - userPrompt = Microsoft.Identity.Client.Prompt.ForceLogin; + case PromptBehavior.SelectAccount: + userPrompt = Microsoft.Identity.Client.Prompt.SelectAccount; break; case PromptBehavior.Never: case PromptBehavior.RefreshSession: userPrompt = Microsoft.Identity.Client.Prompt.NoPrompt; break; - case PromptBehavior.SelectAccount: - userPrompt = Microsoft.Identity.Client.Prompt.SelectAccount; - break; default: break; } diff --git a/src/GeneralTools/DataverseClient/Client/Auth/OnPremises_Auth.cs b/src/GeneralTools/DataverseClient/Client/Auth/OnPremises_Auth.cs index 69d8fda..d2f6847 100644 --- a/src/GeneralTools/DataverseClient/Client/Auth/OnPremises_Auth.cs +++ b/src/GeneralTools/DataverseClient/Client/Auth/OnPremises_Auth.cs @@ -35,9 +35,8 @@ internal static object CreateAndAuthenticateProxy(IServiceManagement servi DataverseTraceLogger logSink = null) { bool createdLogSource = false; - Stopwatch dtProxyCreate = new Stopwatch(); - dtProxyCreate.Start(); - Stopwatch dtConnectTimeCheck = new Stopwatch(); + Stopwatch dtProxyCreate = Stopwatch.StartNew(); + Stopwatch dtConnectTimeCheck = Stopwatch.StartNew(); try { if (logSink == null) @@ -54,7 +53,8 @@ internal static object CreateAndAuthenticateProxy(IServiceManagement servi logSink.Log(string.Format(CultureInfo.InvariantCulture, "{0} - attempting to connect to On-Premises Dataverse server @ {1}", LogString, ServiceUri.ToString()), TraceEventType.Verbose); // Create the Service configuration for that URL - dtConnectTimeCheck.Restart(); + dtConnectTimeCheck.Stop(); + dtConnectTimeCheck = Stopwatch.StartNew(); servicecfg = ServiceConfigurationFactoryAsync.CreateManagement(ServiceUri); dtConnectTimeCheck.Stop(); if (servicecfg == null) @@ -73,7 +73,8 @@ internal static object CreateAndAuthenticateProxy(IServiceManagement servi { // Connect via anything other then AD. // Setup for Auth Check Performance. - dtConnectTimeCheck.Restart(); + dtConnectTimeCheck.Stop(); + dtConnectTimeCheck = Stopwatch.StartNew(); // Deal with IFD QurikyNess in ADFS configuration, where ADFS can be configured to fall though to Kerb Auth. AuthenticationCredentials authCred = ClaimsIFDFailOverAuth(servicecfg, homeRealm, userCredentials); @@ -89,7 +90,8 @@ internal static object CreateAndAuthenticateProxy(IServiceManagement servi { logSink.Log(string.Format(CultureInfo.InvariantCulture, "{0} - Initial Authenticated via {1} {3} . Auth Elapsed:{2}", LogString, servicecfg.AuthenticationType, dtConnectTimeCheck.Elapsed.ToString(), homeRealm.ToString()), TraceEventType.Verbose); logSink.Log(string.Format(CultureInfo.InvariantCulture, "{0} - Relaying Auth to Resource Server: From {1} to {2}", LogString, homeRealm.ToString(), servicecfg.PolicyConfiguration.SecureTokenServiceIdentifier), TraceEventType.Verbose); - dtConnectTimeCheck.Restart(); + dtConnectTimeCheck.Stop(); + dtConnectTimeCheck = Stopwatch.StartNew(); // Auth token against the correct server. AuthenticationCredentials authCred2 = servicecfg.Authenticate(new AuthenticationCredentials() { diff --git a/src/GeneralTools/DataverseClient/Client/Auth/TokenCache/FileBackedTokenCacheHints.cs b/src/GeneralTools/DataverseClient/Client/Auth/TokenCache/FileBackedTokenCacheHints.cs index 674499c..106f284 100644 --- a/src/GeneralTools/DataverseClient/Client/Auth/TokenCache/FileBackedTokenCacheHints.cs +++ b/src/GeneralTools/DataverseClient/Client/Auth/TokenCache/FileBackedTokenCacheHints.cs @@ -36,17 +36,10 @@ public FileBackedTokenCacheHints(string tokenPathAndFileName) string hostName = "DvBaseClient"; if (AppDomain.CurrentDomain != null) { - hostName = Path.GetFileNameWithoutExtension(AppDomain.CurrentDomain.FriendlyName); - if (hostName.IndexOfAny(Path.GetInvalidFileNameChars()) < 0) - { - foreach (var c in Path.GetInvalidFileNameChars()) - { - hostName = hostName.Replace(c, '_'); - } - } + hostName = Path.GetFileNameWithoutExtension(Utilities.CleanUpPotentialFileName(AppDomain.CurrentDomain.FriendlyName)); } string hostVersion = Environs.XrmSdkFileVersion; - string companyName = typeof(OrganizationDetail).Assembly.GetCustomAttribute().Company; + string companyName = Utilities.CleanUpPotentialFileName(typeof(OrganizationDetail).Assembly.GetCustomAttribute().Company); if (string.IsNullOrEmpty(tokenPathAndFileName)) @@ -54,7 +47,7 @@ public FileBackedTokenCacheHints(string tokenPathAndFileName) tokenPathAndFileName = Path.Combine(MsalCacheHelper.UserRootDirectory, companyName?.Replace(" ", "_"), hostName, hostVersion, "dvtokens.dat"); } - System.Diagnostics.Trace.WriteLine($"TokenCacheFilePath: {tokenPathAndFileName}"); + Trace.WriteLine($"TokenCacheFilePath: {tokenPathAndFileName}"); cacheFileDirectory = Path.GetDirectoryName(tokenPathAndFileName); cacheFileName = Path.GetFileName(tokenPathAndFileName); diff --git a/src/GeneralTools/DataverseClient/Client/ConnectionService.cs b/src/GeneralTools/DataverseClient/Client/ConnectionService.cs index fd00df1..6dba71a 100644 --- a/src/GeneralTools/DataverseClient/Client/ConnectionService.cs +++ b/src/GeneralTools/DataverseClient/Client/ConnectionService.cs @@ -117,7 +117,6 @@ internal sealed class ConnectionService : IConnectionService, IDisposable private PromptBehavior _promptBehavior; // prompt behavior private string _tokenCachePath; // user specified token cache file path private bool _isOnPremOAuth = false; // Identifies whether the connection is for OnPrem or Online Deployment for OAuth - private static string _userId = null; //cached userid reading from config file private bool _isCalledbyExecuteRequest = false; //Flag indicating that the an request called by Execute_Command private bool _isDefaultCredsLoginForOAuth = false; //Flag indicating that the user is trying to login with the current user id. @@ -342,7 +341,7 @@ internal sealed class ConnectionService : IConnectionService, IDisposable /// /// Cached userid /// - internal string UserId { get { return _userId; } } + internal string UserId { get { return _userAccount != null ? _userAccount.Username : string.Empty; } } /// /// Flag indicating that the an request called by Execute_Command used for OAuth @@ -957,7 +956,6 @@ private void GenerateCacheKeys(bool useUniqueCacheName) { //unqueInstance = true; // this instance is unique. _authority = string.Empty; - _userId = null; Guid guID = Guid.NewGuid(); _ServiceCACHEName = _ServiceCACHEName + guID.ToString(); // Creating a unique instance name for the cache object. } @@ -1064,7 +1062,7 @@ private async Task InitServiceAsync() { // Dataverse Service Endpoint to work with IOrganizationService dvService = null; - Stopwatch dtQueryTimer = new Stopwatch(); + Stopwatch dtQueryTimer = Stopwatch.StartNew(); try { if (!IsAClone) @@ -1406,21 +1404,6 @@ private async Task InitServiceAsync() return null; } - //// Do a WHO AM I request to make sure the connection is good. - //if (!UseExternalConnection) - //{ - // Guid guIntialTrackingID = Guid.NewGuid(); - // logEntry.Log(string.Format("Beginning Validation of Dataverse Connection. RequestID: {0}", guIntialTrackingID.ToString())); - // dtQueryTimer.Restart(); - // user = await GetWhoAmIDetails(dvService, guIntialTrackingID).ConfigureAwait(false); - // dtQueryTimer.Stop(); - // logEntry.Log(string.Format(CultureInfo.InvariantCulture, "Validation of Dataverse Connection Complete, total duration: {0}", dtQueryTimer.Elapsed.ToString())); - //} - //else - //{ - // logEntry.Log("External Dataverse Connection Provided, Skipping Validation"); - //} - return (IOrganizationService)dvService; } @@ -1483,8 +1466,7 @@ private async Task InitServiceAsync() private async Task DoDirectLoginAsync(bool IsOnPrem = false) { logEntry.Log("Direct Login Process Started", TraceEventType.Verbose); - Stopwatch sw = new Stopwatch(); - sw.Start(); + Stopwatch sw = Stopwatch.StartNew(); IOrganizationService dvService = null; Uri OrgWorkingURI = null; @@ -1600,8 +1582,7 @@ private async Task GetServerVersion(IOrganizationService dvService, Uri uriOfIns { Guid trackingID = Guid.NewGuid(); logEntry.Log(string.Format("Querying Organization Version. Request ID: {0}", trackingID)); - Stopwatch dtQueryTimer = new Stopwatch(); - dtQueryTimer.Restart(); + Stopwatch dtQueryTimer = Stopwatch.StartNew(); RetrieveVersionResponse getVersionResp = null; var request = new RetrieveVersionRequest() { RequestId = trackingID }; @@ -1680,8 +1661,7 @@ private async Task RefreshInstanceDetails(IOrganizationService dvService, Uri ur //TODO:// Add Logic here to improve perf by connecting to global disco. Guid trackingID = Guid.NewGuid(); logEntry.Log(string.Format("Querying Organization Instance Details. Request ID: {0}", trackingID)); - Stopwatch dtQueryTimer = new Stopwatch(); - dtQueryTimer.Restart(); + Stopwatch dtQueryTimer = Stopwatch.StartNew(); var request = new RetrieveCurrentOrganizationRequest() { AccessType = 0, RequestId = trackingID }; RetrieveCurrentOrganizationResponse resp; @@ -1795,8 +1775,7 @@ internal async Task GetWhoAmIDetails(IOrganizationService dvServ { if (dvService != null) { - Stopwatch dtQueryTimer = new Stopwatch(); - dtQueryTimer.Restart(); + Stopwatch dtQueryTimer = Stopwatch.StartNew(); try { if (trackingID == Guid.Empty) @@ -1894,6 +1873,8 @@ internal void SetClonedProperties(ServiceClient sourceClient) debugingCloneStateFilter++; EnableCookieRelay = sourceClient._connectionSvc.EnableCookieRelay; debugingCloneStateFilter++; + RequestAdditionalHeadersAsync = sourceClient._connectionSvc.RequestAdditionalHeadersAsync; + debugingCloneStateFilter++; } catch (Exception ex) { @@ -2098,8 +2079,15 @@ internal async Task Command_WebAPIProcess_ExecuteAsync(Org { postUri = $"{postUri}?{addedQueryParams}"; } + + Guid userProvidedRequestId = Guid.Empty; + if (req.RequestId.HasValue && req.RequestId != Guid.Empty ) + { + userProvidedRequestId = req.RequestId.Value; + } + // Execute request - var sResp = await Command_WebExecuteAsync(postUri, bodyOfRequest, methodToExecute, headers, "application/json", logMessageTag, callerId, disableConnectionLocking, maxRetryCount, retryPauseTime, uriOfInstance, cancellationToken: cancellationToken).ConfigureAwait(false); + var sResp = await Command_WebExecuteAsync(postUri, bodyOfRequest, methodToExecute, headers, "application/json", logMessageTag, callerId, disableConnectionLocking, maxRetryCount, retryPauseTime, uriOfInstance, cancellationToken: cancellationToken, requestTrackingId: userProvidedRequestId).ConfigureAwait(false); if (sResp != null && sResp.IsSuccessStatusCode) { if (req is CreateRequest) @@ -2182,7 +2170,7 @@ internal async Task Command_WebAPIProcess_ExecuteAsync(Org internal async Task Command_WebExecuteAsync(string queryString, string body, HttpMethod method, Dictionary> customHeaders, string contentType, string errorStringCheck, Guid callerId, bool disableConnectionLocking, int maxRetryCount, TimeSpan retryPauseTime, Uri uriOfInstance = null, Guid requestTrackingId = default, CancellationToken cancellationToken = default) { - Stopwatch logDt = new Stopwatch(); + Stopwatch logDt = Stopwatch.StartNew(); int retryCount = 0; bool retry = false; @@ -2350,8 +2338,8 @@ internal async Task Command_WebExecuteAsync(string queryStr // Add authorization header. - Here to catch the situation where a token expires during retry. if (!customHeaders.ContainsKey(Utilities.RequestHeaders.AUTHORIZATION_HEADER)) customHeaders.Add(Utilities.RequestHeaders.AUTHORIZATION_HEADER, new List() { string.Format("Bearer {0}", await RefreshClientTokenAsync().ConfigureAwait(false)) }); - - logDt.Restart(); // start clock. + logDt.Stop(); + logDt = Stopwatch.StartNew(); logEntry.Log(string.Format(CultureInfo.InvariantCulture, "Execute Command - {0}{1}: {2}", $"{method} {queryString}", @@ -2534,7 +2522,7 @@ private bool ShouldRetryWebAPI(Exception ex, int retryCount, int maxRetryCount, RequestId = requestTrackingId.Value; HttpResponseMessage _httpResponse = null; - Stopwatch logDt = new Stopwatch(); + Stopwatch logDt = Stopwatch.StartNew(); try { using (var _httpRequest = new HttpRequestMessage()) @@ -2596,7 +2584,8 @@ private bool ShouldRetryWebAPI(Exception ex, int retryCount, int maxRetryCount, if (providedHttpClient != null) { - logDt.Restart(); + logDt.Stop(); + logDt = Stopwatch.StartNew(); try { if (providedHttpClient.Timeout != MaxConnectionTimeout) @@ -2613,7 +2602,8 @@ private bool ShouldRetryWebAPI(Exception ex, int retryCount, int maxRetryCount, // Fall though logic to deal with an Http client not being passed in. using (HttpClient httpCli = new HttpClient()) { - logDt.Restart(); + logDt.Stop(); + logDt = Stopwatch.StartNew(); try { if (httpCli.Timeout != MaxConnectionTimeout) @@ -2805,7 +2795,7 @@ internal static async Task DiscoverGlobalOrganizat private static async Task DiscoverOrganizations_InternalAsync(Uri discoveryServiceUri, ClientCredentials clientCredentials, X509Certificate2 loginCertificate, string clientId, Uri redirectUri, PromptBehavior promptBehavior, bool isOnPrem, string authority, bool useDefaultCreds = false, string tokenCacheStorePath = null, DataverseTraceLogger logSink = null, CancellationToken cancellationToken = default) { bool createdLogSource = false; - Stopwatch dtStartQuery = new Stopwatch(); + Stopwatch dtStartQuery = Stopwatch.StartNew(); try { if (logSink == null) @@ -2845,7 +2835,8 @@ private static async Task DiscoverOrganizations_Int try { - dtStartQuery.Restart(); + dtStartQuery.Stop(); + dtStartQuery = Stopwatch.StartNew(); RetrieveOrganizationsResponse orgResponse = (RetrieveOrganizationsResponse)svcDiscoveryProxy.Execute(orgRequest); dtStartQuery.Stop(); @@ -2973,8 +2964,7 @@ private static async Task QueryGlobalDiscoveryAsyn if (discoveryServiceUri == null) throw new ArgumentNullException(nameof(discoveryServiceUri), "Discovery service uri cannot be null."); - Stopwatch dtStartQuery = new Stopwatch(); - dtStartQuery.Start(); + Stopwatch dtStartQuery = Stopwatch.StartNew(); // Initialize discovery service proxy. logSink.Log("QueryGlobalDiscovery - Initializing Discovery Server Uri with " + discoveryServiceUri.ToString()); @@ -3143,10 +3133,9 @@ private async Task ConnectAndInitServiceAsync(Organization // Set the Org into system config _organization = orgdata.UniqueName; - ConnectedOrganizationDetail = orgdata; + ConnectedOrganizationDetail = orgdata; - var logDt = new Stopwatch(); - logDt.Start(); + var logDt = Stopwatch.StartNew(); // Build User Credential logEntry.Log("ConnectAndInitService - Initializing Organization Service Object", TraceEventType.Verbose); // this to provide trouble shooting information when determining org connect failures. diff --git a/src/GeneralTools/DataverseClient/Client/DataverseTelemetryBehaviors.cs b/src/GeneralTools/DataverseClient/Client/DataverseTelemetryBehaviors.cs index c430dfd..6c9f510 100644 --- a/src/GeneralTools/DataverseClient/Client/DataverseTelemetryBehaviors.cs +++ b/src/GeneralTools/DataverseClient/Client/DataverseTelemetryBehaviors.cs @@ -96,9 +96,9 @@ public DataverseTelemetryBehaviors(ConnectionService cli) logg.Log($"Failed to parse MaxReceivedMessageSizeOverride property. Value found: {maxRecvSz}. MaxReceivedMessageSizeOverride must be a valid integer.", System.Diagnostics.TraceEventType.Warning); } - if (_maxBufferPoolSize == -1 && !string.IsNullOrEmpty(_configuration.Value.MaxBufferPoolSizeOveride)) + if (_maxBufferPoolSize == -1 && !string.IsNullOrEmpty(_configuration.Value.MaxBufferPoolSizeOverride)) { - var maxBufferPoolSz = _configuration.Value.MaxBufferPoolSizeOveride; + var maxBufferPoolSz = _configuration.Value.MaxBufferPoolSizeOverride; if (maxBufferPoolSz is string && !string.IsNullOrWhiteSpace(maxBufferPoolSz)) { int.TryParse(maxBufferPoolSz, out _maxBufferPoolSize); diff --git a/src/GeneralTools/DataverseClient/Client/Model/ConfigurationOptions.cs b/src/GeneralTools/DataverseClient/Client/Model/ConfigurationOptions.cs index 8a439ac..38537e4 100644 --- a/src/GeneralTools/DataverseClient/Client/Model/ConfigurationOptions.cs +++ b/src/GeneralTools/DataverseClient/Client/Model/ConfigurationOptions.cs @@ -23,7 +23,7 @@ public void UpdateOptions(ConfigurationOptions options) if (options != null) { EnableAffinityCookie = options.EnableAffinityCookie; - MaxBufferPoolSizeOveride = options.MaxBufferPoolSizeOveride; + MaxBufferPoolSizeOverride = options.MaxBufferPoolSizeOverride; MaxFaultSizeOverride = options.MaxFaultSizeOverride; MaxReceivedMessageSizeOverride = options.MaxReceivedMessageSizeOverride; MaxRetryCount = options.MaxRetryCount; @@ -131,11 +131,11 @@ public string MaxReceivedMessageSizeOverride set => _maxReceivedMessageSize = value; } - private string _maxBufferPoolSizeOveride = Utils.AppSettingsHelper.GetAppSetting("MaxBufferPoolSizeOveride", null); + private string _maxBufferPoolSizeOveride = Utils.AppSettingsHelper.GetAppSetting("MaxBufferPoolSizeOverride", null); /// /// MaxBufferPoolSize override. - Use under Microsoft Direction only. /// - public string MaxBufferPoolSizeOveride + public string MaxBufferPoolSizeOverride { get => _maxBufferPoolSizeOveride; set => _maxBufferPoolSizeOveride = value; diff --git a/src/GeneralTools/DataverseClient/Client/ServiceClient.cs b/src/GeneralTools/DataverseClient/Client/ServiceClient.cs index f775e75..e33b48c 100644 --- a/src/GeneralTools/DataverseClient/Client/ServiceClient.cs +++ b/src/GeneralTools/DataverseClient/Client/ServiceClient.cs @@ -89,6 +89,11 @@ public class ServiceClient : IOrganizationService, IOrganizationServiceAsync2, I /// internal object _lockObject = new object(); + /// + /// This is an internal lock object, used to sync clone operations. + /// + internal object _cloneLockObject = new object(); + /// /// BatchManager for Execute Multiple. /// @@ -1396,19 +1401,25 @@ public ServiceClient Clone(System.Reflection.Assembly strongTypeAsm, ILogger log { try { + if (_cloneLockObject == null) + _cloneLockObject = new object(); + // Get Current Access Token. // This will get the current access token if (logger == null) logger = _logEntry._logger; - proxy.HeaderToken = this.CurrentAccessToken; - var SvcClient = new ServiceClient(proxy, true, _connectionSvc.AuthenticationTypeInUse, _connectionSvc?.OrganizationVersion, logger: logger); - SvcClient._connectionSvc.SetClonedProperties(this); - SvcClient.CallerAADObjectId = CallerAADObjectId; - SvcClient.CallerId = CallerId; - SvcClient.MaxRetryCount = _configuration.Value.MaxRetryCount; - SvcClient.RetryPauseTime = _configuration.Value.RetryPauseTime; - SvcClient.GetAccessToken = GetAccessToken; - - return SvcClient; + lock (_cloneLockObject) + { + proxy.HeaderToken = this.CurrentAccessToken; + var SvcClient = new ServiceClient(proxy, true, _connectionSvc.AuthenticationTypeInUse, _connectionSvc?.OrganizationVersion, logger: logger); + SvcClient._connectionSvc.SetClonedProperties(this); + SvcClient.CallerAADObjectId = CallerAADObjectId; + SvcClient.CallerId = CallerId; + SvcClient.MaxRetryCount = _configuration.Value.MaxRetryCount; + SvcClient.RetryPauseTime = _configuration.Value.RetryPauseTime; + SvcClient.GetAccessToken = GetAccessToken; + SvcClient.GetCustomHeaders = GetCustomHeaders; + return SvcClient; + } } catch (DataverseConnectionException) { @@ -1791,7 +1802,7 @@ internal async Task Command_ExecuteAsyncImpl(OrganizationR ValidateConnectionLive(); Guid requestTrackingId = Guid.NewGuid(); OrganizationResponse resp = null; - Stopwatch logDt = new Stopwatch(); + Stopwatch logDt = Stopwatch.StartNew(); TimeSpan LockWait = TimeSpan.Zero; int retryCount = 0; bool retry = false; @@ -1834,8 +1845,9 @@ internal async Task Command_ExecuteAsyncImpl(OrganizationR SessionTrackingId.HasValue && SessionTrackingId.Value != Guid.Empty ? $"SessionID={SessionTrackingId.Value.ToString()} : " : "" ), TraceEventType.Verbose); - logDt.Restart(); - _= await _connectionSvc.RefreshClientTokenAsync().ConfigureAwait(false); + logDt.Stop(); + logDt = Stopwatch.StartNew(); + _ = await _connectionSvc.RefreshClientTokenAsync().ConfigureAwait(false); rsp = await DataverseServiceAsync.ExecuteAsync(req).ConfigureAwait(false); logDt.Stop(); @@ -1887,7 +1899,7 @@ internal OrganizationResponse Command_Execute(OrganizationRequest req, string er ValidateConnectionLive(); Guid requestTrackingId = Guid.NewGuid(); OrganizationResponse resp = null; - Stopwatch logDt = new Stopwatch(); + Stopwatch logDt = Stopwatch.StartNew(); TimeSpan LockWait = TimeSpan.Zero; int retryCount = 0; bool retry = false; @@ -1931,14 +1943,16 @@ internal OrganizationResponse Command_Execute(OrganizationRequest req, string er requestIdLogSegement ), TraceEventType.Verbose); - logDt.Restart(); + logDt.Stop(); + logDt = Stopwatch.StartNew(); _connectionSvc.RefreshClientTokenAsync().Wait(); // Refresh the token if needed.. if (!_disableConnectionLocking) // Allow Developer to override Cross Thread Safeties lock (_lockObject) { if (logDt.Elapsed > TimeSpan.FromMilliseconds(0000010)) LockWait = logDt.Elapsed; - logDt.Restart(); + logDt.Stop(); + logDt = Stopwatch.StartNew(); rsp = DataverseService.Execute(req); } else diff --git a/src/GeneralTools/DataverseClient/Client/Utils/DataverseConnectionStringProcessor.cs b/src/GeneralTools/DataverseClient/Client/Utils/DataverseConnectionStringProcessor.cs index 2362a39..79d281a 100644 --- a/src/GeneralTools/DataverseClient/Client/Utils/DataverseConnectionStringProcessor.cs +++ b/src/GeneralTools/DataverseClient/Client/Utils/DataverseConnectionStringProcessor.cs @@ -290,7 +290,8 @@ private DataverseConnectionStringProcessor(string serviceUri, string userName, s } //if the client Id was not passed, use Sample AppID - if ((authenticationType != AuthenticationType.AD || authenticationType != AuthenticationType.ExternalTokenManagement) + if ((authenticationType != AuthenticationType.AD + && authenticationType != AuthenticationType.ExternalTokenManagement) && string.IsNullOrWhiteSpace(ClientId)) { logEntry.Log($"Client ID not supplied, using SDK Sample Client ID for this connection", System.Diagnostics.TraceEventType.Warning); diff --git a/src/GeneralTools/DataverseClient/Client/Utils/Utils.cs b/src/GeneralTools/DataverseClient/Client/Utils/Utils.cs index 3cee3af..a129de9 100644 --- a/src/GeneralTools/DataverseClient/Client/Utils/Utils.cs +++ b/src/GeneralTools/DataverseClient/Client/Utils/Utils.cs @@ -1280,5 +1280,20 @@ internal static T SeekExceptionOnStack(Exception e) where T : Exception return null; } + /// + /// Removes invalid characters from a potential file name. + /// + /// + /// + internal static string CleanUpPotentialFileName ( string potentialFileName) + { + if (string.IsNullOrEmpty(potentialFileName)) + return potentialFileName; + + System.IO.Path.GetInvalidFileNameChars().Where(c => potentialFileName.Contains(c)).ToList().ForEach(c => potentialFileName = potentialFileName.Replace(c, '_')); + + return potentialFileName; + } + } } diff --git a/src/GeneralTools/DataverseClient/ConnectControl/AdvancedOptions.xaml b/src/GeneralTools/DataverseClient/ConnectControl/AdvancedOptions.xaml index bf0fce2..c648d88 100644 --- a/src/GeneralTools/DataverseClient/ConnectControl/AdvancedOptions.xaml +++ b/src/GeneralTools/DataverseClient/ConnectControl/AdvancedOptions.xaml @@ -44,13 +44,39 @@ - + - - - + + + + + + + + + + + + + + + diff --git a/src/GeneralTools/DataverseClient/ConnectControl/ConnectionManager.cs b/src/GeneralTools/DataverseClient/ConnectControl/ConnectionManager.cs index 59b0e2b..2211599 100644 --- a/src/GeneralTools/DataverseClient/ConnectControl/ConnectionManager.cs +++ b/src/GeneralTools/DataverseClient/ConnectControl/ConnectionManager.cs @@ -531,7 +531,11 @@ private void bgWorker_DoWork(object sender, DoWorkEventArgs e) else { // Set URL that was connected too. - StorageUtils.SetConfigKey(ServerConfigKeys, Dynamics_ConfigFileServerKeys.DirectConnectionUri, ServiceClient.ConnectedOrgUriActual.ToString()); + if (!string.IsNullOrEmpty(ServiceClient?._connectionSvc?.AuthContext?.Account?.Username)) + { + StorageUtils.SetConfigKey(ServerConfigKeys, Dynamics_ConfigFileServerKeys.CrmUserName, ServiceClient?._connectionSvc?.AuthContext?.Account?.Username); + } + StorageUtils.SetConfigKey(ServerConfigKeys, Dynamics_ConfigFileServerKeys.DirectConnectionUri, ServiceClient.ConnectedOrgUriActual.ToString()); StorageUtils.SetConfigKey(ServerConfigKeys, Dynamics_ConfigFileServerKeys.UseDirectConnection, true.ToString()); } @@ -575,7 +579,6 @@ private bool ValidateServerConnection(OrgByServer selectedOrg) // Make sure there is a value if (!ValidateUserSpecifiedData()) return false; - // Check to see if its a direct connect. _directConnectUri = StorageUtils.GetConfigKey(ServerConfigKeys, Dynamics_ConfigFileServerKeys.DirectConnectionUri); // Value is not a bool.. check to see if there is a value. @@ -1159,9 +1162,11 @@ private bool ValidateUserSpecifiedData() { bool IsClientIdOrRedirectUriEmpty = string.IsNullOrEmpty(ClientId) || RedirectUri == null; bool IsUserNameNull = string.IsNullOrEmpty(StorageUtils.GetConfigKey(ServerConfigKeys, Dynamics_ConfigFileServerKeys.CrmUserName)); - var passwordSecureString = StorageUtils.GetConfigKey(ServerConfigKeys, Dynamics_ConfigFileServerKeys.CrmPassword); - bool IsPasswordNull = (passwordSecureString == null) || (passwordSecureString.Length == 0); - if (!bool.TryParse(StorageUtils.GetConfigKey(ServerConfigKeys, Dynamics_ConfigFileServerKeys.AdvancedCheck), out IsAdvancedCheckEnabled)) + var passwordSecureString = StorageUtils.GetConfigKey(ServerConfigKeys, Dynamics_ConfigFileServerKeys.CrmPassword); + bool IsPasswordNull = (passwordSecureString == null) || (passwordSecureString.Length == 0); + bool IsServerUrlSpecifed = !string.IsNullOrEmpty(StorageUtils.GetConfigKey(ServerConfigKeys, Dynamics_ConfigFileServerKeys.DirectConnectionUri)); + + if (!bool.TryParse(StorageUtils.GetConfigKey(ServerConfigKeys, Dynamics_ConfigFileServerKeys.AdvancedCheck), out IsAdvancedCheckEnabled)) { IsAdvancedCheckEnabled = false; } @@ -1198,25 +1203,25 @@ private bool ValidateUserSpecifiedData() } else if(!string.IsNullOrEmpty(StorageUtils.GetConfigKey(ServerConfigKeys, Dynamics_ConfigFileServerKeys.CrmDeploymentType)) && StorageUtils.GetConfigKey(ServerConfigKeys, Dynamics_ConfigFileServerKeys.CrmDeploymentType).Equals(CrmDeploymentType.O365.ToString(), StringComparison.OrdinalIgnoreCase) && - IsAdvancedCheckEnabled) + IsAdvancedCheckEnabled && !IsServerUrlSpecifed) { // Use Default is not checked and Auth type is OAuth. - if (IsUserNameNull && !IsPasswordNull) + if (IsUserNameNull) { ErrorLogger.WriteToFile(new System.ArgumentException(Messages.CRMCONNECT_LOGIN_VALIDATION_ERR_USERID, Dynamics_ConfigFileServerKeys.CrmUserName.ToString())); _bgWorker.ReportProgress(100, new ServerConnectStatusEventArgs(Messages.CRMCONNECT_LOGIN_VALIDATION_ERR_USERID_MSG, false, new System.ArgumentException(Messages.CRMCONNECT_LOGIN_VALIDATION_ERR_USERID))); - _tracer.Log("You must specify both User Name and Password or both are required to be null", TraceEventType.Information); - return false; - } - else if (!IsUserNameNull && IsPasswordNull) - { - ErrorLogger.WriteToFile(new System.ArgumentException(Messages.CRMCONNECT_LOGIN_VALIDATION_ERR_PASSWORD, Dynamics_ConfigFileServerKeys.CrmPassword.ToString())); - _bgWorker.ReportProgress(100, new ServerConnectStatusEventArgs(Messages.CRMCONNECT_LOGIN_VALIDATION_ERR_PASSWORD_MSG, - false, new System.ArgumentException(Messages.CRMCONNECT_LOGIN_VALIDATION_ERR_PASSWORD))); - _tracer.Log("You must specify both User Name and Password or both are required to be null", TraceEventType.Information); + _tracer.Log("You must specify a User Name and Password or both are required to be null", TraceEventType.Information); return false; } + //else if (!IsUserNameNull && IsPasswordNull) + //{ + // ErrorLogger.WriteToFile(new System.ArgumentException(Messages.CRMCONNECT_LOGIN_VALIDATION_ERR_PASSWORD, Dynamics_ConfigFileServerKeys.CrmPassword.ToString())); + // _bgWorker.ReportProgress(100, new ServerConnectStatusEventArgs(Messages.CRMCONNECT_LOGIN_VALIDATION_ERR_PASSWORD_MSG, + // false, new System.ArgumentException(Messages.CRMCONNECT_LOGIN_VALIDATION_ERR_PASSWORD))); + // _tracer.Log("You must specify both User Name and Password or both are required to be null", TraceEventType.Information); + // return false; + //} if (IsClientIdOrRedirectUriEmpty && IsUserNameNull && IsPasswordNull) { @@ -1245,7 +1250,7 @@ private bool ValidateUserSpecifiedData() { // Use Default is not checked.. // if the on useDefualt Creds is not checked make sure the user name is there. - if (string.IsNullOrEmpty(StorageUtils.GetConfigKey(ServerConfigKeys, Dynamics_ConfigFileServerKeys.CrmUserName))) + if (string.IsNullOrEmpty(StorageUtils.GetConfigKey(ServerConfigKeys, Dynamics_ConfigFileServerKeys.CrmUserName)) && !IsServerUrlSpecifed) { ErrorLogger.WriteToFile(new System.ArgumentException(Messages.CRMCONNECT_LOGIN_VALIDATION_ERR_USERID, Dynamics_ConfigFileServerKeys.CrmUserName.ToString())); _bgWorker.ReportProgress(100, new ServerConnectStatusEventArgs(Messages.CRMCONNECT_LOGIN_VALIDATION_ERR_USERID_MSG, @@ -1254,7 +1259,7 @@ private bool ValidateUserSpecifiedData() return false; } - if (StorageUtils.GetConfigKey(ServerConfigKeys, Dynamics_ConfigFileServerKeys.CrmPassword) == null) + if (StorageUtils.GetConfigKey(ServerConfigKeys, Dynamics_ConfigFileServerKeys.CrmPassword) == null && !IsServerUrlSpecifed) { ErrorLogger.WriteToFile(new System.ArgumentException(Messages.CRMCONNECT_LOGIN_VALIDATION_ERR_PASSWORD, Dynamics_ConfigFileServerKeys.CrmPassword.ToString())); _bgWorker.ReportProgress(100, new ServerConnectStatusEventArgs(Messages.CRMCONNECT_LOGIN_VALIDATION_ERR_PASSWORD_MSG, @@ -1441,21 +1446,34 @@ private DiscoverOrganizationsResult QueryOnlineServerList(ObservableCollection - + diff --git a/src/GeneralTools/DataverseClient/ConnectControl/ServerLoginControl.xaml.cs b/src/GeneralTools/DataverseClient/ConnectControl/ServerLoginControl.xaml.cs index 9136121..6db7658 100644 --- a/src/GeneralTools/DataverseClient/ConnectControl/ServerLoginControl.xaml.cs +++ b/src/GeneralTools/DataverseClient/ConnectControl/ServerLoginControl.xaml.cs @@ -22,6 +22,7 @@ using Microsoft.PowerPlatform.Dataverse.Client.Model; using System.Media; using System.Windows.Automation.Peers; +using System.Security.Policy; #endregion @@ -327,7 +328,7 @@ private void LoadDisplayWithAppSettingsData() tbCrmServerPort.Text = StorageUtils.GetConfigKey(ServerConfigKeys, Dynamics_ConfigFileServerKeys.CrmPort); AdvancedOptions.tbUserId.Text = StorageUtils.GetConfigKey(ServerConfigKeys, Dynamics_ConfigFileServerKeys.CrmUserName); - AdvancedOptions.tbConnectUrl.Text = StorageUtils.GetConfigKey(ServerConfigKeys, Dynamics_ConfigFileServerKeys.DirectConnectionUri); + AdvancedOptions.tbConnectUrl.Text = GetCleanedUpURL(StorageUtils.GetConfigKey(ServerConfigKeys, Dynamics_ConfigFileServerKeys.DirectConnectionUri)); AdvancedOptions.tbDomain.Text = StorageUtils.GetConfigKey(ServerConfigKeys, Dynamics_ConfigFileServerKeys.CrmDomain); // Get Bool Settings @@ -403,6 +404,22 @@ private void LoadDisplayWithAppSettingsData() rbOnlinePrem_Click(this, null); } + private string GetCleanedUpURL(string connectionUrl) + { + if( Uri.TryCreate(connectionUrl, UriKind.Absolute, out Uri workingUrl)) + { + string workingUrlString = workingUrl.GetComponents(UriComponents.SchemeAndServer, UriFormat.Unescaped); + if (connectionUrl.ToUpperInvariant().Contains($"{workingUrlString.ToUpperInvariant()}/XRMSERVICES")) + return workingUrlString; + else + return workingUrl.ToString(); + } + else + { + return connectionUrl; + } + } + /// /// Sets the current UI element for the Online region. /// @@ -462,7 +479,8 @@ private void UpdateServerConfigKeysFromUI() { StorageUtils.SetConfigKey(ServerConfigKeys, Dynamics_ConfigFileServerKeys.CrmOrg, string.Empty); StorageUtils.SetConfigKey(ServerConfigKeys, Dynamics_ConfigFileServerKeys.UseDirectConnection, false.ToString()); - } + StorageUtils.SetConfigKey(ServerConfigKeys, Dynamics_ConfigFileServerKeys.DirectConnectionUri, string.Empty); + } else { StorageUtils.SetConfigKey(ServerConfigKeys, Dynamics_ConfigFileServerKeys.CrmOrg, tbCrmOrg.Text); diff --git a/src/GeneralTools/DataverseClient/UnitTests/CdsClient_Core_Tests/ServiceClientTests.cs b/src/GeneralTools/DataverseClient/UnitTests/CdsClient_Core_Tests/ServiceClientTests.cs index 1c2caf9..e5c9d27 100644 --- a/src/GeneralTools/DataverseClient/UnitTests/CdsClient_Core_Tests/ServiceClientTests.cs +++ b/src/GeneralTools/DataverseClient/UnitTests/CdsClient_Core_Tests/ServiceClientTests.cs @@ -23,6 +23,7 @@ using System.Net.Http; using System.Runtime.CompilerServices; using System.Security; +using System.Threading.Tasks; using Xunit; using Xunit.Abstractions; #endregion @@ -708,6 +709,18 @@ public void TestResponseHeaderWebAPIBehavior() cli.Delete("account", testSupport._DefaultId); Assert.Equal(baseTestDOP, cli.RecommendedDegreesOfParallelism); + + Guid requestId = Guid.NewGuid(); + cli._logEntry.Log($"New Request ID is {requestId}", TraceEventType.Information); + DeleteRequest deleteRequest = new DeleteRequest() + { + Target = new EntityReference("account", testSupport._DefaultId), + RequestId = requestId + }; + cli.ExecuteOrganizationRequest(deleteRequest, useWebAPI: true); + Assert.Equal(baseTestDOP, cli.RecommendedDegreesOfParallelism); + + } [Fact] @@ -798,7 +811,7 @@ public void RetrieveSolutionImportResultAsyncTestWithAsyncImport() [Trait("Category", "Live Connect Required")] public void ConnectUsingServiceIdentity_ClientSecret_CtorV1() { - System.Net.ServicePointManager.SecurityProtocol = System.Net.SecurityProtocolType.Tls12; + // System.Net.ServicePointManager.SecurityProtocol = System.Net.SecurityProtocolType.Tls12; var Conn_AppID = System.Environment.GetEnvironmentVariable("XUNITCONNTESTAPPID"); var Conn_Secret = System.Environment.GetEnvironmentVariable("XUNITCONNTESTSECRET"); @@ -893,7 +906,48 @@ public void ConnectUsingServiceIdentity_ClientSecret_Consetup() ValidateConnection(client); } - [SkippableConnectionTest] + public Task> GetAdditionalHeadersAsync() + { + var headers = new Dictionary(); + headers.Add("User-Agent", "abc"); + return Task.FromResult(headers); + } + + [SkippableConnectionTest] + [Trait("Category", "Live Connect Required")] + public void ConnectUsingUserIdentity_UIDPWHeaders_Consetup() + { + System.Net.ServicePointManager.SecurityProtocol = System.Net.SecurityProtocolType.Tls12; + ConnectionOptions connectionOptions = new ConnectionOptions() + { + ServiceUri = new Uri(System.Environment.GetEnvironmentVariable("XUNITCONNTESTURI")), + AuthenticationType = Microsoft.PowerPlatform.Dataverse.Client.AuthenticationType.OAuth, + UserName = System.Environment.GetEnvironmentVariable("XUNITCONNTESTUSERID"), + Password = ServiceClient.MakeSecureString(System.Environment.GetEnvironmentVariable("XUNITCONNTESTPW")), + ClientId = DataverseConnectionStringProcessor.sampleClientId, + RedirectUri = new Uri(DataverseConnectionStringProcessor.sampleRedirectUrl), + LoginPrompt = PromptBehavior.Auto, + RequestAdditionalHeadersAsync = GetAdditionalHeadersAsync, + Logger = Ilogger + }; + + // Connection params. + var client = new ServiceClient(connectionOptions, deferConnection: true); + Assert.NotNull(client); + Assert.False(client.IsReady, "Client is showing True on Deferred Connection."); + Assert.True(client.Connect(), "Connection was not activated"); + Assert.True(client.IsReady, "Failed to Create Connection via Constructor"); + + // Validate connection + ValidateConnection(client); + client._connectionSvc.RequestAdditionalHeadersAsync.Should().NotBeNull(); + + var clientClone = client.Clone(); + ValidateConnection(clientClone, usingExternalAuth: true); + clientClone._connectionSvc.RequestAdditionalHeadersAsync.Should().NotBeNull(); + } + + [SkippableConnectionTest] [Trait("Category", "Live Connect Required")] public void ConnectUsingServiceIdentity_ClientSecret_ExternalAuth_CtorV1() { @@ -1681,8 +1735,7 @@ private ServiceClient CreateServiceClient() return client; } - - private void WaitForAsyncOperationToComplete(Stopwatch _HoldTime, Stopwatch _RunTime, ServiceClient client, Guid? asyncTrackingId) + private void WaitForAsyncOperationToComplete(Stopwatch _HoldTime, Stopwatch _RunTime, ServiceClient client, Guid? asyncTrackingId) { if (asyncTrackingId != null && asyncTrackingId != Guid.Empty) { diff --git a/src/GeneralTools/DataverseClient/UnitTests/CdsClient_Core_Tests/appsettings.json b/src/GeneralTools/DataverseClient/UnitTests/CdsClient_Core_Tests/appsettings.json index b19250d..99f296b 100644 --- a/src/GeneralTools/DataverseClient/UnitTests/CdsClient_Core_Tests/appsettings.json +++ b/src/GeneralTools/DataverseClient/UnitTests/CdsClient_Core_Tests/appsettings.json @@ -1,7 +1,7 @@ { "Logging": { "LogLevel": { - "Default": "Information", + "Default": "Trace", "Microsoft.PowerPlatform.Dataverse.Client.ServiceClient": "Trace", "Client_Core_Tests.ClientTests": "Trace", "Client_Core_UnitTests.ClientDynamicsExtensionsTests": "Trace", diff --git a/src/nuspecs/Microsoft.PowerPlatform.Dataverse.Client.ReleaseNotes.txt b/src/nuspecs/Microsoft.PowerPlatform.Dataverse.Client.ReleaseNotes.txt index c76ae95..450d4bd 100644 --- a/src/nuspecs/Microsoft.PowerPlatform.Dataverse.Client.ReleaseNotes.txt +++ b/src/nuspecs/Microsoft.PowerPlatform.Dataverse.Client.ReleaseNotes.txt @@ -7,6 +7,14 @@ Notice: Note: Only AD on FullFramework, OAuth, Certificate, ClientSecret Authentication types are supported at this time. ++CURRENTRELEASEID++ +Fix for Request ID not reflecting correctly for some requests. +Fix for RequestAdditionalHeadersAsync interface not being forwarded to Cloned copies of DVSC. GitHub Reported - Fix #419 +Fix for Clone being called concurrently causing unnecessary calls to dataverse. GitHub reported - Fix #422 +Fix for invalid filenames and paths being created for token cache path when using user interactive mode. Git Hub reported - Fix #406 +RENAME (Possible breaking change) MaxBufferPoolSizeOveride parameter name spelling issue, corrected to MaxBufferPoolSizeOverride + + +1.1.16: Updated Core SDK Added new properties to Organization detail to report Schema Type and Deployment Type. Fixed bug in creating file to user token cache when host name contains invalid characters. Fixes #406