Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve SiteExistsAnywhere in Tenant Extensions. #1071

Open
wants to merge 1 commit into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions src/lib/PnP.Framework.Test/Extensions/TenantExtensionsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,33 @@ public void SubSiteExistsTest()
Assert.IsFalse(subSiteExists4);
}
}

/// <summary>
/// Validate if site collection exists anywhere
/// </summary>
[TestMethod()]
[Timeout(15 * 60 * 1000)]
public void SiteExistsAnywhereTest()
{
using (var tenantContext = TestCommon.CreateTenantClientContext())
{
Tenant tenant = new(tenantContext);
IList<SiteEntity> 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
Expand Down
96 changes: 53 additions & 43 deletions src/lib/PnP.Framework/Extensions/TenantExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -1229,71 +1231,60 @@ public static bool CheckIfSiteExists(this Tenant tenant, string siteFullUrl, str
/// <returns>An enumerated type that can be: No, Yes, Recycled</returns>
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;
}
}

Expand Down Expand Up @@ -1438,6 +1429,25 @@ private static bool IsCannotRemoveSiteException(Exception ex)
return false;
}
}

/// <summary>
/// Check if exception Is "unauthorized" to access site collection
/// </summary>
/// <param name="ex">Exception</param>
/// <returns>True if yes, false if not</returns>
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
Expand Down