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 @@ ...@@ -3634,6 +3634,72 @@
], ],
"time": "2016-10-04T09:27:04+00:00" "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", "name": "mikey179/vfsStream",
"version": "v1.6.5", "version": "v1.6.5",
......
...@@ -248,6 +248,11 @@ JavaScript ...@@ -248,6 +248,11 @@ JavaScript
- Matthew Grill 'alwaysworking' https://www.drupal.org/u/alwaysworking - Matthew Grill 'alwaysworking' https://www.drupal.org/u/alwaysworking
- Sally Young 'justafish' https://www.drupal.org/u/justafish - 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 Language
- Francesco Placella 'plach' https://www.drupal.org/u/plach - Francesco Placella 'plach' https://www.drupal.org/u/plach
- Daniel F. Kudwien 'sun' https://www.drupal.org/u/sun - Daniel F. Kudwien 'sun' https://www.drupal.org/u/sun
......
...@@ -64,7 +64,8 @@ ...@@ -64,7 +64,8 @@
"phpspec/prophecy": "^1.7", "phpspec/prophecy": "^1.7",
"symfony/css-selector": "^3.4.0", "symfony/css-selector": "^3.4.0",
"symfony/phpunit-bridge": "^3.4.3", "symfony/phpunit-bridge": "^3.4.3",
"symfony/debug": "^3.4.0" "symfony/debug": "^3.4.0",
"justinrainbow/json-schema": "^5.2"
}, },
"replace": { "replace": {
"drupal/action": "self.version", "drupal/action": "self.version",
...@@ -129,6 +130,7 @@ ...@@ -129,6 +130,7 @@
"drupal/history": "self.version", "drupal/history": "self.version",
"drupal/image": "self.version", "drupal/image": "self.version",
"drupal/inline_form_errors": "self.version", "drupal/inline_form_errors": "self.version",
"drupal/jsonapi": "self.version",
"drupal/language": "self.version", "drupal/language": "self.version",
"drupal/layout_builder": "self.version", "drupal/layout_builder": "self.version",
"drupal/layout_discovery": "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'
This diff is collapsed.
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
This diff is collapsed.
route_callbacks:
- '\Drupal\jsonapi\Routing\Routes::routes'
jsonapi.settings:
path: '/admin/config/services/jsonapi'
defaults:
_form: 'Drupal\jsonapi\Form\JsonApiSettingsForm'
_title: 'JSON:API'
requirements:
_permission: 'administer site configuration'
parameters:
jsonapi.base_path: /jsonapi
services:
jsonapi.serializer:
class: Drupal\jsonapi\Serializer\Serializer
calls:
- [setFallbackNormalizer, ['@serializer']]
arguments: [{ }, { }]
serializer.normalizer.http_exception.jsonapi:
class: Drupal\jsonapi\Normalizer\HttpExceptionNormalizer
arguments: ['@current_user']
tags:
- { name: jsonapi_normalizer }
serializer.normalizer.unprocessable_entity_exception.jsonapi:
class: Drupal\jsonapi\Normalizer\UnprocessableHttpEntityExceptionNormalizer
arguments: ['@current_user']
tags:
# This must have a higher priority than the 'serializer.normalizer.http_exception.jsonapi' to take effect.
- { name: jsonapi_normalizer, priority: 1 }
serializer.normalizer.entity_access_exception.jsonapi:
class: Drupal\jsonapi\Normalizer\EntityAccessDeniedHttpExceptionNormalizer
arguments: ['@current_user']
tags:
# This must have a higher priority than the 'serializer.normalizer.http_exception.jsonapi' to take effect.
- { name: jsonapi_normalizer, priority: 1 }
serializer.normalizer.field_item.jsonapi:
class: Drupal\jsonapi\Normalizer\FieldItemNormalizer
arguments: ['@entity_type.manager']
tags:
- { name: jsonapi_normalizer }
serializer.normalizer.field.jsonapi:
class: Drupal\jsonapi\Normalizer\FieldNormalizer
tags:
- { name: jsonapi_normalizer }
serializer.normalizer.resource_identifier.jsonapi:
class: Drupal\jsonapi\Normalizer\ResourceIdentifierNormalizer
arguments: ['@entity_field.manager']
tags:
- { name: jsonapi_normalizer }
serializer.normalizer.data.jsonapi:
class: Drupal\jsonapi\Normalizer\DataNormalizer
tags:
- { name: jsonapi_normalizer }
serializer.normalizer.resource_object.jsonapi:
class: Drupal\jsonapi\Normalizer\ResourceObjectNormalizer
tags:
- { name: jsonapi_normalizer }
serializer.normalizer.content_entity.jsonapi:
class: Drupal\jsonapi\Normalizer\ContentEntityDenormalizer
arguments: ['@entity_type.manager', '@entity_field.manager', '@plugin.manager.field.field_type']
tags:
- { name: jsonapi_normalizer }
serializer.normalizer.config_entity.jsonapi:
class: Drupal\jsonapi\Normalizer\ConfigEntityDenormalizer
arguments: ['@entity_type.manager', '@entity_field.manager', '@plugin.manager.field.field_type']
tags:
- { name: jsonapi_normalizer }
serializer.normalizer.jsonapi_document_toplevel.jsonapi:
class: Drupal\jsonapi\Normalizer\JsonApiDocumentTopLevelNormalizer
arguments: ['@entity_type.manager', '@jsonapi.resource_type.repository']
tags:
- { name: jsonapi_normalizer }
serializer.normalizer.link_collection.jsonapi:
class: Drupal\jsonapi\Normalizer\LinkCollectionNormalizer
tags:
- { name: jsonapi_normalizer }
serializer.normalizer.entity_reference_field.jsonapi:
class: Drupal\jsonapi\Normalizer\EntityReferenceFieldNormalizer
tags:
# This must have a higher priority than the 'serializer.normalizer.field.jsonapi' to take effect.
- { name: jsonapi_normalizer, priority: 1 }
serializer.encoder.jsonapi:
class: Drupal\jsonapi\Encoder\JsonEncoder
tags:
- { name: jsonapi_encoder, format: 'api_json' }
jsonapi.resource_type.repository:
class: Drupal\jsonapi\ResourceType\ResourceTypeRepository
arguments: ['@entity_type.manager', '@entity_type.bundle.info', '@entity_field.manager', '@cache.jsonapi_resource_types']
jsonapi.route_enhancer:
class: Drupal\jsonapi\Routing\RouteEnhancer
tags:
- { name: route_enhancer }
jsonapi.field_resolver:
class: Drupal\jsonapi\Context\FieldResolver
arguments: ['@entity_type.manager', '@entity_field.manager', '@entity_type.bundle.info', '@jsonapi.resource_type.repository', '@module_handler']
jsonapi.include_resolver:
class: Drupal\jsonapi\IncludeResolver
arguments:
- '@entity_type.manager'
- '@jsonapi.entity_access_checker'
paramconverter.jsonapi.entity_uuid:
parent: paramconverter.entity
class: Drupal\jsonapi\ParamConverter\EntityUuidConverter
calls:
- [setLanguageManager, ['@language_manager']]
tags:
# Priority 10, to ensure it runs before @paramconverter.entity.
- { name: paramconverter, priority: 10 }
paramconverter.jsonapi.resource_type:
class: Drupal\jsonapi\ParamConverter\ResourceTypeConverter
arguments: ['@jsonapi.resource_type.repository']
tags:
- { name: paramconverter }
jsonapi.exception_subscriber:
class: Drupal\jsonapi\EventSubscriber\DefaultExceptionSubscriber
tags:
- { name: event_subscriber }
arguments: ['@jsonapi.serializer', '%serializer.formats%']
logger.channel.jsonapi:
parent: logger.channel_base
arguments: ['jsonapi']
# Cache.
cache.jsonapi_resource_types:
class: Drupal\Core\Cache\MemoryCache\MemoryCache
# We need this to add this to the Drupal's cache_tags.invalidator service.
# This way it can invalidate the data in here based on tags.
tags: [{ name: cache.bin }]
# Route filter.
jsonapi.route_filter.format_setter:
class: Drupal\jsonapi\Routing\EarlyFormatSetter
tags:
# Set to a high priority so it runs before content_type_header_matcher
# and other filters that might throw exceptions.
- { name: route_filter, priority: 100 }
# Access Control
jsonapi.entity_access_checker:
class: Drupal\jsonapi\Access\EntityAccessChecker
public: false
arguments: ['@jsonapi.resource_type.repository', '@router.no_access_checks', '@current_user', '@entity.repository']
calls:
- [setNodeRevisionAccessCheck, ['@?access_check.node.revision']] # This is only injected when the service is available.
- [setMediaRevisionAccessCheck, ['@?access_check.media.revision']] # This is only injected when the service is available.
# This is a temporary measure. JSON:API should not need to be aware of the Content Moderation module.
- [setLatestRevisionCheck, ['@?access_check.latest_revision']] # This is only injected when the service is available.
access_check.jsonapi.relationship_field_access:
class: Drupal\jsonapi\Access\RelationshipFieldAccess
arguments: ['@jsonapi.entity_access_checker']
tags:
- { name: access_check, applies_to: _jsonapi_relationship_field_access, needs_incoming_request: TRUE }
# Route filters.
method_filter.jsonapi:
public: false
class: Drupal\jsonapi\Routing\ReadOnlyModeMethodFilter
decorates: method_filter
arguments: ['@method_filter.jsonapi.inner', '@config.factory']
# Controller.
jsonapi.entity_resource:
class: Drupal\jsonapi\Controller\EntityResource
arguments:
- '@entity_type.manager'
- '@entity_field.manager'
- '@jsonapi.resource_type.repository'
- '@renderer'
- '@entity.repository'
- '@jsonapi.include_resolver'
- '@jsonapi.entity_access_checker'
- '@jsonapi.field_resolver'
- '@jsonapi.serializer'
- '@datetime.time'
- '@current_user'
jsonapi.file_upload:
class: Drupal\jsonapi\Controller\FileUpload
arguments:
- '@current_user'
- '@entity_field.manager'
- '@jsonapi.file.uploader.field'
- '@http_kernel'
# Event subscribers.
jsonapi.custom_query_parameter_names_validator.subscriber:
class: Drupal\jsonapi\EventSubscriber\JsonApiRequestValidator
tags:
- { name: event_subscriber }
jsonapi.resource_response.subscriber:
class: Drupal\jsonapi\EventSubscriber\ResourceResponseSubscriber
arguments: ['@jsonapi.serializer']
tags:
- { name: event_subscriber }
jsonapi.resource_response_validator.subscriber:
class: Drupal\jsonapi\EventSubscriber\ResourceResponseValidator
arguments: ['@jsonapi.serializer', '@logger.channel.jsonapi', '@module_handler', '@app.root']
calls:
- [setValidator, []]
tags:
- { name: event_subscriber, priority: 1000 }
# Revision management.
jsonapi.version_negotiator:
class: Drupal\jsonapi\Revisions\VersionNegotiator
public: false
tags:
- { name: service_collector, tag: jsonapi_version_negotiator, call: addVersionNegotiator }
jsonapi.version_negotiator.default:
arguments: ['@entity_type.manager']
public: false
abstract: true
jsonapi.version_negotiator.id:
class: Drupal\jsonapi\Revisions\VersionById
parent: jsonapi.version_negotiator.default
tags:
- { name: jsonapi_version_negotiator, negotiator_name: 'id' }
jsonapi.version_negotiator.rel:
class: Drupal\jsonapi\Revisions\VersionByRel
parent: jsonapi.version_negotiator.default
tags:
- { name: jsonapi_version_negotiator, negotiator_name: 'rel' }
jsonapi.resource_version.route_enhancer:
class: Drupal\jsonapi\Revisions\ResourceVersionRouteEnhancer
public: false
arguments:
- '@jsonapi.version_negotiator'
tags:
- { name: route_enhancer }
# Deprecated services.
serializer.normalizer.htt_exception.jsonapi:
alias: serializer.normalizer.http_exception.jsonapi
deprecated: The "%service_id%" service is deprecated. You should use the 'serializer.normalizer.http_exception.jsonapi' service instead.
# @todo Remove once https://www.drupal.org/project/drupal/issues/2940383 lands.
jsonapi.file.uploader.field:
class: Drupal\jsonapi\Controller\TemporaryJsonapiFileFieldUploader
public: false
arguments: ['@logger.channel.file', '@file_system', '@file.mime_type.guesser', '@token', '@lock', '@config.factory']
This diff is collapsed.
This diff is collapsed.
<?php
namespace Drupal\jsonapi\Access;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Access\AccessResultReasonInterface;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\Entity\FieldableEntityInterface;
use Drupal\Core\Http\Exception\CacheableAccessDeniedHttpException;
use Drupal\Core\Routing\Access\AccessInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\jsonapi\ResourceType\ResourceType;
use Drupal\jsonapi\Routing\Routes;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Route;
/**
* Defines a class to check access to related and relationship routes.
*
* @internal JSON:API maintains no PHP API. The API is the HTTP API. This class
* may change at any time and could break any dependencies on it.
*
* @see https://www.drupal.org/project/jsonapi/issues/3032787
* @see jsonapi.api.php
*/
class RelationshipFieldAccess implements AccessInterface {
/**
* The route requirement key for this access check.
*
* @var string
*/
const ROUTE_REQUIREMENT_KEY = '_jsonapi_relationship_field_access';
/**
* The JSON:API entity access checker.
*
* @var \Drupal\jsonapi\Access\EntityAccessChecker
*/
protected $entityAccessChecker;
/**
* RelationshipFieldAccess constructor.
*
* @param \Drupal\jsonapi\Access\EntityAccessChecker $entity_access_checker
* The JSON:API entity access checker.
*/
public function __construct(EntityAccessChecker $entity_access_checker) {
$this->entityAccessChecker = $entity_access_checker;
}
/**
* Checks access to the relationship field on the given route.
*
* @param \Symfony\Component\HttpFoundation\Request $request
* The incoming HTTP request object.
* @param \Symfony\Component\Routing\Route $route
* The route to check against.
* @param \Drupal\Core\Session\AccountInterface $account
* The currently logged in account.
*
* @return \Drupal\Core\Access\AccessResultInterface
* The access result.
*/
public function access(Request $request, Route $route, AccountInterface $account) {
$relationship_field_name = $route->getRequirement(static::ROUTE_REQUIREMENT_KEY);
$field_operation = $request->isMethodCacheable() ? 'view' : 'edit';
$entity_operation = $request->isMethodCacheable() ? 'view' : 'update';
if ($resource_type = $request->get(Routes::RESOURCE_TYPE_KEY)) {
assert($resource_type instanceof ResourceType);
$entity = $request->get('entity');
$internal_name = $resource_type->getInternalName($relationship_field_name);
if ($entity instanceof FieldableEntityInterface && $entity->hasField($internal_name)) {
$entity_access = $this->entityAccessChecker->checkEntityAccess($entity, $entity_operation, $account);
$field_access = $entity->get($internal_name)->access($field_operation, $account, TRUE);
// Ensure that access is respected for different entity revisions.
$access_result = $entity_access->andIf($field_access);
if (!$access_result->isAllowed()) {
$reason = "The current user is not allowed to {$field_operation} this relationship.";
$access_reason = $access_result instanceof AccessResultReasonInterface ? $access_result->getReason() : NULL;
$detailed_reason = empty($access_reason) ? $reason : $reason . " {$access_reason}";
$access_result->setReason($detailed_reason);
if ($request->isMethodCacheable()) {
throw new CacheableAccessDeniedHttpException(CacheableMetadata::createFromObject($access_result), $detailed_reason);
}
}
return $access_result;
}
}
return AccessResult::neutral();
}
}
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
<?php
namespace Drupal\jsonapi\Controller;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Url;
use Drupal\jsonapi\JsonApiResource\JsonApiDocumentTopLevel;
use Drupal\jsonapi\JsonApiResource\LinkCollection;
use Drupal\jsonapi\JsonApiResource\NullIncludedData;
use Drupal\jsonapi\JsonApiResource\Link;
use Drupal\jsonapi\JsonApiResource\ResourceObjectData;
use Drupal\jsonapi\ResourceResponse;
use Drupal\jsonapi\ResourceType\ResourceType;
use Drupal\jsonapi\ResourceType\ResourceTypeRepositoryInterface;
use Drupal\user\Entity\User;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\Routing\Exception\RouteNotFoundException;
/**
* Controller for the API entry point.
*
* @internal JSON:API maintains no PHP API. The API is the HTTP API. This class
* may change at any time and could break any dependencies on it.
*
* @see https://www.drupal.org/project/jsonapi/issues/3032787
* @see jsonapi.api.php
*/
class EntryPoint extends ControllerBase {
/**
* The JSON:API resource type repository.
*
* @var \Drupal\jsonapi\ResourceType\ResourceTypeRepositoryInterface
*/
protected $resourceTypeRepository;
/**
* The account object.
*
* @var \Drupal\Core\Session\AccountInterface
*/
protected $user;
/**
* EntryPoint constructor.
*
* @param \Drupal\jsonapi\ResourceType\ResourceTypeRepositoryInterface $resource_type_repository
* The resource type repository.
* @param \Drupal\Core\Session\AccountInterface $user
* The current user.
*/
public function __construct(ResourceTypeRepositoryInterface $resource_type_repository, AccountInterface $user) {
$this->resourceTypeRepository = $resource_type_repository;
$this->user = $user;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('jsonapi.resource_type.repository'),
$container->get(