Skip to content

Commit c1311b2

Browse files
jatgargJatin Garg
andauthored
[main > release/client/2.3]: Handle Location redirection for getSharingInformation call (#22551) (#22619)
## Description Main PR Link: #22551 [AB#15188](https://dev.azure.com/fluidframework/internal/_workitems/edit/15188) Handle location redirection when calling getSharingInformation in ODSP Driver. Since this call does not include the v2.0 which calls the API without the drive and item ID, these calls fail if the tenant has been renamed. Sometimes this creates issues when creating a new page, since the user could not get the sharing link to the new page. GetFileItem call also returns the new siteUrl to which the file has been moved. Use that to detect the new domain and then change the resolved url accordingly. Co-authored-by: Jatin Garg <[email protected]>
1 parent 6b9f122 commit c1311b2

File tree

2 files changed

+115
-94
lines changed

2 files changed

+115
-94
lines changed

packages/drivers/odsp-driver/src/getFileLink.ts

Lines changed: 54 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,12 @@
66
import type { ITelemetryBaseProperties } from "@fluidframework/core-interfaces";
77
import { assert } from "@fluidframework/core-utils/internal";
88
import { NonRetryableError, runWithRetry } from "@fluidframework/driver-utils/internal";
9-
import { hasRedirectionLocation } from "@fluidframework/odsp-doclib-utils/internal";
109
import {
1110
IOdspUrlParts,
1211
OdspErrorTypes,
1312
OdspResourceTokenFetchOptions,
1413
TokenFetcher,
14+
type IOdspResolvedUrl,
1515
} from "@fluidframework/odsp-driver-definitions/internal";
1616
import {
1717
ITelemetryLoggerExt,
@@ -44,10 +44,10 @@ const fileLinkCache = new Map<string, Promise<string>>();
4444
*/
4545
export async function getFileLink(
4646
getToken: TokenFetcher<OdspResourceTokenFetchOptions>,
47-
odspUrlParts: IOdspUrlParts,
47+
resolvedUrl: IOdspResolvedUrl,
4848
logger: ITelemetryLoggerExt,
4949
): Promise<string> {
50-
const cacheKey = `${odspUrlParts.siteUrl}_${odspUrlParts.driveId}_${odspUrlParts.itemId}`;
50+
const cacheKey = `${resolvedUrl.siteUrl}_${resolvedUrl.driveId}_${resolvedUrl.itemId}`;
5151
const maybeFileLinkCacheEntry = fileLinkCache.get(cacheKey);
5252
if (maybeFileLinkCacheEntry !== undefined) {
5353
return maybeFileLinkCacheEntry;
@@ -61,7 +61,7 @@ export async function getFileLink(
6161
async () =>
6262
runWithRetryForCoherencyAndServiceReadOnlyErrors(
6363
async () =>
64-
getFileLinkWithLocationRedirectionHandling(getToken, odspUrlParts, logger),
64+
getFileLinkWithLocationRedirectionHandling(getToken, resolvedUrl, logger),
6565
"getFileLinkCore",
6666
logger,
6767
),
@@ -116,32 +116,41 @@ export async function getFileLink(
116116
*/
117117
async function getFileLinkWithLocationRedirectionHandling(
118118
getToken: TokenFetcher<OdspResourceTokenFetchOptions>,
119-
odspUrlParts: IOdspUrlParts,
119+
resolvedUrl: IOdspResolvedUrl,
120120
logger: ITelemetryLoggerExt,
121121
): Promise<string> {
122122
// We can have chains of location redirection one after the other, so have a for loop
123123
// so that we can keep handling the same type of error. Set max number of redirection to 5.
124124
let lastError: unknown;
125+
let locationRedirected = false;
125126
for (let count = 1; count <= 5; count++) {
126127
try {
127-
return await getFileLinkCore(getToken, odspUrlParts, logger);
128+
const fileItem = await getFileItemLite(getToken, resolvedUrl, logger, true);
129+
// Sometimes the siteUrl in the actual file is different from the siteUrl in the resolvedUrl due to location
130+
// redirection. This creates issues in the getSharingInformation call. So we need to update the siteUrl in the
131+
// resolvedUrl to the siteUrl in the fileItem which is the updated siteUrl.
132+
const oldSiteDomain = new URL(resolvedUrl.siteUrl).origin;
133+
const newSiteDomain = new URL(fileItem.sharepointIds.siteUrl).origin;
134+
if (oldSiteDomain !== newSiteDomain) {
135+
locationRedirected = true;
136+
logger.sendTelemetryEvent({
137+
eventName: "LocationRedirectionErrorForGetOdspFileLink",
138+
retryCount: count,
139+
});
140+
renameTenantInOdspResolvedUrl(resolvedUrl, newSiteDomain);
141+
}
142+
return await getFileLinkCore(getToken, resolvedUrl, logger, fileItem);
128143
} catch (error: unknown) {
129144
lastError = error;
145+
// If the getSharingLink call fails with the 401/403/404 error, then it could be due to that the file has moved
146+
// to another location. This could occur in case we have more than 1 tenant rename. In that case, we should retry
147+
// the getFileItemLite call to get the updated fileItem.
130148
if (
131149
isFluidError(error) &&
132-
error.errorType === OdspErrorTypes.fileNotFoundOrAccessDeniedError &&
133-
hasRedirectionLocation(error) &&
134-
error.redirectLocation !== undefined
150+
locationRedirected &&
151+
(error.errorType === OdspErrorTypes.fileNotFoundOrAccessDeniedError ||
152+
error.errorType === OdspErrorTypes.authorizationError)
135153
) {
136-
const redirectLocation = error.redirectLocation;
137-
logger.sendTelemetryEvent({
138-
eventName: "LocationRedirectionErrorForGetOdspFileLink",
139-
retryCount: count,
140-
});
141-
// Generate the new SiteUrl from the redirection location.
142-
const newSiteDomain = new URL(redirectLocation).origin;
143-
const newSiteUrl = `${newSiteDomain}${new URL(odspUrlParts.siteUrl).pathname}`;
144-
odspUrlParts.siteUrl = newSiteUrl;
145154
continue;
146155
}
147156
throw error;
@@ -154,9 +163,8 @@ async function getFileLinkCore(
154163
getToken: TokenFetcher<OdspResourceTokenFetchOptions>,
155164
odspUrlParts: IOdspUrlParts,
156165
logger: ITelemetryLoggerExt,
166+
fileItem: FileItemLite,
157167
): Promise<string> {
158-
const fileItem = await getFileItemLite(getToken, odspUrlParts, logger, true);
159-
160168
// ODSP link requires extra call to return link that is resistant to file being renamed or moved to different folder
161169
return PerformanceEvent.timedExecAsync(
162170
logger,
@@ -194,7 +202,6 @@ async function getFileLinkCore(
194202
headers: {
195203
"Content-Type": "application/json;odata=verbose",
196204
"Accept": "application/json;odata=verbose",
197-
"redirect": "manual",
198205
...headers,
199206
},
200207
};
@@ -281,7 +288,6 @@ async function getFileItemLite(
281288
);
282289

283290
const headers = getHeadersWithAuth(authHeader);
284-
headers.redirect = "manual";
285291
const requestInit = { method, headers };
286292
const response = await fetchHelper(url, requestInit);
287293
additionalProps = response.propsToLog;
@@ -302,3 +308,29 @@ async function getFileItemLite(
302308
},
303309
);
304310
}
311+
312+
/**
313+
* It takes a resolved url with old siteUrl and patches resolved url with updated site url domain.
314+
* @param odspResolvedUrl - Previous odsp resolved url with older site url.
315+
* @param newSiteDomain - New site domain after the tenant rename.
316+
*/
317+
function renameTenantInOdspResolvedUrl(
318+
odspResolvedUrl: IOdspResolvedUrl,
319+
newSiteDomain: string,
320+
): void {
321+
const newSiteUrl = `${newSiteDomain}${new URL(odspResolvedUrl.siteUrl).pathname}`;
322+
odspResolvedUrl.siteUrl = newSiteUrl;
323+
324+
if (odspResolvedUrl.endpoints.attachmentGETStorageUrl) {
325+
odspResolvedUrl.endpoints.attachmentGETStorageUrl = `${newSiteDomain}${new URL(odspResolvedUrl.endpoints.attachmentGETStorageUrl).pathname}`;
326+
}
327+
if (odspResolvedUrl.endpoints.attachmentPOSTStorageUrl) {
328+
odspResolvedUrl.endpoints.attachmentPOSTStorageUrl = `${newSiteDomain}${new URL(odspResolvedUrl.endpoints.attachmentPOSTStorageUrl).pathname}`;
329+
}
330+
if (odspResolvedUrl.endpoints.deltaStorageUrl) {
331+
odspResolvedUrl.endpoints.deltaStorageUrl = `${newSiteDomain}${new URL(odspResolvedUrl.endpoints.deltaStorageUrl).pathname}`;
332+
}
333+
if (odspResolvedUrl.endpoints.snapshotStorageUrl) {
334+
odspResolvedUrl.endpoints.snapshotStorageUrl = `${newSiteDomain}${new URL(odspResolvedUrl.endpoints.snapshotStorageUrl).pathname}`;
335+
}
336+
}

0 commit comments

Comments
 (0)