diff --git a/core/modules/rest/src/Plugin/rest/resource/UserRegistrationResource.php b/core/modules/rest/src/Plugin/rest/resource/UserRegistrationResource.php index f384346..30e1265 100644 --- a/core/modules/rest/src/Plugin/rest/resource/UserRegistrationResource.php +++ b/core/modules/rest/src/Plugin/rest/resource/UserRegistrationResource.php @@ -2,15 +2,19 @@ /** * @file - * Definition of Drupal\rest\Plugin\rest\resource\UserRegistrationResource. + * Contains Drupal\rest\Plugin\rest\resource\UserRegistrationResource. */ namespace Drupal\rest\Plugin\rest\resource; -use Drupal\Core\Entity\EntityInterface; +use Drupal\Component\Utility\SafeMarkup; +use Drupal\Core\Config\ConfigFactoryInterface; +use Drupal\Core\Entity\ContentEntityBase; use Drupal\rest\ResourceResponse; +use Drupal\user\Entity\User; +use Psr\Log\LoggerInterface; +use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\Routing\RouteCollection; -use Drupal\Component\Utility\String; use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; use Drupal\rest\Plugin\ResourceBase; @@ -33,70 +37,109 @@ class UserRegistrationResource extends ResourceBase { /** + * The config factory. + * + * @var \Drupal\Core\Config\ConfigFactoryInterface + */ + protected $configFactory; + + /** + * Constructs a new RestPermissions instance. + * + * @param array $configuration + * A configuration array containing information about the plugin instance. + * @param string $plugin_id + * The plugin_id for the plugin instance. + * @param mixed $plugin_definition + * The plugin implementation definition. + * @param array $serializer_formats + * The available serialization formats. + * @param LoggerInterface $loggery + * A logger instance. + * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory + * The config factory. + */ + public function __construct(array $configuration, $plugin_id, $plugin_definition, array $serializer_formats, LoggerInterface $loggery, ConfigFactoryInterface $config_factory) { + parent::__construct($configuration, $plugin_id, $plugin_definition, $serializer_formats, $loggery); + $this->configFactory = $config_factory; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + return new static( + $configuration, + $plugin_id, + $plugin_definition, + $container->getParameter('serializer.formats'), + $container->get('logger.factory')->get('rest'), + $container->get('config.factory') + ); + } + + /** * Responds to entity POST requests and saves the new entity. * - * @param \Drupal\Core\Entity\EntityInterface $entity - * The entity. + * @param \Drupal\user\Entity\User $account + * The user account entity. * * @return \Drupal\rest\ResourceResponse * The HTTP response object. * * @throws \Symfony\Component\HttpKernel\Exception\HttpException */ - public function post(EntityInterface $entity = NULL) { - - if ($entity == NULL) { - throw new BadRequestHttpException('No entity content received.'); + public function post(User $account = NULL) { + if ($account == NULL) { + throw new BadRequestHttpException('No user account data for registration received.'); } - $definition = $this->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'); + if ($account->getEntityTypeId() != 'user') { + 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'); + // POSTed user accounts must not have an ID set, because we always + // want to create new entities here. + if (!$account->isNew()) { + throw new BadRequestHttpException('Only new user accounts can be created.'); } - $approvalSettings = \Drupal::config('user.settings')->get('register'); + $approvalSettings = $this->configFactory->get('user.settings')->get('register'); // Verify that the current user can register a user account. - if ($approvalSettings == 'admin_only') { + if ($approvalSettings == USER_REGISTER_ADMINISTRATORS_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(); + elseif ($approvalSettings == USER_REGISTER_VISITORS_ADMINISTRATIVE_APPROVAL) { + $account->block(); } - // Username and email cannot exist. - $this->validate($entity); + // Make sure that the user entity is valid (email and name are valid). + $this->validate($account); // Cannot add extra roles. - $roles = $entity->getRoles(); + $roles = $account->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' . + throw new BadRequestHttpException(SafeMarkup::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(); + $account->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); + if (!$this->configFactory->get('user.settings')->get('verify_mail') && $account->isActive()) { + _user_mail_notify('register_no_approval_required', $account); + user_login_finalize($account); } // The new account as blocked means that it needs approval. - elseif (!$entity->isActive()) { - _user_mail_notify('register_pending_approval', $entity); + elseif (!$account->isActive()) { + _user_mail_notify('register_pending_approval', $account); } return new ResourceResponse(NULL, 201); - } /** @@ -113,7 +156,7 @@ public function routes() { switch ($method) { case 'POST': - $route->setPattern('/entity/user/register'); + $route->setPath('/entity/user/register'); // Restrict the incoming HTTP Content-type header to the known // serialization formats. $route->addRequirements(array('_content_type_format' => implode('|', $this->serializerFormats))); @@ -132,13 +175,13 @@ public function routes() { /** * Verifies that the whole entity does not violate any validation constraints. * - * @param \Drupal\Core\Entity\EntityInterface $entity + * @param \Drupal\Core\Entity\ContentEntityBase $entity * The entity object. * * @throws \Symfony\Component\HttpKernel\Exception\HttpException * If validation errors are found. */ - protected function validate(EntityInterface $entity) { + protected function validate(ContentEntityBase $entity) { $violations = $entity->validate(); if (count($violations) > 0) { $message = "Unprocessable Entity: validation failed.\n"; diff --git a/core/modules/rest/tests/src/Unit/UserRegistrationResourceTest.php b/core/modules/rest/tests/src/Unit/UserRegistrationResourceTest.php new file mode 100644 index 0000000..844b845 --- /dev/null +++ b/core/modules/rest/tests/src/Unit/UserRegistrationResourceTest.php @@ -0,0 +1,199 @@ +configStub = $this->getConfigFactoryStub(array( + 'user.settings' => array( + 'register' => USER_REGISTER_VISITORS, + 'verify_mail' => FALSE + ), + )); + $this->logger = $this->getMock('Psr\Log\LoggerInterface'); + $this->testClass = new UserRegistrationResource([], 'plugin_id', '', [], $this->logger, $this->configStub); + $this->reflection = new \ReflectionClass($this->testClass); + } + + public function getProtectedMethod($method) { + $method = $this->reflection->getMethod($method); + $method->setAccessible(TRUE); + + return $method; + } + + public function testValidate() { + $entity = $this->getMockBuilder('Drupal\user\Entity\User') + ->disableOriginalConstructor() + ->getMock(); + $entity->expects($this->once()) + ->method('validate') + ->will($this->returnValue(array())); + + $method = $this->getProtectedMethod('validate'); + // No exception is thrown. + $method->invokeArgs($this->testClass, array($entity)); + } + + /** + * @expectedException \Symfony\Component\HttpKernel\Exception\HttpException + * @expectedException UserRegistrationResourceTest::ERROR_MESSAGE + */ + public function testFailedValidate() { + $violation1 = $this->getMock('Symfony\Component\Validator\ConstraintViolationInterface'); + $violation1->expects($this->once()) + ->method('getPropertyPath') + ->will($this->returnValue('property_path')); + $violation1->expects($this->once()) + ->method('getMessage') + ->will($this->returnValue('message')); + $violation2 = $this->getMock('Symfony\Component\Validator\ConstraintViolationInterface'); + $violation2->expects($this->once()) + ->method('getPropertyPath') + ->will($this->returnValue('property_path_2')); + $violation2->expects($this->once()) + ->method('getMessage') + ->will($this->returnValue('message_2')); + $entity = $this->getMockBuilder('Drupal\user\Entity\User') + ->disableOriginalConstructor() + ->getMock(); + $entity->expects($this->once()) + ->method('validate') + ->will($this->returnValue(array($violation1, $violation2))); + + $method = $this->getProtectedMethod('validate'); + // No exception is thrown. + $method->invoke($this->testClass, $entity); + } + + /** + * @expectedException \Symfony\Component\HttpKernel\Exception\BadRequestHttpException + * @expectedExceptionMessage No user account data for registration received. + */ + public function testEmptyPost() { + $this->testClass->post(NULL); + } + + /** + * @expectedException \Symfony\Component\HttpKernel\Exception\BadRequestHttpException + * @expectedExceptionMessage Invalid entity type. + */ + public function testInvalidEntityTypePost() { + $entity = $this->getMockBuilder('Drupal\user\Entity\User') + ->disableOriginalConstructor() + ->getMock(); + $entity->expects($this->once()) + ->method('getEntityTypeId') + ->will($this->returnValue('node')); + $this->testClass->post($entity); + } + + /** + * @expectedException \Symfony\Component\HttpKernel\Exception\BadRequestHttpException + * @expectedExceptionMessage Only new user accounts can be created. + */ + public function testExistedEntityPost() { + $entity = $this->getMockBuilder('Drupal\user\Entity\User') + ->disableOriginalConstructor() + ->getMock(); + $entity->expects($this->once()) + ->method('getEntityTypeId') + ->will($this->returnValue('user')); + $entity->expects($this->once()) + ->method('isNew') + ->will($this->returnValue(FALSE)); + $this->testClass->post($entity); + } + + /** + * @expectedException \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException + * @expectedExceptionMessage Only administrators can register users. + */ + public function testRegistrationAdminOnlyPost() { + $this->configStub = $this->getConfigFactoryStub(array( + 'user.settings' => array( + 'register' => USER_REGISTER_ADMINISTRATORS_ONLY, + 'verify_mail' => FALSE + ), + )); + $this->testClass = new UserRegistrationResource([], 'plugin_id', '', [], $this->logger, $this->configStub); + $entity = $this->getMockBuilder('Drupal\user\Entity\User') + ->disableOriginalConstructor() + ->getMock(); + $entity->expects($this->once()) + ->method('getEntityTypeId') + ->will($this->returnValue('user')); + $entity->expects($this->once()) + ->method('isNew') + ->will($this->returnValue(TRUE)); + $this->testClass->post($entity); + } + + /** + * @expectedException \Symfony\Component\HttpKernel\Exception\BadRequestHttpException + */ + public function testInvalidRolesPost() { + $this->configStub = $this->getConfigFactoryStub(array( + 'user.settings' => array( + 'register' => USER_REGISTER_VISITORS_ADMINISTRATIVE_APPROVAL, + 'verify_mail' => FALSE + ), + )); + $this->testClass = new UserRegistrationResource([], 'plugin_id', '', [], $this->logger, $this->configStub); + $entity = $this->getMockBuilder('Drupal\user\Entity\User') + ->disableOriginalConstructor() + ->getMock(); + $entity->expects($this->once()) + ->method('getEntityTypeId') + ->will($this->returnValue('user')); + $entity->expects($this->once()) + ->method('isNew') + ->will($this->returnValue(TRUE)); + $entity->expects($this->once()) + ->method('getRoles') + ->will($this->returnValue(array('administrator'))); + $this->testClass->post($entity); + } + +}