Overview

We have the route

canvas.boot.entity:
  path: '/canvas/editor/{entity_type}/{entity}'
  defaults:
    _controller: 'Drupal\canvas\Controller\CanvasController'
    _title: 'Drupal Canvas'
  requirements:
    _canvas_authentication_required: TRUE
    _canvas_component_tree_edit_access: TRUE
    _entity_access: 'entity.update'
  options:
    parameters:
      entity:
        type: entity:{entity_type}
    _format: 'json'

But in \Drupal\canvas\PathProcessor\CanvasPathProcessor::processInbound we have

if (str_starts_with($path, '/canvas/') && !str_starts_with($path, '/canvas/api')) {
      return '/canvas';
    }

So at least in \Drupal\canvas\Controller\CanvasController::__invoke we will never call if from this route
Is this intentional?
I created an MR ensure I am correct and only \Drupal\Tests\canvas\Kernel\LibraryInfoAlterTest::testTransformMounting failed because it calls the controller directly.

Is canvas.boot.entity only there so we make links to the path? If so why do have logic around $entity_type and $entity in \Drupal\canvas\Controller\CanvasController::__invoke because these will always be NULL?

Proposed resolution

Not sure

User interface changes

Issue fork canvas-3567161

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

tedbow created an issue. See original summary.

tedbow’s picture

Title: Testing is canvas.boot.entity route » tedbow: test issue ignore,
Assigned: Unassigned » tedbow

tedbow’s picture

Title: tedbow: test issue ignore, » Either canvas.boot.entity is unused or it is very confusing
Assigned: tedbow » Unassigned
Issue summary: View changes
Priority: Minor » Normal
tedbow’s picture

Issue summary: View changes
tedbow’s picture

Status: Active » Needs review

Setting to Needs Review for someone to double check my assumptions

wim leers’s picture

Assigned: Unassigned » tedbow
Status: Needs review » Postponed (maintainer needs more info)
Related issues: +#3529836: Enable starting with an empty XB UI (so without first having to create an entity with a component tree)

Grepping the codebase for that route name indicates \Drupal\canvas\Controller\ApiContentControllers::getEntityOperations() uses this.

#3529836: Enable starting with an empty XB UI (so without first having to create an entity with a component tree) introduced this. I think you'll find answers there. That's where I'd start looking.

tedbow’s picture

Assigned: tedbow » wim leers
Status: Postponed (maintainer needs more info) » Needs work
wim leers’s picture

Title: Either canvas.boot.entity is unused or it is very confusing » Either the canvas.boot.entity route is unused or it is very confusing
Component: … to be triaged » Code
Assigned: wim leers » tedbow
Related issues: +#3502887: Prepare for avoiding full page reloads: move entity type and ID from base path and into routing parameters

Q for Ted: https://git.drupalcode.org/project/canvas/-/merge_requests/466/diffs#not...


A for Ted:

And … guess what … I literally called this out as a concern over at https://git.drupalcode.org/project/canvas/-/merge_requests/4/diffs#note_..., which was merged without my approval 😅

The commit that introduced this: https://git.drupalcode.org/project/canvas/-/merge_requests/4/diffs?commi...


⇒ AFAICT this was an unintentional side effect. Needs further investigation to understand the consequences.

wim leers’s picture

Title: Either the canvas.boot.entity route is unused or it is very confusing » The canvas.boot.entity route is never matched due to inbound path processor regression introduced in #3502887
Assigned: tedbow » Unassigned
StatusFileSize
new1.16 MB
new619.48 KB
new139.16 KB

This was almost a security vulnerability, because due to #3502887: Prepare for avoiding full page reloads: move entity type and ID from base path and into routing parameters, the wrong route is matched:

Which causes an fewer route requirements to be checked:

So, if for the current user those route requirements are checked, so just this one:

_canvas_ui_access: 'TRUE'

Then the resulting call to \Drupal\canvas\Access\CanvasUiAccessCheck::access() allows access, then I'm getting a 200, not a 403! 😱

However, thanks to it then booting the Canvas UI, and the Canvas UI trying to fetch the right info, it ends up not revealing anything:

jessebaker’s picture

Hopefully I can give a bit more history/context as to why this change came about. Then someone with more understanding of the Drupal side of things can weigh in on if there is a better approach.

At the start the Canvas React app could only be accessed by going to a URL for a specific entity that was to be edited.

As Canvas grew that made less sense - you might want to navigate straight to a Code Component in the Code Editor for instance. And then maybe we want /canvas to allow people to see the UI without any task in mind and then pick what they want to do from the menu.

To enable this Canvas React app is a Single Page Application - all the routing and loading of data is handled by the client based on the URL route after /canvas.

The intent then is that whenever a user goes to any URL that starts /canvas (e.g. /canvas, /canvas/editor/, /canvas/editor/canvas_page/1, canvas/doesnt_exist) they are served essentially the same practically blank document with the React App JS assets. The React app then initialises, examines the route parameters and decides which data to fetch (so if the url is /canvas/editor/canvas_page/1 it will immediately fetch the layout/model for that entity from the API).

Essentially the desired outcome is that Drupal ignores everything after the /canvas part and no matter what comes next in the URL path Drupal always serves the same page.


One wrinkle in the above that exists at the moment is that we have not yet fully solved the technical challenge of navigating from one edited entity to another without forcing the whole page to reload. Certain parts of the app still assume/rely on data being present on page load rather than being dynamically fetched. This is being addressed in #3538104: Add seamless SPA navigation between editing different pages/content.
penyaskito’s picture

Proposed resolution
Not sure

Options:
a) remove canvas.boot.entity.
b) If we use that as a "helper" for e.g. generating links, let's just ensure it has the same requirements than canvas.boot.empty.

a) Keeping it is still useful for e.g. generating links from the admin UI in the backend.
b) Then I was thinking that canvas.boot.entity should have the same requirements than canvas.boot.empty. But as Drupal already checks access before rendering a link, this would require boilerplate code when we want to have a link at the backend.

This is not a problem I've seen before, as we traditionally don't often have SPAs inside Drupal. My suggestion is:

a) We don't remove canvas.boot.entity
b) We add very explicit comments saying that this is not run when the actual page is visited as the routing actually happens client-side. But still we want to know the route server-side and check if the user will have access to it in the UI.
c) We add a cross-referenced comment with @see in routing.yml and wherever those permissions are checked client-side, pointing to each other, ensuring that we keep those in sync.

penyaskito’s picture

Related: #3568238: Create route for content template . If the outcome of this is NOT what I described in #12, then #3568238: Create route for content template should be a Won't fix. Because it will introduce the same problem!

vishalkhode’s picture

@penyaskito: This is the same reason we want to add a new route for Content Template. I agree we should keep the existing route unchanged and add comments to clearly document why this route exists and the rationale behind it.

As Drupal already checks access before rendering a link.

wim leers’s picture

Agreed with #12 + #13 + #14.


I'm not yet convinced that this is long-term desirable:

Essentially the desired outcome is that Drupal ignores everything after the /canvas part and no matter what comes next in the URL path Drupal always serves the same page.

… nor that it is required for #3502887: Prepare for avoiding full page reloads: move entity type and ID from base path and into routing parameters or #3538104: Add seamless SPA navigation between editing different pages/content. I think that making Drupal ignore everything after /canvas was the sane/simple choice to keep the Canvas JS codebase as simple as possible for now.

While I understand that this is the intended behavior on the client side, I think that this misses an important optimization opportunity: the canvas.boot.entity route (/canvas/editor/{entity_type}/{entity}) could pre-load data that otherwise requires additional requests.

Loading /canvas/editor/canvas_page/1 currently triggers the following API requests:

Those requests to /canvas/api/v0/layout/canvas_page/1 and /canvas/api/v0/form/content-entity/canvas_page/1/default are the ones that are specific to "the entity being booted into Canvas". They could be preloaded, server-side, and embedded in the first response. They're also the slowest. (And that's for the simplest content entity in existence, with an empty component tree, with zero network latency.)

IOW: I think that eventually, we'll actually want to make the client a bit more complex, to face the reality that Canvas would be too slow to boot (especially on high-latency networks) to achieve the desired front-end performance.

We've not yet done any meaningful performance optimizations on either the server or client side. Some things here and there, yes. But the focus has been, and still is, on feature expansion. Not performance or polish. That will change eventually.

IOW: I think making the server ignore everything after /canvas is the right call right now to prioritize iteration and features, and avoid premature optimization. But I doubt it we will want to keep it like this.

So, IMHO, #12's a+b+c make sense, and I would add d:

d) Update CanvasPathProcessor::processInbound() to explain/justify as @jessebaker did here why for now Canvas' server side pretends it only serves /canvas

which would have answered my question on the original MR: https://git.drupalcode.org/project/canvas/-/merge_requests/4/diffs#note_...

jessebaker’s picture

Your comment there makes sense and is in line with what I’m thinking.

Once #3538104: Add seamless SPA navigation between editing different pages/content lands then there won’t be additional page loads being asked of Drupal after the initial one because all the subsequent navigation within Canvas will be handled purely client side via fetching the data.

The ideal of preloading data would be a nice optimisation in the longer term however it's going to be an optimisation for that initial page load. I think we’d be better off putting our efforts into optimising that new way of moving between pages once #3538104 lands.

Having a nice generic way of preloading various data based on the initial route the user lands on would be cool - be that a page or a template to edit or a component in the code editor.