aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorNicolas LÅ“uillet <nicolas@loeuillet.org>2015-09-29 14:31:52 +0200
committerJeremy Benoist <jeremy.benoist@gmail.com>2015-10-03 13:30:43 +0200
commitfcb1fba5c2fdb12c9f4041bd334aaced6f302d91 (patch)
tree0f388190a3648127c06dd3b4b9b198d2505bb7a8
parent8a60bc4cc2b6b1cfb5d8beb7ddcafc51d89a64c9 (diff)
downloadwallabag-fcb1fba5c2fdb12c9f4041bd334aaced6f302d91.tar.gz
wallabag-fcb1fba5c2fdb12c9f4041bd334aaced6f302d91.tar.zst
wallabag-fcb1fba5c2fdb12c9f4041bd334aaced6f302d91.zip
* public registration
* remove WSSE implementation * add oAuth2 implementation
-rw-r--r--app/AppKernel.php1
-rw-r--r--app/config/config.yml14
-rw-r--r--app/config/config_prod.yml5
-rw-r--r--app/config/routing.yml6
-rw-r--r--app/config/security.yml21
-rw-r--r--app/config/services.yml5
-rw-r--r--composer.json3
-rw-r--r--composer.lock158
-rw-r--r--src/Wallabag/ApiBundle/Controller/WallabagRestController.php51
-rw-r--r--src/Wallabag/ApiBundle/DependencyInjection/Security/Factory/WsseFactory.php40
-rw-r--r--src/Wallabag/ApiBundle/DependencyInjection/WallabagApiExtension.php3
-rw-r--r--src/Wallabag/ApiBundle/Entity/AccessToken.php31
-rw-r--r--src/Wallabag/ApiBundle/Entity/AuthCode.php31
-rw-r--r--src/Wallabag/ApiBundle/Entity/Client.php25
-rw-r--r--src/Wallabag/ApiBundle/Entity/RefreshToken.php31
-rw-r--r--src/Wallabag/ApiBundle/Resources/config/services.yml12
-rw-r--r--src/Wallabag/ApiBundle/Security/Authentication/Provider/WsseProvider.php79
-rw-r--r--src/Wallabag/ApiBundle/Security/Authentication/Token/WsseUserToken.php24
-rw-r--r--src/Wallabag/ApiBundle/Security/Firewall/WsseListener.php62
-rw-r--r--src/Wallabag/ApiBundle/Tests/AbstractControllerTest.php46
-rw-r--r--src/Wallabag/ApiBundle/Tests/Controller/WallabagRestControllerTest.php238
-rw-r--r--src/Wallabag/ApiBundle/WallabagApiBundle.php9
-rw-r--r--src/Wallabag/CoreBundle/Controller/ConfigController.php15
-rw-r--r--src/Wallabag/CoreBundle/DataFixtures/ORM/LoadUserData.php5
-rw-r--r--src/Wallabag/CoreBundle/Entity/User.php22
-rw-r--r--src/Wallabag/CoreBundle/EventListener/AuthenticationListener.php44
-rw-r--r--src/Wallabag/CoreBundle/Form/Type/NewUserType.php3
-rw-r--r--src/Wallabag/CoreBundle/Form/Type/RegistrationType.php24
-rw-r--r--src/Wallabag/CoreBundle/Resources/config/services.yml11
-rw-r--r--src/Wallabag/CoreBundle/Resources/views/themes/baggy/Config/index.html.twig16
-rw-r--r--src/Wallabag/CoreBundle/Resources/views/themes/material/Config/index.html.twig19
-rw-r--r--src/Wallabag/CoreBundle/Resources/views/themes/material/Security/login.html.twig1
-rw-r--r--src/Wallabag/CoreBundle/Tests/Controller/ConfigControllerTest.php24
33 files changed, 551 insertions, 528 deletions
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
26 new Wallabag\ApiBundle\WallabagApiBundle(), 26 new Wallabag\ApiBundle\WallabagApiBundle(),
27 new Bazinga\Bundle\HateoasBundle\BazingaHateoasBundle(), 27 new Bazinga\Bundle\HateoasBundle\BazingaHateoasBundle(),
28 new Lexik\Bundle\FormFilterBundle\LexikFormFilterBundle(), 28 new Lexik\Bundle\FormFilterBundle\LexikFormFilterBundle(),
29 new FOS\OAuthServerBundle\FOSOAuthServerBundle(),
29 ); 30 );
30 31
31 if (in_array($this->getEnvironment(), array('dev', 'test'))) { 32 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:
157 db_driver: orm 157 db_driver: orm
158 firewall_name: main 158 firewall_name: main
159 user_class: Wallabag\CoreBundle\Entity\User 159 user_class: Wallabag\CoreBundle\Entity\User
160 registration:
161 form:
162 type: wallabag_user_registration
163 confirmation:
164 enabled: true
165
166fos_oauth_server:
167 db_driver: orm
168 client_class: Wallabag\ApiBundle\Entity\Client
169 access_token_class: Wallabag\ApiBundle\Entity\AccessToken
170 refresh_token_class: Wallabag\ApiBundle\Entity\RefreshToken
171 auth_code_class: Wallabag\ApiBundle\Entity\AuthCode
172 service:
173 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:
17 type: fingers_crossed 17 type: fingers_crossed
18 action_level: error 18 action_level: error
19 handler: nested 19 handler: nested
20 wsse:
21 type: stream
22 path: %kernel.logs_dir%/%kernel.environment%.wsse.log
23 level: error
24 channels: [wsse]
25 nested: 20 nested:
26 type: stream 21 type: stream
27 path: "%kernel.logs_dir%/%kernel.environment%.log" 22 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:
30 defaults: { _controller: WallabagCoreBundle:Entry:showUnread, page : 1 } 30 defaults: { _controller: WallabagCoreBundle:Entry:showUnread, page : 1 }
31 requirements: 31 requirements:
32 page: \d+ 32 page: \d+
33
34fos_user:
35 resource: "@FOSUserBundle/Resources/config/routing/all.xml"
36
37fos_oauth_server_token:
38 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 @@
1security: 1security:
2 encoders: 2 encoders:
3 Wallabag\CoreBundle\Entity\User: 3 FOS\UserBundle\Model\UserInterface: sha512
4 algorithm: sha1
5 encode_as_base64: false
6 iterations: 1
7 4
8 role_hierarchy: 5 role_hierarchy:
9 ROLE_ADMIN: ROLE_USER 6 ROLE_ADMIN: ROLE_USER
@@ -18,11 +15,15 @@ security:
18 # the main part of the security, where you can set up firewalls 15 # the main part of the security, where you can set up firewalls
19 # for specific sections of your app 16 # for specific sections of your app
20 firewalls: 17 firewalls:
21 wsse_secured: 18 oauth_token:
22 pattern: /api/.* 19 pattern: ^/oauth/v2/token
23 wsse: true 20 security: false
24 stateless: true 21 api:
25 anonymous: true 22 pattern: /api/.*
23 fos_oauth: true
24 stateless: true
25 anonymous: false
26
26 login_firewall: 27 login_firewall:
27 pattern: ^/login$ 28 pattern: ^/login$
28 anonymous: ~ 29 anonymous: ~
@@ -45,9 +46,9 @@ security:
45 target: / 46 target: /
46 47
47 access_control: 48 access_control:
48 - { path: ^/api/salt, roles: IS_AUTHENTICATED_ANONYMOUSLY }
49 - { path: ^/api/doc, roles: IS_AUTHENTICATED_ANONYMOUSLY } 49 - { path: ^/api/doc, roles: IS_AUTHENTICATED_ANONYMOUSLY }
50 - { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY } 50 - { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
51 - { path: ^/register, role: IS_AUTHENTICATED_ANONYMOUSLY }
51 - { path: ^/forgot-password, roles: IS_AUTHENTICATED_ANONYMOUSLY } 52 - { path: ^/forgot-password, roles: IS_AUTHENTICATED_ANONYMOUSLY }
52 - { path: /(unread|starred|archive).xml$, roles: IS_AUTHENTICATED_ANONYMOUSLY } 53 - { path: /(unread|starred|archive).xml$, roles: IS_AUTHENTICATED_ANONYMOUSLY }
53 - { path: ^/, roles: ROLE_USER } 54 - { 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 @@
1# Learn more about services, parameters and containers at
2# http://symfony.com/doc/current/book/service_container.html
3parameters: 1parameters:
4 security.authentication.provider.dao.class: Wallabag\CoreBundle\Security\Authentication\Provider\WallabagAuthenticationProvider
5 security.encoder.digest.class: Wallabag\CoreBundle\Security\Authentication\Encoder\WallabagPasswordEncoder
6 security.validator.user_password.class: Wallabag\CoreBundle\Security\Validator\WallabagUserPasswordValidator
7 lexik_form_filter.get_filter.doctrine_orm.class: Wallabag\CoreBundle\Event\Subscriber\CustomDoctrineORMSubscriber 2 lexik_form_filter.get_filter.doctrine_orm.class: Wallabag\CoreBundle\Event\Subscriber\CustomDoctrineORMSubscriber
8 3
9services: 4services:
diff --git a/composer.json b/composer.json
index babe9356..22cb277c 100644
--- a/composer.json
+++ b/composer.json
@@ -53,7 +53,8 @@
53 "pagerfanta/pagerfanta": "~1.0.3", 53 "pagerfanta/pagerfanta": "~1.0.3",
54 "lexik/form-filter-bundle": "~4.0", 54 "lexik/form-filter-bundle": "~4.0",
55 "j0k3r/graby": "~1.0", 55 "j0k3r/graby": "~1.0",
56 "friendsofsymfony/user-bundle": "dev-master" 56 "friendsofsymfony/user-bundle": "dev-master",
57 "friendsofsymfony/oauth-server-bundle": "^1.4@dev"
57 }, 58 },
58 "require-dev": { 59 "require-dev": {
59 "doctrine/doctrine-fixtures-bundle": "~2.2.0", 60 "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 @@
1{ 1{
2 "_readme": [ 2 "_readme": [
3 "This file locks the dependencies of your project to a known state", 3 "This file locks the dependencies of your project to a known state",
4 "Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", 4 "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
5 "This file is @generated automatically" 5 "This file is @generated automatically"
6 ], 6 ],
7 "hash": "350d05d95be50b6d93e8a046f784e00c", 7 "hash": "7c1f2c88df608eb6e1b4bc7c5ed24acc",
8 "packages": [ 8 "packages": [
9 { 9 {
10 "name": "doctrine/annotations", 10 "name": "doctrine/annotations",
@@ -859,6 +859,129 @@
859 "time": "2014-05-20 12:10:12" 859 "time": "2014-05-20 12:10:12"
860 }, 860 },
861 { 861 {
862 "name": "friendsofsymfony/oauth-server-bundle",
863 "version": "1.4.2",
864 "target-dir": "FOS/OAuthServerBundle",
865 "source": {
866 "type": "git",
867 "url": "https://github.com/FriendsOfSymfony/FOSOAuthServerBundle.git",
868 "reference": "9e15c229eff547443d686445d629e9356ab0672e"
869 },
870 "dist": {
871 "type": "zip",
872 "url": "https://api.github.com/repos/FriendsOfSymfony/FOSOAuthServerBundle/zipball/9e15c229eff547443d686445d629e9356ab0672e",
873 "reference": "9e15c229eff547443d686445d629e9356ab0672e",
874 "shasum": ""
875 },
876 "require": {
877 "friendsofsymfony/oauth2-php": "~1.1.0",
878 "php": ">=5.3.3",
879 "symfony/framework-bundle": "~2.1",
880 "symfony/security-bundle": "~2.1"
881 },
882 "require-dev": {
883 "doctrine/doctrine-bundle": "~1.0",
884 "doctrine/mongodb-odm": "1.0.*@dev",
885 "doctrine/orm": ">=2.2,<2.5-dev",
886 "symfony/class-loader": "~2.1",
887 "symfony/yaml": "~2.1",
888 "willdurand/propel-typehintable-behavior": "1.0.*"
889 },
890 "suggest": {
891 "doctrine/doctrine-bundle": "*",
892 "doctrine/mongodb-odm-bundle": "*",
893 "propel/propel-bundle": "If you want to use Propel with Symfony2, then you will have to install the PropelBundle",
894 "willdurand/propel-typehintable-behavior": "The Typehintable behavior is useful to add type hints on generated methods, to be compliant with interfaces"
895 },
896 "type": "symfony-bundle",
897 "extra": {
898 "branch-alias": {
899 "dev-master": "1.4-dev"
900 }
901 },
902 "autoload": {
903 "psr-0": {
904 "FOS\\OAuthServerBundle": ""
905 }
906 },
907 "notification-url": "https://packagist.org/downloads/",
908 "license": [
909 "MIT"
910 ],
911 "authors": [
912 {
913 "name": "Arnaud Le Blanc",
914 "email": "arnaud.lb@gmail.com"
915 },
916 {
917 "name": "FriendsOfSymfony Community",
918 "homepage": "https://github.com/FriendsOfSymfony/FOSOAuthServerBundle/contributors"
919 }
920 ],
921 "description": "Symfony2 OAuth Server Bundle",
922 "homepage": "http://friendsofsymfony.github.com",
923 "keywords": [
924 "oauth",
925 "oauth2",
926 "server"
927 ],
928 "time": "2014-10-31 13:44:14"
929 },
930 {
931 "name": "friendsofsymfony/oauth2-php",
932 "version": "1.1.1",
933 "source": {
934 "type": "git",
935 "url": "https://github.com/FriendsOfSymfony/oauth2-php.git",
936 "reference": "23e76537c4a02e666ab4ba5abe67a69a886a0310"
937 },
938 "dist": {
939 "type": "zip",
940 "url": "https://api.github.com/repos/FriendsOfSymfony/oauth2-php/zipball/23e76537c4a02e666ab4ba5abe67a69a886a0310",
941 "reference": "23e76537c4a02e666ab4ba5abe67a69a886a0310",
942 "shasum": ""
943 },
944 "require": {
945 "php": ">=5.3.2",
946 "symfony/http-foundation": "~2.0"
947 },
948 "require-dev": {
949 "phpunit/phpunit": "~4.0"
950 },
951 "type": "library",
952 "extra": {
953 "branch-alias": {
954 "dev-master": "1.1.x-dev"
955 }
956 },
957 "autoload": {
958 "psr-4": {
959 "OAuth2\\": "lib/"
960 }
961 },
962 "notification-url": "https://packagist.org/downloads/",
963 "license": [
964 "MIT"
965 ],
966 "authors": [
967 {
968 "name": "Arnaud Le Blanc",
969 "email": "arnaud.lb@gmail.com"
970 },
971 {
972 "name": "FriendsOfSymfony Community",
973 "homepage": "https://github.com/FriendsOfSymfony/oauth2-php/contributors"
974 }
975 ],
976 "description": "OAuth2 library",
977 "homepage": "https://github.com/FriendsOfSymfony/oauth2-php",
978 "keywords": [
979 "oauth",
980 "oauth2"
981 ],
982 "time": "2014-11-03 10:21:20"
983 },
984 {
862 "name": "friendsofsymfony/rest-bundle", 985 "name": "friendsofsymfony/rest-bundle",
863 "version": "1.7.1", 986 "version": "1.7.1",
864 "target-dir": "FOS/RestBundle", 987 "target-dir": "FOS/RestBundle",
@@ -2787,12 +2910,12 @@
2787 "version": "v2.7.0", 2910 "version": "v2.7.0",
2788 "source": { 2911 "source": {
2789 "type": "git", 2912 "type": "git",
2790 "url": "https://github.com/symfony/AsseticBundle.git", 2913 "url": "https://github.com/symfony/assetic-bundle.git",
2791 "reference": "3ae5c8ca3079b6e0033cc9fbfb6500e2bc964da5" 2914 "reference": "3ae5c8ca3079b6e0033cc9fbfb6500e2bc964da5"
2792 }, 2915 },
2793 "dist": { 2916 "dist": {
2794 "type": "zip", 2917 "type": "zip",
2795 "url": "https://api.github.com/repos/symfony/AsseticBundle/zipball/3ae5c8ca3079b6e0033cc9fbfb6500e2bc964da5", 2918 "url": "https://api.github.com/repos/symfony/assetic-bundle/zipball/3ae5c8ca3079b6e0033cc9fbfb6500e2bc964da5",
2796 "reference": "3ae5c8ca3079b6e0033cc9fbfb6500e2bc964da5", 2919 "reference": "3ae5c8ca3079b6e0033cc9fbfb6500e2bc964da5",
2797 "shasum": "" 2920 "shasum": ""
2798 }, 2921 },
@@ -2857,12 +2980,12 @@
2857 "version": "v2.7.1", 2980 "version": "v2.7.1",
2858 "source": { 2981 "source": {
2859 "type": "git", 2982 "type": "git",
2860 "url": "https://github.com/symfony/MonologBundle.git", 2983 "url": "https://github.com/symfony/monolog-bundle.git",
2861 "reference": "9320b6863404c70ebe111e9040dab96f251de7ac" 2984 "reference": "9320b6863404c70ebe111e9040dab96f251de7ac"
2862 }, 2985 },
2863 "dist": { 2986 "dist": {
2864 "type": "zip", 2987 "type": "zip",
2865 "url": "https://api.github.com/repos/symfony/MonologBundle/zipball/9320b6863404c70ebe111e9040dab96f251de7ac", 2988 "url": "https://api.github.com/repos/symfony/monolog-bundle/zipball/9320b6863404c70ebe111e9040dab96f251de7ac",
2866 "reference": "9320b6863404c70ebe111e9040dab96f251de7ac", 2989 "reference": "9320b6863404c70ebe111e9040dab96f251de7ac",
2867 "shasum": "" 2990 "shasum": ""
2868 }, 2991 },
@@ -2916,12 +3039,12 @@
2916 "version": "v2.3.8", 3039 "version": "v2.3.8",
2917 "source": { 3040 "source": {
2918 "type": "git", 3041 "type": "git",
2919 "url": "https://github.com/symfony/SwiftmailerBundle.git", 3042 "url": "https://github.com/symfony/swiftmailer-bundle.git",
2920 "reference": "970b13d01871207e81d17b17ddda025e7e21e797" 3043 "reference": "970b13d01871207e81d17b17ddda025e7e21e797"
2921 }, 3044 },
2922 "dist": { 3045 "dist": {
2923 "type": "zip", 3046 "type": "zip",
2924 "url": "https://api.github.com/repos/symfony/SwiftmailerBundle/zipball/970b13d01871207e81d17b17ddda025e7e21e797", 3047 "url": "https://api.github.com/repos/symfony/swiftmailer-bundle/zipball/970b13d01871207e81d17b17ddda025e7e21e797",
2925 "reference": "970b13d01871207e81d17b17ddda025e7e21e797", 3048 "reference": "970b13d01871207e81d17b17ddda025e7e21e797",
2926 "shasum": "" 3049 "shasum": ""
2927 }, 3050 },
@@ -2970,20 +3093,20 @@
2970 }, 3093 },
2971 { 3094 {
2972 "name": "symfony/symfony", 3095 "name": "symfony/symfony",
2973 "version": "v2.7.5", 3096 "version": "v2.7.4",
2974 "source": { 3097 "source": {
2975 "type": "git", 3098 "type": "git",
2976 "url": "https://github.com/symfony/symfony.git", 3099 "url": "https://github.com/symfony/symfony.git",
2977 "reference": "619528a274647cffc1792063c3ea04c4fa8266a0" 3100 "reference": "1fdf23fe28876844b887b0e1935c9adda43ee645"
2978 }, 3101 },
2979 "dist": { 3102 "dist": {
2980 "type": "zip", 3103 "type": "zip",
2981 "url": "https://api.github.com/repos/symfony/symfony/zipball/619528a274647cffc1792063c3ea04c4fa8266a0", 3104 "url": "https://api.github.com/repos/symfony/symfony/zipball/1fdf23fe28876844b887b0e1935c9adda43ee645",
2982 "reference": "619528a274647cffc1792063c3ea04c4fa8266a0", 3105 "reference": "1fdf23fe28876844b887b0e1935c9adda43ee645",
2983 "shasum": "" 3106 "shasum": ""
2984 }, 3107 },
2985 "require": { 3108 "require": {
2986 "doctrine/common": "~2.4", 3109 "doctrine/common": "~2.3",
2987 "php": ">=5.3.9", 3110 "php": ">=5.3.9",
2988 "psr/log": "~1.0", 3111 "psr/log": "~1.0",
2989 "twig/twig": "~1.20|~2.0" 3112 "twig/twig": "~1.20|~2.0"
@@ -3036,9 +3159,9 @@
3036 }, 3159 },
3037 "require-dev": { 3160 "require-dev": {
3038 "doctrine/data-fixtures": "1.0.*", 3161 "doctrine/data-fixtures": "1.0.*",
3039 "doctrine/dbal": "~2.4", 3162 "doctrine/dbal": "~2.2",
3040 "doctrine/doctrine-bundle": "~1.2", 3163 "doctrine/doctrine-bundle": "~1.2",
3041 "doctrine/orm": "~2.4,>=2.4.5", 3164 "doctrine/orm": "~2.2,>=2.2.3",
3042 "egulias/email-validator": "~1.2", 3165 "egulias/email-validator": "~1.2",
3043 "ircmaxell/password-compat": "~1.0", 3166 "ircmaxell/password-compat": "~1.0",
3044 "monolog/monolog": "~1.11", 3167 "monolog/monolog": "~1.11",
@@ -3088,7 +3211,7 @@
3088 "keywords": [ 3211 "keywords": [
3089 "framework" 3212 "framework"
3090 ], 3213 ],
3091 "time": "2015-09-25 11:16:52" 3214 "time": "2015-09-08 14:26:39"
3092 }, 3215 },
3093 { 3216 {
3094 "name": "tecnickcom/tcpdf", 3217 "name": "tecnickcom/tcpdf",
@@ -4488,7 +4611,8 @@
4488 "aliases": [], 4611 "aliases": [],
4489 "minimum-stability": "dev", 4612 "minimum-stability": "dev",
4490 "stability-flags": { 4613 "stability-flags": {
4491 "friendsofsymfony/user-bundle": 20 4614 "friendsofsymfony/user-bundle": 20,
4615 "friendsofsymfony/oauth-server-bundle": 20
4492 }, 4616 },
4493 "prefer-stable": true, 4617 "prefer-stable": true,
4494 "prefer-lowest": false, 4618 "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 @@
2 2
3namespace Wallabag\ApiBundle\Controller; 3namespace Wallabag\ApiBundle\Controller;
4 4
5use FOS\RestBundle\Controller\FOSRestController;
5use Nelmio\ApiDocBundle\Annotation\ApiDoc; 6use Nelmio\ApiDocBundle\Annotation\ApiDoc;
6use Symfony\Bundle\FrameworkBundle\Controller\Controller;
7use Symfony\Component\HttpFoundation\Request; 7use Symfony\Component\HttpFoundation\Request;
8use Symfony\Component\HttpFoundation\Response; 8use Symfony\Component\HttpFoundation\Response;
9use Wallabag\CoreBundle\Entity\Entry; 9use Wallabag\CoreBundle\Entity\Entry;
@@ -11,7 +11,7 @@ use Wallabag\CoreBundle\Entity\Tag;
11use Hateoas\Configuration\Route; 11use Hateoas\Configuration\Route;
12use Hateoas\Representation\Factory\PagerfantaFactory; 12use Hateoas\Representation\Factory\PagerfantaFactory;
13 13
14class WallabagRestController extends Controller 14class WallabagRestController extends FOSRestController
15{ 15{
16 /** 16 /**
17 * @param Entry $entry 17 * @param Entry $entry
@@ -39,31 +39,6 @@ class WallabagRestController extends Controller
39 } 39 }
40 40
41 /** 41 /**
42 * Retrieve salt for a giver user.
43 *
44 * @ApiDoc(
45 * parameters={
46 * {"name"="username", "dataType"="string", "required"=true, "description"="username"}
47 * }
48 * )
49 *
50 * @return array
51 */
52 public function getSaltAction($username)
53 {
54 $user = $this
55 ->getDoctrine()
56 ->getRepository('WallabagCoreBundle:User')
57 ->findOneByUsername($username);
58
59 if (is_null($user)) {
60 throw $this->createNotFoundException();
61 }
62
63 return array($user->getSalt() ?: null);
64 }
65
66 /**
67 * Retrieve all entries. It could be filtered by many options. 42 * Retrieve all entries. It could be filtered by many options.
68 * 43 *
69 * @ApiDoc( 44 * @ApiDoc(
@@ -122,7 +97,7 @@ class WallabagRestController extends Controller
122 */ 97 */
123 public function getEntryAction(Entry $entry) 98 public function getEntryAction(Entry $entry)
124 { 99 {
125 $this->validateUserAccess($entry->getUser()->getId(), $this->getUser()->getId()); 100 $this->validateUserAccess($entry->getUser()->getId());
126 101
127 $json = $this->get('serializer')->serialize($entry, 'json'); 102 $json = $this->get('serializer')->serialize($entry, 'json');
128 103
@@ -184,7 +159,7 @@ class WallabagRestController extends Controller
184 */ 159 */
185 public function patchEntriesAction(Entry $entry, Request $request) 160 public function patchEntriesAction(Entry $entry, Request $request)
186 { 161 {
187 $this->validateUserAccess($entry->getUser()->getId(), $this->getUser()->getId()); 162 $this->validateUserAccess($entry->getUser()->getId());
188 163
189 $title = $request->request->get('title'); 164 $title = $request->request->get('title');
190 $isArchived = $request->request->get('is_archived'); 165 $isArchived = $request->request->get('is_archived');
@@ -228,7 +203,7 @@ class WallabagRestController extends Controller
228 */ 203 */
229 public function deleteEntriesAction(Entry $entry) 204 public function deleteEntriesAction(Entry $entry)
230 { 205 {
231 $this->validateUserAccess($entry->getUser()->getId(), $this->getUser()->getId()); 206 $this->validateUserAccess($entry->getUser()->getId());
232 207
233 $em = $this->getDoctrine()->getManager(); 208 $em = $this->getDoctrine()->getManager();
234 $em->remove($entry); 209 $em->remove($entry);
@@ -250,7 +225,7 @@ class WallabagRestController extends Controller
250 */ 225 */
251 public function getEntriesTagsAction(Entry $entry) 226 public function getEntriesTagsAction(Entry $entry)
252 { 227 {
253 $this->validateUserAccess($entry->getUser()->getId(), $this->getUser()->getId()); 228 $this->validateUserAccess($entry->getUser()->getId());
254 229
255 $json = $this->get('serializer')->serialize($entry->getTags(), 'json'); 230 $json = $this->get('serializer')->serialize($entry->getTags(), 'json');
256 231
@@ -271,7 +246,7 @@ class WallabagRestController extends Controller
271 */ 246 */
272 public function postEntriesTagsAction(Request $request, Entry $entry) 247 public function postEntriesTagsAction(Request $request, Entry $entry)
273 { 248 {
274 $this->validateUserAccess($entry->getUser()->getId(), $this->getUser()->getId()); 249 $this->validateUserAccess($entry->getUser()->getId());
275 250
276 $tags = $request->request->get('tags', ''); 251 $tags = $request->request->get('tags', '');
277 if (!empty($tags)) { 252 if (!empty($tags)) {
@@ -299,7 +274,7 @@ class WallabagRestController extends Controller
299 */ 274 */
300 public function deleteEntriesTagsAction(Entry $entry, Tag $tag) 275 public function deleteEntriesTagsAction(Entry $entry, Tag $tag)
301 { 276 {
302 $this->validateUserAccess($entry->getUser()->getId(), $this->getUser()->getId()); 277 $this->validateUserAccess($entry->getUser()->getId());
303 278
304 $entry->removeTag($tag); 279 $entry->removeTag($tag);
305 $em = $this->getDoctrine()->getManager(); 280 $em = $this->getDoctrine()->getManager();
@@ -334,7 +309,7 @@ class WallabagRestController extends Controller
334 */ 309 */
335 public function deleteTagAction(Tag $tag) 310 public function deleteTagAction(Tag $tag)
336 { 311 {
337 $this->validateUserAccess($tag->getUser()->getId(), $this->getUser()->getId()); 312 $this->validateUserAccess($tag->getUser()->getId());
338 313
339 $em = $this->getDoctrine()->getManager(); 314 $em = $this->getDoctrine()->getManager();
340 $em->remove($tag); 315 $em->remove($tag);
@@ -350,12 +325,12 @@ class WallabagRestController extends Controller
350 * If not, throw exception. It means a user try to access information from an other user. 325 * If not, throw exception. It means a user try to access information from an other user.
351 * 326 *
352 * @param int $requestUserId User id from the requested source 327 * @param int $requestUserId User id from the requested source
353 * @param int $currentUserId User id from the retrieved source
354 */ 328 */
355 private function validateUserAccess($requestUserId, $currentUserId) 329 private function validateUserAccess($requestUserId)
356 { 330 {
357 if ($requestUserId != $currentUserId) { 331 $user = $this->get('security.context')->getToken()->getUser();
358 throw $this->createAccessDeniedException('Access forbidden. Entry user id: '.$requestUserId.', logged user id: '.$currentUserId); 332 if ($requestUserId != $user->getId()) {
333 throw $this->createAccessDeniedException('Access forbidden. Entry user id: '.$requestUserId.', logged user id: '.$user->getId());
359 } 334 }
360 } 335 }
361 336
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 @@
1<?php
2
3namespace Wallabag\ApiBundle\DependencyInjection\Security\Factory;
4
5use Symfony\Component\DependencyInjection\ContainerBuilder;
6use Symfony\Component\DependencyInjection\Reference;
7use Symfony\Component\DependencyInjection\DefinitionDecorator;
8use Symfony\Component\Config\Definition\Builder\NodeDefinition;
9use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\SecurityFactoryInterface;
10
11class WsseFactory implements SecurityFactoryInterface
12{
13 public function create(ContainerBuilder $container, $id, $config, $userProvider, $defaultEntryPoint)
14 {
15 $providerId = 'security.authentication.provider.wsse.'.$id;
16 $container
17 ->setDefinition($providerId, new DefinitionDecorator('wsse.security.authentication.provider'))
18 ->replaceArgument(0, new Reference($userProvider))
19 ;
20
21 $listenerId = 'security.authentication.listener.wsse.'.$id;
22 $listener = $container->setDefinition($listenerId, new DefinitionDecorator('wsse.security.authentication.listener'));
23
24 return array($providerId, $listenerId, $defaultEntryPoint);
25 }
26
27 public function getPosition()
28 {
29 return 'pre_auth';
30 }
31
32 public function getKey()
33 {
34 return 'wsse';
35 }
36
37 public function addConfiguration(NodeDefinition $node)
38 {
39 }
40}
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
13 { 13 {
14 $configuration = new Configuration(); 14 $configuration = new Configuration();
15 $config = $this->processConfiguration($configuration, $configs); 15 $config = $this->processConfiguration($configuration, $configs);
16
17 $loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
18 $loader->load('services.yml');
19 } 16 }
20 17
21 public function getAlias() 18 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 @@
1<?php
2
3namespace Wallabag\ApiBundle\Entity;
4
5use FOS\OAuthServerBundle\Entity\AccessToken as BaseAccessToken;
6use Doctrine\ORM\Mapping as ORM;
7
8/**
9 * @ORM\Table("oauth2_access_tokens")
10 * @ORM\Entity
11 */
12class AccessToken extends BaseAccessToken
13{
14 /**
15 * @ORM\Id
16 * @ORM\Column(type="integer")
17 * @ORM\GeneratedValue(strategy="AUTO")
18 */
19 protected $id;
20
21 /**
22 * @ORM\ManyToOne(targetEntity="Client")
23 * @ORM\JoinColumn(nullable=false)
24 */
25 protected $client;
26
27 /**
28 * @ORM\ManyToOne(targetEntity="Wallabag\CoreBundle\Entity\User")
29 */
30 protected $user;
31}
diff --git a/src/Wallabag/ApiBundle/Entity/AuthCode.php b/src/Wallabag/ApiBundle/Entity/AuthCode.php
new file mode 100644
index 00000000..7873d97d
--- /dev/null
+++ b/src/Wallabag/ApiBundle/Entity/AuthCode.php
@@ -0,0 +1,31 @@
1<?php
2
3namespace Wallabag\ApiBundle\Entity;
4
5use FOS\OAuthServerBundle\Entity\AuthCode as BaseAuthCode;
6use Doctrine\ORM\Mapping as ORM;
7
8/**
9 * @ORM\Table("oauth2_auth_codes")
10 * @ORM\Entity
11 */
12class AuthCode extends BaseAuthCode
13{
14 /**
15 * @ORM\Id
16 * @ORM\Column(type="integer")
17 * @ORM\GeneratedValue(strategy="AUTO")
18 */
19 protected $id;
20
21 /**
22 * @ORM\ManyToOne(targetEntity="Client")
23 * @ORM\JoinColumn(nullable=false)
24 */
25 protected $client;
26
27 /**
28 * @ORM\ManyToOne(targetEntity="Wallabag\CoreBundle\Entity\User")
29 */
30 protected $user;
31}
diff --git a/src/Wallabag/ApiBundle/Entity/Client.php b/src/Wallabag/ApiBundle/Entity/Client.php
new file mode 100644
index 00000000..d449870a
--- /dev/null
+++ b/src/Wallabag/ApiBundle/Entity/Client.php
@@ -0,0 +1,25 @@
1<?php
2
3namespace Wallabag\ApiBundle\Entity;
4
5use FOS\OAuthServerBundle\Entity\Client as BaseClient;
6use Doctrine\ORM\Mapping as ORM;
7
8/**
9 * @ORM\Table("oauth2_clients")
10 * @ORM\Entity
11 */
12class Client extends BaseClient
13{
14 /**
15 * @ORM\Id
16 * @ORM\Column(type="integer")
17 * @ORM\GeneratedValue(strategy="AUTO")
18 */
19 protected $id;
20
21 public function __construct()
22 {
23 parent::__construct();
24 }
25}
diff --git a/src/Wallabag/ApiBundle/Entity/RefreshToken.php b/src/Wallabag/ApiBundle/Entity/RefreshToken.php
new file mode 100644
index 00000000..74c564b7
--- /dev/null
+++ b/src/Wallabag/ApiBundle/Entity/RefreshToken.php
@@ -0,0 +1,31 @@
1<?php
2
3namespace Wallabag\ApiBundle\Entity;
4
5use FOS\OAuthServerBundle\Entity\RefreshToken as BaseRefreshToken;
6use Doctrine\ORM\Mapping as ORM;
7
8/**
9 * @ORM\Table("oauth2_refresh_tokens")
10 * @ORM\Entity
11 */
12class RefreshToken extends BaseRefreshToken
13{
14 /**
15 * @ORM\Id
16 * @ORM\Column(type="integer")
17 * @ORM\GeneratedValue(strategy="AUTO")
18 */
19 protected $id;
20
21 /**
22 * @ORM\ManyToOne(targetEntity="Client")
23 * @ORM\JoinColumn(nullable=false)
24 */
25 protected $client;
26
27 /**
28 * @ORM\ManyToOne(targetEntity="Wallabag\CoreBundle\Entity\User")
29 */
30 protected $user;
31}
diff --git a/src/Wallabag/ApiBundle/Resources/config/services.yml b/src/Wallabag/ApiBundle/Resources/config/services.yml
deleted file mode 100644
index 6854a444..00000000
--- a/src/Wallabag/ApiBundle/Resources/config/services.yml
+++ /dev/null
@@ -1,12 +0,0 @@
1services:
2 wsse.security.authentication.provider:
3 class: Wallabag\ApiBundle\Security\Authentication\Provider\WsseProvider
4 public: false
5 arguments: ['', '%kernel.cache_dir%/security/nonces']
6
7 wsse.security.authentication.listener:
8 class: Wallabag\ApiBundle\Security\Firewall\WsseListener
9 public: false
10 tags:
11 - { name: monolog.logger, channel: wsse }
12 arguments: ['@security.context', '@security.authentication.manager', '@logger']
diff --git a/src/Wallabag/ApiBundle/Security/Authentication/Provider/WsseProvider.php b/src/Wallabag/ApiBundle/Security/Authentication/Provider/WsseProvider.php
deleted file mode 100644
index 9bf8b377..00000000
--- a/src/Wallabag/ApiBundle/Security/Authentication/Provider/WsseProvider.php
+++ /dev/null
@@ -1,79 +0,0 @@
1<?php
2
3namespace Wallabag\ApiBundle\Security\Authentication\Provider;
4
5use Symfony\Component\Security\Core\Authentication\Provider\AuthenticationProviderInterface;
6use Symfony\Component\Security\Core\User\UserProviderInterface;
7use Symfony\Component\Security\Core\Exception\AuthenticationException;
8use Symfony\Component\Security\Core\Exception\NonceExpiredException;
9use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
10use Wallabag\ApiBundle\Security\Authentication\Token\WsseUserToken;
11
12class WsseProvider implements AuthenticationProviderInterface
13{
14 private $userProvider;
15 private $cacheDir;
16
17 public function __construct(UserProviderInterface $userProvider, $cacheDir)
18 {
19 $this->userProvider = $userProvider;
20 $this->cacheDir = $cacheDir;
21
22 // If cache directory does not exist we create it
23 if (!is_dir($this->cacheDir)) {
24 mkdir($this->cacheDir, 0777, true);
25 }
26 }
27
28 public function authenticate(TokenInterface $token)
29 {
30 $user = $this->userProvider->loadUserByUsername($token->getUsername());
31
32 if (!$user) {
33 throw new AuthenticationException('Bad credentials. Did you forgot your username?');
34 }
35
36 if ($user && $this->validateDigest($token->digest, $token->nonce, $token->created, $user->getPassword())) {
37 $authenticatedToken = new WsseUserToken($user->getRoles());
38 $authenticatedToken->setUser($user);
39
40 return $authenticatedToken;
41 }
42
43 throw new AuthenticationException('The WSSE authentication failed.');
44 }
45
46 protected function validateDigest($digest, $nonce, $created, $secret)
47 {
48 // Check created time is not in the future
49 if (strtotime($created) > time()) {
50 throw new AuthenticationException('Back to the future...');
51 }
52
53 // Expire timestamp after 5 minutes
54 if (time() - strtotime($created) > 300) {
55 throw new AuthenticationException('Too late for this timestamp... Watch your watch.');
56 }
57
58 // Validate nonce is unique within 5 minutes
59 if (file_exists($this->cacheDir.'/'.$nonce) && file_get_contents($this->cacheDir.'/'.$nonce) + 300 > time()) {
60 throw new NonceExpiredException('Previously used nonce detected');
61 }
62
63 file_put_contents($this->cacheDir.'/'.$nonce, time());
64
65 // Validate Secret
66 $expected = base64_encode(sha1(base64_decode($nonce).$created.$secret, true));
67
68 if ($digest !== $expected) {
69 throw new AuthenticationException('Bad credentials ! Digest is not as expected.');
70 }
71
72 return $digest === $expected;
73 }
74
75 public function supports(TokenInterface $token)
76 {
77 return $token instanceof WsseUserToken;
78 }
79}
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 @@
1<?php
2
3namespace Wallabag\ApiBundle\Security\Authentication\Token;
4
5use Symfony\Component\Security\Core\Authentication\Token\AbstractToken;
6
7class WsseUserToken extends AbstractToken
8{
9 public $created;
10 public $digest;
11 public $nonce;
12
13 public function __construct(array $roles = array())
14 {
15 parent::__construct($roles);
16
17 $this->setAuthenticated(count($roles) > 0);
18 }
19
20 public function getCredentials()
21 {
22 return '';
23 }
24}
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 @@
1<?php
2
3namespace Wallabag\ApiBundle\Security\Firewall;
4
5use Symfony\Component\HttpFoundation\Response;
6use Symfony\Component\HttpKernel\Event\GetResponseEvent;
7use Symfony\Component\Security\Http\Firewall\ListenerInterface;
8use Symfony\Component\Security\Core\Exception\AuthenticationException;
9use Symfony\Component\Security\Core\SecurityContextInterface;
10use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface;
11use Wallabag\ApiBundle\Security\Authentication\Token\WsseUserToken;
12use Psr\Log\LoggerInterface;
13
14class WsseListener implements ListenerInterface
15{
16 protected $securityContext;
17 protected $authenticationManager;
18 protected $logger;
19
20 public function __construct(SecurityContextInterface $securityContext, AuthenticationManagerInterface $authenticationManager, LoggerInterface $logger)
21 {
22 $this->securityContext = $securityContext;
23 $this->authenticationManager = $authenticationManager;
24 $this->logger = $logger;
25 }
26
27 public function handle(GetResponseEvent $event)
28 {
29 $request = $event->getRequest();
30
31 $wsseRegex = '/UsernameToken Username="([^"]+)", PasswordDigest="([^"]+)", Nonce="([^"]+)", Created="([^"]+)"/';
32 if (!$request->headers->has('x-wsse') || 1 !== preg_match($wsseRegex, $request->headers->get('x-wsse'), $matches)) {
33 return;
34 }
35
36 $token = new WsseUserToken();
37 $token->setUser($matches[1]);
38
39 $token->digest = $matches[2];
40 $token->nonce = $matches[3];
41 $token->created = $matches[4];
42
43 try {
44 $authToken = $this->authenticationManager->authenticate($token);
45
46 $this->securityContext->setToken($authToken);
47
48 return;
49 } catch (AuthenticationException $failed) {
50 $failedMessage = 'WSSE Login failed for '.$token->getUsername().'. Why ? '.$failed->getMessage();
51 $this->logger->err($failedMessage);
52
53 // Deny authentication with a '403 Forbidden' HTTP response
54 $response = new Response();
55 $response->setStatusCode(403);
56 $response->setContent($failedMessage);
57 $event->setResponse($response);
58
59 return;
60 }
61 }
62}
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 @@
1<?php
2
3namespace Wallabag\ApiBundle\Tests;
4
5use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
6use Symfony\Component\BrowserKit\Cookie;
7
8abstract class AbstractControllerTest extends WebTestCase
9{
10 /**
11 * @var Client
12 */
13 protected $client = null;
14
15 public function setUp()
16 {
17 $this->client = $this->createAuthorizedClient();
18 }
19
20 /**
21 * @return Client
22 */
23 protected function createAuthorizedClient()
24 {
25 $client = static::createClient();
26 $container = $client->getContainer();
27
28 $session = $container->get('session');
29 /** @var $userManager \FOS\UserBundle\Doctrine\UserManager */
30 $userManager = $container->get('fos_user.user_manager');
31 /** @var $loginManager \FOS\UserBundle\Security\LoginManager */
32 $loginManager = $container->get('fos_user.security.login_manager');
33 $firewallName = $container->getParameter('fos_user.firewall_name');
34
35 $user = $userManager->findUserBy(array('username' => 'admin'));
36 $loginManager->loginUser($firewallName, $user);
37
38 // save the login token into the session and put it in a cookie
39 $container->get('session')->set('_security_'.$firewallName,
40 serialize($container->get('security.context')->getToken()));
41 $container->get('session')->save();
42 $client->getCookieJar()->set(new Cookie($session->getName(), $session->getId()));
43
44 return $client;
45 }
46}
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 @@
2 2
3namespace Wallabag\ApiBundle\Tests\Controller; 3namespace Wallabag\ApiBundle\Tests\Controller;
4 4
5use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; 5use Wallabag\ApiBundle\Tests\AbstractControllerTest;
6 6
7class WallabagRestControllerTest extends WebTestCase 7class WallabagRestControllerTest extends AbstractControllerTest
8{ 8{
9 protected static $salt; 9 protected static $salt;
10 10
11 /**
12 * Grab the salt once and store it to be available for all tests.
13 */
14 public static function setUpBeforeClass()
15 {
16 $client = self::createClient();
17
18 $user = $client->getContainer()
19 ->get('doctrine.orm.entity_manager')
20 ->getRepository('WallabagCoreBundle:User')
21 ->findOneByUsername('admin');
22
23 self::$salt = $user->getSalt();
24 }
25
26 /**
27 * Generate HTTP headers for authenticate user on API.
28 *
29 * @param string $username
30 * @param string $password
31 *
32 * @return array
33 */
34 private function generateHeaders($username, $password)
35 {
36 $encryptedPassword = sha1($password.$username.self::$salt);
37 $nonce = substr(md5(uniqid('nonce_', true)), 0, 16);
38
39 $now = new \DateTime('now', new \DateTimeZone('UTC'));
40 $created = (string) $now->format('Y-m-d\TH:i:s\Z');
41 $digest = base64_encode(sha1(base64_decode($nonce).$created.$encryptedPassword, true));
42
43 return array(
44 'HTTP_AUTHORIZATION' => 'Authorization profile="UsernameToken"',
45 'HTTP_x-wsse' => 'X-WSSE: UsernameToken Username="'.$username.'", PasswordDigest="'.$digest.'", Nonce="'.$nonce.'", Created="'.$created.'"',
46 );
47 }
48
49 public function testGetSalt()
50 {
51 $client = $this->createClient();
52 $client->request('GET', '/api/salts/admin.json');
53
54 $user = $client->getContainer()
55 ->get('doctrine.orm.entity_manager')
56 ->getRepository('WallabagCoreBundle:User')
57 ->findOneByUsername('admin');
58
59 $this->assertEquals(200, $client->getResponse()->getStatusCode());
60
61 $content = json_decode($client->getResponse()->getContent(), true);
62
63 $this->assertArrayHasKey(0, $content);
64 $this->assertEquals($user->getSalt(), $content[0]);
65
66 $client->request('GET', '/api/salts/notfound.json');
67 $this->assertEquals(404, $client->getResponse()->getStatusCode());
68 }
69
70 public function testWithBadHeaders()
71 {
72 $client = $this->createClient();
73
74 $entry = $client->getContainer()
75 ->get('doctrine.orm.entity_manager')
76 ->getRepository('WallabagCoreBundle:Entry')
77 ->findOneByIsArchived(false);
78
79 if (!$entry) {
80 $this->markTestSkipped('No content found in db.');
81 }
82
83 $badHeaders = array(
84 'HTTP_AUTHORIZATION' => 'Authorization profile="UsernameToken"',
85 'HTTP_x-wsse' => 'X-WSSE: UsernameToken Username="admin", PasswordDigest="Wr0ngDig3st", Nonce="n0Nc3", Created="2015-01-01T13:37:00Z"',
86 );
87
88 $client->request('GET', '/api/entries/'.$entry->getId().'.json', array(), array(), $badHeaders);
89 $this->assertEquals(403, $client->getResponse()->getStatusCode());
90 }
91
92 public function testGetOneEntry() 11 public function testGetOneEntry()
93 { 12 {
94 $client = $this->createClient(); 13 $entry = $this->client->getContainer()
95 $headers = $this->generateHeaders('admin', 'mypassword');
96
97 $entry = $client->getContainer()
98 ->get('doctrine.orm.entity_manager') 14 ->get('doctrine.orm.entity_manager')
99 ->getRepository('WallabagCoreBundle:Entry') 15 ->getRepository('WallabagCoreBundle:Entry')
100 ->findOneBy(array('user' => 1, 'isArchived' => false)); 16 ->findOneBy(array('user' => 1, 'isArchived' => false));
@@ -103,18 +19,17 @@ class WallabagRestControllerTest extends WebTestCase
103 $this->markTestSkipped('No content found in db.'); 19 $this->markTestSkipped('No content found in db.');
104 } 20 }
105 21
106 $client->request('GET', '/api/entries/'.$entry->getId().'.json', array(), array(), $headers); 22 $this->client->request('GET', '/api/entries/'.$entry->getId().'.json');
23 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
107 24
108 $this->assertEquals(200, $client->getResponse()->getStatusCode()); 25 $content = json_decode($this->client->getResponse()->getContent(), true);
109
110 $content = json_decode($client->getResponse()->getContent(), true);
111 26
112 $this->assertEquals($entry->getTitle(), $content['title']); 27 $this->assertEquals($entry->getTitle(), $content['title']);
113 $this->assertEquals($entry->getUrl(), $content['url']); 28 $this->assertEquals($entry->getUrl(), $content['url']);
114 $this->assertCount(count($entry->getTags()), $content['tags']); 29 $this->assertCount(count($entry->getTags()), $content['tags']);
115 30
116 $this->assertTrue( 31 $this->assertTrue(
117 $client->getResponse()->headers->contains( 32 $this->client->getResponse()->headers->contains(
118 'Content-Type', 33 'Content-Type',
119 'application/json' 34 'application/json'
120 ) 35 )
@@ -123,10 +38,7 @@ class WallabagRestControllerTest extends WebTestCase
123 38
124 public function testGetOneEntryWrongUser() 39 public function testGetOneEntryWrongUser()
125 { 40 {
126 $client = $this->createClient(); 41 $entry = $this->client->getContainer()
127 $headers = $this->generateHeaders('admin', 'mypassword');
128
129 $entry = $client->getContainer()
130 ->get('doctrine.orm.entity_manager') 42 ->get('doctrine.orm.entity_manager')
131 ->getRepository('WallabagCoreBundle:Entry') 43 ->getRepository('WallabagCoreBundle:Entry')
132 ->findOneBy(array('user' => 2, 'isArchived' => false)); 44 ->findOneBy(array('user' => 2, 'isArchived' => false));
@@ -135,21 +47,18 @@ class WallabagRestControllerTest extends WebTestCase
135 $this->markTestSkipped('No content found in db.'); 47 $this->markTestSkipped('No content found in db.');
136 } 48 }
137 49
138 $client->request('GET', '/api/entries/'.$entry->getId().'.json', array(), array(), $headers); 50 $this->client->request('GET', '/api/entries/'.$entry->getId().'.json');
139 51
140 $this->assertEquals(403, $client->getResponse()->getStatusCode()); 52 $this->assertEquals(403, $this->client->getResponse()->getStatusCode());
141 } 53 }
142 54
143 public function testGetEntries() 55 public function testGetEntries()
144 { 56 {
145 $client = $this->createClient(); 57 $this->client->request('GET', '/api/entries');
146 $headers = $this->generateHeaders('admin', 'mypassword');
147
148 $client->request('GET', '/api/entries', array(), array(), $headers);
149 58
150 $this->assertEquals(200, $client->getResponse()->getStatusCode()); 59 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
151 60
152 $content = json_decode($client->getResponse()->getContent(), true); 61 $content = json_decode($this->client->getResponse()->getContent(), true);
153 62
154 $this->assertGreaterThanOrEqual(1, count($content)); 63 $this->assertGreaterThanOrEqual(1, count($content));
155 $this->assertNotEmpty($content['_embedded']['items']); 64 $this->assertNotEmpty($content['_embedded']['items']);
@@ -158,7 +67,7 @@ class WallabagRestControllerTest extends WebTestCase
158 $this->assertGreaterThanOrEqual(1, $content['pages']); 67 $this->assertGreaterThanOrEqual(1, $content['pages']);
159 68
160 $this->assertTrue( 69 $this->assertTrue(
161 $client->getResponse()->headers->contains( 70 $this->client->getResponse()->headers->contains(
162 'Content-Type', 71 'Content-Type',
163 'application/json' 72 'application/json'
164 ) 73 )
@@ -167,14 +76,11 @@ class WallabagRestControllerTest extends WebTestCase
167 76
168 public function testGetStarredEntries() 77 public function testGetStarredEntries()
169 { 78 {
170 $client = $this->createClient(); 79 $this->client->request('GET', '/api/entries', array('star' => 1, 'sort' => 'updated'));
171 $headers = $this->generateHeaders('admin', 'mypassword');
172 80
173 $client->request('GET', '/api/entries', array('star' => 1, 'sort' => 'updated'), array(), $headers); 81 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
174 82
175 $this->assertEquals(200, $client->getResponse()->getStatusCode()); 83 $content = json_decode($this->client->getResponse()->getContent(), true);
176
177 $content = json_decode($client->getResponse()->getContent(), true);
178 84
179 $this->assertGreaterThanOrEqual(1, count($content)); 85 $this->assertGreaterThanOrEqual(1, count($content));
180 $this->assertNotEmpty($content['_embedded']['items']); 86 $this->assertNotEmpty($content['_embedded']['items']);
@@ -183,7 +89,7 @@ class WallabagRestControllerTest extends WebTestCase
183 $this->assertGreaterThanOrEqual(1, $content['pages']); 89 $this->assertGreaterThanOrEqual(1, $content['pages']);
184 90
185 $this->assertTrue( 91 $this->assertTrue(
186 $client->getResponse()->headers->contains( 92 $this->client->getResponse()->headers->contains(
187 'Content-Type', 93 'Content-Type',
188 'application/json' 94 'application/json'
189 ) 95 )
@@ -192,14 +98,11 @@ class WallabagRestControllerTest extends WebTestCase
192 98
193 public function testGetArchiveEntries() 99 public function testGetArchiveEntries()
194 { 100 {
195 $client = $this->createClient(); 101 $this->client->request('GET', '/api/entries', array('archive' => 1));
196 $headers = $this->generateHeaders('admin', 'mypassword');
197
198 $client->request('GET', '/api/entries', array('archive' => 1), array(), $headers);
199 102
200 $this->assertEquals(200, $client->getResponse()->getStatusCode()); 103 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
201 104
202 $content = json_decode($client->getResponse()->getContent(), true); 105 $content = json_decode($this->client->getResponse()->getContent(), true);
203 106
204 $this->assertGreaterThanOrEqual(1, count($content)); 107 $this->assertGreaterThanOrEqual(1, count($content));
205 $this->assertNotEmpty($content['_embedded']['items']); 108 $this->assertNotEmpty($content['_embedded']['items']);
@@ -208,7 +111,7 @@ class WallabagRestControllerTest extends WebTestCase
208 $this->assertGreaterThanOrEqual(1, $content['pages']); 111 $this->assertGreaterThanOrEqual(1, $content['pages']);
209 112
210 $this->assertTrue( 113 $this->assertTrue(
211 $client->getResponse()->headers->contains( 114 $this->client->getResponse()->headers->contains(
212 'Content-Type', 115 'Content-Type',
213 'application/json' 116 'application/json'
214 ) 117 )
@@ -217,10 +120,7 @@ class WallabagRestControllerTest extends WebTestCase
217 120
218 public function testDeleteEntry() 121 public function testDeleteEntry()
219 { 122 {
220 $client = $this->createClient(); 123 $entry = $this->client->getContainer()
221 $headers = $this->generateHeaders('admin', 'mypassword');
222
223 $entry = $client->getContainer()
224 ->get('doctrine.orm.entity_manager') 124 ->get('doctrine.orm.entity_manager')
225 ->getRepository('WallabagCoreBundle:Entry') 125 ->getRepository('WallabagCoreBundle:Entry')
226 ->findOneByUser(1); 126 ->findOneByUser(1);
@@ -229,36 +129,31 @@ class WallabagRestControllerTest extends WebTestCase
229 $this->markTestSkipped('No content found in db.'); 129 $this->markTestSkipped('No content found in db.');
230 } 130 }
231 131
232 $client->request('DELETE', '/api/entries/'.$entry->getId().'.json', array(), array(), $headers); 132 $this->client->request('DELETE', '/api/entries/'.$entry->getId().'.json');
233 133
234 $this->assertEquals(200, $client->getResponse()->getStatusCode()); 134 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
235 135
236 $content = json_decode($client->getResponse()->getContent(), true); 136 $content = json_decode($this->client->getResponse()->getContent(), true);
237 137
238 $this->assertEquals($entry->getTitle(), $content['title']); 138 $this->assertEquals($entry->getTitle(), $content['title']);
239 $this->assertEquals($entry->getUrl(), $content['url']); 139 $this->assertEquals($entry->getUrl(), $content['url']);
240 140
241 // We'll try to delete this entry again 141 // We'll try to delete this entry again
242 $headers = $this->generateHeaders('admin', 'mypassword'); 142 $this->client->request('DELETE', '/api/entries/'.$entry->getId().'.json');
243
244 $client->request('DELETE', '/api/entries/'.$entry->getId().'.json', array(), array(), $headers);
245 143
246 $this->assertEquals(404, $client->getResponse()->getStatusCode()); 144 $this->assertEquals(404, $this->client->getResponse()->getStatusCode());
247 } 145 }
248 146
249 public function testPostEntry() 147 public function testPostEntry()
250 { 148 {
251 $client = $this->createClient(); 149 $this->client->request('POST', '/api/entries.json', array(
252 $headers = $this->generateHeaders('admin', 'mypassword');
253
254 $client->request('POST', '/api/entries.json', array(
255 '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', 150 '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',
256 'tags' => 'google', 151 'tags' => 'google',
257 ), array(), $headers); 152 ));
258 153
259 $this->assertEquals(200, $client->getResponse()->getStatusCode()); 154 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
260 155
261 $content = json_decode($client->getResponse()->getContent(), true); 156 $content = json_decode($this->client->getResponse()->getContent(), true);
262 157
263 $this->assertGreaterThan(0, $content['id']); 158 $this->assertGreaterThan(0, $content['id']);
264 $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']); 159 $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
269 164
270 public function testPatchEntry() 165 public function testPatchEntry()
271 { 166 {
272 $client = $this->createClient(); 167 $entry = $this->client->getContainer()
273 $headers = $this->generateHeaders('admin', 'mypassword');
274
275 $entry = $client->getContainer()
276 ->get('doctrine.orm.entity_manager') 168 ->get('doctrine.orm.entity_manager')
277 ->getRepository('WallabagCoreBundle:Entry') 169 ->getRepository('WallabagCoreBundle:Entry')
278 ->findOneByUser(1); 170 ->findOneByUser(1);
@@ -284,16 +176,16 @@ class WallabagRestControllerTest extends WebTestCase
284 // hydrate the tags relations 176 // hydrate the tags relations
285 $nbTags = count($entry->getTags()); 177 $nbTags = count($entry->getTags());
286 178
287 $client->request('PATCH', '/api/entries/'.$entry->getId().'.json', array( 179 $this->client->request('PATCH', '/api/entries/'.$entry->getId().'.json', array(
288 'title' => 'New awesome title', 180 'title' => 'New awesome title',
289 'tags' => 'new tag '.uniqid(), 181 'tags' => 'new tag '.uniqid(),
290 'star' => true, 182 'star' => true,
291 'archive' => false, 183 'archive' => false,
292 ), array(), $headers); 184 ));
293 185
294 $this->assertEquals(200, $client->getResponse()->getStatusCode()); 186 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
295 187
296 $content = json_decode($client->getResponse()->getContent(), true); 188 $content = json_decode($this->client->getResponse()->getContent(), true);
297 189
298 $this->assertEquals($entry->getId(), $content['id']); 190 $this->assertEquals($entry->getId(), $content['id']);
299 $this->assertEquals($entry->getUrl(), $content['url']); 191 $this->assertEquals($entry->getUrl(), $content['url']);
@@ -303,10 +195,7 @@ class WallabagRestControllerTest extends WebTestCase
303 195
304 public function testGetTagsEntry() 196 public function testGetTagsEntry()
305 { 197 {
306 $client = $this->createClient(); 198 $entry = $this->client->getContainer()
307 $headers = $this->generateHeaders('admin', 'mypassword');
308
309 $entry = $client->getContainer()
310 ->get('doctrine.orm.entity_manager') 199 ->get('doctrine.orm.entity_manager')
311 ->getRepository('WallabagCoreBundle:Entry') 200 ->getRepository('WallabagCoreBundle:Entry')
312 ->findOneWithTags(1); 201 ->findOneWithTags(1);
@@ -322,17 +211,14 @@ class WallabagRestControllerTest extends WebTestCase
322 $tags[] = array('id' => $tag->getId(), 'label' => $tag->getLabel()); 211 $tags[] = array('id' => $tag->getId(), 'label' => $tag->getLabel());
323 } 212 }
324 213
325 $client->request('GET', '/api/entries/'.$entry->getId().'/tags', array(), array(), $headers); 214 $this->client->request('GET', '/api/entries/'.$entry->getId().'/tags');
326 215
327 $this->assertEquals(json_encode($tags, JSON_HEX_QUOT), $client->getResponse()->getContent()); 216 $this->assertEquals(json_encode($tags, JSON_HEX_QUOT), $this->client->getResponse()->getContent());
328 } 217 }
329 218
330 public function testPostTagsOnEntry() 219 public function testPostTagsOnEntry()
331 { 220 {
332 $client = $this->createClient(); 221 $entry = $this->client->getContainer()
333 $headers = $this->generateHeaders('admin', 'mypassword');
334
335 $entry = $client->getContainer()
336 ->get('doctrine.orm.entity_manager') 222 ->get('doctrine.orm.entity_manager')
337 ->getRepository('WallabagCoreBundle:Entry') 223 ->getRepository('WallabagCoreBundle:Entry')
338 ->findOneByUser(1); 224 ->findOneByUser(1);
@@ -345,16 +231,16 @@ class WallabagRestControllerTest extends WebTestCase
345 231
346 $newTags = 'tag1,tag2,tag3'; 232 $newTags = 'tag1,tag2,tag3';
347 233
348 $client->request('POST', '/api/entries/'.$entry->getId().'/tags', array('tags' => $newTags), array(), $headers); 234 $this->client->request('POST', '/api/entries/'.$entry->getId().'/tags', array('tags' => $newTags));
349 235
350 $this->assertEquals(200, $client->getResponse()->getStatusCode()); 236 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
351 237
352 $content = json_decode($client->getResponse()->getContent(), true); 238 $content = json_decode($this->client->getResponse()->getContent(), true);
353 239
354 $this->assertArrayHasKey('tags', $content); 240 $this->assertArrayHasKey('tags', $content);
355 $this->assertEquals($nbTags + 3, count($content['tags'])); 241 $this->assertEquals($nbTags + 3, count($content['tags']));
356 242
357 $entryDB = $client->getContainer() 243 $entryDB = $this->client->getContainer()
358 ->get('doctrine.orm.entity_manager') 244 ->get('doctrine.orm.entity_manager')
359 ->getRepository('WallabagCoreBundle:Entry') 245 ->getRepository('WallabagCoreBundle:Entry')
360 ->find($entry->getId()); 246 ->find($entry->getId());
@@ -369,15 +255,13 @@ class WallabagRestControllerTest extends WebTestCase
369 } 255 }
370 } 256 }
371 257
372 public function testDeleteOneTagEntrie() 258 public function testDeleteOneTagEntry()
373 { 259 {
374 $client = $this->createClient(); 260 $entry = $this->client->getContainer()
375 $headers = $this->generateHeaders('admin', 'mypassword');
376
377 $entry = $client->getContainer()
378 ->get('doctrine.orm.entity_manager') 261 ->get('doctrine.orm.entity_manager')
379 ->getRepository('WallabagCoreBundle:Entry') 262 ->getRepository('WallabagCoreBundle:Entry')
380 ->findOneByUser(1); 263 ->findOneWithTags(1);
264 $entry = $entry[0];
381 265
382 if (!$entry) { 266 if (!$entry) {
383 $this->markTestSkipped('No content found in db.'); 267 $this->markTestSkipped('No content found in db.');
@@ -387,11 +271,11 @@ class WallabagRestControllerTest extends WebTestCase
387 $nbTags = count($entry->getTags()); 271 $nbTags = count($entry->getTags());
388 $tag = $entry->getTags()[0]; 272 $tag = $entry->getTags()[0];
389 273
390 $client->request('DELETE', '/api/entries/'.$entry->getId().'/tags/'.$tag->getId().'.json', array(), array(), $headers); 274 $this->client->request('DELETE', '/api/entries/'.$entry->getId().'/tags/'.$tag->getId().'.json');
391 275
392 $this->assertEquals(200, $client->getResponse()->getStatusCode()); 276 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
393 277
394 $content = json_decode($client->getResponse()->getContent(), true); 278 $content = json_decode($this->client->getResponse()->getContent(), true);
395 279
396 $this->assertArrayHasKey('tags', $content); 280 $this->assertArrayHasKey('tags', $content);
397 $this->assertEquals($nbTags - 1, count($content['tags'])); 281 $this->assertEquals($nbTags - 1, count($content['tags']));
@@ -399,14 +283,11 @@ class WallabagRestControllerTest extends WebTestCase
399 283
400 public function testGetUserTags() 284 public function testGetUserTags()
401 { 285 {
402 $client = $this->createClient(); 286 $this->client->request('GET', '/api/tags.json');
403 $headers = $this->generateHeaders('admin', 'mypassword');
404
405 $client->request('GET', '/api/tags.json', array(), array(), $headers);
406 287
407 $this->assertEquals(200, $client->getResponse()->getStatusCode()); 288 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
408 289
409 $content = json_decode($client->getResponse()->getContent(), true); 290 $content = json_decode($this->client->getResponse()->getContent(), true);
410 291
411 $this->assertGreaterThan(0, $content); 292 $this->assertGreaterThan(0, $content);
412 $this->assertArrayHasKey('id', $content[0]); 293 $this->assertArrayHasKey('id', $content[0]);
@@ -420,14 +301,11 @@ class WallabagRestControllerTest extends WebTestCase
420 */ 301 */
421 public function testDeleteUserTag($tag) 302 public function testDeleteUserTag($tag)
422 { 303 {
423 $client = $this->createClient(); 304 $this->client->request('DELETE', '/api/tags/'.$tag['id'].'.json');
424 $headers = $this->generateHeaders('admin', 'mypassword');
425
426 $client->request('DELETE', '/api/tags/'.$tag['id'].'.json', array(), array(), $headers);
427 305
428 $this->assertEquals(200, $client->getResponse()->getStatusCode()); 306 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
429 307
430 $content = json_decode($client->getResponse()->getContent(), true); 308 $content = json_decode($this->client->getResponse()->getContent(), true);
431 309
432 $this->assertArrayHasKey('label', $content); 310 $this->assertArrayHasKey('label', $content);
433 $this->assertEquals($tag['label'], $content['label']); 311 $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 @@
3namespace Wallabag\ApiBundle; 3namespace Wallabag\ApiBundle;
4 4
5use Symfony\Component\HttpKernel\Bundle\Bundle; 5use Symfony\Component\HttpKernel\Bundle\Bundle;
6use Wallabag\ApiBundle\DependencyInjection\Security\Factory\WsseFactory;
7use Symfony\Component\DependencyInjection\ContainerBuilder;
8 6
9class WallabagApiBundle extends Bundle 7class WallabagApiBundle extends Bundle
10{ 8{
11 public function build(ContainerBuilder $container)
12 {
13 parent::build($container);
14
15 $extension = $container->getExtension('security');
16 $extension->addSecurityListenerFactory(new WsseFactory());
17 }
18} 9}
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
25 { 25 {
26 $em = $this->getDoctrine()->getManager(); 26 $em = $this->getDoctrine()->getManager();
27 $config = $this->getConfig(); 27 $config = $this->getConfig();
28 $userManager = $this->container->get('fos_user.user_manager');
28 $user = $this->getUser(); 29 $user = $this->getUser();
29 30
30 // handle basic config detail (this form is defined as a service) 31 // handle basic config detail (this form is defined as a service)
@@ -52,9 +53,8 @@ class ConfigController extends Controller
52 $pwdForm->handleRequest($request); 53 $pwdForm->handleRequest($request);
53 54
54 if ($pwdForm->isValid()) { 55 if ($pwdForm->isValid()) {
55 $user->setPassword($pwdForm->get('new_password')->getData()); 56 $user->setPlainPassword($pwdForm->get('new_password')->getData());
56 $em->persist($user); 57 $userManager->updateUser($user, true);
57 $em->flush();
58 58
59 $this->get('session')->getFlashBag()->add( 59 $this->get('session')->getFlashBag()->add(
60 'notice', 60 'notice',
@@ -69,8 +69,7 @@ class ConfigController extends Controller
69 $userForm->handleRequest($request); 69 $userForm->handleRequest($request);
70 70
71 if ($userForm->isValid()) { 71 if ($userForm->isValid()) {
72 $em->persist($user); 72 $userManager->updateUser($user, true);
73 $em->flush();
74 73
75 $this->get('session')->getFlashBag()->add( 74 $this->get('session')->getFlashBag()->add(
76 'notice', 75 'notice',
@@ -97,14 +96,14 @@ class ConfigController extends Controller
97 } 96 }
98 97
99 // handle adding new user 98 // handle adding new user
100 $newUser = new User(); 99 $newUser = $userManager->createUser();
101 // enable created user by default 100 // enable created user by default
102 $newUser->setEnabled(true); 101 $newUser->setEnabled(true);
103 $newUserForm = $this->createForm(new NewUserType(), $newUser, array('validation_groups' => array('Profile'))); 102 $newUserForm = $this->createForm(new NewUserType(), $newUser, array('validation_groups' => array('Profile')));
104 $newUserForm->handleRequest($request); 103 $newUserForm->handleRequest($request);
105 104
106 if ($newUserForm->isValid()) { 105 if ($newUserForm->isValid() && $this->get('security.authorization_checker')->isGranted('ROLE_SUPER_ADMIN')) {
107 $em->persist($newUser); 106 $userManager->updateUser($newUser, true);
108 107
109 $config = new Config($newUser); 108 $config = new Config($newUser);
110 $config->setTheme($this->container->getParameter('theme')); 109 $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
18 $userAdmin->setName('Big boss'); 18 $userAdmin->setName('Big boss');
19 $userAdmin->setEmail('bigboss@wallabag.org'); 19 $userAdmin->setEmail('bigboss@wallabag.org');
20 $userAdmin->setUsername('admin'); 20 $userAdmin->setUsername('admin');
21 $userAdmin->setPassword('mypassword'); 21 $userAdmin->setPlainPassword('mypassword');
22 $userAdmin->setEnabled(true); 22 $userAdmin->setEnabled(true);
23 $userAdmin->addRole('ROLE_SUPER_ADMIN');
23 24
24 $manager->persist($userAdmin); 25 $manager->persist($userAdmin);
25 26
@@ -29,7 +30,7 @@ class LoadUserData extends AbstractFixture implements OrderedFixtureInterface
29 $bobUser->setName('Bobby'); 30 $bobUser->setName('Bobby');
30 $bobUser->setEmail('bobby@wallabag.org'); 31 $bobUser->setEmail('bobby@wallabag.org');
31 $bobUser->setUsername('bob'); 32 $bobUser->setUsername('bob');
32 $bobUser->setPassword('mypassword'); 33 $bobUser->setPlainPassword('mypassword');
33 $bobUser->setEnabled(true); 34 $bobUser->setEnabled(true);
34 35
35 $manager->persist($bobUser); 36 $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;
6use Doctrine\ORM\Mapping as ORM; 6use Doctrine\ORM\Mapping as ORM;
7use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; 7use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
8use Symfony\Component\Security\Core\User\UserInterface; 8use Symfony\Component\Security\Core\User\UserInterface;
9use Symfony\Component\Security\Core\User\AdvancedUserInterface;
10use JMS\Serializer\Annotation\ExclusionPolicy; 9use JMS\Serializer\Annotation\ExclusionPolicy;
11use JMS\Serializer\Annotation\Expose; 10use JMS\Serializer\Annotation\Expose;
12use FOS\UserBundle\Model\User as BaseUser; 11use FOS\UserBundle\Model\User as BaseUser;
@@ -22,7 +21,7 @@ use FOS\UserBundle\Model\User as BaseUser;
22 * @UniqueEntity("email") 21 * @UniqueEntity("email")
23 * @UniqueEntity("username") 22 * @UniqueEntity("username")
24 */ 23 */
25class User extends BaseUser implements AdvancedUserInterface, \Serializable 24class User extends BaseUser
26{ 25{
27 /** 26 /**
28 * @var int 27 * @var int
@@ -75,6 +74,7 @@ class User extends BaseUser implements AdvancedUserInterface, \Serializable
75 parent::__construct(); 74 parent::__construct();
76 $this->entries = new ArrayCollection(); 75 $this->entries = new ArrayCollection();
77 $this->tags = new ArrayCollection(); 76 $this->tags = new ArrayCollection();
77 $this->roles = array('ROLE_USER');
78 } 78 }
79 79
80 /** 80 /**
@@ -91,24 +91,6 @@ class User extends BaseUser implements AdvancedUserInterface, \Serializable
91 } 91 }
92 92
93 /** 93 /**
94 * Set password.
95 *
96 * @param string $password
97 *
98 * @return User
99 */
100 public function setPassword($password)
101 {
102 if (!$password && 0 === strlen($password)) {
103 return;
104 }
105
106 $this->password = sha1($password.$this->getUsername().$this->getSalt());
107
108 return $this;
109 }
110
111 /**
112 * Set name. 94 * Set name.
113 * 95 *
114 * @param string $name 96 * @param string $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 @@
1<?php
2
3namespace Wallabag\CoreBundle\EventListener;
4
5use FOS\UserBundle\FOSUserEvents;
6use Symfony\Component\DependencyInjection\Container;
7use Symfony\Component\EventDispatcher\EventDispatcherInterface;
8use Symfony\Component\EventDispatcher\EventSubscriberInterface;
9use FOS\UserBundle\Event\FilterUserResponseEvent;
10use Wallabag\CoreBundle\Entity\Config;
11
12class AuthenticationListener implements EventSubscriberInterface
13{
14 private $em;
15 private $container;
16
17 public function __construct(Container $container, $em)
18 {
19 $this->container = $container;
20 $this->em = $em;
21 }
22
23 public static function getSubscribedEvents()
24 {
25 return array(
26 FOSUserEvents::REGISTRATION_CONFIRMED => 'authenticate',
27 );
28 }
29
30 public function authenticate(FilterUserResponseEvent $event, $eventName = null, EventDispatcherInterface $eventDispatcher = null)
31 {
32 if (!$event->getUser()->isEnabled()) {
33 return;
34 }
35
36 $config = new Config($event->getUser());
37 $config->setTheme($this->container->getParameter('theme'));
38 $config->setItemsPerPage($this->container->getParameter('items_on_page'));
39 $config->setRssLimit($this->container->getParameter('rss_limit'));
40 $config->setLanguage($this->container->getParameter('language'));
41 $this->em->persist($config);
42 $this->em->flush();
43 }
44}
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
13 { 13 {
14 $builder 14 $builder
15 ->add('username', 'text', array('required' => true)) 15 ->add('username', 'text', array('required' => true))
16 ->add('password', 'password', array( 16 ->add('plainPassword', 'repeated', array(
17 'type' => 'password',
17 'constraints' => array( 18 'constraints' => array(
18 new Constraints\Length(array( 19 new Constraints\Length(array(
19 'min' => 8, 20 '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 @@
1<?php
2
3namespace Wallabag\CoreBundle\Form\Type;
4
5use Symfony\Component\Form\AbstractType;
6use Symfony\Component\Form\FormBuilderInterface;
7
8class RegistrationType extends AbstractType
9{
10 public function buildForm(FormBuilderInterface $builder, array $options)
11 {
12 $builder->add('name');
13 }
14
15 public function getParent()
16 {
17 return 'fos_user_registration';
18 }
19
20 public function getName()
21 {
22 return 'wallabag_user_registration';
23 }
24}
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:
13 tags: 13 tags:
14 - { name: form.type, alias: config } 14 - { name: form.type, alias: config }
15 15
16 wallabag_core.form.registration:
17 class: Wallabag\CoreBundle\Form\Type\RegistrationType
18 tags:
19 - { name: form.type, alias: wallabag_user_registration }
20
16 wallabag_core.form.type.forgot_password: 21 wallabag_core.form.type.forgot_password:
17 class: Wallabag\CoreBundle\Form\Type\ForgotPasswordType 22 class: Wallabag\CoreBundle\Form\Type\ForgotPasswordType
18 arguments: 23 arguments:
@@ -40,3 +45,9 @@ services:
40 class: Wallabag\CoreBundle\Helper\ContentProxy 45 class: Wallabag\CoreBundle\Helper\ContentProxy
41 arguments: 46 arguments:
42 - @wallabag_core.graby 47 - @wallabag_core.graby
48
49 wallabag_core.registration_confirmed:
50 class: Wallabag\CoreBundle\EventListener\AuthenticationListener
51 arguments: [@service_container, @doctrine.orm.entity_manager]
52 tags:
53 - { 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 @@
135 {{ form_rest(form.pwd) }} 135 {{ form_rest(form.pwd) }}
136 </form> 136 </form>
137 137
138 {% if is_granted('ROLE_SUPER_ADMIN') %}
138 <h2>{% trans %}Add a user{% endtrans %}</h2> 139 <h2>{% trans %}Add a user{% endtrans %}</h2>
139 140
140 <form action="{{ path('config') }}" method="post" {{ form_enctype(form.new_user) }}> 141 <form action="{{ path('config') }}" method="post" {{ form_enctype(form.new_user) }}>
@@ -150,9 +151,17 @@
150 151
151 <fieldset class="w500p inline"> 152 <fieldset class="w500p inline">
152 <div class="row"> 153 <div class="row">
153 {{ form_label(form.new_user.password) }} 154 {{ form_label(form.new_user.plainPassword.first) }}
154 {{ form_errors(form.new_user.password) }} 155 {{ form_errors(form.new_user.plainPassword.first) }}
155 {{ form_widget(form.new_user.password) }} 156 {{ form_widget(form.new_user.plainPassword.first) }}
157 </div>
158 </fieldset>
159
160 <fieldset class="w500p inline">
161 <div class="row">
162 {{ form_label(form.new_user.plainPassword.second) }}
163 {{ form_errors(form.new_user.plainPassword.second) }}
164 {{ form_widget(form.new_user.plainPassword.second) }}
156 </div> 165 </div>
157 </fieldset> 166 </fieldset>
158 167
@@ -165,5 +174,6 @@
165 </fieldset> 174 </fieldset>
166 175
167 {{ form_rest(form.new_user) }} 176 {{ form_rest(form.new_user) }}
177 {% endif %}
168 </form> 178 </form>
169{% endblock %} 179{% 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 @@
15 <li class="tab col s3"><a href="#set2">{% trans %}RSS{% endtrans %}</a></li> 15 <li class="tab col s3"><a href="#set2">{% trans %}RSS{% endtrans %}</a></li>
16 <li class="tab col s3"><a href="#set3">{% trans %}User information{% endtrans %}</a></li> 16 <li class="tab col s3"><a href="#set3">{% trans %}User information{% endtrans %}</a></li>
17 <li class="tab col s3"><a href="#set4">{% trans %}Password{% endtrans %}</a></li> 17 <li class="tab col s3"><a href="#set4">{% trans %}Password{% endtrans %}</a></li>
18 {% if is_granted('ROLE_SUPER_ADMIN') %}
18 <li class="tab col s3"><a href="#set5">{% trans %}Add a user{% endtrans %}</a></li> 19 <li class="tab col s3"><a href="#set5">{% trans %}Add a user{% endtrans %}</a></li>
20 {% endif %}
19 </ul> 21 </ul>
20 </div> 22 </div>
21 23
@@ -175,7 +177,7 @@
175 </form> 177 </form>
176 </div> 178 </div>
177 179
178 180 {% if is_granted('ROLE_SUPER_ADMIN') %}
179 <div id="set5" class="col s12"> 181 <div id="set5" class="col s12">
180 <form action="{{ path('config') }}#set5" method="post" {{ form_enctype(form.new_user) }}> 182 <form action="{{ path('config') }}#set5" method="post" {{ form_enctype(form.new_user) }}>
181 {{ form_errors(form.new_user) }} 183 {{ form_errors(form.new_user) }}
@@ -190,9 +192,17 @@
190 192
191 <div class="row"> 193 <div class="row">
192 <div class="input-field col s12"> 194 <div class="input-field col s12">
193 {{ form_label(form.new_user.password) }} 195 {{ form_label(form.new_user.plainPassword.first) }}
194 {{ form_errors(form.new_user.password) }} 196 {{ form_errors(form.new_user.plainPassword.first) }}
195 {{ form_widget(form.new_user.password) }} 197 {{ form_widget(form.new_user.plainPassword.first) }}
198 </div>
199 </div>
200
201 <div class="row">
202 <div class="input-field col s12">
203 {{ form_label(form.new_user.plainPassword.second) }}
204 {{ form_errors(form.new_user.plainPassword.second) }}
205 {{ form_widget(form.new_user.plainPassword.second) }}
196 </div> 206 </div>
197 </div> 207 </div>
198 208
@@ -211,6 +221,7 @@
211 221
212 </form> 222 </form>
213 </div> 223 </div>
224 {% endif %}
214 </div> 225 </div>
215 226
216 </div> 227 </div>
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 @@
49 {% trans %}Login{% endtrans %} 49 {% trans %}Login{% endtrans %}
50 <i class="mdi-content-send right"></i> 50 <i class="mdi-content-send right"></i>
51 </button> 51 </button>
52 <a href="{{ path('fos_user_registration_register') }}">{% trans %}Register{% endtrans %}</a>
52 </div> 53 </div>
53 </form> 54 </form>
54 </div> 55 </div>
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
258 array( 258 array(
259 array( 259 array(
260 'new_user[username]' => '', 260 'new_user[username]' => '',
261 'new_user[password]' => '', 261 'new_user[plainPassword][first]' => '',
262 'new_user[plainPassword][second]' => '',
262 'new_user[email]' => '', 263 'new_user[email]' => '',
263 ), 264 ),
264 'Please enter a username', 265 'Please enter a username',
@@ -266,7 +267,8 @@ class ConfigControllerTest extends WallabagCoreTestCase
266 array( 267 array(
267 array( 268 array(
268 'new_user[username]' => 'a', 269 'new_user[username]' => 'a',
269 'new_user[password]' => 'mypassword', 270 'new_user[plainPassword][first]' => 'mypassword',
271 'new_user[plainPassword][second]' => 'mypassword',
270 'new_user[email]' => '', 272 'new_user[email]' => '',
271 ), 273 ),
272 'The username is too short', 274 'The username is too short',
@@ -274,7 +276,8 @@ class ConfigControllerTest extends WallabagCoreTestCase
274 array( 276 array(
275 array( 277 array(
276 'new_user[username]' => 'wallace', 278 'new_user[username]' => 'wallace',
277 'new_user[password]' => 'mypassword', 279 'new_user[plainPassword][first]' => 'mypassword',
280 'new_user[plainPassword][second]' => 'mypassword',
278 'new_user[email]' => 'test', 281 'new_user[email]' => 'test',
279 ), 282 ),
280 'The email is not valid', 283 'The email is not valid',
@@ -282,11 +285,21 @@ class ConfigControllerTest extends WallabagCoreTestCase
282 array( 285 array(
283 array( 286 array(
284 'new_user[username]' => 'admin', 287 'new_user[username]' => 'admin',
285 'new_user[password]' => 'wallacewallace', 288 'new_user[plainPassword][first]' => 'wallacewallace',
289 'new_user[plainPassword][second]' => 'wallacewallace',
286 'new_user[email]' => 'wallace@wallace.me', 290 'new_user[email]' => 'wallace@wallace.me',
287 ), 291 ),
288 'The username is already used', 292 'The username is already used',
289 ), 293 ),
294 array(
295 array(
296 'new_user[username]' => 'wallace',
297 'new_user[plainPassword][first]' => 'mypassword1',
298 'new_user[plainPassword][second]' => 'mypassword2',
299 'new_user[email]' => 'wallace@wallace.me',
300 ),
301 'This value is not valid',
302 ),
290 ); 303 );
291 } 304 }
292 305
@@ -325,7 +338,8 @@ class ConfigControllerTest extends WallabagCoreTestCase
325 338
326 $data = array( 339 $data = array(
327 'new_user[username]' => 'wallace', 340 'new_user[username]' => 'wallace',
328 'new_user[password]' => 'wallace1', 341 'new_user[plainPassword][first]' => 'wallace1',
342 'new_user[plainPassword][second]' => 'wallace1',
329 'new_user[email]' => 'wallace@wallace.me', 343 'new_user[email]' => 'wallace@wallace.me',
330 ); 344 );
331 345