aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorJeremy Benoist <jeremy.benoist@gmail.com>2017-05-30 07:56:01 +0200
committerJeremy Benoist <jeremy.benoist@gmail.com>2017-05-30 07:56:01 +0200
commit5709ecb36809fb009446a11a758232bbe8f264e4 (patch)
tree0880f40fe8e6e563ef5648c267bb1f65251a0355
parent2251045901875aa815dee43ec467fb1af8d416d0 (diff)
downloadwallabag-5709ecb36809fb009446a11a758232bbe8f264e4.tar.gz
wallabag-5709ecb36809fb009446a11a758232bbe8f264e4.tar.zst
wallabag-5709ecb36809fb009446a11a758232bbe8f264e4.zip
Re-use `NewUserType` to validate registration
The only ugly things is how we handle error by generating the view and then parse the content to retrieve all errors… Fix exposition fields in User entity
-rw-r--r--src/Wallabag/ApiBundle/Controller/UserRestController.php119
-rw-r--r--src/Wallabag/UserBundle/Entity/User.php25
-rw-r--r--tests/Wallabag/ApiBundle/Controller/UserRestControllerTest.php98
-rw-r--r--tests/Wallabag/ApiBundle/WallabagApiTestCase.php2
4 files changed, 200 insertions, 44 deletions
diff --git a/src/Wallabag/ApiBundle/Controller/UserRestController.php b/src/Wallabag/ApiBundle/Controller/UserRestController.php
index c5ffbdf1..a1b78e3f 100644
--- a/src/Wallabag/ApiBundle/Controller/UserRestController.php
+++ b/src/Wallabag/ApiBundle/Controller/UserRestController.php
@@ -6,12 +6,14 @@ use FOS\UserBundle\Event\UserEvent;
6use FOS\UserBundle\FOSUserEvents; 6use FOS\UserBundle\FOSUserEvents;
7use JMS\Serializer\SerializationContext; 7use JMS\Serializer\SerializationContext;
8use Nelmio\ApiDocBundle\Annotation\ApiDoc; 8use Nelmio\ApiDocBundle\Annotation\ApiDoc;
9use Symfony\Component\HttpFoundation\Request;
9use Symfony\Component\HttpFoundation\JsonResponse; 10use Symfony\Component\HttpFoundation\JsonResponse;
11use Wallabag\UserBundle\Entity\User;
10 12
11class UserRestController extends WallabagRestController 13class UserRestController extends WallabagRestController
12{ 14{
13 /** 15 /**
14 * Retrieve user informations 16 * Retrieve current logged in user informations.
15 * 17 *
16 * @ApiDoc() 18 * @ApiDoc()
17 * 19 *
@@ -21,78 +23,117 @@ class UserRestController extends WallabagRestController
21 { 23 {
22 $this->validateAuthentication(); 24 $this->validateAuthentication();
23 25
24 $serializationContext = SerializationContext::create()->setGroups(['user_api']); 26 return $this->sendUser($this->getUser());
25 $json = $this->get('serializer')->serialize($this->getUser(), 'json', $serializationContext);
26
27 return (new JsonResponse())->setJson($json);
28 } 27 }
29 28
30 /** 29 /**
31 * Register an user 30 * Register an user.
32 * 31 *
33 * @ApiDoc( 32 * @ApiDoc(
34 * requirements={ 33 * requirements={
35 * {"name"="username", "dataType"="string", "required"=true, "description"="The user's username"}, 34 * {"name"="username", "dataType"="string", "required"=true, "description"="The user's username"},
36 * {"name"="password", "dataType"="string", "required"=true, "description"="The user's password"} 35 * {"name"="password", "dataType"="string", "required"=true, "description"="The user's password"},
37 * {"name"="email", "dataType"="string", "required"=true, "description"="The user's email"} 36 * {"name"="email", "dataType"="string", "required"=true, "description"="The user's email"}
38 * } 37 * }
39 * ) 38 * )
39 *
40 * @todo Make this method (or the whole API) accessible only through https
41 *
40 * @return JsonResponse 42 * @return JsonResponse
41 */ 43 */
42 // TODO : Make this method (or the whole API) accessible only through https 44 public function putUserAction(Request $request)
43 public function putUserAction($username, $password, $email)
44 { 45 {
45 if (!$this->container->getParameter('fosuser_registration')) { 46 if (!$this->container->getParameter('fosuser_registration')) {
46 $json = $this->get('serializer')->serialize(['error' => "Server doesn't allow registrations"], 'json'); 47 $json = $this->get('serializer')->serialize(['error' => "Server doesn't allow registrations"], 'json');
48
47 return (new JsonResponse())->setJson($json)->setStatusCode(403); 49 return (new JsonResponse())->setJson($json)->setStatusCode(403);
48 } 50 }
49 51
50 if ($password === '') { // TODO : might be a good idea to enforce restrictions here 52 $userManager = $this->get('fos_user.user_manager');
51 $json = $this->get('serializer')->serialize(['error' => 'Password is blank'], 'json'); 53 $user = $userManager->createUser();
52 return (new JsonResponse())->setJson($json)->setStatusCode(400); 54 // enable created user by default
53 } 55 $user->setEnabled(true);
54 56
57 $form = $this->createForm('Wallabag\UserBundle\Form\NewUserType', $user, [
58 'csrf_protection' => false,
59 ]);
55 60
56 // TODO : Make only one call to database by using a custom repository method 61 // simulate form submission
57 if ($this->getDoctrine() 62 $form->submit([
58 ->getRepository('WallabagUserBundle:User') 63 'username' => $request->request->get('username'),
59 ->findOneByUserName($username)) { 64 'plainPassword' => [
60 $json = $this->get('serializer')->serialize(['error' => 'Username is already taken'], 'json'); 65 'first' => $request->request->get('password'),
61 return (new JsonResponse())->setJson($json)->setStatusCode(409); 66 'second' => $request->request->get('password'),
62 } 67 ],
68 'email' => $request->request->get('email'),
69 ]);
63 70
64 if ($this->getDoctrine() 71 if ($form->isSubmitted() && false === $form->isValid()) {
65 ->getRepository('WallabagUserBundle:User') 72 $view = $this->view($form, 400);
66 ->findOneByEmail($email)) { 73 $view->setFormat('json');
67 $json = $this->get('serializer')->serialize(['error' => 'An account with this email already exists'], 'json');
68 return (new JsonResponse())->setJson($json)->setStatusCode(409);
69 }
70 74
71 $em = $this->get('doctrine.orm.entity_manager'); 75 // handle errors in a more beautiful way than the default view
76 $data = json_decode($this->handleView($view)->getContent(), true)['children'];
77 $errors = [];
72 78
73 $userManager = $this->get('fos_user.user_manager'); 79 if (isset($data['username']['errors'])) {
74 $user = $userManager->createUser(); 80 $errors['username'] = $this->translateErrors($data['username']['errors']);
81 }
75 82
76 $user->setUsername($username); 83 if (isset($data['email']['errors'])) {
84 $errors['email'] = $this->translateErrors($data['email']['errors']);
85 }
77 86
78 $user->setPlainPassword($password); 87 if (isset($data['plainPassword']['children']['first']['errors'])) {
88 $errors['password'] = $this->translateErrors($data['plainPassword']['children']['first']['errors']);
89 }
79 90
80 $user->setEmail($email); 91 $json = $this->get('serializer')->serialize(['error' => $errors], 'json');
81 92
82 $user->setEnabled(true); 93 return (new JsonResponse())->setJson($json)->setStatusCode(400);
83 $user->addRole('ROLE_USER'); 94 }
84 95
85 $em->persist($user); 96 $userManager->updateUser($user);
86 97
87 // dispatch a created event so the associated config will be created 98 // dispatch a created event so the associated config will be created
88 $event = new UserEvent($user); 99 $event = new UserEvent($user, $request);
89 $this->get('event_dispatcher')->dispatch(FOSUserEvents::USER_CREATED, $event); 100 $this->get('event_dispatcher')->dispatch(FOSUserEvents::USER_CREATED, $event);
90 101
91 $serializationContext = SerializationContext::create()->setGroups(['user_api']); 102 return $this->sendUser($user);
92 $json = $this->get('serializer')->serialize($user, 'json', $serializationContext); 103 }
93 104
94 return (new JsonResponse())->setJson($json); 105 /**
106 * Send user response.
107 *
108 * @param User $user
109 *
110 * @return JsonResponse
111 */
112 private function sendUser(User $user)
113 {
114 $json = $this->get('serializer')->serialize(
115 $user,
116 'json',
117 SerializationContext::create()->setGroups(['user_api'])
118 );
95 119
120 return (new JsonResponse())->setJson($json);
96 } 121 }
97 122
123 /**
124 * Translate errors message.
125 *
126 * @param array $errors
127 *
128 * @return array
129 */
130 private function translateErrors($errors)
131 {
132 $translatedErrors = [];
133 foreach ($errors as $error) {
134 $translatedErrors[] = $this->get('translator')->trans($error);
135 }
136
137 return $translatedErrors;
138 }
98} 139}
diff --git a/src/Wallabag/UserBundle/Entity/User.php b/src/Wallabag/UserBundle/Entity/User.php
index 1863c966..1ff3046a 100644
--- a/src/Wallabag/UserBundle/Entity/User.php
+++ b/src/Wallabag/UserBundle/Entity/User.php
@@ -5,11 +5,10 @@ namespace Wallabag\UserBundle\Entity;
5use Doctrine\Common\Collections\ArrayCollection; 5use Doctrine\Common\Collections\ArrayCollection;
6use Doctrine\ORM\Mapping as ORM; 6use Doctrine\ORM\Mapping as ORM;
7use JMS\Serializer\Annotation\Groups; 7use JMS\Serializer\Annotation\Groups;
8use JMS\Serializer\Annotation\XmlRoot;
8use Scheb\TwoFactorBundle\Model\Email\TwoFactorInterface; 9use Scheb\TwoFactorBundle\Model\Email\TwoFactorInterface;
9use Scheb\TwoFactorBundle\Model\TrustedComputerInterface; 10use Scheb\TwoFactorBundle\Model\TrustedComputerInterface;
10use FOS\UserBundle\Model\User as BaseUser; 11use FOS\UserBundle\Model\User as BaseUser;
11use JMS\Serializer\Annotation\ExclusionPolicy;
12use JMS\Serializer\Annotation\Expose;
13use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; 12use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
14use Symfony\Component\Security\Core\User\UserInterface; 13use Symfony\Component\Security\Core\User\UserInterface;
15use Wallabag\ApiBundle\Entity\Client; 14use Wallabag\ApiBundle\Entity\Client;
@@ -19,23 +18,24 @@ use Wallabag\CoreBundle\Entity\Entry;
19/** 18/**
20 * User. 19 * User.
21 * 20 *
21 * @XmlRoot("user")
22 * @ORM\Entity(repositoryClass="Wallabag\UserBundle\Repository\UserRepository") 22 * @ORM\Entity(repositoryClass="Wallabag\UserBundle\Repository\UserRepository")
23 * @ORM\Table(name="`user`") 23 * @ORM\Table(name="`user`")
24 * @ORM\HasLifecycleCallbacks() 24 * @ORM\HasLifecycleCallbacks()
25 * @ExclusionPolicy("all")
26 * 25 *
27 * @UniqueEntity("email") 26 * @UniqueEntity("email")
28 * @UniqueEntity("username") 27 * @UniqueEntity("username")
29 */ 28 */
30class User extends BaseUser implements TwoFactorInterface, TrustedComputerInterface 29class User extends BaseUser implements TwoFactorInterface, TrustedComputerInterface
31{ 30{
31 /** @Serializer\XmlAttribute */
32 /** 32 /**
33 * @var int 33 * @var int
34 * 34 *
35 * @Expose
36 * @ORM\Column(name="id", type="integer") 35 * @ORM\Column(name="id", type="integer")
37 * @ORM\Id 36 * @ORM\Id
38 * @ORM\GeneratedValue(strategy="AUTO") 37 * @ORM\GeneratedValue(strategy="AUTO")
38 *
39 * @Groups({"user_api"}) 39 * @Groups({"user_api"})
40 */ 40 */
41 protected $id; 41 protected $id;
@@ -44,14 +44,30 @@ class User extends BaseUser implements TwoFactorInterface, TrustedComputerInterf
44 * @var string 44 * @var string
45 * 45 *
46 * @ORM\Column(name="name", type="text", nullable=true) 46 * @ORM\Column(name="name", type="text", nullable=true)
47 *
47 * @Groups({"user_api"}) 48 * @Groups({"user_api"})
48 */ 49 */
49 protected $name; 50 protected $name;
50 51
51 /** 52 /**
53 * @var string
54 *
55 * @Groups({"user_api"})
56 */
57 protected $username;
58
59 /**
60 * @var string
61 *
62 * @Groups({"user_api"})
63 */
64 protected $email;
65
66 /**
52 * @var date 67 * @var date
53 * 68 *
54 * @ORM\Column(name="created_at", type="datetime") 69 * @ORM\Column(name="created_at", type="datetime")
70 *
55 * @Groups({"user_api"}) 71 * @Groups({"user_api"})
56 */ 72 */
57 protected $createdAt; 73 protected $createdAt;
@@ -60,6 +76,7 @@ class User extends BaseUser implements TwoFactorInterface, TrustedComputerInterf
60 * @var date 76 * @var date
61 * 77 *
62 * @ORM\Column(name="updated_at", type="datetime") 78 * @ORM\Column(name="updated_at", type="datetime")
79 *
63 * @Groups({"user_api"}) 80 * @Groups({"user_api"})
64 */ 81 */
65 protected $updatedAt; 82 protected $updatedAt;
diff --git a/tests/Wallabag/ApiBundle/Controller/UserRestControllerTest.php b/tests/Wallabag/ApiBundle/Controller/UserRestControllerTest.php
new file mode 100644
index 00000000..21d59a16
--- /dev/null
+++ b/tests/Wallabag/ApiBundle/Controller/UserRestControllerTest.php
@@ -0,0 +1,98 @@
1<?php
2
3namespace Tests\Wallabag\ApiBundle\Controller;
4
5use Tests\Wallabag\ApiBundle\WallabagApiTestCase;
6
7class UserRestControllerTest extends WallabagApiTestCase
8{
9 public function testGetUser()
10 {
11 $this->client->request('GET', '/api/user.json');
12 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
13
14 $content = json_decode($this->client->getResponse()->getContent(), true);
15
16 $this->assertArrayHasKey('id', $content);
17 $this->assertArrayHasKey('email', $content);
18 $this->assertArrayHasKey('name', $content);
19 $this->assertArrayHasKey('username', $content);
20 $this->assertArrayHasKey('created_at', $content);
21 $this->assertArrayHasKey('updated_at', $content);
22
23 $this->assertEquals('bigboss@wallabag.org', $content['email']);
24 $this->assertEquals('Big boss', $content['name']);
25 $this->assertEquals('admin', $content['username']);
26
27 $this->assertEquals('application/json', $this->client->getResponse()->headers->get('Content-Type'));
28 }
29
30 public function testCreateNewUser()
31 {
32 $this->client->request('PUT', '/api/user.json', [
33 'username' => 'google',
34 'password' => 'googlegoogle',
35 'email' => 'wallabag@google.com',
36 ]);
37
38 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
39
40 $content = json_decode($this->client->getResponse()->getContent(), true);
41
42 $this->assertArrayHasKey('id', $content);
43 $this->assertArrayHasKey('email', $content);
44 $this->assertArrayHasKey('username', $content);
45 $this->assertArrayHasKey('created_at', $content);
46 $this->assertArrayHasKey('updated_at', $content);
47
48 $this->assertEquals('wallabag@google.com', $content['email']);
49 $this->assertEquals('google', $content['username']);
50
51 $this->assertEquals('application/json', $this->client->getResponse()->headers->get('Content-Type'));
52 }
53
54 public function testCreateNewUserWithExistingEmail()
55 {
56 $this->client->request('PUT', '/api/user.json', [
57 'username' => 'google',
58 'password' => 'googlegoogle',
59 'email' => 'bigboss@wallabag.org',
60 ]);
61
62 $this->assertEquals(400, $this->client->getResponse()->getStatusCode());
63
64 $content = json_decode($this->client->getResponse()->getContent(), true);
65
66 $this->assertArrayHasKey('error', $content);
67 $this->assertArrayHasKey('username', $content['error']);
68 $this->assertArrayHasKey('email', $content['error']);
69
70 // $this->assertEquals('fos_user.username.already_used', $content['error']['username'][0]);
71 // $this->assertEquals('fos_user.email.already_used', $content['error']['email'][0]);
72 // This shouldn't be translated ...
73 $this->assertEquals('This value is already used.', $content['error']['username'][0]);
74 $this->assertEquals('This value is already used.', $content['error']['email'][0]);
75
76 $this->assertEquals('application/json', $this->client->getResponse()->headers->get('Content-Type'));
77 }
78
79 public function testCreateNewUserWithTooShortPassword()
80 {
81 $this->client->request('PUT', '/api/user.json', [
82 'username' => 'facebook',
83 'password' => 'face',
84 'email' => 'facebook@wallabag.org',
85 ]);
86
87 $this->assertEquals(400, $this->client->getResponse()->getStatusCode());
88
89 $content = json_decode($this->client->getResponse()->getContent(), true);
90
91 $this->assertArrayHasKey('error', $content);
92 $this->assertArrayHasKey('password', $content['error']);
93
94 $this->assertEquals('validator.password_too_short', $content['error']['password'][0]);
95
96 $this->assertEquals('application/json', $this->client->getResponse()->headers->get('Content-Type'));
97 }
98}
diff --git a/tests/Wallabag/ApiBundle/WallabagApiTestCase.php b/tests/Wallabag/ApiBundle/WallabagApiTestCase.php
index cf9b3347..a67655c8 100644
--- a/tests/Wallabag/ApiBundle/WallabagApiTestCase.php
+++ b/tests/Wallabag/ApiBundle/WallabagApiTestCase.php
@@ -37,7 +37,7 @@ abstract class WallabagApiTestCase extends WebTestCase
37 $firewallName = $container->getParameter('fos_user.firewall_name'); 37 $firewallName = $container->getParameter('fos_user.firewall_name');
38 38
39 $this->user = $userManager->findUserBy(['username' => 'admin']); 39 $this->user = $userManager->findUserBy(['username' => 'admin']);
40 $loginManager->loginUser($firewallName, $this->user); 40 $loginManager->logInUser($firewallName, $this->user);
41 41
42 // save the login token into the session and put it in a cookie 42 // save the login token into the session and put it in a cookie
43 $container->get('session')->set('_security_'.$firewallName, serialize($container->get('security.token_storage')->getToken())); 43 $container->get('session')->set('_security_'.$firewallName, serialize($container->get('security.token_storage')->getToken()));