diff options
author | Jérémy Benoist <j0k3r@users.noreply.github.com> | 2017-05-30 15:26:11 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-05-30 15:26:11 +0200 |
commit | 2150576d86709968faec3ed7b8cdc576c0200ae2 (patch) | |
tree | 105724e36303bd75755b19b8bc385bea800cae1c | |
parent | d181bd728565454ec53d960f321ed0a4c3bf26c8 (diff) | |
parent | fe6461e4aaff5aa2fd846492e3abd9ea38c07a5b (diff) | |
download | wallabag-2150576d86709968faec3ed7b8cdc576c0200ae2.tar.gz wallabag-2150576d86709968faec3ed7b8cdc576c0200ae2.tar.zst wallabag-2150576d86709968faec3ed7b8cdc576c0200ae2.zip |
Merge pull request #3065 from wallabag/api-creation-endpoint
Register through API
7 files changed, 284 insertions, 10 deletions
diff --git a/src/Wallabag/ApiBundle/Controller/UserRestController.php b/src/Wallabag/ApiBundle/Controller/UserRestController.php new file mode 100644 index 00000000..a1b78e3f --- /dev/null +++ b/src/Wallabag/ApiBundle/Controller/UserRestController.php | |||
@@ -0,0 +1,139 @@ | |||
1 | <?php | ||
2 | |||
3 | namespace Wallabag\ApiBundle\Controller; | ||
4 | |||
5 | use FOS\UserBundle\Event\UserEvent; | ||
6 | use FOS\UserBundle\FOSUserEvents; | ||
7 | use JMS\Serializer\SerializationContext; | ||
8 | use Nelmio\ApiDocBundle\Annotation\ApiDoc; | ||
9 | use Symfony\Component\HttpFoundation\Request; | ||
10 | use Symfony\Component\HttpFoundation\JsonResponse; | ||
11 | use Wallabag\UserBundle\Entity\User; | ||
12 | |||
13 | class UserRestController extends WallabagRestController | ||
14 | { | ||
15 | /** | ||
16 | * Retrieve current logged in user informations. | ||
17 | * | ||
18 | * @ApiDoc() | ||
19 | * | ||
20 | * @return JsonResponse | ||
21 | */ | ||
22 | public function getUserAction() | ||
23 | { | ||
24 | $this->validateAuthentication(); | ||
25 | |||
26 | return $this->sendUser($this->getUser()); | ||
27 | } | ||
28 | |||
29 | /** | ||
30 | * Register an user. | ||
31 | * | ||
32 | * @ApiDoc( | ||
33 | * requirements={ | ||
34 | * {"name"="username", "dataType"="string", "required"=true, "description"="The user's username"}, | ||
35 | * {"name"="password", "dataType"="string", "required"=true, "description"="The user's password"}, | ||
36 | * {"name"="email", "dataType"="string", "required"=true, "description"="The user's email"} | ||
37 | * } | ||
38 | * ) | ||
39 | * | ||
40 | * @todo Make this method (or the whole API) accessible only through https | ||
41 | * | ||
42 | * @return JsonResponse | ||
43 | */ | ||
44 | public function putUserAction(Request $request) | ||
45 | { | ||
46 | if (!$this->container->getParameter('fosuser_registration')) { | ||
47 | $json = $this->get('serializer')->serialize(['error' => "Server doesn't allow registrations"], 'json'); | ||
48 | |||
49 | return (new JsonResponse())->setJson($json)->setStatusCode(403); | ||
50 | } | ||
51 | |||
52 | $userManager = $this->get('fos_user.user_manager'); | ||
53 | $user = $userManager->createUser(); | ||
54 | // enable created user by default | ||
55 | $user->setEnabled(true); | ||
56 | |||
57 | $form = $this->createForm('Wallabag\UserBundle\Form\NewUserType', $user, [ | ||
58 | 'csrf_protection' => false, | ||
59 | ]); | ||
60 | |||
61 | // simulate form submission | ||
62 | $form->submit([ | ||
63 | 'username' => $request->request->get('username'), | ||
64 | 'plainPassword' => [ | ||
65 | 'first' => $request->request->get('password'), | ||
66 | 'second' => $request->request->get('password'), | ||
67 | ], | ||
68 | 'email' => $request->request->get('email'), | ||
69 | ]); | ||
70 | |||
71 | if ($form->isSubmitted() && false === $form->isValid()) { | ||
72 | $view = $this->view($form, 400); | ||
73 | $view->setFormat('json'); | ||
74 | |||
75 | // handle errors in a more beautiful way than the default view | ||
76 | $data = json_decode($this->handleView($view)->getContent(), true)['children']; | ||
77 | $errors = []; | ||
78 | |||
79 | if (isset($data['username']['errors'])) { | ||
80 | $errors['username'] = $this->translateErrors($data['username']['errors']); | ||
81 | } | ||
82 | |||
83 | if (isset($data['email']['errors'])) { | ||
84 | $errors['email'] = $this->translateErrors($data['email']['errors']); | ||
85 | } | ||
86 | |||
87 | if (isset($data['plainPassword']['children']['first']['errors'])) { | ||
88 | $errors['password'] = $this->translateErrors($data['plainPassword']['children']['first']['errors']); | ||
89 | } | ||
90 | |||
91 | $json = $this->get('serializer')->serialize(['error' => $errors], 'json'); | ||
92 | |||
93 | return (new JsonResponse())->setJson($json)->setStatusCode(400); | ||
94 | } | ||
95 | |||
96 | $userManager->updateUser($user); | ||
97 | |||
98 | // dispatch a created event so the associated config will be created | ||
99 | $event = new UserEvent($user, $request); | ||
100 | $this->get('event_dispatcher')->dispatch(FOSUserEvents::USER_CREATED, $event); | ||
101 | |||
102 | return $this->sendUser($user); | ||
103 | } | ||
104 | |||
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 | ); | ||
119 | |||
120 | return (new JsonResponse())->setJson($json); | ||
121 | } | ||
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 | } | ||
139 | } | ||
diff --git a/src/Wallabag/ApiBundle/Resources/config/routing_rest.yml b/src/Wallabag/ApiBundle/Resources/config/routing_rest.yml index 57d37f4b..c0283e71 100644 --- a/src/Wallabag/ApiBundle/Resources/config/routing_rest.yml +++ b/src/Wallabag/ApiBundle/Resources/config/routing_rest.yml | |||
@@ -17,3 +17,8 @@ misc: | |||
17 | type: rest | 17 | type: rest |
18 | resource: "WallabagApiBundle:WallabagRest" | 18 | resource: "WallabagApiBundle:WallabagRest" |
19 | name_prefix: api_ | 19 | name_prefix: api_ |
20 | |||
21 | user: | ||
22 | type: rest | ||
23 | resource: "WallabagApiBundle:UserRest" | ||
24 | name_prefix: api_ | ||
diff --git a/src/Wallabag/UserBundle/Controller/ManageController.php b/src/Wallabag/UserBundle/Controller/ManageController.php index 1c5c86d4..084f2c67 100644 --- a/src/Wallabag/UserBundle/Controller/ManageController.php +++ b/src/Wallabag/UserBundle/Controller/ManageController.php | |||
@@ -33,9 +33,7 @@ class ManageController extends Controller | |||
33 | // enable created user by default | 33 | // enable created user by default |
34 | $user->setEnabled(true); | 34 | $user->setEnabled(true); |
35 | 35 | ||
36 | $form = $this->createForm('Wallabag\UserBundle\Form\NewUserType', $user, [ | 36 | $form = $this->createForm('Wallabag\UserBundle\Form\NewUserType', $user); |
37 | 'validation_groups' => ['Profile'], | ||
38 | ]); | ||
39 | $form->handleRequest($request); | 37 | $form->handleRequest($request); |
40 | 38 | ||
41 | if ($form->isSubmitted() && $form->isValid()) { | 39 | if ($form->isSubmitted() && $form->isValid()) { |
diff --git a/src/Wallabag/UserBundle/Entity/User.php b/src/Wallabag/UserBundle/Entity/User.php index 3a167de7..1ff3046a 100644 --- a/src/Wallabag/UserBundle/Entity/User.php +++ b/src/Wallabag/UserBundle/Entity/User.php | |||
@@ -4,11 +4,11 @@ namespace Wallabag\UserBundle\Entity; | |||
4 | 4 | ||
5 | use Doctrine\Common\Collections\ArrayCollection; | 5 | use Doctrine\Common\Collections\ArrayCollection; |
6 | use Doctrine\ORM\Mapping as ORM; | 6 | use Doctrine\ORM\Mapping as ORM; |
7 | use JMS\Serializer\Annotation\Groups; | ||
8 | use JMS\Serializer\Annotation\XmlRoot; | ||
7 | use Scheb\TwoFactorBundle\Model\Email\TwoFactorInterface; | 9 | use Scheb\TwoFactorBundle\Model\Email\TwoFactorInterface; |
8 | use Scheb\TwoFactorBundle\Model\TrustedComputerInterface; | 10 | use Scheb\TwoFactorBundle\Model\TrustedComputerInterface; |
9 | use FOS\UserBundle\Model\User as BaseUser; | 11 | use FOS\UserBundle\Model\User as BaseUser; |
10 | use JMS\Serializer\Annotation\ExclusionPolicy; | ||
11 | use JMS\Serializer\Annotation\Expose; | ||
12 | use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; | 12 | use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; |
13 | use Symfony\Component\Security\Core\User\UserInterface; | 13 | use Symfony\Component\Security\Core\User\UserInterface; |
14 | use Wallabag\ApiBundle\Entity\Client; | 14 | use Wallabag\ApiBundle\Entity\Client; |
@@ -18,23 +18,25 @@ use Wallabag\CoreBundle\Entity\Entry; | |||
18 | /** | 18 | /** |
19 | * User. | 19 | * User. |
20 | * | 20 | * |
21 | * @XmlRoot("user") | ||
21 | * @ORM\Entity(repositoryClass="Wallabag\UserBundle\Repository\UserRepository") | 22 | * @ORM\Entity(repositoryClass="Wallabag\UserBundle\Repository\UserRepository") |
22 | * @ORM\Table(name="`user`") | 23 | * @ORM\Table(name="`user`") |
23 | * @ORM\HasLifecycleCallbacks() | 24 | * @ORM\HasLifecycleCallbacks() |
24 | * @ExclusionPolicy("all") | ||
25 | * | 25 | * |
26 | * @UniqueEntity("email") | 26 | * @UniqueEntity("email") |
27 | * @UniqueEntity("username") | 27 | * @UniqueEntity("username") |
28 | */ | 28 | */ |
29 | class User extends BaseUser implements TwoFactorInterface, TrustedComputerInterface | 29 | class User extends BaseUser implements TwoFactorInterface, TrustedComputerInterface |
30 | { | 30 | { |
31 | /** @Serializer\XmlAttribute */ | ||
31 | /** | 32 | /** |
32 | * @var int | 33 | * @var int |
33 | * | 34 | * |
34 | * @Expose | ||
35 | * @ORM\Column(name="id", type="integer") | 35 | * @ORM\Column(name="id", type="integer") |
36 | * @ORM\Id | 36 | * @ORM\Id |
37 | * @ORM\GeneratedValue(strategy="AUTO") | 37 | * @ORM\GeneratedValue(strategy="AUTO") |
38 | * | ||
39 | * @Groups({"user_api"}) | ||
38 | */ | 40 | */ |
39 | protected $id; | 41 | protected $id; |
40 | 42 | ||
@@ -42,13 +44,31 @@ class User extends BaseUser implements TwoFactorInterface, TrustedComputerInterf | |||
42 | * @var string | 44 | * @var string |
43 | * | 45 | * |
44 | * @ORM\Column(name="name", type="text", nullable=true) | 46 | * @ORM\Column(name="name", type="text", nullable=true) |
47 | * | ||
48 | * @Groups({"user_api"}) | ||
45 | */ | 49 | */ |
46 | protected $name; | 50 | protected $name; |
47 | 51 | ||
48 | /** | 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 | /** | ||
49 | * @var date | 67 | * @var date |
50 | * | 68 | * |
51 | * @ORM\Column(name="created_at", type="datetime") | 69 | * @ORM\Column(name="created_at", type="datetime") |
70 | * | ||
71 | * @Groups({"user_api"}) | ||
52 | */ | 72 | */ |
53 | protected $createdAt; | 73 | protected $createdAt; |
54 | 74 | ||
@@ -56,6 +76,8 @@ class User extends BaseUser implements TwoFactorInterface, TrustedComputerInterf | |||
56 | * @var date | 76 | * @var date |
57 | * | 77 | * |
58 | * @ORM\Column(name="updated_at", type="datetime") | 78 | * @ORM\Column(name="updated_at", type="datetime") |
79 | * | ||
80 | * @Groups({"user_api"}) | ||
59 | */ | 81 | */ |
60 | protected $updatedAt; | 82 | protected $updatedAt; |
61 | 83 | ||
diff --git a/tests/Wallabag/ApiBundle/Controller/UserRestControllerTest.php b/tests/Wallabag/ApiBundle/Controller/UserRestControllerTest.php new file mode 100644 index 00000000..3f4969a5 --- /dev/null +++ b/tests/Wallabag/ApiBundle/Controller/UserRestControllerTest.php | |||
@@ -0,0 +1,110 @@ | |||
1 | <?php | ||
2 | |||
3 | namespace Tests\Wallabag\ApiBundle\Controller; | ||
4 | |||
5 | use Tests\Wallabag\ApiBundle\WallabagApiTestCase; | ||
6 | |||
7 | class 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 | // remove the created user to avoid side effect on other tests | ||
54 | // @todo remove these lines when test will be isolated | ||
55 | $em = $this->client->getContainer()->get('doctrine.orm.entity_manager'); | ||
56 | |||
57 | $query = $em->createQuery('DELETE FROM Wallabag\CoreBundle\Entity\Config c WHERE c.user = :user_id'); | ||
58 | $query->setParameter('user_id', $content['id']); | ||
59 | $query->execute(); | ||
60 | |||
61 | $query = $em->createQuery('DELETE FROM Wallabag\UserBundle\Entity\User u WHERE u.id = :id'); | ||
62 | $query->setParameter('id', $content['id']); | ||
63 | $query->execute(); | ||
64 | } | ||
65 | |||
66 | public function testCreateNewUserWithExistingEmail() | ||
67 | { | ||
68 | $this->client->request('PUT', '/api/user.json', [ | ||
69 | 'username' => 'admin', | ||
70 | 'password' => 'googlegoogle', | ||
71 | 'email' => 'bigboss@wallabag.org', | ||
72 | ]); | ||
73 | |||
74 | $this->assertEquals(400, $this->client->getResponse()->getStatusCode()); | ||
75 | |||
76 | $content = json_decode($this->client->getResponse()->getContent(), true); | ||
77 | |||
78 | $this->assertArrayHasKey('error', $content); | ||
79 | $this->assertArrayHasKey('username', $content['error']); | ||
80 | $this->assertArrayHasKey('email', $content['error']); | ||
81 | |||
82 | // $this->assertEquals('fos_user.username.already_used', $content['error']['username'][0]); | ||
83 | // $this->assertEquals('fos_user.email.already_used', $content['error']['email'][0]); | ||
84 | // This shouldn't be translated ... | ||
85 | $this->assertEquals('This value is already used.', $content['error']['username'][0]); | ||
86 | $this->assertEquals('This value is already used.', $content['error']['email'][0]); | ||
87 | |||
88 | $this->assertEquals('application/json', $this->client->getResponse()->headers->get('Content-Type')); | ||
89 | } | ||
90 | |||
91 | public function testCreateNewUserWithTooShortPassword() | ||
92 | { | ||
93 | $this->client->request('PUT', '/api/user.json', [ | ||
94 | 'username' => 'facebook', | ||
95 | 'password' => 'face', | ||
96 | 'email' => 'facebook@wallabag.org', | ||
97 | ]); | ||
98 | |||
99 | $this->assertEquals(400, $this->client->getResponse()->getStatusCode()); | ||
100 | |||
101 | $content = json_decode($this->client->getResponse()->getContent(), true); | ||
102 | |||
103 | $this->assertArrayHasKey('error', $content); | ||
104 | $this->assertArrayHasKey('password', $content['error']); | ||
105 | |||
106 | $this->assertEquals('validator.password_too_short', $content['error']['password'][0]); | ||
107 | |||
108 | $this->assertEquals('application/json', $this->client->getResponse()->headers->get('Content-Type')); | ||
109 | } | ||
110 | } | ||
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())); |
diff --git a/tests/Wallabag/UserBundle/Controller/ManageControllerTest.php b/tests/Wallabag/UserBundle/Controller/ManageControllerTest.php index 44b9a030..b46256a6 100644 --- a/tests/Wallabag/UserBundle/Controller/ManageControllerTest.php +++ b/tests/Wallabag/UserBundle/Controller/ManageControllerTest.php | |||
@@ -30,8 +30,8 @@ class ManageControllerTest extends WallabagCoreTestCase | |||
30 | $form = $crawler->selectButton('user.form.save')->form(array( | 30 | $form = $crawler->selectButton('user.form.save')->form(array( |
31 | 'new_user[username]' => 'test_user', | 31 | 'new_user[username]' => 'test_user', |
32 | 'new_user[email]' => 'test@test.io', | 32 | 'new_user[email]' => 'test@test.io', |
33 | 'new_user[plainPassword][first]' => 'test', | 33 | 'new_user[plainPassword][first]' => 'testtest', |
34 | 'new_user[plainPassword][second]' => 'test', | 34 | 'new_user[plainPassword][second]' => 'testtest', |
35 | )); | 35 | )); |
36 | 36 | ||
37 | $client->submit($form); | 37 | $client->submit($form); |