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

[main > release/client/2.0]: Handle Location redirection for getSharingInformation call (#22551) #22622

Merged
merged 5 commits into from
Sep 25, 2024
Merged
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
84 changes: 62 additions & 22 deletions packages/drivers/odsp-driver/src/getFileLink.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@
import type { ITelemetryBaseProperties } from "@fluidframework/core-interfaces";
import { assert } from "@fluidframework/core-utils/internal";
import { NonRetryableError, runWithRetry } from "@fluidframework/driver-utils/internal";
import { hasRedirectionLocation } from "@fluidframework/odsp-doclib-utils/internal";
import {
IOdspUrlParts,
OdspErrorTypes,
OdspResourceTokenFetchOptions,
TokenFetcher,
type IOdspResolvedUrl,
} from "@fluidframework/odsp-driver-definitions/internal";
import {
ITelemetryLoggerExt,
Expand Down Expand Up @@ -44,10 +44,10 @@ const fileLinkCache = new Map<string, Promise<string>>();
*/
export async function getFileLink(
getToken: TokenFetcher<OdspResourceTokenFetchOptions>,
odspUrlParts: IOdspUrlParts,
resolvedUrl: IOdspResolvedUrl,
logger: ITelemetryLoggerExt,
): Promise<string> {
const cacheKey = `${odspUrlParts.siteUrl}_${odspUrlParts.driveId}_${odspUrlParts.itemId}`;
const cacheKey = `${resolvedUrl.siteUrl}_${resolvedUrl.driveId}_${resolvedUrl.itemId}`;
const maybeFileLinkCacheEntry = fileLinkCache.get(cacheKey);
if (maybeFileLinkCacheEntry !== undefined) {
return maybeFileLinkCacheEntry;
Expand All @@ -61,7 +61,7 @@ export async function getFileLink(
async () =>
runWithRetryForCoherencyAndServiceReadOnlyErrors(
async () =>
getFileLinkWithLocationRedirectionHandling(getToken, odspUrlParts, logger),
getFileLinkWithLocationRedirectionHandling(getToken, resolvedUrl, logger),
"getFileLinkCore",
logger,
),
Expand Down Expand Up @@ -115,32 +115,41 @@ export async function getFileLink(
*/
async function getFileLinkWithLocationRedirectionHandling(
getToken: TokenFetcher<OdspResourceTokenFetchOptions>,
odspUrlParts: IOdspUrlParts,
resolvedUrl: IOdspResolvedUrl,
logger: ITelemetryLoggerExt,
): Promise<string> {
// We can have chains of location redirection one after the other, so have a for loop
// so that we can keep handling the same type of error. Set max number of redirection to 5.
let lastError: unknown;
let locationRedirected = false;
for (let count = 1; count <= 5; count++) {
try {
return await getFileLinkCore(getToken, odspUrlParts, logger);
const fileItem = await getFileItemLite(getToken, resolvedUrl, logger, true);
// Sometimes the siteUrl in the actual file is different from the siteUrl in the resolvedUrl due to location
// redirection. This creates issues in the getSharingInformation call. So we need to update the siteUrl in the
// resolvedUrl to the siteUrl in the fileItem which is the updated siteUrl.
const oldSiteDomain = new URL(resolvedUrl.siteUrl).origin;
const newSiteDomain = new URL(fileItem.sharepointIds.siteUrl).origin;
if (oldSiteDomain !== newSiteDomain) {
locationRedirected = true;
logger.sendTelemetryEvent({
eventName: "LocationRedirectionErrorForGetOdspFileLink",
retryCount: count,
});
renameTenantInOdspResolvedUrl(resolvedUrl, newSiteDomain);
}
return await getFileLinkCore(getToken, resolvedUrl, logger, fileItem);
} catch (error: unknown) {
lastError = error;
// If the getSharingLink call fails with the 401/403/404 error, then it could be due to that the file has moved
// to another location. This could occur in case we have more than 1 tenant rename. In that case, we should retry
// the getFileItemLite call to get the updated fileItem.
if (
isFluidError(error) &&
error.errorType === OdspErrorTypes.fileNotFoundOrAccessDeniedError &&
hasRedirectionLocation(error) &&
error.redirectLocation !== undefined
locationRedirected &&
(error.errorType === OdspErrorTypes.fileNotFoundOrAccessDeniedError ||
error.errorType === OdspErrorTypes.authorizationError)
) {
const redirectLocation = error.redirectLocation;
logger.sendTelemetryEvent({
eventName: "LocationRedirectionErrorForGetOdspFileLink",
retryCount: count,
});
// Generate the new SiteUrl from the redirection location.
const newSiteDomain = new URL(redirectLocation).origin;
const newSiteUrl = `${newSiteDomain}${new URL(odspUrlParts.siteUrl).pathname}`;
odspUrlParts.siteUrl = newSiteUrl;
continue;
}
throw error;
Expand All @@ -153,9 +162,8 @@ async function getFileLinkCore(
getToken: TokenFetcher<OdspResourceTokenFetchOptions>,
odspUrlParts: IOdspUrlParts,
logger: ITelemetryLoggerExt,
fileItem: FileItemLite,
): Promise<string> {
const fileItem = await getFileItemLite(getToken, odspUrlParts, logger, true);

// ODSP link requires extra call to return link that is resistant to file being renamed or moved to different folder
return PerformanceEvent.timedExecAsync(
logger,
Expand Down Expand Up @@ -193,7 +201,6 @@ async function getFileLinkCore(
headers: {
"Content-Type": "application/json;odata=verbose",
"Accept": "application/json;odata=verbose",
"redirect": "manual",
...headers,
},
};
Expand Down Expand Up @@ -280,7 +287,6 @@ async function getFileItemLite(
);

const headers = getHeadersWithAuth(authHeader);
headers.redirect = "manual";
const requestInit = { method, headers };
const response = await fetchHelper(url, requestInit);
additionalProps = response.propsToLog;
Expand All @@ -301,3 +307,37 @@ async function getFileItemLite(
},
);
}

/**
* It takes a resolved url with old siteUrl and patches resolved url with updated site url domain.
* @param odspResolvedUrl - Previous odsp resolved url with older site url.
* @param newSiteDomain - New site domain after the tenant rename.
*/
function renameTenantInOdspResolvedUrl(
odspResolvedUrl: IOdspResolvedUrl,
newSiteDomain: string,
): void {
const newSiteUrl = `${newSiteDomain}${new URL(odspResolvedUrl.siteUrl).pathname}`;
odspResolvedUrl.siteUrl = newSiteUrl;

if (odspResolvedUrl.endpoints.attachmentGETStorageUrl) {
odspResolvedUrl.endpoints.attachmentGETStorageUrl = `${newSiteDomain}${
new URL(odspResolvedUrl.endpoints.attachmentGETStorageUrl).pathname
}`;
}
if (odspResolvedUrl.endpoints.attachmentPOSTStorageUrl) {
odspResolvedUrl.endpoints.attachmentPOSTStorageUrl = `${newSiteDomain}${
new URL(odspResolvedUrl.endpoints.attachmentPOSTStorageUrl).pathname
}`;
}
if (odspResolvedUrl.endpoints.deltaStorageUrl) {
odspResolvedUrl.endpoints.deltaStorageUrl = `${newSiteDomain}${
new URL(odspResolvedUrl.endpoints.deltaStorageUrl).pathname
}`;
}
if (odspResolvedUrl.endpoints.snapshotStorageUrl) {
odspResolvedUrl.endpoints.snapshotStorageUrl = `${newSiteDomain}${
new URL(odspResolvedUrl.endpoints.snapshotStorageUrl).pathname
}`;
}
}
Loading
Loading