diff --git a/core/modules/rest/src/Annotation/RestResource.php b/core/modules/rest/src/Annotation/RestResource.php index e63f07d..947e3dd 100644 --- a/core/modules/rest/src/Annotation/RestResource.php +++ b/core/modules/rest/src/Annotation/RestResource.php @@ -43,4 +43,11 @@ class RestResource extends Plugin { */ public $label; + /** + * Options available. + * + * @var array + */ + public $serialization_context; + } diff --git a/core/modules/rest/src/Plugin/rest/resource/UserRegistrationResource.php b/core/modules/rest/src/Plugin/rest/resource/UserRegistrationResource.php new file mode 100644 index 0000000..9c0eb00 --- /dev/null +++ b/core/modules/rest/src/Plugin/rest/resource/UserRegistrationResource.php @@ -0,0 +1,156 @@ +getPluginDefinition(); + // Verify that the deserialized entity is of the type that we expect to + // prevent security issues. + if ($entity->getEntityTypeId() != $definition['serialization_context']['entity_type']) { + throw new BadRequestHttpException('Invalid entity type'); + } + // POSTed entities must not have an ID set, because we always want to create + // new entities here. + if (!$entity->isNew()) { + throw new BadRequestHttpException('Only new entities can be created'); + } + + $approvalSettings = \Drupal::config('user.settings')->get('register'); + // Verify that the current user can register a user account. + if ($approvalSettings == 'admin_only') { + throw new AccessDeniedHttpException('Only administrators can register users.'); + } + // If current user can register accounts then let's block the new registered user if admin approval is needed. + elseif ($approvalSettings == 'visitors_admin_approval') { + $entity->block(); + } + + // Username and email cannot exist. + $this->validate($entity); + + // Cannot add extra roles. + $roles = $entity->getRoles(); + foreach ($roles as $role) { + if ($role != 'authenticated' && $role != 'anonymous') { + throw new BadRequestHttpException(String::format('Anonymous user cannot assign roles when registering a new user account and by default' . + ' authenticated is added, so you cannot assign @role role.', array('@role' => $role))); + } + } + + $entity->save(); + + // "Verify email" option disabled and the account as active means that the user can be logged in now. + if (!\Drupal::config('user.settings')->get('verify_mail') && $entity->isActive()) { + _user_mail_notify('register_no_approval_required', $entity); + user_login_finalize($entity); + } + // The new account as blocked means that it needs approval. + elseif (!$entity->isActive()) { + _user_mail_notify('register_pending_approval', $entity); + } + + return new ResourceResponse(NULL, 201); + + } + + /** + * Implements ResourceInterface::routes(). + */ + public function routes() { + $collection = new RouteCollection(); + + $route_name = strtr($this->pluginId, ':', '.'); + + $methods = $this->availableMethods(); + foreach ($methods as $method) { + $route = $this->getBaseRoute('/entity/user/register', $method); + + switch ($method) { + case 'POST': + $route->setPattern('/entity/user/register'); + // Restrict the incoming HTTP Content-type header to the known + // serialization formats. + $route->addRequirements(array('_content_type_format' => implode('|', $this->serializerFormats))); + $collection->add("$route_name.$method", $route); + break; + + default: + $collection->add("$route_name.$method", $route); + break; + } + } + + return $collection; + } + + /** + * Verifies that the whole entity does not violate any validation constraints. + * + * @param \Drupal\Core\Entity\EntityInterface $entity + * The entity object. + * + * @throws \Symfony\Component\HttpKernel\Exception\HttpException + * If validation errors are found. + */ + protected function validate(EntityInterface $entity) { + $violations = $entity->validate(); + if (count($violations) > 0) { + $message = "Unprocessable Entity: validation failed.\n"; + foreach ($violations as $violation) { + $message .= $violation->getPropertyPath() . ': ' . $violation->getMessage() . "\n"; + } + // Instead of returning a generic 400 response we use the more specific + // 422 Unprocessable Entity code from RFC 4918. That way clients can + // distinguish between general syntax errors in bad serializations (code + // 400) and semantic errors in well-formed requests (code 422). + throw new HttpException(422, $message); + } + } + +} \ No newline at end of file diff --git a/core/modules/rest/src/RequestHandler.php b/core/modules/rest/src/RequestHandler.php index 0ddfdd7..1071ddc 100644 --- a/core/modules/rest/src/RequestHandler.php +++ b/core/modules/rest/src/RequestHandler.php @@ -62,8 +62,17 @@ public function handle(RouteMatchInterface $route_match, Request $request, Route if (empty($method_settings['supported_formats']) || in_array($format, $method_settings['supported_formats'])) { $definition = $resource->getPluginDefinition(); $class = $definition['serialization_class']; + $context = array(); + // Get context information for deserialization from the plugin + // definition. + if (!empty($definition['serialization_context'])) { + $context = $definition['serialization_context']; + } + // Always add the resource ID to the deserialization context. + $context['resource_id'] = $plugin; + $context['request_method'] = $method; try { - $unserialized = $serializer->deserialize($received, $class, $format, array('request_method' => $method)); + $unserialized = $serializer->deserialize($received, $class, $format, $context); } catch (UnexpectedValueException $e) { $error['error'] = $e->getMessage(); diff --git a/core/modules/serialization/src/Normalizer/EntityNormalizer.php b/core/modules/serialization/src/Normalizer/EntityNormalizer.php index 5e81b5a..be2abc8 100644 --- a/core/modules/serialization/src/Normalizer/EntityNormalizer.php +++ b/core/modules/serialization/src/Normalizer/EntityNormalizer.php @@ -45,7 +45,7 @@ public function __construct(EntityManagerInterface $entity_manager) { */ public function denormalize($data, $class, $format = NULL, array $context = array()) { if (empty($context['entity_type'])) { - throw new UnexpectedValueException('Entity type parameter must be included in context.'); + throw new UnexpectedValueException('Entity type parameter must be included in context.' . print_r($context, TRUE)); } $entity_type = $this->entityManager->getDefinition($context['entity_type']);