mirror of
https://github.com/Part-DB/Part-DB-server.git
synced 2025-07-09 18:04:33 +02:00
Merge branch '2fa' into master
This commit is contained in:
commit
1016f0d4ee
65 changed files with 4769 additions and 320 deletions
|
@ -21,7 +21,11 @@
|
|||
|
||||
namespace App\Controller;
|
||||
|
||||
use App\Entity\Parts\Part;
|
||||
use App\Entity\UserSystem\U2FKey;
|
||||
use App\Entity\UserSystem\User;
|
||||
use App\Services\PasswordResetManager;
|
||||
use App\Services\TFA\BackupCodeManager;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Gregwar\CaptchaBundle\Type\CaptchaType;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
|
@ -29,6 +33,7 @@ use Symfony\Component\Form\Extension\Core\Type\PasswordType;
|
|||
use Symfony\Component\Form\Extension\Core\Type\RepeatedType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
||||
use Symfony\Component\HttpFoundation\RedirectResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
||||
use Symfony\Component\Mailer\MailerInterface;
|
||||
|
|
|
@ -25,16 +25,22 @@ use App\Entity\Attachments\AttachmentType;
|
|||
use App\Entity\Attachments\UserAttachment;
|
||||
use App\Entity\UserSystem\User;
|
||||
use App\Form\Permissions\PermissionsType;
|
||||
use App\Form\TFAGoogleSettingsType;
|
||||
use App\Form\UserAdminForm;
|
||||
use App\Form\UserSettingsType;
|
||||
use App\Services\EntityExporter;
|
||||
use App\Services\EntityImporter;
|
||||
use App\Services\StructuralElementRecursionHelper;
|
||||
use App\Services\TFA\BackupCodeManager;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use \Exception;
|
||||
use Scheb\TwoFactorBundle\Security\TwoFactor\Provider\Google\GoogleAuthenticator;
|
||||
use Symfony\Component\Asset\Packages;
|
||||
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\RepeatedType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
|
@ -61,6 +67,29 @@ class UserController extends AdminPages\BaseAdminController
|
|||
*/
|
||||
public function edit(User $entity, Request $request, EntityManagerInterface $em)
|
||||
{
|
||||
//Handle 2FA disabling
|
||||
|
||||
if($request->request->has('reset_2fa')) {
|
||||
//Check if the admin has the needed permissions
|
||||
$this->denyAccessUnlessGranted('set_password', $entity);
|
||||
if ($this->isCsrfTokenValid('reset_2fa'.$entity->getId(), $request->request->get('_token'))) {
|
||||
//Disable Google authenticator
|
||||
$entity->setGoogleAuthenticatorSecret(null);
|
||||
$entity->setBackupCodes([]);
|
||||
//Remove all U2F keys
|
||||
foreach($entity->getU2FKeys() as $key) {
|
||||
$em->remove($key);
|
||||
}
|
||||
//Invalidate trusted devices
|
||||
$entity->invalidateTrustedDeviceTokens();
|
||||
$em->flush();
|
||||
|
||||
$this->addFlash('success', 'user.edit.reset_success');
|
||||
} else {
|
||||
$this->addFlash('danger', 'csfr_invalid');
|
||||
}
|
||||
}
|
||||
|
||||
return $this->_edit($entity, $request, $em);
|
||||
}
|
||||
|
||||
|
@ -76,7 +105,7 @@ class UserController extends AdminPages\BaseAdminController
|
|||
}
|
||||
|
||||
/**
|
||||
* @Route("/{id}", name="user_delete", methods={"DELETE"})
|
||||
* @Route("/{id}", name="user_delete", methods={"DELETE"}, requirements={"id"="\d+"})
|
||||
*/
|
||||
public function delete(Request $request, User $entity, StructuralElementRecursionHelper $recursionHelper)
|
||||
{
|
||||
|
@ -147,94 +176,6 @@ class UserController extends AdminPages\BaseAdminController
|
|||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/settings", name="user_settings")
|
||||
*/
|
||||
public function userSettings(Request $request, EntityManagerInterface $em, UserPasswordEncoderInterface $passwordEncoder)
|
||||
{
|
||||
/**
|
||||
* @var User
|
||||
*/
|
||||
$user = $this->getUser();
|
||||
|
||||
$page_need_reload = false;
|
||||
|
||||
if (!$user instanceof User) {
|
||||
return new \RuntimeException('This controller only works only for Part-DB User objects!');
|
||||
}
|
||||
|
||||
//When user change its settings, he should be logged in fully.
|
||||
$this->denyAccessUnlessGranted('IS_AUTHENTICATED_FULLY');
|
||||
|
||||
/***************************
|
||||
* User settings form
|
||||
***************************/
|
||||
|
||||
$form = $this->createForm(UserSettingsType::class, $user);
|
||||
|
||||
$form->handleRequest($request);
|
||||
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
//Check if user theme setting has changed
|
||||
if ($user->getTheme() !== $em->getUnitOfWork()->getOriginalEntityData($user)['theme']) {
|
||||
$page_need_reload = true;
|
||||
}
|
||||
|
||||
$em->flush();
|
||||
$this->addFlash('success', 'user.settings.saved_flash');
|
||||
}
|
||||
|
||||
/*****************************
|
||||
* Password change form
|
||||
****************************/
|
||||
|
||||
$demo_mode = $this->getParameter('demo_mode');
|
||||
|
||||
$pw_form = $this->createFormBuilder()
|
||||
->add('old_password', PasswordType::class, [
|
||||
'label' => 'user.settings.pw_old.label',
|
||||
'disabled' => $demo_mode,
|
||||
'constraints' => [new UserPassword()], ]) //This constraint checks, if the current user pw was inputted.
|
||||
->add('new_password', RepeatedType::class, [
|
||||
'disabled' => $demo_mode,
|
||||
'type' => PasswordType::class,
|
||||
'first_options' => ['label' => 'user.settings.pw_new.label'],
|
||||
'second_options' => ['label' => 'user.settings.pw_confirm.label'],
|
||||
'invalid_message' => 'password_must_match',
|
||||
'constraints' => [new Length([
|
||||
'min' => 6,
|
||||
'max' => 128,
|
||||
])],
|
||||
])
|
||||
->add('submit', SubmitType::class, ['label' => 'save'])
|
||||
->getForm();
|
||||
|
||||
$pw_form->handleRequest($request);
|
||||
|
||||
//Check if password if everything was correct, then save it to User and DB
|
||||
if ($pw_form->isSubmitted() && $pw_form->isValid()) {
|
||||
$password = $passwordEncoder->encodePassword($user, $pw_form['new_password']->getData());
|
||||
$user->setPassword($password);
|
||||
|
||||
//After the change reset the password change needed setting
|
||||
$user->setNeedPwChange(false);
|
||||
|
||||
$em->persist($user);
|
||||
$em->flush();
|
||||
$this->addFlash('success', 'user.settings.pw_changed_flash');
|
||||
}
|
||||
|
||||
/******************************
|
||||
* Output both forms
|
||||
*****************************/
|
||||
|
||||
return $this->render('Users/user_settings.html.twig', [
|
||||
'settings_form' => $form->createView(),
|
||||
'pw_form' => $pw_form->createView(),
|
||||
'page_need_reload' => $page_need_reload,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get either a Gravatar URL or complete image tag for a specified email address.
|
||||
*
|
||||
|
|
308
src/Controller/UserSettingsController.php
Normal file
308
src/Controller/UserSettingsController.php
Normal file
|
@ -0,0 +1,308 @@
|
|||
<?php
|
||||
/**
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
|
||||
*/
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
|
||||
use App\Entity\UserSystem\U2FKey;
|
||||
use App\Entity\UserSystem\User;
|
||||
use App\Form\TFAGoogleSettingsType;
|
||||
use App\Form\UserSettingsType;
|
||||
use App\Services\TFA\BackupCodeManager;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Exception;
|
||||
use Scheb\TwoFactorBundle\Security\TwoFactor\Provider\Google\GoogleAuthenticator;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\RepeatedType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
|
||||
use Symfony\Component\Security\Core\Validator\Constraints\UserPassword;
|
||||
use Symfony\Component\Validator\Constraints\Length;
|
||||
|
||||
/**
|
||||
* @Route("/user")
|
||||
* @package App\Controller
|
||||
*/
|
||||
class UserSettingsController extends AbstractController
|
||||
{
|
||||
protected $demo_mode;
|
||||
|
||||
public function __construct(bool $demo_mode)
|
||||
{
|
||||
$this->demo_mode = $demo_mode;
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/2fa_backup_codes", name="show_backup_codes")
|
||||
*/
|
||||
public function showBackupCodes()
|
||||
{
|
||||
$user = $this->getUser();
|
||||
|
||||
//When user change its settings, he should be logged in fully.
|
||||
$this->denyAccessUnlessGranted('IS_AUTHENTICATED_FULLY');
|
||||
|
||||
if (!$user instanceof User) {
|
||||
return new \RuntimeException('This controller only works only for Part-DB User objects!');
|
||||
}
|
||||
|
||||
if (empty($user->getBackupCodes())) {
|
||||
$this->addFlash('error', 'tfa_backup.no_codes_enabled');
|
||||
throw new Exception('You do not have any backup codes enabled, therefore you can not view them!');
|
||||
}
|
||||
|
||||
return $this->render('Users/backup_codes.html.twig', [
|
||||
'user' => $user
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/u2f_delete", name="u2f_delete", methods={"DELETE"})
|
||||
*
|
||||
* @return \Symfony\Component\HttpFoundation\RedirectResponse
|
||||
*/
|
||||
public function removeU2FToken(Request $request, EntityManagerInterface $entityManager, BackupCodeManager $backupCodeManager)
|
||||
{
|
||||
if($this->demo_mode) {
|
||||
throw new \RuntimeException('You can not do 2FA things in demo mode');
|
||||
}
|
||||
|
||||
$user = $this->getUser();
|
||||
|
||||
//When user change its settings, he should be logged in fully.
|
||||
$this->denyAccessUnlessGranted('IS_AUTHENTICATED_FULLY');
|
||||
|
||||
if (!$user instanceof User) {
|
||||
throw new \RuntimeException('This controller only works only for Part-DB User objects!');
|
||||
}
|
||||
|
||||
|
||||
if ($this->isCsrfTokenValid('delete'.$user->getId(), $request->request->get('_token'))) {
|
||||
if($request->request->has('key_id')) {
|
||||
$key_id = $request->request->get('key_id');
|
||||
$key_repo = $entityManager->getRepository(U2FKey::class);
|
||||
/** @var U2FKey|null $u2f */
|
||||
$u2f = $key_repo->find($key_id);
|
||||
if($u2f === null) {
|
||||
$this->addFlash('danger','tfa_u2f.u2f_delete.not_existing');
|
||||
throw new \RuntimeException('Key not existing!');
|
||||
}
|
||||
|
||||
//User can only delete its own U2F keys
|
||||
if ($u2f->getUser() !== $user) {
|
||||
$this->addFlash('danger', 'tfa_u2f.u2f_delete.access_denied');
|
||||
throw new \RuntimeException('You can only delete your own U2F keys!');
|
||||
}
|
||||
|
||||
$backupCodeManager->disableBackupCodesIfUnused($user);
|
||||
$entityManager->remove($u2f);
|
||||
$entityManager->flush();
|
||||
$this->addFlash('success', 'tfa.u2f.u2f_delete.success');
|
||||
}
|
||||
} else {
|
||||
$this->addFlash('error','csfr_invalid');
|
||||
}
|
||||
|
||||
return $this->redirectToRoute('user_settings');
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/invalidate_trustedDevices", name="tfa_trustedDevices_invalidate", methods={"DELETE"})
|
||||
*/
|
||||
public function resetTrustedDevices(Request $request, EntityManagerInterface $entityManager)
|
||||
{
|
||||
if($this->demo_mode) {
|
||||
throw new \RuntimeException('You can not do 2FA things in demo mode');
|
||||
}
|
||||
|
||||
$user = $this->getUser();
|
||||
|
||||
//When user change its settings, he should be logged in fully.
|
||||
$this->denyAccessUnlessGranted('IS_AUTHENTICATED_FULLY');
|
||||
|
||||
if (!$user instanceof User) {
|
||||
return new \RuntimeException('This controller only works only for Part-DB User objects!');
|
||||
}
|
||||
|
||||
|
||||
if ($this->isCsrfTokenValid('devices_reset'.$user->getId(), $request->request->get('_token'))) {
|
||||
$user->invalidateTrustedDeviceTokens();
|
||||
$entityManager->flush();
|
||||
$this->addFlash('success', 'tfa_trustedDevice.invalidate.success');
|
||||
} else {
|
||||
$this->addFlash('error','csfr_invalid');
|
||||
}
|
||||
|
||||
return $this->redirectToRoute('user_settings');
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/settings", name="user_settings")
|
||||
*/
|
||||
public function userSettings(Request $request, EntityManagerInterface $em, UserPasswordEncoderInterface $passwordEncoder, GoogleAuthenticator $googleAuthenticator, BackupCodeManager $backupCodeManager)
|
||||
{
|
||||
/**
|
||||
* @var User
|
||||
*/
|
||||
$user = $this->getUser();
|
||||
|
||||
$page_need_reload = false;
|
||||
|
||||
//When user change its settings, he should be logged in fully.
|
||||
$this->denyAccessUnlessGranted('IS_AUTHENTICATED_FULLY');
|
||||
|
||||
if (!$user instanceof User) {
|
||||
throw new \RuntimeException('This controller only works only for Part-DB User objects!');
|
||||
}
|
||||
|
||||
/***************************
|
||||
* User settings form
|
||||
***************************/
|
||||
|
||||
$form = $this->createForm(UserSettingsType::class, $user);
|
||||
|
||||
$form->handleRequest($request);
|
||||
|
||||
if ($form->isSubmitted() && $form->isValid() && !$this->demo_mode) {
|
||||
//Check if user theme setting has changed
|
||||
if ($user->getTheme() !== $em->getUnitOfWork()->getOriginalEntityData($user)['theme']) {
|
||||
$page_need_reload = true;
|
||||
}
|
||||
|
||||
$em->flush();
|
||||
$this->addFlash('success', 'user.settings.saved_flash');
|
||||
}
|
||||
|
||||
/*****************************
|
||||
* Password change form
|
||||
****************************/
|
||||
|
||||
$pw_form = $this->createFormBuilder()
|
||||
//Username field for autocomplete
|
||||
->add('username', TextType::class, [
|
||||
'data' => $user->getName(),
|
||||
'attr' => ['autocomplete' => 'username'],
|
||||
'disabled' => true,
|
||||
'row_attr' => ['class' => 'd-none']
|
||||
])
|
||||
->add('old_password', PasswordType::class, [
|
||||
'label' => 'user.settings.pw_old.label',
|
||||
'disabled' => $this->demo_mode,
|
||||
'attr' => ['autocomplete' => 'current-password'],
|
||||
'constraints' => [new UserPassword()], ]) //This constraint checks, if the current user pw was inputted.
|
||||
->add('new_password', RepeatedType::class, [
|
||||
'disabled' => $this->demo_mode,
|
||||
'type' => PasswordType::class,
|
||||
'first_options' => ['label' => 'user.settings.pw_new.label'],
|
||||
'second_options' => ['label' => 'user.settings.pw_confirm.label'],
|
||||
'invalid_message' => 'password_must_match',
|
||||
'options' => [
|
||||
'attr' => ['autocomplete' => 'new-password']
|
||||
],
|
||||
'constraints' => [new Length([
|
||||
'min' => 6,
|
||||
'max' => 128,
|
||||
])],
|
||||
])
|
||||
->add('submit', SubmitType::class, ['label' => 'save'])
|
||||
->getForm();
|
||||
|
||||
$pw_form->handleRequest($request);
|
||||
|
||||
//Check if password if everything was correct, then save it to User and DB
|
||||
if ($pw_form->isSubmitted() && $pw_form->isValid() && !$this->demo_mode) {
|
||||
$password = $passwordEncoder->encodePassword($user, $pw_form['new_password']->getData());
|
||||
$user->setPassword($password);
|
||||
|
||||
//After the change reset the password change needed setting
|
||||
$user->setNeedPwChange(false);
|
||||
|
||||
$em->persist($user);
|
||||
$em->flush();
|
||||
$this->addFlash('success', 'user.settings.pw_changed_flash');
|
||||
}
|
||||
|
||||
//Handle 2FA things
|
||||
$google_form = $this->createForm(TFAGoogleSettingsType::class, $user);
|
||||
$google_enabled = $user->isGoogleAuthenticatorEnabled();
|
||||
if (!$form->isSubmitted() && !$google_enabled) {
|
||||
$user->setGoogleAuthenticatorSecret($googleAuthenticator->generateSecret());
|
||||
$google_form->get('googleAuthenticatorSecret')->setData($user->getGoogleAuthenticatorSecret());
|
||||
}
|
||||
$google_form->handleRequest($request);
|
||||
|
||||
if($google_form->isSubmitted() && $google_form->isValid() && !$this->demo_mode) {
|
||||
if (!$google_enabled) {
|
||||
//Save 2FA settings (save secrets)
|
||||
$user->setGoogleAuthenticatorSecret($google_form->get('googleAuthenticatorSecret')->getData());
|
||||
$backupCodeManager->enableBackupCodes($user);
|
||||
$em->flush();
|
||||
$this->addFlash('success', 'user.settings.2fa.google.activated');
|
||||
return $this->redirectToRoute('user_settings');
|
||||
} elseif ($google_enabled) {
|
||||
//Remove secret to disable google authenticator
|
||||
$user->setGoogleAuthenticatorSecret(null);
|
||||
$backupCodeManager->disableBackupCodesIfUnused($user);
|
||||
$em->flush();
|
||||
$this->addFlash('success', 'user.settings.2fa.google.disabled');
|
||||
return $this->redirectToRoute('user_settings');
|
||||
}
|
||||
}
|
||||
|
||||
$backup_form = $this->get('form.factory')->createNamedBuilder('backup_codes')->add('reset_codes', SubmitType::class,[
|
||||
'label' => 'tfa_backup.regenerate_codes',
|
||||
'attr' => ['class' => 'btn-danger'],
|
||||
'disabled' => empty($user->getBackupCodes())
|
||||
])->getForm();
|
||||
|
||||
$backup_form->handleRequest($request);
|
||||
if ($backup_form->isSubmitted() && $backup_form->isValid() && !$this->demo_mode) {
|
||||
$backupCodeManager->regenerateBackupCodes($user);
|
||||
$em->flush();
|
||||
$this->addFlash('success', 'user.settings.2fa.backup_codes.regenerated');
|
||||
}
|
||||
|
||||
|
||||
/******************************
|
||||
* Output both forms
|
||||
*****************************/
|
||||
|
||||
return $this->render('Users/user_settings.html.twig', [
|
||||
'user' => $user,
|
||||
'settings_form' => $form->createView(),
|
||||
'pw_form' => $pw_form->createView(),
|
||||
'page_need_reload' => $page_need_reload,
|
||||
|
||||
'google_form' => $google_form->createView(),
|
||||
'backup_form' => $backup_form->createView(),
|
||||
'tfa_google' => [
|
||||
'enabled' => $google_enabled,
|
||||
'qrContent' => $googleAuthenticator->getQRContent($user),
|
||||
'secret' => $user->getGoogleAuthenticatorSecret(),
|
||||
'username' => $user->getGoogleAuthenticatorUsername()
|
||||
]
|
||||
]);
|
||||
}
|
||||
}
|
|
@ -53,9 +53,9 @@ trait MasterAttachmentTrait
|
|||
* Sets the new master picture for this part.
|
||||
*
|
||||
* @param Attachment|null $new_master_attachment
|
||||
* @return Part
|
||||
* @return $this
|
||||
*/
|
||||
public function setMasterPictureAttachment(?Attachment $new_master_attachment): self
|
||||
public function setMasterPictureAttachment(?Attachment $new_master_attachment)
|
||||
{
|
||||
$this->master_picture_attachment = $new_master_attachment;
|
||||
|
||||
|
|
|
@ -107,11 +107,11 @@ class Storelocation extends PartsContainingDBElement
|
|||
protected $storage_type;
|
||||
|
||||
/**
|
||||
* @ORM\ManyToMany(targetEntity="Part", fetch="EXTRA_LAZY")
|
||||
* @ORM\JoinTable(name="part_lots",
|
||||
* joinColumns={@ORM\JoinColumn(name="id_store_location", referencedColumnName="id")},
|
||||
* inverseJoinColumns={@ORM\JoinColumn(name="id_part", referencedColumnName="id")}
|
||||
* )
|
||||
* //@ORM\ManyToMany(targetEntity="Part", fetch="EXTRA_LAZY")
|
||||
* //@ORM\JoinTable(name="part_lots",
|
||||
* // joinColumns={@ORM\JoinColumn(name="id_store_location", referencedColumnName="id")},
|
||||
* // inverseJoinColumns={@ORM\JoinColumn(name="id_part", referencedColumnName="id")}
|
||||
* //)
|
||||
*/
|
||||
protected $parts;
|
||||
|
||||
|
|
|
@ -106,11 +106,11 @@ class Supplier extends Company
|
|||
protected $shipping_costs;
|
||||
|
||||
/**
|
||||
* @ORM\ManyToMany(targetEntity="Part", fetch="EXTRA_LAZY")
|
||||
* @ORM\JoinTable(name="orderdetails",
|
||||
* joinColumns={@ORM\JoinColumn(name="id_supplier", referencedColumnName="id")},
|
||||
* inverseJoinColumns={@ORM\JoinColumn(name="part_id", referencedColumnName="id")}
|
||||
* )
|
||||
* //@ORM\ManyToMany(targetEntity="Part", fetch="EXTRA_LAZY")
|
||||
* //@ORM\JoinTable(name="orderdetails",
|
||||
* // joinColumns={@ORM\JoinColumn(name="id_supplier", referencedColumnName="id")},
|
||||
* // inverseJoinColumns={@ORM\JoinColumn(name="part_id", referencedColumnName="id")}
|
||||
* //)
|
||||
*/
|
||||
protected $parts;
|
||||
|
||||
|
|
|
@ -64,12 +64,40 @@ class Group extends StructuralDBElement implements HasPermissionsInterface
|
|||
*/
|
||||
protected $permissions;
|
||||
|
||||
/**
|
||||
* @var bool If true all users associated with this group must have enabled some kind of 2 factor authentication
|
||||
* @ORM\Column(type="boolean", name="enforce_2fa")
|
||||
*/
|
||||
protected $enforce2FA = false;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
$this->permissions = new PermissionsEmbed();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the users of this group are enforced to have two factor authentification (2FA) enabled.
|
||||
* @return bool
|
||||
*/
|
||||
public function isEnforce2FA(): bool
|
||||
{
|
||||
return $this->enforce2FA;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets if the user of this group are enforced to have two factor authentification enabled.
|
||||
* @param bool $enforce2FA True, if the users of this group are enforced to have 2FA enabled.
|
||||
* @return $this
|
||||
*/
|
||||
public function setEnforce2FA(bool $enforce2FA): Group
|
||||
{
|
||||
$this->enforce2FA = $enforce2FA;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Returns the ID as an string, defined by the element class.
|
||||
* This should have a form like P000014, for a part with ID 14.
|
||||
|
|
183
src/Entity/UserSystem/U2FKey.php
Normal file
183
src/Entity/UserSystem/U2FKey.php
Normal file
|
@ -0,0 +1,183 @@
|
|||
<?php
|
||||
/**
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
|
||||
*/
|
||||
|
||||
namespace App\Entity\UserSystem;
|
||||
|
||||
|
||||
use App\Entity\Base\TimestampTrait;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use R\U2FTwoFactorBundle\Model\U2F\TwoFactorInterface;
|
||||
use R\U2FTwoFactorBundle\Model\U2F\TwoFactorKeyInterface;
|
||||
use u2flib_server\Registration;
|
||||
|
||||
/**
|
||||
* @ORM\Entity
|
||||
* @ORM\Table(name="u2f_keys",
|
||||
* uniqueConstraints={@ORM\UniqueConstraint(name="user_unique",columns={"user_id",
|
||||
* "key_handle"})})
|
||||
* @ORM\HasLifecycleCallbacks()
|
||||
*/
|
||||
class U2FKey implements TwoFactorKeyInterface
|
||||
{
|
||||
use TimestampTrait;
|
||||
|
||||
/**
|
||||
* @ORM\Id
|
||||
* @ORM\Column(type="integer")
|
||||
* @ORM\GeneratedValue(strategy="AUTO")
|
||||
*/
|
||||
protected $id;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="string")
|
||||
* @var string
|
||||
**/
|
||||
public $keyHandle;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="string")
|
||||
* @var string
|
||||
**/
|
||||
public $publicKey;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="text")
|
||||
* @var string
|
||||
**/
|
||||
public $certificate;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="string")
|
||||
* @var int
|
||||
**/
|
||||
public $counter;
|
||||
|
||||
/**
|
||||
* @ORM\ManyToOne(targetEntity="App\Entity\UserSystem\User", inversedBy="u2fKeys")
|
||||
* @var User
|
||||
**/
|
||||
protected $user;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="string")
|
||||
* @var string
|
||||
**/
|
||||
protected $name;
|
||||
|
||||
|
||||
public function fromRegistrationData(Registration $data): void
|
||||
{
|
||||
$this->keyHandle = $data->keyHandle;
|
||||
$this->publicKey = $data->publicKey;
|
||||
$this->certificate = $data->certificate;
|
||||
$this->counter = $data->counter;
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
public function getKeyHandle()
|
||||
{
|
||||
return $this->keyHandle;
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
public function setKeyHandle($keyHandle)
|
||||
{
|
||||
$this->keyHandle = $keyHandle;
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
public function getPublicKey()
|
||||
{
|
||||
return $this->publicKey;
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
public function setPublicKey($publicKey)
|
||||
{
|
||||
$this->publicKey = $publicKey;
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
public function getCertificate()
|
||||
{
|
||||
return $this->certificate;
|
||||
}
|
||||
|
||||
|
||||
/** @inheritDoc */
|
||||
public function setCertificate($certificate)
|
||||
{
|
||||
$this->certificate = $certificate;
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
public function getCounter()
|
||||
{
|
||||
return $this->counter;
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
public function setCounter($counter)
|
||||
{
|
||||
$this->counter = $counter;
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
public function getName()
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
public function setName($name)
|
||||
{
|
||||
$this->name = $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the user, this U2F key belongs to.
|
||||
* @return User
|
||||
*/
|
||||
public function getUser() : User
|
||||
{
|
||||
return $this->user;
|
||||
}
|
||||
|
||||
/**
|
||||
* The primary key ID of this key
|
||||
* @return int
|
||||
*/
|
||||
public function getID() : int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the user this U2F key belongs to.
|
||||
* @param TwoFactorInterface $new_user
|
||||
* @return $this
|
||||
*/
|
||||
public function setUser(TwoFactorInterface $new_user) : self
|
||||
{
|
||||
$this->user = $new_user;
|
||||
return $this;
|
||||
}
|
||||
}
|
|
@ -53,28 +53,38 @@ namespace App\Entity\UserSystem;
|
|||
|
||||
use App\Entity\Attachments\AttachmentContainingDBElement;
|
||||
use App\Entity\Attachments\UserAttachment;
|
||||
use App\Entity\Base\MasterAttachmentTrait;
|
||||
use App\Entity\Base\NamedDBElement;
|
||||
use App\Entity\PriceInformations\Currency;
|
||||
use App\Security\Interfaces\HasPermissionsInterface;
|
||||
use App\Validator\Constraints\Selectable;
|
||||
use App\Validator\Constraints\ValidPermission;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use R\U2FTwoFactorBundle\Model\U2F\TwoFactorKeyInterface;
|
||||
use Scheb\TwoFactorBundle\Model\BackupCodeInterface;
|
||||
use Scheb\TwoFactorBundle\Model\Google\TwoFactorInterface;
|
||||
use Scheb\TwoFactorBundle\Model\PreferredProviderInterface;
|
||||
use Scheb\TwoFactorBundle\Model\TrustedDeviceInterface;
|
||||
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
|
||||
use Symfony\Component\Security\Core\User\UserInterface;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
use R\U2FTwoFactorBundle\Model\U2F\TwoFactorInterface as U2FTwoFactorInterface;
|
||||
|
||||
/**
|
||||
* This entity represents a user, which can log in and have permissions.
|
||||
* Also this entity is able to save some informations about the user, like the names, email-address and other info.
|
||||
* Also this entity is able to save some informations about the user, like the names, email-address and other info.
|
||||
*
|
||||
* @ORM\Entity(repositoryClass="App\Repository\UserRepository")
|
||||
* @ORM\Table("`users`")
|
||||
* @UniqueEntity("name", message="validator.user.username_already_used")
|
||||
*/
|
||||
class User extends AttachmentContainingDBElement implements UserInterface, HasPermissionsInterface
|
||||
class User extends AttachmentContainingDBElement implements UserInterface, HasPermissionsInterface,
|
||||
TwoFactorInterface, BackupCodeInterface, TrustedDeviceInterface, U2FTwoFactorInterface, PreferredProviderInterface
|
||||
{
|
||||
use MasterAttachmentTrait;
|
||||
|
||||
/** The User id of the anonymous user */
|
||||
public const ID_ANONYMOUS = 1;
|
||||
|
||||
|
@ -172,6 +182,33 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe
|
|||
*/
|
||||
protected $group;
|
||||
|
||||
/**
|
||||
* @var string|null The secret used for google authenticator
|
||||
* @ORM\Column(name="google_authenticator_secret", type="string", nullable=true)
|
||||
*/
|
||||
protected $googleAuthenticatorSecret;
|
||||
|
||||
/**
|
||||
* @var string[]|null A list of backup codes that can be used, if the user has no access to its Google Authenticator device
|
||||
* @ORM\Column(type="json")
|
||||
*/
|
||||
protected $backupCodes = [];
|
||||
|
||||
/** @var \DateTime The time when the backup codes were generated
|
||||
* @ORM\Column(type="datetime", nullable=true)
|
||||
*/
|
||||
protected $backupCodesGenerationDate;
|
||||
|
||||
/** @var int The version of the trusted device cookie. Used to invalidate all trusted device cookies at once.
|
||||
* @ORM\Column(type="integer")
|
||||
*/
|
||||
protected $trustedDeviceCookieVersion = 0;
|
||||
|
||||
/** @var Collection<TwoFactorKeyInterface>
|
||||
* @ORM\OneToMany(targetEntity="App\Entity\UserSystem\U2FKey", mappedBy="user", cascade={"REMOVE"}, orphanRemoval=true)
|
||||
*/
|
||||
protected $u2fKeys;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
* @ORM\Column(type="json")
|
||||
|
@ -227,6 +264,7 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe
|
|||
{
|
||||
parent::__construct();
|
||||
$this->permissions = new PermissionsEmbed();
|
||||
$this->u2fKeys = new ArrayCollection();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -457,6 +495,11 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe
|
|||
return sprintf('%s %s', $this->getFirstName(), $this->getLastName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the username of this user
|
||||
* @param string $new_name The new username.
|
||||
* @return $this
|
||||
*/
|
||||
public function setName(string $new_name): NamedDBElement
|
||||
{
|
||||
// Anonymous user is not allowed to change its username
|
||||
|
@ -468,7 +511,8 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe
|
|||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
* Get the first name of the user.
|
||||
* @return string|null
|
||||
*/
|
||||
public function getFirstName(): ?string
|
||||
{
|
||||
|
@ -476,9 +520,10 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe
|
|||
}
|
||||
|
||||
/**
|
||||
* @param string $first_name
|
||||
* Change the first name of the user
|
||||
* @param string $first_name The new first name
|
||||
*
|
||||
* @return User
|
||||
* @return $this
|
||||
*/
|
||||
public function setFirstName(?string $first_name): self
|
||||
{
|
||||
|
@ -488,7 +533,8 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe
|
|||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
* Get the last name of the user
|
||||
* @return string|null
|
||||
*/
|
||||
public function getLastName(): ?string
|
||||
{
|
||||
|
@ -496,9 +542,10 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe
|
|||
}
|
||||
|
||||
/**
|
||||
* @param string $last_name
|
||||
* Change the last name of the user
|
||||
* @param string $last_name The new last name
|
||||
*
|
||||
* @return User
|
||||
* @return $this
|
||||
*/
|
||||
public function setLastName(?string $last_name): self
|
||||
{
|
||||
|
@ -508,6 +555,7 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe
|
|||
}
|
||||
|
||||
/**
|
||||
* Gets the department of this user
|
||||
* @return string
|
||||
*/
|
||||
public function getDepartment(): ?string
|
||||
|
@ -516,8 +564,8 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe
|
|||
}
|
||||
|
||||
/**
|
||||
* @param string $department
|
||||
*
|
||||
* Change the department of the user
|
||||
* @param string $department The new department
|
||||
* @return User
|
||||
*/
|
||||
public function setDepartment(?string $department): self
|
||||
|
@ -528,6 +576,7 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe
|
|||
}
|
||||
|
||||
/**
|
||||
* Get the email of the user.
|
||||
* @return string
|
||||
*/
|
||||
public function getEmail(): ?string
|
||||
|
@ -536,9 +585,9 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe
|
|||
}
|
||||
|
||||
/**
|
||||
* @param string $email
|
||||
*
|
||||
* @return User
|
||||
* Change the email of the user
|
||||
* @param string $email The new email adress
|
||||
* @return $this
|
||||
*/
|
||||
public function setEmail(?string $email): self
|
||||
{
|
||||
|
@ -548,7 +597,9 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe
|
|||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
* Gets the language the user prefers (as 2 letter ISO code).
|
||||
* @return string|null The 2 letter ISO code of the preferred language (e.g. 'en' or 'de').
|
||||
* If null is returned, the user has not specified a language and the server wide language should be used.
|
||||
*/
|
||||
public function getLanguage(): ?string
|
||||
{
|
||||
|
@ -556,19 +607,21 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe
|
|||
}
|
||||
|
||||
/**
|
||||
* @param string $language
|
||||
*
|
||||
* Change the language the user prefers.
|
||||
* @param string|null $language The new language as 2 letter ISO code (e.g. 'en' or 'de').
|
||||
* Set to null, to use the system wide language.
|
||||
* @return User
|
||||
*/
|
||||
public function setLanguage(?string $language): self
|
||||
{
|
||||
$this->language = $language;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
* Gets the timezone of the user
|
||||
* @return string|null The timezone of the user (e.g. 'Europe/Berlin') or null if the user has not specified
|
||||
* a timezone (then the global one should be used)
|
||||
*/
|
||||
public function getTimezone(): ?string
|
||||
{
|
||||
|
@ -576,9 +629,9 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe
|
|||
}
|
||||
|
||||
/**
|
||||
* @param string $timezone
|
||||
*
|
||||
* @return User
|
||||
* Change the timezone of this user.
|
||||
* @param string $timezone|null The new timezone (e.g. 'Europe/Berlin') or null to use the system wide one.
|
||||
* @return $this
|
||||
*/
|
||||
public function setTimezone(?string $timezone): self
|
||||
{
|
||||
|
@ -588,7 +641,8 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe
|
|||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
* Gets the theme the users wants to see. See self::AVAILABLE_THEMES for valid values.
|
||||
* @return string|null The name of the theme the user wants to see, or null if the system wide should be used.
|
||||
*/
|
||||
public function getTheme(): ?string
|
||||
{
|
||||
|
@ -596,9 +650,10 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe
|
|||
}
|
||||
|
||||
/**
|
||||
* @param string $theme
|
||||
*
|
||||
* @return User
|
||||
* Change the theme the user wants to see.
|
||||
* @param string|null $theme The name of the theme (See See self::AVAILABLE_THEMES for valid values). Set to null
|
||||
* if the system wide theme should be used.
|
||||
* @return $this
|
||||
*/
|
||||
public function setTheme(?string $theme): self
|
||||
{
|
||||
|
@ -607,11 +662,20 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe
|
|||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the group to which this user belongs to.
|
||||
* @return Group|null The group of this user. Null if this user does not have a group.
|
||||
*/
|
||||
public function getGroup(): ?Group
|
||||
{
|
||||
return $this->group;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the group of this user.
|
||||
* @param Group|null $group The new group of this user. Set to null if this user should not have a group.
|
||||
* @return $this
|
||||
*/
|
||||
public function setGroup(?Group $group): self
|
||||
{
|
||||
$this->group = $group;
|
||||
|
@ -619,10 +683,181 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe
|
|||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string representation of this user (the full name).
|
||||
* E.g. 'Jane Doe (j.doe) [DISABLED]
|
||||
* @return string
|
||||
*/
|
||||
public function __toString()
|
||||
{
|
||||
$tmp = $this->isDisabled() ? ' [DISABLED]' : '';
|
||||
|
||||
return $this->getFullName(true).$tmp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the user should do two-factor authentication.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isGoogleAuthenticatorEnabled(): bool
|
||||
{
|
||||
return $this->googleAuthenticatorSecret ? true : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the user name that should be shown in Google Authenticator.
|
||||
* @return string
|
||||
*/
|
||||
public function getGoogleAuthenticatorUsername(): string
|
||||
{
|
||||
return $this->getUsername();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the Google Authenticator secret
|
||||
* When an empty string is returned, the Google authentication is disabled.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getGoogleAuthenticatorSecret(): ?string
|
||||
{
|
||||
return $this->googleAuthenticatorSecret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the secret used for Google Authenticator. Set to null to disable Google Authenticator.
|
||||
* @param string|null $googleAuthenticatorSecret
|
||||
* @return $this
|
||||
*/
|
||||
public function setGoogleAuthenticatorSecret(?string $googleAuthenticatorSecret): self
|
||||
{
|
||||
$this->googleAuthenticatorSecret = $googleAuthenticatorSecret;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the given code is a valid backup code.
|
||||
*
|
||||
* @param string $code The code that should be checked.
|
||||
* @return bool True if the backup code is valid.
|
||||
*/
|
||||
public function isBackupCode(string $code): bool
|
||||
{
|
||||
return in_array($code, $this->backupCodes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidate a backup code.
|
||||
*
|
||||
* @param string $code The code that should be invalidated
|
||||
*/
|
||||
public function invalidateBackupCode(string $code): void
|
||||
{
|
||||
$key = array_search($code, $this->backupCodes);
|
||||
if ($key !== false){
|
||||
unset($this->backupCodes[$key]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of all valid backup codes
|
||||
* @return string[] An array with all backup codes
|
||||
*/
|
||||
public function getBackupCodes() : array
|
||||
{
|
||||
return $this->backupCodes ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the backup codes for this user. Existing backup codes are overridden.
|
||||
* @param string[] $codes A
|
||||
* @return $this
|
||||
*/
|
||||
public function setBackupCodes(array $codes) : self
|
||||
{
|
||||
$this->backupCodes = $codes;
|
||||
if(empty($codes)) {
|
||||
$this->backupCodesGenerationDate = null;
|
||||
} else {
|
||||
$this->backupCodesGenerationDate = new \DateTime();
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the date when the backup codes were generated.
|
||||
* @return \DateTime|null
|
||||
*/
|
||||
public function getBackupCodesGenerationDate() : ?\DateTime
|
||||
{
|
||||
return $this->backupCodesGenerationDate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return version for the trusted device token. Increase version to invalidate all trusted token of the user.
|
||||
* @return int The version of trusted device token
|
||||
*/
|
||||
public function getTrustedTokenVersion(): int
|
||||
{
|
||||
return $this->trustedDeviceCookieVersion;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidate all trusted device tokens at once, by incrementing the token version.
|
||||
* You have to flush the changes to database afterwards.
|
||||
*/
|
||||
public function invalidateTrustedDeviceTokens() : void
|
||||
{
|
||||
$this->trustedDeviceCookieVersion++;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if U2F is enabled
|
||||
* @return bool
|
||||
*/
|
||||
public function isU2FAuthEnabled(): bool
|
||||
{
|
||||
return count($this->u2fKeys) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all U2F Keys that are associated with this user
|
||||
* @return Collection<TwoFactorKeyInterface>
|
||||
*/
|
||||
public function getU2FKeys(): Collection
|
||||
{
|
||||
return $this->u2fKeys;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a U2F key to this user.
|
||||
* @param TwoFactorKeyInterface $key
|
||||
*/
|
||||
public function addU2FKey(TwoFactorKeyInterface $key): void
|
||||
{
|
||||
$this->u2fKeys->add($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a U2F key from this user.
|
||||
* @param TwoFactorKeyInterface $key
|
||||
*/
|
||||
public function removeU2FKey(TwoFactorKeyInterface $key): void
|
||||
{
|
||||
$this->u2fKeys->removeElement($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getPreferredTwoFactorProvider(): ?string
|
||||
{
|
||||
//If U2F is available then prefer it
|
||||
if($this->isU2FAuthEnabled()) {
|
||||
return 'u2f_two_factor';
|
||||
}
|
||||
|
||||
//Otherwise use other methods
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
145
src/EventSubscriber/PasswordChangeNeededSubscriber.php
Normal file
145
src/EventSubscriber/PasswordChangeNeededSubscriber.php
Normal file
|
@ -0,0 +1,145 @@
|
|||
<?php
|
||||
/**
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
|
||||
*/
|
||||
|
||||
namespace App\EventSubscriber;
|
||||
|
||||
|
||||
use App\Entity\UserSystem\User;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
use Symfony\Component\HttpFoundation\Session\Flash\FlashBagInterface;
|
||||
use Symfony\Component\HttpKernel\Event\RequestEvent;
|
||||
use Symfony\Component\HttpKernel\KernelEvents;
|
||||
use Symfony\Component\Security\Core\Security;
|
||||
use Symfony\Component\Security\Http\HttpUtils;
|
||||
|
||||
/**
|
||||
* This event subscriber redirects a user to its settings page, when it needs to change its password or is enforced
|
||||
* to setup a 2FA method (enforcement can be set per group).
|
||||
* In this cases the user is unable to access sites other than the whitelisted (see ALLOWED_ROUTES).
|
||||
* @package App\EventSubscriber
|
||||
*/
|
||||
class PasswordChangeNeededSubscriber implements EventSubscriberInterface
|
||||
{
|
||||
|
||||
protected $security;
|
||||
protected $flashBag;
|
||||
protected $httpUtils;
|
||||
|
||||
/**
|
||||
* @var string[] The routes the user is allowed to access without being redirected.
|
||||
* This should be only routes related to login/logout and user settings
|
||||
*/
|
||||
public const ALLOWED_ROUTES = [
|
||||
'2fa_login',
|
||||
'2fa_login_check',
|
||||
'user_settings',
|
||||
'club_base_register_u2f',
|
||||
'logout',
|
||||
];
|
||||
|
||||
/** @var string The route the user will redirected to, if he needs to change this password */
|
||||
public const REDIRECT_TARGET = 'user_settings';
|
||||
|
||||
public function __construct(Security $security, FlashBagInterface $flashBag, HttpUtils $httpUtils)
|
||||
{
|
||||
$this->security = $security;
|
||||
$this->flashBag = $flashBag;
|
||||
$this->httpUtils = $httpUtils;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function is called when the kernel encounters a request.
|
||||
* It checks if the user must change its password or add an 2FA mehtod and redirect it to the user settings page,
|
||||
* if needed.
|
||||
* @param RequestEvent $event
|
||||
*/
|
||||
public function redirectToSettingsIfNeeded(RequestEvent $event) : void
|
||||
{
|
||||
$user = $this->security->getUser();
|
||||
$request = $event->getRequest();
|
||||
|
||||
if(!$event->isMasterRequest()) {
|
||||
return;
|
||||
}
|
||||
if(!$user instanceof User) {
|
||||
return;
|
||||
}
|
||||
|
||||
//Abort if we dont need to redirect the user.
|
||||
if (!$user->isNeedPwChange() && !static::TFARedirectNeeded($user)) {
|
||||
return;
|
||||
}
|
||||
|
||||
//Check for a whitelisted URL
|
||||
foreach (static::ALLOWED_ROUTES as $route) {
|
||||
//Dont do anything if we encounter an allowed route
|
||||
if ($this->httpUtils->checkRequestPath($request, $route)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* Dont redirect tree endpoints, as this would cause trouble and creates multiple flash
|
||||
warnigs for one page reload */
|
||||
if(strpos($request->getUri(), '/tree/') !== false) {
|
||||
return;
|
||||
}
|
||||
|
||||
//Show appropriate message to user about the reason he was redirected
|
||||
if($user->isNeedPwChange()) {
|
||||
$this->flashBag->add('warning', 'user.pw_change_needed.flash');
|
||||
}
|
||||
|
||||
if(static::TFARedirectNeeded($user)) {
|
||||
$this->flashBag->add('warning', 'user.2fa_needed.flash');
|
||||
}
|
||||
|
||||
$event->setResponse($this->httpUtils->createRedirectResponse($request, static::REDIRECT_TARGET));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a redirect because of a missing 2FA method is needed.
|
||||
* That is the case if the group of the user enforces 2FA, but the user has neither Google Authenticator nor an
|
||||
* U2F key setup.
|
||||
* @param User $user The user for which should be checked if it needs to be redirected.
|
||||
* @return bool True if the user needs to be redirected.
|
||||
*/
|
||||
public static function TFARedirectNeeded(User $user) : bool
|
||||
{
|
||||
$tfa_enabled = $user->isU2FAuthEnabled() || $user->isGoogleAuthenticatorEnabled();
|
||||
|
||||
if ($user->getGroup() !== null && $user->getGroup()->isEnforce2FA() && !$tfa_enabled) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public static function getSubscribedEvents()
|
||||
{
|
||||
return [
|
||||
KernelEvents::REQUEST => 'redirectToSettingsIfNeeded',
|
||||
];
|
||||
}
|
||||
}
|
81
src/EventSubscriber/U2FRegistrationSubscriber.php
Normal file
81
src/EventSubscriber/U2FRegistrationSubscriber.php
Normal file
|
@ -0,0 +1,81 @@
|
|||
<?php
|
||||
/**
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
|
||||
*/
|
||||
|
||||
namespace App\EventSubscriber;
|
||||
|
||||
|
||||
use App\Entity\UserSystem\U2FKey;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use R\U2FTwoFactorBundle\Event\RegisterEvent;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
use Symfony\Component\HttpFoundation\RedirectResponse;
|
||||
use Symfony\Component\HttpFoundation\Session\Flash\FlashBagInterface;
|
||||
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
|
||||
|
||||
class U2FRegistrationSubscriber implements EventSubscriberInterface
|
||||
{
|
||||
/** @var UrlGeneratorInterface */
|
||||
private $router;
|
||||
|
||||
protected $em;
|
||||
|
||||
protected $demo_mode;
|
||||
protected $flashBag;
|
||||
|
||||
public function __construct(UrlGeneratorInterface $router, EntityManagerInterface $entityManager, FlashBagInterface $flashBag, bool $demo_mode)
|
||||
{
|
||||
$this->router = $router;
|
||||
$this->em = $entityManager;
|
||||
$this->demo_mode = $demo_mode;
|
||||
$this->flashBag = $flashBag;
|
||||
}
|
||||
|
||||
/** @return string[] **/
|
||||
public static function getSubscribedEvents(): array
|
||||
{
|
||||
return array(
|
||||
'r_u2f_two_factor.register' => 'onRegister',
|
||||
);
|
||||
}
|
||||
|
||||
public function onRegister(RegisterEvent $event): void
|
||||
{
|
||||
//Skip adding of U2F key on demo mode
|
||||
if (!$this->demo_mode) {
|
||||
$user = $event->getUser();
|
||||
$registration = $event->getRegistration();
|
||||
$newKey = new U2FKey();
|
||||
$newKey->fromRegistrationData($registration);
|
||||
$newKey->setUser($user);
|
||||
$newKey->setName($event->getKeyName());
|
||||
|
||||
// persist the new key
|
||||
$this->em->persist($newKey);
|
||||
$this->em->flush();
|
||||
$this->flashBag->add('success', 'tfa_u2f.key_added_successful');
|
||||
}
|
||||
|
||||
// generate new response, here we redirect the user to the fos user
|
||||
// profile
|
||||
$response = new RedirectResponse($this->router->generate('user_settings'));
|
||||
$event->setResponse($response);
|
||||
}
|
||||
}
|
|
@ -23,12 +23,22 @@ namespace App\Form\AdminPages;
|
|||
|
||||
use App\Entity\Base\NamedDBElement;
|
||||
use App\Form\Permissions\PermissionsType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
|
||||
class GroupAdminForm extends BaseEntityAdminForm
|
||||
{
|
||||
protected function additionalFormElements(FormBuilderInterface $builder, array $options, NamedDBElement $entity)
|
||||
{
|
||||
$is_new = null === $entity->getID();
|
||||
|
||||
$builder->add('enforce2FA', CheckboxType::class, ['required' => false,
|
||||
'label' => 'group.edit.enforce_2fa',
|
||||
'help' => 'entity.edit.enforce_2fa.help',
|
||||
'label_attr' => ['class' => 'checkbox-custom'],
|
||||
'disabled' => !$this->security->isGranted($is_new ? 'create' : 'edit', $entity)
|
||||
]);
|
||||
|
||||
$builder->add('permissions', PermissionsType::class, [
|
||||
'mapped' => false,
|
||||
'data' => $builder->getData(),
|
||||
|
|
94
src/Form/TFAGoogleSettingsType.php
Normal file
94
src/Form/TFAGoogleSettingsType.php
Normal file
|
@ -0,0 +1,94 @@
|
|||
<?php
|
||||
/**
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
|
||||
*/
|
||||
|
||||
namespace App\Form;
|
||||
|
||||
|
||||
use App\Entity\UserSystem\User;
|
||||
use App\Validator\Constraints\ValidGoogleAuthCode;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\ResetType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\Form\FormEvent;
|
||||
use Symfony\Component\Form\FormEvents;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
class TFAGoogleSettingsType extends AbstractType
|
||||
{
|
||||
|
||||
protected $translator;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
{
|
||||
$builder->addEventListener(FormEvents::PRE_SET_DATA, function(FormEvent $event) {
|
||||
$form = $event->getForm();
|
||||
/** @var User $user */
|
||||
$user = $event->getData();
|
||||
|
||||
//Only show setup fields, when google authenticator is not enabled
|
||||
if(!$user->isGoogleAuthenticatorEnabled()) {
|
||||
$form->add(
|
||||
'google_confirmation',
|
||||
TextType::class,
|
||||
[
|
||||
'mapped' => false,
|
||||
'attr' => ['maxlength' => '6', 'minlength' => '6', 'pattern' => '\d*', 'autocomplete' => 'off'],
|
||||
'constraints' => [new ValidGoogleAuthCode()]
|
||||
]
|
||||
);
|
||||
|
||||
$form->add(
|
||||
'googleAuthenticatorSecret',
|
||||
HiddenType::class,
|
||||
[
|
||||
'disabled' => false,
|
||||
]
|
||||
);
|
||||
|
||||
$form->add('submit', SubmitType::class, [
|
||||
'label' => 'tfa_google.enable'
|
||||
]);
|
||||
} else {
|
||||
$form->add('submit', SubmitType::class, [
|
||||
'label' =>'tfa_google.disable',
|
||||
'attr' => ['class' => 'btn-danger']
|
||||
]);
|
||||
}
|
||||
});
|
||||
|
||||
//$builder->add('cancel', ResetType::class);
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'data_class' => User::class,
|
||||
]);
|
||||
}
|
||||
}
|
59
src/Migrations/Version20191214153125.php
Normal file
59
src/Migrations/Version20191214153125.php
Normal file
|
@ -0,0 +1,59 @@
|
|||
<?php
|
||||
/**
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20191214153125 extends AbstractMigration
|
||||
{
|
||||
public function getDescription() : string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function up(Schema $schema) : void
|
||||
{
|
||||
// this up() migration is auto-generated, please modify it to your needs
|
||||
$this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'mysql', 'Migration can only be executed safely on \'mysql\'.');
|
||||
|
||||
$this->addSql('CREATE TABLE u2f_keys (id INT AUTO_INCREMENT NOT NULL, user_id INT DEFAULT NULL, key_handle VARCHAR(255) NOT NULL, public_key VARCHAR(255) NOT NULL, certificate LONGTEXT NOT NULL, counter VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, INDEX IDX_4F4ADB4BA76ED395 (user_id), UNIQUE INDEX user_unique (user_id, key_handle), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ENGINE = InnoDB');
|
||||
$this->addSql('ALTER TABLE u2f_keys ADD CONSTRAINT FK_4F4ADB4BA76ED395 FOREIGN KEY (user_id) REFERENCES `users` (id)');
|
||||
$this->addSql('ALTER TABLE `groups` ADD enforce_2fa TINYINT(1) NOT NULL');
|
||||
$this->addSql('ALTER TABLE users ADD google_authenticator_secret VARCHAR(255) DEFAULT NULL, ADD backup_codes LONGTEXT NOT NULL COMMENT \'(DC2Type:json)\', ADD backup_codes_generation_date DATETIME DEFAULT NULL, ADD trusted_device_cookie_version INT NOT NULL');
|
||||
}
|
||||
|
||||
public function down(Schema $schema) : void
|
||||
{
|
||||
// this down() migration is auto-generated, please modify it to your needs
|
||||
$this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'mysql', 'Migration can only be executed safely on \'mysql\'.');
|
||||
|
||||
$this->addSql('DROP TABLE u2f_keys');
|
||||
$this->addSql('ALTER TABLE `groups` DROP enforce_2fa');
|
||||
$this->addSql('ALTER TABLE `users` DROP google_authenticator_secret, DROP backup_codes, DROP backup_codes_generation_date, DROP trusted_device_cookie_version');
|
||||
}
|
||||
}
|
77
src/Services/TFA/BackupCodeGenerator.php
Normal file
77
src/Services/TFA/BackupCodeGenerator.php
Normal file
|
@ -0,0 +1,77 @@
|
|||
<?php
|
||||
/**
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
|
||||
*/
|
||||
|
||||
namespace App\Services\TFA;
|
||||
|
||||
/**
|
||||
* This class generates random backup codes for two factor authentication
|
||||
* @package App\Services\TFA
|
||||
*/
|
||||
class BackupCodeGenerator
|
||||
{
|
||||
protected $code_length;
|
||||
protected $code_count;
|
||||
|
||||
/**
|
||||
* BackupCodeGenerator constructor.
|
||||
* @param int $code_length How many characters a single code should have.
|
||||
* @param int $code_count How many codes are generated for a whole backup set.
|
||||
*/
|
||||
public function __construct(int $code_length, int $code_count)
|
||||
{
|
||||
if ($code_length > 32) {
|
||||
throw new \RuntimeException('Backup code can have maximum 32 digits!');
|
||||
}
|
||||
if ($code_length < 6) {
|
||||
throw new \RuntimeException('Code must have at least 6 digits to ensure security!');
|
||||
}
|
||||
|
||||
$this->code_count = $code_count;
|
||||
$this->code_length = $code_length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a single backup code.
|
||||
* It is a random hexadecimal value with the digit count configured in constructor
|
||||
* @return string The generated backup code (e.g. 1f3870be2)
|
||||
* @throws \Exception If no entropy source is available.
|
||||
*/
|
||||
public function generateSingleCode() : string
|
||||
{
|
||||
$bytes = random_bytes(32);
|
||||
return substr(md5($bytes), 0, $this->code_length);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a full backup code set. The code count can be configured in the constructor
|
||||
* @return string[] An array containing different backup codes.
|
||||
*/
|
||||
public function generateCodeSet() : array
|
||||
{
|
||||
$array = [];
|
||||
for($n=0; $n<$this->code_count; $n++) {
|
||||
$array[] = $this->generateSingleCode();
|
||||
}
|
||||
|
||||
return $array;
|
||||
}
|
||||
}
|
75
src/Services/TFA/BackupCodeManager.php
Normal file
75
src/Services/TFA/BackupCodeManager.php
Normal file
|
@ -0,0 +1,75 @@
|
|||
<?php
|
||||
/**
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
|
||||
*/
|
||||
|
||||
namespace App\Services\TFA;
|
||||
|
||||
|
||||
use App\Entity\UserSystem\User;
|
||||
|
||||
/**
|
||||
* This services offers methods to manage backup codes for two factor authentication
|
||||
* @package App\Services\TFA
|
||||
*/
|
||||
class BackupCodeManager
|
||||
{
|
||||
protected $backupCodeGenerator;
|
||||
|
||||
public function __construct(BackupCodeGenerator $backupCodeGenerator)
|
||||
{
|
||||
$this->backupCodeGenerator = $backupCodeGenerator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable backup codes for the given user, by generating a set of backup codes.
|
||||
* If the backup codes were already enabled before, they a
|
||||
* @param User $user
|
||||
*/
|
||||
public function enableBackupCodes(User $user)
|
||||
{
|
||||
if(empty($user->getBackupCodes())) {
|
||||
$this->regenerateBackupCodes($user);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable (remove) the backup codes when no other 2 factor authentication methods are enabled.
|
||||
* @param User $user
|
||||
*/
|
||||
public function disableBackupCodesIfUnused(User $user)
|
||||
{
|
||||
if($user->isGoogleAuthenticatorEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$user->setBackupCodes([]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a new set of backup codes for the user. If no backup codes were available before, new ones are
|
||||
* generated.
|
||||
* @param User $user The user for which the backup codes should be regenerated
|
||||
*/
|
||||
public function regenerateBackupCodes(User $user)
|
||||
{
|
||||
$codes = $this->backupCodeGenerator->generateCodeSet();
|
||||
$user->setBackupCodes($codes);
|
||||
}
|
||||
}
|
|
@ -33,6 +33,7 @@ use App\Services\MoneyFormatter;
|
|||
use App\Services\SIFormatter;
|
||||
use App\Services\TreeBuilder;
|
||||
use Symfony\Component\Serializer\SerializerInterface;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
use Twig\Extension\AbstractExtension;
|
||||
use Twig\TwigFilter;
|
||||
use Twig\TwigFunction;
|
||||
|
@ -49,13 +50,14 @@ class AppExtension extends AbstractExtension
|
|||
protected $amountFormatter;
|
||||
protected $attachmentURLGenerator;
|
||||
protected $FAIconGenerator;
|
||||
protected $translator;
|
||||
|
||||
public function __construct(EntityURLGenerator $entityURLGenerator, MarkdownParser $markdownParser,
|
||||
SerializerInterface $serializer, TreeBuilder $treeBuilder,
|
||||
MoneyFormatter $moneyFormatter,
|
||||
SIFormatter $SIFormatter, AmountFormatter $amountFormatter,
|
||||
AttachmentURLGenerator $attachmentURLGenerator,
|
||||
FAIconGenerator $FAIconGenerator)
|
||||
SerializerInterface $serializer, TreeBuilder $treeBuilder,
|
||||
MoneyFormatter $moneyFormatter,
|
||||
SIFormatter $SIFormatter, AmountFormatter $amountFormatter,
|
||||
AttachmentURLGenerator $attachmentURLGenerator,
|
||||
FAIconGenerator $FAIconGenerator, TranslatorInterface $translator)
|
||||
{
|
||||
$this->entityURLGenerator = $entityURLGenerator;
|
||||
$this->markdownParser = $markdownParser;
|
||||
|
@ -66,6 +68,7 @@ class AppExtension extends AbstractExtension
|
|||
$this->amountFormatter = $amountFormatter;
|
||||
$this->attachmentURLGenerator = $attachmentURLGenerator;
|
||||
$this->FAIconGenerator = $FAIconGenerator;
|
||||
$this->translator = $translator;
|
||||
}
|
||||
|
||||
public function getFilters()
|
||||
|
|
30
src/Validator/Constraints/ValidGoogleAuthCode.php
Normal file
30
src/Validator/Constraints/ValidGoogleAuthCode.php
Normal file
|
@ -0,0 +1,30 @@
|
|||
<?php
|
||||
/**
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
|
||||
*/
|
||||
|
||||
namespace App\Validator\Constraints;
|
||||
|
||||
|
||||
use Symfony\Component\Validator\Constraint;
|
||||
|
||||
class ValidGoogleAuthCode extends Constraint
|
||||
{
|
||||
|
||||
}
|
83
src/Validator/Constraints/ValidGoogleAuthCodeValidator.php
Normal file
83
src/Validator/Constraints/ValidGoogleAuthCodeValidator.php
Normal file
|
@ -0,0 +1,83 @@
|
|||
<?php
|
||||
/**
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
|
||||
*/
|
||||
|
||||
namespace App\Validator\Constraints;
|
||||
|
||||
|
||||
use App\Entity\UserSystem\User;
|
||||
use Scheb\TwoFactorBundle\Security\TwoFactor\Provider\Google\GoogleAuthenticator;
|
||||
use Symfony\Component\Form\Form;
|
||||
use Symfony\Component\Form\FormInterface;
|
||||
use Symfony\Component\Validator\Constraint;
|
||||
use Symfony\Component\Validator\ConstraintValidator;
|
||||
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
|
||||
use Symfony\Component\Validator\Exception\UnexpectedValueException;
|
||||
|
||||
class ValidGoogleAuthCodeValidator extends ConstraintValidator
|
||||
{
|
||||
|
||||
protected $googleAuthenticator;
|
||||
|
||||
public function __construct(GoogleAuthenticator $googleAuthenticator)
|
||||
{
|
||||
$this->googleAuthenticator = $googleAuthenticator;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function validate($value, Constraint $constraint)
|
||||
{
|
||||
if (!$constraint instanceof ValidGoogleAuthCode) {
|
||||
throw new UnexpectedTypeException($constraint, ValidGoogleAuthCode::class);
|
||||
}
|
||||
|
||||
if (null === $value || '' === $value) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!\is_string($value)) {
|
||||
throw new UnexpectedValueException($value, 'string');
|
||||
}
|
||||
|
||||
if(!ctype_digit($value)) {
|
||||
$this->context->addViolation('validator.google_code.only_digits_allowed');
|
||||
}
|
||||
|
||||
//Number must have 6 digits
|
||||
if(strlen($value) !== 6) {
|
||||
$this->context->addViolation('validator.google_code.wrong_digit_count');
|
||||
}
|
||||
|
||||
//Try to retrieve the user we want to check
|
||||
if($this->context->getObject() instanceof FormInterface &&
|
||||
$this->context->getObject()->getParent() instanceof FormInterface
|
||||
&& $this->context->getObject()->getParent()->getData() instanceof User) {
|
||||
$user = $this->context->getObject()->getParent()->getData();
|
||||
|
||||
//Check if the given code is valid
|
||||
if(!$this->googleAuthenticator->checkCode($user, $value)) {
|
||||
$this->context->addViolation('validator.google_code.wrong_code');
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue