Mercurial > hg > isophonics-drupal-site
comparison core/modules/jsonapi/jsonapi.api.php @ 18:af1871eacc83
Update to Drupal core 8.7.1
author | Chris Cannam |
---|---|
date | Thu, 09 May 2019 15:33:08 +0100 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
17:129ea1e6d783 | 18:af1871eacc83 |
---|---|
1 <?php | |
2 | |
3 /** | |
4 * @file | |
5 * Documentation related to JSON:API. | |
6 */ | |
7 | |
8 use Drupal\Core\Access\AccessResult; | |
9 | |
10 /** | |
11 * @defgroup jsonapi_architecture JSON:API Architecture | |
12 * @{ | |
13 * | |
14 * @section overview Overview | |
15 * The JSON:API module is a Drupal-centric implementation of the JSON:API | |
16 * specification. By its own definition, the JSON:API specification "is a | |
17 * specification for how a client should request that resources be fetched or | |
18 * modified, and how a server should respond to those requests. [It] is designed | |
19 * to minimize both the number of requests and the amount of data transmitted | |
20 * between clients and servers. This efficiency is achieved without compromising | |
21 * readability, flexibility, or discoverability." | |
22 * | |
23 * While "Drupal-centric", the JSON:API module is committed to strict compliance | |
24 * with the specification. Wherever possible, the module attempts to implement | |
25 * the specification in a way which is compatible and familiar with the patterns | |
26 * and concepts inherent to Drupal. However, when "Drupalisms" cannot be | |
27 * reconciled with the specification, the module will always choose the | |
28 * implementation most faithful to the specification. | |
29 * | |
30 * @see http://jsonapi.org/ | |
31 * | |
32 * @section resources Resources | |
33 * Every unit of data in the specification is a "resource". The specification | |
34 * defines how a client should interact with a server to fetch and manipulate | |
35 * these resources. | |
36 * | |
37 * The JSON:API module maps every entity type + bundle to a resource type. | |
38 * Since the specification does not have a concept of resource type inheritance | |
39 * or composition, the JSON:API module implements different bundles of the same | |
40 * entity type as *distinct* resource types. | |
41 * | |
42 * While it is theoretically possible to expose arbitrary data as resources, the | |
43 * JSON:API module only exposes resources from (config and content) entities. | |
44 * This eliminates the need for another abstraction layer in order implement | |
45 * certain features of the specification. | |
46 * | |
47 * @section relationships Relationships | |
48 * The specification defines semantics for the "relationships" between | |
49 * resources. Since the JSON:API module defines every entity type + bundle as a | |
50 * resource type and does not allow non-entity resources, it is able to use | |
51 * entity references to automatically define and represent the relationships | |
52 * between all resources. | |
53 * | |
54 * @section revisions Resource versioning | |
55 * The JSON:API module exposes entity revisions in a manner inspired by RFC5829: | |
56 * Link Relation Types for Simple Version Navigation between Web Resources. | |
57 * | |
58 * Revision support is not an official part of the JSON:API specification. | |
59 * However, a number of "profiles" are being developed (also not officially part | |
60 * in the spec, but already committed to JSON:API v1.1) to standardize any | |
61 * custom behaviors that the JSON:API module has developed (all of which are | |
62 * still specification-compliant). | |
63 * | |
64 * @see https://github.com/json-api/json-api/pull/1268 | |
65 * @see https://github.com/json-api/json-api/pull/1311 | |
66 * @see https://www.drupal.org/project/jsonapi/issues/2955020 | |
67 * | |
68 * By implementing revision support as a profile, the JSON:API module should be | |
69 * maximally compatible with other systems. | |
70 * | |
71 * A "version" in the JSON:API module is any revision that was previously, or is | |
72 * currently, a default revision. Not all revisions are considered to be a | |
73 * "version". Revisions that are not marked as a "default" revision are | |
74 * considered "working copies" since they are not usually publicly available | |
75 * and are the revisions to which most new work is applied. | |
76 * | |
77 * When the Content Moderation module is installed, it is possible that the | |
78 * most recent default revision is *not* the latest revision. | |
79 * | |
80 * Requesting a resource version is done via a URL query parameter. It has the | |
81 * following form: | |
82 * | |
83 * @code | |
84 * version-identifier | |
85 * __|__ | |
86 * / \ | |
87 * ?resource_version=foo:bar | |
88 * \_/ \_/ | |
89 * | | | |
90 * version-negotiator | | |
91 * version-argument | |
92 * @endcode | |
93 * | |
94 * A version identifier is a string with enough information to load a | |
95 * particular revision. The version negotiator component names the negotiation | |
96 * mechanism for loading a revision. Currently, this can be either `id` or | |
97 * `rel`. The `id` negotiator takes a version argument which is the desired | |
98 * revision ID. The `rel` negotiator takes a version argument which is either | |
99 * the string `latest-version` or the string `working-copy`. | |
100 * | |
101 * In the future, other negotiatiors may be developed, such as negotiatiors that | |
102 * are UUID-, timestamp-, or workspace-based. | |
103 * | |
104 * To illustrate how a particular entity revision is requested, imagine a node | |
105 * that has a "Published" revision and a subsequent "Draft" revision. | |
106 * | |
107 * Using JSON:API, one could request the "Published" node by requesting | |
108 * `/jsonapi/node/page/{{uuid}}?resource_version=rel:latest-version`. | |
109 * | |
110 * To preview an entity that is still a work-in-progress (i.e. the "Draft" | |
111 * revision) one could request | |
112 * `/jsonapi/node/page/{{uuid}}?resource_version=rel:working-copy`. | |
113 * | |
114 * To request a specific revision ID, one can request | |
115 * `/jsonapi/node/page/{{uuid}}?resource_version=id:{{revision_id}}`. | |
116 * | |
117 * It is not yet possible to request a collection of revisions. This is still | |
118 * under development in issue [#3009588]. | |
119 * | |
120 * @see https://www.drupal.org/project/jsonapi/issues/3009588. | |
121 * @see https://tools.ietf.org/html/rfc5829 | |
122 * @see https://www.drupal.org/docs/8/modules/jsonapi/revisions | |
123 * | |
124 * @section translations Resource translations | |
125 * | |
126 * Some multilingual features currently do not work well with JSON:API. See | |
127 * JSON:API modules's multilingual support documentation online for more | |
128 * information on the current status of multilingual support. | |
129 * | |
130 * @see https://www.drupal.org/docs/8/modules/jsonapi/translations | |
131 * | |
132 * @section api API | |
133 * The JSON:API module provides an HTTP API that adheres to the JSON:API | |
134 * specification. | |
135 * | |
136 * The JSON:API module provides *no PHP API to modify its behavior.* It is | |
137 * designed to have zero configuration. | |
138 * | |
139 * - Adding new resources/resource types is unsupported: all entities/entity | |
140 * types are exposed automatically. If you want to expose more data via the | |
141 * JSON:API module, the data must be defined as entity. See the "Resources" | |
142 * section. | |
143 * - Custom field type normalization is not supported because the JSON:API | |
144 * specification requires specific representations for resources (entities), | |
145 * attributes on resources (non-entity reference fields) and relationships | |
146 * between those resources (entity reference fields). A field contains | |
147 * properties, and properties are of a certain data type. All non-internal | |
148 * properties on a field are normalized. | |
149 * - The same data type normalizers as those used by core's Serialization and | |
150 * REST modules are also used by the JSON:API module. | |
151 * - All available authentication mechanisms are allowed. | |
152 * | |
153 * @section tests Test Coverage | |
154 * The JSON:API module comes with extensive unit and kernel tests. But most | |
155 * importantly for end users, it also has comprehensive integration tests. These | |
156 * integration tests are designed to: | |
157 * | |
158 * - ensure a great DX (Developer Experience) | |
159 * - detect regressions and normalization changes before shipping a release | |
160 * - guarantee 100% of Drupal core's entity types work as expected | |
161 * | |
162 * The integration tests test the same common cases and edge cases using | |
163 * \Drupal\Tests\jsonapi\Functional\ResourceTestBase, which is a base class | |
164 * subclassed for every entity type that Drupal core ships with. It is ensured | |
165 * that 100% of Drupal core's entity types are tested thanks to | |
166 * \Drupal\Tests\jsonapi\Functional\TestCoverageTest. | |
167 * | |
168 * Custom entity type developers can get the same assurances by subclassing it | |
169 * for their entity types. | |
170 * | |
171 * @section bc Backwards Compatibility | |
172 * PHP API: there is no PHP API except for three security-related hooks. This | |
173 * means that this module's implementation details are entirely free to | |
174 * change at any time. | |
175 * | |
176 * Note that *normalizers are internal implementation details.* While | |
177 * normalizers are services, they are *not* to be used directly. This is due to | |
178 * the design of the Symfony Serialization component, not because the JSON:API | |
179 * module wanted to publicly expose services. | |
180 * | |
181 * HTTP API: URLs and JSON response structures are considered part of this | |
182 * module's public API. However, inconsistencies with the JSON:API specification | |
183 * will be considered bugs. Fixes which bring the module into compliance with | |
184 * the specification are *not* guaranteed to be backwards-compatible. When | |
185 * compliance bugs are found, clients are expected to be made compatible with | |
186 * both the pre-fix and post-fix representations. | |
187 * | |
188 * What this means for developing consumers of the HTTP API is that *clients | |
189 * should be implemented from the specification first and foremost.* This should | |
190 * mitigate implicit dependencies on implementation details or inconsistencies | |
191 * with the specification that are specific to this module. | |
192 * | |
193 * To help develop compatible clients, every response indicates the version of | |
194 * the JSON:API specification used under its "jsonapi" key. Future releases | |
195 * *may* increment the minor version number if the module implements features of | |
196 * a later specification. Remember that the specification stipulates that future | |
197 * versions *will* remain backwards-compatible as only additions may be | |
198 * released. | |
199 * | |
200 * @see http://jsonapi.org/faq/#what-is-the-meaning-of-json-apis-version | |
201 * | |
202 * Tests: subclasses of base test classes may contain BC breaks between minor | |
203 * releases, to allow minor releases to A) comply better with the JSON:API spec, | |
204 * B) guarantee that all resource types (and therefore entity types) function as | |
205 * expected, C) update to future versions of the JSON:API spec. | |
206 * | |
207 * @} | |
208 */ | |
209 | |
210 /** | |
211 * @addtogroup hooks | |
212 * @{ | |
213 */ | |
214 | |
215 /** | |
216 * Controls access when filtering by entity data via JSON:API. | |
217 * | |
218 * This module supports filtering by resource object attributes referenced by | |
219 * relationship fields. For example, a site may add a "Favorite Animal" field | |
220 * to user entities, which would permit the following filtered query: | |
221 * @code | |
222 * /jsonapi/node/article?filter[uid.field_favorite_animal]=llama | |
223 * @endcode | |
224 * This query would return articles authored by users whose favorite animal is a | |
225 * llama. However, the information about a user's favorite animal should not be | |
226 * available to users without the "access user profiles" permission. The same | |
227 * must hold true even if that user is referenced as an article's author. | |
228 * Therefore, access to filter by this data must be restricted so that access | |
229 * cannot be bypassed via a JSON:API filtered query. | |
230 * | |
231 * As a rule, clients should only be able to filter by data that they can | |
232 * view. | |
233 * | |
234 * Conventionally, `$entity->access('view')` is how entity access is checked. | |
235 * This call invokes the corresponding hooks. However, these access checks | |
236 * require an `$entity` object. This means that they cannot be called prior to | |
237 * executing a database query. | |
238 * | |
239 * In order to safely enable filtering across a relationship, modules | |
240 * responsible for entity access must do two things: | |
241 * - Implement this hook (or hook_jsonapi_ENTITY_TYPE_filter_access()) and | |
242 * return an array of AccessResults keyed by the named entity subsets below. | |
243 * - If the AccessResult::allowed() returned by the above hook does not provide | |
244 * enough granularity (for example, if access depends on a bundle field value | |
245 * of the entity being queried), then hook_query_TAG_alter() must be | |
246 * implemented using the 'entity_access' or 'ENTITY_TYPE_access' query tag. | |
247 * See node_query_node_access_alter() for an example. | |
248 * | |
249 * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type | |
250 * The entity type of the entity to be filtered upon. | |
251 * @param \Drupal\Core\Session\AccountInterface $account | |
252 * The account for which to check access. | |
253 * | |
254 * @return \Drupal\Core\Access\AccessResultInterface[] | |
255 * An array keyed by a constant which identifies a subset of entities. For | |
256 * each subset, the value is one of the following access results: | |
257 * - AccessResult::allowed() if all entities within the subset (potentially | |
258 * narrowed by hook_query_TAG_alter() implementations) are viewable. | |
259 * - AccessResult::forbidden() if any entity within the subset is not | |
260 * viewable. | |
261 * - AccessResult::neutral() if the implementation has no opinion. | |
262 * The supported subsets for which an access result may be returned are: | |
263 * - JSONAPI_FILTER_AMONG_ALL: all entities of the given type. | |
264 * - JSONAPI_FILTER_AMONG_PUBLISHED: all published entities of the given type. | |
265 * - JSONAPI_FILTER_AMONG_ENABLED: all enabled entities of the given type. | |
266 * - JSONAPI_FILTER_AMONG_OWN: all entities of the given type owned by the | |
267 * user for whom access is being checked. | |
268 * See the documentation of the above constants for more information about | |
269 * each subset. | |
270 * | |
271 * @see hook_jsonapi_ENTITY_TYPE_filter_access() | |
272 */ | |
273 function hook_jsonapi_entity_filter_access(\Drupal\Core\Entity\EntityTypeInterface $entity_type, \Drupal\Core\Session\AccountInterface $account) { | |
274 // For every entity type that has an admin permission, allow access to filter | |
275 // by all entities of that type to users with that permission. | |
276 if ($admin_permission = $entity_type->getAdminPermission()) { | |
277 return ([ | |
278 JSONAPI_FILTER_AMONG_ALL => AccessResult::allowedIfHasPermission($account, $admin_permission), | |
279 ]); | |
280 } | |
281 } | |
282 | |
283 /** | |
284 * Controls access to filtering by entity data via JSON:API. | |
285 * | |
286 * This is the entity-type-specific variant of | |
287 * hook_jsonapi_entity_filter_access(). For implementations with logic that is | |
288 * specific to a single entity type, it is recommended to implement this hook | |
289 * rather than the generic hook_jsonapi_entity_filter_access() hook, which is | |
290 * called for every entity type. | |
291 * | |
292 * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type | |
293 * The entity type of the entities to be filtered upon. | |
294 * @param \Drupal\Core\Session\AccountInterface $account | |
295 * The account for which to check access. | |
296 * | |
297 * @return \Drupal\Core\Access\AccessResultInterface[] | |
298 * The array of access results, keyed by subset. See | |
299 * hook_jsonapi_entity_filter_access() for details. | |
300 * | |
301 * @see hook_jsonapi_entity_filter_access() | |
302 */ | |
303 function hook_jsonapi_ENTITY_TYPE_filter_access(\Drupal\Core\Entity\EntityTypeInterface $entity_type, \Drupal\Core\Session\AccountInterface $account) { | |
304 return ([ | |
305 JSONAPI_FILTER_AMONG_ALL => AccessResult::allowedIfHasPermission($account, 'administer llamas'), | |
306 JSONAPI_FILTER_AMONG_PUBLISHED => AccessResult::allowedIfHasPermission($account, 'view all published llamas'), | |
307 JSONAPI_FILTER_AMONG_OWN => AccessResult::allowedIfHasPermissions($account, ['view own published llamas', 'view own unpublished llamas'], 'AND'), | |
308 ]); | |
309 } | |
310 | |
311 /** | |
312 * Restricts filtering access to the given field. | |
313 * | |
314 * Some fields may contain sensitive information. In these cases, modules are | |
315 * supposed to implement hook_entity_field_access(). However, this hook receives | |
316 * an optional `$items` argument and often must return AccessResult::neutral() | |
317 * when `$items === NULL`. This is because access may or may not be allowed | |
318 * based on the field items or based on the entity on which the field is | |
319 * attached (if the user is the entity owner, for example). | |
320 * | |
321 * Since JSON:API must check field access prior to having a field item list | |
322 * instance available (access must be checked before a database query is made), | |
323 * it is not sufficiently secure to check field 'view' access alone. | |
324 * | |
325 * This hook exists so that modules which cannot return | |
326 * AccessResult::forbidden() from hook_entity_field_access() can still secure | |
327 * JSON:API requests where necessary. | |
328 * | |
329 * If a corresponding implementation of hook_entity_field_access() *can* be | |
330 * forbidden for one or more values of the `$items` argument, this hook *MUST* | |
331 * return AccessResult::forbidden(). | |
332 * | |
333 * @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition | |
334 * The field definition of the field to be filtered upon. | |
335 * @param \Drupal\Core\Session\AccountInterface $account | |
336 * The account for which to check access. | |
337 * | |
338 * @return \Drupal\Core\Access\AccessResultInterface | |
339 * The access result. | |
340 */ | |
341 function hook_jsonapi_entity_field_filter_access(\Drupal\Core\Field\FieldDefinitionInterface $field_definition, \Drupal\Core\Session\AccountInterface $account) { | |
342 if ($field_definition->getTargetEntityTypeId() === 'node' && $field_definition->getName() === 'field_sensitive_data') { | |
343 $has_sufficient_access = FALSE; | |
344 foreach (['administer nodes', 'view all sensitive field data'] as $permission) { | |
345 $has_sufficient_access = $has_sufficient_access ?: $account->hasPermission($permission); | |
346 } | |
347 return AccessResult::forbiddenIf(!$has_sufficient_access)->cachePerPermissions(); | |
348 } | |
349 return AccessResult::neutral(); | |
350 } | |
351 | |
352 /** | |
353 * @} End of "addtogroup hooks". | |
354 */ |