Commit e59ec93f authored by effulgentsia's avatar effulgentsia

Issue #2843147 by Wim Leers, e0ipso, gabesullice, dagmar, xjm, effulgentsia,...

Issue #2843147 by Wim Leers, e0ipso, gabesullice, dagmar, xjm, effulgentsia, webchick, dww, Berdir, dawehner, colan, mgalalm, govind.maloo, das-peter, jludwig, ndobromirov, martin107, abhisekmazumdar, nuez, catch, amateescu, pwolanin, mallezie, borisson_, jibran, voleger, plach, GeduR, Grimreaper, apupiales, Sutharsan, RenatoG, michaellander, sergesemashko, dbt102, hampercm, dremy, rpayanm, chriscalip, tstoeckler, keopx, skyredwang, Spleshka, RajeevK, matthew.perry, kaysenlau, jazzdrive3, dsdeiz, ebeyrent, acbramley, deviantintegral, samuel.mortenson, DamienMcKenna, mohit_aghera, ibustos, dpolant, garphy: Add JSON:API to core as a stable module
parent fe95def3
......@@ -3634,6 +3634,72 @@
],
"time": "2016-10-04T09:27:04+00:00"
},
{
"name": "justinrainbow/json-schema",
"version": "5.2.8",
"source": {
"type": "git",
"url": "https://github.com/justinrainbow/json-schema.git",
"reference": "dcb6e1006bb5fd1e392b4daa68932880f37550d4"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/justinrainbow/json-schema/zipball/dcb6e1006bb5fd1e392b4daa68932880f37550d4",
"reference": "dcb6e1006bb5fd1e392b4daa68932880f37550d4",
"shasum": ""
},
"require": {
"php": ">=5.3.3"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "~2.2.20",
"json-schema/json-schema-test-suite": "1.2.0",
"phpunit/phpunit": "^4.8.35"
},
"bin": [
"bin/validate-json"
],
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "5.0.x-dev"
}
},
"autoload": {
"psr-4": {
"JsonSchema\\": "src/JsonSchema/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Bruno Prieto Reis",
"email": "bruno.p.reis@gmail.com"
},
{
"name": "Justin Rainbow",
"email": "justin.rainbow@gmail.com"
},
{
"name": "Igor Wiedler",
"email": "igor@wiedler.ch"
},
{
"name": "Robert Schönthal",
"email": "seroscho@googlemail.com"
}
],
"description": "A library to validate a json schema.",
"homepage": "https://github.com/justinrainbow/json-schema",
"keywords": [
"json",
"schema"
],
"time": "2019-01-14T23:55:14+00:00"
},
{
"name": "mikey179/vfsStream",
"version": "v1.6.5",
......
......@@ -248,6 +248,11 @@ JavaScript
- Matthew Grill 'alwaysworking' https://www.drupal.org/u/alwaysworking
- Sally Young 'justafish' https://www.drupal.org/u/justafish
JSON:API
- Gabe Sullice 'gabesullice' https://www.drupal.org/u/gabesullice
- Mateu Aguiló Bosch 'e0ipso' https://www.drupal.org/u/e0ipso
- Wim Leers 'Wim Leers' https://www.drupal.org/u/wim-leers
Language
- Francesco Placella 'plach' https://www.drupal.org/u/plach
- Daniel F. Kudwien 'sun' https://www.drupal.org/u/sun
......
......@@ -64,7 +64,8 @@
"phpspec/prophecy": "^1.7",
"symfony/css-selector": "^3.4.0",
"symfony/phpunit-bridge": "^3.4.3",
"symfony/debug": "^3.4.0"
"symfony/debug": "^3.4.0",
"justinrainbow/json-schema": "^5.2"
},
"replace": {
"drupal/action": "self.version",
......@@ -129,6 +130,7 @@
"drupal/history": "self.version",
"drupal/image": "self.version",
"drupal/inline_form_errors": "self.version",
"drupal/jsonapi": "self.version",
"drupal/language": "self.version",
"drupal/layout_builder": "self.version",
"drupal/layout_discovery": "self.version",
......
jsonapi.settings:
type: config_object
label: 'JSON:API settings'
mapping:
read_only:
type: boolean
label: 'Restrict JSON:API to only read operations'
<?php
/**
* @file
* Documentation related to JSON:API.
*/
use Drupal\Core\Access\AccessResult;
/**
* @defgroup jsonapi_architecture JSON:API Architecture
* @{
*
* @section overview Overview
* The JSON:API module is a Drupal-centric implementation of the JSON:API
* specification. By its own definition, the JSON:API specification "is a
* specification for how a client should request that resources be fetched or
* modified, and how a server should respond to those requests. [It] is designed
* to minimize both the number of requests and the amount of data transmitted
* between clients and servers. This efficiency is achieved without compromising
* readability, flexibility, or discoverability."
*
* While "Drupal-centric", the JSON:API module is committed to strict compliance
* with the specification. Wherever possible, the module attempts to implement
* the specification in a way which is compatible and familiar with the patterns
* and concepts inherent to Drupal. However, when "Drupalisms" cannot be
* reconciled with the specification, the module will always choose the
* implementation most faithful to the specification.
*
* @see http://jsonapi.org/
*
* @section resources Resources
* Every unit of data in the specification is a "resource". The specification
* defines how a client should interact with a server to fetch and manipulate
* these resources.
*
* The JSON:API module maps every entity type + bundle to a resource type.
* Since the specification does not have a concept of resource type inheritance
* or composition, the JSON:API module implements different bundles of the same
* entity type as *distinct* resource types.
*
* While it is theoretically possible to expose arbitrary data as resources, the
* JSON:API module only exposes resources from (config and content) entities.
* This eliminates the need for another abstraction layer in order implement
* certain features of the specification.
*
* @section relationships Relationships
* The specification defines semantics for the "relationships" between
* resources. Since the JSON:API module defines every entity type + bundle as a
* resource type and does not allow non-entity resources, it is able to use
* entity references to automatically define and represent the relationships
* between all resources.
*
* @section revisions Resource versioning
* The JSON:API module exposes entity revisions in a manner inspired by RFC5829:
* Link Relation Types for Simple Version Navigation between Web Resources.
*
* Revision support is not an official part of the JSON:API specification.
* However, a number of "profiles" are being developed (also not officially part
* in the spec, but already committed to JSON:API v1.1) to standardize any
* custom behaviors that the JSON:API module has developed (all of which are
* still specification-compliant).
*
* @see https://github.com/json-api/json-api/pull/1268
* @see https://github.com/json-api/json-api/pull/1311
* @see https://www.drupal.org/project/jsonapi/issues/2955020
*
* By implementing revision support as a profile, the JSON:API module should be
* maximally compatible with other systems.
*
* A "version" in the JSON:API module is any revision that was previously, or is
* currently, a default revision. Not all revisions are considered to be a
* "version". Revisions that are not marked as a "default" revision are
* considered "working copies" since they are not usually publicly available
* and are the revisions to which most new work is applied.
*
* When the Content Moderation module is installed, it is possible that the
* most recent default revision is *not* the latest revision.
*
* Requesting a resource version is done via a URL query parameter. It has the
* following form:
*
* @code
* version-identifier
* __|__
* / \
* ?resource_version=foo:bar
* \_/ \_/
* | |
* version-negotiator |
* version-argument
* @endcode
*
* A version identifier is a string with enough information to load a
* particular revision. The version negotiator component names the negotiation
* mechanism for loading a revision. Currently, this can be either `id` or
* `rel`. The `id` negotiator takes a version argument which is the desired
* revision ID. The `rel` negotiator takes a version argument which is either
* the string `latest-version` or the string `working-copy`.
*
* In the future, other negotiatiors may be developed, such as negotiatiors that
* are UUID-, timestamp-, or workspace-based.
*
* To illustrate how a particular entity revision is requested, imagine a node
* that has a "Published" revision and a subsequent "Draft" revision.
*
* Using JSON:API, one could request the "Published" node by requesting
* `/jsonapi/node/page/{{uuid}}?resource_version=rel:latest-version`.
*
* To preview an entity that is still a work-in-progress (i.e. the "Draft"
* revision) one could request
* `/jsonapi/node/page/{{uuid}}?resource_version=rel:working-copy`.
*
* To request a specific revision ID, one can request
* `/jsonapi/node/page/{{uuid}}?resource_version=id:{{revision_id}}`.
*
* It is not yet possible to request a collection of revisions. This is still
* under development in issue [#3009588].
*
* @see https://www.drupal.org/project/jsonapi/issues/3009588.
* @see https://tools.ietf.org/html/rfc5829
* @see https://www.drupal.org/docs/8/modules/jsonapi/revisions
*
* @section translations Resource translations
*
* Some multilingual features currently do not work well with JSON:API. See
* JSON:API modules's multilingual support documentation online for more
* information on the current status of multilingual support.
*
* @see https://www.drupal.org/docs/8/modules/jsonapi/translations
*
* @section api API
* The JSON:API module provides an HTTP API that adheres to the JSON:API
* specification.
*
* The JSON:API module provides *no PHP API to modify its behavior.* It is
* designed to have zero configuration.
*
* - Adding new resources/resource types is unsupported: all entities/entity
* types are exposed automatically. If you want to expose more data via the
* JSON:API module, the data must be defined as entity. See the "Resources"
* section.
* - Custom field type normalization is not supported because the JSON:API
* specification requires specific representations for resources (entities),
* attributes on resources (non-entity reference fields) and relationships
* between those resources (entity reference fields). A field contains
* properties, and properties are of a certain data type. All non-internal
* properties on a field are normalized.
* - The same data type normalizers as those used by core's Serialization and
* REST modules are also used by the JSON:API module.
* - All available authentication mechanisms are allowed.
*
* @section tests Test Coverage
* The JSON:API module comes with extensive unit and kernel tests. But most
* importantly for end users, it also has comprehensive integration tests. These
* integration tests are designed to:
*
* - ensure a great DX (Developer Experience)
* - detect regressions and normalization changes before shipping a release
* - guarantee 100% of Drupal core's entity types work as expected
*
* The integration tests test the same common cases and edge cases using
* \Drupal\Tests\jsonapi\Functional\ResourceTestBase, which is a base class
* subclassed for every entity type that Drupal core ships with. It is ensured
* that 100% of Drupal core's entity types are tested thanks to
* \Drupal\Tests\jsonapi\Functional\TestCoverageTest.
*
* Custom entity type developers can get the same assurances by subclassing it
* for their entity types.
*
* @section bc Backwards Compatibility
* PHP API: there is no PHP API except for three security-related hooks. This
* means that this module's implementation details are entirely free to
* change at any time.
*
* Note that *normalizers are internal implementation details.* While
* normalizers are services, they are *not* to be used directly. This is due to
* the design of the Symfony Serialization component, not because the JSON:API
* module wanted to publicly expose services.
*
* HTTP API: URLs and JSON response structures are considered part of this
* module's public API. However, inconsistencies with the JSON:API specification
* will be considered bugs. Fixes which bring the module into compliance with
* the specification are *not* guaranteed to be backwards-compatible. When
* compliance bugs are found, clients are expected to be made compatible with
* both the pre-fix and post-fix representations.
*
* What this means for developing consumers of the HTTP API is that *clients
* should be implemented from the specification first and foremost.* This should
* mitigate implicit dependencies on implementation details or inconsistencies
* with the specification that are specific to this module.
*
* To help develop compatible clients, every response indicates the version of
* the JSON:API specification used under its "jsonapi" key. Future releases
* *may* increment the minor version number if the module implements features of
* a later specification. Remember that the specification stipulates that future
* versions *will* remain backwards-compatible as only additions may be
* released.
*
* @see http://jsonapi.org/faq/#what-is-the-meaning-of-json-apis-version
*
* Tests: subclasses of base test classes may contain BC breaks between minor
* releases, to allow minor releases to A) comply better with the JSON:API spec,
* B) guarantee that all resource types (and therefore entity types) function as
* expected, C) update to future versions of the JSON:API spec.
*
* @}
*/
/**
* @addtogroup hooks
* @{
*/
/**
* Controls access when filtering by entity data via JSON:API.
*
* This module supports filtering by resource object attributes referenced by
* relationship fields. For example, a site may add a "Favorite Animal" field
* to user entities, which would permit the following filtered query:
* @code
* /jsonapi/node/article?filter[uid.field_favorite_animal]=llama
* @endcode
* This query would return articles authored by users whose favorite animal is a
* llama. However, the information about a user's favorite animal should not be
* available to users without the "access user profiles" permission. The same
* must hold true even if that user is referenced as an article's author.
* Therefore, access to filter by this data must be restricted so that access
* cannot be bypassed via a JSON:API filtered query.
*
* As a rule, clients should only be able to filter by data that they can
* view.
*
* Conventionally, `$entity->access('view')` is how entity access is checked.
* This call invokes the corresponding hooks. However, these access checks
* require an `$entity` object. This means that they cannot be called prior to
* executing a database query.
*
* In order to safely enable filtering across a relationship, modules
* responsible for entity access must do two things:
* - Implement this hook (or hook_jsonapi_ENTITY_TYPE_filter_access()) and
* return an array of AccessResults keyed by the named entity subsets below.
* - If the AccessResult::allowed() returned by the above hook does not provide
* enough granularity (for example, if access depends on a bundle field value
* of the entity being queried), then hook_query_TAG_alter() must be
* implemented using the 'entity_access' or 'ENTITY_TYPE_access' query tag.
* See node_query_node_access_alter() for an example.
*
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
* The entity type of the entity to be filtered upon.
* @param \Drupal\Core\Session\AccountInterface $account
* The account for which to check access.
*
* @return \Drupal\Core\Access\AccessResultInterface[]
* An array keyed by a constant which identifies a subset of entities. For
* each subset, the value is one of the following access results:
* - AccessResult::allowed() if all entities within the subset (potentially
* narrowed by hook_query_TAG_alter() implementations) are viewable.
* - AccessResult::forbidden() if any entity within the subset is not
* viewable.
* - AccessResult::neutral() if the implementation has no opinion.
* The supported subsets for which an access result may be returned are:
* - JSONAPI_FILTER_AMONG_ALL: all entities of the given type.
* - JSONAPI_FILTER_AMONG_PUBLISHED: all published entities of the given type.
* - JSONAPI_FILTER_AMONG_ENABLED: all enabled entities of the given type.
* - JSONAPI_FILTER_AMONG_OWN: all entities of the given type owned by the
* user for whom access is being checked.
* See the documentation of the above constants for more information about
* each subset.
*
* @see hook_jsonapi_ENTITY_TYPE_filter_access()
*/
function hook_jsonapi_entity_filter_access(\Drupal\Core\Entity\EntityTypeInterface $entity_type, \Drupal\Core\Session\AccountInterface $account) {
// For every entity type that has an admin permission, allow access to filter
// by all entities of that type to users with that permission.
if ($admin_permission = $entity_type->getAdminPermission()) {
return ([
JSONAPI_FILTER_AMONG_ALL => AccessResult::allowedIfHasPermission($account, $admin_permission),
]);
}
}
/**
* Controls access to filtering by entity data via JSON:API.
*
* This is the entity-type-specific variant of
* hook_jsonapi_entity_filter_access(). For implementations with logic that is
* specific to a single entity type, it is recommended to implement this hook
* rather than the generic hook_jsonapi_entity_filter_access() hook, which is
* called for every entity type.
*
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
* The entity type of the entities to be filtered upon.
* @param \Drupal\Core\Session\AccountInterface $account
* The account for which to check access.
*
* @return \Drupal\Core\Access\AccessResultInterface[]
* The array of access results, keyed by subset. See
* hook_jsonapi_entity_filter_access() for details.
*
* @see hook_jsonapi_entity_filter_access()
*/
function hook_jsonapi_ENTITY_TYPE_filter_access(\Drupal\Core\Entity\EntityTypeInterface $entity_type, \Drupal\Core\Session\AccountInterface $account) {
return ([
JSONAPI_FILTER_AMONG_ALL => AccessResult::allowedIfHasPermission($account, 'administer llamas'),
JSONAPI_FILTER_AMONG_PUBLISHED => AccessResult::allowedIfHasPermission($account, 'view all published llamas'),
JSONAPI_FILTER_AMONG_OWN => AccessResult::allowedIfHasPermissions($account, ['view own published llamas', 'view own unpublished llamas'], 'AND'),
]);
}
/**
* Restricts filtering access to the given field.
*
* Some fields may contain sensitive information. In these cases, modules are
* supposed to implement hook_entity_field_access(). However, this hook receives
* an optional `$items` argument and often must return AccessResult::neutral()
* when `$items === NULL`. This is because access may or may not be allowed
* based on the field items or based on the entity on which the field is
* attached (if the user is the entity owner, for example).
*
* Since JSON:API must check field access prior to having a field item list
* instance available (access must be checked before a database query is made),
* it is not sufficiently secure to check field 'view' access alone.
*
* This hook exists so that modules which cannot return
* AccessResult::forbidden() from hook_entity_field_access() can still secure
* JSON:API requests where necessary.
*
* If a corresponding implementation of hook_entity_field_access() *can* be
* forbidden for one or more values of the `$items` argument, this hook *MUST*
* return AccessResult::forbidden().
*
* @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition
* The field definition of the field to be filtered upon.
* @param \Drupal\Core\Session\AccountInterface $account
* The account for which to check access.
*
* @return \Drupal\Core\Access\AccessResultInterface
* The access result.
*/
function hook_jsonapi_entity_field_filter_access(\Drupal\Core\Field\FieldDefinitionInterface $field_definition, \Drupal\Core\Session\AccountInterface $account) {
if ($field_definition->getTargetEntityTypeId() === 'node' && $field_definition->getName() === 'field_sensitive_data') {
$has_sufficient_access = FALSE;
foreach (['administer nodes', 'view all sensitive field data'] as $permission) {
$has_sufficient_access = $has_sufficient_access ?: $account->hasPermission($permission);
}
return AccessResult::forbiddenIf(!$has_sufficient_access)->cachePerPermissions();
}
return AccessResult::neutral();
}
/**
* @} End of "addtogroup hooks".
*/
name: JSON:API
type: module
description: Exposes entities as a JSON:API-specification-compliant web API.
core: 8.x
package: Web services
configure: jsonapi.settings
dependencies:
- drupal:serialization
<?php
/**
* @file
* Module install file.
*/
/**
* Implements hook_install().
*/
function jsonapi_install() {
$module_handler = \Drupal::moduleHandler();
$potential_conflicts = [
'content_translation',
'config_translation',
'language',
];
$should_warn = array_reduce($potential_conflicts, function ($should_warn, $module_name) use ($module_handler) {
return $should_warn ?: $module_handler->moduleExists($module_name);
}, FALSE);
if ($should_warn) {
\Drupal::messenger()->addWarning(t('Some multilingual features currently do not work well with JSON:API. See the <a href=":jsonapi-docs">JSON:API multilingual support documentation</a> for more information on the current status of multilingual support.', [
':jsonapi-docs' => 'https://www.drupal.org/docs/8/modules/jsonapi/translations',
]));
}
}
/**
* Implements hook_requirements().
*/
function jsonapi_requirements($phase) {
$requirements = [];
if ($phase === 'runtime') {
$module_handler = \Drupal::moduleHandler();
$potential_conflicts = [
'content_translation',
'config_translation',
'language',
];
$should_warn = array_reduce($potential_conflicts, function ($should_warn, $module_name) use ($module_handler) {
return $should_warn ?: $module_handler->moduleExists($module_name);
}, FALSE);
if ($should_warn) {
$requirements['jsonapi_multilingual_support'] = [
'title' => t('JSON:API multilingual support'),
'value' => t('Limited'),
'severity' => REQUIREMENT_INFO,
'description' => t('Some multilingual features currently do not work well with JSON:API. See the <a href=":jsonapi-docs">JSON:API multilingual support documentation</a> for more information on the current status of multilingual support.', [
':jsonapi-docs' => 'https://www.drupal.org/docs/8/modules/jsonapi/translations',
]),
];
}
$requirements['jsonapi_revision_support'] = [
'title' => t('JSON:API revision support'),
'value' => t('Limited'),
'severity' => REQUIREMENT_INFO,
'description' => t('Revision support is currently read-only and only for the "Content" and "Media" entity types in JSON:API. See the <a href=":jsonapi-docs">JSON:API revision support documentation</a> for more information on the current status of revision support.', [
':jsonapi-docs' => 'https://www.drupal.org/docs/8/modules/jsonapi/revisions',
]),
];
}
return $requirements;
}
/**
* Enable BC: default the new read-only mode to "off" on existing sites.
*/
function jsonapi_update_8701() {
$config_factory = \Drupal::configFactory();
$jsonapi_settings = $config_factory->getEditable('jsonapi.settings');
$jsonapi_settings->set('read_only', FALSE)
->save(TRUE);
}
jsonapi.settings:
title: 'JSON:API'
parent: system.admin_config_services
description: "Configure whether to allow only read operations or all operations."
route_name: jsonapi.settings
<?php
/**
* @file
* Module implementation file.
*/
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\jsonapi\Routing\Routes as JsonApiRoutes;
/**
* Array key for denoting type-based filtering access.
*
* Array key for denoting access to filter among all entities of a given type,
* regardless of whether they are published or enabled, and regardless of
* their owner.
*
* @see hook_jsonapi_entity_filter_access()
* @see hook_jsonapi_ENTITY_TYPE_filter_access()
*/
const JSONAPI_FILTER_AMONG_ALL = 'filter_among_all';
/**
* Array key for denoting type-based published-only filtering access.
*
* Array key for denoting access to filter among all published entities of a
* given type, regardless of their owner.
*
* This is used when an entity type has a "published" entity key and there's a
* query condition for the value of that equaling 1.
*
* @see hook_jsonapi_entity_filter_access()
* @see hook_jsonapi_ENTITY_TYPE_filter_access()
*/
const JSONAPI_FILTER_AMONG_PUBLISHED = 'filter_among_published';
/**
* Array key for denoting type-based enabled-only filtering access.
*
* Array key for denoting access to filter among all enabled entities of a
* given type, regardless of their owner.
*
* This is used when an entity type has a "status" entity key and there's a
* query condition for the value of that equaling 1.
*
* For the User entity type, which does not have a "status" entity key, the
* "status" field is used.
*
* @see hook_jsonapi_entity_filter_access()
* @see hook_jsonapi_ENTITY_TYPE_filter_access()
*/
const JSONAPI_FILTER_AMONG_ENABLED = 'filter_among_enabled';
/**
* Array key for denoting type-based owned-only filtering access.
*
* Array key for denoting access to filter among all entities of a given type,
* regardless of whether they are published or enabled, so long as they are
* owned by the user for whom access is being checked.
*
* When filtering among User entities, this is used when access is being
* checked for an authenticated user and there's a query condition
* limiting the result set to just that user's entity object.
*
* When filtering among entities of another type, this is used when all of the
* following conditions are met:
* - Access is being checked for an authenticated user.
* - The entity type has an "owner" entity key.
* - There's a filter/query condition for the value equal to the user's ID.
*
* @see hook_jsonapi_entity_filter_access()
* @see hook_jsonapi_ENTITY_TYPE_filter_access()
*/
const JSONAPI_FILTER_AMONG_OWN = 'filter_among_own';
/**
* Implements hook_help().
*/