From f3750aac50c07f7bc8a78203b1cfe9da1d96b689 Mon Sep 17 00:00:00 2001 From: Vasco Azevedo Date: Sat, 12 Oct 2024 18:57:20 +0100 Subject: [PATCH] Improve SiteExistsAnywhere in Tenant Extensions. - Change logic to validate if site exists. - Added unit tests to ensure the functionality works as intended, covering various scenarios. --- .../Extensions/TenantExtensionsTests.cs | 27 ++++++ .../Extensions/TenantExtensions.cs | 96 ++++++++++--------- 2 files changed, 80 insertions(+), 43 deletions(-) diff --git a/src/lib/PnP.Framework.Test/Extensions/TenantExtensionsTests.cs b/src/lib/PnP.Framework.Test/Extensions/TenantExtensionsTests.cs index 258cd8077..baed4289b 100644 --- a/src/lib/PnP.Framework.Test/Extensions/TenantExtensionsTests.cs +++ b/src/lib/PnP.Framework.Test/Extensions/TenantExtensionsTests.cs @@ -210,6 +210,33 @@ public void SubSiteExistsTest() Assert.IsFalse(subSiteExists4); } } + + /// + /// Validate if site collection exists anywhere + /// + [TestMethod()] + [Timeout(15 * 60 * 1000)] + public void SiteExistsAnywhereTest() + { + using (var tenantContext = TestCommon.CreateTenantClientContext()) + { + Tenant tenant = new(tenantContext); + IList siteCollections = tenant.GetSiteCollections(includeDetail: false); + + // Grab a random site collection from the list of returned site collections + int siteNumberToCheck = new Random().Next(0, siteCollections.Count - 1); + + SiteEntity site = siteCollections[siteNumberToCheck]; + + SiteExistence siteExists1 = tenant.SiteExistsAnywhere(site.Url); + Assert.IsTrue(siteExists1 == SiteExistence.Yes); + + string devSiteUrl = TestCommon.AppSetting("SPODevSiteUrl"); + string siteToCreateUrl = GetTestSiteCollectionName(devSiteUrl, "aaabbbccc"); + SiteExistence siteExists2 = tenant.SiteExistsAnywhere(siteToCreateUrl); + Assert.IsFalse(siteExists2 == SiteExistence.No, "Invalid site returned as valid."); + } + } #endregion #region Site collection creation and deletion tests diff --git a/src/lib/PnP.Framework/Extensions/TenantExtensions.cs b/src/lib/PnP.Framework/Extensions/TenantExtensions.cs index d64e1e530..f9271481f 100644 --- a/src/lib/PnP.Framework/Extensions/TenantExtensions.cs +++ b/src/lib/PnP.Framework/Extensions/TenantExtensions.cs @@ -5,6 +5,7 @@ using PnP.Framework.Entities; using PnP.Framework.Graph; using PnP.Framework.Graph.Model; +using PnP.Framework.Http; using PnP.Framework.Provisioning.Model; using PnP.Framework.Provisioning.Model.Configuration; using PnP.Framework.Provisioning.ObjectHandlers; @@ -15,6 +16,7 @@ using System.Globalization; using System.Linq; using System.Net; +using System.Net.Http; using System.Text.Json; using System.Threading; using System.Threading.Tasks; @@ -1229,71 +1231,60 @@ public static bool CheckIfSiteExists(this Tenant tenant, string siteFullUrl, str /// An enumerated type that can be: No, Yes, Recycled public static SiteExistence SiteExistsAnywhere(this Tenant tenant, string siteFullUrl) { - var userIsTenantAdmin = TenantExtensions.IsCurrentUserTenantAdmin((ClientContext)tenant.Context); - try { - // CHANGED: Modified in order to support non privilege users - if (userIsTenantAdmin) + using (ClientContext siteContext = tenant.Context.Clone(siteFullUrl)) { - // Get the site name - var properties = tenant.GetSitePropertiesByUrl(siteFullUrl, false); - tenant.Context.Load(properties); - tenant.Context.ExecuteQueryRetry(); - } - else - { - // Get the site context for the current user - using (var siteContext = tenant.Context.Clone(siteFullUrl)) - { - var site = siteContext.Site; - siteContext.Load(site); - siteContext.ExecuteQueryRetry(); - } + Site site = siteContext.Site; + siteContext.Load(site); + siteContext.ExecuteQueryRetry(); } - // Will cause an exception if site URL is not there. Not optimal, but the way it works. return SiteExistence.Yes; } catch (Exception ex) { - if (userIsTenantAdmin && (IsCannotGetSiteException(ex) || IsUnableToAccessSiteException(ex))) + //If is unauthorized to access site collection, it means the site collection exists + if (IsUnauthorizedToAccessSiteException(ex)) { - if (IsUnableToAccessSiteException(ex)) + return SiteExistence.Yes; + } + + //Validate if when connect to the site the response contains 'connection: close', if it returns that, the site is recycled + try + { + // Use an HTTP client to send a request to the site URL. + using (HttpClient httpClient = PnPHttpClient.Instance.GetHttpClient(tenant.Context.Clone(siteFullUrl))) + using (HttpRequestMessage request = new(HttpMethod.Get, siteFullUrl)) { - //Let's retry to see if this site collection was recycled - try + HttpResponseMessage response = httpClient.SendAsync(request, new CancellationToken()).GetAwaiter().GetResult(); + + // Check if the response indicates a failure (not successful). + if (response.StatusCode == HttpStatusCode.NotFound) { - var deletedProperties = tenant.GetDeletedSitePropertiesByUrl(siteFullUrl); - tenant.Context.Load(deletedProperties); - tenant.Context.ExecuteQueryRetry(); - if (deletedProperties.Status.Equals("Recycled", StringComparison.OrdinalIgnoreCase)) + string responseBody = response.Content.ReadAsStringAsync().GetAwaiter().GetResult(); + + // Check if the response indicates that the site was not found (404 error). + if (responseBody.ToUpper().Contains("404 FILE NOT FOUND")) { - return SiteExistence.Recycled; + return SiteExistence.No; } - else + // Check if the response indicates that the site is recycled. + else if (responseBody.ToUpper().Contains("CONNECTION: CLOSE")) { - return SiteExistence.No; + return SiteExistence.Recycled; } } - catch - { - return SiteExistence.No; - } - } - else - { - return SiteExistence.No; } } - else if (IsNotFoundException(ex)) + catch { } + + if (IsNotFoundException(ex)) { return SiteExistence.No; } - else - { - return SiteExistence.Yes; - } + + return SiteExistence.Yes; } } @@ -1438,6 +1429,25 @@ private static bool IsCannotRemoveSiteException(Exception ex) return false; } } + + /// + /// Check if exception Is "unauthorized" to access site collection + /// + /// Exception + /// True if yes, false if not + private static bool IsUnauthorizedToAccessSiteException(Exception ex) + { + if (ex is ServerException serverException && serverException.ServerErrorCode == -2147024891 && serverException.ServerErrorTypeName.Equals("System.UnauthorizedAccessException", StringComparison.InvariantCultureIgnoreCase)) + { + return true; + } + + if (ex is System.Net.WebException { Response: System.Net.HttpWebResponse { StatusCode: HttpStatusCode.Unauthorized } }) + { + return true; + } + return false; + } #endregion #region ClientSide Package Deployment