Problem/Motivation
Any attempt to create a paragraph entity through jsonapi POST results in a 403 response and no paragraph created. This is because ParagraphAccessControlHandler::checkCreateAccess() only allows access when the request format is html.
This means jsonapi cannot be used to create new paragraph based pages. This is a shame, especially as jsonapi is moving in to core with 8.7.0.
This can be worked around by writing code, for example, implementing hook_paragraph_create_access() to allow creation of paragraphs. But it would be better if this was possible using just the paragraphs module with no custom code.
Proposed resolution
Add a new permission(s) to allow creation of paragraphs remotely.
Extend Drupal\paragraphs\ParagraphAccessControlHandler::checkCreateAccess() to check such permissions and allow operations.
Remaining tasks
Decide whether the permissions should be per bundle
Decide what other permissions we should allow for remote operations - eg update/delete/own/others.
Actually implement.
User interface changes
None
API changes
None
Data model changes
None
Release notes snippet
✄
| Comment | File | Size | Author |
|---|---|---|---|
| #6 | paragraphs-3047678-allow_creation_via_jsonapi-6.patch | 978 bytes | logickal |
Issue fork paragraphs-3047678
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
Comment #2
berdir> Decide whether the permissions should be per bundle
The paragraph type module already provides per-bundle permissions.
Comment #3
eli-tAha! Paragraphs Type Permissions.
Thanks @berdir
Comment #4
berdirDidn't mean to suggest closing this issue. The current implementation was done as a security fix because before, it required _no_ permission at all :)
The type permissions module is an option, but it can also be tedious to configure possibly dozens of permissions if you don't need that level of granularity.
Comment #5
berdirThis actually came up a few times already, so a good start might be a documentation page that explains the currently available options (custom create access hook or using the type permissions module).
Comment #6
logickal commentedUnderstanding that there should probably be more discussion around this issue, I do think that with jsonapi now in core, it's reasonable to think that Paragraphs should support creation via that method. Since we just ran into it, here is a quick patch that adds api_json to the valid types. Definitely needs tests as well.
Comment #7
logickal commentedComment #8
berdirNo, this check is specially there to exclude jsonapi.
You need to you your own hook with custom logic or use the type permission submodule
Comment #9
logickal commentedSorry @Berdir - I think I must have misunderstood #4, so my apologies. I then also later found #3012600: Unable to create Paragraph with format json which describes the same.
What is your feeling about adding a new permission that would get evaluated in checkCreateAccess() to allow JSON API creation globally?
Comment #10
miro_dietikerIMHO it is a bad idea that json api does not introduce dedicated permissions or settings. Virtually exposing everything through the api can easily lead to vulnerabillities: You enable it just for a specific tiny task and everything js exposed.
It is not the job of Paragraphs to mitigate such a design limitation. IMHO core should fix the situation. That‘s why we decided for a defensive default behavior with the last security update.
If we add a custom perm, we later need to catch up with ugly updates in case core covers the requirement at some point.
This leads us exactly back to the root problem that core doesn‘t support composite entities (what we did in ERR). And core is exactly hitting the pains of it as well with moderated translatable custom blocks and more...
Let‘s see what berdir proposes as a feasible mitigation or next milestone focus.
Comment #13
guypaddock commentedI'm working on an alternate approach that provides a way to "sidepost" paragraph entities through a parent's JSON:API resource.
Comment #14
guypaddock commentedThe approach in MR 24 implements a way to create or update paragraph entities through JSON:API requests against the parent resource.
Concept and Approach
As mentioned in my earlier comment, this is a concept known as "sideposting" -- the idea that you can effect change on multiple related entities under the same top-level request. Ideally, JSON:API would have finalized their own sideposting specification but it's been under debate since 2017 and they still haven't come to consensus.
My intent is to introduce a mechanism for persisting entities with nested paragraphs using standard JSON:API clients. So, that ruled out using Subrequests because that's an RPC-style approach that is radically different from a standard JSON:API request. With Subrequests, the client not only has to construct the requests a special way, but then has to check on the status of each sub-request afterwards.
Requirements
This approach requires the patch to Entity Reference Revisions from #3267304: Infer "target_revision_id" to be default revision when only a "target_id" is provided to function. Without the patch, nested paragraphs would be created or updated but would not get linked-up to their parent entity because ERR currently requires the client to provide revision IDs.
How this Works
To accomplish the goal of this change, I added an event subscriber that provides sideposting behavior. The new behavior only kicks in on JSON:API requests that opt-in for it. The client opts-in via a new
"meta.persistence_behavior"key on the included entity (the value must be"create_or_update"). The client also has to generate and assign a valid UUID for every sideposted paragraph entity.For example, an outgoing
POSTrequest to/jsonapi/node/samplecould look like:In the example above, the UUID
"62ff56ab-56bd-425a-908c-7556638add54"was generated by the client.Updates via
PATCHare also supported, allowing the nested paragraph to be updated at the same time as the parent. For example, an outgoingPATCHrequest to/jsonapi/node/sample/6d521732-3ef3-4aa1-a779-96163ecd3c6dcould look like:If a paragraph identified by UUID
62ff56ab-56bd-425a-908c-7556638add54does not exist, it will be created with aPOSTSymfony sub-request. If it does exist, it will be updated in-place via aPATCHsub-request.On the server side, the event subscriber does the following:
includedrelationships of the top-level resource object for any paragraph entities that the client has signaled will need sideposting. If there are none, the event subscriber does no further processing on its own (JSON:API in Core handles the rest of the request).POSTsub-request to create the paragraph. The sub-request carries with it all theincludedobjects from the top-level document, in case we need to handle deeper-nested relationships.PATCHsub-request to update the paragraph. The sub-request carries with it all theincludedobjects from the top-level document, in case we need to handle deeper-nested relationships.Benefits
This approach has the following benefits over other approaches:
includedarray gets passed through all the sub-requests, this approach should traverse paragraphs depth-first. If paragraphs contain other paragraphs fields, the deepest entities of the object graph should get created/updated first, followed by the higher levels, and so-on.Limitations
This approach does have some flaws/limitations:
node -> node -> paragraph,node -> taxonomy term -> paragraph), this approach won't be an improvement over what JSON:API in core does. To address this use case, this approach would need to be expanded to support more types of resources than just Paragraphs, but that's beyond the scope of the Paragraphs module.Security Considerations
From the discussion earlier in this thread, I recognized a need to restrict what users can create paragraphs. As is the case before this approach, users do NOT have the ability to create paragraphs directly through JSON:API as allowing this without additional permissions would have opened-up a security hole.
Borrowing from
TemporaryJsonapiFileFieldUploaderin Core, we check the user's field access. The JSON:API client is not allowed to perform a sidepost if any of the following are true:Next Steps
If this approach meets the approval of the maintainers, assign back to me as NW and then the next steps are:
meta.persistence_statusvalue in responses to signal to the client if a sidepost resulted in a 201 or 200, in case they need that metadata to know whether a particular paragraph was created or updated.Comment #15
guypaddock commentedComment #16
JBHUTT09 commentedI've applied the latest version of this patch and the patch from #3267304 (listed as a requirement), but when I send a POST request I get a response saying that the paragraph item cannot be found.
This is my request uri:
/jsonapi/node/page?include=field_phone_numbers&jsonapi_include=1This is my request body:
Has something changed since this was last worked on or am I doing something wrong?
Comment #17
guypaddock commentedI believe the
persistence_behaviorneeds to becreate_or_update, notcreate_or_publish.Comment #18
JBHUTT09 commentedOh my god. That was it. Sorry to bother you for something so dumb and thank you so much.
Comment #19
guypaddock commentedSorry for the delay, JBHUTT09 -- no worries at all!
Comment #20
mxr576Source: https://github.com/json-api/json-api/pull/1197#issuecomment-1345658588
So the Sideposting spec draft was closed on the JSONAPI side :(
What I am aware and might be related is https://jsonapi.org/ext/atomic specification that https://www.drupal.org/project/jsonapi_operations tried to implement once, but it is also not production ready.
Comment #21
mxh commentedNeeds review over two years, maybe this is a "won't fix"?