From a6b242a1fd6f8900d80354361449f1bf62506ef9 Mon Sep 17 00:00:00 2001 From: Jeremy Benoist Date: Sun, 2 Dec 2018 12:43:05 +0100 Subject: [PATCH] Enable OTP 2FA - Update SchebTwoFactorBundle to version 3 - Enable Google 2fa on the bundle - Disallow ability to use both email and google as 2fa - Update Ocramius Proxy Manager to handle typed function & attributes (from PHP 7) - use `$this->addFlash` shortcut instead of `$this->get('session')->getFlashBag()->add` - update admin to be able to create/reset the 2fa --- .../Version20181202073750.php | 22 ++++ app/Resources/static/themes/_global/index.js | 18 +++ app/Resources/static/themes/material/index.js | 5 + app/config/config.yml | 8 +- app/config/routing.yml | 8 ++ app/config/security.yml | 9 ++ composer.json | 6 +- .../CoreBundle/Command/ShowUserCommand.php | 3 +- .../Controller/ConfigController.php | 35 ++++-- .../Form/Type/UserInformationType.php | 9 +- .../Resources/translations/messages.da.yml | 9 +- .../Resources/translations/messages.de.yml | 9 +- .../Resources/translations/messages.en.yml | 9 +- .../Resources/translations/messages.es.yml | 9 +- .../Resources/translations/messages.fa.yml | 9 +- .../Resources/translations/messages.fr.yml | 8 +- .../Resources/translations/messages.it.yml | 9 +- .../Resources/translations/messages.oc.yml | 9 +- .../Resources/translations/messages.pl.yml | 9 +- .../Resources/translations/messages.pt.yml | 9 +- .../Resources/translations/messages.ro.yml | 9 +- .../Resources/translations/messages.ru.yml | 9 +- .../Resources/translations/messages.th.yml | 9 +- .../Resources/translations/messages.tr.yml | 9 +- .../views/themes/baggy/Config/index.html.twig | 88 ++++++++------ .../themes/material/Config/index.html.twig | 45 ++++--- .../Controller/ManageController.php | 67 +++++++++-- src/Wallabag/UserBundle/Entity/User.php | 94 ++++++++++----- src/Wallabag/UserBundle/Form/UserType.php | 9 +- .../UserBundle/Mailer/AuthCodeMailer.php | 2 +- .../views/Authentication/form.html.twig | 14 ++- .../Resources/views/Manage/edit.html.twig | 17 ++- .../Command/ShowUserCommandTest.php | 3 +- .../Controller/ConfigControllerTest.php | 113 ++++++++++++++++++ .../Controller/SecurityControllerTest.php | 28 +++-- .../UserBundle/Mailer/AuthCodeMailerTest.php | 2 +- 36 files changed, 553 insertions(+), 177 deletions(-) create mode 100644 app/DoctrineMigrations/Version20181202073750.php diff --git a/app/DoctrineMigrations/Version20181202073750.php b/app/DoctrineMigrations/Version20181202073750.php new file mode 100644 index 00000000..a2308b99 --- /dev/null +++ b/app/DoctrineMigrations/Version20181202073750.php @@ -0,0 +1,22 @@ +addSql('ALTER TABLE ' . $this->getTable('user') . ' ADD googleAuthenticatorSecret VARCHAR(191) DEFAULT NULL, CHANGE twoFactorAuthentication emailTwoFactor BOOLEAN NOT NULL, DROP trusted'); + } + + public function down(Schema $schema): void + { + $this->addSql('ALTER TABLE `' . $this->getTable('user') . '` DROP googleAuthenticatorSecret, CHANGE emailtwofactor twoFactorAuthentication BOOLEAN NOT NULL, ADD trusted TEXT DEFAULT NULL'); + } +} diff --git a/app/Resources/static/themes/_global/index.js b/app/Resources/static/themes/_global/index.js index bb3e95b6..9ad96fc0 100644 --- a/app/Resources/static/themes/_global/index.js +++ b/app/Resources/static/themes/_global/index.js @@ -89,4 +89,22 @@ $(document).ready(() => { } }; }); + + // mimic radio button because emailTwoFactor is a boolean + $('#update_user_googleTwoFactor').on('change', () => { + $('#update_user_emailTwoFactor').prop('checked', false); + }); + + $('#update_user_emailTwoFactor').on('change', () => { + $('#update_user_googleTwoFactor').prop('checked', false); + }); + + // same mimic for super admin + $('#user_googleTwoFactor').on('change', () => { + $('#user_emailTwoFactor').prop('checked', false); + }); + + $('#user_emailTwoFactor').on('change', () => { + $('#user_googleTwoFactor').prop('checked', false); + }); }); diff --git a/app/Resources/static/themes/material/index.js b/app/Resources/static/themes/material/index.js index 05794597..2926cad1 100755 --- a/app/Resources/static/themes/material/index.js +++ b/app/Resources/static/themes/material/index.js @@ -50,25 +50,30 @@ $(document).ready(() => { $('#tag_label').focus(); return false; }); + $('#nav-btn-add').on('click', () => { toggleNav('.nav-panel-add', '#entry_url'); return false; }); + const materialAddForm = $('.nav-panel-add'); materialAddForm.on('submit', () => { materialAddForm.addClass('disabled'); $('input#entry_url', materialAddForm).prop('readonly', true).trigger('blur'); }); + $('#nav-btn-search').on('click', () => { toggleNav('.nav-panel-search', '#search_entry_term'); return false; }); + $('.close').on('click', (e) => { $(e.target).parent('.nav-panel-item').hide(100); $('.nav-panel-actions').show(100); $('.nav-panels').css('background', 'transparent'); return false; }); + $(window).scroll(() => { const s = $(window).scrollTop(); const d = $(document).height(); diff --git a/app/config/config.yml b/app/config/config.yml index 4b34af30..908f53b7 100644 --- a/app/config/config.yml +++ b/app/config/config.yml @@ -198,10 +198,14 @@ fos_oauth_server: refresh_token_lifetime: 1209600 scheb_two_factor: - trusted_computer: + trusted_device: enabled: true cookie_name: wllbg_trusted_computer - cookie_lifetime: 2592000 + lifetime: 2592000 + + google: + enabled: "%twofactor_auth%" + template: WallabagUserBundle:Authentication:form.html.twig email: enabled: "%twofactor_auth%" diff --git a/app/config/routing.yml b/app/config/routing.yml index 0bd2d130..a7c0f7e9 100644 --- a/app/config/routing.yml +++ b/app/config/routing.yml @@ -51,3 +51,11 @@ craue_config_settings_modify: fos_js_routing: resource: "@FOSJsRoutingBundle/Resources/config/routing/routing.xml" + +2fa_login: + path: /2fa + defaults: + _controller: "scheb_two_factor.form_controller:form" + +2fa_login_check: + path: /2fa_check diff --git a/app/config/security.yml b/app/config/security.yml index 96489e26..6a21b4e5 100644 --- a/app/config/security.yml +++ b/app/config/security.yml @@ -56,9 +56,17 @@ security: path: /logout target: / + two_factor: + provider: fos_userbundle + auth_form_path: 2fa_login + check_path: 2fa_login_check + access_control: - { path: ^/api/(doc|version|info|user), roles: IS_AUTHENTICATED_ANONYMOUSLY } - { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY } + # force role for logout otherwise when 2fa enable, you won't be able to logout + # https://github.com/scheb/two-factor-bundle/issues/168#issuecomment-430822478 + - { path: ^/logout, roles: [IS_AUTHENTICATED_ANONYMOUSLY, IS_AUTHENTICATED_2FA_IN_PROGRESS] } - { path: ^/register, role: IS_AUTHENTICATED_ANONYMOUSLY } - { path: ^/resetting, role: IS_AUTHENTICATED_ANONYMOUSLY } - { path: /(unread|starred|archive|all).xml$, roles: IS_AUTHENTICATED_ANONYMOUSLY } @@ -67,5 +75,6 @@ security: - { path: ^/share, roles: IS_AUTHENTICATED_ANONYMOUSLY } - { path: ^/settings, roles: ROLE_SUPER_ADMIN } - { path: ^/annotations, roles: ROLE_USER } + - { path: ^/2fa, role: IS_AUTHENTICATED_2FA_IN_PROGRESS } - { path: ^/users, roles: ROLE_SUPER_ADMIN } - { path: ^/, roles: ROLE_USER } diff --git a/composer.json b/composer.json index 21d71b74..771580c6 100644 --- a/composer.json +++ b/composer.json @@ -31,7 +31,7 @@ "issues": "https://github.com/wallabag/wallabag/issues" }, "require": { - "php": ">=7.1.0", + "php": ">=7.1.3", "ext-pcre": "*", "ext-dom": "*", "ext-curl": "*", @@ -70,7 +70,7 @@ "friendsofsymfony/user-bundle": "2.0.*", "friendsofsymfony/oauth-server-bundle": "^1.5", "stof/doctrine-extensions-bundle": "^1.2", - "scheb/two-factor-bundle": "^2.14", + "scheb/two-factor-bundle": "^3.0", "grandt/phpepub": "dev-master", "wallabag/php-mobi": "~1.0", "kphoen/rulerz-bundle": "~0.13", @@ -147,7 +147,7 @@ "config": { "bin-dir": "bin", "platform": { - "php": "7.1" + "php": "7.1.3" } }, "minimum-stability": "dev", diff --git a/src/Wallabag/CoreBundle/Command/ShowUserCommand.php b/src/Wallabag/CoreBundle/Command/ShowUserCommand.php index a0184267..c95efbf3 100644 --- a/src/Wallabag/CoreBundle/Command/ShowUserCommand.php +++ b/src/Wallabag/CoreBundle/Command/ShowUserCommand.php @@ -57,7 +57,8 @@ class ShowUserCommand extends ContainerAwareCommand sprintf('Display name: %s', $user->getName()), sprintf('Creation date: %s', $user->getCreatedAt()->format('Y-m-d H:i:s')), sprintf('Last login: %s', null !== $user->getLastLogin() ? $user->getLastLogin()->format('Y-m-d H:i:s') : 'never'), - sprintf('2FA activated: %s', $user->isTwoFactorAuthentication() ? 'yes' : 'no'), + sprintf('2FA (email) activated: %s', $user->isEmailTwoFactor() ? 'yes' : 'no'), + sprintf('2FA (OTP) activated: %s', $user->isGoogleAuthenticatorEnabled() ? 'yes' : 'no'), ]); } diff --git a/src/Wallabag/CoreBundle/Controller/ConfigController.php b/src/Wallabag/CoreBundle/Controller/ConfigController.php index be6feb7c..5bbe1c74 100644 --- a/src/Wallabag/CoreBundle/Controller/ConfigController.php +++ b/src/Wallabag/CoreBundle/Controller/ConfigController.php @@ -46,7 +46,7 @@ class ConfigController extends Controller $activeTheme = $this->get('liip_theme.active_theme'); $activeTheme->setName($config->getTheme()); - $this->get('session')->getFlashBag()->add( + $this->addFlash( 'notice', 'flashes.config.notice.config_saved' ); @@ -68,7 +68,7 @@ class ConfigController extends Controller $userManager->updateUser($user, true); } - $this->get('session')->getFlashBag()->add('notice', $message); + $this->addFlash('notice', $message); return $this->redirect($this->generateUrl('config') . '#set4'); } @@ -80,10 +80,29 @@ class ConfigController extends Controller ]); $userForm->handleRequest($request); + // `googleTwoFactor` isn't a field within the User entity, we need to define it's value in a different way + if (true === $user->isGoogleAuthenticatorEnabled() && false === $userForm->isSubmitted()) { + $userForm->get('googleTwoFactor')->setData(true); + } + if ($userForm->isSubmitted() && $userForm->isValid()) { + // handle creation / reset of the OTP secret if checkbox changed from the previous state + if (true === $userForm->get('googleTwoFactor')->getData() && false === $user->isGoogleAuthenticatorEnabled()) { + $secret = $this->get('scheb_two_factor.security.google_authenticator')->generateSecret(); + + $user->setGoogleAuthenticatorSecret($secret); + $user->setEmailTwoFactor(false); + + $qrCode = $this->get('scheb_two_factor.security.google_authenticator')->getQRContent($user); + + $this->addFlash('OTPSecret', ['code' => $secret, 'qrCode' => $qrCode]); + } elseif (false === $userForm->get('googleTwoFactor')->getData() && true === $user->isGoogleAuthenticatorEnabled()) { + $user->setGoogleAuthenticatorSecret(null); + } + $userManager->updateUser($user, true); - $this->get('session')->getFlashBag()->add( + $this->addFlash( 'notice', 'flashes.config.notice.user_updated' ); @@ -99,7 +118,7 @@ class ConfigController extends Controller $em->persist($config); $em->flush(); - $this->get('session')->getFlashBag()->add( + $this->addFlash( 'notice', 'flashes.config.notice.rss_updated' ); @@ -131,7 +150,7 @@ class ConfigController extends Controller $em->persist($taggingRule); $em->flush(); - $this->get('session')->getFlashBag()->add( + $this->addFlash( 'notice', 'flashes.config.notice.tagging_rules_updated' ); @@ -178,7 +197,7 @@ class ConfigController extends Controller return new JsonResponse(['token' => $config->getRssToken()]); } - $this->get('session')->getFlashBag()->add( + $this->addFlash( 'notice', 'flashes.config.notice.rss_token_updated' ); @@ -203,7 +222,7 @@ class ConfigController extends Controller $em->remove($rule); $em->flush(); - $this->get('session')->getFlashBag()->add( + $this->addFlash( 'notice', 'flashes.config.notice.tagging_rules_deleted' ); @@ -269,7 +288,7 @@ class ConfigController extends Controller break; } - $this->get('session')->getFlashBag()->add( + $this->addFlash( 'notice', 'flashes.config.notice.' . $type . '_reset' ); diff --git a/src/Wallabag/CoreBundle/Form/Type/UserInformationType.php b/src/Wallabag/CoreBundle/Form/Type/UserInformationType.php index 07c99949..6e4c9154 100644 --- a/src/Wallabag/CoreBundle/Form/Type/UserInformationType.php +++ b/src/Wallabag/CoreBundle/Form/Type/UserInformationType.php @@ -21,9 +21,14 @@ class UserInformationType extends AbstractType ->add('email', EmailType::class, [ 'label' => 'config.form_user.email_label', ]) - ->add('twoFactorAuthentication', CheckboxType::class, [ + ->add('emailTwoFactor', CheckboxType::class, [ 'required' => false, - 'label' => 'config.form_user.twoFactorAuthentication_label', + 'label' => 'config.form_user.emailTwoFactor_label', + ]) + ->add('googleTwoFactor', CheckboxType::class, [ + 'required' => false, + 'label' => 'config.form_user.googleTwoFactor_label', + 'mapped' => false, ]) ->add('save', SubmitType::class, [ 'label' => 'config.form.save', diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.da.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.da.yml index 96679a9c..e62ba6d0 100644 --- a/src/Wallabag/CoreBundle/Resources/translations/messages.da.yml +++ b/src/Wallabag/CoreBundle/Resources/translations/messages.da.yml @@ -99,11 +99,11 @@ config: # all: 'All' # rss_limit: 'Number of items in the feed' form_user: - # two_factor_description: "Enabling two factor authentication means you'll receive an email with a code on every new untrusted connexion" + # two_factor_description: "Enabling two factor authentication means you'll receive an email with a code OR need to use an OTP app (like Google Authenticator) to get a one time code on every new untrusted connection. You can't choose both option." name_label: 'Navn' email_label: 'Emailadresse' - # twoFactorAuthentication_label: 'Two factor authentication' - # help_twoFactorAuthentication: "If you enable 2FA, each time you want to login to wallabag, you'll receive a code by email." + # emailTwoFactor_label: 'Using email (receive a code by email)' + # googleTwoFactor_label: 'Using an OTP app (open the app, like Google Authenticator, to get a one time code)' delete: # title: Delete my account (a.k.a danger zone) # description: If you remove your account, ALL your articles, ALL your tags, ALL your annotations and your account will be PERMANENTLY removed (it can't be UNDONE). You'll then be logged out. @@ -533,7 +533,8 @@ user: email_label: 'Emailadresse' # enabled_label: 'Enabled' # last_login_label: 'Last login' - # twofactor_label: Two factor authentication + # twofactor_email_label: Two factor authentication by email + # twofactor_google_label: Two factor authentication by Google # save: Save # delete: Delete # delete_confirm: Are you sure? diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.de.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.de.yml index c56e87b5..f2d0408f 100644 --- a/src/Wallabag/CoreBundle/Resources/translations/messages.de.yml +++ b/src/Wallabag/CoreBundle/Resources/translations/messages.de.yml @@ -99,11 +99,11 @@ config: all: 'Alle' rss_limit: 'Anzahl der Einträge pro Feed' form_user: - two_factor_description: "Wenn du die Zwei-Faktor-Authentifizierung aktivierst, erhältst du eine E-Mail mit einem Code bei jeder nicht vertrauenswürdigen Verbindung" + # two_factor_description: "Enabling two factor authentication means you'll receive an email with a code OR need to use an OTP app (like Google Authenticator) to get a one time code on every new untrusted connection. You can't choose both option." name_label: 'Name' email_label: 'E-Mail-Adresse' - twoFactorAuthentication_label: 'Zwei-Faktor-Authentifizierung' - help_twoFactorAuthentication: "Wenn du 2FA aktivierst, wirst du bei jedem Login einen Code per E-Mail bekommen." + # emailTwoFactor_label: 'Using email (receive a code by email)' + # googleTwoFactor_label: 'Using an OTP app (open the app, like Google Authenticator, to get a one time code)' delete: title: 'Lösche mein Konto (a.k.a Gefahrenzone)' description: 'Wenn du dein Konto löschst, werden ALL deine Artikel, ALL deine Tags, ALL deine Anmerkungen und dein Konto dauerhaft gelöscht (kann NICHT RÜCKGÄNGIG gemacht werden). Du wirst anschließend ausgeloggt.' @@ -533,7 +533,8 @@ user: email_label: 'E-Mail-Adresse' enabled_label: 'Aktiviert' last_login_label: 'Letzter Login' - twofactor_label: 'Zwei-Faktor-Authentifizierung' + # twofactor_email_label: Two factor authentication by email + # twofactor_google_label: Two factor authentication by Google save: 'Speichern' delete: 'Löschen' delete_confirm: 'Bist du sicher?' diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.en.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.en.yml index d57cea0f..859acdc0 100644 --- a/src/Wallabag/CoreBundle/Resources/translations/messages.en.yml +++ b/src/Wallabag/CoreBundle/Resources/translations/messages.en.yml @@ -99,11 +99,11 @@ config: all: 'All' rss_limit: 'Number of items in the feed' form_user: - two_factor_description: "Enabling two factor authentication means you'll receive an email with a code on every new untrusted connection." + two_factor_description: "Enabling two factor authentication means you'll receive an email with a code OR need to use an OTP app (like Google Authenticator) to get a one time code on every new untrusted connection. You can't choose both option." name_label: 'Name' email_label: 'Email' - twoFactorAuthentication_label: 'Two factor authentication' - help_twoFactorAuthentication: "If you enable 2FA, each time you want to login to wallabag, you'll receive a code by email." + emailTwoFactor_label: 'Using email (receive a code by email)' + googleTwoFactor_label: 'Using an OTP app (open the app, like Google Authenticator, to get a one time code)' delete: title: Delete my account (a.k.a danger zone) description: If you remove your account, ALL your articles, ALL your tags, ALL your annotations and your account will be PERMANENTLY removed (it can't be UNDONE). You'll then be logged out. @@ -533,7 +533,8 @@ user: email_label: 'Email' enabled_label: 'Enabled' last_login_label: 'Last login' - twofactor_label: Two factor authentication + twofactor_email_label: Two factor authentication by email + twofactor_google_label: Two factor authentication by Google save: Save delete: Delete delete_confirm: Are you sure? diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.es.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.es.yml index e1c4e221..3c3cbed4 100644 --- a/src/Wallabag/CoreBundle/Resources/translations/messages.es.yml +++ b/src/Wallabag/CoreBundle/Resources/translations/messages.es.yml @@ -99,11 +99,11 @@ config: # all: 'All' rss_limit: 'Límite de artículos en feed RSS' form_user: - two_factor_description: "Con la autenticación en dos pasos recibirá código por e-mail en cada nueva conexión que no sea de confianza." + # two_factor_description: "Enabling two factor authentication means you'll receive an email with a code OR need to use an OTP app (like Google Authenticator) to get a one time code on every new untrusted connection. You can't choose both option." name_label: 'Nombre' email_label: 'Dirección de e-mail' - twoFactorAuthentication_label: 'Autenticación en dos pasos' - help_twoFactorAuthentication: "Si activas la autenticación en dos pasos, cada vez que quieras iniciar sesión en wallabag recibirás un código por e-mail." + # emailTwoFactor_label: 'Using email (receive a code by email)' + # googleTwoFactor_label: 'Using an OTP app (open the app, like Google Authenticator, to get a one time code)' delete: title: Eliminar mi cuenta (Zona peligrosa) description: Si eliminas tu cuenta, TODOS tus artículos, TODAS tus etiquetas, TODAS tus anotaciones y tu cuenta serán eliminadas de forma PERMANENTE (no se puede deshacer). Después serás desconectado. @@ -533,7 +533,8 @@ user: email_label: 'E-mail' enabled_label: 'Activado' last_login_label: 'Último inicio de sesión' - twofactor_label: Autenticación en dos pasos + # twofactor_email_label: Two factor authentication by email + # twofactor_google_label: Two factor authentication by Google save: Guardar delete: Eliminar delete_confirm: ¿Estás seguro? diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.fa.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.fa.yml index 2ede433e..ca25b390 100644 --- a/src/Wallabag/CoreBundle/Resources/translations/messages.fa.yml +++ b/src/Wallabag/CoreBundle/Resources/translations/messages.fa.yml @@ -99,11 +99,11 @@ config: # all: 'All' rss_limit: 'محدودیت آر-اس-اس' form_user: - two_factor_description: "با فعال‌کردن تأیید ۲مرحله‌ای هر بار که اتصال تأییدنشده‌ای برقرار شد، به شما یک کد از راه ایمیل فرستاده می‌شود" + # two_factor_description: "Enabling two factor authentication means you'll receive an email with a code OR need to use an OTP app (like Google Authenticator) to get a one time code on every new untrusted connection. You can't choose both option." name_label: 'نام' email_label: 'نشانی ایمیل' - twoFactorAuthentication_label: 'تأیید ۲مرحله‌ای' - # help_twoFactorAuthentication: "If you enable 2FA, each time you want to login to wallabag, you'll receive a code by email." + # emailTwoFactor_label: 'Using email (receive a code by email)' + # googleTwoFactor_label: 'Using an OTP app (open the app, like Google Authenticator, to get a one time code)' delete: # title: Delete my account (a.k.a danger zone) # description: If you remove your account, ALL your articles, ALL your tags, ALL your annotations and your account will be PERMANENTLY removed (it can't be UNDONE). You'll then be logged out. @@ -533,7 +533,8 @@ user: email_label: 'نشانی ایمیل' # enabled_label: 'Enabled' # last_login_label: 'Last login' - # twofactor_label: Two factor authentication + # twofactor_email_label: Two factor authentication by email + # twofactor_google_label: Two factor authentication by Google # save: Save # delete: Delete # delete_confirm: Are you sure? diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.fr.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.fr.yml index d69ae280..b809ca32 100644 --- a/src/Wallabag/CoreBundle/Resources/translations/messages.fr.yml +++ b/src/Wallabag/CoreBundle/Resources/translations/messages.fr.yml @@ -99,11 +99,11 @@ config: all: "Tous" rss_limit: "Nombre d’articles dans le flux" form_user: - two_factor_description: "Activer l’authentification double-facteur veut dire que vous allez recevoir un code par courriel à chaque nouvelle connexion non approuvée." + two_factor_description: "Activer l’authentification double-facteur veut dire que vous allez recevoir un code par courriel OU que vous devriez utiliser une application de mot de passe à usage unique (comme Google Authenticator) pour obtenir un code temporaire à chaque nouvelle connexion non approuvée. Vous ne pouvez pas choisir les deux options." name_label: "Nom" email_label: "Adresse courriel" - twoFactorAuthentication_label: "Double authentification" - help_twoFactorAuthentication: "Si vous activez 2FA, à chaque tentative de connexion à wallabag, vous recevrez un code par email." + emailTwoFactor_label: 'En utlisant l’email (recevez un code par email)' + googleTwoFactor_label: 'En utilisant une application de mot de passe à usage unique (ouvrez l’app, comme Google Authenticator, pour obtenir un mot de passe à usage unique)' delete: title: "Supprimer mon compte (attention danger !)" description: "Si vous confirmez la suppression de votre compte, TOUS les articles, TOUS les tags, TOUTES les annotations et votre compte seront DÉFINITIVEMENT supprimé (c’est IRRÉVERSIBLE). Vous serez ensuite déconnecté." @@ -534,6 +534,8 @@ user: enabled_label: "Activé" last_login_label: "Dernière connexion" twofactor_label: "Double authentification" + twofactor_email_label: Double authentification par email + twofactor_google_label: Double authentification par Google save: "Sauvegarder" delete: "Supprimer" delete_confirm: "Êtes-vous sûr ?" diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.it.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.it.yml index f16ffb6b..7279dba1 100644 --- a/src/Wallabag/CoreBundle/Resources/translations/messages.it.yml +++ b/src/Wallabag/CoreBundle/Resources/translations/messages.it.yml @@ -99,11 +99,11 @@ config: # all: 'All' rss_limit: 'Numero di elementi nel feed' form_user: - two_factor_description: "Abilitando l'autenticazione a due fattori riceverai una e-mail con un codice per ogni nuova connesione non verificata" + # two_factor_description: "Enabling two factor authentication means you'll receive an email with a code OR need to use an OTP app (like Google Authenticator) to get a one time code on every new untrusted connection. You can't choose both option." name_label: 'Nome' email_label: 'E-mail' - twoFactorAuthentication_label: 'Autenticazione a due fattori' - help_twoFactorAuthentication: "Se abiliti l'autenticazione a due fattori, ogni volta che vorrai connetterti a wallabag, riceverai un codice via E-mail." + # emailTwoFactor_label: 'Using email (receive a code by email)' + # googleTwoFactor_label: 'Using an OTP app (open the app, like Google Authenticator, to get a one time code)' delete: title: Cancella il mio account (zona pericolosa) description: Rimuovendo il tuo account, TUTTI i tuoi articoli, TUTTE le tue etichette, TUTTE le tue annotazioni ed il tuo account verranno rimossi PERMANENTEMENTE (impossibile da ANNULLARE). Verrai poi disconnesso. @@ -533,7 +533,8 @@ user: email_label: 'E-mail' enabled_label: 'Abilitato' last_login_label: 'Ultima connessione' - twofactor_label: Autenticazione a due fattori + # twofactor_email_label: Two factor authentication by email + # twofactor_google_label: Two factor authentication by Google save: Salva delete: Cancella delete_confirm: Sei sicuro? diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.oc.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.oc.yml index b568fbc5..f262ba7b 100644 --- a/src/Wallabag/CoreBundle/Resources/translations/messages.oc.yml +++ b/src/Wallabag/CoreBundle/Resources/translations/messages.oc.yml @@ -99,11 +99,11 @@ config: all: 'Totes' rss_limit: "Nombre d'articles dins un flux RSS" form_user: - two_factor_description: "Activar l'autentificacion en dos temps vòl dire que recebretz un còdi per corrièl per cada novèla connexion pas aprovada." + # two_factor_description: "Enabling two factor authentication means you'll receive an email with a code OR need to use an OTP app (like Google Authenticator) to get a one time code on every new untrusted connection. You can't choose both option." name_label: 'Nom' email_label: 'Adreça de corrièl' - twoFactorAuthentication_label: 'Dobla autentificacion' - help_twoFactorAuthentication: "S'avètz activat l'autentificacion en dos temps, cada còp que volètz vos connectar a wallabag, recebretz un còdi per corrièl." + # emailTwoFactor_label: 'Using email (receive a code by email)' + # googleTwoFactor_label: 'Using an OTP app (open the app, like Google Authenticator, to get a one time code)' delete: title: Suprimir mon compte (Mèfi zòna perilhosa) description: Se confirmatz la supression de vòstre compte, TOTES vòstres articles, TOTAS vòstras etiquetas, TOTAS vòstras anotacions e vòstre compte seràn suprimits per totjorn. E aquò es IRREVERSIBLE. Puèi seretz desconnectat. @@ -533,7 +533,8 @@ user: email_label: 'Adreça de corrièl' enabled_label: 'Actiu' last_login_label: 'Darrièra connexion' - twofactor_label: 'Autentificacion doble-factor' + # twofactor_email_label: Two factor authentication by email + # twofactor_google_label: Two factor authentication by Google save: 'Enregistrar' delete: 'Suprimir' delete_confirm: 'Sètz segur ?' diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.pl.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.pl.yml index b58e14f4..99c2183e 100644 --- a/src/Wallabag/CoreBundle/Resources/translations/messages.pl.yml +++ b/src/Wallabag/CoreBundle/Resources/translations/messages.pl.yml @@ -99,11 +99,11 @@ config: all: 'Wszystkie' rss_limit: 'Link do RSS' form_user: - two_factor_description: "Włączenie autoryzacji dwuetapowej oznacza, że będziesz otrzymywał maile z kodem przy każdym nowym, niezaufanym połączeniu" + two_factor_description: "Enabling two factor authentication means you'll receive an email with a code OR need to use an OTP app (like Google Authenticator) to get a one time code on every new untrusted connection. You can't choose both option." name_label: 'Nazwa' email_label: 'Adres email' - twoFactorAuthentication_label: 'Autoryzacja dwuetapowa' - help_twoFactorAuthentication: "Jeżeli włączysz autoryzację dwuetapową. Za każdym razem, kiedy będziesz chciał się zalogować, dostaniesz kod na swój e-mail." + # emailTwoFactor_label: 'Using email (receive a code by email)' + # googleTwoFactor_label: 'Using an OTP app (open the app, like Google Authenticator, to get a one time code)' delete: title: Usuń moje konto (niebezpieczna strefa !) description: Jeżeli usuniesz swoje konto, wszystkie twoje artykuły, tagi, adnotacje, oraz konto zostaną trwale usunięte (operacja jest NIEODWRACALNA). Następnie zostaniesz wylogowany. @@ -533,7 +533,8 @@ user: email_label: 'Adres email' enabled_label: 'Włączony' last_login_label: 'Ostatnie logowanie' - twofactor_label: Autoryzacja dwuetapowa + # twofactor_email_label: Two factor authentication by email + # twofactor_google_label: Two factor authentication by Google save: Zapisz delete: Usuń delete_confirm: Jesteś pewien? diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.pt.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.pt.yml index add28bf7..806c2d78 100644 --- a/src/Wallabag/CoreBundle/Resources/translations/messages.pt.yml +++ b/src/Wallabag/CoreBundle/Resources/translations/messages.pt.yml @@ -99,11 +99,11 @@ config: # all: 'All' rss_limit: 'Número de itens no feed' form_user: - two_factor_description: 'Habilitar autenticação de dois passos significa que você receberá um e-mail com um código a cada nova conexão desconhecida.' + # two_factor_description: "Enabling two factor authentication means you'll receive an email with a code OR need to use an OTP app (like Google Authenticator) to get a one time code on every new untrusted connection. You can't choose both option." name_label: 'Nome' email_label: 'E-mail' - twoFactorAuthentication_label: 'Autenticação de dois passos' - # help_twoFactorAuthentication: "If you enable 2FA, each time you want to login to wallabag, you'll receive a code by email." + # emailTwoFactor_label: 'Using email (receive a code by email)' + # googleTwoFactor_label: 'Using an OTP app (open the app, like Google Authenticator, to get a one time code)' delete: # title: Delete my account (a.k.a danger zone) # description: If you remove your account, ALL your articles, ALL your tags, ALL your annotations and your account will be PERMANENTLY removed (it can't be UNDONE). You'll then be logged out. @@ -533,7 +533,8 @@ user: email_label: 'E-mail' enabled_label: 'Habilitado' last_login_label: 'Último login' - twofactor_label: 'Autenticação de dois passos' + # twofactor_email_label: Two factor authentication by email + # twofactor_google_label: Two factor authentication by Google save: 'Salvar' delete: 'Apagar' delete_confirm: 'Tem certeza?' diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.ro.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.ro.yml index 6a38c4a1..ed75ed6e 100644 --- a/src/Wallabag/CoreBundle/Resources/translations/messages.ro.yml +++ b/src/Wallabag/CoreBundle/Resources/translations/messages.ro.yml @@ -99,11 +99,11 @@ config: # all: 'All' rss_limit: 'Limită RSS' form_user: - # two_factor_description: "Enabling two factor authentication means you'll receive an email with a code on every new untrusted connexion" + # two_factor_description: "Enabling two factor authentication means you'll receive an email with a code OR need to use an OTP app (like Google Authenticator) to get a one time code on every new untrusted connection. You can't choose both option." name_label: 'Nume' email_label: 'E-mail' - # twoFactorAuthentication_label: 'Two factor authentication' - # help_twoFactorAuthentication: "If you enable 2FA, each time you want to login to wallabag, you'll receive a code by email." + # emailTwoFactor_label: 'Using email (receive a code by email)' + # googleTwoFactor_label: 'Using an OTP app (open the app, like Google Authenticator, to get a one time code)' delete: # title: Delete my account (a.k.a danger zone) # description: If you remove your account, ALL your articles, ALL your tags, ALL your annotations and your account will be PERMANENTLY removed (it can't be UNDONE). You'll then be logged out. @@ -533,7 +533,8 @@ user: email_label: 'E-mail' # enabled_label: 'Enabled' # last_login_label: 'Last login' - # twofactor_label: Two factor authentication + # twofactor_email_label: Two factor authentication by email + # twofactor_google_label: Two factor authentication by Google # save: Save # delete: Delete # delete_confirm: Are you sure? diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.ru.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.ru.yml index 1b7ac38a..1c6e6771 100644 --- a/src/Wallabag/CoreBundle/Resources/translations/messages.ru.yml +++ b/src/Wallabag/CoreBundle/Resources/translations/messages.ru.yml @@ -96,11 +96,11 @@ config: archive: 'архивные' rss_limit: 'Количество записей в фиде' form_user: - two_factor_description: "Включить двухфакторную аутентификацию, Вы получите сообщение на указанный email с кодом, при каждом новом непроверенном подключении." + # two_factor_description: "Enabling two factor authentication means you'll receive an email with a code OR need to use an OTP app (like Google Authenticator) to get a one time code on every new untrusted connection. You can't choose both option." name_label: 'Имя' email_label: 'Email' - twoFactorAuthentication_label: 'Двухфакторная аутентификация' - help_twoFactorAuthentication: "Если Вы включите двухфакторную аутентификацию, то Вы будете получать код на указанный ранее email, каждый раз при входе в wallabag." + # emailTwoFactor_label: 'Using email (receive a code by email)' + # googleTwoFactor_label: 'Using an OTP app (open the app, like Google Authenticator, to get a one time code)' delete: title: "Удалить мой аккаунт (или опасная зона)" description: "Если Вы удалите ваш аккаунт, ВСЕ ваши записи, теги и другие данные, будут БЕЗВОЗВРАТНО удалены (операция не может быть отменена после). Затем Вы выйдете из системы." @@ -521,7 +521,8 @@ user: email_label: 'Email' enabled_label: 'Включить' last_login_label: 'Последний вход' - twofactor_label: "Двухфакторная аутентификация" + # twofactor_email_label: Two factor authentication by email + # twofactor_google_label: Two factor authentication by Google save: "Сохранить" delete: "Удалить" delete_confirm: "Вы уверены?" diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.th.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.th.yml index fe1b35be..af798943 100644 --- a/src/Wallabag/CoreBundle/Resources/translations/messages.th.yml +++ b/src/Wallabag/CoreBundle/Resources/translations/messages.th.yml @@ -99,11 +99,11 @@ config: all: 'ทั้งหมด' rss_limit: 'จำนวนไอเทมที่เก็บ' form_user: - two_factor_description: "การเปิดใช้งาน two factor authentication คือคุณจะต้องได้รับอีเมลกับ code ที่ยังไม่ตรวจสอบในการเชื่อมต่อ" + # two_factor_description: "Enabling two factor authentication means you'll receive an email with a code OR need to use an OTP app (like Google Authenticator) to get a one time code on every new untrusted connection. You can't choose both option." name_label: 'ชื่อ' email_label: 'อีเมล' - twoFactorAuthentication_label: 'Two factor authentication' - help_twoFactorAuthentication: "ถ้าคุณเปิด 2FA, ในแต่ละช่วงเวลาที่คุณต้องการลงชื่อเข้าใช wallabag, คุณจะต้องได้รับ code จากอีเมล" + # emailTwoFactor_label: 'Using email (receive a code by email)' + # googleTwoFactor_label: 'Using an OTP app (open the app, like Google Authenticator, to get a one time code)' delete: title: ลบบัญชีของฉัน (โซนที่เป็นภัย!) description: ถ้าคุณลบบัญชีของคุณIf , รายการทั้งหมดของคุณ, แท็กทั้งหมดของคุณ, หมายเหตุทั้งหมดของคุณและบัญชีของคุณจะถูกลบอย่างถาวร (มันไม่สามารถยกเลิกได้) คุณจะต้องลงชื่อออก @@ -531,7 +531,8 @@ user: email_label: 'อีเมล' enabled_label: 'เปิดใช้งาน' last_login_label: 'ลงชื้อเข้าใช้ครั้งสุดท้าย' - twofactor_label: Two factor authentication + # twofactor_email_label: Two factor authentication by email + # twofactor_google_label: Two factor authentication by Google save: บันทึก delete: ลบ delete_confirm: ตุณแน่ใจหรือไม่? diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.tr.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.tr.yml index 638714e4..352a2cc4 100644 --- a/src/Wallabag/CoreBundle/Resources/translations/messages.tr.yml +++ b/src/Wallabag/CoreBundle/Resources/translations/messages.tr.yml @@ -99,11 +99,11 @@ config: # all: 'All' rss_limit: 'RSS içeriğinden talep edilecek makale limiti' form_user: - two_factor_description: "İki adımlı doğrulamayı aktifleştirdiğinizde, her yeni güvenilmeyen bağlantılarda size e-posta ile bir kod alacaksınız." + # two_factor_description: "Enabling two factor authentication means you'll receive an email with a code OR need to use an OTP app (like Google Authenticator) to get a one time code on every new untrusted connection. You can't choose both option." name_label: 'İsim' email_label: 'E-posta' - twoFactorAuthentication_label: 'İki adımlı doğrulama' - # help_twoFactorAuthentication: "If you enable 2FA, each time you want to login to wallabag, you'll receive a code by email." + # emailTwoFactor_label: 'Using email (receive a code by email)' + # googleTwoFactor_label: 'Using an OTP app (open the app, like Google Authenticator, to get a one time code)' delete: # title: Delete my account (a.k.a danger zone) # description: If you remove your account, ALL your articles, ALL your tags, ALL your annotations and your account will be PERMANENTLY removed (it can't be UNDONE). You'll then be logged out. @@ -531,7 +531,8 @@ user: email_label: 'E-posta' # enabled_label: 'Enabled' # last_login_label: 'Last login' - # twofactor_label: Two factor authentication + # twofactor_email_label: Two factor authentication by email + # twofactor_google_label: Two factor authentication by Google # save: Save # delete: Delete # delete_confirm: Are you sure? 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 bcc57dac..5c4e44dd 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 @@ -176,43 +176,36 @@
- {{ form_label(form.user.twoFactorAuthentication) }} - {{ form_errors(form.user.twoFactorAuthentication) }} - {{ form_widget(form.user.twoFactorAuthentication) }} + {{ form_label(form.user.emailTwoFactor) }} + {{ form_errors(form.user.emailTwoFactor) }} + {{ form_widget(form.user.emailTwoFactor) }}
- - live_help - +
+
+ {{ form_label(form.user.googleTwoFactor) }} + {{ form_widget(form.user.googleTwoFactor) }} + {{ form_errors(form.user.googleTwoFactor) }} +
+ {% for OTPSecret in app.session.flashbag.get('OTPSecret') %} +
+ You just enabled the OTP two factor authentication, open your OTP app and use that code to get a one time password. +
+ That code will disapear after a page reload. +

+ {{ OTPSecret.code }} +

+ Or you can scan that QR Code with your app: +
+ + + +
+ {% endfor %}
{% endif %} -

{{ 'config.reset.title'|trans }}

-
-

{{ 'config.reset.description'|trans }}

- -
- {{ form_widget(form.user._token) }} {{ form_widget(form.user.save) }} @@ -277,7 +270,7 @@ {% endfor %} - {{ form_start(form.new_tagging_rule) }} + {{ form_start(form.new_tagging_rule) }} {{ form_errors(form.new_tagging_rule) }}
@@ -382,4 +375,31 @@ + +

{{ 'config.reset.title'|trans }}

+
+

{{ 'config.reset.description'|trans }}

+ +
{% 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 35800989..887d154f 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 @@ -112,8 +112,7 @@ @@ -198,22 +197,38 @@ {% if twofactor_auth %} -
-
+
{{ 'config.form_user.two_factor_description'|trans }} -
- - {{ form_widget(form.user.twoFactorAuthentication) }} - {{ form_label(form.user.twoFactorAuthentication) }} - {{ form_errors(form.user.twoFactorAuthentication) }} -
-
- - live_help - +
+ {{ form_widget(form.user.emailTwoFactor) }} + {{ form_label(form.user.emailTwoFactor) }} + {{ form_errors(form.user.emailTwoFactor) }} +
+
+ {{ form_widget(form.user.googleTwoFactor) }} + {{ form_label(form.user.googleTwoFactor) }} + {{ form_errors(form.user.googleTwoFactor) }} +
-
+ + {% for OTPSecret in app.session.flashbag.get('OTPSecret') %} +
+ You just enabled the OTP two factor authentication, open your OTP app and use that code to get a one time password. +
+ That code will disapear after a page reload. +

+ {{ OTPSecret.code }} +

+ Or you can scan that QR Code with your app: +
+ + + +
+ {% endfor %} {% endif %} {{ form_widget(form.user.save, {'attr': {'class': 'btn waves-effect waves-light'}}) }} diff --git a/src/Wallabag/UserBundle/Controller/ManageController.php b/src/Wallabag/UserBundle/Controller/ManageController.php index a9746fb4..08ed25dd 100644 --- a/src/Wallabag/UserBundle/Controller/ManageController.php +++ b/src/Wallabag/UserBundle/Controller/ManageController.php @@ -8,6 +8,7 @@ use Pagerfanta\Adapter\DoctrineORMAdapter; use Pagerfanta\Exception\OutOfRangeCurrentPageException; use Pagerfanta\Pagerfanta; use Symfony\Bundle\FrameworkBundle\Controller\Controller; +use Symfony\Component\Form\FormInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\Routing\Annotation\Route; use Wallabag\UserBundle\Entity\User; @@ -31,10 +32,10 @@ class ManageController extends Controller // enable created user by default $user->setEnabled(true); - $form = $this->createForm('Wallabag\UserBundle\Form\NewUserType', $user); - $form->handleRequest($request); + $form = $this->createEditForm('NewUserType', $user, $request); if ($form->isSubmitted() && $form->isValid()) { + $user = $this->handleOtp($form, $user); $userManager->updateUser($user); // dispatch a created event so the associated config will be created @@ -62,14 +63,14 @@ class ManageController extends Controller */ public function editAction(Request $request, User $user) { + $userManager = $this->container->get('fos_user.user_manager'); + $deleteForm = $this->createDeleteForm($user); - $editForm = $this->createForm('Wallabag\UserBundle\Form\UserType', $user); - $editForm->handleRequest($request); + $form = $this->createEditForm('UserType', $user, $request); - if ($editForm->isSubmitted() && $editForm->isValid()) { - $em = $this->getDoctrine()->getManager(); - $em->persist($user); - $em->flush(); + if ($form->isSubmitted() && $form->isValid()) { + $user = $this->handleOtp($form, $user); + $userManager->updateUser($user); $this->get('session')->getFlashBag()->add( 'notice', @@ -81,7 +82,7 @@ class ManageController extends Controller return $this->render('WallabagUserBundle:Manage:edit.html.twig', [ 'user' => $user, - 'edit_form' => $editForm->createView(), + 'edit_form' => $form->createView(), 'delete_form' => $deleteForm->createView(), 'twofactor_auth' => $this->getParameter('twofactor_auth'), ]); @@ -157,7 +158,7 @@ class ManageController extends Controller } /** - * Creates a form to delete a User entity. + * Create a form to delete a User entity. * * @param User $user The User entity * @@ -171,4 +172,50 @@ class ManageController extends Controller ->getForm() ; } + + /** + * Create a form to create or edit a User entity. + * + * @param string $type Might be NewUserType or UserType + * @param User $user The new / edit user + * @param Request $request The request + * + * @return FormInterface + */ + private function createEditForm($type, User $user, Request $request) + { + $form = $this->createForm('Wallabag\UserBundle\Form\\' . $type, $user); + $form->handleRequest($request); + + // `googleTwoFactor` isn't a field within the User entity, we need to define it's value in a different way + if (true === $user->isGoogleAuthenticatorEnabled() && false === $form->isSubmitted()) { + $form->get('googleTwoFactor')->setData(true); + } + + return $form; + } + + /** + * Handle OTP update, taking care to only have one 2fa enable at a time. + * + * @see ConfigController + * + * @param FormInterface $form + * @param User $user + * + * @return User + */ + private function handleOtp(FormInterface $form, User $user) + { + if (true === $form->get('googleTwoFactor')->getData() && false === $user->isGoogleAuthenticatorEnabled()) { + $user->setGoogleAuthenticatorSecret($this->get('scheb_two_factor.security.google_authenticator')->generateSecret()); + $user->setEmailTwoFactor(false); + + return $user; + } + + $user->setGoogleAuthenticatorSecret(null); + + return $user; + } } diff --git a/src/Wallabag/UserBundle/Entity/User.php b/src/Wallabag/UserBundle/Entity/User.php index 48446e3c..6e305719 100644 --- a/src/Wallabag/UserBundle/Entity/User.php +++ b/src/Wallabag/UserBundle/Entity/User.php @@ -8,8 +8,8 @@ use FOS\UserBundle\Model\User as BaseUser; use JMS\Serializer\Annotation\Accessor; use JMS\Serializer\Annotation\Groups; use JMS\Serializer\Annotation\XmlRoot; -use Scheb\TwoFactorBundle\Model\Email\TwoFactorInterface; -use Scheb\TwoFactorBundle\Model\TrustedComputerInterface; +use Scheb\TwoFactorBundle\Model\Email\TwoFactorInterface as EmailTwoFactorInterface; +use Scheb\TwoFactorBundle\Model\Google\TwoFactorInterface as GoogleTwoFactorInterface; use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; use Symfony\Component\Security\Core\User\UserInterface; use Wallabag\ApiBundle\Entity\Client; @@ -28,7 +28,7 @@ use Wallabag\CoreBundle\Helper\EntityTimestampsTrait; * @UniqueEntity("email") * @UniqueEntity("username") */ -class User extends BaseUser implements TwoFactorInterface, TrustedComputerInterface +class User extends BaseUser implements EmailTwoFactorInterface, GoogleTwoFactorInterface { use EntityTimestampsTrait; @@ -123,16 +123,16 @@ class User extends BaseUser implements TwoFactorInterface, TrustedComputerInterf private $authCode; /** - * @var bool - * - * @ORM\Column(type="boolean") + * @ORM\Column(name="googleAuthenticatorSecret", type="string", nullable=true) */ - private $twoFactorAuthentication = false; + private $googleAuthenticatorSecret; /** - * @ORM\Column(type="json_array", nullable=true) + * @var bool + * + * @ORM\Column(type="boolean") */ - private $trusted; + private $emailTwoFactor = false; public function __construct() { @@ -233,49 +233,89 @@ class User extends BaseUser implements TwoFactorInterface, TrustedComputerInterf /** * @return bool */ - public function isTwoFactorAuthentication() + public function isEmailTwoFactor() + { + return $this->emailTwoFactor; + } + + /** + * @param bool $emailTwoFactor + */ + public function setEmailTwoFactor($emailTwoFactor) { - return $this->twoFactorAuthentication; + $this->emailTwoFactor = $emailTwoFactor; } /** - * @param bool $twoFactorAuthentication + * Used in the user config form to be "like" the email option. */ - public function setTwoFactorAuthentication($twoFactorAuthentication) + public function isGoogleTwoFactor() { - $this->twoFactorAuthentication = $twoFactorAuthentication; + return $this->isGoogleAuthenticatorEnabled(); } - public function isEmailAuthEnabled() + /** + * {@inheritdoc} + */ + public function isEmailAuthEnabled(): bool { - return $this->twoFactorAuthentication; + return $this->emailTwoFactor; } - public function getEmailAuthCode() + /** + * {@inheritdoc} + */ + public function getEmailAuthCode(): string { return $this->authCode; } - public function setEmailAuthCode($authCode) + /** + * {@inheritdoc} + */ + public function setEmailAuthCode(string $authCode): void { $this->authCode = $authCode; } - public function addTrustedComputer($token, \DateTime $validUntil) + /** + * {@inheritdoc} + */ + public function getEmailAuthRecipient(): string { - $this->trusted[$token] = $validUntil->format('r'); + return $this->email; } - public function isTrustedComputer($token) + /** + * {@inheritdoc} + */ + public function isGoogleAuthenticatorEnabled(): bool { - if (isset($this->trusted[$token])) { - $now = new \DateTime(); - $validUntil = new \DateTime($this->trusted[$token]); + return $this->googleAuthenticatorSecret ? true : false; + } - return $now < $validUntil; - } + /** + * {@inheritdoc} + */ + public function getGoogleAuthenticatorUsername(): string + { + return $this->username; + } - return false; + /** + * {@inheritdoc} + */ + public function getGoogleAuthenticatorSecret(): string + { + return $this->googleAuthenticatorSecret; + } + + /** + * {@inheritdoc} + */ + public function setGoogleAuthenticatorSecret(?string $googleAuthenticatorSecret): void + { + $this->googleAuthenticatorSecret = $googleAuthenticatorSecret; } /** diff --git a/src/Wallabag/UserBundle/Form/UserType.php b/src/Wallabag/UserBundle/Form/UserType.php index 56fea640..026db9a2 100644 --- a/src/Wallabag/UserBundle/Form/UserType.php +++ b/src/Wallabag/UserBundle/Form/UserType.php @@ -35,9 +35,14 @@ class UserType extends AbstractType 'required' => false, 'label' => 'user.form.enabled_label', ]) - ->add('twoFactorAuthentication', CheckboxType::class, [ + ->add('emailTwoFactor', CheckboxType::class, [ 'required' => false, - 'label' => 'user.form.twofactor_label', + 'label' => 'user.form.twofactor_email_label', + ]) + ->add('googleTwoFactor', CheckboxType::class, [ + 'required' => false, + 'label' => 'user.form.twofactor_google_label', + 'mapped' => false, ]) ->add('save', SubmitType::class, [ 'label' => 'user.form.save', diff --git a/src/Wallabag/UserBundle/Mailer/AuthCodeMailer.php b/src/Wallabag/UserBundle/Mailer/AuthCodeMailer.php index aed805c9..e8e29aa9 100644 --- a/src/Wallabag/UserBundle/Mailer/AuthCodeMailer.php +++ b/src/Wallabag/UserBundle/Mailer/AuthCodeMailer.php @@ -78,7 +78,7 @@ class AuthCodeMailer implements AuthCodeMailerInterface * * @param TwoFactorInterface $user */ - public function sendAuthCode(TwoFactorInterface $user) + public function sendAuthCode(TwoFactorInterface $user): void { $template = $this->twig->loadTemplate('WallabagUserBundle:TwoFactor:email_auth_code.html.twig'); diff --git a/src/Wallabag/UserBundle/Resources/views/Authentication/form.html.twig b/src/Wallabag/UserBundle/Resources/views/Authentication/form.html.twig index c8471bdd..47a5cb78 100644 --- a/src/Wallabag/UserBundle/Resources/views/Authentication/form.html.twig +++ b/src/Wallabag/UserBundle/Resources/views/Authentication/form.html.twig @@ -1,7 +1,8 @@ +{# Override `vendor/scheb/two-factor-bundle/Resources/views/Authentication/form.html.twig` #} {% extends "WallabagUserBundle::layout.html.twig" %} {% block fos_user_content %} -
+
@@ -9,14 +10,19 @@

{{ flashMessage|trans }}

{% endfor %} + {# Authentication errors #} + {% if authenticationError %} +

{{ authenticationError|trans(authenticationErrorData) }}

+ {% endif %} +
- +
- {% if useTrustedOption %} + {% if displayTrustedOption %}
- +
{% endif %} diff --git a/src/Wallabag/UserBundle/Resources/views/Manage/edit.html.twig b/src/Wallabag/UserBundle/Resources/views/Manage/edit.html.twig index 3ffd15f5..8be37e79 100644 --- a/src/Wallabag/UserBundle/Resources/views/Manage/edit.html.twig +++ b/src/Wallabag/UserBundle/Resources/views/Manage/edit.html.twig @@ -50,10 +50,21 @@ {% if twofactor_auth %}
- {{ form_widget(edit_form.twoFactorAuthentication) }} - {{ form_label(edit_form.twoFactorAuthentication) }} - {{ form_errors(edit_form.twoFactorAuthentication) }} + {{ form_widget(edit_form.emailTwoFactor) }} + {{ form_label(edit_form.emailTwoFactor) }} + {{ form_errors(edit_form.emailTwoFactor) }}
+
+ {{ form_widget(edit_form.googleTwoFactor) }} + {{ form_label(edit_form.googleTwoFactor) }} + {{ form_errors(edit_form.googleTwoFactor) }} +
+ + {% if user.isGoogleAuthenticatorEnabled %} +
+

OTP Secret: {{ user.googleAuthenticatorSecret }}

+
+ {% endif %}
{% endif %} diff --git a/tests/Wallabag/CoreBundle/Command/ShowUserCommandTest.php b/tests/Wallabag/CoreBundle/Command/ShowUserCommandTest.php index 9b34f2a0..ed383a2c 100644 --- a/tests/Wallabag/CoreBundle/Command/ShowUserCommandTest.php +++ b/tests/Wallabag/CoreBundle/Command/ShowUserCommandTest.php @@ -59,7 +59,8 @@ class ShowUserCommandTest extends WallabagCoreTestCase $this->assertContains('Username: admin', $tester->getDisplay()); $this->assertContains('Email: bigboss@wallabag.org', $tester->getDisplay()); $this->assertContains('Display name: Big boss', $tester->getDisplay()); - $this->assertContains('2FA activated: no', $tester->getDisplay()); + $this->assertContains('2FA (email) activated', $tester->getDisplay()); + $this->assertContains('2FA (OTP) activated', $tester->getDisplay()); } public function testShowUser() diff --git a/tests/Wallabag/CoreBundle/Controller/ConfigControllerTest.php b/tests/Wallabag/CoreBundle/Controller/ConfigControllerTest.php index c9dbbaa3..9ca52c64 100644 --- a/tests/Wallabag/CoreBundle/Controller/ConfigControllerTest.php +++ b/tests/Wallabag/CoreBundle/Controller/ConfigControllerTest.php @@ -297,6 +297,119 @@ class ConfigControllerTest extends WallabagCoreTestCase $this->assertContains('flashes.config.notice.user_updated', $alert[0]); } + public function testUserEnable2faEmail() + { + $this->logInAs('admin'); + $client = $this->getClient(); + + $crawler = $client->request('GET', '/config'); + + $this->assertSame(200, $client->getResponse()->getStatusCode()); + + $form = $crawler->filter('button[id=update_user_save]')->form(); + + $data = [ + 'update_user[emailTwoFactor]' => '1', + ]; + + $client->submit($form, $data); + + $this->assertSame(302, $client->getResponse()->getStatusCode()); + + $crawler = $client->followRedirect(); + + $this->assertGreaterThan(1, $alert = $crawler->filter('body')->extract(['_text'])); + $this->assertContains('flashes.config.notice.user_updated', $alert[0]); + + // restore user + $em = $this->getEntityManager(); + $user = $em + ->getRepository('WallabagUserBundle:User') + ->findOneByUsername('admin'); + + $this->assertTrue($user->isEmailTwoFactor()); + + $user->setEmailTwoFactor(false); + $em->persist($user); + $em->flush(); + } + + public function testUserEnable2faGoogle() + { + $this->logInAs('admin'); + $client = $this->getClient(); + + $crawler = $client->request('GET', '/config'); + + $this->assertSame(200, $client->getResponse()->getStatusCode()); + + $form = $crawler->filter('button[id=update_user_save]')->form(); + + $data = [ + 'update_user[googleTwoFactor]' => '1', + ]; + + $client->submit($form, $data); + + $this->assertSame(302, $client->getResponse()->getStatusCode()); + + $crawler = $client->followRedirect(); + + $this->assertGreaterThan(1, $alert = $crawler->filter('body')->extract(['_text'])); + $this->assertContains('flashes.config.notice.user_updated', $alert[0]); + + // restore user + $em = $this->getEntityManager(); + $user = $em + ->getRepository('WallabagUserBundle:User') + ->findOneByUsername('admin'); + + $this->assertTrue($user->isGoogleAuthenticatorEnabled()); + + $user->setGoogleAuthenticatorSecret(null); + $em->persist($user); + $em->flush(); + } + + public function testUserEnable2faBoth() + { + $this->logInAs('admin'); + $client = $this->getClient(); + + $crawler = $client->request('GET', '/config'); + + $this->assertSame(200, $client->getResponse()->getStatusCode()); + + $form = $crawler->filter('button[id=update_user_save]')->form(); + + $data = [ + 'update_user[googleTwoFactor]' => '1', + 'update_user[emailTwoFactor]' => '1', + ]; + + $client->submit($form, $data); + + $this->assertSame(302, $client->getResponse()->getStatusCode()); + + $crawler = $client->followRedirect(); + + $this->assertGreaterThan(1, $alert = $crawler->filter('body')->extract(['_text'])); + $this->assertContains('flashes.config.notice.user_updated', $alert[0]); + + // restore user + $em = $this->getEntityManager(); + $user = $em + ->getRepository('WallabagUserBundle:User') + ->findOneByUsername('admin'); + + $this->assertTrue($user->isGoogleAuthenticatorEnabled()); + $this->assertFalse($user->isEmailTwoFactor()); + + $user->setGoogleAuthenticatorSecret(null); + $em->persist($user); + $em->flush(); + } + public function testRssUpdateResetToken() { $this->logInAs('admin'); diff --git a/tests/Wallabag/CoreBundle/Controller/SecurityControllerTest.php b/tests/Wallabag/CoreBundle/Controller/SecurityControllerTest.php index 395208a2..b03c7550 100644 --- a/tests/Wallabag/CoreBundle/Controller/SecurityControllerTest.php +++ b/tests/Wallabag/CoreBundle/Controller/SecurityControllerTest.php @@ -26,7 +26,7 @@ class SecurityControllerTest extends WallabagCoreTestCase $this->assertContains('config.form_rss.description', $crawler->filter('body')->extract(['_text'])[0]); } - public function testLoginWith2Factor() + public function testLoginWith2FactorEmail() { $client = $this->getClient(); @@ -42,7 +42,7 @@ class SecurityControllerTest extends WallabagCoreTestCase $user = $em ->getRepository('WallabagUserBundle:User') ->findOneByUsername('admin'); - $user->setTwoFactorAuthentication(true); + $user->setEmailTwoFactor(true); $em->persist($user); $em->flush(); @@ -54,12 +54,12 @@ class SecurityControllerTest extends WallabagCoreTestCase $user = $em ->getRepository('WallabagUserBundle:User') ->findOneByUsername('admin'); - $user->setTwoFactorAuthentication(false); + $user->setEmailTwoFactor(false); $em->persist($user); $em->flush(); } - public function testTrustedComputer() + public function testLoginWith2FactorGoogle() { $client = $this->getClient(); @@ -69,15 +69,27 @@ class SecurityControllerTest extends WallabagCoreTestCase return; } + $client->followRedirects(); + $em = $client->getContainer()->get('doctrine.orm.entity_manager'); $user = $em ->getRepository('WallabagUserBundle:User') ->findOneByUsername('admin'); + $user->setGoogleAuthenticatorSecret('26LDIHYGHNELOQEM'); + $em->persist($user); + $em->flush(); + + $this->logInAsUsingHttp('admin'); + $crawler = $client->request('GET', '/config'); + $this->assertContains('scheb_two_factor.trusted', $crawler->filter('body')->extract(['_text'])[0]); - $date = new \DateTime(); - $user->addTrustedComputer('ABCDEF', $date->add(new \DateInterval('P1M'))); - $this->assertTrue($user->isTrustedComputer('ABCDEF')); - $this->assertFalse($user->isTrustedComputer('FEDCBA')); + // restore user + $user = $em + ->getRepository('WallabagUserBundle:User') + ->findOneByUsername('admin'); + $user->setGoogleAuthenticatorSecret(null); + $em->persist($user); + $em->flush(); } public function testEnabledRegistration() diff --git a/tests/Wallabag/UserBundle/Mailer/AuthCodeMailerTest.php b/tests/Wallabag/UserBundle/Mailer/AuthCodeMailerTest.php index e34e13a8..1713c10c 100644 --- a/tests/Wallabag/UserBundle/Mailer/AuthCodeMailerTest.php +++ b/tests/Wallabag/UserBundle/Mailer/AuthCodeMailerTest.php @@ -33,7 +33,7 @@ TWIG; public function testSendEmail() { $user = new User(); - $user->setTwoFactorAuthentication(true); + $user->setEmailTwoFactor(true); $user->setEmailAuthCode(666666); $user->setEmail('test@wallabag.io'); $user->setName('Bob'); -- 2.41.0