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/EntityResource.php b/core/modules/rest/src/Plugin/rest/resource/EntityResource.php index b7d0264..03555b2 100644 --- a/core/modules/rest/src/Plugin/rest/resource/EntityResource.php +++ b/core/modules/rest/src/Plugin/rest/resource/EntityResource.php @@ -89,7 +89,7 @@ public function post(EntityInterface $entity = NULL) { } foreach ($entity as $field_name => $field) { if (!$field->access('create')) { - throw new AccessDeniedHttpException(String::format('Access denied on creating field ', array('@field' => $field_name))); + throw new AccessDeniedHttpException(String::format('Access denied on creating field @field', array('@field' => $field_name))); } } 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..14dedb1 --- /dev/null +++ b/core/modules/rest/src/Plugin/rest/resource/UserRegistrationResource.php @@ -0,0 +1,168 @@ +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; + } + + + /** + * Validate the user entity values added for a new user registration. + * + * @param AccountInterface $account + * @throws \Symfony\Component\HttpKernel\Exception\BadRequestHttpException + */ + public function validate(AccountInterface $account) { + // New username cannot exist. + $name_taken = (bool) \Drupal::service('entity.query')->get('user') + ->condition('uid', (int) $account->id(), '<>') + ->condition('name', $account->getUsername()) + ->range(0, 1) + ->count() + ->execute(); + + if ($name_taken) { + throw new BadRequestHttpException(String::format('The username @name is already taken.', array('@name' => $account->getUsername()))); + } + + // New email cannot exist. + $mail_taken = (bool) \Drupal::service('entity.query')->get('user') + ->condition('uid', (int) $account->id(), '<>') + ->condition('mail', $account->getEmail()) + ->range(0, 1) + ->count() + ->execute(); + + if ($mail_taken) { + throw new BadRequestHttpException(String::format('The email address @email is already registered', array('@email' => $account->getEmail()))); + } + } + +} \ No newline at end of file diff --git a/core/modules/rest/src/RequestHandler.php b/core/modules/rest/src/RequestHandler.php index c31600d..d48fe03 100644 --- a/core/modules/rest/src/RequestHandler.php +++ b/core/modules/rest/src/RequestHandler.php @@ -60,8 +60,17 @@ public function handle(RouteMatchInterface $route_match, Request $request) { 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']);