Problem/Motivation
In #2942975-33: [PP-1] Expose Layout Builder data to REST and JSON:API @Wim Leers pointed out that there was no field level access to Layout Builder's override field.
He also pointed out the implications of exposing this field over the API for read and write access.
For read:
Once we make this available via APIs, we have to support it forever. Are we confident enough to commit to that?
For write:
To be more precise: there is no active need for validation logic, because the UI is structured in such a way that only trusted logic ever generates values to be stored.
My biggest fear is letting API clients writing layout information. AFAICT validation is absent. Which is fine for the coupled UI because it naturally limits what data can be saved, but not for a decoupled UI. I've explained why this can easily lead to broken sites at minimum, and information disclosure at worst.
For more details read the linked comment.
To avoid this problem we can first not expose this field via the API at all.
If we expose the field over the API it would be effectively just that same as saying we support modules getting the json string decoding it and figuring out the layout from there. Because this is effectively what a decoupled application would be doing.
And once we expose we can never change that structure. Though in theory we could write a BC layer to with a normalizer to keep the format the same for sites that want it the same. But considering how much discussion there was around #2751325: All serialized values are strings, should be integers/booleans when appropriate and #2768651: Let TimestampItem (de)normalize to/from RFC3339 timestamps, not UNIX timestamps, for better DX I think that is something we want to avoid at all costs if we can.
Therefore we could be free to change the raw array structure the field was stored in until we make the field available over the API.
original problem
While reviewing #2942975: [PP-1] Expose Layout Builder data to REST and JSON:API, I noticed that anybody who is allowed to update a given entity is also allowed to modify its layout. Given the existence of the configure any layout permission, I don't think this is intentional?
Proposed resolution
Implement \Drupal\layout_builder\Field\LayoutSectionItemList::defaultAccess() and always return access forbidden.
We could instead implement \Drupal\layout_builder\Field\LayoutSectionItemList::access() but this would mean that no code could alter that access. Maybe that is what we want.
If we chose defaultAccess() we could still give the option to those really living on the edge to alter the access and provide their own normalizers. Of course they would get no BC support. I think the fact that they would need to both alter the access and provide their own normalizers would make it very clear we don't offer BC. We can't be expected to offer BC for custom normalizers.
If implemented access()it would still be possible for modules to alter the access by implementing hook_field_info_alter and switching their own field class similar to language_field_info_alter(). Perhaps this would be a stronger signal that we don't intend to offer API BC support for the internal field structure.
If we forbid all API access to the field now we could in stages:
- Provide read access.
At this point we would have to be fairly confident that the field structure is not going to change or that we would provide a BC normalization layer to keep it in the same format for existing sites. - Provide write access
At this point we would also have to have validation for the field type.
It would also be possible for these 2 stages to happen in contrib by using the above mentioned methods of altering the field access and providing a normalizer. This may give more assurance the we get the API we offer to decoupled apps(as opposed to code level API) right the first time. We could have real world use cases before we are stuck with a particular format forever.
Remaining tasks
Discuss.
User interface changes
None
API changes
Instead of getting an empty array for the layout field via REST the field would not be return at all for Rest responses.
Data model changes
None
Release notes snippet
How to test
This comes from comment #12:
You could test by:
Turn on REST and Layout Builder
Enable Layout overrides for articles
Enable ReST Get requests on Articles. Probably easiest with https://www.drupal.org/project/restui
Make a get request for an article node with an override https://www.drupal.org/docs/8/core/modules/rest/2-get-for-reading-conten...
Inspect the JSON response
Without the patch: There should be a key for layout_builder__layout
You probably need to save the node in between requests. No need to change the layout. Just change the title to make sure you aren't getting a cached response.
With the patch: There should NOT be a key for layout_builder__layout
| Comment | File | Size | Author |
|---|---|---|---|
| #42 | 3028301-rest-41-interdiff.txt | 2.08 KB | tim.plunkett |
| #42 | 3028301-rest-41.patch | 15.58 KB | tim.plunkett |
| #40 | 3028301-rest-40-interdiff.txt | 6.19 KB | tim.plunkett |
| #40 | 3028301-rest-40.patch | 15.55 KB | tim.plunkett |
| #34 | 3028301-rest-34-interdiff.txt | 2.1 KB | tim.plunkett |
Comments
Comment #2
wim leersComment #4
wim leersWell that was silly. (Canceled testing #2.)
Comment #5
tedbow@Wim Leers thanks for finding this.
No that is not the intent.
Here are is unit test for access.
Comment #7
tedbowWow I forgot
@groupfor the new test file. Totally never did that before 😉Comment #8
tedbowUpdated/repurposed the issue.
This patch implements
\Drupal\layout_builder\Field\LayoutSectionItemList::defaultAccess()See the summary for reasons
I removed the unit test because there is no logic in the method now.
Comment #9
tedbowComment #10
tim.plunkettMoving the tag here
Comment #11
kristen polThe patch in #8 is clear but I'm not sure how to test this manually. Thoughts?
Comment #12
tedbow@Kristen Pol thanks for the offer to test. Basically this patch should make it so the field 'layout_builder__layout' is return on REST GET request.
You could test by:
Comment #13
tedbowI realized now after #2724823: EntityResource: read-only (GET) support for configuration entities you can actually make GET rest request for config entities. So we need to make sure that the sections are not return with that entity either. Since it is not a fieldable entity we will actually have to have our own normalizer.
This patch just adds the normalizer. We will need test for the normalizer and probably rest test to show it does not get returned.
Comment #14
tedbowChanging title to encompass more that just the field
Comment #15
xjmThe overall plan described in the issue summary sounds correct to me from a release management perspective. I checked with @tedbow and right now HEAD just gives you:
[]So there's no real BC break for the beta by adding this restriction now.
+1 for forbidding access by default, but still making a workaround possible if needed. What's missing from the patch right now is:
Other than that, I'm comfortable signing off on the general approach here from a release management perspective, and we also have @Wim Leers' (implied) signoff as API-first maintainer.
Thanks!
Comment #16
xjmTo clarify #15 after discussing with Ted in Slack, we need to document in the patch why we're restricting access and why we're not exposing sections in the normalizer, with @todos. Let's see what that looks like firstt. And then if those docs do mention overriding the access as a workaround, then they might need the warning of that not having BC.
Comment #17
tedbow@xjm thanks for the comment
This patch adds REST coverage for the override field
Tests override field with rest:
We still need GET test for defaults. Other methods are not supported on config entities.
Comment #19
tedbowThis patch adds the rest test coverage for layout defaults.
Comment #22
tedbowNo sure why this is failing. It is passing for me locally.
I am going to try to switch to
json_decodedirectly to decode. Just running this 1 test.Comment #24
wim leersAFAICT this is out of scope for this issue, but it's just here for testing?
👍
Whu not subclass
\Drupal\Tests\rest\Functional\ResourceTestBase??
I think this might be the culprit.
\Psr\Http\Message\StreamInterface::getContents()'s docs say:Returns the remaining contents in a stringremaining only!
This is a big WTF for sure.
It's also why all REST/JSON:API test coverage uses
(string) $response->getBody().And … that's another reason to use
ResourceTestBase:)Comment #25
tedbowtestLayoutEntityDisplay().defaultAccess()and notaccess(). Just checking to be sure.ResourceTestBase🤞 for tests passing on drupalci
Comment #27
tedbowNotEncodableValueExceptionexception above.@tim.plunkett help me debug this and we think it might be related to #2985882: Workaround for "Call to a member function getLabel() after enabling layout_builder"
For now I can't get this to work unless I clear the field cache. We can tell the field is actually there because the override gets stored in setup().
This allows remove the test module.
Comment #29
tedbowWhoops I don't need the
->getContents().Comment #30
kristen polAdding "how to test" to the issue summary per #12.
Comment #31
wim leersNot reviewing each interdiff, but responding to each comment, and then reviewing the entire patch:
#25:
#27:
#29: :)
Patch review:
👎 This is still here, just like in #24.1. I still don't understand why.I should've read it better back in #24!
👍This is overriding
serializer.normalizer.config_entityto ensure we don't lock ourselves into that data structure either. To be honest, I had not yet considered the config entity aspect yet — I was only thinking about content entities.But given that the default layout for a given content entity type+bundle is stored in a config entity, and a content entity's "layout" field may contain an override of that default layout, and therefore both actually would expose the same data structure that is not something we want to ossify just yet, I think this makes perfect sense! 👏
Until we have at least per-top-level key for config entities (if that ever happens, if that even makes sense), this is the only possible solution. 👍
👎✅ Now this is my remaining concern for this service.The comment does not make sense to me ("so that we do not replace it" — huh?). And rather than duplicating all of its service dependencies, we should at minimum use
parent: …, and preferably usedecorates: ….Otherwise, any upstream change would need to be duplicated here. IOW: this introduces tight coupling we don't want or need.
Done.
✅ This is definitely ephemeral, so we should mark it as
@internal. Done.✅ This is in both subclasses, so might as well do it in the base class. Done.
👍
👏👏
👏👏
Comment #33
tim.plunkettIn order for this to work, we'd have to move this to LayoutBuilderServiceProvider and check for moduleExists('serialization')
Comment #34
tim.plunkettComment #35
wim leers👍
Sorry, I should have realized that!
Comment #36
xjmWe should link the issues where we're discussing whether to add back normalization and how with an inline comment and an
@todo.Why must the priority be higher? Edit: My point, is the comment should say why.
Let's document why it's internal.
Nit: "third-party settings".
This comment also made it sound like section data was actually being deleted from a config entity. I think the comment should end with "...in the serialized data."
lol puns!
Double blank lines; these will cause a phpcs fail and make this patch uncommittable. :)
For clarity, we probably should say "...no permission that gives the user access to layout sections over REST."
MORE puns.
Comment #37
xjmOh this also still needs its change record.
Comment #38
xjmActually this probably needs to be mentioned in the release notes as well.
Comment #39
xjmSorry, never mind about the release note, I forgot since the last time I reviewed this that currently we don't expose anything and this is just making it explicit. So just the CR. Thanks!
Comment #40
tim.plunkett#36
1) Done
2) Done, but opened #3031698: Consider making the default normalizers take a lower priority by default as well because this seems unwieldy.
3) Done
4) Fixed and expanded the test slightly
5) 😂
6) Fixed
7) Fixed
8) I love it
Also I changed the test classes to have a Test suffix.
Opened a CR https://www.drupal.org/node/3031697
Comment #41
xjmOkay yes that's what a higher priority is by definition, but why must it take precedence?
There's a verb missing now.
Comment #42
tim.plunkettFixed
Comment #43
tedbowLooks good!
Comment #45
xjmCommitted to 8.7.x and published the change record. Thanks!
Comment #46
tedbow@xjm thanks for committing.
@Wim Leers thanks for your help on this an suggesting not exposing the field right now.
@tim.plunkett @Kristen Pol thanks for the help too