From fcb1fba5c2fdb12c9f4041bd334aaced6f302d91 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Nicolas=20L=C5=93uillet?= Date: Tue, 29 Sep 2015 14:31:52 +0200 Subject: [PATCH] * public registration * remove WSSE implementation * add oAuth2 implementation --- app/AppKernel.php | 1 + app/config/config.yml | 14 ++ app/config/config_prod.yml | 5 - app/config/routing.yml | 6 + app/config/security.yml | 21 +- app/config/services.yml | 5 - composer.json | 3 +- composer.lock | 158 ++++++++++-- .../Controller/WallabagRestController.php | 51 +--- .../Security/Factory/WsseFactory.php | 40 --- .../WallabagApiExtension.php | 3 - src/Wallabag/ApiBundle/Entity/AccessToken.php | 31 +++ src/Wallabag/ApiBundle/Entity/AuthCode.php | 31 +++ src/Wallabag/ApiBundle/Entity/Client.php | 25 ++ .../ApiBundle/Entity/RefreshToken.php | 31 +++ .../ApiBundle/Resources/config/services.yml | 12 - .../Authentication/Provider/WsseProvider.php | 79 ------ .../Authentication/Token/WsseUserToken.php | 24 -- .../Security/Firewall/WsseListener.php | 62 ----- .../Tests/AbstractControllerTest.php | 46 ++++ .../Controller/WallabagRestControllerTest.php | 238 +++++------------- src/Wallabag/ApiBundle/WallabagApiBundle.php | 9 - .../Controller/ConfigController.php | 15 +- .../DataFixtures/ORM/LoadUserData.php | 5 +- src/Wallabag/CoreBundle/Entity/User.php | 22 +- .../EventListener/AuthenticationListener.php | 44 ++++ .../CoreBundle/Form/Type/NewUserType.php | 3 +- .../CoreBundle/Form/Type/RegistrationType.php | 24 ++ .../CoreBundle/Resources/config/services.yml | 11 + .../views/themes/baggy/Config/index.html.twig | 16 +- .../themes/material/Config/index.html.twig | 19 +- .../themes/material/Security/login.html.twig | 1 + .../Tests/Controller/ConfigControllerTest.php | 24 +- 33 files changed, 551 insertions(+), 528 deletions(-) delete mode 100644 src/Wallabag/ApiBundle/DependencyInjection/Security/Factory/WsseFactory.php create mode 100644 src/Wallabag/ApiBundle/Entity/AccessToken.php create mode 100644 src/Wallabag/ApiBundle/Entity/AuthCode.php create mode 100644 src/Wallabag/ApiBundle/Entity/Client.php create mode 100644 src/Wallabag/ApiBundle/Entity/RefreshToken.php delete mode 100644 src/Wallabag/ApiBundle/Resources/config/services.yml delete mode 100644 src/Wallabag/ApiBundle/Security/Authentication/Provider/WsseProvider.php delete mode 100644 src/Wallabag/ApiBundle/Security/Authentication/Token/WsseUserToken.php delete mode 100644 src/Wallabag/ApiBundle/Security/Firewall/WsseListener.php create mode 100644 src/Wallabag/ApiBundle/Tests/AbstractControllerTest.php create mode 100644 src/Wallabag/CoreBundle/EventListener/AuthenticationListener.php create mode 100644 src/Wallabag/CoreBundle/Form/Type/RegistrationType.php diff --git a/app/AppKernel.php b/app/AppKernel.php index 08e14b8f..6f8c3a6d 100644 --- a/app/AppKernel.php +++ b/app/AppKernel.php @@ -26,6 +26,7 @@ class AppKernel extends Kernel new Wallabag\ApiBundle\WallabagApiBundle(), new Bazinga\Bundle\HateoasBundle\BazingaHateoasBundle(), new Lexik\Bundle\FormFilterBundle\LexikFormFilterBundle(), + new FOS\OAuthServerBundle\FOSOAuthServerBundle(), ); if (in_array($this->getEnvironment(), array('dev', 'test'))) { diff --git a/app/config/config.yml b/app/config/config.yml index f623ab23..adf68d6c 100644 --- a/app/config/config.yml +++ b/app/config/config.yml @@ -157,3 +157,17 @@ fos_user: db_driver: orm firewall_name: main user_class: Wallabag\CoreBundle\Entity\User + registration: + form: + type: wallabag_user_registration + confirmation: + enabled: true + +fos_oauth_server: + db_driver: orm + client_class: Wallabag\ApiBundle\Entity\Client + access_token_class: Wallabag\ApiBundle\Entity\AccessToken + refresh_token_class: Wallabag\ApiBundle\Entity\RefreshToken + auth_code_class: Wallabag\ApiBundle\Entity\AuthCode + service: + user_provider: fos_user.user_manager diff --git a/app/config/config_prod.yml b/app/config/config_prod.yml index c45f0fa6..342837a0 100644 --- a/app/config/config_prod.yml +++ b/app/config/config_prod.yml @@ -17,11 +17,6 @@ monolog: type: fingers_crossed action_level: error handler: nested - wsse: - type: stream - path: %kernel.logs_dir%/%kernel.environment%.wsse.log - level: error - channels: [wsse] nested: type: stream path: "%kernel.logs_dir%/%kernel.environment%.log" diff --git a/app/config/routing.yml b/app/config/routing.yml index e8bf08a5..dabb48fa 100644 --- a/app/config/routing.yml +++ b/app/config/routing.yml @@ -30,3 +30,9 @@ homepage: defaults: { _controller: WallabagCoreBundle:Entry:showUnread, page : 1 } requirements: page: \d+ + +fos_user: + resource: "@FOSUserBundle/Resources/config/routing/all.xml" + +fos_oauth_server_token: + resource: "@FOSOAuthServerBundle/Resources/config/routing/token.xml" diff --git a/app/config/security.yml b/app/config/security.yml index 98846656..6533a430 100644 --- a/app/config/security.yml +++ b/app/config/security.yml @@ -1,9 +1,6 @@ security: encoders: - Wallabag\CoreBundle\Entity\User: - algorithm: sha1 - encode_as_base64: false - iterations: 1 + FOS\UserBundle\Model\UserInterface: sha512 role_hierarchy: ROLE_ADMIN: ROLE_USER @@ -18,11 +15,15 @@ security: # the main part of the security, where you can set up firewalls # for specific sections of your app firewalls: - wsse_secured: - pattern: /api/.* - wsse: true - stateless: true - anonymous: true + oauth_token: + pattern: ^/oauth/v2/token + security: false + api: + pattern: /api/.* + fos_oauth: true + stateless: true + anonymous: false + login_firewall: pattern: ^/login$ anonymous: ~ @@ -45,9 +46,9 @@ security: target: / access_control: - - { path: ^/api/salt, roles: IS_AUTHENTICATED_ANONYMOUSLY } - { path: ^/api/doc, roles: IS_AUTHENTICATED_ANONYMOUSLY } - { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY } + - { path: ^/register, role: IS_AUTHENTICATED_ANONYMOUSLY } - { path: ^/forgot-password, roles: IS_AUTHENTICATED_ANONYMOUSLY } - { path: /(unread|starred|archive).xml$, roles: IS_AUTHENTICATED_ANONYMOUSLY } - { path: ^/, roles: ROLE_USER } diff --git a/app/config/services.yml b/app/config/services.yml index 965bc319..ff6a582b 100644 --- a/app/config/services.yml +++ b/app/config/services.yml @@ -1,9 +1,4 @@ -# Learn more about services, parameters and containers at -# http://symfony.com/doc/current/book/service_container.html parameters: - security.authentication.provider.dao.class: Wallabag\CoreBundle\Security\Authentication\Provider\WallabagAuthenticationProvider - security.encoder.digest.class: Wallabag\CoreBundle\Security\Authentication\Encoder\WallabagPasswordEncoder - security.validator.user_password.class: Wallabag\CoreBundle\Security\Validator\WallabagUserPasswordValidator lexik_form_filter.get_filter.doctrine_orm.class: Wallabag\CoreBundle\Event\Subscriber\CustomDoctrineORMSubscriber services: diff --git a/composer.json b/composer.json index babe9356..22cb277c 100644 --- a/composer.json +++ b/composer.json @@ -53,7 +53,8 @@ "pagerfanta/pagerfanta": "~1.0.3", "lexik/form-filter-bundle": "~4.0", "j0k3r/graby": "~1.0", - "friendsofsymfony/user-bundle": "dev-master" + "friendsofsymfony/user-bundle": "dev-master", + "friendsofsymfony/oauth-server-bundle": "^1.4@dev" }, "require-dev": { "doctrine/doctrine-fixtures-bundle": "~2.2.0", diff --git a/composer.lock b/composer.lock index 370d8ddd..606c3678 100644 --- a/composer.lock +++ b/composer.lock @@ -1,10 +1,10 @@ { "_readme": [ "This file locks the dependencies of your project to a known state", - "Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "hash": "350d05d95be50b6d93e8a046f784e00c", + "hash": "7c1f2c88df608eb6e1b4bc7c5ed24acc", "packages": [ { "name": "doctrine/annotations", @@ -858,6 +858,129 @@ ], "time": "2014-05-20 12:10:12" }, + { + "name": "friendsofsymfony/oauth-server-bundle", + "version": "1.4.2", + "target-dir": "FOS/OAuthServerBundle", + "source": { + "type": "git", + "url": "https://github.com/FriendsOfSymfony/FOSOAuthServerBundle.git", + "reference": "9e15c229eff547443d686445d629e9356ab0672e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/FriendsOfSymfony/FOSOAuthServerBundle/zipball/9e15c229eff547443d686445d629e9356ab0672e", + "reference": "9e15c229eff547443d686445d629e9356ab0672e", + "shasum": "" + }, + "require": { + "friendsofsymfony/oauth2-php": "~1.1.0", + "php": ">=5.3.3", + "symfony/framework-bundle": "~2.1", + "symfony/security-bundle": "~2.1" + }, + "require-dev": { + "doctrine/doctrine-bundle": "~1.0", + "doctrine/mongodb-odm": "1.0.*@dev", + "doctrine/orm": ">=2.2,<2.5-dev", + "symfony/class-loader": "~2.1", + "symfony/yaml": "~2.1", + "willdurand/propel-typehintable-behavior": "1.0.*" + }, + "suggest": { + "doctrine/doctrine-bundle": "*", + "doctrine/mongodb-odm-bundle": "*", + "propel/propel-bundle": "If you want to use Propel with Symfony2, then you will have to install the PropelBundle", + "willdurand/propel-typehintable-behavior": "The Typehintable behavior is useful to add type hints on generated methods, to be compliant with interfaces" + }, + "type": "symfony-bundle", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "psr-0": { + "FOS\\OAuthServerBundle": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Arnaud Le Blanc", + "email": "arnaud.lb@gmail.com" + }, + { + "name": "FriendsOfSymfony Community", + "homepage": "https://github.com/FriendsOfSymfony/FOSOAuthServerBundle/contributors" + } + ], + "description": "Symfony2 OAuth Server Bundle", + "homepage": "http://friendsofsymfony.github.com", + "keywords": [ + "oauth", + "oauth2", + "server" + ], + "time": "2014-10-31 13:44:14" + }, + { + "name": "friendsofsymfony/oauth2-php", + "version": "1.1.1", + "source": { + "type": "git", + "url": "https://github.com/FriendsOfSymfony/oauth2-php.git", + "reference": "23e76537c4a02e666ab4ba5abe67a69a886a0310" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/FriendsOfSymfony/oauth2-php/zipball/23e76537c4a02e666ab4ba5abe67a69a886a0310", + "reference": "23e76537c4a02e666ab4ba5abe67a69a886a0310", + "shasum": "" + }, + "require": { + "php": ">=5.3.2", + "symfony/http-foundation": "~2.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + }, + "autoload": { + "psr-4": { + "OAuth2\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Arnaud Le Blanc", + "email": "arnaud.lb@gmail.com" + }, + { + "name": "FriendsOfSymfony Community", + "homepage": "https://github.com/FriendsOfSymfony/oauth2-php/contributors" + } + ], + "description": "OAuth2 library", + "homepage": "https://github.com/FriendsOfSymfony/oauth2-php", + "keywords": [ + "oauth", + "oauth2" + ], + "time": "2014-11-03 10:21:20" + }, { "name": "friendsofsymfony/rest-bundle", "version": "1.7.1", @@ -2787,12 +2910,12 @@ "version": "v2.7.0", "source": { "type": "git", - "url": "https://github.com/symfony/AsseticBundle.git", + "url": "https://github.com/symfony/assetic-bundle.git", "reference": "3ae5c8ca3079b6e0033cc9fbfb6500e2bc964da5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/AsseticBundle/zipball/3ae5c8ca3079b6e0033cc9fbfb6500e2bc964da5", + "url": "https://api.github.com/repos/symfony/assetic-bundle/zipball/3ae5c8ca3079b6e0033cc9fbfb6500e2bc964da5", "reference": "3ae5c8ca3079b6e0033cc9fbfb6500e2bc964da5", "shasum": "" }, @@ -2857,12 +2980,12 @@ "version": "v2.7.1", "source": { "type": "git", - "url": "https://github.com/symfony/MonologBundle.git", + "url": "https://github.com/symfony/monolog-bundle.git", "reference": "9320b6863404c70ebe111e9040dab96f251de7ac" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/MonologBundle/zipball/9320b6863404c70ebe111e9040dab96f251de7ac", + "url": "https://api.github.com/repos/symfony/monolog-bundle/zipball/9320b6863404c70ebe111e9040dab96f251de7ac", "reference": "9320b6863404c70ebe111e9040dab96f251de7ac", "shasum": "" }, @@ -2916,12 +3039,12 @@ "version": "v2.3.8", "source": { "type": "git", - "url": "https://github.com/symfony/SwiftmailerBundle.git", + "url": "https://github.com/symfony/swiftmailer-bundle.git", "reference": "970b13d01871207e81d17b17ddda025e7e21e797" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/SwiftmailerBundle/zipball/970b13d01871207e81d17b17ddda025e7e21e797", + "url": "https://api.github.com/repos/symfony/swiftmailer-bundle/zipball/970b13d01871207e81d17b17ddda025e7e21e797", "reference": "970b13d01871207e81d17b17ddda025e7e21e797", "shasum": "" }, @@ -2970,20 +3093,20 @@ }, { "name": "symfony/symfony", - "version": "v2.7.5", + "version": "v2.7.4", "source": { "type": "git", "url": "https://github.com/symfony/symfony.git", - "reference": "619528a274647cffc1792063c3ea04c4fa8266a0" + "reference": "1fdf23fe28876844b887b0e1935c9adda43ee645" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/symfony/zipball/619528a274647cffc1792063c3ea04c4fa8266a0", - "reference": "619528a274647cffc1792063c3ea04c4fa8266a0", + "url": "https://api.github.com/repos/symfony/symfony/zipball/1fdf23fe28876844b887b0e1935c9adda43ee645", + "reference": "1fdf23fe28876844b887b0e1935c9adda43ee645", "shasum": "" }, "require": { - "doctrine/common": "~2.4", + "doctrine/common": "~2.3", "php": ">=5.3.9", "psr/log": "~1.0", "twig/twig": "~1.20|~2.0" @@ -3036,9 +3159,9 @@ }, "require-dev": { "doctrine/data-fixtures": "1.0.*", - "doctrine/dbal": "~2.4", + "doctrine/dbal": "~2.2", "doctrine/doctrine-bundle": "~1.2", - "doctrine/orm": "~2.4,>=2.4.5", + "doctrine/orm": "~2.2,>=2.2.3", "egulias/email-validator": "~1.2", "ircmaxell/password-compat": "~1.0", "monolog/monolog": "~1.11", @@ -3088,7 +3211,7 @@ "keywords": [ "framework" ], - "time": "2015-09-25 11:16:52" + "time": "2015-09-08 14:26:39" }, { "name": "tecnickcom/tcpdf", @@ -4488,7 +4611,8 @@ "aliases": [], "minimum-stability": "dev", "stability-flags": { - "friendsofsymfony/user-bundle": 20 + "friendsofsymfony/user-bundle": 20, + "friendsofsymfony/oauth-server-bundle": 20 }, "prefer-stable": true, "prefer-lowest": false, diff --git a/src/Wallabag/ApiBundle/Controller/WallabagRestController.php b/src/Wallabag/ApiBundle/Controller/WallabagRestController.php index 349229f3..284dbb25 100644 --- a/src/Wallabag/ApiBundle/Controller/WallabagRestController.php +++ b/src/Wallabag/ApiBundle/Controller/WallabagRestController.php @@ -2,8 +2,8 @@ namespace Wallabag\ApiBundle\Controller; +use FOS\RestBundle\Controller\FOSRestController; use Nelmio\ApiDocBundle\Annotation\ApiDoc; -use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Wallabag\CoreBundle\Entity\Entry; @@ -11,7 +11,7 @@ use Wallabag\CoreBundle\Entity\Tag; use Hateoas\Configuration\Route; use Hateoas\Representation\Factory\PagerfantaFactory; -class WallabagRestController extends Controller +class WallabagRestController extends FOSRestController { /** * @param Entry $entry @@ -38,31 +38,6 @@ class WallabagRestController extends Controller } } - /** - * Retrieve salt for a giver user. - * - * @ApiDoc( - * parameters={ - * {"name"="username", "dataType"="string", "required"=true, "description"="username"} - * } - * ) - * - * @return array - */ - public function getSaltAction($username) - { - $user = $this - ->getDoctrine() - ->getRepository('WallabagCoreBundle:User') - ->findOneByUsername($username); - - if (is_null($user)) { - throw $this->createNotFoundException(); - } - - return array($user->getSalt() ?: null); - } - /** * Retrieve all entries. It could be filtered by many options. * @@ -122,7 +97,7 @@ class WallabagRestController extends Controller */ public function getEntryAction(Entry $entry) { - $this->validateUserAccess($entry->getUser()->getId(), $this->getUser()->getId()); + $this->validateUserAccess($entry->getUser()->getId()); $json = $this->get('serializer')->serialize($entry, 'json'); @@ -184,7 +159,7 @@ class WallabagRestController extends Controller */ public function patchEntriesAction(Entry $entry, Request $request) { - $this->validateUserAccess($entry->getUser()->getId(), $this->getUser()->getId()); + $this->validateUserAccess($entry->getUser()->getId()); $title = $request->request->get('title'); $isArchived = $request->request->get('is_archived'); @@ -228,7 +203,7 @@ class WallabagRestController extends Controller */ public function deleteEntriesAction(Entry $entry) { - $this->validateUserAccess($entry->getUser()->getId(), $this->getUser()->getId()); + $this->validateUserAccess($entry->getUser()->getId()); $em = $this->getDoctrine()->getManager(); $em->remove($entry); @@ -250,7 +225,7 @@ class WallabagRestController extends Controller */ public function getEntriesTagsAction(Entry $entry) { - $this->validateUserAccess($entry->getUser()->getId(), $this->getUser()->getId()); + $this->validateUserAccess($entry->getUser()->getId()); $json = $this->get('serializer')->serialize($entry->getTags(), 'json'); @@ -271,7 +246,7 @@ class WallabagRestController extends Controller */ public function postEntriesTagsAction(Request $request, Entry $entry) { - $this->validateUserAccess($entry->getUser()->getId(), $this->getUser()->getId()); + $this->validateUserAccess($entry->getUser()->getId()); $tags = $request->request->get('tags', ''); if (!empty($tags)) { @@ -299,7 +274,7 @@ class WallabagRestController extends Controller */ public function deleteEntriesTagsAction(Entry $entry, Tag $tag) { - $this->validateUserAccess($entry->getUser()->getId(), $this->getUser()->getId()); + $this->validateUserAccess($entry->getUser()->getId()); $entry->removeTag($tag); $em = $this->getDoctrine()->getManager(); @@ -334,7 +309,7 @@ class WallabagRestController extends Controller */ public function deleteTagAction(Tag $tag) { - $this->validateUserAccess($tag->getUser()->getId(), $this->getUser()->getId()); + $this->validateUserAccess($tag->getUser()->getId()); $em = $this->getDoctrine()->getManager(); $em->remove($tag); @@ -350,12 +325,12 @@ class WallabagRestController extends Controller * If not, throw exception. It means a user try to access information from an other user. * * @param int $requestUserId User id from the requested source - * @param int $currentUserId User id from the retrieved source */ - private function validateUserAccess($requestUserId, $currentUserId) + private function validateUserAccess($requestUserId) { - if ($requestUserId != $currentUserId) { - throw $this->createAccessDeniedException('Access forbidden. Entry user id: '.$requestUserId.', logged user id: '.$currentUserId); + $user = $this->get('security.context')->getToken()->getUser(); + if ($requestUserId != $user->getId()) { + throw $this->createAccessDeniedException('Access forbidden. Entry user id: '.$requestUserId.', logged user id: '.$user->getId()); } } diff --git a/src/Wallabag/ApiBundle/DependencyInjection/Security/Factory/WsseFactory.php b/src/Wallabag/ApiBundle/DependencyInjection/Security/Factory/WsseFactory.php deleted file mode 100644 index 402eb869..00000000 --- a/src/Wallabag/ApiBundle/DependencyInjection/Security/Factory/WsseFactory.php +++ /dev/null @@ -1,40 +0,0 @@ -setDefinition($providerId, new DefinitionDecorator('wsse.security.authentication.provider')) - ->replaceArgument(0, new Reference($userProvider)) - ; - - $listenerId = 'security.authentication.listener.wsse.'.$id; - $listener = $container->setDefinition($listenerId, new DefinitionDecorator('wsse.security.authentication.listener')); - - return array($providerId, $listenerId, $defaultEntryPoint); - } - - public function getPosition() - { - return 'pre_auth'; - } - - public function getKey() - { - return 'wsse'; - } - - public function addConfiguration(NodeDefinition $node) - { - } -} diff --git a/src/Wallabag/ApiBundle/DependencyInjection/WallabagApiExtension.php b/src/Wallabag/ApiBundle/DependencyInjection/WallabagApiExtension.php index c5cc204e..a147e7ef 100644 --- a/src/Wallabag/ApiBundle/DependencyInjection/WallabagApiExtension.php +++ b/src/Wallabag/ApiBundle/DependencyInjection/WallabagApiExtension.php @@ -13,9 +13,6 @@ class WallabagApiExtension extends Extension { $configuration = new Configuration(); $config = $this->processConfiguration($configuration, $configs); - - $loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); - $loader->load('services.yml'); } public function getAlias() diff --git a/src/Wallabag/ApiBundle/Entity/AccessToken.php b/src/Wallabag/ApiBundle/Entity/AccessToken.php new file mode 100644 index 00000000..d6cf0af5 --- /dev/null +++ b/src/Wallabag/ApiBundle/Entity/AccessToken.php @@ -0,0 +1,31 @@ +userProvider = $userProvider; - $this->cacheDir = $cacheDir; - - // If cache directory does not exist we create it - if (!is_dir($this->cacheDir)) { - mkdir($this->cacheDir, 0777, true); - } - } - - public function authenticate(TokenInterface $token) - { - $user = $this->userProvider->loadUserByUsername($token->getUsername()); - - if (!$user) { - throw new AuthenticationException('Bad credentials. Did you forgot your username?'); - } - - if ($user && $this->validateDigest($token->digest, $token->nonce, $token->created, $user->getPassword())) { - $authenticatedToken = new WsseUserToken($user->getRoles()); - $authenticatedToken->setUser($user); - - return $authenticatedToken; - } - - throw new AuthenticationException('The WSSE authentication failed.'); - } - - protected function validateDigest($digest, $nonce, $created, $secret) - { - // Check created time is not in the future - if (strtotime($created) > time()) { - throw new AuthenticationException('Back to the future...'); - } - - // Expire timestamp after 5 minutes - if (time() - strtotime($created) > 300) { - throw new AuthenticationException('Too late for this timestamp... Watch your watch.'); - } - - // Validate nonce is unique within 5 minutes - if (file_exists($this->cacheDir.'/'.$nonce) && file_get_contents($this->cacheDir.'/'.$nonce) + 300 > time()) { - throw new NonceExpiredException('Previously used nonce detected'); - } - - file_put_contents($this->cacheDir.'/'.$nonce, time()); - - // Validate Secret - $expected = base64_encode(sha1(base64_decode($nonce).$created.$secret, true)); - - if ($digest !== $expected) { - throw new AuthenticationException('Bad credentials ! Digest is not as expected.'); - } - - return $digest === $expected; - } - - public function supports(TokenInterface $token) - { - return $token instanceof WsseUserToken; - } -} diff --git a/src/Wallabag/ApiBundle/Security/Authentication/Token/WsseUserToken.php b/src/Wallabag/ApiBundle/Security/Authentication/Token/WsseUserToken.php deleted file mode 100644 index e6d30224..00000000 --- a/src/Wallabag/ApiBundle/Security/Authentication/Token/WsseUserToken.php +++ /dev/null @@ -1,24 +0,0 @@ -setAuthenticated(count($roles) > 0); - } - - public function getCredentials() - { - return ''; - } -} diff --git a/src/Wallabag/ApiBundle/Security/Firewall/WsseListener.php b/src/Wallabag/ApiBundle/Security/Firewall/WsseListener.php deleted file mode 100644 index 2fcbe014..00000000 --- a/src/Wallabag/ApiBundle/Security/Firewall/WsseListener.php +++ /dev/null @@ -1,62 +0,0 @@ -securityContext = $securityContext; - $this->authenticationManager = $authenticationManager; - $this->logger = $logger; - } - - public function handle(GetResponseEvent $event) - { - $request = $event->getRequest(); - - $wsseRegex = '/UsernameToken Username="([^"]+)", PasswordDigest="([^"]+)", Nonce="([^"]+)", Created="([^"]+)"/'; - if (!$request->headers->has('x-wsse') || 1 !== preg_match($wsseRegex, $request->headers->get('x-wsse'), $matches)) { - return; - } - - $token = new WsseUserToken(); - $token->setUser($matches[1]); - - $token->digest = $matches[2]; - $token->nonce = $matches[3]; - $token->created = $matches[4]; - - try { - $authToken = $this->authenticationManager->authenticate($token); - - $this->securityContext->setToken($authToken); - - return; - } catch (AuthenticationException $failed) { - $failedMessage = 'WSSE Login failed for '.$token->getUsername().'. Why ? '.$failed->getMessage(); - $this->logger->err($failedMessage); - - // Deny authentication with a '403 Forbidden' HTTP response - $response = new Response(); - $response->setStatusCode(403); - $response->setContent($failedMessage); - $event->setResponse($response); - - return; - } - } -} diff --git a/src/Wallabag/ApiBundle/Tests/AbstractControllerTest.php b/src/Wallabag/ApiBundle/Tests/AbstractControllerTest.php new file mode 100644 index 00000000..119889b3 --- /dev/null +++ b/src/Wallabag/ApiBundle/Tests/AbstractControllerTest.php @@ -0,0 +1,46 @@ +client = $this->createAuthorizedClient(); + } + + /** + * @return Client + */ + protected function createAuthorizedClient() + { + $client = static::createClient(); + $container = $client->getContainer(); + + $session = $container->get('session'); + /** @var $userManager \FOS\UserBundle\Doctrine\UserManager */ + $userManager = $container->get('fos_user.user_manager'); + /** @var $loginManager \FOS\UserBundle\Security\LoginManager */ + $loginManager = $container->get('fos_user.security.login_manager'); + $firewallName = $container->getParameter('fos_user.firewall_name'); + + $user = $userManager->findUserBy(array('username' => 'admin')); + $loginManager->loginUser($firewallName, $user); + + // save the login token into the session and put it in a cookie + $container->get('session')->set('_security_'.$firewallName, + serialize($container->get('security.context')->getToken())); + $container->get('session')->save(); + $client->getCookieJar()->set(new Cookie($session->getName(), $session->getId())); + + return $client; + } +} diff --git a/src/Wallabag/ApiBundle/Tests/Controller/WallabagRestControllerTest.php b/src/Wallabag/ApiBundle/Tests/Controller/WallabagRestControllerTest.php index 7ae54b57..bc7ef489 100644 --- a/src/Wallabag/ApiBundle/Tests/Controller/WallabagRestControllerTest.php +++ b/src/Wallabag/ApiBundle/Tests/Controller/WallabagRestControllerTest.php @@ -2,99 +2,15 @@ namespace Wallabag\ApiBundle\Tests\Controller; -use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; +use Wallabag\ApiBundle\Tests\AbstractControllerTest; -class WallabagRestControllerTest extends WebTestCase +class WallabagRestControllerTest extends AbstractControllerTest { protected static $salt; - /** - * Grab the salt once and store it to be available for all tests. - */ - public static function setUpBeforeClass() - { - $client = self::createClient(); - - $user = $client->getContainer() - ->get('doctrine.orm.entity_manager') - ->getRepository('WallabagCoreBundle:User') - ->findOneByUsername('admin'); - - self::$salt = $user->getSalt(); - } - - /** - * Generate HTTP headers for authenticate user on API. - * - * @param string $username - * @param string $password - * - * @return array - */ - private function generateHeaders($username, $password) - { - $encryptedPassword = sha1($password.$username.self::$salt); - $nonce = substr(md5(uniqid('nonce_', true)), 0, 16); - - $now = new \DateTime('now', new \DateTimeZone('UTC')); - $created = (string) $now->format('Y-m-d\TH:i:s\Z'); - $digest = base64_encode(sha1(base64_decode($nonce).$created.$encryptedPassword, true)); - - return array( - 'HTTP_AUTHORIZATION' => 'Authorization profile="UsernameToken"', - 'HTTP_x-wsse' => 'X-WSSE: UsernameToken Username="'.$username.'", PasswordDigest="'.$digest.'", Nonce="'.$nonce.'", Created="'.$created.'"', - ); - } - - public function testGetSalt() - { - $client = $this->createClient(); - $client->request('GET', '/api/salts/admin.json'); - - $user = $client->getContainer() - ->get('doctrine.orm.entity_manager') - ->getRepository('WallabagCoreBundle:User') - ->findOneByUsername('admin'); - - $this->assertEquals(200, $client->getResponse()->getStatusCode()); - - $content = json_decode($client->getResponse()->getContent(), true); - - $this->assertArrayHasKey(0, $content); - $this->assertEquals($user->getSalt(), $content[0]); - - $client->request('GET', '/api/salts/notfound.json'); - $this->assertEquals(404, $client->getResponse()->getStatusCode()); - } - - public function testWithBadHeaders() - { - $client = $this->createClient(); - - $entry = $client->getContainer() - ->get('doctrine.orm.entity_manager') - ->getRepository('WallabagCoreBundle:Entry') - ->findOneByIsArchived(false); - - if (!$entry) { - $this->markTestSkipped('No content found in db.'); - } - - $badHeaders = array( - 'HTTP_AUTHORIZATION' => 'Authorization profile="UsernameToken"', - 'HTTP_x-wsse' => 'X-WSSE: UsernameToken Username="admin", PasswordDigest="Wr0ngDig3st", Nonce="n0Nc3", Created="2015-01-01T13:37:00Z"', - ); - - $client->request('GET', '/api/entries/'.$entry->getId().'.json', array(), array(), $badHeaders); - $this->assertEquals(403, $client->getResponse()->getStatusCode()); - } - public function testGetOneEntry() { - $client = $this->createClient(); - $headers = $this->generateHeaders('admin', 'mypassword'); - - $entry = $client->getContainer() + $entry = $this->client->getContainer() ->get('doctrine.orm.entity_manager') ->getRepository('WallabagCoreBundle:Entry') ->findOneBy(array('user' => 1, 'isArchived' => false)); @@ -103,18 +19,17 @@ class WallabagRestControllerTest extends WebTestCase $this->markTestSkipped('No content found in db.'); } - $client->request('GET', '/api/entries/'.$entry->getId().'.json', array(), array(), $headers); + $this->client->request('GET', '/api/entries/'.$entry->getId().'.json'); + $this->assertEquals(200, $this->client->getResponse()->getStatusCode()); - $this->assertEquals(200, $client->getResponse()->getStatusCode()); - - $content = json_decode($client->getResponse()->getContent(), true); + $content = json_decode($this->client->getResponse()->getContent(), true); $this->assertEquals($entry->getTitle(), $content['title']); $this->assertEquals($entry->getUrl(), $content['url']); $this->assertCount(count($entry->getTags()), $content['tags']); $this->assertTrue( - $client->getResponse()->headers->contains( + $this->client->getResponse()->headers->contains( 'Content-Type', 'application/json' ) @@ -123,10 +38,7 @@ class WallabagRestControllerTest extends WebTestCase public function testGetOneEntryWrongUser() { - $client = $this->createClient(); - $headers = $this->generateHeaders('admin', 'mypassword'); - - $entry = $client->getContainer() + $entry = $this->client->getContainer() ->get('doctrine.orm.entity_manager') ->getRepository('WallabagCoreBundle:Entry') ->findOneBy(array('user' => 2, 'isArchived' => false)); @@ -135,21 +47,18 @@ class WallabagRestControllerTest extends WebTestCase $this->markTestSkipped('No content found in db.'); } - $client->request('GET', '/api/entries/'.$entry->getId().'.json', array(), array(), $headers); + $this->client->request('GET', '/api/entries/'.$entry->getId().'.json'); - $this->assertEquals(403, $client->getResponse()->getStatusCode()); + $this->assertEquals(403, $this->client->getResponse()->getStatusCode()); } public function testGetEntries() { - $client = $this->createClient(); - $headers = $this->generateHeaders('admin', 'mypassword'); - - $client->request('GET', '/api/entries', array(), array(), $headers); + $this->client->request('GET', '/api/entries'); - $this->assertEquals(200, $client->getResponse()->getStatusCode()); + $this->assertEquals(200, $this->client->getResponse()->getStatusCode()); - $content = json_decode($client->getResponse()->getContent(), true); + $content = json_decode($this->client->getResponse()->getContent(), true); $this->assertGreaterThanOrEqual(1, count($content)); $this->assertNotEmpty($content['_embedded']['items']); @@ -158,7 +67,7 @@ class WallabagRestControllerTest extends WebTestCase $this->assertGreaterThanOrEqual(1, $content['pages']); $this->assertTrue( - $client->getResponse()->headers->contains( + $this->client->getResponse()->headers->contains( 'Content-Type', 'application/json' ) @@ -167,14 +76,11 @@ class WallabagRestControllerTest extends WebTestCase public function testGetStarredEntries() { - $client = $this->createClient(); - $headers = $this->generateHeaders('admin', 'mypassword'); + $this->client->request('GET', '/api/entries', array('star' => 1, 'sort' => 'updated')); - $client->request('GET', '/api/entries', array('star' => 1, 'sort' => 'updated'), array(), $headers); + $this->assertEquals(200, $this->client->getResponse()->getStatusCode()); - $this->assertEquals(200, $client->getResponse()->getStatusCode()); - - $content = json_decode($client->getResponse()->getContent(), true); + $content = json_decode($this->client->getResponse()->getContent(), true); $this->assertGreaterThanOrEqual(1, count($content)); $this->assertNotEmpty($content['_embedded']['items']); @@ -183,7 +89,7 @@ class WallabagRestControllerTest extends WebTestCase $this->assertGreaterThanOrEqual(1, $content['pages']); $this->assertTrue( - $client->getResponse()->headers->contains( + $this->client->getResponse()->headers->contains( 'Content-Type', 'application/json' ) @@ -192,14 +98,11 @@ class WallabagRestControllerTest extends WebTestCase public function testGetArchiveEntries() { - $client = $this->createClient(); - $headers = $this->generateHeaders('admin', 'mypassword'); - - $client->request('GET', '/api/entries', array('archive' => 1), array(), $headers); + $this->client->request('GET', '/api/entries', array('archive' => 1)); - $this->assertEquals(200, $client->getResponse()->getStatusCode()); + $this->assertEquals(200, $this->client->getResponse()->getStatusCode()); - $content = json_decode($client->getResponse()->getContent(), true); + $content = json_decode($this->client->getResponse()->getContent(), true); $this->assertGreaterThanOrEqual(1, count($content)); $this->assertNotEmpty($content['_embedded']['items']); @@ -208,7 +111,7 @@ class WallabagRestControllerTest extends WebTestCase $this->assertGreaterThanOrEqual(1, $content['pages']); $this->assertTrue( - $client->getResponse()->headers->contains( + $this->client->getResponse()->headers->contains( 'Content-Type', 'application/json' ) @@ -217,10 +120,7 @@ class WallabagRestControllerTest extends WebTestCase public function testDeleteEntry() { - $client = $this->createClient(); - $headers = $this->generateHeaders('admin', 'mypassword'); - - $entry = $client->getContainer() + $entry = $this->client->getContainer() ->get('doctrine.orm.entity_manager') ->getRepository('WallabagCoreBundle:Entry') ->findOneByUser(1); @@ -229,36 +129,31 @@ class WallabagRestControllerTest extends WebTestCase $this->markTestSkipped('No content found in db.'); } - $client->request('DELETE', '/api/entries/'.$entry->getId().'.json', array(), array(), $headers); + $this->client->request('DELETE', '/api/entries/'.$entry->getId().'.json'); - $this->assertEquals(200, $client->getResponse()->getStatusCode()); + $this->assertEquals(200, $this->client->getResponse()->getStatusCode()); - $content = json_decode($client->getResponse()->getContent(), true); + $content = json_decode($this->client->getResponse()->getContent(), true); $this->assertEquals($entry->getTitle(), $content['title']); $this->assertEquals($entry->getUrl(), $content['url']); // We'll try to delete this entry again - $headers = $this->generateHeaders('admin', 'mypassword'); - - $client->request('DELETE', '/api/entries/'.$entry->getId().'.json', array(), array(), $headers); + $this->client->request('DELETE', '/api/entries/'.$entry->getId().'.json'); - $this->assertEquals(404, $client->getResponse()->getStatusCode()); + $this->assertEquals(404, $this->client->getResponse()->getStatusCode()); } public function testPostEntry() { - $client = $this->createClient(); - $headers = $this->generateHeaders('admin', 'mypassword'); - - $client->request('POST', '/api/entries.json', array( + $this->client->request('POST', '/api/entries.json', array( 'url' => 'http://www.lemonde.fr/pixels/article/2015/03/28/plongee-dans-l-univers-d-ingress-le-jeu-de-google-aux-frontieres-du-reel_4601155_4408996.html', 'tags' => 'google', - ), array(), $headers); + )); - $this->assertEquals(200, $client->getResponse()->getStatusCode()); + $this->assertEquals(200, $this->client->getResponse()->getStatusCode()); - $content = json_decode($client->getResponse()->getContent(), true); + $content = json_decode($this->client->getResponse()->getContent(), true); $this->assertGreaterThan(0, $content['id']); $this->assertEquals('http://www.lemonde.fr/pixels/article/2015/03/28/plongee-dans-l-univers-d-ingress-le-jeu-de-google-aux-frontieres-du-reel_4601155_4408996.html', $content['url']); @@ -269,10 +164,7 @@ class WallabagRestControllerTest extends WebTestCase public function testPatchEntry() { - $client = $this->createClient(); - $headers = $this->generateHeaders('admin', 'mypassword'); - - $entry = $client->getContainer() + $entry = $this->client->getContainer() ->get('doctrine.orm.entity_manager') ->getRepository('WallabagCoreBundle:Entry') ->findOneByUser(1); @@ -284,16 +176,16 @@ class WallabagRestControllerTest extends WebTestCase // hydrate the tags relations $nbTags = count($entry->getTags()); - $client->request('PATCH', '/api/entries/'.$entry->getId().'.json', array( + $this->client->request('PATCH', '/api/entries/'.$entry->getId().'.json', array( 'title' => 'New awesome title', 'tags' => 'new tag '.uniqid(), 'star' => true, 'archive' => false, - ), array(), $headers); + )); - $this->assertEquals(200, $client->getResponse()->getStatusCode()); + $this->assertEquals(200, $this->client->getResponse()->getStatusCode()); - $content = json_decode($client->getResponse()->getContent(), true); + $content = json_decode($this->client->getResponse()->getContent(), true); $this->assertEquals($entry->getId(), $content['id']); $this->assertEquals($entry->getUrl(), $content['url']); @@ -303,10 +195,7 @@ class WallabagRestControllerTest extends WebTestCase public function testGetTagsEntry() { - $client = $this->createClient(); - $headers = $this->generateHeaders('admin', 'mypassword'); - - $entry = $client->getContainer() + $entry = $this->client->getContainer() ->get('doctrine.orm.entity_manager') ->getRepository('WallabagCoreBundle:Entry') ->findOneWithTags(1); @@ -322,17 +211,14 @@ class WallabagRestControllerTest extends WebTestCase $tags[] = array('id' => $tag->getId(), 'label' => $tag->getLabel()); } - $client->request('GET', '/api/entries/'.$entry->getId().'/tags', array(), array(), $headers); + $this->client->request('GET', '/api/entries/'.$entry->getId().'/tags'); - $this->assertEquals(json_encode($tags, JSON_HEX_QUOT), $client->getResponse()->getContent()); + $this->assertEquals(json_encode($tags, JSON_HEX_QUOT), $this->client->getResponse()->getContent()); } public function testPostTagsOnEntry() { - $client = $this->createClient(); - $headers = $this->generateHeaders('admin', 'mypassword'); - - $entry = $client->getContainer() + $entry = $this->client->getContainer() ->get('doctrine.orm.entity_manager') ->getRepository('WallabagCoreBundle:Entry') ->findOneByUser(1); @@ -345,16 +231,16 @@ class WallabagRestControllerTest extends WebTestCase $newTags = 'tag1,tag2,tag3'; - $client->request('POST', '/api/entries/'.$entry->getId().'/tags', array('tags' => $newTags), array(), $headers); + $this->client->request('POST', '/api/entries/'.$entry->getId().'/tags', array('tags' => $newTags)); - $this->assertEquals(200, $client->getResponse()->getStatusCode()); + $this->assertEquals(200, $this->client->getResponse()->getStatusCode()); - $content = json_decode($client->getResponse()->getContent(), true); + $content = json_decode($this->client->getResponse()->getContent(), true); $this->assertArrayHasKey('tags', $content); $this->assertEquals($nbTags + 3, count($content['tags'])); - $entryDB = $client->getContainer() + $entryDB = $this->client->getContainer() ->get('doctrine.orm.entity_manager') ->getRepository('WallabagCoreBundle:Entry') ->find($entry->getId()); @@ -369,15 +255,13 @@ class WallabagRestControllerTest extends WebTestCase } } - public function testDeleteOneTagEntrie() + public function testDeleteOneTagEntry() { - $client = $this->createClient(); - $headers = $this->generateHeaders('admin', 'mypassword'); - - $entry = $client->getContainer() + $entry = $this->client->getContainer() ->get('doctrine.orm.entity_manager') ->getRepository('WallabagCoreBundle:Entry') - ->findOneByUser(1); + ->findOneWithTags(1); + $entry = $entry[0]; if (!$entry) { $this->markTestSkipped('No content found in db.'); @@ -387,11 +271,11 @@ class WallabagRestControllerTest extends WebTestCase $nbTags = count($entry->getTags()); $tag = $entry->getTags()[0]; - $client->request('DELETE', '/api/entries/'.$entry->getId().'/tags/'.$tag->getId().'.json', array(), array(), $headers); + $this->client->request('DELETE', '/api/entries/'.$entry->getId().'/tags/'.$tag->getId().'.json'); - $this->assertEquals(200, $client->getResponse()->getStatusCode()); + $this->assertEquals(200, $this->client->getResponse()->getStatusCode()); - $content = json_decode($client->getResponse()->getContent(), true); + $content = json_decode($this->client->getResponse()->getContent(), true); $this->assertArrayHasKey('tags', $content); $this->assertEquals($nbTags - 1, count($content['tags'])); @@ -399,14 +283,11 @@ class WallabagRestControllerTest extends WebTestCase public function testGetUserTags() { - $client = $this->createClient(); - $headers = $this->generateHeaders('admin', 'mypassword'); - - $client->request('GET', '/api/tags.json', array(), array(), $headers); + $this->client->request('GET', '/api/tags.json'); - $this->assertEquals(200, $client->getResponse()->getStatusCode()); + $this->assertEquals(200, $this->client->getResponse()->getStatusCode()); - $content = json_decode($client->getResponse()->getContent(), true); + $content = json_decode($this->client->getResponse()->getContent(), true); $this->assertGreaterThan(0, $content); $this->assertArrayHasKey('id', $content[0]); @@ -420,14 +301,11 @@ class WallabagRestControllerTest extends WebTestCase */ public function testDeleteUserTag($tag) { - $client = $this->createClient(); - $headers = $this->generateHeaders('admin', 'mypassword'); - - $client->request('DELETE', '/api/tags/'.$tag['id'].'.json', array(), array(), $headers); + $this->client->request('DELETE', '/api/tags/'.$tag['id'].'.json'); - $this->assertEquals(200, $client->getResponse()->getStatusCode()); + $this->assertEquals(200, $this->client->getResponse()->getStatusCode()); - $content = json_decode($client->getResponse()->getContent(), true); + $content = json_decode($this->client->getResponse()->getContent(), true); $this->assertArrayHasKey('label', $content); $this->assertEquals($tag['label'], $content['label']); diff --git a/src/Wallabag/ApiBundle/WallabagApiBundle.php b/src/Wallabag/ApiBundle/WallabagApiBundle.php index 2484f277..19d887ab 100644 --- a/src/Wallabag/ApiBundle/WallabagApiBundle.php +++ b/src/Wallabag/ApiBundle/WallabagApiBundle.php @@ -3,16 +3,7 @@ namespace Wallabag\ApiBundle; use Symfony\Component\HttpKernel\Bundle\Bundle; -use Wallabag\ApiBundle\DependencyInjection\Security\Factory\WsseFactory; -use Symfony\Component\DependencyInjection\ContainerBuilder; class WallabagApiBundle extends Bundle { - public function build(ContainerBuilder $container) - { - parent::build($container); - - $extension = $container->getExtension('security'); - $extension->addSecurityListenerFactory(new WsseFactory()); - } } diff --git a/src/Wallabag/CoreBundle/Controller/ConfigController.php b/src/Wallabag/CoreBundle/Controller/ConfigController.php index 5affdee8..27c323b7 100644 --- a/src/Wallabag/CoreBundle/Controller/ConfigController.php +++ b/src/Wallabag/CoreBundle/Controller/ConfigController.php @@ -25,6 +25,7 @@ class ConfigController extends Controller { $em = $this->getDoctrine()->getManager(); $config = $this->getConfig(); + $userManager = $this->container->get('fos_user.user_manager'); $user = $this->getUser(); // handle basic config detail (this form is defined as a service) @@ -52,9 +53,8 @@ class ConfigController extends Controller $pwdForm->handleRequest($request); if ($pwdForm->isValid()) { - $user->setPassword($pwdForm->get('new_password')->getData()); - $em->persist($user); - $em->flush(); + $user->setPlainPassword($pwdForm->get('new_password')->getData()); + $userManager->updateUser($user, true); $this->get('session')->getFlashBag()->add( 'notice', @@ -69,8 +69,7 @@ class ConfigController extends Controller $userForm->handleRequest($request); if ($userForm->isValid()) { - $em->persist($user); - $em->flush(); + $userManager->updateUser($user, true); $this->get('session')->getFlashBag()->add( 'notice', @@ -97,14 +96,14 @@ class ConfigController extends Controller } // handle adding new user - $newUser = new User(); + $newUser = $userManager->createUser(); // enable created user by default $newUser->setEnabled(true); $newUserForm = $this->createForm(new NewUserType(), $newUser, array('validation_groups' => array('Profile'))); $newUserForm->handleRequest($request); - if ($newUserForm->isValid()) { - $em->persist($newUser); + if ($newUserForm->isValid() && $this->get('security.authorization_checker')->isGranted('ROLE_SUPER_ADMIN')) { + $userManager->updateUser($newUser, true); $config = new Config($newUser); $config->setTheme($this->container->getParameter('theme')); diff --git a/src/Wallabag/CoreBundle/DataFixtures/ORM/LoadUserData.php b/src/Wallabag/CoreBundle/DataFixtures/ORM/LoadUserData.php index 4ef53329..811451da 100644 --- a/src/Wallabag/CoreBundle/DataFixtures/ORM/LoadUserData.php +++ b/src/Wallabag/CoreBundle/DataFixtures/ORM/LoadUserData.php @@ -18,8 +18,9 @@ class LoadUserData extends AbstractFixture implements OrderedFixtureInterface $userAdmin->setName('Big boss'); $userAdmin->setEmail('bigboss@wallabag.org'); $userAdmin->setUsername('admin'); - $userAdmin->setPassword('mypassword'); + $userAdmin->setPlainPassword('mypassword'); $userAdmin->setEnabled(true); + $userAdmin->addRole('ROLE_SUPER_ADMIN'); $manager->persist($userAdmin); @@ -29,7 +30,7 @@ class LoadUserData extends AbstractFixture implements OrderedFixtureInterface $bobUser->setName('Bobby'); $bobUser->setEmail('bobby@wallabag.org'); $bobUser->setUsername('bob'); - $bobUser->setPassword('mypassword'); + $bobUser->setPlainPassword('mypassword'); $bobUser->setEnabled(true); $manager->persist($bobUser); diff --git a/src/Wallabag/CoreBundle/Entity/User.php b/src/Wallabag/CoreBundle/Entity/User.php index a6002352..ae2902a3 100644 --- a/src/Wallabag/CoreBundle/Entity/User.php +++ b/src/Wallabag/CoreBundle/Entity/User.php @@ -6,7 +6,6 @@ use Doctrine\Common\Collections\ArrayCollection; use Doctrine\ORM\Mapping as ORM; use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; use Symfony\Component\Security\Core\User\UserInterface; -use Symfony\Component\Security\Core\User\AdvancedUserInterface; use JMS\Serializer\Annotation\ExclusionPolicy; use JMS\Serializer\Annotation\Expose; use FOS\UserBundle\Model\User as BaseUser; @@ -22,7 +21,7 @@ use FOS\UserBundle\Model\User as BaseUser; * @UniqueEntity("email") * @UniqueEntity("username") */ -class User extends BaseUser implements AdvancedUserInterface, \Serializable +class User extends BaseUser { /** * @var int @@ -75,6 +74,7 @@ class User extends BaseUser implements AdvancedUserInterface, \Serializable parent::__construct(); $this->entries = new ArrayCollection(); $this->tags = new ArrayCollection(); + $this->roles = array('ROLE_USER'); } /** @@ -90,24 +90,6 @@ class User extends BaseUser implements AdvancedUserInterface, \Serializable $this->updatedAt = new \DateTime(); } - /** - * Set password. - * - * @param string $password - * - * @return User - */ - public function setPassword($password) - { - if (!$password && 0 === strlen($password)) { - return; - } - - $this->password = sha1($password.$this->getUsername().$this->getSalt()); - - return $this; - } - /** * Set name. * diff --git a/src/Wallabag/CoreBundle/EventListener/AuthenticationListener.php b/src/Wallabag/CoreBundle/EventListener/AuthenticationListener.php new file mode 100644 index 00000000..7c2826ec --- /dev/null +++ b/src/Wallabag/CoreBundle/EventListener/AuthenticationListener.php @@ -0,0 +1,44 @@ +container = $container; + $this->em = $em; + } + + public static function getSubscribedEvents() + { + return array( + FOSUserEvents::REGISTRATION_CONFIRMED => 'authenticate', + ); + } + + public function authenticate(FilterUserResponseEvent $event, $eventName = null, EventDispatcherInterface $eventDispatcher = null) + { + if (!$event->getUser()->isEnabled()) { + return; + } + + $config = new Config($event->getUser()); + $config->setTheme($this->container->getParameter('theme')); + $config->setItemsPerPage($this->container->getParameter('items_on_page')); + $config->setRssLimit($this->container->getParameter('rss_limit')); + $config->setLanguage($this->container->getParameter('language')); + $this->em->persist($config); + $this->em->flush(); + } +} diff --git a/src/Wallabag/CoreBundle/Form/Type/NewUserType.php b/src/Wallabag/CoreBundle/Form/Type/NewUserType.php index 985cb55b..ea7bb7ae 100644 --- a/src/Wallabag/CoreBundle/Form/Type/NewUserType.php +++ b/src/Wallabag/CoreBundle/Form/Type/NewUserType.php @@ -13,7 +13,8 @@ class NewUserType extends AbstractType { $builder ->add('username', 'text', array('required' => true)) - ->add('password', 'password', array( + ->add('plainPassword', 'repeated', array( + 'type' => 'password', 'constraints' => array( new Constraints\Length(array( 'min' => 8, diff --git a/src/Wallabag/CoreBundle/Form/Type/RegistrationType.php b/src/Wallabag/CoreBundle/Form/Type/RegistrationType.php new file mode 100644 index 00000000..47d4f341 --- /dev/null +++ b/src/Wallabag/CoreBundle/Form/Type/RegistrationType.php @@ -0,0 +1,24 @@ +add('name'); + } + + public function getParent() + { + return 'fos_user_registration'; + } + + public function getName() + { + return 'wallabag_user_registration'; + } +} diff --git a/src/Wallabag/CoreBundle/Resources/config/services.yml b/src/Wallabag/CoreBundle/Resources/config/services.yml index 3beb5d0e..96ea482a 100644 --- a/src/Wallabag/CoreBundle/Resources/config/services.yml +++ b/src/Wallabag/CoreBundle/Resources/config/services.yml @@ -13,6 +13,11 @@ services: tags: - { name: form.type, alias: config } + wallabag_core.form.registration: + class: Wallabag\CoreBundle\Form\Type\RegistrationType + tags: + - { name: form.type, alias: wallabag_user_registration } + wallabag_core.form.type.forgot_password: class: Wallabag\CoreBundle\Form\Type\ForgotPasswordType arguments: @@ -40,3 +45,9 @@ services: class: Wallabag\CoreBundle\Helper\ContentProxy arguments: - @wallabag_core.graby + + wallabag_core.registration_confirmed: + class: Wallabag\CoreBundle\EventListener\AuthenticationListener + arguments: [@service_container, @doctrine.orm.entity_manager] + tags: + - { name: kernel.event_subscriber } diff --git a/src/Wallabag/CoreBundle/Resources/views/themes/baggy/Config/index.html.twig b/src/Wallabag/CoreBundle/Resources/views/themes/baggy/Config/index.html.twig index c90bb2e3..64305b16 100644 --- a/src/Wallabag/CoreBundle/Resources/views/themes/baggy/Config/index.html.twig +++ b/src/Wallabag/CoreBundle/Resources/views/themes/baggy/Config/index.html.twig @@ -135,6 +135,7 @@ {{ form_rest(form.pwd) }} + {% if is_granted('ROLE_SUPER_ADMIN') %}

{% trans %}Add a user{% endtrans %}

@@ -150,9 +151,17 @@
- {{ form_label(form.new_user.password) }} - {{ form_errors(form.new_user.password) }} - {{ form_widget(form.new_user.password) }} + {{ form_label(form.new_user.plainPassword.first) }} + {{ form_errors(form.new_user.plainPassword.first) }} + {{ form_widget(form.new_user.plainPassword.first) }} +
+
+ +
+
+ {{ form_label(form.new_user.plainPassword.second) }} + {{ form_errors(form.new_user.plainPassword.second) }} + {{ form_widget(form.new_user.plainPassword.second) }}
@@ -165,5 +174,6 @@ {{ form_rest(form.new_user) }} + {% endif %}
{% endblock %} diff --git a/src/Wallabag/CoreBundle/Resources/views/themes/material/Config/index.html.twig b/src/Wallabag/CoreBundle/Resources/views/themes/material/Config/index.html.twig index 0ff21f22..0d8e9f24 100644 --- a/src/Wallabag/CoreBundle/Resources/views/themes/material/Config/index.html.twig +++ b/src/Wallabag/CoreBundle/Resources/views/themes/material/Config/index.html.twig @@ -15,7 +15,9 @@
  • {% trans %}RSS{% endtrans %}
  • {% trans %}User information{% endtrans %}
  • {% trans %}Password{% endtrans %}
  • + {% if is_granted('ROLE_SUPER_ADMIN') %}
  • {% trans %}Add a user{% endtrans %}
  • + {% endif %} @@ -175,7 +177,7 @@ - + {% if is_granted('ROLE_SUPER_ADMIN') %}
    {{ form_errors(form.new_user) }} @@ -190,9 +192,17 @@
    - {{ form_label(form.new_user.password) }} - {{ form_errors(form.new_user.password) }} - {{ form_widget(form.new_user.password) }} + {{ form_label(form.new_user.plainPassword.first) }} + {{ form_errors(form.new_user.plainPassword.first) }} + {{ form_widget(form.new_user.plainPassword.first) }} +
    +
    + +
    +
    + {{ form_label(form.new_user.plainPassword.second) }} + {{ form_errors(form.new_user.plainPassword.second) }} + {{ form_widget(form.new_user.plainPassword.second) }}
    @@ -211,6 +221,7 @@
    + {% endif %} diff --git a/src/Wallabag/CoreBundle/Resources/views/themes/material/Security/login.html.twig b/src/Wallabag/CoreBundle/Resources/views/themes/material/Security/login.html.twig index 4eb6d2b8..10f380fe 100644 --- a/src/Wallabag/CoreBundle/Resources/views/themes/material/Security/login.html.twig +++ b/src/Wallabag/CoreBundle/Resources/views/themes/material/Security/login.html.twig @@ -49,6 +49,7 @@ {% trans %}Login{% endtrans %} + {% trans %}Register{% endtrans %} diff --git a/src/Wallabag/CoreBundle/Tests/Controller/ConfigControllerTest.php b/src/Wallabag/CoreBundle/Tests/Controller/ConfigControllerTest.php index 3407fc5e..708a07b1 100644 --- a/src/Wallabag/CoreBundle/Tests/Controller/ConfigControllerTest.php +++ b/src/Wallabag/CoreBundle/Tests/Controller/ConfigControllerTest.php @@ -258,7 +258,8 @@ class ConfigControllerTest extends WallabagCoreTestCase array( array( 'new_user[username]' => '', - 'new_user[password]' => '', + 'new_user[plainPassword][first]' => '', + 'new_user[plainPassword][second]' => '', 'new_user[email]' => '', ), 'Please enter a username', @@ -266,7 +267,8 @@ class ConfigControllerTest extends WallabagCoreTestCase array( array( 'new_user[username]' => 'a', - 'new_user[password]' => 'mypassword', + 'new_user[plainPassword][first]' => 'mypassword', + 'new_user[plainPassword][second]' => 'mypassword', 'new_user[email]' => '', ), 'The username is too short', @@ -274,7 +276,8 @@ class ConfigControllerTest extends WallabagCoreTestCase array( array( 'new_user[username]' => 'wallace', - 'new_user[password]' => 'mypassword', + 'new_user[plainPassword][first]' => 'mypassword', + 'new_user[plainPassword][second]' => 'mypassword', 'new_user[email]' => 'test', ), 'The email is not valid', @@ -282,11 +285,21 @@ class ConfigControllerTest extends WallabagCoreTestCase array( array( 'new_user[username]' => 'admin', - 'new_user[password]' => 'wallacewallace', + 'new_user[plainPassword][first]' => 'wallacewallace', + 'new_user[plainPassword][second]' => 'wallacewallace', 'new_user[email]' => 'wallace@wallace.me', ), 'The username is already used', ), + array( + array( + 'new_user[username]' => 'wallace', + 'new_user[plainPassword][first]' => 'mypassword1', + 'new_user[plainPassword][second]' => 'mypassword2', + 'new_user[email]' => 'wallace@wallace.me', + ), + 'This value is not valid', + ), ); } @@ -325,7 +338,8 @@ class ConfigControllerTest extends WallabagCoreTestCase $data = array( 'new_user[username]' => 'wallace', - 'new_user[password]' => 'wallace1', + 'new_user[plainPassword][first]' => 'wallace1', + 'new_user[plainPassword][second]' => 'wallace1', 'new_user[email]' => 'wallace@wallace.me', ); -- 2.41.0