Redirect draft HTML requests to Asset Manager preflight route [WHIT-3983] - #5783
Redirect draft HTML requests to Asset Manager preflight route [WHIT-3983]#5783ChrisBAshton wants to merge 2 commits into
Conversation
6913bd8 to
7d0296a
Compare
7d0296a to
c9bbd68
Compare
c9bbd68 to
5e38e49
Compare
6e8e720 to
7b7d6a9
Compare
7b7d6a9 to
a539ae5
Compare
a539ae5 to
1e60501
Compare
| # rather than introducing a second cookie for the stack in front of us | ||
| # to have to forward correctly. | ||
| def redirect_to_asset_manager_preflight_if_required | ||
| return unless request.get? |
See next commit for details.
55f2bc5 to
e5cfd14
Compare
Start using the new route we added in the previous commit. A draft document can contain several draft images, and each one independently triggers its own Asset Manager/Signon authentication handshake when there's no existing Asset Manager session. Because those handshakes can complete out of order, they invalidate each other's session state and the images fail to load. To avoid this, we make sure a single Asset Manager session has already been established before rendering any draft page that might contain draft images. The AssetManagerPreflightController loads a single, permanent, harmless placeholder image from Asset Manager's draft assets host. That's enough to complete one Signon handshake and establish part of the Frontend session cookie as denoting that an Asset Manager handshake has completed. The user should already have an active Signon session by the time they reach a draft preview, so this is just completing a handshake, not asking them to sign in again. Once that's done - or once we've given it a reasonable amount of time to finish - we bounce the user back to the page they originally asked for. From then on, every image request on that page reuses the now-warm Asset Manager session instead of starting its own handshake.
e5cfd14 to
14b8684
Compare
| # Favicon redirect | ||
| get "/favicon.ico", to: "favicon#redirect_to_asset" | ||
|
|
||
| get "/draft-asset-preflight", to: "asset_manager_preflight#show", as: :asset_manager_preflight |
There was a problem hiding this comment.
Could you move this under the /api section? (we're trying to make this file as alphabetical as possible given the constraints).
As of alphagov/whitehall#11667, images can now be uploaded to the draft stack, meaning Frontend needs to be able to render the draft images in the page when on draft preview. Unlike live images, draft images require the user to be authenticated (via Signon) and for an Asset Manager session to exist. When rendering a draft asset, the request for the asset from Asset Manager is redirected through a chain of calls including Authenticating Proxy and Signon, and if 1) the user is logged into Signon, and 2) they have permission to view the asset, the asset is then downloaded. Subsequent attempts to render any asset avoids the OAuth round-trip, as the user already has a warm Asset Manager session cookie by then. The problem arises when more than one draft asset is included in a page, and a user is coming to the page cold. When rendering the page, every draft asset begins its own authentication journey, and the various callbacks and tokens will often arrive out of order, meaning no session takes hold. The images fail to load, and subsequent refreshes of the page rarely help (unless the order of requests sent and received happens in a very particular order). Visiting a draft asset in isolation, e.g. on a new tab, forces the session cookie and then subsequent attempts to render a page full of draft assets will succeed. In #5783 we explored adding a preflight mechanism to Frontend whereby 'cold' users are redirected to a page containing a single asset, and then redirected back to their original page. Unfortunately our nginx config would require that the single-asset-page be added to an allowlist of pages that are allowed to set cookies, and even then, subsequent pages would not be allowed to read the cookie, which is then clobbered to an empty string: https://github.com/alphagov/govuk-helm-charts/blob/3f15020fa87e412e8e67ac9c1e1339030c4ef440/charts/app-config/templates/router-nginx-config.tpl#L116-L149 The solution is to force an asset to download at the beginning of the page, before even attempting to render the rest of the page. Various Javascript attempts were looked into here, but there was no reliable means of blocking rendering and waiting for the asset request to complete. Instead, we can rely on the native behaviour of the browser: treat the image as a JavaScript source instead and attempt to download that in the head. That is a blocking operation, and the full OAuth redirect chain is followed before eventually the image is returned. That clearly isn't JavaScript, so the console will error, but as the 'script' is external, its error doesn't affect any other JavaScript in the page, so the console syntax error is a no-op. The blast radius is small - we only inject this request on the draft stack, so only publishers will see it, and the real world ramifications are perhaps a very slight delay in the rendering of the page. Alongside the change to the layout, I've added tests for all of the environments listed in https://docs.publishing.service.gov.uk/manual/environments.html#determining-your-environment Jira: https://gov-uk.atlassian.net/browse/WHIT-3983
As of alphagov/whitehall#11667, images can now be uploaded to the draft stack, meaning Frontend needs to be able to render the draft images in the page when on draft preview. Unlike live images, draft images require the user to be authenticated (via Signon) and for an Asset Manager session to exist. When rendering a draft asset, the request for the asset from Asset Manager is redirected through a chain of calls including Authenticating Proxy and Signon, and if 1) the user is logged into Signon, and 2) they have permission to view the asset, the asset is then downloaded. Subsequent attempts to render any asset avoids the OAuth round-trip, as the user already has a warm Asset Manager session cookie by then. The problem arises when more than one draft asset is included in a page, and a user is coming to the page cold. When rendering the page, every draft asset begins its own authentication journey, and the various callbacks and tokens will often arrive out of order, meaning no session takes hold. The images fail to load, and subsequent refreshes of the page rarely help (unless the order of requests sent and received happens in a very particular order). Visiting a draft asset in isolation, e.g. on a new tab, forces the session cookie and then subsequent attempts to render a page full of draft assets will succeed. In #5783 we explored adding a preflight mechanism to Frontend whereby 'cold' users are redirected to a page containing a single asset, and then redirected back to their original page. Unfortunately our nginx config would require that the single-asset-page be added to an allowlist of pages that are allowed to set cookies, and even then, subsequent pages would not be allowed to read the cookie, which is then clobbered to an empty string: https://github.com/alphagov/govuk-helm-charts/blob/3f15020fa87e412e8e67ac9c1e1339030c4ef440/charts/app-config/templates/router-nginx-config.tpl#L116-L149 The solution is to force an asset to download at the beginning of the page, before even attempting to render the rest of the page. Various Javascript attempts were looked into here, but there was no reliable means of blocking rendering and waiting for the asset request to complete. Instead, we can rely on the native behaviour of the browser: treat the image as a JavaScript source instead and attempt to download that in the head. That is a blocking operation, and the full OAuth redirect chain is followed before eventually the image is returned. That clearly isn't JavaScript, so the console will error, but as the 'script' is external, its error doesn't affect any other JavaScript in the page, so the console syntax error is a no-op. The blast radius is small - we only inject this request on the draft stack, so only publishers will see it, and the real world ramifications are perhaps a very slight delay in the rendering of the page. The asset chosen is the default lead image placeholder referenced throughout Frontend. It is the same asset used in a similar solution in Whitehall: alphagov/whitehall#11775 Alongside the change to the layout, I've added tests for all of the environments listed in https://docs.publishing.service.gov.uk/manual/environments.html#determining-your-environment Jira: https://gov-uk.atlassian.net/browse/WHIT-3983
As of alphagov/whitehall#11667, images can now be uploaded to the draft stack, meaning Frontend needs to be able to render the draft images in the page when on draft preview. Unlike live images, draft images require the user to be authenticated (via Signon) and for an Asset Manager session to exist. When rendering a draft asset, the request for the asset from Asset Manager is redirected through a chain of calls including Authenticating Proxy and Signon, and if 1) the user is logged into Signon, and 2) they have permission to view the asset, the asset is then downloaded. Subsequent attempts to render any asset avoids the OAuth round-trip, as the user already has a warm Asset Manager session cookie by then. The problem arises when more than one draft asset is included in a page, and a user is coming to the page cold. When rendering the page, every draft asset begins its own authentication journey, and the various callbacks and tokens will often arrive out of order, meaning no session takes hold. The images fail to load, and subsequent refreshes of the page rarely help (unless the order of requests sent and received happens in a very particular order). Visiting a draft asset in isolation, e.g. on a new tab, forces the session cookie and then subsequent attempts to render a page full of draft assets will succeed. In #5783 we explored adding a preflight mechanism to Frontend whereby 'cold' users are redirected to a page containing a single asset, and then redirected back to their original page. Unfortunately our nginx config would require that the single-asset-page be added to an allowlist of pages that are allowed to set cookies, and even then, subsequent pages would not be allowed to read the cookie, which is then clobbered to an empty string: https://github.com/alphagov/govuk-helm-charts/blob/3f15020fa87e412e8e67ac9c1e1339030c4ef440/charts/app-config/templates/router-nginx-config.tpl#L116-L149 The solution is to force an asset to download at the beginning of the page, before even attempting to render the rest of the page. Various Javascript attempts were looked into here, but there was no reliable means of blocking rendering and waiting for the asset request to complete. Instead, we can rely on the native behaviour of the browser: treat the image as a JavaScript source instead and attempt to download that in the head. That is a blocking operation, and the full OAuth redirect chain is followed before eventually the image is returned. That clearly isn't JavaScript, so the console will error, but as the 'script' is external, its error doesn't affect any other JavaScript in the page, so the console syntax error is a no-op. The blast radius is small - we only inject this request on the draft stack, so only publishers will see it, and the real world ramifications are perhaps a very slight delay in the rendering of the page. The asset chosen is the default lead image placeholder referenced throughout Frontend. It is the same asset used in a similar solution in Whitehall: alphagov/whitehall#11775 Alongside the change to the layout, I've added tests for all of the environments listed in https://docs.publishing.service.gov.uk/manual/environments.html#determining-your-environment Jira: https://gov-uk.atlassian.net/browse/WHIT-3983
|
Closing in favour of #5788. This approach wouldn't work because, as per our docs:
The docs point to Puppet, which was replaced by Kubernetes a couple of years ago, but the logic still stands. There is VCL in govuk-helm-charts that prevents |
What
This PR introduces a new AssetManagerPreflightController, which forces an Asset Manager session to be set up.
The AssetManagerPreflightController loads a single, permanent, harmless placeholder image from Asset Manager's draft assets host. That's enough to complete one Signon handshake and establish an Asset Manager session cookie in the browser - the user should already have an active Signon session by the time they reach a draft preview, so this is just completing a handshake, not asking them to sign in again.
Once that's done - or once we've given it a reasonable amount of time to finish - we bounce the user back to the page they originally asked for. From then on, every image request on that page reuses the now-warm Asset Manager session instead of starting its own handshake.
Why
Last week, we shipped a change in Whitehall, changing how images work and bringing them in line with attachments. Images are now uploaded to the draft stack by default, when uploaded to a draft document - and only become 'live' when the document is published. Access-limiting metadata on the document (individual/organisation access-limiting, auth-bypass tokens) are also proliferated through to the assets in the same way as they are for attachments.
Unlike attachments, which are exclusively viewed in isolation in browser (e.g. a PDF in its own tab) or downloaded at their own endpoint, images are embedded in the page. Whereas attachments, by virtue of being visited/downloaded in isolation, funnel publishers through Signon and then allow the visit/download, images are not able to do so because they live within the page.
If only one image is embedded in the page, it works: the publisher is already signed into Signon at the point of getting through to draft-frontend, and so under the hood when the browser renders the DOM, the draft image is parsed, several round trip network requests are made to Asset Manager and Signon, and if the user is authorised, the image downloads and renders.
The problem occurs when we're dealing with more than one draft asset in the page. A draft document can contain several draft images, and each one independently triggers its own Asset Manager/Signon authentication handshake when there's no existing Asset Manager session. Because those handshakes can complete out of order, they invalidate each other's session state and the images fail to load.
To avoid this, we need to make sure a single Asset Manager session has already been established before rendering any draft page that might contain draft images.
An equivalent fix has been applied to Whitehall, where the same issue occurs when viewing the Images tab of a draft document: alphagov/whitehall#11775
This is seen as a suboptimal fix. We will raise a ticket on the Publishing Tech Debt board to reconsider a better engineered solution to the problem, but any alternatives we've considered so far require significantly more work and come with additional risk. Given the draft assets issue is affecting publishers today, we want to prioritise what we think is an acceptable patch here and pay down the technical debt later.
Jira card: https://gov-uk.atlassian.net/browse/WHIT-3983
Visual changes