-
Notifications
You must be signed in to change notification settings - Fork 535
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
Fix: Creating new Loop pages after SharePoint domain change #19443
Fix: Creating new Loop pages after SharePoint domain change #19443
Conversation
…t-file-line-after-redirect' into philip-scott/fix-get-file-line-after-redirect
…://github.com/Philip-Scott/FluidFramework into philip-scott/fix-get-file-line-after-redirect
Let's add a test for these new changes in |
options: TokenFetchOptionsEx, | ||
fileItem: FileItemLite, | ||
): Promise<IOdspResponse<Response>> { | ||
for (;;) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is the reason for this loop? My understanding from the changes in the code here is that we only want to perform a retry once after we get the 403 error. If this is true, can we structure the code such that we perform retry within the catch block?
let { url, requestInit } = await getRequestInformation...
try {
response = await fetchHelper();
}catch(e){
// 403 error occured and we need to retry with correct headers here
if(error.redirectLocation){
{ url, requestInit } = = await getRequestInformation...
response = await fetchHelper();
}
}
return response;
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Redirects can actually be chained together! One redirect may point to another redirect and so on.
This was actually a pattern taken from packages/loader/container-loader/src/location-redirection-utilities/resolveWithLocationRedirection.ts
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please add comments to explain this logic.
return await fetchHelper(url, requestInit); | ||
} catch (error: any) { | ||
if ( | ||
error.errorType !== DriverErrorTypes.fileNotFoundOrAccessDeniedError || |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Checking for fileNotFoundOrAccessDeniedError
error type seems unrelated to this change.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider doing the following:
class OdspRedirectError {
public static errorType = DriverErrorTypes.fileNotFoundOrAccessDeniedError;
readonly errorType = OdspRedirectError.type;
...
}
if (error?.errorType !== OdspRedirectError .Type || !error?.redirectLocation) {
throw error;
}
or even better - way more readable.
class OdspRedirectError {
public static errorType = DriverErrorTypes.fileNotFoundOrAccessDeniedError;
readonly errorType = OdspRedirectError.type;
static public isRedirectError(error: unknown) {
return error?.errorType === OdspRedirectError.errorType && !!error?.redirect;
}
...
}
if (!OdspRedirectError(error)) {
throw error;
}
...
}
Notice that error does not have to be object (i.e. it can be undefined, null, etc. - we never know)
const newSiteDomain = new URL(error.redirectLocation).origin; | ||
const oldSiteDomain = new URL(odspUrlParts.siteUrl).origin; | ||
|
||
odspUrlParts.siteUrl = odspUrlParts.siteUrl.replace(oldSiteDomain, newSiteDomain); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changing input parameters of a function is not the best programming practice. (Although I do see it in some parts of the existing code already, but let's avoid adding any new ones.) If possible, always return the new value to the caller, and let the caller assign the value as it may.
(Might be irrelevant if we changed the code here to what I suggested above)
@@ -90,6 +93,86 @@ export async function getFileLink( | |||
return fileLink; | |||
} | |||
|
|||
async function getRequestInformation( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Recommend adding code docs to explain the purpose of this function.
@Philip-Scott As far as I remember the |
// database on server becomes readonly. The driver retries for such cases with exponential backup logic. | ||
if (innerMostErrorCode === OdspServiceReadOnlyErrorCode) { | ||
// After a redirect due to a domain change, auth headers are not sent by the browser and can trigger a 403. | ||
if (response?.redirected) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we assert that response?.redirected => 401 or 403? I.e. that we are not missing cases?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does 404 / @error.redirectLocation case also have redirect property on response?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have not seen a 404 redirect error for me to test that this is valid and works. Do you know how one could trigger a 404 redirection error?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, the case below (for 404) has code that deals with redirects. I do not know much about how redirects work (i.e. what error codes can be returned) - @jatgarg & @marcmasmsft are rigth people to consult here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes 404 could be triggered due to domain change.
const storageToken = await storageTokenFetcher(options, "GetFileLinkCore"); | ||
assert( | ||
storageToken !== null, | ||
0x2bb /* "Instrumented token fetcher with throwOnNullToken = true should never return null" */, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Every place that calls storageTokenFetcher() asserts that non-null result is returned. Given that's a wrapper produced by driver, can we at least move this assert into toInstrumentedOdspTokenFetcher() itself?
Or better, TokenFetcher type and force callers to change / assert? That's a bit bigger change, but having null on the type does not help.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mmmm that is actually part of the original code before moving it out to its own function. We can probably follow that up on a separate PR to keep this one small, is that alright?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
raised #20409 to fix it
`'${fileItem.sharepointIds.listItemUniqueId}'`, | ||
)}`, | ||
storageToken, | ||
true, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please add comments with arg names where it's not very clear what is the arg semantics
true, // forceAccessTokenViaAuthorizationHeader
Looks like this is now being handled with #21277 |
Description
After a tenant changes their domain, this lead existing workspaces on the Loop app to stop allowing the creation of new pages. After a redirect, the authorization headers are not sent by the browser. And even if were to just retry with the headers set, SharePoint requires for the auth tokens to be generated for that particular domain.
After these changes, I was able to get new pages to attach again. What I did was catch the redirect on the
fetchHelper(
function, and allow getFileLink to handle the redirect for us. For this to work, we need to request new tokens for the new domain.The fix is sadly not perfect as we need to go through the following flow in order to detect and retry the redirect error.