jsonapi.api.php 17 KB

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