From a4123bac284f52544ad0d2ffff0b0561a36f0e84 Mon Sep 17 00:00:00 2001 From: Guilherme Donato Date: Tue, 8 Jan 2019 18:03:47 -0200 Subject: [PATCH 01/40] added new configuration options PROCERGS#830 --- .../DependencyInjection/Configuration.php | 18 ++++++++++++++++++ .../LoginCidadaoPhoneVerificationExtension.php | 3 +++ .../Resources/config/services.yml | 1 + .../Service/PhoneVerificationOptions.php | 13 ++++++++++++- .../DependencyInjection/ConfigurationTest.php | 5 +++++ ...inCidadaoPhoneVerificationExtensionTest.php | 4 ++++ .../Service/PhoneVerificationOptionsTest.php | 5 ++++- 7 files changed, 47 insertions(+), 2 deletions(-) diff --git a/src/LoginCidadao/PhoneVerificationBundle/DependencyInjection/Configuration.php b/src/LoginCidadao/PhoneVerificationBundle/DependencyInjection/Configuration.php index c37e5d747..44d84f67b 100644 --- a/src/LoginCidadao/PhoneVerificationBundle/DependencyInjection/Configuration.php +++ b/src/LoginCidadao/PhoneVerificationBundle/DependencyInjection/Configuration.php @@ -63,6 +63,24 @@ public function getConfigTreeBuilder() ->end() ->end() ->end() + + ->arrayNode('blocklist') + ->addDefaultsIfNotSet() + ->children() + ->booleanNode('enable_auto_block') + ->defaultValue(true) + ->end() + // A phone will get block-listed after X accounts are using it + ->scalarNode('auto_block_limit') + ->defaultValue(10) + ->end() + // TODO: require_validation_threshold might be better suited as a child of login_cidadao_phone_verification + // User's will HAVE TO verify their phone number after X accounts are using it + ->scalarNode('require_validation_threshold') + ->defaultValue(3) + ->end() + ->end() + ->end() ->end(); // Here you should define the parameters that are allowed to diff --git a/src/LoginCidadao/PhoneVerificationBundle/DependencyInjection/LoginCidadaoPhoneVerificationExtension.php b/src/LoginCidadao/PhoneVerificationBundle/DependencyInjection/LoginCidadaoPhoneVerificationExtension.php index 575c6d41e..231e944b2 100644 --- a/src/LoginCidadao/PhoneVerificationBundle/DependencyInjection/LoginCidadaoPhoneVerificationExtension.php +++ b/src/LoginCidadao/PhoneVerificationBundle/DependencyInjection/LoginCidadaoPhoneVerificationExtension.php @@ -33,6 +33,9 @@ public function load(array $configs, ContainerBuilder $container) foreach ($config['sms'] as $key => $value) { $container->setParameter("lc.phone_verification.options.sms.{$key}", $value); } + foreach ($config['blocklist'] as $key => $value) { + $container->setParameter("lc.phone_verification.options.blocklist.{$key}", $value); + } $loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); $loader->load('services.yml'); diff --git a/src/LoginCidadao/PhoneVerificationBundle/Resources/config/services.yml b/src/LoginCidadao/PhoneVerificationBundle/Resources/config/services.yml index 24bb06720..4105dcbb4 100644 --- a/src/LoginCidadao/PhoneVerificationBundle/Resources/config/services.yml +++ b/src/LoginCidadao/PhoneVerificationBundle/Resources/config/services.yml @@ -9,6 +9,7 @@ services: - "%lc.phone_verification.options.code.use_upper%" - "%lc.phone_verification.options.sms.resend_timeout%" - "%lc.phone_verification.options.token.length%" + - "%lc.phone_verification.options.blocklist.require_validation_threshold%" lazy: true phone_verification: diff --git a/src/LoginCidadao/PhoneVerificationBundle/Service/PhoneVerificationOptions.php b/src/LoginCidadao/PhoneVerificationBundle/Service/PhoneVerificationOptions.php index 39d9b7c4f..1f3144baf 100644 --- a/src/LoginCidadao/PhoneVerificationBundle/Service/PhoneVerificationOptions.php +++ b/src/LoginCidadao/PhoneVerificationBundle/Service/PhoneVerificationOptions.php @@ -33,6 +33,9 @@ class PhoneVerificationOptions /** @var string */ private $verificationTokenLength; + /** @var int */ + private $enforceVerificationThreshold; + /** * PhoneVerificationOptions constructor. * @param int $length @@ -42,6 +45,7 @@ class PhoneVerificationOptions * @param bool $useUpperCase * @param $smsResendTimeout * @param $verificationTokenLength + * @param int $enforceVerificationThreshold */ public function __construct( $length, @@ -50,7 +54,8 @@ public function __construct( $useLowerCase, $useUpperCase, $smsResendTimeout, - $verificationTokenLength + $verificationTokenLength, + int $enforceVerificationThreshold ) { $this->length = $length; $this->useNumbers = $useNumbers; @@ -59,6 +64,7 @@ public function __construct( $this->useUpperCase = $useUpperCase; $this->smsResendTimeout = $smsResendTimeout; $this->verificationTokenLength = $verificationTokenLength; + $this->enforceVerificationThreshold = $enforceVerificationThreshold; } /** @@ -113,4 +119,9 @@ public function getVerificationTokenLength() { return $this->verificationTokenLength; } + + public function getEnforceVerificationThreshold(): int + { + return $this->enforceVerificationThreshold; + } } diff --git a/src/LoginCidadao/PhoneVerificationBundle/Tests/DependencyInjection/ConfigurationTest.php b/src/LoginCidadao/PhoneVerificationBundle/Tests/DependencyInjection/ConfigurationTest.php index 7ab532859..e7767d89f 100644 --- a/src/LoginCidadao/PhoneVerificationBundle/Tests/DependencyInjection/ConfigurationTest.php +++ b/src/LoginCidadao/PhoneVerificationBundle/Tests/DependencyInjection/ConfigurationTest.php @@ -33,6 +33,11 @@ public static function getSampleConfig() 'sms' => [ 'resend_timeout' => '+5 minutes', ], + 'blocklist' => [ + 'enable_auto_block' => true, + 'auto_block_limit' => 10, + 'require_validation_threshold' => 3, + ], ]; } diff --git a/src/LoginCidadao/PhoneVerificationBundle/Tests/DependencyInjection/LoginCidadaoPhoneVerificationExtensionTest.php b/src/LoginCidadao/PhoneVerificationBundle/Tests/DependencyInjection/LoginCidadaoPhoneVerificationExtensionTest.php index 63abe2f04..b6fd69dfc 100644 --- a/src/LoginCidadao/PhoneVerificationBundle/Tests/DependencyInjection/LoginCidadaoPhoneVerificationExtensionTest.php +++ b/src/LoginCidadao/PhoneVerificationBundle/Tests/DependencyInjection/LoginCidadaoPhoneVerificationExtensionTest.php @@ -81,5 +81,9 @@ public function testParametersLoaded() $config['verification_token']['length'], $container->getParameter('lc.phone_verification.options.token.length') ); + $this->assertEquals( + $config['blocklist']['require_validation_threshold'], + $container->getParameter('lc.phone_verification.options.blocklist.require_validation_threshold') + ); } } diff --git a/src/LoginCidadao/PhoneVerificationBundle/Tests/Service/PhoneVerificationOptionsTest.php b/src/LoginCidadao/PhoneVerificationBundle/Tests/Service/PhoneVerificationOptionsTest.php index a91dc66ee..f1d680356 100644 --- a/src/LoginCidadao/PhoneVerificationBundle/Tests/Service/PhoneVerificationOptionsTest.php +++ b/src/LoginCidadao/PhoneVerificationBundle/Tests/Service/PhoneVerificationOptionsTest.php @@ -24,6 +24,7 @@ public function testOptions() $caseSensitive = true; $smsResendTimeout = '+10 minutes'; $tokenLength = 5; + $enforceVerificationThreshold = 2; $options = new PhoneVerificationOptions( $length, @@ -32,7 +33,8 @@ public function testOptions() $useLower, $useUpper, $smsResendTimeout, - $tokenLength + $tokenLength, + $enforceVerificationThreshold ); $this->assertEquals($length, $options->getLength()); @@ -42,5 +44,6 @@ public function testOptions() $this->assertTrue($options->isCaseSensitive()); $this->assertEquals($smsResendTimeout, $options->getSmsResendTimeout()); $this->assertEquals($tokenLength, $options->getVerificationTokenLength()); + $this->assertEquals($enforceVerificationThreshold, $options->getEnforceVerificationThreshold()); } } From 79db348c49c38d92974405f8d7ac0b39b0e99ecf Mon Sep 17 00:00:00 2001 From: Guilherme Donato Date: Tue, 8 Jan 2019 18:04:37 -0200 Subject: [PATCH 02/40] creating new classes for the Blocklist PROCERGS#830 --- .../Entity/BlockedPhoneNumber.php | 72 ++++++++++++++ .../Entity/BlockedPhoneNumberRepository.php | 29 ++++++ .../Event/BlocklistSubscriber.php | 44 +++++++++ .../Model/BlockedPhoneNumberInterface.php | 37 ++++++++ .../Service/Blocklist.php | 93 +++++++++++++++++++ .../Service/BlocklistInterface.php | 25 +++++ .../Service/BlocklistOptions.php | 41 ++++++++ 7 files changed, 341 insertions(+) create mode 100644 src/LoginCidadao/PhoneVerificationBundle/Entity/BlockedPhoneNumber.php create mode 100644 src/LoginCidadao/PhoneVerificationBundle/Entity/BlockedPhoneNumberRepository.php create mode 100644 src/LoginCidadao/PhoneVerificationBundle/Event/BlocklistSubscriber.php create mode 100644 src/LoginCidadao/PhoneVerificationBundle/Model/BlockedPhoneNumberInterface.php create mode 100644 src/LoginCidadao/PhoneVerificationBundle/Service/Blocklist.php create mode 100644 src/LoginCidadao/PhoneVerificationBundle/Service/BlocklistInterface.php create mode 100644 src/LoginCidadao/PhoneVerificationBundle/Service/BlocklistOptions.php diff --git a/src/LoginCidadao/PhoneVerificationBundle/Entity/BlockedPhoneNumber.php b/src/LoginCidadao/PhoneVerificationBundle/Entity/BlockedPhoneNumber.php new file mode 100644 index 000000000..8af6e7cbf --- /dev/null +++ b/src/LoginCidadao/PhoneVerificationBundle/Entity/BlockedPhoneNumber.php @@ -0,0 +1,72 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace LoginCidadao\PhoneVerificationBundle\Entity; + +use Doctrine\ORM\Mapping as ORM; +use libphonenumber\PhoneNumber; +use LoginCidadao\CoreBundle\Model\PersonInterface; +use LoginCidadao\PhoneVerificationBundle\Model\BlockedPhoneNumberInterface; + +/** + * Class BlockedPhoneNumber + * + * @package LoginCidadao\PhoneVerificationBundle\Model + * + * @ORM\Entity(repositoryClass="LoginCidadao\PhoneVerificationBundle\Entity\BlockedPhoneNumberRepository") + * @ORM\Table(name="person") + * @ORM\HasLifecycleCallbacks + */ +class BlockedPhoneNumber implements BlockedPhoneNumberInterface +{ + private $id; + + /** + * @var PhoneNumber + */ + private $phoneNumber; + + /** + * @var PersonInterface + */ + private $blockedBy; + + /** + * @var \DateTime + */ + private $createdAt; + + public function __construct(PhoneNumber $phoneNumber, PersonInterface $blockedBy, \DateTime $createdAt) + { + $this->phoneNumber = $phoneNumber; + $this->blockedBy = $blockedBy; + $this->createdAt = $createdAt; + } + + public function getId() + { + return $this->id; + } + + public function getPhoneNumber(): PhoneNumber + { + return $this->phoneNumber; + } + + public function getBlockedBy(): PersonInterface + { + return $this->blockedBy; + } + + public function getCreatedAt(): \DateTime + { + return $this->createdAt; + } +} diff --git a/src/LoginCidadao/PhoneVerificationBundle/Entity/BlockedPhoneNumberRepository.php b/src/LoginCidadao/PhoneVerificationBundle/Entity/BlockedPhoneNumberRepository.php new file mode 100644 index 000000000..51e417169 --- /dev/null +++ b/src/LoginCidadao/PhoneVerificationBundle/Entity/BlockedPhoneNumberRepository.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace LoginCidadao\PhoneVerificationBundle\Entity; + +use Doctrine\ORM\EntityRepository; +use libphonenumber\PhoneNumber; + +class BlockedPhoneNumberRepository extends EntityRepository +{ + /** + * @param PhoneNumber $phoneNumber + * @return BlockedPhoneNumber + */ + public function findByPhone(PhoneNumber $phoneNumber): BlockedPhoneNumber + { + /** @var BlockedPhoneNumber $blockedPhone */ + $blockedPhone = $this->findOneBy(['phoneNumber' => $phoneNumber]); + + return $blockedPhone; + } +} diff --git a/src/LoginCidadao/PhoneVerificationBundle/Event/BlocklistSubscriber.php b/src/LoginCidadao/PhoneVerificationBundle/Event/BlocklistSubscriber.php new file mode 100644 index 000000000..104c3ab99 --- /dev/null +++ b/src/LoginCidadao/PhoneVerificationBundle/Event/BlocklistSubscriber.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace LoginCidadao\PhoneVerificationBundle\Event; + +use libphonenumber\PhoneNumber; +use LoginCidadao\CoreBundle\Security\User\Manager\UserManager; +use LoginCidadao\PhoneVerificationBundle\PhoneVerificationEvents; +use LoginCidadao\PhoneVerificationBundle\Service\BlocklistInterface; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; + +class BlocklistSubscriber implements EventSubscriberInterface +{ + /** @var BlocklistInterface */ + private $blocklist; + + /** @var UserManager */ + private $userManager; + + /** + * @inheritDoc + */ + public static function getSubscribedEvents() + { + return [ + PhoneVerificationEvents::PHONE_CHANGED => 'onPhoneChange', + ]; + } + + public function onPhoneChange(PhoneChangedEvent $event) + { + $phone = $event->getPerson()->getMobile(); + if ($phone instanceof PhoneNumber && $this->blocklist->isBlocked($phone)) { + $this->userManager->blockUsersByPhone($phone); + } + } +} diff --git a/src/LoginCidadao/PhoneVerificationBundle/Model/BlockedPhoneNumberInterface.php b/src/LoginCidadao/PhoneVerificationBundle/Model/BlockedPhoneNumberInterface.php new file mode 100644 index 000000000..9909f8b6a --- /dev/null +++ b/src/LoginCidadao/PhoneVerificationBundle/Model/BlockedPhoneNumberInterface.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace LoginCidadao\PhoneVerificationBundle\Model; + +use libphonenumber\PhoneNumber; +use LoginCidadao\CoreBundle\Model\PersonInterface; + +interface BlockedPhoneNumberInterface +{ + /** + * BlockedPhoneNumberInterface constructor. + * @param PhoneNumber $phoneNumber + * @param PersonInterface $blockedBy + * @param \DateTime $createdAt + */ + public function __construct(PhoneNumber $phoneNumber, PersonInterface $blockedBy, \DateTime $createdAt); + + public function getPhoneNumber(): PhoneNumber; + + /** + * @return PersonInterface + */ + public function getBlockedBy(): PersonInterface; + + /** + * @return \DateTime + */ + public function getCreatedAt(): \DateTime; +} diff --git a/src/LoginCidadao/PhoneVerificationBundle/Service/Blocklist.php b/src/LoginCidadao/PhoneVerificationBundle/Service/Blocklist.php new file mode 100644 index 000000000..c1820e66b --- /dev/null +++ b/src/LoginCidadao/PhoneVerificationBundle/Service/Blocklist.php @@ -0,0 +1,93 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace LoginCidadao\PhoneVerificationBundle\Service; + +use Doctrine\ORM\EntityManagerInterface; +use libphonenumber\PhoneNumber; +use LoginCidadao\CoreBundle\Entity\PersonRepository; +use LoginCidadao\CoreBundle\Mailer\TwigSwiftMailer; +use LoginCidadao\CoreBundle\Model\PersonInterface; +use LoginCidadao\CoreBundle\Security\User\Manager\UserManager; +use LoginCidadao\PhoneVerificationBundle\Entity\BlockedPhoneNumber; +use LoginCidadao\PhoneVerificationBundle\Entity\BlockedPhoneNumberRepository; +use LoginCidadao\PhoneVerificationBundle\Model\BlockedPhoneNumberInterface; + +class Blocklist implements BlocklistInterface +{ + /** @var UserManager */ + private $userManager; + + /** @var TwigSwiftMailer */ + private $mailer; + + /** @var BlockedPhoneNumberRepository */ + private $blockedPhoneRepository; + + /** @var PersonRepository */ + private $personRepository; + + /** @var EntityManagerInterface */ + private $em; + + /** @var BlocklistOptions */ + private $options; + + public function isBlocked(PhoneNumber $phoneNumber): bool + { + return $this->isManuallyBlocked($phoneNumber) || $this->isBlockedAutomatically($phoneNumber); + } + + public function blockByPhone(PhoneNumber $phoneNumber): array + { + $blockedUsers = $this->userManager->blockUsersByPhone($phoneNumber); + $this->notifyBlockedUsers($blockedUsers); + + return $blockedUsers; + } + + public function addBlockedPhoneNumber( + PhoneNumber $phoneNumber, + PersonInterface $blockedBy + ): BlockedPhoneNumberInterface { + $blockedPhoneNumber = new BlockedPhoneNumber($phoneNumber, $blockedBy, new \DateTime()); + $this->em->persist($blockedPhoneNumber); + $this->em->flush(); + + return $blockedPhoneNumber; + } + + /** + * @param PersonInterface[] $blockedUsers + */ + private function notifyBlockedUsers(array $blockedUsers) + { + foreach ($blockedUsers as $person) { + $this->mailer->sendAccountBlockedMessage($person); + } + } + + private function isManuallyBlocked(PhoneNumber $phoneNumber): bool + { + return $this->blockedPhoneRepository->findByPhone($phoneNumber) instanceof PhoneNumber; + } + + private function isBlockedAutomatically(PhoneNumber $phoneNumber): bool + { + if ($this->options->isAutoBlockEnabled()) { + $autoBlockLimit = $this->options->getAutoBlockPhoneLimit(); + + // TODO: count ONLY verified phones!!!!! + return $this->personRepository->countByPhone($phoneNumber) >= $autoBlockLimit; + } + + return false; + } +} diff --git a/src/LoginCidadao/PhoneVerificationBundle/Service/BlocklistInterface.php b/src/LoginCidadao/PhoneVerificationBundle/Service/BlocklistInterface.php new file mode 100644 index 000000000..3788652b4 --- /dev/null +++ b/src/LoginCidadao/PhoneVerificationBundle/Service/BlocklistInterface.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace LoginCidadao\PhoneVerificationBundle\Service; + +use libphonenumber\PhoneNumber; +use LoginCidadao\CoreBundle\Model\PersonInterface; + +interface BlocklistInterface +{ + public function isBlocked(PhoneNumber $phoneNumber): bool; + + /** + * @param PhoneNumber $phoneNumber + * @return PersonInterface[] + */ + public function blockByPhone(PhoneNumber $phoneNumber): array; +} diff --git a/src/LoginCidadao/PhoneVerificationBundle/Service/BlocklistOptions.php b/src/LoginCidadao/PhoneVerificationBundle/Service/BlocklistOptions.php new file mode 100644 index 000000000..6e96f7af0 --- /dev/null +++ b/src/LoginCidadao/PhoneVerificationBundle/Service/BlocklistOptions.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace LoginCidadao\PhoneVerificationBundle\Service; + +class BlocklistOptions +{ + /** @var int */ + private $autoBlockPhoneLimit; + + /** + * BlocklistOptions constructor. + * @param int $autoBlockPhoneLimit + */ + public function __construct(int $autoBlockPhoneLimit) + { + $this->autoBlockPhoneLimit = $autoBlockPhoneLimit; + } + + public function isAutoBlockEnabled(): bool + { + $limit = $this->getAutoBlockPhoneLimit(); + + return null !== $limit && $limit > 0; + } + + /** + * @return int + */ + public function getAutoBlockPhoneLimit(): int + { + return $this->autoBlockPhoneLimit; + } +} From 807f5547af1d331db98426ae6150152216103dfb Mon Sep 17 00:00:00 2001 From: Guilherme Donato Date: Wed, 9 Jan 2019 16:41:43 -0200 Subject: [PATCH 03/40] allowing users to change unverified phone numbers PROCERGS#830 --- .../CoreBundle/Controller/TaskController.php | 3 +- .../CoreBundle/Form/Type/ProfileFormType.php | 106 +++++++++--------- .../Controller/VerificationController.php | 59 +++++++++- .../DependencyInjection/Configuration.php | 9 +- ...LoginCidadaoPhoneVerificationExtension.php | 1 + .../Entity/BlockedPhoneNumberRepository.php | 6 + .../Event/TaskSubscriber.php | 5 +- .../Form/PhoneNumberFormType.php | 44 ++++++++ .../Form/PhoneVerificationType.php | 20 ++-- .../Model/ConfirmPhoneTask.php | 10 +- .../Resources/config/services.yml | 8 +- .../Resources/translations/messages.en.yml | 4 + .../Resources/translations/messages.pt_BR.yml | 4 + .../views/Verification/verify.html.twig | 54 +++++++-- .../Service/PhoneVerificationService.php | 45 +++++++- .../PhoneVerificationServiceInterface.php | 8 ++ .../DependencyInjection/ConfigurationTest.php | 2 +- ...nCidadaoPhoneVerificationExtensionTest.php | 4 +- .../Tests/Entity/BlockedPhoneNumberTest.php | 33 ++++++ .../Tests/Event/TaskSubscriberTest.php | 12 +- .../Tests/Form/PhoneNumberFormTypeTest.php | 54 +++++++++ .../Service/PhoneVerificationServiceTest.php | 31 ++++- 22 files changed, 420 insertions(+), 102 deletions(-) create mode 100644 src/LoginCidadao/PhoneVerificationBundle/Form/PhoneNumberFormType.php create mode 100644 src/LoginCidadao/PhoneVerificationBundle/Tests/Entity/BlockedPhoneNumberTest.php create mode 100644 src/LoginCidadao/PhoneVerificationBundle/Tests/Form/PhoneNumberFormTypeTest.php diff --git a/src/LoginCidadao/CoreBundle/Controller/TaskController.php b/src/LoginCidadao/CoreBundle/Controller/TaskController.php index 1575539d6..4ed2356ae 100644 --- a/src/LoginCidadao/CoreBundle/Controller/TaskController.php +++ b/src/LoginCidadao/CoreBundle/Controller/TaskController.php @@ -8,6 +8,7 @@ use FOS\UserBundle\Util\TokenGenerator; use FOS\UserBundle\Event\GetResponseUserEvent; use LoginCidadao\CoreBundle\Entity\Person; +use LoginCidadao\CoreBundle\Form\Type\EmailFormType; use LoginCidadao\CoreBundle\Model\ConfirmEmailTask; use LoginCidadao\CoreBundle\Model\PersonInterface; use LoginCidadao\TaskStackBundle\Model\RouteTaskTarget; @@ -57,7 +58,7 @@ public function confirmEmailAction(Request $request) $event = new GetResponseUserEvent($person, $request); $dispatcher->dispatch(FOSUserEvents::PROFILE_EDIT_INITIALIZE, $event); - $form = $this->createForm('LoginCidadao\CoreBundle\Form\Type\EmailFormType', $person); + $form = $this->createForm(EmailFormType::class, $person); $response = null; $form->handleRequest($request); diff --git a/src/LoginCidadao/CoreBundle/Form/Type/ProfileFormType.php b/src/LoginCidadao/CoreBundle/Form/Type/ProfileFormType.php index 2a6aa382f..1a6de27bd 100644 --- a/src/LoginCidadao/CoreBundle/Form/Type/ProfileFormType.php +++ b/src/LoginCidadao/CoreBundle/Form/Type/ProfileFormType.php @@ -12,10 +12,17 @@ use Doctrine\ORM\EntityManagerInterface; use libphonenumber\PhoneNumberFormat; +use Misd\PhoneNumberBundle\Form\Type\PhoneNumberType; +use Symfony\Bridge\Doctrine\Form\Type\EntityType; +use Symfony\Component\Form\Extension\Core\Type\BirthdayType; +use Symfony\Component\Form\Extension\Core\Type\EmailType; +use Symfony\Component\Form\Extension\Core\Type\HiddenType; +use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\Form\FormBuilderInterface; use FOS\UserBundle\Form\Type\ProfileFormType as BaseType; use Doctrine\ORM\EntityRepository; use LoginCidadao\CoreBundle\Entity\Country; +use Vich\UploaderBundle\Form\Type\VichFileType; class ProfileFormType extends BaseType { @@ -41,68 +48,59 @@ public function buildForm(FormBuilderInterface $builder, array $options) 'iso2' => $this->defaultCountryIso2, )); - $emailType = 'Symfony\Component\Form\Extension\Core\Type\EmailType'; - $textType = 'Symfony\Component\Form\Extension\Core\Type\TextType'; + $emailType = EmailType::class; + $textType = TextType::class; $builder ->add('email', $emailType, ['label' => 'form.email', 'translation_domain' => 'FOSUserBundle']) ->add('firstName', $textType, ['label' => 'form.firstName', 'translation_domain' => 'FOSUserBundle']) ->add('surname', $textType, ['label' => 'form.surname', 'translation_domain' => 'FOSUserBundle']) - ->add('birthdate', - 'Symfony\Component\Form\Extension\Core\Type\BirthdayType', - [ - 'required' => false, - 'format' => 'dd/MM/yyyy', - 'widget' => 'single_text', - 'label' => 'form.birthdate', - 'translation_domain' => 'FOSUserBundle', - 'attr' => ['pattern' => '[0-9/]*', 'class' => 'form-control birthdate'], - ]) - ->add('mobile', 'Misd\PhoneNumberBundle\Form\Type\PhoneNumberType', - [ - 'required' => false, - 'label' => 'person.form.mobile.label', - 'attr' => [ - 'class' => 'form-control intl-tel', - 'placeholder' => 'person.form.mobile.placeholder', - ], - 'label_attr' => ['class' => 'intl-tel-label'], - 'format' => PhoneNumberFormat::E164, - ]) - ->add('image', 'Vich\UploaderBundle\Form\Type\VichFileType', - [ - 'required' => false, - 'allow_delete' => true, // not mandatory, default is true - 'download_uri' => true,// not mandatory, default is true - ]) - ->add('placeOfBirth', - 'LoginCidadao\CoreBundle\Form\Type\CitySelectorComboType', - [ - 'required' => false, - 'level' => 'city', - 'city_label' => 'Place of birth - City', - 'state_label' => 'Place of birth - State', - 'country_label' => 'Place of birth - Country', - ]); - $builder->add('nationality', - 'Symfony\Bridge\Doctrine\Form\Type\EntityType', - [ + ->add('birthdate', BirthdayType::class, [ 'required' => false, - 'class' => 'LoginCidadaoCoreBundle:Country', - 'choice_label' => 'name', - 'preferred_choices' => [$country], - 'choice_translation_domain' => true, - 'query_builder' => function (EntityRepository $er) { - return $er->createQueryBuilder('u') - ->where('u.reviewed = :reviewed') - ->setParameter('reviewed', Country::REVIEWED_OK) - ->orderBy('u.name', 'ASC'); - }, - 'label' => 'Nationality', + 'format' => 'dd/MM/yyyy', + 'widget' => 'single_text', + 'label' => 'form.birthdate', + 'translation_domain' => 'FOSUserBundle', + 'attr' => ['pattern' => '[0-9/]*', 'class' => 'form-control birthdate'], + ]) + ->add('mobile', PhoneNumberType::class, [ + 'required' => false, + 'label' => 'person.form.mobile.label', + 'attr' => [ + 'class' => 'form-control intl-tel', + 'placeholder' => 'person.form.mobile.placeholder', + ], + 'label_attr' => ['class' => 'intl-tel-label'], + 'format' => PhoneNumberFormat::E164, + ]) + ->add('image', VichFileType::class, [ + 'required' => false, + 'allow_delete' => true, // not mandatory, default is true + 'download_uri' => true,// not mandatory, default is true + ]) + ->add('placeOfBirth', CitySelectorComboType::class, [ + 'required' => false, + 'level' => 'city', + 'city_label' => 'Place of birth - City', + 'state_label' => 'Place of birth - State', + 'country_label' => 'Place of birth - Country', ]); + $builder->add('nationality', EntityType::class, [ + 'required' => false, + 'class' => 'LoginCidadaoCoreBundle:Country', + 'choice_label' => 'name', + 'preferred_choices' => [$country], + 'choice_translation_domain' => true, + 'query_builder' => function (EntityRepository $er) { + return $er->createQueryBuilder('u') + ->where('u.reviewed = :reviewed') + ->setParameter('reviewed', Country::REVIEWED_OK) + ->orderBy('u.name', 'ASC'); + }, + 'label' => 'Nationality', + ]); - $builder->add('defaultCountry', - 'Symfony\Component\Form\Extension\Core\Type\HiddenType', + $builder->add('defaultCountry', HiddenType::class, ["data" => $country->getId(), "required" => false, "mapped" => false] ); } diff --git a/src/LoginCidadao/PhoneVerificationBundle/Controller/VerificationController.php b/src/LoginCidadao/PhoneVerificationBundle/Controller/VerificationController.php index 61d06ce02..61ca70125 100644 --- a/src/LoginCidadao/PhoneVerificationBundle/Controller/VerificationController.php +++ b/src/LoginCidadao/PhoneVerificationBundle/Controller/VerificationController.php @@ -10,13 +10,23 @@ namespace LoginCidadao\PhoneVerificationBundle\Controller; +use FOS\UserBundle\Event\FilterUserResponseEvent; +use FOS\UserBundle\Event\FormEvent; +use FOS\UserBundle\Event\GetResponseUserEvent; +use FOS\UserBundle\FOSUserEvents; +use FOS\UserBundle\Model\UserManagerInterface; +use LoginCidadao\CoreBundle\Model\PersonInterface; +use LoginCidadao\CoreBundle\Security\User\Manager\UserManager; use LoginCidadao\PhoneVerificationBundle\Exception\VerificationNotSentException; +use LoginCidadao\PhoneVerificationBundle\Form\PhoneNumberFormType; +use LoginCidadao\PhoneVerificationBundle\Form\PhoneVerificationType; use LoginCidadao\PhoneVerificationBundle\Model\PhoneVerificationInterface; use LoginCidadao\PhoneVerificationBundle\Service\PhoneVerificationServiceInterface; use LoginCidadao\TaskStackBundle\Service\TaskStackManagerInterface; use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\Form\FormError; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Exception\TooManyRequestsHttpException; @@ -43,7 +53,10 @@ public function verifyAction(Request $request, $id) return $this->noVerificationOrVerified($request); } - $form = $this->createForm('LoginCidadao\PhoneVerificationBundle\Form\PhoneVerificationType'); + $editPhoneForm = $this->createForm(PhoneNumberFormType::class, $verification->getPerson(), [ + 'action' => $this->generateUrl('lc_phone_verification_edit_phone', ['verificationId' => $id]), + ]); + $form = $this->createForm(PhoneVerificationType::class); $form->handleRequest($request); $verified = false; @@ -62,8 +75,15 @@ public function verifyAction(Request $request, $id) } $nextResend = $this->getNextResendDate($verification); + $mandatory = $phoneVerificationService->isVerificationMandatory($verification); - return ['verification' => $verification, 'nextResend' => $nextResend, 'form' => $form->createView()]; + return [ + 'verification' => $verification, + 'nextResend' => $nextResend, + 'mandatory' => $mandatory, + 'form' => $form->createView(), + 'editPhoneForm' => $editPhoneForm->createView(), + ]; } /** @@ -112,6 +132,41 @@ public function resendAction(Request $request, $id) return $this->redirectToRoute('lc_verify_phone', ['id' => $id]); } + /** + * @Route("/task/verify-phone/{verificationId}/edit-phone", name="lc_phone_verification_edit_phone") + */ + public function editPhoneAction(Request $request, $verificationId) + { + /** @var PersonInterface $person */ + $person = $this->getUser(); + $response = $this->redirectToRoute('lc_verify_phone', ['id' => $verificationId]); + + /** @var EventDispatcherInterface $dispatcher */ + $dispatcher = $this->get('event_dispatcher'); + $event = new GetResponseUserEvent($person, $request); + $dispatcher->dispatch(FOSUserEvents::PROFILE_EDIT_INITIALIZE, $event); + + $editPhoneForm = $this->createForm(PhoneNumberFormType::class, $person, [ + 'action' => $this->generateUrl('lc_phone_verification_edit_phone', ['verificationId' => $verificationId]), + ]); + $editPhoneForm->handleRequest($request); + if ($editPhoneForm->isValid()) { + /** @var $userManager UserManager */ + $userManager = $this->get('lc.user_manager'); + + $event = new FormEvent($editPhoneForm, $request); + $dispatcher->dispatch(FOSUserEvents::PROFILE_EDIT_SUCCESS, $event); + + $userManager->updateUser($person); + + $event = new FilterUserResponseEvent($person, $request, $response); + $dispatcher->dispatch(FOSUserEvents::PROFILE_EDIT_COMPLETED, $event); + $response = $event->getResponse(); + } + + return $response; + } + /** * @Route("/task/verify-phone/{id}/skip", name="lc_phone_verification_skip") * @Template() diff --git a/src/LoginCidadao/PhoneVerificationBundle/DependencyInjection/Configuration.php b/src/LoginCidadao/PhoneVerificationBundle/DependencyInjection/Configuration.php index 44d84f67b..5df9a4b29 100644 --- a/src/LoginCidadao/PhoneVerificationBundle/DependencyInjection/Configuration.php +++ b/src/LoginCidadao/PhoneVerificationBundle/DependencyInjection/Configuration.php @@ -25,6 +25,10 @@ public function getConfigTreeBuilder() ->booleanNode('enabled') ->defaultFalse() ->end() + // User's will HAVE TO verify their phone number after X accounts are using it + ->scalarNode('require_validation_threshold') + ->defaultValue(3) + ->end() ->arrayNode('verification_code') ->addDefaultsIfNotSet() ->children() @@ -74,11 +78,6 @@ public function getConfigTreeBuilder() ->scalarNode('auto_block_limit') ->defaultValue(10) ->end() - // TODO: require_validation_threshold might be better suited as a child of login_cidadao_phone_verification - // User's will HAVE TO verify their phone number after X accounts are using it - ->scalarNode('require_validation_threshold') - ->defaultValue(3) - ->end() ->end() ->end() ->end(); diff --git a/src/LoginCidadao/PhoneVerificationBundle/DependencyInjection/LoginCidadaoPhoneVerificationExtension.php b/src/LoginCidadao/PhoneVerificationBundle/DependencyInjection/LoginCidadaoPhoneVerificationExtension.php index 231e944b2..4afe1bdfc 100644 --- a/src/LoginCidadao/PhoneVerificationBundle/DependencyInjection/LoginCidadaoPhoneVerificationExtension.php +++ b/src/LoginCidadao/PhoneVerificationBundle/DependencyInjection/LoginCidadaoPhoneVerificationExtension.php @@ -23,6 +23,7 @@ public function load(array $configs, ContainerBuilder $container) $config = $this->processConfiguration($configuration, $configs); $container->setParameter("lc.phone_verification.options.enabled", $config['enabled']); + $container->setParameter("lc.phone_verification.options.require_validation_threshold", $config['require_validation_threshold']); foreach ($config['verification_code'] as $key => $value) { $container->setParameter("lc.phone_verification.options.code.{$key}", $value); diff --git a/src/LoginCidadao/PhoneVerificationBundle/Entity/BlockedPhoneNumberRepository.php b/src/LoginCidadao/PhoneVerificationBundle/Entity/BlockedPhoneNumberRepository.php index 51e417169..d4f2022bb 100644 --- a/src/LoginCidadao/PhoneVerificationBundle/Entity/BlockedPhoneNumberRepository.php +++ b/src/LoginCidadao/PhoneVerificationBundle/Entity/BlockedPhoneNumberRepository.php @@ -13,6 +13,12 @@ use Doctrine\ORM\EntityRepository; use libphonenumber\PhoneNumber; +/** + * Class BlockedPhoneNumberRepository + * @package LoginCidadao\PhoneVerificationBundle\Entity + * + * @codeCoverageIgnore + */ class BlockedPhoneNumberRepository extends EntityRepository { /** diff --git a/src/LoginCidadao/PhoneVerificationBundle/Event/TaskSubscriber.php b/src/LoginCidadao/PhoneVerificationBundle/Event/TaskSubscriber.php index 1e0e3370d..95bf6314d 100644 --- a/src/LoginCidadao/PhoneVerificationBundle/Event/TaskSubscriber.php +++ b/src/LoginCidadao/PhoneVerificationBundle/Event/TaskSubscriber.php @@ -11,6 +11,7 @@ namespace LoginCidadao\PhoneVerificationBundle\Event; use FOS\OAuthServerBundle\Security\Authentication\Token\OAuthToken; +use LoginCidadao\CoreBundle\Entity\PersonRepository; use LoginCidadao\CoreBundle\Model\PersonInterface; use LoginCidadao\PhoneVerificationBundle\Entity\PhoneVerification; use LoginCidadao\PhoneVerificationBundle\Exception\VerificationNotSentException; @@ -94,6 +95,8 @@ public function onGetTasks(GetTasksEvent $event) } } - $event->addTaskIfStackEmpty(new ConfirmPhoneTask($pendingVerification->getId())); + $isMandatory = $this->phoneVerificationService->isVerificationMandatory($pendingVerification); + $task = new ConfirmPhoneTask($pendingVerification->getId(), $isMandatory); + $event->addTaskIfStackEmpty($task); } } diff --git a/src/LoginCidadao/PhoneVerificationBundle/Form/PhoneNumberFormType.php b/src/LoginCidadao/PhoneVerificationBundle/Form/PhoneNumberFormType.php new file mode 100644 index 000000000..a981de00a --- /dev/null +++ b/src/LoginCidadao/PhoneVerificationBundle/Form/PhoneNumberFormType.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace LoginCidadao\PhoneVerificationBundle\Form; + +use libphonenumber\PhoneNumberFormat; +use LoginCidadao\CoreBundle\Entity\Person; +use Misd\PhoneNumberBundle\Form\Type\PhoneNumberType; +use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\OptionsResolver\OptionsResolver; + +class PhoneNumberFormType extends AbstractType +{ + public function buildForm(FormBuilderInterface $builder, array $options) + { + $builder + ->add('mobile', PhoneNumberType::class, [ + 'required' => false, + 'label' => 'person.form.mobile.label', + 'attr' => [ + 'class' => 'form-control intl-tel', + 'placeholder' => 'person.form.mobile.placeholder', + ], + 'label_attr' => ['class' => 'intl-tel-label'], + 'format' => PhoneNumberFormat::E164, + ]); + } + + public function configureOptions(OptionsResolver $resolver) + { + $resolver->setDefaults([ + 'data_class' => Person::class, + 'validation_groups' => ['LoginCidadaoEmailForm'], + ]); + } +} diff --git a/src/LoginCidadao/PhoneVerificationBundle/Form/PhoneVerificationType.php b/src/LoginCidadao/PhoneVerificationBundle/Form/PhoneVerificationType.php index 0c0dd9106..ef06da665 100644 --- a/src/LoginCidadao/PhoneVerificationBundle/Form/PhoneVerificationType.php +++ b/src/LoginCidadao/PhoneVerificationBundle/Form/PhoneVerificationType.php @@ -10,8 +10,10 @@ namespace LoginCidadao\PhoneVerificationBundle\Form; +use LoginCidadao\CoreBundle\Form\Type\TelType; use LoginCidadao\PhoneVerificationBundle\Service\PhoneVerificationOptions; use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\Form\FormBuilderInterface; class PhoneVerificationType extends AbstractType @@ -34,20 +36,14 @@ public function buildForm(FormBuilderInterface $builder, array $options) || $this->phoneVerificationOptions->isUseLowerCase(); if ($useLetters) { - $type = 'Symfony\Component\Form\Extension\Core\Type\TextType'; + $type = TextType::class; } else { - $type = 'LoginCidadao\CoreBundle\Form\Type\TelType'; + $type = TelType::class; } - $builder->add( - 'verificationCode', - $type, - [ - 'label' => 'tasks.verify_phone.form.verificationCode.label', - 'attr' => [ - 'placeholder' => 'tasks.verify_phone.form.verificationCode.placeholder', - ], - ] - ); + $builder->add('verificationCode', $type, [ + 'label' => 'tasks.verify_phone.form.verificationCode.label', + 'attr' => ['placeholder' => 'tasks.verify_phone.form.verificationCode.placeholder'], + ]); } } diff --git a/src/LoginCidadao/PhoneVerificationBundle/Model/ConfirmPhoneTask.php b/src/LoginCidadao/PhoneVerificationBundle/Model/ConfirmPhoneTask.php index 5fe587bf3..3cf4b5925 100644 --- a/src/LoginCidadao/PhoneVerificationBundle/Model/ConfirmPhoneTask.php +++ b/src/LoginCidadao/PhoneVerificationBundle/Model/ConfirmPhoneTask.php @@ -22,13 +22,18 @@ class ConfirmPhoneTask extends AbstractTask /** @var TaskTargetInterface */ private $target; + /** @var bool */ + private $mandatory; + /** * ConfirmPhoneTask constructor. * @param mixed $verificationId + * @param bool $mandatory */ - public function __construct($verificationId) + public function __construct($verificationId, bool $mandatory = false) { $this->verificationId = $verificationId; + $this->mandatory = $mandatory; $this->target = new RouteTaskTarget('lc_verify_phone', ['id' => $verificationId]); } @@ -54,6 +59,7 @@ public function getRoutes() 'lc_phone_verification_code_resend', 'lc_phone_verification_verify_link', 'lc_phone_verification_skip', + 'lc_phone_verification_edit_phone', ]; } @@ -70,6 +76,6 @@ public function getTarget() */ public function isMandatory() { - return false; + return $this->mandatory; } } diff --git a/src/LoginCidadao/PhoneVerificationBundle/Resources/config/services.yml b/src/LoginCidadao/PhoneVerificationBundle/Resources/config/services.yml index 4105dcbb4..042760a62 100644 --- a/src/LoginCidadao/PhoneVerificationBundle/Resources/config/services.yml +++ b/src/LoginCidadao/PhoneVerificationBundle/Resources/config/services.yml @@ -9,7 +9,13 @@ services: - "%lc.phone_verification.options.code.use_upper%" - "%lc.phone_verification.options.sms.resend_timeout%" - "%lc.phone_verification.options.token.length%" - - "%lc.phone_verification.options.blocklist.require_validation_threshold%" + - "%lc.phone_verification.options.require_validation_threshold%" + lazy: true + + phone_verification.blocklist.options: + class: LoginCidadao\PhoneVerificationBundle\Service\BlocklistOptions + arguments: + - "%lc.phone_verification.options.blocklist.auto_block_limit%" lazy: true phone_verification: diff --git a/src/LoginCidadao/PhoneVerificationBundle/Resources/translations/messages.en.yml b/src/LoginCidadao/PhoneVerificationBundle/Resources/translations/messages.en.yml index 401807177..d34f3950f 100644 --- a/src/LoginCidadao/PhoneVerificationBundle/Resources/translations/messages.en.yml +++ b/src/LoginCidadao/PhoneVerificationBundle/Resources/translations/messages.en.yml @@ -31,3 +31,7 @@ tasks: errors: too_many_requests: It's not possible to resend the verification code yet. Please, retry later. unavailable: The SMS service is not available right now. Please, wait a few minutes and try again. + + edit: + show_form: Click here to change your phone number + submit: Change Phone Number diff --git a/src/LoginCidadao/PhoneVerificationBundle/Resources/translations/messages.pt_BR.yml b/src/LoginCidadao/PhoneVerificationBundle/Resources/translations/messages.pt_BR.yml index 91c2b59c2..86ad36ae7 100644 --- a/src/LoginCidadao/PhoneVerificationBundle/Resources/translations/messages.pt_BR.yml +++ b/src/LoginCidadao/PhoneVerificationBundle/Resources/translations/messages.pt_BR.yml @@ -31,3 +31,7 @@ tasks: errors: too_many_requests: Ainda não é possível reenviar o código de verificação. Aguarde alguns minutos e tente novamente. unavailable: O serviço de SMS está indisponível no momento. Por favor, aguarde alguns minutos e tente novamente. + + edit: + show_form: Clique aqui para alterar seu telefone + submit: Alterar Telefone diff --git a/src/LoginCidadao/PhoneVerificationBundle/Resources/views/Verification/verify.html.twig b/src/LoginCidadao/PhoneVerificationBundle/Resources/views/Verification/verify.html.twig index 25992d441..bcebdb565 100644 --- a/src/LoginCidadao/PhoneVerificationBundle/Resources/views/Verification/verify.html.twig +++ b/src/LoginCidadao/PhoneVerificationBundle/Resources/views/Verification/verify.html.twig @@ -28,7 +28,8 @@ {# Resend #}
{% if nextResend %} -

{{ 'tasks.verify_phone.resend.waiting.please_wait' | trans({'%date%': nextResend | date('tasks.verify_phone.resend.waiting.date_format' | trans)}) | raw }}

+

{{ 'tasks.verify_phone.resend.waiting.please_wait' | trans({'%date%': nextResend | date('tasks.verify_phone.resend.waiting.date_format' | trans)}) | raw }}

{% endif %}
@@ -43,21 +44,52 @@
{# END OF Resend #} - {# Skip #} - -
-

{{ 'tasks.verify_phone.skip.warning' | trans }}

- {{ 'tasks.verify_phone.skip.confirm' | trans }} + {# Change Phone #} + +
+ {{ form_start(editPhoneForm) }} + + {{ form_row(editPhoneForm.mobile) }} + + + {{ form_end(editPhoneForm) }}
+ {# END OF Change Phone #} + + {# Skip #} + {% if not mandatory %} + +
+

{{ 'tasks.verify_phone.skip.warning' | trans }}

+ {{ 'tasks.verify_phone.skip.confirm' | trans }} +
+ {% endif %} {# END OF Skip #} {% endblock %} {% block javascripts %} {{ parent() }} + {% javascripts + '@intl_tel_input_js' + '@LoginCidadaoCoreBundle/Resources/public/js/components/intl-tel-input.js' + filter='uglifyjs2' %} + + {% endjavascripts %} {% endblock %} + +{% block stylesheets_custom %} + {% stylesheets '@lc_compact_layout_css' '@intl_tel_input_css' filter='cssrewrite' filter='?uglifycss' %} + + {% endstylesheets %} +{% endblock %} diff --git a/src/LoginCidadao/PhoneVerificationBundle/Service/PhoneVerificationService.php b/src/LoginCidadao/PhoneVerificationBundle/Service/PhoneVerificationService.php index 332b06d10..0c43a7069 100644 --- a/src/LoginCidadao/PhoneVerificationBundle/Service/PhoneVerificationService.php +++ b/src/LoginCidadao/PhoneVerificationBundle/Service/PhoneVerificationService.php @@ -12,6 +12,9 @@ use Doctrine\ORM\EntityManager; use libphonenumber\PhoneNumber; +use LoginCidadao\CoreBundle\Entity\Person; +use LoginCidadao\CoreBundle\Entity\PersonRepository; +use LoginCidadao\PhoneVerificationBundle\Entity\SentVerification; use LoginCidadao\PhoneVerificationBundle\Entity\SentVerificationRepository; use LoginCidadao\PhoneVerificationBundle\Event\PhoneVerificationEvent; use LoginCidadao\PhoneVerificationBundle\Event\SendPhoneVerificationEvent; @@ -24,7 +27,6 @@ use LoginCidadao\PhoneVerificationBundle\Entity\PhoneVerificationRepository; use LoginCidadao\PhoneVerificationBundle\Model\PhoneVerificationInterface; use Symfony\Component\HttpKernel\Exception\TooManyRequestsHttpException; -use Symfony\Component\Security\Core\Exception\AccessDeniedException; class PhoneVerificationService implements PhoneVerificationServiceInterface { @@ -40,6 +42,9 @@ class PhoneVerificationService implements PhoneVerificationServiceInterface /** @var SentVerificationRepository */ private $sentVerificationRepository; + /** @var PersonRepository */ + private $personRepository; + /** @var EventDispatcherInterface */ private $dispatcher; @@ -57,10 +62,9 @@ public function __construct( $this->options = $options; $this->em = $em; $this->dispatcher = $dispatcher; - $this->phoneVerificationRepository = $this->em - ->getRepository('LoginCidadaoPhoneVerificationBundle:PhoneVerification'); - $this->sentVerificationRepository = $this->em - ->getRepository('LoginCidadaoPhoneVerificationBundle:SentVerification'); + $this->phoneVerificationRepository = $this->em->getRepository(PhoneVerification::class); + $this->sentVerificationRepository = $this->em->getRepository(SentVerification::class); + $this->personRepository = $this->em->getRepository(Person::class); } /** @@ -126,6 +130,8 @@ public function getPendingPhoneVerificationById($id, PersonInterface $person = n * @param PersonInterface $person * @param mixed $phone * @return PhoneVerificationInterface + * @throws \Doctrine\ORM\ORMException + * @throws \Doctrine\ORM\OptimisticLockException */ public function createPhoneVerification(PersonInterface $person, PhoneNumber $phone) { @@ -185,6 +191,8 @@ public function getAllPendingPhoneVerification(PersonInterface $person) /** * @param PhoneVerificationInterface $phoneVerification * @return bool + * @throws \Doctrine\ORM\ORMException + * @throws \Doctrine\ORM\OptimisticLockException */ public function removePhoneVerification(PhoneVerificationInterface $phoneVerification) { @@ -198,6 +206,8 @@ public function removePhoneVerification(PhoneVerificationInterface $phoneVerific * @param PersonInterface $person * @param mixed $phone * @return PhoneVerificationInterface + * @throws \Doctrine\ORM\ORMException + * @throws \Doctrine\ORM\OptimisticLockException */ public function enforcePhoneVerification(PersonInterface $person, PhoneNumber $phone) { @@ -256,6 +266,8 @@ public function checkVerificationCode($provided, $expected) * @param PhoneVerificationInterface $phoneVerification * @param $providedCode * @return bool + * @throws \Doctrine\ORM\ORMException + * @throws \Doctrine\ORM\OptimisticLockException */ public function verify(PhoneVerificationInterface $phoneVerification, $providedCode) { @@ -297,6 +309,12 @@ public function sendVerificationCode(PhoneVerificationInterface $phoneVerificati return $sentVerification; } + /** + * @param SentVerificationInterface $sentVerification + * @return \LoginCidadao\PhoneVerificationBundle\Entity\SentVerification|SentVerificationInterface + * @throws \Doctrine\ORM\ORMException + * @throws \Doctrine\ORM\OptimisticLockException + */ public function registerVerificationSent(SentVerificationInterface $sentVerification) { $this->em->persist($sentVerification); @@ -322,6 +340,13 @@ public function getNextResendDate(PhoneVerificationInterface $phoneVerification) return $lastSentVerification->getSentAt()->add($timeout); } + /** + * @param PhoneVerificationInterface $phoneVerification + * @param string $token + * @return bool + * @throws \Doctrine\ORM\ORMException + * @throws \Doctrine\ORM\OptimisticLockException + */ public function verifyToken(PhoneVerificationInterface $phoneVerification, $token) { if ($phoneVerification->isVerified()) { @@ -334,4 +359,14 @@ public function verifyToken(PhoneVerificationInterface $phoneVerification, $toke return $this->verify($phoneVerification, $phoneVerification->getVerificationCode()); } + + /** + * @inheritDoc + */ + public function isVerificationMandatory(PhoneVerificationInterface $phoneVerification): bool + { + $accountsCount = $this->personRepository->countByPhone($phoneVerification->getPhone()); + + return $accountsCount >= $this->options->getEnforceVerificationThreshold(); + } } diff --git a/src/LoginCidadao/PhoneVerificationBundle/Service/PhoneVerificationServiceInterface.php b/src/LoginCidadao/PhoneVerificationBundle/Service/PhoneVerificationServiceInterface.php index f7298000c..79c1e260c 100644 --- a/src/LoginCidadao/PhoneVerificationBundle/Service/PhoneVerificationServiceInterface.php +++ b/src/LoginCidadao/PhoneVerificationBundle/Service/PhoneVerificationServiceInterface.php @@ -135,4 +135,12 @@ public function getNextResendDate(PhoneVerificationInterface $phoneVerification) * @return bool */ public function verifyToken(PhoneVerificationInterface $phoneVerification, $providedToken); + + /** + * Checks if the user MUST verify their phone + * + * @param PhoneVerificationInterface $phoneVerification + * @return bool + */ + public function isVerificationMandatory(PhoneVerificationInterface $phoneVerification): bool; } diff --git a/src/LoginCidadao/PhoneVerificationBundle/Tests/DependencyInjection/ConfigurationTest.php b/src/LoginCidadao/PhoneVerificationBundle/Tests/DependencyInjection/ConfigurationTest.php index e7767d89f..a93390d35 100644 --- a/src/LoginCidadao/PhoneVerificationBundle/Tests/DependencyInjection/ConfigurationTest.php +++ b/src/LoginCidadao/PhoneVerificationBundle/Tests/DependencyInjection/ConfigurationTest.php @@ -20,6 +20,7 @@ public static function getSampleConfig() { return [ 'enabled' => false, + 'require_validation_threshold' => 3, 'verification_code' => [ 'length' => 6, 'use_numbers' => true, @@ -36,7 +37,6 @@ public static function getSampleConfig() 'blocklist' => [ 'enable_auto_block' => true, 'auto_block_limit' => 10, - 'require_validation_threshold' => 3, ], ]; } diff --git a/src/LoginCidadao/PhoneVerificationBundle/Tests/DependencyInjection/LoginCidadaoPhoneVerificationExtensionTest.php b/src/LoginCidadao/PhoneVerificationBundle/Tests/DependencyInjection/LoginCidadaoPhoneVerificationExtensionTest.php index b6fd69dfc..40f84bdc2 100644 --- a/src/LoginCidadao/PhoneVerificationBundle/Tests/DependencyInjection/LoginCidadaoPhoneVerificationExtensionTest.php +++ b/src/LoginCidadao/PhoneVerificationBundle/Tests/DependencyInjection/LoginCidadaoPhoneVerificationExtensionTest.php @@ -82,8 +82,8 @@ public function testParametersLoaded() $container->getParameter('lc.phone_verification.options.token.length') ); $this->assertEquals( - $config['blocklist']['require_validation_threshold'], - $container->getParameter('lc.phone_verification.options.blocklist.require_validation_threshold') + $config['require_validation_threshold'], + $container->getParameter('lc.phone_verification.options.require_validation_threshold') ); } } diff --git a/src/LoginCidadao/PhoneVerificationBundle/Tests/Entity/BlockedPhoneNumberTest.php b/src/LoginCidadao/PhoneVerificationBundle/Tests/Entity/BlockedPhoneNumberTest.php new file mode 100644 index 000000000..6ea0c8021 --- /dev/null +++ b/src/LoginCidadao/PhoneVerificationBundle/Tests/Entity/BlockedPhoneNumberTest.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace LoginCidadao\PhoneVerificationBundle\Tests\Entity; + +use libphonenumber\PhoneNumber; +use LoginCidadao\CoreBundle\Entity\Person; +use LoginCidadao\PhoneVerificationBundle\Entity\BlockedPhoneNumber; +use PHPUnit\Framework\TestCase; + +class BlockedPhoneNumberTest extends TestCase +{ + public function testEntity() + { + $phone = new PhoneNumber(); + $blockedBy = new Person(); + $date = new \DateTime(); + + $blocked = new BlockedPhoneNumber($phone, $blockedBy, $date); + + $this->assertNull($blocked->getId()); + $this->assertSame($phone, $blocked->getPhoneNumber()); + $this->assertSame($blockedBy, $blocked->getBlockedBy()); + $this->assertSame($date, $blocked->getCreatedAt()); + } +} diff --git a/src/LoginCidadao/PhoneVerificationBundle/Tests/Event/TaskSubscriberTest.php b/src/LoginCidadao/PhoneVerificationBundle/Tests/Event/TaskSubscriberTest.php index a4305b708..2dc1fb86b 100644 --- a/src/LoginCidadao/PhoneVerificationBundle/Tests/Event/TaskSubscriberTest.php +++ b/src/LoginCidadao/PhoneVerificationBundle/Tests/Event/TaskSubscriberTest.php @@ -13,6 +13,7 @@ use libphonenumber\PhoneNumberType; use LoginCidadao\PhoneVerificationBundle\Event\TaskSubscriber; use LoginCidadao\PhoneVerificationBundle\Exception\VerificationNotSentException; +use LoginCidadao\PhoneVerificationBundle\Model\ConfirmPhoneTask; use LoginCidadao\TaskStackBundle\TaskStackEvents; use PHPUnit\Framework\TestCase; @@ -72,12 +73,19 @@ public function testOnGetTasks() $tokenStorage = $this->getTokenStorage(); $phoneVerificationService = $this->getPhoneVerificationService(); - $phoneVerificationService->expects($this->once())->method('getAllPendingPhoneVerification') + $phoneVerificationService->expects($this->once()) + ->method('getAllPendingPhoneVerification') ->willReturn([$phoneVerification]); + $phoneVerificationService->expects($this->once()) + ->method('isVerificationMandatory') + ->willReturn(true); $event = $this->getMockBuilder('LoginCidadao\TaskStackBundle\Event\GetTasksEvent') ->disableOriginalConstructor()->getMock(); - $event->expects($this->once())->method('addTaskIfStackEmpty'); + $event->expects($this->once())->method('addTaskIfStackEmpty') + ->willReturnCallback(function (ConfirmPhoneTask $task) { + $this->assertTrue($task->isMandatory()); + }); $subscriber = new TaskSubscriber($tokenStorage, $phoneVerificationService, true); $subscriber->onGetTasks($event); diff --git a/src/LoginCidadao/PhoneVerificationBundle/Tests/Form/PhoneNumberFormTypeTest.php b/src/LoginCidadao/PhoneVerificationBundle/Tests/Form/PhoneNumberFormTypeTest.php new file mode 100644 index 000000000..2244a6836 --- /dev/null +++ b/src/LoginCidadao/PhoneVerificationBundle/Tests/Form/PhoneNumberFormTypeTest.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace LoginCidadao\PhoneVerificationBundle\Tests\Form; + +use libphonenumber\PhoneNumberFormat; +use LoginCidadao\CoreBundle\Entity\Person; +use LoginCidadao\PhoneVerificationBundle\Form\PhoneNumberFormType; +use Misd\PhoneNumberBundle\Form\Type\PhoneNumberType; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; +use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\OptionsResolver\OptionsResolver; + +class PhoneNumberFormTypeTest extends TestCase +{ + public function testForm() + { + /** @var MockObject|FormBuilderInterface $builder */ + $builder = $this->createMock(FormBuilderInterface::class); + $builder->expects($this->once())->method('add')->with( + $this->equalTo('mobile'), + $this->equalTo(PhoneNumberType::class), + $this->equalTo([ + 'required' => false, + 'label' => 'person.form.mobile.label', + 'attr' => [ + 'class' => 'form-control intl-tel', + 'placeholder' => 'person.form.mobile.placeholder', + ], + 'label_attr' => ['class' => 'intl-tel-label'], + 'format' => PhoneNumberFormat::E164, + ]) + ); + + /** @var MockObject|OptionsResolver $resolver */ + $resolver = $this->createMock(OptionsResolver::class); + $resolver->expects($this->once())->method('setDefaults')->with([ + 'data_class' => Person::class, + 'validation_groups' => ['LoginCidadaoEmailForm'], + ]); + + $form = new PhoneNumberFormType(); + $form->configureOptions($resolver); + $form->buildForm($builder, []); + } +} diff --git a/src/LoginCidadao/PhoneVerificationBundle/Tests/Service/PhoneVerificationServiceTest.php b/src/LoginCidadao/PhoneVerificationBundle/Tests/Service/PhoneVerificationServiceTest.php index f26cfd589..4cfc121c5 100644 --- a/src/LoginCidadao/PhoneVerificationBundle/Tests/Service/PhoneVerificationServiceTest.php +++ b/src/LoginCidadao/PhoneVerificationBundle/Tests/Service/PhoneVerificationServiceTest.php @@ -10,6 +10,12 @@ namespace LoginCidadao\PhoneVerificationBundle\Tests\Service; +use LoginCidadao\CoreBundle\Entity\Person; +use LoginCidadao\CoreBundle\Entity\PersonRepository; +use LoginCidadao\PhoneVerificationBundle\Entity\PhoneVerification; +use LoginCidadao\PhoneVerificationBundle\Entity\PhoneVerificationRepository; +use LoginCidadao\PhoneVerificationBundle\Entity\SentVerification; +use LoginCidadao\PhoneVerificationBundle\Entity\SentVerificationRepository; use LoginCidadao\PhoneVerificationBundle\PhoneVerificationEvents; use LoginCidadao\PhoneVerificationBundle\Service\PhoneVerificationService; use PHPUnit\Framework\TestCase; @@ -58,12 +64,17 @@ private function getRepository($class) private function getPhoneVerificationRepository() { - return $this->getRepository('LoginCidadao\PhoneVerificationBundle\Entity\PhoneVerificationRepository'); + return $this->getRepository(PhoneVerificationRepository::class); } private function getSentVerificationRepository() { - return $this->getRepository('LoginCidadao\PhoneVerificationBundle\Entity\SentVerificationRepository'); + return $this->getRepository(SentVerificationRepository::class); + } + + private function getPersonRepository() + { + return $this->getRepository(PersonRepository::class); } /** @@ -105,13 +116,21 @@ private function getService(array $arguments = []) $sentVerificationRepository = $this->getSentVerificationRepository(); } - $em->expects($this->exactly(2))->method('getRepository')->willReturnCallback( - function ($class) use ($phoneVerificationRepository, $sentVerificationRepository) { + if (array_key_exists('person_repository', $arguments)) { + $personRepository = $arguments['person_repository']; + } else { + $personRepository = $this->getPersonRepository(); + } + + $em->expects($this->exactly(3))->method('getRepository')->willReturnCallback( + function ($class) use ($phoneVerificationRepository, $sentVerificationRepository, $personRepository) { switch ($class) { - case 'LoginCidadaoPhoneVerificationBundle:PhoneVerification': + case PhoneVerification::class: return $phoneVerificationRepository; - case 'LoginCidadaoPhoneVerificationBundle:SentVerification': + case SentVerification::class: return $sentVerificationRepository; + case Person::class: + return $personRepository; default: return null; } From 7cce647bc785b9851da8cf792cf50897b0d4b9e1 Mon Sep 17 00:00:00 2001 From: Guilherme Donato Date: Wed, 16 Jan 2019 10:01:28 -0200 Subject: [PATCH 04/40] code style PROCERGS#830 --- .../Event/PhoneVerificationSubscriber.php | 35 ++++++------------- 1 file changed, 11 insertions(+), 24 deletions(-) diff --git a/src/LoginCidadao/PhoneVerificationBundle/Event/PhoneVerificationSubscriber.php b/src/LoginCidadao/PhoneVerificationBundle/Event/PhoneVerificationSubscriber.php index 13a0c4f5d..89720bf5f 100644 --- a/src/LoginCidadao/PhoneVerificationBundle/Event/PhoneVerificationSubscriber.php +++ b/src/LoginCidadao/PhoneVerificationBundle/Event/PhoneVerificationSubscriber.php @@ -10,8 +10,6 @@ namespace LoginCidadao\PhoneVerificationBundle\Event; - -use FOS\UserBundle\FOSUserEvents; use libphonenumber\PhoneNumberFormat; use libphonenumber\PhoneNumberUtil; use LoginCidadao\CoreBundle\Model\PersonInterface; @@ -92,14 +90,11 @@ public function onPhoneChange(PhoneChangedEvent $event, $eventName, EventDispatc $newPhone = $person->getMobile(); $phoneUtil = PhoneNumberUtil::getInstance(); - $this->info( - 'Phone changed from {old} to {new} for user {id}', - [ - 'id' => $person->getId(), - 'old' => $oldPhone ? $phoneUtil->format($oldPhone, PhoneNumberFormat::E164) : null, - 'new' => $newPhone ? $phoneUtil->format($newPhone, PhoneNumberFormat::E164) : null, - ] - ); + $this->info('Phone changed from {old} to {new} for user {id}', [ + 'id' => $person->getId(), + 'old' => $oldPhone ? $phoneUtil->format($oldPhone, PhoneNumberFormat::E164) : null, + 'new' => $newPhone ? $phoneUtil->format($newPhone, PhoneNumberFormat::E164) : null, + ]); if ($oldPhone) { $oldPhoneVerification = $this->phoneVerificationService->getPhoneVerification($person, $oldPhone); @@ -112,10 +107,7 @@ public function onPhoneChange(PhoneChangedEvent $event, $eventName, EventDispatc } if ($newPhone) { - $phoneVerification = $this->phoneVerificationService->createPhoneVerification( - $person, - $newPhone - ); + $phoneVerification = $this->phoneVerificationService->createPhoneVerification($person, $newPhone); $sendEvent = new SendPhoneVerificationEvent($phoneVerification); $dispatcher->dispatch(PhoneVerificationEvents::PHONE_VERIFICATION_REQUESTED, $sendEvent); @@ -126,13 +118,10 @@ public function onVerificationRequest(SendPhoneVerificationEvent $event) { $person = $event->getPhoneVerification()->getPerson(); $phoneUtil = PhoneNumberUtil::getInstance(); - $this->info( - 'Phone Verification requested for {phone} for user {user_id}', - [ - 'user_id' => $person->getId(), - 'phone' => $phoneUtil->format($person->getMobile(), PhoneNumberFormat::E164), - ] - ); + $this->info('Phone Verification requested for {phone} for user {user_id}', [ + 'user_id' => $person->getId(), + 'phone' => $phoneUtil->format($person->getMobile(), PhoneNumberFormat::E164), + ]); } public function onCodeSent(SendPhoneVerificationEvent $event) @@ -153,9 +142,7 @@ public function onLogin(InteractiveLoginEvent $event, $eventName, EventDispatche if (!$verification) { $this->info( "User {user_id} has a phone but didn't verify it. Creating Phone Verification request.", - [ - 'user_id' => $person->getId(), - ] + ['user_id' => $person->getId()] ); $verification = $this->phoneVerificationService->enforcePhoneVerification($person, $phone); From 1166ffe64e3366d079d0ebf20cecbb811c8cb6dc Mon Sep 17 00:00:00 2001 From: Guilherme Donato Date: Wed, 16 Jan 2019 10:03:34 -0200 Subject: [PATCH 05/40] added tests and fixed bugs found PROCERGS#830 --- .../Entity/BlockedPhoneNumberRepository.php | 3 +- .../Service/Blocklist.php | 25 +++- .../Tests/Service/BlocklistOptionsTest.php | 29 +++++ .../Tests/Service/BlocklistTest.php | 123 ++++++++++++++++++ 4 files changed, 178 insertions(+), 2 deletions(-) create mode 100644 src/LoginCidadao/PhoneVerificationBundle/Tests/Service/BlocklistOptionsTest.php create mode 100644 src/LoginCidadao/PhoneVerificationBundle/Tests/Service/BlocklistTest.php diff --git a/src/LoginCidadao/PhoneVerificationBundle/Entity/BlockedPhoneNumberRepository.php b/src/LoginCidadao/PhoneVerificationBundle/Entity/BlockedPhoneNumberRepository.php index d4f2022bb..b5b45633b 100644 --- a/src/LoginCidadao/PhoneVerificationBundle/Entity/BlockedPhoneNumberRepository.php +++ b/src/LoginCidadao/PhoneVerificationBundle/Entity/BlockedPhoneNumberRepository.php @@ -12,6 +12,7 @@ use Doctrine\ORM\EntityRepository; use libphonenumber\PhoneNumber; +use LoginCidadao\PhoneVerificationBundle\Model\BlockedPhoneNumberInterface; /** * Class BlockedPhoneNumberRepository @@ -25,7 +26,7 @@ class BlockedPhoneNumberRepository extends EntityRepository * @param PhoneNumber $phoneNumber * @return BlockedPhoneNumber */ - public function findByPhone(PhoneNumber $phoneNumber): BlockedPhoneNumber + public function findByPhone(PhoneNumber $phoneNumber): ?BlockedPhoneNumberInterface { /** @var BlockedPhoneNumber $blockedPhone */ $blockedPhone = $this->findOneBy(['phoneNumber' => $phoneNumber]); diff --git a/src/LoginCidadao/PhoneVerificationBundle/Service/Blocklist.php b/src/LoginCidadao/PhoneVerificationBundle/Service/Blocklist.php index c1820e66b..3f7887ec5 100644 --- a/src/LoginCidadao/PhoneVerificationBundle/Service/Blocklist.php +++ b/src/LoginCidadao/PhoneVerificationBundle/Service/Blocklist.php @@ -12,6 +12,7 @@ use Doctrine\ORM\EntityManagerInterface; use libphonenumber\PhoneNumber; +use LoginCidadao\CoreBundle\Entity\Person; use LoginCidadao\CoreBundle\Entity\PersonRepository; use LoginCidadao\CoreBundle\Mailer\TwigSwiftMailer; use LoginCidadao\CoreBundle\Model\PersonInterface; @@ -40,6 +41,28 @@ class Blocklist implements BlocklistInterface /** @var BlocklistOptions */ private $options; + /** + * Blocklist constructor. + * @param UserManager $userManager + * @param TwigSwiftMailer $mailer + * @param EntityManagerInterface $em + * @param BlocklistOptions $options + */ + public function __construct( + UserManager $userManager, + TwigSwiftMailer $mailer, + EntityManagerInterface $em, + BlocklistOptions $options + ) { + $this->userManager = $userManager; + $this->mailer = $mailer; + $this->em = $em; + $this->options = $options; + $this->blockedPhoneRepository = $this->em->getRepository(BlockedPhoneNumber::class); + $this->personRepository = $this->em->getRepository(Person::class); + } + + public function isBlocked(PhoneNumber $phoneNumber): bool { return $this->isManuallyBlocked($phoneNumber) || $this->isBlockedAutomatically($phoneNumber); @@ -76,7 +99,7 @@ private function notifyBlockedUsers(array $blockedUsers) private function isManuallyBlocked(PhoneNumber $phoneNumber): bool { - return $this->blockedPhoneRepository->findByPhone($phoneNumber) instanceof PhoneNumber; + return $this->blockedPhoneRepository->findByPhone($phoneNumber) instanceof BlockedPhoneNumberInterface; } private function isBlockedAutomatically(PhoneNumber $phoneNumber): bool diff --git a/src/LoginCidadao/PhoneVerificationBundle/Tests/Service/BlocklistOptionsTest.php b/src/LoginCidadao/PhoneVerificationBundle/Tests/Service/BlocklistOptionsTest.php new file mode 100644 index 000000000..ac81b0b6c --- /dev/null +++ b/src/LoginCidadao/PhoneVerificationBundle/Tests/Service/BlocklistOptionsTest.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace LoginCidadao\PhoneVerificationBundle\Tests\Service; + +use LoginCidadao\PhoneVerificationBundle\Service\BlocklistOptions; +use PHPUnit\Framework\TestCase; + +class BlocklistOptionsTest extends TestCase +{ + public function testOptions() + { + $autoBlockPhoneLimit = 3; + + $options = new BlocklistOptions($autoBlockPhoneLimit); + + $this->assertTrue($options->isAutoBlockEnabled()); + $this->assertSame($autoBlockPhoneLimit, $options->getAutoBlockPhoneLimit()); + + $this->assertFalse((new BlocklistOptions(0))->isAutoBlockEnabled()); + } +} diff --git a/src/LoginCidadao/PhoneVerificationBundle/Tests/Service/BlocklistTest.php b/src/LoginCidadao/PhoneVerificationBundle/Tests/Service/BlocklistTest.php new file mode 100644 index 000000000..9cfa77737 --- /dev/null +++ b/src/LoginCidadao/PhoneVerificationBundle/Tests/Service/BlocklistTest.php @@ -0,0 +1,123 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace LoginCidadao\PhoneVerificationBundle\Tests\Service; + +use Doctrine\ORM\EntityManagerInterface; +use libphonenumber\PhoneNumber; +use LoginCidadao\CoreBundle\Entity\Person; +use LoginCidadao\CoreBundle\Entity\PersonRepository; +use LoginCidadao\CoreBundle\Mailer\TwigSwiftMailer; +use LoginCidadao\CoreBundle\Security\User\Manager\UserManager; +use LoginCidadao\PhoneVerificationBundle\Entity\BlockedPhoneNumber; +use LoginCidadao\PhoneVerificationBundle\Entity\BlockedPhoneNumberRepository; +use LoginCidadao\PhoneVerificationBundle\Model\BlockedPhoneNumberInterface; +use LoginCidadao\PhoneVerificationBundle\Service\Blocklist; +use LoginCidadao\PhoneVerificationBundle\Service\BlocklistOptions; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; + +class BlocklistTest extends TestCase +{ + public function testManuallyBlocked() + { + $phoneNumber = new PhoneNumber(); + $blocked = $this->createMock(BlockedPhoneNumberInterface::class); + + $blockedPhoneRepository = $this->createMock(BlockedPhoneNumberRepository::class); + $blockedPhoneRepository->expects($this->once()) + ->method('findByPhone')->with($phoneNumber) + ->willReturn($blocked); + + $em = $this->getEntityManager([ + BlockedPhoneNumber::class => $blockedPhoneRepository, + Person::class => $this->createMock(PersonRepository::class), + ]); + + $options = new BlocklistOptions(2); + + $blocklist = new Blocklist($this->getUserManager(), $this->getMailer(), $em, $options); + $this->assertTrue($blocklist->isBlocked($phoneNumber)); + } + + public function testAutoBlocked() + { + $phoneNumber = new PhoneNumber(); + + $blockedPhoneRepository = $this->createMock(BlockedPhoneNumberRepository::class); + $blockedPhoneRepository->expects($this->once()) + ->method('findByPhone')->with($phoneNumber) + ->willReturn(null); + + $personRepo = $this->createMock(PersonRepository::class); + $personRepo->expects($this->once())->method('countByPhone')->willReturn(3); + + $em = $this->getEntityManager([ + BlockedPhoneNumber::class => $blockedPhoneRepository, + Person::class => $personRepo, + ]); + + $options = new BlocklistOptions(2); + + $blocklist = new Blocklist($this->getUserManager(), $this->getMailer(), $em, $options); + $this->assertTrue($blocklist->isBlocked($phoneNumber)); + } + + public function testNotBlocked() + { + $phoneNumber = new PhoneNumber(); + + $blockedPhoneRepository = $this->createMock(BlockedPhoneNumberRepository::class); + $blockedPhoneRepository->expects($this->once()) + ->method('findByPhone')->with($phoneNumber) + ->willReturn(null); + + $em = $this->getEntityManager([ + BlockedPhoneNumber::class => $blockedPhoneRepository, + Person::class => $this->createMock(PersonRepository::class), + ]); + + $options = new BlocklistOptions(0); + + $blocklist = new Blocklist($this->getUserManager(), $this->getMailer(), $em, $options); + $this->assertFalse($blocklist->isBlocked($phoneNumber)); + } + + /** + * @return MockObject|UserManager + */ + private function getUserManager() + { + return $this->createMock(UserManager::class); + } + + /** + * @return MockObject|TwigSwiftMailer + */ + private function getMailer() + { + return $this->createMock(TwigSwiftMailer::class); + } + + /** + * @param array $repositories + * @return MockObject|EntityManagerInterface + */ + private function getEntityManager(array $repositories) + { + $em = $this->createMock(EntityManagerInterface::class); + $em->expects($this->atLeast(2))->method('getRepository') + ->willReturnCallback(function ($entity) use ($repositories) { + return $repositories[$entity]; + }); + + return $em; + } +} From 17d10f57b11407f6b0f05aabc70dd12658a9d282 Mon Sep 17 00:00:00 2001 From: Guilherme Donato Date: Thu, 17 Jan 2019 17:09:34 -0200 Subject: [PATCH 06/40] fixed Person::getBadges() PROCERGS#835 --- .../CoreBundle/Controller/DefaultController.php | 2 ++ src/LoginCidadao/CoreBundle/Entity/Person.php | 9 ++++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/LoginCidadao/CoreBundle/Controller/DefaultController.php b/src/LoginCidadao/CoreBundle/Controller/DefaultController.php index 89ff4942a..a9748cfef 100644 --- a/src/LoginCidadao/CoreBundle/Controller/DefaultController.php +++ b/src/LoginCidadao/CoreBundle/Controller/DefaultController.php @@ -11,6 +11,7 @@ namespace LoginCidadao\CoreBundle\Controller; use LoginCidadao\APIBundle\Entity\ActionLogRepository; +use LoginCidadao\BadgesControlBundle\Handler\BadgesHandler; use LoginCidadao\CoreBundle\Form\Type\ContactFormType; use LoginCidadao\CoreBundle\Model\PersonInterface; use LoginCidadao\CoreBundle\Model\SupportMessage; @@ -79,6 +80,7 @@ public function contactAction(Request $request, $correlationId = null) public function dashboardAction() { // badges + /** @var BadgesHandler $badgesHandler */ $badgesHandler = $this->get('badges.handler'); $badges = $badgesHandler->getAvailableBadges(); $userBadges = $badgesHandler->evaluate($this->getUser())->getBadges(); diff --git a/src/LoginCidadao/CoreBundle/Entity/Person.php b/src/LoginCidadao/CoreBundle/Entity/Person.php index f0dd4d95a..21affeb0f 100644 --- a/src/LoginCidadao/CoreBundle/Entity/Person.php +++ b/src/LoginCidadao/CoreBundle/Entity/Person.php @@ -1053,8 +1053,15 @@ public function getIdCards() public function getBadges() { - return /** @scrutinizer ignore-deprecated */ + $badges = /** @scrutinizer ignore-deprecated */ $this->badges; + $badgesMap = []; + foreach ($badges as $badge) { + $key = "{$badge->getNamespace()}.{$badge->getName()}"; + $badgesMap[$key] = $badge; + } + + return $badgesMap; } public function mergeBadges(array $badges) From 603ed26a53655ea4bf852c4ac2d43b9d7a4dd393 Mon Sep 17 00:00:00 2001 From: Guilherme Donato Date: Mon, 21 Jan 2019 10:45:28 -0200 Subject: [PATCH 07/40] implemented auto phone blocking PROCERGS#830 --- .../CoreBundle/Mailer/TwigSwiftMailer.php | 11 +++ .../CoreBundle/Resources/config/services.yml | 2 + .../Resources/translations/messages.pt_BR.yml | 33 +++++++ .../views/Person/accountAutoBlocked.html.twig | 20 +++++ .../Entity/BlockedPhoneNumber.php | 16 +++- .../Entity/PhoneVerificationRepository.php | 35 ++++++-- .../Event/BlocklistSubscriber.php | 65 +++++++++++++- .../Event/TaskSubscriber.php | 1 - .../Resources/config/services.yml | 18 ++++ .../Service/Blocklist.php | 36 ++++++-- .../Service/BlocklistInterface.php | 10 ++- .../Service/PhoneVerificationService.php | 8 ++ .../PhoneVerificationServiceInterface.php | 6 ++ .../Tests/Service/BlocklistTest.php | 88 +++++++++++++------ 14 files changed, 304 insertions(+), 45 deletions(-) create mode 100644 src/LoginCidadao/CoreBundle/Resources/views/Person/accountAutoBlocked.html.twig diff --git a/src/LoginCidadao/CoreBundle/Mailer/TwigSwiftMailer.php b/src/LoginCidadao/CoreBundle/Mailer/TwigSwiftMailer.php index acb9e1817..431355f41 100644 --- a/src/LoginCidadao/CoreBundle/Mailer/TwigSwiftMailer.php +++ b/src/LoginCidadao/CoreBundle/Mailer/TwigSwiftMailer.php @@ -48,4 +48,15 @@ public function sendAccountBlockedMessage(PersonInterface $person) $context = ['email' => $person->getEmail()]; $this->sendMessage($template, $context, $from, $person->getEmail()); } + + public function sendAccountAutoBlockedMessage(PersonInterface $person) + { + $template = $this->parameters['template']['account_auto_blocked']; + $fromEmail = $this->parameters['from_email']['account_auto_blocked']; + $fromName = $this->parameters['from_email']['email_sender_name']; + $from = [$fromEmail => $fromName]; + + $context = ['email' => $person->getEmail()]; + $this->sendMessage($template, $context, $from, $person->getEmail()); + } } diff --git a/src/LoginCidadao/CoreBundle/Resources/config/services.yml b/src/LoginCidadao/CoreBundle/Resources/config/services.yml index 9c4c05ac1..07abaf166 100644 --- a/src/LoginCidadao/CoreBundle/Resources/config/services.yml +++ b/src/LoginCidadao/CoreBundle/Resources/config/services.yml @@ -190,11 +190,13 @@ services: email_changed: "%lc.emailChanged.template%" notification_email: "%lc.notification.email.template%" account_blocked: "LoginCidadaoCoreBundle:Person:accountBlocked.html.twig" + account_auto_blocked: "LoginCidadaoCoreBundle:Person:accountAutoBlocked.html.twig" from_email: confirmation: "%fos_user.registration.confirmation.from_email%" resetting: "%fos_user.resetting.email.from_email%" email_changed: "%mailer_sender_mail%" account_blocked: "%mailer_sender_mail%" + account_auto_blocked: "%mailer_sender_mail%" email_sender_name: "%mailer_sender_name%" kernel.listener.logged_in_user_listener: diff --git a/src/LoginCidadao/CoreBundle/Resources/translations/messages.pt_BR.yml b/src/LoginCidadao/CoreBundle/Resources/translations/messages.pt_BR.yml index c3752adf1..93159ff6a 100644 --- a/src/LoginCidadao/CoreBundle/Resources/translations/messages.pt_BR.yml +++ b/src/LoginCidadao/CoreBundle/Resources/translations/messages.pt_BR.yml @@ -1030,3 +1030,36 @@ email:
Cordialmente,
Equipe Login Cidadão + account_auto_blocked: + message: + subject: 'Notificação de Bloqueio por Violação dos Termos de Uso' + text: | + Notificação de Bloqueio por Violação dos Termos de Uso: + + Um mecanismo automático anti-fraude identificou uma atividade suspeita em sua conta do Login Cidadão registrada sob seu email %email%. + + Foi identificada que a conta em questão poderia estar sendo utilizada para tentar acessar informações pessoais de terceiros, o que viola os Termos de Uso do Login Cidadão e, por esse motivo, foi bloqueada com base no texto: + + "O Login Cidadão somente pode ser utilizado para fins pessoais, não sendo permitida a utilização em nome de terceiros ou com a finalidade de obter informações de terceiros." + (...) + "A não observância pelo usuário dos seus deveres previstos nestes Termos de Uso ou em qualquer outra legislação aplicável, poderá acarretar a suspensão ou cancelamento do seu acesso ao Login Cidadão, ou a um ou mais serviços disponibilizados, além de outras sanções previstas em lei." + + Caso acredite que essa ação tenha sido executada por engano, é possível solicitar uma reavaliação não-automática através do forumlário de contato disponível no Login Cidadão. + + Cordialmente, + Equipe Login Cidadão + html: > + Notificação de Bloqueio por Violação dos Termos de Uso:
+
+ Um mecanismo automático anti-fraude identificou uma atividade suspeita em sua conta do Login Cidadão registrada sob seu email %email%.
+
+ Foi identificada que a conta em questão poderia estar sendo utilizada para tentar acessar informações pessoais de terceiros, o que viola os Termos de Uso do Login Cidadão e, por esse motivo, foi bloqueada com base no texto:
+
+ "O Login Cidadão somente pode ser utilizado para fins pessoais, não sendo permitida a utilização em nome de terceiros ou com a finalidade de obter informações de terceiros."
+ (...)
+ "A não observância pelo usuário dos seus deveres previstos nestes Termos de Uso ou em qualquer outra legislação aplicável, poderá acarretar a suspensão ou cancelamento do seu acesso ao Login Cidadão, ou a um ou mais serviços disponibilizados, além de outras sanções previstas em lei."
+
+ Caso acredite que essa ação tenha sido executada por engano, é possível solicitar uma reavaliação não-automática através do forumlário de contato disponível no Login Cidadão.
+
+ Cordialmente,
+ Equipe Login Cidadão diff --git a/src/LoginCidadao/CoreBundle/Resources/views/Person/accountAutoBlocked.html.twig b/src/LoginCidadao/CoreBundle/Resources/views/Person/accountAutoBlocked.html.twig new file mode 100644 index 000000000..3a9f88472 --- /dev/null +++ b/src/LoginCidadao/CoreBundle/Resources/views/Person/accountAutoBlocked.html.twig @@ -0,0 +1,20 @@ +{% block subject %} + {% autoescape false %} + {{ 'email.account_auto_blocked.message.subject' | trans }} + {% endautoescape %} +{% endblock %} + +{% block body_text %} + {% autoescape false %} + {{ 'email.account_auto_blocked.message.text' | trans({ '%email%': email }) }} + {% endautoescape %} +{% endblock %} + +{% block body_html %} + {% autoescape false %} + {{ include("LoginCidadaoCoreBundle::common.email.html.twig", { + 'subject' : 'email.account_auto_blocked.message.subject' | trans, + 'msg' : 'email.account_auto_blocked.message.html' | trans({ '%email%': email }) | raw + } ) }} + {% endautoescape %} +{% endblock %} diff --git a/src/LoginCidadao/PhoneVerificationBundle/Entity/BlockedPhoneNumber.php b/src/LoginCidadao/PhoneVerificationBundle/Entity/BlockedPhoneNumber.php index 8af6e7cbf..d8b018bed 100644 --- a/src/LoginCidadao/PhoneVerificationBundle/Entity/BlockedPhoneNumber.php +++ b/src/LoginCidadao/PhoneVerificationBundle/Entity/BlockedPhoneNumber.php @@ -21,25 +21,39 @@ * @package LoginCidadao\PhoneVerificationBundle\Model * * @ORM\Entity(repositoryClass="LoginCidadao\PhoneVerificationBundle\Entity\BlockedPhoneNumberRepository") - * @ORM\Table(name="person") + * @ORM\Table(name="blocked_phone_number") * @ORM\HasLifecycleCallbacks */ class BlockedPhoneNumber implements BlockedPhoneNumberInterface { + /** + * @var integer + * + * @ORM\Column(name="id", type="integer") + * @ORM\Id + * @ORM\GeneratedValue(strategy="AUTO") + */ private $id; /** * @var PhoneNumber + * + * @ORM\Column(name="phone_number", type="phone_number", nullable=false) */ private $phoneNumber; /** * @var PersonInterface + * + * @ORM\ManyToOne(targetEntity="LoginCidadao\CoreBundle\Entity\Person") + * @ORM\JoinColumn(name="blocked_by_person_id", referencedColumnName="id", unique=false) */ private $blockedBy; /** * @var \DateTime + * + * @ORM\Column(name="created_at", type="datetime", nullable=true) */ private $createdAt; diff --git a/src/LoginCidadao/PhoneVerificationBundle/Entity/PhoneVerificationRepository.php b/src/LoginCidadao/PhoneVerificationBundle/Entity/PhoneVerificationRepository.php index 0b77fab24..d0c2fd16a 100644 --- a/src/LoginCidadao/PhoneVerificationBundle/Entity/PhoneVerificationRepository.php +++ b/src/LoginCidadao/PhoneVerificationBundle/Entity/PhoneVerificationRepository.php @@ -11,6 +11,10 @@ namespace LoginCidadao\PhoneVerificationBundle\Entity; use Doctrine\ORM\EntityRepository; +use Doctrine\ORM\NonUniqueResultException; +use libphonenumber\PhoneNumber; +use LoginCidadao\CoreBundle\Entity\Person; +use Misd\PhoneNumberBundle\Doctrine\DBAL\Types\PhoneNumberType; /** * PhoneVerificationRepository @@ -24,10 +28,31 @@ class PhoneVerificationRepository extends EntityRepository { public function countBadges() { - return $this->createQueryBuilder('ph') - ->select('COUNT(p)') - ->innerJoin('LoginCidadaoCoreBundle:Person', 'p', 'WITH', 'ph.person = p') - ->andWhere('ph.verifiedAt IS NOT NULL') - ->getQuery()->getSingleScalarResult(); + try { + return $this->createQueryBuilder('ph') + ->select('COUNT(p)') + ->innerJoin(Person::class, 'p', 'WITH', 'ph.person = p') + ->andWhere('ph.verifiedAt IS NOT NULL') + ->getQuery()->getSingleScalarResult(); + } catch (NonUniqueResultException $e) { + throw new \RuntimeException('Could not count how many people has a verified phone number', 0, $e); + } + } + + public function countVerified(PhoneNumber $phoneNumber) + { + try { + return $this->createQueryBuilder('v') + ->select('COUNT(p)') + ->innerJoin(Person::class, 'p', 'WITH', 'v.person = p') + ->where('v.phone = :phone') + ->andWhere('v.phone = p.mobile') + ->andWhere('v.verifiedAt IS NOT NULL') + ->setParameter('phone', $phoneNumber, PhoneNumberType::NAME) + ->getQuery() + ->getSingleScalarResult(); + } catch (NonUniqueResultException $e) { + throw new \RuntimeException('Could not count how many people uses the same verified phone number', 0, $e); + } } } diff --git a/src/LoginCidadao/PhoneVerificationBundle/Event/BlocklistSubscriber.php b/src/LoginCidadao/PhoneVerificationBundle/Event/BlocklistSubscriber.php index 104c3ab99..17a8249a0 100644 --- a/src/LoginCidadao/PhoneVerificationBundle/Event/BlocklistSubscriber.php +++ b/src/LoginCidadao/PhoneVerificationBundle/Event/BlocklistSubscriber.php @@ -11,10 +11,16 @@ namespace LoginCidadao\PhoneVerificationBundle\Event; use libphonenumber\PhoneNumber; +use LoginCidadao\CoreBundle\Model\PersonInterface; use LoginCidadao\CoreBundle\Security\User\Manager\UserManager; use LoginCidadao\PhoneVerificationBundle\PhoneVerificationEvents; use LoginCidadao\PhoneVerificationBundle\Service\BlocklistInterface; use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\HttpKernel\Event\GetResponseEvent; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; +use Symfony\Component\Security\Http\Event\InteractiveLoginEvent; +use Symfony\Component\Security\Http\SecurityEvents; class BlocklistSubscriber implements EventSubscriberInterface { @@ -24,6 +30,25 @@ class BlocklistSubscriber implements EventSubscriberInterface /** @var UserManager */ private $userManager; + /** @var TokenStorageInterface */ + private $tokenStorage; + + /** + * BlocklistSubscriber constructor. + * @param BlocklistInterface $blocklist + * @param UserManager $userManager + * @param TokenStorageInterface $tokenStorage + */ + public function __construct( + BlocklistInterface $blocklist, + UserManager $userManager, + TokenStorageInterface $tokenStorage + ) { + $this->blocklist = $blocklist; + $this->userManager = $userManager; + $this->tokenStorage = $tokenStorage; + } + /** * @inheritDoc */ @@ -31,14 +56,50 @@ public static function getSubscribedEvents() { return [ PhoneVerificationEvents::PHONE_CHANGED => 'onPhoneChange', + SecurityEvents::INTERACTIVE_LOGIN => 'onLogin', + KernelEvents::REQUEST => 'onRequest', // TODO: remove ]; } public function onPhoneChange(PhoneChangedEvent $event) { $phone = $event->getPerson()->getMobile(); - if ($phone instanceof PhoneNumber && $this->blocklist->isBlocked($phone)) { - $this->userManager->blockUsersByPhone($phone); + $this->checkPhone($phone); + } + + public function onLogin(InteractiveLoginEvent $event) + { + try { + $person = $event->getAuthenticationToken()->getUser(); + $phone = $person->getMobile(); + } catch (\Exception $e) { + // User object not available... + return; + } + + $this->checkPhone($phone); + } + + public function onRequest(GetResponseEvent $event) + { + if ($event->isMasterRequest() && null !== $this->tokenStorage->getToken()) { + /** @var PersonInterface $person */ + $person = $this->tokenStorage->getToken()->getUser(); + if ($person instanceof PersonInterface) { + $phone = $person->getMobile(); + + $this->checkPhone($phone); + } + } + } + + /** + * @param PhoneNumber|null $phoneNumber + */ + private function checkPhone(?PhoneNumber $phoneNumber) + { + if ($phoneNumber instanceof PhoneNumber) { + $this->blocklist->checkPhoneNumber($phoneNumber); } } } diff --git a/src/LoginCidadao/PhoneVerificationBundle/Event/TaskSubscriber.php b/src/LoginCidadao/PhoneVerificationBundle/Event/TaskSubscriber.php index 95bf6314d..f5cbcc4f2 100644 --- a/src/LoginCidadao/PhoneVerificationBundle/Event/TaskSubscriber.php +++ b/src/LoginCidadao/PhoneVerificationBundle/Event/TaskSubscriber.php @@ -11,7 +11,6 @@ namespace LoginCidadao\PhoneVerificationBundle\Event; use FOS\OAuthServerBundle\Security\Authentication\Token\OAuthToken; -use LoginCidadao\CoreBundle\Entity\PersonRepository; use LoginCidadao\CoreBundle\Model\PersonInterface; use LoginCidadao\PhoneVerificationBundle\Entity\PhoneVerification; use LoginCidadao\PhoneVerificationBundle\Exception\VerificationNotSentException; diff --git a/src/LoginCidadao/PhoneVerificationBundle/Resources/config/services.yml b/src/LoginCidadao/PhoneVerificationBundle/Resources/config/services.yml index 042760a62..f09318c79 100644 --- a/src/LoginCidadao/PhoneVerificationBundle/Resources/config/services.yml +++ b/src/LoginCidadao/PhoneVerificationBundle/Resources/config/services.yml @@ -75,6 +75,15 @@ services: tags: - { name: kernel.event_subscriber } + phone_verification.blocklist.subscriber: + class: LoginCidadao\PhoneVerificationBundle\Event\BlocklistSubscriber + arguments: + - "@phone_verification.blocklist" + - "@lc.user_manager" + - "@security.token_storage" + tags: + - { name: kernel.event_subscriber } + phone_verification.form: class: LoginCidadao\PhoneVerificationBundle\Form\PhoneVerificationType arguments: @@ -94,3 +103,12 @@ services: - "@doctrine.orm.entity_manager" - "@event_dispatcher" - "@phone_verification.sent_verification.repository" + + phone_verification.blocklist: + class: LoginCidadao\PhoneVerificationBundle\Service\Blocklist + arguments: + - "@lc.user_manager" + - "@lc.mailer" + - "@doctrine.orm.entity_manager" + - "@phone_verification" + - "@phone_verification.blocklist.options" diff --git a/src/LoginCidadao/PhoneVerificationBundle/Service/Blocklist.php b/src/LoginCidadao/PhoneVerificationBundle/Service/Blocklist.php index 3f7887ec5..5fa105d3c 100644 --- a/src/LoginCidadao/PhoneVerificationBundle/Service/Blocklist.php +++ b/src/LoginCidadao/PhoneVerificationBundle/Service/Blocklist.php @@ -19,6 +19,8 @@ use LoginCidadao\CoreBundle\Security\User\Manager\UserManager; use LoginCidadao\PhoneVerificationBundle\Entity\BlockedPhoneNumber; use LoginCidadao\PhoneVerificationBundle\Entity\BlockedPhoneNumberRepository; +use LoginCidadao\PhoneVerificationBundle\Entity\PhoneVerification; +use LoginCidadao\PhoneVerificationBundle\Entity\PhoneVerificationRepository; use LoginCidadao\PhoneVerificationBundle\Model\BlockedPhoneNumberInterface; class Blocklist implements BlocklistInterface @@ -32,8 +34,8 @@ class Blocklist implements BlocklistInterface /** @var BlockedPhoneNumberRepository */ private $blockedPhoneRepository; - /** @var PersonRepository */ - private $personRepository; + /** @var PhoneVerificationServiceInterface */ + private $phoneVerificationService; /** @var EntityManagerInterface */ private $em; @@ -46,12 +48,14 @@ class Blocklist implements BlocklistInterface * @param UserManager $userManager * @param TwigSwiftMailer $mailer * @param EntityManagerInterface $em + * @param PhoneVerificationServiceInterface $phoneVerificationService * @param BlocklistOptions $options */ public function __construct( UserManager $userManager, TwigSwiftMailer $mailer, EntityManagerInterface $em, + PhoneVerificationServiceInterface $phoneVerificationService, BlocklistOptions $options ) { $this->userManager = $userManager; @@ -59,23 +63,38 @@ public function __construct( $this->em = $em; $this->options = $options; $this->blockedPhoneRepository = $this->em->getRepository(BlockedPhoneNumber::class); - $this->personRepository = $this->em->getRepository(Person::class); + $this->phoneVerificationService = $phoneVerificationService; } - - public function isBlocked(PhoneNumber $phoneNumber): bool + /** + * @inheritDoc + */ + public function isPhoneBlocked(PhoneNumber $phoneNumber): bool { return $this->isManuallyBlocked($phoneNumber) || $this->isBlockedAutomatically($phoneNumber); } + /** + * @inheritDoc + */ public function blockByPhone(PhoneNumber $phoneNumber): array { - $blockedUsers = $this->userManager->blockUsersByPhone($phoneNumber); + $blockedUsers = $this->userManager->blockUsersByPhone($phoneNumber, false); $this->notifyBlockedUsers($blockedUsers); return $blockedUsers; } + /** + * @inheritDoc + */ + public function checkPhoneNumber(PhoneNumber $phoneNumber) + { + if ($this->isPhoneBlocked($phoneNumber)) { + $this->blockByPhone($phoneNumber); + } + } + public function addBlockedPhoneNumber( PhoneNumber $phoneNumber, PersonInterface $blockedBy @@ -93,7 +112,7 @@ public function addBlockedPhoneNumber( private function notifyBlockedUsers(array $blockedUsers) { foreach ($blockedUsers as $person) { - $this->mailer->sendAccountBlockedMessage($person); + $this->mailer->sendAccountAutoBlockedMessage($person); } } @@ -107,8 +126,7 @@ private function isBlockedAutomatically(PhoneNumber $phoneNumber): bool if ($this->options->isAutoBlockEnabled()) { $autoBlockLimit = $this->options->getAutoBlockPhoneLimit(); - // TODO: count ONLY verified phones!!!!! - return $this->personRepository->countByPhone($phoneNumber) >= $autoBlockLimit; + return $this->phoneVerificationService->countVerified($phoneNumber) >= $autoBlockLimit; } return false; diff --git a/src/LoginCidadao/PhoneVerificationBundle/Service/BlocklistInterface.php b/src/LoginCidadao/PhoneVerificationBundle/Service/BlocklistInterface.php index 3788652b4..730f02489 100644 --- a/src/LoginCidadao/PhoneVerificationBundle/Service/BlocklistInterface.php +++ b/src/LoginCidadao/PhoneVerificationBundle/Service/BlocklistInterface.php @@ -15,11 +15,19 @@ interface BlocklistInterface { - public function isBlocked(PhoneNumber $phoneNumber): bool; + public function isPhoneBlocked(PhoneNumber $phoneNumber): bool; /** * @param PhoneNumber $phoneNumber * @return PersonInterface[] */ public function blockByPhone(PhoneNumber $phoneNumber): array; + + /** + * Checks if the phone is blocked. If it is, all relevant accounts will be blocked. + * + * @param PhoneNumber $phoneNumber + * @return mixed + */ + public function checkPhoneNumber(PhoneNumber $phoneNumber); } diff --git a/src/LoginCidadao/PhoneVerificationBundle/Service/PhoneVerificationService.php b/src/LoginCidadao/PhoneVerificationBundle/Service/PhoneVerificationService.php index 0c43a7069..b20851468 100644 --- a/src/LoginCidadao/PhoneVerificationBundle/Service/PhoneVerificationService.php +++ b/src/LoginCidadao/PhoneVerificationBundle/Service/PhoneVerificationService.php @@ -369,4 +369,12 @@ public function isVerificationMandatory(PhoneVerificationInterface $phoneVerific return $accountsCount >= $this->options->getEnforceVerificationThreshold(); } + + /** + * @inheritDoc + */ + public function countVerified(PhoneNumber $phoneNumber): int + { + return $this->phoneVerificationRepository->countVerified($phoneNumber); + } } diff --git a/src/LoginCidadao/PhoneVerificationBundle/Service/PhoneVerificationServiceInterface.php b/src/LoginCidadao/PhoneVerificationBundle/Service/PhoneVerificationServiceInterface.php index 79c1e260c..11863e784 100644 --- a/src/LoginCidadao/PhoneVerificationBundle/Service/PhoneVerificationServiceInterface.php +++ b/src/LoginCidadao/PhoneVerificationBundle/Service/PhoneVerificationServiceInterface.php @@ -143,4 +143,10 @@ public function verifyToken(PhoneVerificationInterface $phoneVerification, $prov * @return bool */ public function isVerificationMandatory(PhoneVerificationInterface $phoneVerification): bool; + + /** + * @param PhoneNumber $phoneNumber + * @return int how many users have this PhoneNumber verified + */ + public function countVerified(PhoneNumber $phoneNumber): int; } diff --git a/src/LoginCidadao/PhoneVerificationBundle/Tests/Service/BlocklistTest.php b/src/LoginCidadao/PhoneVerificationBundle/Tests/Service/BlocklistTest.php index 9cfa77737..cc06eeecc 100644 --- a/src/LoginCidadao/PhoneVerificationBundle/Tests/Service/BlocklistTest.php +++ b/src/LoginCidadao/PhoneVerificationBundle/Tests/Service/BlocklistTest.php @@ -15,12 +15,16 @@ use LoginCidadao\CoreBundle\Entity\Person; use LoginCidadao\CoreBundle\Entity\PersonRepository; use LoginCidadao\CoreBundle\Mailer\TwigSwiftMailer; +use LoginCidadao\CoreBundle\Model\PersonInterface; use LoginCidadao\CoreBundle\Security\User\Manager\UserManager; use LoginCidadao\PhoneVerificationBundle\Entity\BlockedPhoneNumber; use LoginCidadao\PhoneVerificationBundle\Entity\BlockedPhoneNumberRepository; +use LoginCidadao\PhoneVerificationBundle\Entity\PhoneVerification; +use LoginCidadao\PhoneVerificationBundle\Entity\PhoneVerificationRepository; use LoginCidadao\PhoneVerificationBundle\Model\BlockedPhoneNumberInterface; use LoginCidadao\PhoneVerificationBundle\Service\Blocklist; use LoginCidadao\PhoneVerificationBundle\Service\BlocklistOptions; +use LoginCidadao\PhoneVerificationBundle\Service\PhoneVerificationServiceInterface; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; @@ -36,15 +40,13 @@ public function testManuallyBlocked() ->method('findByPhone')->with($phoneNumber) ->willReturn($blocked); - $em = $this->getEntityManager([ - BlockedPhoneNumber::class => $blockedPhoneRepository, - Person::class => $this->createMock(PersonRepository::class), - ]); + $em = $this->getEntityManager($blockedPhoneRepository); $options = new BlocklistOptions(2); - $blocklist = new Blocklist($this->getUserManager(), $this->getMailer(), $em, $options); - $this->assertTrue($blocklist->isBlocked($phoneNumber)); + $blocklist = new Blocklist($this->getUserManager(), $this->getMailer(), $em, + $this->getPhoneVerificationService(), $options); + $this->assertTrue($blocklist->isPhoneBlocked($phoneNumber)); } public function testAutoBlocked() @@ -56,18 +58,15 @@ public function testAutoBlocked() ->method('findByPhone')->with($phoneNumber) ->willReturn(null); - $personRepo = $this->createMock(PersonRepository::class); - $personRepo->expects($this->once())->method('countByPhone')->willReturn(3); + $phoneVerification = $this->getPhoneVerificationService(); + $phoneVerification->expects($this->once())->method('countVerified')->willReturn(3); - $em = $this->getEntityManager([ - BlockedPhoneNumber::class => $blockedPhoneRepository, - Person::class => $personRepo, - ]); + $em = $this->getEntityManager($blockedPhoneRepository); $options = new BlocklistOptions(2); - $blocklist = new Blocklist($this->getUserManager(), $this->getMailer(), $em, $options); - $this->assertTrue($blocklist->isBlocked($phoneNumber)); + $blocklist = new Blocklist($this->getUserManager(), $this->getMailer(), $em, $phoneVerification, $options); + $this->assertTrue($blocklist->isPhoneBlocked($phoneNumber)); } public function testNotBlocked() @@ -79,15 +78,45 @@ public function testNotBlocked() ->method('findByPhone')->with($phoneNumber) ->willReturn(null); - $em = $this->getEntityManager([ - BlockedPhoneNumber::class => $blockedPhoneRepository, - Person::class => $this->createMock(PersonRepository::class), - ]); + $em = $this->getEntityManager($blockedPhoneRepository); $options = new BlocklistOptions(0); - $blocklist = new Blocklist($this->getUserManager(), $this->getMailer(), $em, $options); - $this->assertFalse($blocklist->isBlocked($phoneNumber)); + $blocklist = new Blocklist($this->getUserManager(), $this->getMailer(), $em, + $this->getPhoneVerificationService(), $options); + $this->assertFalse($blocklist->isPhoneBlocked($phoneNumber)); + } + + public function testBlockByPhone() + { + $phoneNumber = new PhoneNumber(); + $users = [ + (new Person())->setMobile($phoneNumber), + (new Person())->setMobile($phoneNumber), + (new Person())->setMobile($phoneNumber), + ]; + + $userManager = $this->getUserManager(); + $userManager->expects($this->once())->method('blockUsersByPhone') + ->with($phoneNumber)->willReturn($users); + + $mailer = $this->getMailer(); + $mailer->expects($this->exactly(count($users)))->method('sendAccountAutoBlockedMessage') + ->with($this->isInstanceOf(PersonInterface::class)); + + $blockedPhoneRepository = $this->createMock(BlockedPhoneNumberRepository::class); + + $em = $this->getEntityManager($blockedPhoneRepository); + + $options = new BlocklistOptions(0); + + $blocklist = new Blocklist($userManager, $mailer, $em, $this->getPhoneVerificationService(), $options); + $this->assertSame($users, $blocklist->blockByPhone($phoneNumber)); + } + + public function testAddBlockedPhoneNumber() + { + } /** @@ -107,17 +136,24 @@ private function getMailer() } /** - * @param array $repositories + * @param BlockedPhoneNumberRepository|MockObject $repository * @return MockObject|EntityManagerInterface */ - private function getEntityManager(array $repositories) + private function getEntityManager($repository) { $em = $this->createMock(EntityManagerInterface::class); - $em->expects($this->atLeast(2))->method('getRepository') - ->willReturnCallback(function ($entity) use ($repositories) { - return $repositories[$entity]; - }); + $em->expects($this->once())->method('getRepository') + ->with(BlockedPhoneNumber::class) + ->willReturn($repository); return $em; } + + /** + * @return MockObject|PhoneVerificationServiceInterface + */ + private function getPhoneVerificationService() + { + return $this->createMock(PhoneVerificationServiceInterface::class); + } } From eac670bfdf9be8e253b8d56ed4ac0cb1d34a28d2 Mon Sep 17 00:00:00 2001 From: Guilherme Donato Date: Tue, 22 Jan 2019 09:57:29 -0200 Subject: [PATCH 08/40] refactored BlocklistSubscriber PROCERGS#830 --- .../Event/BlocklistSubscriber.php | 36 ++++---- .../Resources/config/services.yml | 4 +- .../Tests/Event/BlocklistSubscriberTest.php | 89 +++++++++++++++++++ 3 files changed, 108 insertions(+), 21 deletions(-) create mode 100644 src/LoginCidadao/PhoneVerificationBundle/Tests/Event/BlocklistSubscriberTest.php diff --git a/src/LoginCidadao/PhoneVerificationBundle/Event/BlocklistSubscriber.php b/src/LoginCidadao/PhoneVerificationBundle/Event/BlocklistSubscriber.php index 17a8249a0..5fa9bf09d 100644 --- a/src/LoginCidadao/PhoneVerificationBundle/Event/BlocklistSubscriber.php +++ b/src/LoginCidadao/PhoneVerificationBundle/Event/BlocklistSubscriber.php @@ -12,7 +12,6 @@ use libphonenumber\PhoneNumber; use LoginCidadao\CoreBundle\Model\PersonInterface; -use LoginCidadao\CoreBundle\Security\User\Manager\UserManager; use LoginCidadao\PhoneVerificationBundle\PhoneVerificationEvents; use LoginCidadao\PhoneVerificationBundle\Service\BlocklistInterface; use Symfony\Component\EventDispatcher\EventSubscriberInterface; @@ -27,25 +26,24 @@ class BlocklistSubscriber implements EventSubscriberInterface /** @var BlocklistInterface */ private $blocklist; - /** @var UserManager */ - private $userManager; - /** @var TokenStorageInterface */ private $tokenStorage; /** * BlocklistSubscriber constructor. * @param BlocklistInterface $blocklist - * @param UserManager $userManager - * @param TokenStorageInterface $tokenStorage */ - public function __construct( - BlocklistInterface $blocklist, - UserManager $userManager, - TokenStorageInterface $tokenStorage - ) { + public function __construct(BlocklistInterface $blocklist) + { $this->blocklist = $blocklist; - $this->userManager = $userManager; + } + + /** + * @param TokenStorageInterface $tokenStorage + * @codeCoverageIgnore + */ + public function setTokenStorage(TokenStorageInterface $tokenStorage) + { $this->tokenStorage = $tokenStorage; } @@ -69,17 +67,17 @@ public function onPhoneChange(PhoneChangedEvent $event) public function onLogin(InteractiveLoginEvent $event) { - try { - $person = $event->getAuthenticationToken()->getUser(); + $person = $event->getAuthenticationToken()->getUser(); + if ($person instanceof PersonInterface) { $phone = $person->getMobile(); - } catch (\Exception $e) { - // User object not available... - return; + $this->checkPhone($phone); } - - $this->checkPhone($phone); } + /** + * @param GetResponseEvent $event + * @codeCoverageIgnore + */ public function onRequest(GetResponseEvent $event) { if ($event->isMasterRequest() && null !== $this->tokenStorage->getToken()) { diff --git a/src/LoginCidadao/PhoneVerificationBundle/Resources/config/services.yml b/src/LoginCidadao/PhoneVerificationBundle/Resources/config/services.yml index f09318c79..5e025b442 100644 --- a/src/LoginCidadao/PhoneVerificationBundle/Resources/config/services.yml +++ b/src/LoginCidadao/PhoneVerificationBundle/Resources/config/services.yml @@ -79,8 +79,8 @@ services: class: LoginCidadao\PhoneVerificationBundle\Event\BlocklistSubscriber arguments: - "@phone_verification.blocklist" - - "@lc.user_manager" - - "@security.token_storage" + calls: + - ['setTokenStorage', ["@security.token_storage"]] tags: - { name: kernel.event_subscriber } diff --git a/src/LoginCidadao/PhoneVerificationBundle/Tests/Event/BlocklistSubscriberTest.php b/src/LoginCidadao/PhoneVerificationBundle/Tests/Event/BlocklistSubscriberTest.php new file mode 100644 index 000000000..8c23123ba --- /dev/null +++ b/src/LoginCidadao/PhoneVerificationBundle/Tests/Event/BlocklistSubscriberTest.php @@ -0,0 +1,89 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace LoginCidadao\PhoneVerificationBundle\Tests\Event; + +use libphonenumber\PhoneNumber; +use LoginCidadao\CoreBundle\Model\PersonInterface; +use LoginCidadao\PhoneVerificationBundle\Event\BlocklistSubscriber; +use LoginCidadao\PhoneVerificationBundle\Event\PhoneChangedEvent; +use LoginCidadao\PhoneVerificationBundle\PhoneVerificationEvents; +use LoginCidadao\PhoneVerificationBundle\Service\BlocklistInterface; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Http\Event\InteractiveLoginEvent; +use Symfony\Component\Security\Http\SecurityEvents; + +class BlocklistSubscriberTest extends TestCase +{ + public function testSubscribedEvents() + { + $this->assertEquals([ + PhoneVerificationEvents::PHONE_CHANGED => 'onPhoneChange', + SecurityEvents::INTERACTIVE_LOGIN => 'onLogin', + KernelEvents::REQUEST => 'onRequest', + ], BlocklistSubscriber::getSubscribedEvents()); + } + + public function testOnPhoneChange() + { + $phoneNumber = $this->createMock(PhoneNumber::class); + + $person = $this->createMock(PersonInterface::class); + $person->expects($this->once())->method('getMobile')->willReturn($phoneNumber); + + /** @var PhoneChangedEvent|MockObject $event */ + $event = $this->createMock(PhoneChangedEvent::class); + $event->expects($this->once())->method('getPerson')->willReturn($person); + + $blocklistService = $this->getBlocklistService(true, $phoneNumber); + + $subscriber = new BlocklistSubscriber($blocklistService); + $subscriber->onPhoneChange($event); + } + + public function testOnLogin() + { + $phoneNumber = $this->createMock(PhoneNumber::class); + + $person = $this->createMock(PersonInterface::class); + $person->expects($this->once())->method('getMobile')->willReturn($phoneNumber); + + $token = $this->createMock(TokenInterface::class); + $token->expects($this->once())->method('getUser')->willReturn($person); + + /** @var InteractiveLoginEvent|MockObject $event */ + $event = $this->createMock(InteractiveLoginEvent::class); + $event->expects($this->once())->method('getAuthenticationToken')->willReturn($token); + + $blocklistService = $this->getBlocklistService(true, $phoneNumber); + + $subscriber = new BlocklistSubscriber($blocklistService); + $subscriber->onLogin($event); + } + + /** + * @param bool $expectCheck + * @param PhoneNumber|null $phoneNumber + * @return MockObject|BlocklistInterface + */ + private function getBlocklistService(bool $expectCheck = false, PhoneNumber $phoneNumber = null) + { + $blocklistService = $this->createMock(BlocklistInterface::class); + + if ($expectCheck) { + $blocklistService->expects($this->once())->method('checkPhoneNumber')->with($phoneNumber); + } + + return $blocklistService; + } +} From 15bb285b4717497808bf7ed20d953e0197709810 Mon Sep 17 00:00:00 2001 From: Guilherme Donato Date: Tue, 22 Jan 2019 11:14:59 -0200 Subject: [PATCH 09/40] improved test coverage PROCERGS#830 --- .../Service/Blocklist.php | 7 +- .../Service/BlocklistInterface.php | 4 +- .../Service/PhoneVerificationService.php | 8 +- .../Tests/Service/BlocklistTest.php | 49 ++++++ .../Service/PhoneVerificationServiceTest.php | 149 +++++++++++------- 5 files changed, 153 insertions(+), 64 deletions(-) diff --git a/src/LoginCidadao/PhoneVerificationBundle/Service/Blocklist.php b/src/LoginCidadao/PhoneVerificationBundle/Service/Blocklist.php index 5fa105d3c..7ad7cf05d 100644 --- a/src/LoginCidadao/PhoneVerificationBundle/Service/Blocklist.php +++ b/src/LoginCidadao/PhoneVerificationBundle/Service/Blocklist.php @@ -88,11 +88,14 @@ public function blockByPhone(PhoneNumber $phoneNumber): array /** * @inheritDoc */ - public function checkPhoneNumber(PhoneNumber $phoneNumber) + public function checkPhoneNumber(PhoneNumber $phoneNumber): array { + $blocked = []; if ($this->isPhoneBlocked($phoneNumber)) { - $this->blockByPhone($phoneNumber); + $blocked = $this->blockByPhone($phoneNumber); } + + return $blocked; } public function addBlockedPhoneNumber( diff --git a/src/LoginCidadao/PhoneVerificationBundle/Service/BlocklistInterface.php b/src/LoginCidadao/PhoneVerificationBundle/Service/BlocklistInterface.php index 730f02489..edaac8cc9 100644 --- a/src/LoginCidadao/PhoneVerificationBundle/Service/BlocklistInterface.php +++ b/src/LoginCidadao/PhoneVerificationBundle/Service/BlocklistInterface.php @@ -27,7 +27,7 @@ public function blockByPhone(PhoneNumber $phoneNumber): array; * Checks if the phone is blocked. If it is, all relevant accounts will be blocked. * * @param PhoneNumber $phoneNumber - * @return mixed + * @return PersonInterface[] */ - public function checkPhoneNumber(PhoneNumber $phoneNumber); + public function checkPhoneNumber(PhoneNumber $phoneNumber): array; } diff --git a/src/LoginCidadao/PhoneVerificationBundle/Service/PhoneVerificationService.php b/src/LoginCidadao/PhoneVerificationBundle/Service/PhoneVerificationService.php index b20851468..08604b718 100644 --- a/src/LoginCidadao/PhoneVerificationBundle/Service/PhoneVerificationService.php +++ b/src/LoginCidadao/PhoneVerificationBundle/Service/PhoneVerificationService.php @@ -10,7 +10,7 @@ namespace LoginCidadao\PhoneVerificationBundle\Service; -use Doctrine\ORM\EntityManager; +use Doctrine\ORM\EntityManagerInterface; use libphonenumber\PhoneNumber; use LoginCidadao\CoreBundle\Entity\Person; use LoginCidadao\CoreBundle\Entity\PersonRepository; @@ -33,7 +33,7 @@ class PhoneVerificationService implements PhoneVerificationServiceInterface /** @var PhoneVerificationOptions */ private $options; - /** @var EntityManager */ + /** @var EntityManagerInterface */ private $em; /** @var PhoneVerificationRepository */ @@ -51,12 +51,12 @@ class PhoneVerificationService implements PhoneVerificationServiceInterface /** * PhoneVerificationService constructor. * @param PhoneVerificationOptions $options - * @param EntityManager $em + * @param EntityManagerInterface $em * @param EventDispatcherInterface $dispatcher */ public function __construct( PhoneVerificationOptions $options, - EntityManager $em, + EntityManagerInterface $em, EventDispatcherInterface $dispatcher ) { $this->options = $options; diff --git a/src/LoginCidadao/PhoneVerificationBundle/Tests/Service/BlocklistTest.php b/src/LoginCidadao/PhoneVerificationBundle/Tests/Service/BlocklistTest.php index cc06eeecc..29e938c80 100644 --- a/src/LoginCidadao/PhoneVerificationBundle/Tests/Service/BlocklistTest.php +++ b/src/LoginCidadao/PhoneVerificationBundle/Tests/Service/BlocklistTest.php @@ -116,7 +116,56 @@ public function testBlockByPhone() public function testAddBlockedPhoneNumber() { + /** @var MockObject|PhoneNumber $phoneNumber */ + $phoneNumber = $this->createMock(PhoneNumber::class); + /** @var MockObject|PersonInterface $blocker */ + $blocker = $this->createMock(PersonInterface::class); + + $userManager = $this->getUserManager(); + $options = new BlocklistOptions(0); + + $blockedPhoneRepository = $this->createMock(BlockedPhoneNumberRepository::class); + $em = $this->getEntityManager($blockedPhoneRepository); + $em->expects($this->once())->method('flush'); + $em->expects($this->once())->method('persist') + ->with($this->isInstanceOf(BlockedPhoneNumberInterface::class)); + + $service = new Blocklist($userManager, $this->getMailer(), $em, $this->getPhoneVerificationService(), $options); + $blockedPhoneNumber = $service->addBlockedPhoneNumber($phoneNumber, $blocker); + + $this->assertInstanceOf(BlockedPhoneNumberInterface::class, $blockedPhoneNumber); + $this->assertInstanceOf(\DateTime::class, $blockedPhoneNumber->getCreatedAt()); + $this->assertSame($phoneNumber, $blockedPhoneNumber->getPhoneNumber()); + $this->assertSame($blocker, $blockedPhoneNumber->getBlockedBy()); + } + + public function testCheckPhoneNumber() + { + $users = [ + $this->createMock(PersonInterface::class), + $this->createMock(PersonInterface::class), + ]; + + /** @var PhoneNumber|MockObject $phoneNumber */ + $phoneNumber = $this->createMock(PhoneNumber::class); + $blockedPhone = $this->createMock(BlockedPhoneNumberInterface::class); + + $blockedPhoneRepository = $this->createMock(BlockedPhoneNumberRepository::class); + $blockedPhoneRepository->expects($this->once())->method('findByPhone')->willReturn($blockedPhone); + + $userManager = $this->getUserManager(); + $userManager->expects($this->once())->method('blockUsersByPhone')->with($phoneNumber)->willReturn($users); + + $mailer = $this->getMailer(); + $mailer->expects($this->exactly(count($users)))->method('sendAccountAutoBlockedMessage') + ->with($this->isInstanceOf(PersonInterface::class)); + + $em = $this->getEntityManager($blockedPhoneRepository); + $options = new BlocklistOptions(0); + + $service = new Blocklist($userManager, $mailer, $em, $this->getPhoneVerificationService(), $options); + $service->checkPhoneNumber($phoneNumber); } /** diff --git a/src/LoginCidadao/PhoneVerificationBundle/Tests/Service/PhoneVerificationServiceTest.php b/src/LoginCidadao/PhoneVerificationBundle/Tests/Service/PhoneVerificationServiceTest.php index 4cfc121c5..1f17da963 100644 --- a/src/LoginCidadao/PhoneVerificationBundle/Tests/Service/PhoneVerificationServiceTest.php +++ b/src/LoginCidadao/PhoneVerificationBundle/Tests/Service/PhoneVerificationServiceTest.php @@ -10,29 +10,37 @@ namespace LoginCidadao\PhoneVerificationBundle\Tests\Service; +use Doctrine\ORM\EntityManagerInterface; +use libphonenumber\PhoneNumber; use LoginCidadao\CoreBundle\Entity\Person; use LoginCidadao\CoreBundle\Entity\PersonRepository; +use LoginCidadao\CoreBundle\Model\PersonInterface; use LoginCidadao\PhoneVerificationBundle\Entity\PhoneVerification; use LoginCidadao\PhoneVerificationBundle\Entity\PhoneVerificationRepository; use LoginCidadao\PhoneVerificationBundle\Entity\SentVerification; use LoginCidadao\PhoneVerificationBundle\Entity\SentVerificationRepository; +use LoginCidadao\PhoneVerificationBundle\Event\SendPhoneVerificationEvent; +use LoginCidadao\PhoneVerificationBundle\Exception\VerificationNotSentException; +use LoginCidadao\PhoneVerificationBundle\Model\PhoneVerificationInterface; +use LoginCidadao\PhoneVerificationBundle\Model\SentVerificationInterface; use LoginCidadao\PhoneVerificationBundle\PhoneVerificationEvents; +use LoginCidadao\PhoneVerificationBundle\Service\PhoneVerificationOptions; use LoginCidadao\PhoneVerificationBundle\Service\PhoneVerificationService; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\HttpKernel\Exception\TooManyRequestsHttpException; class PhoneVerificationServiceTest extends TestCase { private function getPhoneVerification() { - $phoneVerificationClass = 'LoginCidadao\PhoneVerificationBundle\Model\PhoneVerificationInterface'; - $phoneVerification = $this->createMock($phoneVerificationClass); - - return $phoneVerification; + return $this->createMock(PhoneVerificationInterface::class); } private function getServiceOptions() { - $options = $this->getMockBuilder('LoginCidadao\PhoneVerificationBundle\Service\PhoneVerificationOptions') + $options = $this->getMockBuilder(PhoneVerificationOptions::class) ->disableOriginalConstructor() ->getMock(); @@ -41,16 +49,12 @@ private function getServiceOptions() private function getEntityManager() { - return $this->getMockBuilder('Doctrine\ORM\EntityManager') - ->disableOriginalConstructor() - ->getMock(); + return $this->createMock(EntityManagerInterface::class); } private function getDispatcher() { - return $this->getMockBuilder('Symfony\Component\EventDispatcher\EventDispatcherInterface') - ->disableOriginalConstructor() - ->getMock(); + return $this->createMock(EventDispatcherInterface::class); } private function getRepository($class) @@ -142,24 +146,22 @@ function ($class) use ($phoneVerificationRepository, $sentVerificationRepository public function testGetPhoneVerification() { - $phoneVerificationClass = 'LoginCidadao\PhoneVerificationBundle\Model\PhoneVerificationInterface'; - $phoneVerification = $this->createMock($phoneVerificationClass); + $phoneVerification = $this->getPhoneVerification(); $repository = $this->getPhoneVerificationRepository(); $repository->expects($this->once())->method('findOneBy')->willReturn($phoneVerification); $service = $this->getService(['phone_verification_repository' => $repository]); - $person = $this->createMock('LoginCidadao\CoreBundle\Model\PersonInterface'); - $phone = $this->createMock('libphonenumber\PhoneNumber'); + $person = $this->createMock(PersonInterface::class); + $phone = $this->createMock(PhoneNumber::class); $this->assertEquals($phoneVerification, $service->getPhoneVerification($person, $phone)); } public function testCreatePhoneVerification() { - $phoneVerificationClass = 'LoginCidadao\PhoneVerificationBundle\Model\PhoneVerificationInterface'; - $existingPhoneVerification = $this->createMock($phoneVerificationClass); + $existingPhoneVerification = $this->getPhoneVerification(); $repository = $this->getPhoneVerificationRepository(); $repository->expects($this->atLeastOnce())->method('findOneBy')->willReturn($existingPhoneVerification); @@ -171,19 +173,17 @@ public function testCreatePhoneVerification() $service = $this->getService(['em' => $em, 'phone_verification_repository' => $repository]); - $person = $this->createMock('LoginCidadao\CoreBundle\Model\PersonInterface'); - $phone = $this->createMock('libphonenumber\PhoneNumber'); + $person = $this->createMock(PersonInterface::class); + $phone = $this->createMock(PhoneNumber::class); - $this->assertInstanceOf($phoneVerificationClass, $service->createPhoneVerification($person, $phone)); + $this->assertInstanceOf(PhoneVerificationInterface::class, $service->createPhoneVerification($person, $phone)); } public function testGetPendingPhoneVerification() { - $phoneVerificationClass = 'LoginCidadao\PhoneVerificationBundle\Model\PhoneVerificationInterface'; - $phoneVerification = $this->createMock($phoneVerificationClass); + $phoneVerification = $this->getPhoneVerification(); - $repoClass = 'LoginCidadao\PhoneVerificationBundle\Entity\PhoneVerificationRepository'; - $repository = $this->getMockBuilder($repoClass) + $repository = $this->getMockBuilder(PhoneVerificationRepository::class) ->disableOriginalConstructor() ->getMock(); $repository->expects($this->once())->method('findOneBy')->willReturn($phoneVerification); @@ -191,8 +191,8 @@ public function testGetPendingPhoneVerification() $service = $this->getService(['phone_verification_repository' => $repository]); - $person = $this->createMock('LoginCidadao\CoreBundle\Model\PersonInterface'); - $phone = $this->createMock('libphonenumber\PhoneNumber'); + $person = $this->createMock(PersonInterface::class); + $phone = $this->createMock(PhoneNumber::class); $this->assertEquals($phoneVerification, $service->getPendingPhoneVerification($person, $phone)); $this->assertEquals($phoneVerification, $service->getAllPendingPhoneVerification($person)); @@ -206,8 +206,7 @@ public function testRemovePhoneVerification() $service = $this->getService(compact('em')); - $phoneVerificationClass = 'LoginCidadao\PhoneVerificationBundle\Model\PhoneVerificationInterface'; - $phoneVerification = $this->createMock($phoneVerificationClass); + $phoneVerification = $this->createMock(PhoneVerificationInterface::class); $this->assertTrue($service->removePhoneVerification($phoneVerification)); } @@ -222,8 +221,8 @@ public function testEnforcePhoneVerification() $service = $this->getService(['em' => $em, 'phone_verification_repository' => $repository]); - $person = $this->createMock('LoginCidadao\CoreBundle\Model\PersonInterface'); - $phone = $this->createMock('libphonenumber\PhoneNumber'); + $person = $this->createMock(PersonInterface::class); + $phone = $this->createMock(PhoneNumber::class); $service->enforcePhoneVerification($person, $phone); } @@ -252,8 +251,7 @@ public function testCheckNotCaseSensitiveVerificationCode() public function testSuccessfulVerify() { - $phoneVerificationClass = 'LoginCidadao\PhoneVerificationBundle\Model\PhoneVerificationInterface'; - $phoneVerification = $this->createMock($phoneVerificationClass); + $phoneVerification = $this->createMock(PhoneVerificationInterface::class); $phoneVerification->expects($this->once())->method('setVerifiedAt'); $phoneVerification->expects($this->atLeastOnce())->method('getVerificationCode')->willReturn('123'); @@ -272,8 +270,7 @@ public function testSuccessfulVerify() public function testUnsuccessfulVerify() { - $phoneVerificationClass = 'LoginCidadao\PhoneVerificationBundle\Model\PhoneVerificationInterface'; - $phoneVerification = $this->createMock($phoneVerificationClass); + $phoneVerification = $this->createMock(PhoneVerificationInterface::class); $phoneVerification->expects($this->never())->method('setVerifiedAt'); $phoneVerification->expects($this->atLeastOnce())->method('getVerificationCode')->willReturn('321'); @@ -293,9 +290,8 @@ public function testUnsuccessfulVerify() public function testGetPhoneVerificationById() { $id = random_int(1, 9999); - $phoneVerificationClass = 'LoginCidadao\PhoneVerificationBundle\Model\PhoneVerificationInterface'; - $phoneVerification = $this->createMock($phoneVerificationClass); - $person = $this->createMock('LoginCidadao\CoreBundle\Model\PersonInterface'); + $phoneVerification = $this->createMock(PhoneVerificationInterface::class); + $person = $this->createMock(PersonInterface::class); $repository = $this->getPhoneVerificationRepository(); $repository->expects($this->once())->method('findOneBy') @@ -310,9 +306,8 @@ public function testGetPhoneVerificationById() public function testGetPendingPhoneVerificationById() { $id = random_int(1, 9999); - $phoneVerificationClass = 'LoginCidadao\PhoneVerificationBundle\Model\PhoneVerificationInterface'; - $phoneVerification = $this->createMock($phoneVerificationClass); - $person = $this->createMock('LoginCidadao\CoreBundle\Model\PersonInterface'); + $phoneVerification = $this->createMock(PhoneVerificationInterface::class); + $person = $this->createMock(PersonInterface::class); $repository = $this->getPhoneVerificationRepository(); $repository->expects($this->once())->method('findOneBy') @@ -326,18 +321,15 @@ public function testGetPendingPhoneVerificationById() public function testSendVerificationCodeSuccess() { - $phoneVerificationClass = 'LoginCidadao\PhoneVerificationBundle\Model\PhoneVerificationInterface'; - $phoneVerification = $this->createMock($phoneVerificationClass); + $phoneVerification = $this->getPhoneVerification(); - $sentVerification = $this->createMock( - 'LoginCidadao\PhoneVerificationBundle\Model\SentVerificationInterface' - ); + $sentVerification = $this->createMock(SentVerificationInterface::class); $dispatcher = $this->getDispatcher(); $dispatcher->expects($this->once())->method('dispatch') ->with( PhoneVerificationEvents::PHONE_VERIFICATION_REQUESTED, - $this->isInstanceOf('LoginCidadao\PhoneVerificationBundle\Event\SendPhoneVerificationEvent') + $this->isInstanceOf(SendPhoneVerificationEvent::class) )->willReturnCallback( function ($eventName, $event) use ($sentVerification) { $event->setSentVerification($sentVerification); @@ -352,7 +344,7 @@ function ($eventName, $event) use ($sentVerification) { public function testSendVerificationCodeFailure() { - $this->expectException('LoginCidadao\PhoneVerificationBundle\Exception\VerificationNotSentException'); + $this->expectException(VerificationNotSentException::class); $phoneVerification = $this->getPhoneVerification(); @@ -360,7 +352,7 @@ public function testSendVerificationCodeFailure() $dispatcher->expects($this->once())->method('dispatch') ->with( PhoneVerificationEvents::PHONE_VERIFICATION_REQUESTED, - $this->isInstanceOf('LoginCidadao\PhoneVerificationBundle\Event\SendPhoneVerificationEvent') + $this->isInstanceOf(SendPhoneVerificationEvent::class) ); $service = $this->getService(compact('dispatcher')); @@ -375,13 +367,11 @@ public function testResendVerificationCodeSuccess() $dispatcher->expects($this->once())->method('dispatch') ->with( PhoneVerificationEvents::PHONE_VERIFICATION_REQUESTED, - $this->isInstanceOf('LoginCidadao\PhoneVerificationBundle\Event\SendPhoneVerificationEvent') + $this->isInstanceOf(SendPhoneVerificationEvent::class) )->willReturnCallback( function ($eventName, $event) { $sentAt = new \DateTime("-5 minutes"); - $sentVerification = $this->createMock( - 'LoginCidadao\PhoneVerificationBundle\Model\SentVerificationInterface' - ); + $sentVerification = $this->createMock(SentVerificationInterface::class); $event->setSentVerification($sentVerification); } ); @@ -392,9 +382,9 @@ function ($eventName, $event) { public function testResendVerificationCodeFailure() { - $this->expectException('Symfony\Component\HttpKernel\Exception\TooManyRequestsHttpException'); + $this->expectException(TooManyRequestsHttpException::class); $sentAt = new \DateTime(); - $sentVerification = $this->createMock('LoginCidadao\PhoneVerificationBundle\Model\SentVerificationInterface'); + $sentVerification = $this->createMock(SentVerificationInterface::class); $sentVerification->expects($this->once())->method('getSentAt')->willReturn($sentAt); $phoneVerification = $this->getPhoneVerification(); @@ -411,7 +401,7 @@ public function testResendVerificationCodeFailure() public function testRegisterVerificationSent() { - $sentVerification = $this->createMock('LoginCidadao\PhoneVerificationBundle\Model\SentVerificationInterface'); + $sentVerification = $this->createMock(SentVerificationInterface::class); $em = $this->getEntityManager(); $em->expects($this->once())->method('persist')->with($sentVerification); @@ -426,7 +416,7 @@ public function testGetNextResendDate() $timeout = \DateInterval::createFromDateString('+ 5 minutes'); $sentAt = new \DateTime("-5 minutes"); $expected = $sentAt->add($timeout); - $sentVerification = $this->createMock('LoginCidadao\PhoneVerificationBundle\Model\SentVerificationInterface'); + $sentVerification = $this->createMock(SentVerificationInterface::class); $sentVerification->expects($this->once())->method('getSentAt')->willReturn($sentAt); $repository = $this->getSentVerificationRepository(); @@ -477,4 +467,51 @@ public function testVerifyTokenInvalidToken() $service = $this->getService(); $this->assertFalse($service->verifyToken($phoneVerification, 'wrong')); } + + public function testMandatoryVerification() + { + $this->assertTrue($this->isMandatoryTest(3, 3)); + $this->assertTrue($this->isMandatoryTest(4, 3)); + } + + public function testOptionalVerification() + { + $this->assertFalse($this->isMandatoryTest(2, 3)); + $this->assertFalse($this->isMandatoryTest(0, 3)); + } + + public function testCountVerified() + { + $count = 5; + + /** @var PhoneNumber|MockObject $phoneNumber */ + $phoneNumber = $this->createMock(PhoneNumber::class); + + $repo = $this->getPhoneVerificationRepository(); + $repo->expects($this->once())->method('countVerified')->with($phoneNumber)->willReturn($count); + + $service = $this->getService(['phone_verification_repository' => $repo]); + $this->assertEquals($count, $service->countVerified($phoneNumber)); + } + + private function isMandatoryTest(int $phoneCount, int $threshold) + { + $phoneNumber = $this->createMock(PhoneNumber::class); + + /** @var PhoneVerificationInterface|MockObject $phoneVerification */ + $phoneVerification = $this->createMock(PhoneVerificationInterface::class); + $phoneVerification->expects($this->once())->method('getPhone')->willReturn($phoneNumber); + + $personRepo = $this->getPersonRepository(); + $personRepo->expects($this->once())->method('countByPhone')->with($phoneNumber)->willReturn($phoneCount); + + $options = new PhoneVerificationOptions(6, true, true, false, false, 10, 6, $threshold); + + $phoneVerificationService = $this->getService([ + 'person_repository' => $personRepo, + 'options' => $options, + ]); + + return $phoneVerificationService->isVerificationMandatory($phoneVerification); + } } From 751d91b339dbee6156e323a1db2fe826c30d13bb Mon Sep 17 00:00:00 2001 From: Guilherme Donato Date: Tue, 22 Jan 2019 11:33:04 -0200 Subject: [PATCH 10/40] code cleanup PROCERGS#830 --- .../Service/PhoneVerificationService.php | 12 ------ .../Service/PhoneVerificationServiceTest.php | 39 ++++++++++++++++--- 2 files changed, 33 insertions(+), 18 deletions(-) diff --git a/src/LoginCidadao/PhoneVerificationBundle/Service/PhoneVerificationService.php b/src/LoginCidadao/PhoneVerificationBundle/Service/PhoneVerificationService.php index 08604b718..594db45fa 100644 --- a/src/LoginCidadao/PhoneVerificationBundle/Service/PhoneVerificationService.php +++ b/src/LoginCidadao/PhoneVerificationBundle/Service/PhoneVerificationService.php @@ -130,8 +130,6 @@ public function getPendingPhoneVerificationById($id, PersonInterface $person = n * @param PersonInterface $person * @param mixed $phone * @return PhoneVerificationInterface - * @throws \Doctrine\ORM\ORMException - * @throws \Doctrine\ORM\OptimisticLockException */ public function createPhoneVerification(PersonInterface $person, PhoneNumber $phone) { @@ -191,8 +189,6 @@ public function getAllPendingPhoneVerification(PersonInterface $person) /** * @param PhoneVerificationInterface $phoneVerification * @return bool - * @throws \Doctrine\ORM\ORMException - * @throws \Doctrine\ORM\OptimisticLockException */ public function removePhoneVerification(PhoneVerificationInterface $phoneVerification) { @@ -206,8 +202,6 @@ public function removePhoneVerification(PhoneVerificationInterface $phoneVerific * @param PersonInterface $person * @param mixed $phone * @return PhoneVerificationInterface - * @throws \Doctrine\ORM\ORMException - * @throws \Doctrine\ORM\OptimisticLockException */ public function enforcePhoneVerification(PersonInterface $person, PhoneNumber $phone) { @@ -266,8 +260,6 @@ public function checkVerificationCode($provided, $expected) * @param PhoneVerificationInterface $phoneVerification * @param $providedCode * @return bool - * @throws \Doctrine\ORM\ORMException - * @throws \Doctrine\ORM\OptimisticLockException */ public function verify(PhoneVerificationInterface $phoneVerification, $providedCode) { @@ -312,8 +304,6 @@ public function sendVerificationCode(PhoneVerificationInterface $phoneVerificati /** * @param SentVerificationInterface $sentVerification * @return \LoginCidadao\PhoneVerificationBundle\Entity\SentVerification|SentVerificationInterface - * @throws \Doctrine\ORM\ORMException - * @throws \Doctrine\ORM\OptimisticLockException */ public function registerVerificationSent(SentVerificationInterface $sentVerification) { @@ -344,8 +334,6 @@ public function getNextResendDate(PhoneVerificationInterface $phoneVerification) * @param PhoneVerificationInterface $phoneVerification * @param string $token * @return bool - * @throws \Doctrine\ORM\ORMException - * @throws \Doctrine\ORM\OptimisticLockException */ public function verifyToken(PhoneVerificationInterface $phoneVerification, $token) { diff --git a/src/LoginCidadao/PhoneVerificationBundle/Tests/Service/PhoneVerificationServiceTest.php b/src/LoginCidadao/PhoneVerificationBundle/Tests/Service/PhoneVerificationServiceTest.php index 1f17da963..dd48fc700 100644 --- a/src/LoginCidadao/PhoneVerificationBundle/Tests/Service/PhoneVerificationServiceTest.php +++ b/src/LoginCidadao/PhoneVerificationBundle/Tests/Service/PhoneVerificationServiceTest.php @@ -33,11 +33,17 @@ class PhoneVerificationServiceTest extends TestCase { + /** + * @return MockObject|PhoneVerificationInterface + */ private function getPhoneVerification() { return $this->createMock(PhoneVerificationInterface::class); } + /** + * @return MockObject|PhoneVerificationOptions + */ private function getServiceOptions() { $options = $this->getMockBuilder(PhoneVerificationOptions::class) @@ -47,11 +53,17 @@ private function getServiceOptions() return $options; } + /** + * @return MockObject|EntityManagerInterface + */ private function getEntityManager() { return $this->createMock(EntityManagerInterface::class); } + /** + * @return MockObject|EventDispatcherInterface + */ private function getDispatcher() { return $this->createMock(EventDispatcherInterface::class); @@ -66,16 +78,25 @@ private function getRepository($class) return $repository; } + /** + * @return MockObject|PhoneVerificationRepository + */ private function getPhoneVerificationRepository() { return $this->getRepository(PhoneVerificationRepository::class); } + /** + * @return MockObject|SentVerificationRepository + */ private function getSentVerificationRepository() { return $this->getRepository(SentVerificationRepository::class); } + /** + * @return MockObject|PersonRepository + */ private function getPersonRepository() { return $this->getRepository(PersonRepository::class); @@ -153,6 +174,7 @@ public function testGetPhoneVerification() $service = $this->getService(['phone_verification_repository' => $repository]); + /** @var PersonInterface|MockObject $person */ $person = $this->createMock(PersonInterface::class); $phone = $this->createMock(PhoneNumber::class); @@ -173,6 +195,7 @@ public function testCreatePhoneVerification() $service = $this->getService(['em' => $em, 'phone_verification_repository' => $repository]); + /** @var PersonInterface|MockObject $person */ $person = $this->createMock(PersonInterface::class); $phone = $this->createMock(PhoneNumber::class); @@ -191,6 +214,7 @@ public function testGetPendingPhoneVerification() $service = $this->getService(['phone_verification_repository' => $repository]); + /** @var PersonInterface|MockObject $person */ $person = $this->createMock(PersonInterface::class); $phone = $this->createMock(PhoneNumber::class); @@ -206,7 +230,7 @@ public function testRemovePhoneVerification() $service = $this->getService(compact('em')); - $phoneVerification = $this->createMock(PhoneVerificationInterface::class); + $phoneVerification = $this->getPhoneVerification(); $this->assertTrue($service->removePhoneVerification($phoneVerification)); } @@ -221,6 +245,7 @@ public function testEnforcePhoneVerification() $service = $this->getService(['em' => $em, 'phone_verification_repository' => $repository]); + /** @var PersonInterface|MockObject $person */ $person = $this->createMock(PersonInterface::class); $phone = $this->createMock(PhoneNumber::class); @@ -251,7 +276,7 @@ public function testCheckNotCaseSensitiveVerificationCode() public function testSuccessfulVerify() { - $phoneVerification = $this->createMock(PhoneVerificationInterface::class); + $phoneVerification = $this->getPhoneVerification(); $phoneVerification->expects($this->once())->method('setVerifiedAt'); $phoneVerification->expects($this->atLeastOnce())->method('getVerificationCode')->willReturn('123'); @@ -270,7 +295,7 @@ public function testSuccessfulVerify() public function testUnsuccessfulVerify() { - $phoneVerification = $this->createMock(PhoneVerificationInterface::class); + $phoneVerification = $this->getPhoneVerification(); $phoneVerification->expects($this->never())->method('setVerifiedAt'); $phoneVerification->expects($this->atLeastOnce())->method('getVerificationCode')->willReturn('321'); @@ -323,6 +348,7 @@ public function testSendVerificationCodeSuccess() { $phoneVerification = $this->getPhoneVerification(); + /** @var SentVerificationInterface|MockObject $sentVerification */ $sentVerification = $this->createMock(SentVerificationInterface::class); $dispatcher = $this->getDispatcher(); @@ -331,7 +357,7 @@ public function testSendVerificationCodeSuccess() PhoneVerificationEvents::PHONE_VERIFICATION_REQUESTED, $this->isInstanceOf(SendPhoneVerificationEvent::class) )->willReturnCallback( - function ($eventName, $event) use ($sentVerification) { + function ($eventName, SendPhoneVerificationEvent $event) use ($sentVerification) { $event->setSentVerification($sentVerification); } ); @@ -369,8 +395,8 @@ public function testResendVerificationCodeSuccess() PhoneVerificationEvents::PHONE_VERIFICATION_REQUESTED, $this->isInstanceOf(SendPhoneVerificationEvent::class) )->willReturnCallback( - function ($eventName, $event) { - $sentAt = new \DateTime("-5 minutes"); + function ($eventName, SendPhoneVerificationEvent $event) { + /** @var SentVerificationInterface|MockObject $sentVerification */ $sentVerification = $this->createMock(SentVerificationInterface::class); $event->setSentVerification($sentVerification); } @@ -401,6 +427,7 @@ public function testResendVerificationCodeFailure() public function testRegisterVerificationSent() { + /** @var SentVerificationInterface|MockObject $sentVerification */ $sentVerification = $this->createMock(SentVerificationInterface::class); $em = $this->getEntityManager(); From 1d21fce233f13e0427d91d82541f58a79cbc330b Mon Sep 17 00:00:00 2001 From: Guilherme Donato Date: Wed, 23 Jan 2019 14:10:59 -0200 Subject: [PATCH 11/40] loggin blocked users out PROCERGS#830 --- app/config/security.yml | 3 +- .../EventListener/RequestListener.php | 32 ++++++++++-- .../CoreBundle/Resources/config/services.yml | 2 + .../Security/User/Manager/UserManager.php | 8 +-- .../Event/BlocklistSubscriber.php | 50 +++++-------------- .../Resources/config/services.yml | 3 +- .../Service/Blocklist.php | 2 +- 7 files changed, 53 insertions(+), 47 deletions(-) diff --git a/app/config/security.yml b/app/config/security.yml index c808ceae0..261bf8d68 100644 --- a/app/config/security.yml +++ b/app/config/security.yml @@ -57,7 +57,7 @@ security: ROLE_VIEW_3RD_PARTY_CONNECTIONS: ROLE_PERSON_BLOCK: ROLE_EDIT_CLIENT_SUBJECT_TYPE: - FEATURE_ALPHA: FEATURE_BETA, FEATURE_SHOW_PROFILE_VIEWS, FEATURE_ORGANIZATIONS + FEATURE_ALPHA: FEATURE_BETA, FEATURE_SHOW_PROFILE_VIEWS, FEATURE_ORGANIZATIONS, FEATURE_PHONE_BLOCKLIST FEATURE_BETA: FEATURE_PROD, FEATURE_REMOTE_CLAIMS FEATURE_PROD: FEATURE_2FACTOR_AUTH, FEATURE_INVALIDATE_SESSIONS FEATURE_IGP_VALIDATION: @@ -66,6 +66,7 @@ security: FEATURE_EDIT_USERNAME: FEATURE_SHOW_PROFILE_VIEWS: FEATURE_REMOTE_CLAIMS: + FEATURE_PHONE_BLOCKLIST: providers: chainprovider: diff --git a/src/LoginCidadao/CoreBundle/EventListener/RequestListener.php b/src/LoginCidadao/CoreBundle/EventListener/RequestListener.php index 5723ed838..c12118930 100644 --- a/src/LoginCidadao/CoreBundle/EventListener/RequestListener.php +++ b/src/LoginCidadao/CoreBundle/EventListener/RequestListener.php @@ -10,28 +10,42 @@ namespace LoginCidadao\CoreBundle\EventListener; +use LoginCidadao\CoreBundle\Model\PersonInterface; use Psr\Log\LoggerInterface; +use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpKernel\Event\GetResponseEvent; -use Symfony\Component\HttpKernel\HttpKernel; +use Symfony\Component\Routing\RouterInterface; +use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; class RequestListener { /** @var LoggerInterface */ private $logger; + /** @var TokenStorageInterface */ + private $tokenStorage; + + /** @var RouterInterface */ + private $router; + /** * RequestListener constructor. * @param LoggerInterface $logger + * @param TokenStorageInterface $tokenStorage + * @param RouterInterface $router */ - public function __construct(LoggerInterface $logger) + public function __construct(LoggerInterface $logger, TokenStorageInterface $tokenStorage, RouterInterface $router) { $this->logger = $logger; + $this->tokenStorage = $tokenStorage; + $this->router = $router; } public function onKernelRequest(GetResponseEvent $event) { - if (HttpKernel::MASTER_REQUEST === $event->getRequestType()) { + if ($event->isMasterRequest()) { $this->logReferer($event); + $this->checkUserEnabled($event); } } @@ -45,4 +59,16 @@ private function logReferer(GetResponseEvent $event) $this->logger->info("Request referrer: {$referer}"); } + + private function checkUserEnabled(GetResponseEvent $event) + { + if (null !== $token = $this->tokenStorage->getToken()) { + /** @var PersonInterface $person */ + if (($person = $token->getUser()) instanceof PersonInterface && false === $person->isEnabled()) { + $uri = $this->router->generate('fos_user_security_logout'); + $event->setResponse(new RedirectResponse($uri)); + $event->stopPropagation(); + } + } + } } diff --git a/src/LoginCidadao/CoreBundle/Resources/config/services.yml b/src/LoginCidadao/CoreBundle/Resources/config/services.yml index 07abaf166..e82106eed 100644 --- a/src/LoginCidadao/CoreBundle/Resources/config/services.yml +++ b/src/LoginCidadao/CoreBundle/Resources/config/services.yml @@ -443,6 +443,8 @@ services: class: LoginCidadao\CoreBundle\EventListener\RequestListener arguments: - "@logger" + - "@security.token_storage" + - "@router" tags: - { name: kernel.event_listener, event: kernel.request, method: onKernelRequest } - { name: monolog.logger, channel: request } diff --git a/src/LoginCidadao/CoreBundle/Security/User/Manager/UserManager.php b/src/LoginCidadao/CoreBundle/Security/User/Manager/UserManager.php index dd1d877c3..3c0a69552 100644 --- a/src/LoginCidadao/CoreBundle/Security/User/Manager/UserManager.php +++ b/src/LoginCidadao/CoreBundle/Security/User/Manager/UserManager.php @@ -18,6 +18,9 @@ class UserManager extends BaseManager { + const FLUSH_STRATEGY_ONCE = 'once'; + const FLUSH_STRATEGY_EACH = 'each'; + public function createUser() { return parent::createUser(); @@ -122,8 +125,8 @@ public function blockPerson(PersonInterface $person, $andFlush = true) public function blockUsersByPhone(PhoneNumber $phone, $flushStrategy = null) { - $andFlush = $flushStrategy === 'each' ? true : false; - $once = $flushStrategy === 'once' ? true : false; + $andFlush = $flushStrategy === self::FLUSH_STRATEGY_EACH ? true : false; + $once = $flushStrategy === self::FLUSH_STRATEGY_ONCE ? true : false; /** @var PersonInterface[] $users */ $users = parent::getRepository()->findBy(['mobile' => $phone]); @@ -134,7 +137,6 @@ public function blockUsersByPhone(PhoneNumber $phone, $flushStrategy = null) if ($user instanceof PersonInterface) { $blockedUsers[] = $user; } - // TODO: add yield when we upgrade to PHP 7.0 } if ($once) { $this->objectManager->flush(); diff --git a/src/LoginCidadao/PhoneVerificationBundle/Event/BlocklistSubscriber.php b/src/LoginCidadao/PhoneVerificationBundle/Event/BlocklistSubscriber.php index 5fa9bf09d..393de5d88 100644 --- a/src/LoginCidadao/PhoneVerificationBundle/Event/BlocklistSubscriber.php +++ b/src/LoginCidadao/PhoneVerificationBundle/Event/BlocklistSubscriber.php @@ -15,9 +15,7 @@ use LoginCidadao\PhoneVerificationBundle\PhoneVerificationEvents; use LoginCidadao\PhoneVerificationBundle\Service\BlocklistInterface; use Symfony\Component\EventDispatcher\EventSubscriberInterface; -use Symfony\Component\HttpKernel\Event\GetResponseEvent; -use Symfony\Component\HttpKernel\KernelEvents; -use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; +use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; use Symfony\Component\Security\Http\Event\InteractiveLoginEvent; use Symfony\Component\Security\Http\SecurityEvents; @@ -26,25 +24,18 @@ class BlocklistSubscriber implements EventSubscriberInterface /** @var BlocklistInterface */ private $blocklist; - /** @var TokenStorageInterface */ - private $tokenStorage; + /** @var AuthorizationCheckerInterface */ + private $authChecker; /** * BlocklistSubscriber constructor. * @param BlocklistInterface $blocklist + * @param AuthorizationCheckerInterface $authorizationChecker */ - public function __construct(BlocklistInterface $blocklist) + public function __construct(BlocklistInterface $blocklist, AuthorizationCheckerInterface $authorizationChecker) { $this->blocklist = $blocklist; - } - - /** - * @param TokenStorageInterface $tokenStorage - * @codeCoverageIgnore - */ - public function setTokenStorage(TokenStorageInterface $tokenStorage) - { - $this->tokenStorage = $tokenStorage; + $this->authChecker = $authorizationChecker; } /** @@ -55,16 +46,21 @@ public static function getSubscribedEvents() return [ PhoneVerificationEvents::PHONE_CHANGED => 'onPhoneChange', SecurityEvents::INTERACTIVE_LOGIN => 'onLogin', - KernelEvents::REQUEST => 'onRequest', // TODO: remove ]; } + /** + * @param PhoneChangedEvent $event + */ public function onPhoneChange(PhoneChangedEvent $event) { $phone = $event->getPerson()->getMobile(); $this->checkPhone($phone); } + /** + * @param InteractiveLoginEvent $event + */ public function onLogin(InteractiveLoginEvent $event) { $person = $event->getAuthenticationToken()->getUser(); @@ -74,29 +70,9 @@ public function onLogin(InteractiveLoginEvent $event) } } - /** - * @param GetResponseEvent $event - * @codeCoverageIgnore - */ - public function onRequest(GetResponseEvent $event) - { - if ($event->isMasterRequest() && null !== $this->tokenStorage->getToken()) { - /** @var PersonInterface $person */ - $person = $this->tokenStorage->getToken()->getUser(); - if ($person instanceof PersonInterface) { - $phone = $person->getMobile(); - - $this->checkPhone($phone); - } - } - } - - /** - * @param PhoneNumber|null $phoneNumber - */ private function checkPhone(?PhoneNumber $phoneNumber) { - if ($phoneNumber instanceof PhoneNumber) { + if ($phoneNumber instanceof PhoneNumber && $this->authChecker->isGranted('FEATURE_PHONE_BLOCKLIST')) { $this->blocklist->checkPhoneNumber($phoneNumber); } } diff --git a/src/LoginCidadao/PhoneVerificationBundle/Resources/config/services.yml b/src/LoginCidadao/PhoneVerificationBundle/Resources/config/services.yml index 5e025b442..d470cea9b 100644 --- a/src/LoginCidadao/PhoneVerificationBundle/Resources/config/services.yml +++ b/src/LoginCidadao/PhoneVerificationBundle/Resources/config/services.yml @@ -79,8 +79,7 @@ services: class: LoginCidadao\PhoneVerificationBundle\Event\BlocklistSubscriber arguments: - "@phone_verification.blocklist" - calls: - - ['setTokenStorage', ["@security.token_storage"]] + - "@security.authorization_checker" tags: - { name: kernel.event_subscriber } diff --git a/src/LoginCidadao/PhoneVerificationBundle/Service/Blocklist.php b/src/LoginCidadao/PhoneVerificationBundle/Service/Blocklist.php index 7ad7cf05d..c8ba31dc8 100644 --- a/src/LoginCidadao/PhoneVerificationBundle/Service/Blocklist.php +++ b/src/LoginCidadao/PhoneVerificationBundle/Service/Blocklist.php @@ -79,7 +79,7 @@ public function isPhoneBlocked(PhoneNumber $phoneNumber): bool */ public function blockByPhone(PhoneNumber $phoneNumber): array { - $blockedUsers = $this->userManager->blockUsersByPhone($phoneNumber, false); + $blockedUsers = $this->userManager->blockUsersByPhone($phoneNumber, UserManager::FLUSH_STRATEGY_ONCE); $this->notifyBlockedUsers($blockedUsers); return $blockedUsers; From c23d55af49792e02e5e70d6f38c26ba6a6d01519 Mon Sep 17 00:00:00 2001 From: Guilherme Donato Date: Wed, 23 Jan 2019 16:18:25 -0200 Subject: [PATCH 12/40] improved RequestListener and its tests PROCERGS#830 --- .../EventListener/RequestListener.php | 18 ++- .../EventListener/RequestListenerTest.php | 144 +++++++++++++----- 2 files changed, 119 insertions(+), 43 deletions(-) diff --git a/src/LoginCidadao/CoreBundle/EventListener/RequestListener.php b/src/LoginCidadao/CoreBundle/EventListener/RequestListener.php index c12118930..6b9eb1a71 100644 --- a/src/LoginCidadao/CoreBundle/EventListener/RequestListener.php +++ b/src/LoginCidadao/CoreBundle/EventListener/RequestListener.php @@ -10,8 +10,9 @@ namespace LoginCidadao\CoreBundle\EventListener; -use LoginCidadao\CoreBundle\Model\PersonInterface; +use FOS\UserBundle\Model\FosUserInterface; use Psr\Log\LoggerInterface; +use Symfony\Component\HttpFoundation\HeaderBag; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpKernel\Event\GetResponseEvent; use Symfony\Component\Routing\RouterInterface; @@ -51,20 +52,21 @@ public function onKernelRequest(GetResponseEvent $event) private function logReferer(GetResponseEvent $event) { + /** @var HeaderBag $headers */ + $headers = $event->getRequest()->headers; + /** @var string|false $referer */ - $referer = $event->getRequest()->headers->get('referer', false); - if (false === $referer) { - return; + $referer = $headers instanceof HeaderBag ? $headers->get('referer', false) : false; + if (false !== $referer) { + $this->logger->info("Request referrer: {$referer}"); } - - $this->logger->info("Request referrer: {$referer}"); } private function checkUserEnabled(GetResponseEvent $event) { if (null !== $token = $this->tokenStorage->getToken()) { - /** @var PersonInterface $person */ - if (($person = $token->getUser()) instanceof PersonInterface && false === $person->isEnabled()) { + /** @var FosUserInterface $person */ + if (($person = $token->getUser()) instanceof FosUserInterface && false === $person->isEnabled()) { $uri = $this->router->generate('fos_user_security_logout'); $event->setResponse(new RedirectResponse($uri)); $event->stopPropagation(); diff --git a/src/LoginCidadao/CoreBundle/Tests/EventListener/RequestListenerTest.php b/src/LoginCidadao/CoreBundle/Tests/EventListener/RequestListenerTest.php index 6e1655326..c31982264 100644 --- a/src/LoginCidadao/CoreBundle/Tests/EventListener/RequestListenerTest.php +++ b/src/LoginCidadao/CoreBundle/Tests/EventListener/RequestListenerTest.php @@ -10,69 +10,143 @@ namespace LoginCidadao\CoreBundle\Tests\EventListener; +use FOS\UserBundle\Model\FosUserInterface; +use FOS\UserBundle\Model\UserInterface; use LoginCidadao\CoreBundle\EventListener\RequestListener; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Psr\Log\LoggerInterface; +use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Event\GetResponseEvent; -use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\Routing\RouterInterface; +use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; class RequestListenerTest extends TestCase { public function testOnKernelRequest() { - /** @var LoggerInterface|\PHPUnit_Framework_MockObject_MockObject $logger */ - $logger = $this->createMock('Psr\Log\LoggerInterface'); - $logger->expects($this->once()) - ->method('info')->with($this->stringContains('https://example.com')); + $logger = $this->getLogger('https://example.com'); $request = new Request([], [], [], [], [], ['HTTP_REFERER' => 'https://example.com']); - /** @var GetResponseEvent|\PHPUnit_Framework_MockObject_MockObject $event */ - $event = $this->getMockBuilder('Symfony\Component\HttpKernel\Event\GetResponseEvent') - ->disableOriginalConstructor()->getMock(); - $event->expects($this->once()) - ->method('getRequestType')->willReturn(HttpKernelInterface::MASTER_REQUEST); - $event->expects($this->once()) - ->method('getRequest')->willReturn($request); + $event = $this->getEvent(true, $request); - $listener = new RequestListener($logger); + $listener = new RequestListener($logger, $this->getTokenStorage(), $this->getRouter()); $listener->onKernelRequest($event); } public function testNoReferer() { - /** @var LoggerInterface|\PHPUnit_Framework_MockObject_MockObject $logger */ - $logger = $this->createMock('Psr\Log\LoggerInterface'); - $logger->expects($this->never())->method('info'); + $event = $this->getEvent(true, new Request()); - $request = new Request(); + $listener = new RequestListener($this->getLogger(), $this->getTokenStorage(), $this->getRouter()); + $listener->onKernelRequest($event); + } - /** @var GetResponseEvent|\PHPUnit_Framework_MockObject_MockObject $event */ - $event = $this->getMockBuilder('Symfony\Component\HttpKernel\Event\GetResponseEvent') - ->disableOriginalConstructor()->getMock(); - $event->expects($this->once()) - ->method('getRequestType')->willReturn(HttpKernelInterface::MASTER_REQUEST); - $event->expects($this->once()) - ->method('getRequest')->willReturn($request); + public function testNotMasterRequest() + { + $event = $this->getEvent(false); - $listener = new RequestListener($logger); + $listener = new RequestListener($this->getLogger(), $this->getTokenStorage(), $this->getRouter()); $listener->onKernelRequest($event); } - public function testNotMasterRequest() + public function testDisabledUser() + { + $user = $this->createMock(FosUserInterface::class); + $user->expects($this->once())->method('isEnabled')->willReturn(false); + + $token = $this->createMock(TokenInterface::class); + $token->expects($this->once())->method('getUser')->willReturn($user); + + $event = $this->getEvent(true); + $event->expects($this->once())->method('stopPropagation'); + $event->expects($this->once())->method('setResponse') + ->with($this->isInstanceOf(RedirectResponse::class)); + + $tokenStorage = $this->getTokenStorage(); + $tokenStorage->expects($this->once())->method('getToken')->willReturn($token); + + $router = $this->getRouter('fos_user_security_logout'); + + $listener = new RequestListener($this->getLogger(), $tokenStorage, $router); + $listener->onKernelRequest($event); + } + + public function testLoggedInButNoUser() { - /** @var LoggerInterface|\PHPUnit_Framework_MockObject_MockObject $logger */ - $logger = $this->createMock('Psr\Log\LoggerInterface'); - $logger->expects($this->never())->method('info'); + $token = $this->createMock(TokenInterface::class); + $token->expects($this->once())->method('getUser')->willReturn(null); + + $event = $this->getEvent(true); + $event->expects($this->never())->method('stopPropagation'); + $event->expects($this->never())->method('setResponse'); + + $tokenStorage = $this->getTokenStorage(); + $tokenStorage->expects($this->once())->method('getToken')->willReturn($token); - /** @var GetResponseEvent|\PHPUnit_Framework_MockObject_MockObject $event */ - $event = $this->getMockBuilder('Symfony\Component\HttpKernel\Event\GetResponseEvent') - ->disableOriginalConstructor()->getMock(); - $event->expects($this->once()) - ->method('getRequestType')->willReturn(HttpKernelInterface::SUB_REQUEST); + $router = $this->getRouter(); + $router->expects($this->never())->method('generate'); - $listener = new RequestListener($logger); + $listener = new RequestListener($this->getLogger(), $tokenStorage, $router); $listener->onKernelRequest($event); } + + /** + * @return MockObject|TokenStorageInterface + */ + private function getTokenStorage() + { + return $this->createMock(TokenStorageInterface::class); + } + + /** + * @param string|null $expected + * @return MockObject|RouterInterface + */ + private function getRouter(string $expected = null) + { + $router = $this->createMock(RouterInterface::class); + if (null !== $expected) { + $router->expects($this->once())->method('generate')->with($expected)->willReturn($expected); + } + + return $router; + } + + /** + * @param bool $isMasterRequest + * @param Request|null $request + * @return MockObject|GetResponseEvent + */ + private function getEvent(bool $isMasterRequest, Request $request = null) + { + /** @var GetResponseEvent|MockObject $event */ + $event = $this->createMock(GetResponseEvent::class); + $event->expects($this->once())->method('isMasterRequest')->willReturn($isMasterRequest); + + if (null !== $request) { + $event->expects($this->once())->method('getRequest')->willReturn($request); + } + + return $event; + } + + /** + * @param string|null $expectedString + * @return MockObject|LoggerInterface + */ + private function getLogger(string $expectedString = null) + { + $logger = $this->createMock(LoggerInterface::class); + if (null !== $expectedString) { + $logger->expects($this->once())->method('info')->with($this->stringContains($expectedString)); + } else { + $logger->expects($this->never())->method('info'); + } + + return $logger; + } } From f2ac3e42737c762bdfc808bca202d55f7c341045 Mon Sep 17 00:00:00 2001 From: Guilherme Donato Date: Wed, 23 Jan 2019 16:23:55 -0200 Subject: [PATCH 13/40] updated tests PROCERGS#830 --- .../Tests/Event/BlocklistSubscriberTest.php | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/src/LoginCidadao/PhoneVerificationBundle/Tests/Event/BlocklistSubscriberTest.php b/src/LoginCidadao/PhoneVerificationBundle/Tests/Event/BlocklistSubscriberTest.php index 8c23123ba..edc069d51 100644 --- a/src/LoginCidadao/PhoneVerificationBundle/Tests/Event/BlocklistSubscriberTest.php +++ b/src/LoginCidadao/PhoneVerificationBundle/Tests/Event/BlocklistSubscriberTest.php @@ -20,6 +20,7 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\HttpKernel\KernelEvents; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; use Symfony\Component\Security\Http\Event\InteractiveLoginEvent; use Symfony\Component\Security\Http\SecurityEvents; @@ -30,7 +31,6 @@ public function testSubscribedEvents() $this->assertEquals([ PhoneVerificationEvents::PHONE_CHANGED => 'onPhoneChange', SecurityEvents::INTERACTIVE_LOGIN => 'onLogin', - KernelEvents::REQUEST => 'onRequest', ], BlocklistSubscriber::getSubscribedEvents()); } @@ -47,7 +47,7 @@ public function testOnPhoneChange() $blocklistService = $this->getBlocklistService(true, $phoneNumber); - $subscriber = new BlocklistSubscriber($blocklistService); + $subscriber = new BlocklistSubscriber($blocklistService, $this->getAuthorizationChecker()); $subscriber->onPhoneChange($event); } @@ -67,7 +67,7 @@ public function testOnLogin() $blocklistService = $this->getBlocklistService(true, $phoneNumber); - $subscriber = new BlocklistSubscriber($blocklistService); + $subscriber = new BlocklistSubscriber($blocklistService, $this->getAuthorizationChecker()); $subscriber->onLogin($event); } @@ -86,4 +86,17 @@ private function getBlocklistService(bool $expectCheck = false, PhoneNumber $pho return $blocklistService; } + + /** + * @param bool $isGranted + * @return MockObject|AuthorizationCheckerInterface + */ + private function getAuthorizationChecker(bool $isGranted = true) + { + $authorizationChecker = $this->createMock(AuthorizationCheckerInterface::class); + $authorizationChecker->expects($this->once())->method('isGranted') + ->with('FEATURE_PHONE_BLOCKLIST')->willReturn($isGranted); + + return $authorizationChecker; + } } From e8ec806f8c78e2cf021c56b9106916eb6068d452 Mon Sep 17 00:00:00 2001 From: Guilherme Donato Date: Wed, 23 Jan 2019 16:30:35 -0200 Subject: [PATCH 14/40] removed unused import PROCERGS#830 --- .../Tests/Event/BlocklistSubscriberTest.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/LoginCidadao/PhoneVerificationBundle/Tests/Event/BlocklistSubscriberTest.php b/src/LoginCidadao/PhoneVerificationBundle/Tests/Event/BlocklistSubscriberTest.php index edc069d51..478647dc6 100644 --- a/src/LoginCidadao/PhoneVerificationBundle/Tests/Event/BlocklistSubscriberTest.php +++ b/src/LoginCidadao/PhoneVerificationBundle/Tests/Event/BlocklistSubscriberTest.php @@ -18,7 +18,6 @@ use LoginCidadao\PhoneVerificationBundle\Service\BlocklistInterface; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use Symfony\Component\HttpKernel\KernelEvents; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; use Symfony\Component\Security\Http\Event\InteractiveLoginEvent; From 92b80fd0383b57f1ced20332a5ddb7823851a4fa Mon Sep 17 00:00:00 2001 From: Guilherme Donato Date: Wed, 23 Jan 2019 16:52:57 -0200 Subject: [PATCH 15/40] testing feature flash PROCERGS#830 --- .../Tests/Event/BlocklistSubscriberTest.php | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/src/LoginCidadao/PhoneVerificationBundle/Tests/Event/BlocklistSubscriberTest.php b/src/LoginCidadao/PhoneVerificationBundle/Tests/Event/BlocklistSubscriberTest.php index 478647dc6..e29d77553 100644 --- a/src/LoginCidadao/PhoneVerificationBundle/Tests/Event/BlocklistSubscriberTest.php +++ b/src/LoginCidadao/PhoneVerificationBundle/Tests/Event/BlocklistSubscriberTest.php @@ -70,6 +70,44 @@ public function testOnLogin() $subscriber->onLogin($event); } + public function testDisabledFeatureOnPhoneChange() + { + $phoneNumber = $this->createMock(PhoneNumber::class); + + $person = $this->createMock(PersonInterface::class); + $person->expects($this->once())->method('getMobile')->willReturn($phoneNumber); + + /** @var PhoneChangedEvent|MockObject $event */ + $event = $this->createMock(PhoneChangedEvent::class); + $event->expects($this->once())->method('getPerson')->willReturn($person); + + $blocklistService = $this->getBlocklistService(false); + $blocklistService->expects($this->never())->method('checkPhoneNumber'); + + $subscriber = new BlocklistSubscriber($blocklistService, $this->getAuthorizationChecker(false)); + $subscriber->onPhoneChange($event); + } + + public function testDisabledFeatureOnLogin() + { + $phoneNumber = $this->createMock(PhoneNumber::class); + + $person = $this->createMock(PersonInterface::class); + $person->expects($this->once())->method('getMobile')->willReturn($phoneNumber); + + $token = $this->createMock(TokenInterface::class); + $token->expects($this->once())->method('getUser')->willReturn($person); + + /** @var InteractiveLoginEvent|MockObject $event */ + $event = $this->createMock(InteractiveLoginEvent::class); + $event->expects($this->once())->method('getAuthenticationToken')->willReturn($token); + + $blocklistService = $this->getBlocklistService(false); + + $subscriber = new BlocklistSubscriber($blocklistService, $this->getAuthorizationChecker(false)); + $subscriber->onLogin($event); + } + /** * @param bool $expectCheck * @param PhoneNumber|null $phoneNumber From de24f8551fd4824b3586b5f5db5119d4c5ced9a4 Mon Sep 17 00:00:00 2001 From: Guilherme Donato Date: Mon, 28 Jan 2019 15:44:32 -0200 Subject: [PATCH 16/40] created role PROCERGS#830 --- app/config/security.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/config/security.yml b/app/config/security.yml index 261bf8d68..1a1bef9e8 100644 --- a/app/config/security.yml +++ b/app/config/security.yml @@ -33,6 +33,7 @@ security: - ROLE_PERSON_EDIT - ROLE_VIEW_3RD_PARTY_CONNECTIONS - ROLE_EDIT_CLIENT_SUBJECT_TYPE + - ROLE_EDIT_BLOCKED_PHONES ROLE_SUPER_ADMIN: - ROLE_ADMIN - ROLE_ALLOWED_TO_SWITCH @@ -57,6 +58,7 @@ security: ROLE_VIEW_3RD_PARTY_CONNECTIONS: ROLE_PERSON_BLOCK: ROLE_EDIT_CLIENT_SUBJECT_TYPE: + ROLE_EDIT_BLOCKED_PHONES: FEATURE_ALPHA: FEATURE_BETA, FEATURE_SHOW_PROFILE_VIEWS, FEATURE_ORGANIZATIONS, FEATURE_PHONE_BLOCKLIST FEATURE_BETA: FEATURE_PROD, FEATURE_REMOTE_CLAIMS FEATURE_PROD: FEATURE_2FACTOR_AUTH, FEATURE_INVALIDATE_SESSIONS From f295e0bd018f48b9884b3085d5d391ec90428e99 Mon Sep 17 00:00:00 2001 From: Guilherme Donato Date: Mon, 28 Jan 2019 15:50:09 -0200 Subject: [PATCH 17/40] created admin pages PROCERGS#830 --- .../Controller/Admin/PersonController.php | 5 +- .../views/Admin/Client/index.html.twig | 6 - .../Resources/views/Admin/sidebar.html.twig | 8 ++ .../Controller/AdminBlocklistController.php | 118 ++++++++++++++++++ .../Entity/BlockedPhoneNumber.php | 6 +- .../Entity/BlockedPhoneNumberRepository.php | 29 ++++- .../Form/BlockPhoneFormType.php | 47 +++++++ .../Form/SearchPhoneNumberType.php | 39 ++++++ .../Model/BlockPhoneNumberRequest.php | 51 ++++++++ .../Resources/config/services.xml | 16 --- .../Resources/translations/messages.en.yml | 23 ++++ .../Resources/translations/messages.pt_BR.yml | 23 ++++ .../views/AdminBlocklist/grid.html.twig | 57 +++++++++ .../views/AdminBlocklist/list.html.twig | 21 ++++ .../views/AdminBlocklist/new.html.twig | 41 ++++++ .../Service/Blocklist.php | 4 - .../Service/BlocklistInterface.php | 11 ++ .../Tests/Form/BlockPhoneFormTypeTest.php | 79 ++++++++++++ .../Tests/Form/SearchPhoneNumberTypeTest.php | 64 ++++++++++ .../Model/BlockPhoneNumberRequestTest.php | 37 ++++++ 20 files changed, 653 insertions(+), 32 deletions(-) create mode 100644 src/LoginCidadao/PhoneVerificationBundle/Controller/AdminBlocklistController.php create mode 100644 src/LoginCidadao/PhoneVerificationBundle/Form/BlockPhoneFormType.php create mode 100644 src/LoginCidadao/PhoneVerificationBundle/Form/SearchPhoneNumberType.php create mode 100644 src/LoginCidadao/PhoneVerificationBundle/Model/BlockPhoneNumberRequest.php delete mode 100644 src/LoginCidadao/PhoneVerificationBundle/Resources/config/services.xml create mode 100644 src/LoginCidadao/PhoneVerificationBundle/Resources/views/AdminBlocklist/grid.html.twig create mode 100644 src/LoginCidadao/PhoneVerificationBundle/Resources/views/AdminBlocklist/list.html.twig create mode 100644 src/LoginCidadao/PhoneVerificationBundle/Resources/views/AdminBlocklist/new.html.twig create mode 100644 src/LoginCidadao/PhoneVerificationBundle/Tests/Form/BlockPhoneFormTypeTest.php create mode 100644 src/LoginCidadao/PhoneVerificationBundle/Tests/Form/SearchPhoneNumberTypeTest.php create mode 100644 src/LoginCidadao/PhoneVerificationBundle/Tests/Model/BlockPhoneNumberRequestTest.php diff --git a/src/LoginCidadao/CoreBundle/Controller/Admin/PersonController.php b/src/LoginCidadao/CoreBundle/Controller/Admin/PersonController.php index 8143dbe48..a051ac68e 100644 --- a/src/LoginCidadao/CoreBundle/Controller/Admin/PersonController.php +++ b/src/LoginCidadao/CoreBundle/Controller/Admin/PersonController.php @@ -6,6 +6,7 @@ use libphonenumber\PhoneNumber; use LoginCidadao\APIBundle\Security\Audit\ActionLogger; use LoginCidadao\CoreBundle\Entity\PersonRepository; +use LoginCidadao\CoreBundle\Form\Type\PersonFilterFormType; use LoginCidadao\CoreBundle\Form\Type\PersonResumeFormType; use LoginCidadao\CoreBundle\Security\User\Manager\UserManager; use LoginCidadao\PhoneVerificationBundle\Service\PhoneVerificationServiceInterface; @@ -39,7 +40,7 @@ public function indexAction(Request $request) if ($request->get('search') !== null) { $data = ['username' => $request->get('search')]; } - $form = $this->createForm('LoginCidadao\CoreBundle\Form\Type\PersonFilterFormType', $data); + $form = $this->createForm(PersonFilterFormType::class, $data); $form = $form->createView(); return compact('form'); @@ -114,7 +115,7 @@ public function blockAction(Request $request, $id, $token) */ public function gridAction(Request $request) { - $form = $this->createForm('LoginCidadao\CoreBundle\Form\Type\PersonFilterFormType'); + $form = $this->createForm(PersonFilterFormType::class); $form->handleRequest($request); $gridView = null; if ($form->isValid()) { diff --git a/src/LoginCidadao/CoreBundle/Resources/views/Admin/Client/index.html.twig b/src/LoginCidadao/CoreBundle/Resources/views/Admin/Client/index.html.twig index e2929dafa..63f441a48 100644 --- a/src/LoginCidadao/CoreBundle/Resources/views/Admin/Client/index.html.twig +++ b/src/LoginCidadao/CoreBundle/Resources/views/Admin/Client/index.html.twig @@ -17,9 +17,3 @@
{% endblock content %} - -{% block javascripts %} -{{ parent() }} - -{% endblock javascripts %} diff --git a/src/LoginCidadao/CoreBundle/Resources/views/Admin/sidebar.html.twig b/src/LoginCidadao/CoreBundle/Resources/views/Admin/sidebar.html.twig index c091f60bd..364c23973 100644 --- a/src/LoginCidadao/CoreBundle/Resources/views/Admin/sidebar.html.twig +++ b/src/LoginCidadao/CoreBundle/Resources/views/Admin/sidebar.html.twig @@ -34,6 +34,14 @@ + {% if is_granted(['FEATURE_PHONE_BLOCKLIST', 'ROLE_EDIT_BLOCKED_PHONES']) %} +
  • + + {{ 'admin.blocklist.menu_item' | trans }} + + +
  • + {% endif %} {% if is_granted('ROLE_AUDIT_PROFILE_VIEWS') %}
  • diff --git a/src/LoginCidadao/PhoneVerificationBundle/Controller/AdminBlocklistController.php b/src/LoginCidadao/PhoneVerificationBundle/Controller/AdminBlocklistController.php new file mode 100644 index 000000000..66bbcd5c7 --- /dev/null +++ b/src/LoginCidadao/PhoneVerificationBundle/Controller/AdminBlocklistController.php @@ -0,0 +1,118 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace LoginCidadao\PhoneVerificationBundle\Controller; + +use LoginCidadao\CoreBundle\Helper\GridHelper; +use LoginCidadao\PhoneVerificationBundle\Entity\BlockedPhoneNumber; +use LoginCidadao\PhoneVerificationBundle\Entity\BlockedPhoneNumberRepository; +use LoginCidadao\PhoneVerificationBundle\Form\BlockPhoneFormType; +use LoginCidadao\PhoneVerificationBundle\Form\SearchPhoneNumberType; +use LoginCidadao\PhoneVerificationBundle\Model\BlockPhoneNumberRequest; +use LoginCidadao\PhoneVerificationBundle\Service\Blocklist; +use LoginCidadao\PhoneVerificationBundle\Service\BlocklistInterface; +use Symfony\Bundle\FrameworkBundle\Controller\Controller; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Session\Session; +use Symfony\Component\Routing\Annotation\Route; +use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security; +use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template; + +/** + * Class AdminBlocklistController + * @package LoginCidadao\PhoneVerificationBundle\Controller + * + * @Security("has_role('ROLE_EDIT_BLOCKED_PHONES')") + * @codeCoverageIgnore + */ +class AdminBlocklistController extends Controller +{ + /** + * @Route("/admin/phones/blocklist", name="phone_blocklist_list") + * @Template() + */ + public function listAction(Request $request) + { + $form = $this->createForm(SearchPhoneNumberType::class); + + $form->handleRequest($request); + if ($form->isSubmitted() && $form->isValid()) { + $data = $form->getData(); + + $grid = new GridHelper(); + $grid->setId('person-grid'); + $grid->setPerPage(5); + $grid->setMaxResult(5); + $grid->setInfiniteGrid(true); + $grid->setRoute('lc_admin_person_grid'); + $grid->setRouteParams([$form->getName()]); + + if ($data['phone']) { + /** @var BlockedPhoneNumberRepository $blockedPhoneRepo */ + $blockedPhoneRepo = $this->getDoctrine()->getRepository(BlockedPhoneNumber::class); + $query = $blockedPhoneRepo->getSearchByPartialPhoneQuery($data['phone']); + $grid->setQueryBuilder($query); + } + + $gridView = $grid->createView($request); + } + + return [ + 'form' => $form->createView(), + 'grid' => $gridView ?? null, + ]; + } + + /** + * @Route("/admin/phones/blocklist/new", name="phone_blocklist_new") + * @Template() + */ + public function newAction(Request $request) + { + $blockRequest = new BlockPhoneNumberRequest($this->getUser()); + + $blockedPhones = []; + $form = $this->createForm(BlockPhoneFormType::class, $blockRequest); + + $form->handleRequest($request); + if ($form->isSubmitted() && $form->isValid()) { + $phoneNumber = $blockRequest->phoneNumber; + + $blocklist = $this->getBlocklistService(); + $blockedPhoneNumber = $blocklist->addBlockedPhoneNumber($phoneNumber, $blockRequest->getBlockedBy()); + $blockedPhones = $blocklist->checkPhoneNumber($blockedPhoneNumber->getPhoneNumber()); + + /** @var Session $session */ + $session = $request->getSession(); + $session->getFlashBag()->add('success', "Phone number successfully banned."); + if (($count = count($blockedPhones)) > 0) { + $session->getFlashBag()->add('success', "{$count} accounts were blocked blocked."); + } + + return $this->redirectToRoute('phone_blocklist_list'); + } + + return [ + 'form' => $form->createView(), + 'blockedPhones' => $blockedPhones, + ]; + } + + /** + * @return BlocklistInterface + */ + private function getBlocklistService(): BlocklistInterface + { + /** @var BlocklistInterface $blocklistService */ + $blocklistService = $this->get('phone_verification.blocklist'); + + return $blocklistService; + } +} diff --git a/src/LoginCidadao/PhoneVerificationBundle/Entity/BlockedPhoneNumber.php b/src/LoginCidadao/PhoneVerificationBundle/Entity/BlockedPhoneNumber.php index d8b018bed..f755ce90e 100644 --- a/src/LoginCidadao/PhoneVerificationBundle/Entity/BlockedPhoneNumber.php +++ b/src/LoginCidadao/PhoneVerificationBundle/Entity/BlockedPhoneNumber.php @@ -21,7 +21,9 @@ * @package LoginCidadao\PhoneVerificationBundle\Model * * @ORM\Entity(repositoryClass="LoginCidadao\PhoneVerificationBundle\Entity\BlockedPhoneNumberRepository") - * @ORM\Table(name="blocked_phone_number") + * @ORM\Table(name="blocked_phone_number", indexes={ + * @ORM\Index(name="blocked_phone_number_idx", columns={"phoneNumber"}) + * }) * @ORM\HasLifecycleCallbacks */ class BlockedPhoneNumber implements BlockedPhoneNumberInterface @@ -38,7 +40,7 @@ class BlockedPhoneNumber implements BlockedPhoneNumberInterface /** * @var PhoneNumber * - * @ORM\Column(name="phone_number", type="phone_number", nullable=false) + * @ORM\Column(name="phone_number", type="phone_number", nullable=false, unique=true) */ private $phoneNumber; diff --git a/src/LoginCidadao/PhoneVerificationBundle/Entity/BlockedPhoneNumberRepository.php b/src/LoginCidadao/PhoneVerificationBundle/Entity/BlockedPhoneNumberRepository.php index b5b45633b..0b0c4a48c 100644 --- a/src/LoginCidadao/PhoneVerificationBundle/Entity/BlockedPhoneNumberRepository.php +++ b/src/LoginCidadao/PhoneVerificationBundle/Entity/BlockedPhoneNumberRepository.php @@ -24,13 +24,38 @@ class BlockedPhoneNumberRepository extends EntityRepository { /** * @param PhoneNumber $phoneNumber - * @return BlockedPhoneNumber + * @return BlockedPhoneNumberInterface */ public function findByPhone(PhoneNumber $phoneNumber): ?BlockedPhoneNumberInterface { - /** @var BlockedPhoneNumber $blockedPhone */ + /** @var BlockedPhoneNumberInterface $blockedPhone */ $blockedPhone = $this->findOneBy(['phoneNumber' => $phoneNumber]); return $blockedPhone; } + + /** + * @param string $search + * @return BlockedPhoneNumberInterface[] + */ + public function searchBlocksByPartialPhone(string $search): array + { + + return $this->getSearchByPartialPhoneQuery($search) + ->getQuery() + ->getResult(); + } + + public function getSearchByPartialPhoneQuery(string $search) + { + if ($search[0] === '+') { + $search = "{$search}%"; + } else { + $search = "%{$search}%"; + } + + return $this->createQueryBuilder('b') + ->where('b.phoneNumber LIKE :search') + ->setParameter('search', $search); + } } diff --git a/src/LoginCidadao/PhoneVerificationBundle/Form/BlockPhoneFormType.php b/src/LoginCidadao/PhoneVerificationBundle/Form/BlockPhoneFormType.php new file mode 100644 index 000000000..6f80ed5ab --- /dev/null +++ b/src/LoginCidadao/PhoneVerificationBundle/Form/BlockPhoneFormType.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace LoginCidadao\PhoneVerificationBundle\Form; + +use libphonenumber\PhoneNumberFormat; +use LoginCidadao\PhoneVerificationBundle\Model\BlockPhoneNumberRequest; +use Misd\PhoneNumberBundle\Form\Type\PhoneNumberType; +use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\Extension\Core\Type\SubmitType; +use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\OptionsResolver\OptionsResolver; + +class BlockPhoneFormType extends AbstractType +{ + public function buildForm(FormBuilderInterface $builder, array $options) + { + $builder + ->add('phoneNumber', PhoneNumberType::class, [ + 'required' => true, + 'label' => 'admin.blocklist.new.form.phone.label', + 'attr' => [ + 'class' => 'form-control intl-tel', + 'placeholder' => 'admin.blocklist.new.form.phone.placeholder', + ], + 'label_attr' => ['class' => 'intl-tel-label'], + 'format' => PhoneNumberFormat::E164, + ]) + ->add('submit', SubmitType::class, [ + 'label' => 'admin.blocklist.new.form.submit.label', + ]); + } + + public function configureOptions(OptionsResolver $resolver) + { + $resolver->setDefaults([ + 'data_class' => BlockPhoneNumberRequest::class, + ]); + } +} diff --git a/src/LoginCidadao/PhoneVerificationBundle/Form/SearchPhoneNumberType.php b/src/LoginCidadao/PhoneVerificationBundle/Form/SearchPhoneNumberType.php new file mode 100644 index 000000000..4d9e5b5b2 --- /dev/null +++ b/src/LoginCidadao/PhoneVerificationBundle/Form/SearchPhoneNumberType.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace LoginCidadao\PhoneVerificationBundle\Form; + +use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\Extension\Core\Type\SubmitType; +use Symfony\Component\Form\Extension\Core\Type\TelType; +use Symfony\Component\Form\FormBuilderInterface; + +class SearchPhoneNumberType extends AbstractType +{ + /** + * @inheritDoc + */ + public function buildForm(FormBuilderInterface $builder, array $options) + { + $builder + ->add('phone', TelType::class, [ + 'required' => true, + 'label' => 'admin.blocklist.list.form.phone.label', + 'attr' => [ + 'pattern' => '[0-9+]*', + 'autocomplete' => 'off', + 'placeholder' => 'admin.blocklist.list.form.phone.placeholder', + ], + ]) + ->add('submit', SubmitType::class, [ + 'label' => 'admin.blocklist.list.form.submit.label', + ]); + } +} diff --git a/src/LoginCidadao/PhoneVerificationBundle/Model/BlockPhoneNumberRequest.php b/src/LoginCidadao/PhoneVerificationBundle/Model/BlockPhoneNumberRequest.php new file mode 100644 index 000000000..be7aa3afd --- /dev/null +++ b/src/LoginCidadao/PhoneVerificationBundle/Model/BlockPhoneNumberRequest.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace LoginCidadao\PhoneVerificationBundle\Model; + +use libphonenumber\PhoneNumber; +use LoginCidadao\CoreBundle\Model\PersonInterface; +use LoginCidadao\ValidationBundle\Validator\Constraints as LCAssert; +use Symfony\Component\Validator\Constraints as Assert; +use Misd\PhoneNumberBundle\Validator\Constraints\PhoneNumber as AssertPhoneNumber; + +class BlockPhoneNumberRequest +{ + /** + * @var PhoneNumber + * @Assert\NotBlank() + * @LCAssert\E164PhoneNumber(maxMessage="person.validation.mobile.length.max") + * @AssertPhoneNumber() + */ + public $phoneNumber; + + /** + * @var PersonInterface + * @Assert\NotBlank() + */ + private $blockedBy; + + /** + * BlockPhoneNumberRequest constructor. + * @param PersonInterface $blockedBy + */ + public function __construct(PersonInterface $blockedBy) + { + $this->blockedBy = $blockedBy; + } + + /** + * @return PersonInterface + */ + public function getBlockedBy(): PersonInterface + { + return $this->blockedBy; + } +} diff --git a/src/LoginCidadao/PhoneVerificationBundle/Resources/config/services.xml b/src/LoginCidadao/PhoneVerificationBundle/Resources/config/services.xml deleted file mode 100644 index 249499582..000000000 --- a/src/LoginCidadao/PhoneVerificationBundle/Resources/config/services.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - diff --git a/src/LoginCidadao/PhoneVerificationBundle/Resources/translations/messages.en.yml b/src/LoginCidadao/PhoneVerificationBundle/Resources/translations/messages.en.yml index d34f3950f..588772512 100644 --- a/src/LoginCidadao/PhoneVerificationBundle/Resources/translations/messages.en.yml +++ b/src/LoginCidadao/PhoneVerificationBundle/Resources/translations/messages.en.yml @@ -35,3 +35,26 @@ tasks: edit: show_form: Click here to change your phone number submit: Change Phone Number + +admin: + blocklist: + menu_item: Ban Phone Numbers + list: + title: Search Banned Phone Numbers + form: + phone: + label: Phone to be searched + placeholder: Type the phone you want to search + submit.label: Search + table: + header: + phone_number: Phone number + blocked_by: Banned by + date_blocked: Date banned + new: + title: Ban users by phone + form: + phone: + label: Phone number that will be banned + placeholder: Type the phone number that will be banned + submit.label: Ban Phone Number diff --git a/src/LoginCidadao/PhoneVerificationBundle/Resources/translations/messages.pt_BR.yml b/src/LoginCidadao/PhoneVerificationBundle/Resources/translations/messages.pt_BR.yml index 86ad36ae7..d8f34abbd 100644 --- a/src/LoginCidadao/PhoneVerificationBundle/Resources/translations/messages.pt_BR.yml +++ b/src/LoginCidadao/PhoneVerificationBundle/Resources/translations/messages.pt_BR.yml @@ -35,3 +35,26 @@ tasks: edit: show_form: Clique aqui para alterar seu telefone submit: Alterar Telefone + +admin: + blocklist: + menu_item: Bloqueio de Telefones + list: + title: Busca de Telefones Banidos + form: + phone: + label: Telefone a ser pesquisado + placeholder: Digite o telefone que deseja pesquisar + submit.label: Pesquisar + table: + header: + phone_number: Telefone + blocked_by: Banido por + date_blocked: Banido em + new: + title: Banir usuários por telefone + form: + phone: + label: Telefone que será banido + placeholder: Digite o telefone a ser banido + submit.label: Banir Telefone diff --git a/src/LoginCidadao/PhoneVerificationBundle/Resources/views/AdminBlocklist/grid.html.twig b/src/LoginCidadao/PhoneVerificationBundle/Resources/views/AdminBlocklist/grid.html.twig new file mode 100644 index 000000000..ffe4409ed --- /dev/null +++ b/src/LoginCidadao/PhoneVerificationBundle/Resources/views/AdminBlocklist/grid.html.twig @@ -0,0 +1,57 @@ +{% extends "LoginCidadaoCoreBundle::grid_layout.html.twig" %} + +{% block grid_header_data %} +
    {{ 'admin.blocklist.list.table.header.phone_number' | trans }}
    +
    {{ 'admin.blocklist.list.table.header.blocked_by' | trans }}
    +
    {{ 'admin.blocklist.list.table.header.date_blocked' | trans }}
    +{% endblock grid_header_data %} + +{% block grid_row_action %} +
    +{% endblock grid_row_action %} + +{% block grid_row_data %} + {% if row.phoneNumber.countryCode == 55 %} + {% set format = 'NATIONAL' %} + {% else %} + {% set format = 'INTERNATIONAL' %} + {% endif %} + +
    +
    {{ 'admin.blocklist.list.table.header.phone_number' | trans }}
    +
    {{ row.phoneNumber | phone_number_format(format) }}
    +
    +
    +
    {{ 'admin.blocklist.list.table.header.blocked_by' | trans }}
    +
    {{ row.blockedBy.fullName }}
    +
    +
    +
    {{ 'admin.blocklist.list.table.header.date_blocked' | trans }}
    +
    {{ row.createdAt | date }}
    +
    +{% endblock grid_row_data %} + +{% block grid_infinite_pagination %} +
    + {% if not grid.getRlast and grid.isInfiniteGrid %} + {% set routeParams = { 'page': grid.page + 1 } %} +
    + +
    + {% endif %} +
    +{% endblock grid_infinite_pagination %} diff --git a/src/LoginCidadao/PhoneVerificationBundle/Resources/views/AdminBlocklist/list.html.twig b/src/LoginCidadao/PhoneVerificationBundle/Resources/views/AdminBlocklist/list.html.twig new file mode 100644 index 000000000..e937b1536 --- /dev/null +++ b/src/LoginCidadao/PhoneVerificationBundle/Resources/views/AdminBlocklist/list.html.twig @@ -0,0 +1,21 @@ +{% extends "LoginCidadaoCoreBundle:Admin:base.html.twig" %} +{% block content %} +
    +
    +
    +

    {{ 'admin.blocklist.list.title'|trans }}

    +
    +
    +
    +
    + {{ form(form) }} +
    +
    +
    +
    + + {% if grid %} + {{ include("LoginCidadaoPhoneVerificationBundle:AdminBlocklist:grid.html.twig") }} + {% endif %} +
    +{% endblock %} diff --git a/src/LoginCidadao/PhoneVerificationBundle/Resources/views/AdminBlocklist/new.html.twig b/src/LoginCidadao/PhoneVerificationBundle/Resources/views/AdminBlocklist/new.html.twig new file mode 100644 index 000000000..b7daa4b75 --- /dev/null +++ b/src/LoginCidadao/PhoneVerificationBundle/Resources/views/AdminBlocklist/new.html.twig @@ -0,0 +1,41 @@ +{% extends "LoginCidadaoCoreBundle:Admin:base.html.twig" %} +{% block content %} +
    +
    +
    +

    {{ 'admin.blocklist.new.title'|trans }}

    +
    +
    +
    +
    + {{ form(form) }} +
    +
    +
    +
    +
    +{% endblock %} + +{% block javascripts %} + {{ parent() }} + + {% javascripts + '@intl_tel_input_js' + '@LoginCidadaoCoreBundle/Resources/public/js/components/intl-tel-input.js' + filter='uglifyjs2' %} + + {% endjavascripts %} + + +{% endblock %} + +{% block stylesheets_custom %} + {% stylesheets '@lc_logged_css' '@intl_tel_input_css' filter='cssrewrite' filter='?uglifycss' %} + + {% endstylesheets %} +{% endblock %} diff --git a/src/LoginCidadao/PhoneVerificationBundle/Service/Blocklist.php b/src/LoginCidadao/PhoneVerificationBundle/Service/Blocklist.php index c8ba31dc8..fe23967b4 100644 --- a/src/LoginCidadao/PhoneVerificationBundle/Service/Blocklist.php +++ b/src/LoginCidadao/PhoneVerificationBundle/Service/Blocklist.php @@ -12,15 +12,11 @@ use Doctrine\ORM\EntityManagerInterface; use libphonenumber\PhoneNumber; -use LoginCidadao\CoreBundle\Entity\Person; -use LoginCidadao\CoreBundle\Entity\PersonRepository; use LoginCidadao\CoreBundle\Mailer\TwigSwiftMailer; use LoginCidadao\CoreBundle\Model\PersonInterface; use LoginCidadao\CoreBundle\Security\User\Manager\UserManager; use LoginCidadao\PhoneVerificationBundle\Entity\BlockedPhoneNumber; use LoginCidadao\PhoneVerificationBundle\Entity\BlockedPhoneNumberRepository; -use LoginCidadao\PhoneVerificationBundle\Entity\PhoneVerification; -use LoginCidadao\PhoneVerificationBundle\Entity\PhoneVerificationRepository; use LoginCidadao\PhoneVerificationBundle\Model\BlockedPhoneNumberInterface; class Blocklist implements BlocklistInterface diff --git a/src/LoginCidadao/PhoneVerificationBundle/Service/BlocklistInterface.php b/src/LoginCidadao/PhoneVerificationBundle/Service/BlocklistInterface.php index edaac8cc9..fac7b831f 100644 --- a/src/LoginCidadao/PhoneVerificationBundle/Service/BlocklistInterface.php +++ b/src/LoginCidadao/PhoneVerificationBundle/Service/BlocklistInterface.php @@ -12,6 +12,7 @@ use libphonenumber\PhoneNumber; use LoginCidadao\CoreBundle\Model\PersonInterface; +use LoginCidadao\PhoneVerificationBundle\Model\BlockedPhoneNumberInterface; interface BlocklistInterface { @@ -30,4 +31,14 @@ public function blockByPhone(PhoneNumber $phoneNumber): array; * @return PersonInterface[] */ public function checkPhoneNumber(PhoneNumber $phoneNumber): array; + + /** + * @param PhoneNumber $phoneNumber + * @param PersonInterface $blockedBy + * @return BlockedPhoneNumberInterface + */ + public function addBlockedPhoneNumber( + PhoneNumber $phoneNumber, + PersonInterface $blockedBy + ): BlockedPhoneNumberInterface; } diff --git a/src/LoginCidadao/PhoneVerificationBundle/Tests/Form/BlockPhoneFormTypeTest.php b/src/LoginCidadao/PhoneVerificationBundle/Tests/Form/BlockPhoneFormTypeTest.php new file mode 100644 index 000000000..df18325c7 --- /dev/null +++ b/src/LoginCidadao/PhoneVerificationBundle/Tests/Form/BlockPhoneFormTypeTest.php @@ -0,0 +1,79 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace LoginCidadao\PhoneVerificationBundle\Tests\Form; + +use libphonenumber\PhoneNumberFormat; +use LoginCidadao\PhoneVerificationBundle\Form\BlockPhoneFormType; +use LoginCidadao\PhoneVerificationBundle\Model\BlockPhoneNumberRequest; +use Misd\PhoneNumberBundle\Form\Type\PhoneNumberType; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; +use Symfony\Component\Form\Extension\Core\Type\SubmitType; +use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\OptionsResolver\OptionsResolver; + +class BlockPhoneFormTypeTest extends TestCase +{ + public function testConfigureOptions() + { + /** @var MockObject|OptionsResolver $resolver */ + $resolver = $this->createMock(OptionsResolver::class); + $resolver->expects($this->once())->method('setDefaults')->with([ + 'data_class' => BlockPhoneNumberRequest::class, + ]); + + $form = new BlockPhoneFormType(); + $form->configureOptions($resolver); + } + + public function testBuildForm() + { + /** @var MockObject|FormBuilderInterface $builder */ + $builder = $this->createMock(FormBuilderInterface::class); + $builder->expects($this->exactly(2)) + ->method('add') + ->willReturnCallback(function ($name, $type, $options) use ($builder) { + switch ($name) { + case 'phoneNumber': + $this->checkPhoneNumberField($type, $options); + + return $builder; + case 'submit': + $this->checkSubmitField($type, $options); + + return $builder; + default: + $this->fail("Unexpected call to add() with name '{$name}'"); + + return null; + } + }); + + $form = new BlockPhoneFormType(); + $form->buildForm($builder, []); + } + + private function checkPhoneNumberField(string $type, array $options) + { + $this->assertEquals(PhoneNumberType::class, $type); + $this->assertTrue($options['required']); + $this->assertEquals('admin.blocklist.new.form.phone.label', $options['label']); + $this->assertStringContainsString('intl-tel', $options['attr']['class']); + $this->assertEquals('admin.blocklist.new.form.phone.placeholder', $options['attr']['placeholder']); + $this->assertEquals(PhoneNumberFormat::E164, $options['format']); + } + + private function checkSubmitField(string $type, array $options) + { + $this->assertEquals(SubmitType::class, $type); + $this->assertEquals('admin.blocklist.new.form.submit.label', $options['label']); + } +} diff --git a/src/LoginCidadao/PhoneVerificationBundle/Tests/Form/SearchPhoneNumberTypeTest.php b/src/LoginCidadao/PhoneVerificationBundle/Tests/Form/SearchPhoneNumberTypeTest.php new file mode 100644 index 000000000..0391c4bd6 --- /dev/null +++ b/src/LoginCidadao/PhoneVerificationBundle/Tests/Form/SearchPhoneNumberTypeTest.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace LoginCidadao\PhoneVerificationBundle\Tests\Form; + +use LoginCidadao\PhoneVerificationBundle\Form\SearchPhoneNumberType; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; +use Symfony\Component\Form\Extension\Core\Type\SubmitType; +use Symfony\Component\Form\Extension\Core\Type\TelType; +use Symfony\Component\Form\FormBuilderInterface; + +class SearchPhoneNumberTypeTest extends TestCase +{ + public function testBuildForm() + { + /** @var MockObject|FormBuilderInterface $builder */ + $builder = $this->createMock(FormBuilderInterface::class); + $builder->expects($this->exactly(2)) + ->method('add') + ->willReturnCallback(function ($name, $type, $options) use ($builder) { + switch ($name) { + case 'phone': + $this->checkPhoneSearchField($type, $options); + + return $builder; + case 'submit': + $this->checkSubmitField($type, $options); + + return $builder; + default: + $this->fail("Unexpected call to add() with name '{$name}'"); + + return null; + } + }); + + $form = new SearchPhoneNumberType(); + $form->buildForm($builder, []); + } + + private function checkPhoneSearchField(string $type, array $options) + { + $this->assertEquals(TelType::class, $type); + $this->assertTrue($options['required']); + $this->assertEquals('admin.blocklist.list.form.phone.label', $options['label']); + $this->assertEquals('[0-9+]*', $options['attr']['pattern']); + $this->assertEquals('off', $options['attr']['autocomplete']); + $this->assertEquals('admin.blocklist.list.form.phone.placeholder', $options['attr']['placeholder']); + } + + private function checkSubmitField(string $type, array $options) + { + $this->assertEquals(SubmitType::class, $type); + $this->assertEquals('admin.blocklist.list.form.submit.label', $options['label']); + } +} diff --git a/src/LoginCidadao/PhoneVerificationBundle/Tests/Model/BlockPhoneNumberRequestTest.php b/src/LoginCidadao/PhoneVerificationBundle/Tests/Model/BlockPhoneNumberRequestTest.php new file mode 100644 index 000000000..aa5595828 --- /dev/null +++ b/src/LoginCidadao/PhoneVerificationBundle/Tests/Model/BlockPhoneNumberRequestTest.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace LoginCidadao\PhoneVerificationBundle\Tests\Model; + +use libphonenumber\PhoneNumber; +use LoginCidadao\CoreBundle\Model\PersonInterface; +use LoginCidadao\PhoneVerificationBundle\Model\BlockPhoneNumberRequest; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; + +class BlockPhoneNumberRequestTest extends TestCase +{ + public function testRequest() + { + /** @var MockObject|PersonInterface $person */ + $person = $this->createMock(PersonInterface::class); + + /** @var MockObject|PhoneNumber $phoneNumber */ + $phoneNumber = $this->createMock(PhoneNumber::class); + + $request = new BlockPhoneNumberRequest($person); + $this->assertSame($person, $request->getBlockedBy()); + $this->assertNull($request->phoneNumber); + + $request->phoneNumber = $phoneNumber; + + $this->assertSame($phoneNumber, $request->phoneNumber); + } +} From 66e80769bca08f034df89f4cd740eae5d147ce11 Mon Sep 17 00:00:00 2001 From: Guilherme Donato Date: Mon, 28 Jan 2019 16:03:40 -0200 Subject: [PATCH 18/40] fixed entity and added audit PROCERGS#830 --- app/config/config.yml | 1 + .../PhoneVerificationBundle/Entity/BlockedPhoneNumber.php | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/app/config/config.yml b/app/config/config.yml index 5195c1e07..f597c2c55 100644 --- a/app/config/config.yml +++ b/app/config/config.yml @@ -288,6 +288,7 @@ simple_things_entity_audit: - LoginCidadao\PhoneVerificationBundle\Entity\PhoneVerification - LoginCidadao\PhoneVerificationBundle\Entity\SentVerification + - LoginCidadao\PhoneVerificationBundle\Entity\BlockedPhoneNumber hwi_oauth: connect: diff --git a/src/LoginCidadao/PhoneVerificationBundle/Entity/BlockedPhoneNumber.php b/src/LoginCidadao/PhoneVerificationBundle/Entity/BlockedPhoneNumber.php index f755ce90e..444ba8822 100644 --- a/src/LoginCidadao/PhoneVerificationBundle/Entity/BlockedPhoneNumber.php +++ b/src/LoginCidadao/PhoneVerificationBundle/Entity/BlockedPhoneNumber.php @@ -22,7 +22,7 @@ * * @ORM\Entity(repositoryClass="LoginCidadao\PhoneVerificationBundle\Entity\BlockedPhoneNumberRepository") * @ORM\Table(name="blocked_phone_number", indexes={ - * @ORM\Index(name="blocked_phone_number_idx", columns={"phoneNumber"}) + * @ORM\Index(name="blocked_phone_number_idx", columns={"phone_number"}) * }) * @ORM\HasLifecycleCallbacks */ From d7ccf833fa16cd38d96f4458c6c71a044ba2285a Mon Sep 17 00:00:00 2001 From: Guilherme Donato Date: Mon, 28 Jan 2019 16:15:56 -0200 Subject: [PATCH 19/40] refactored RequestListener PROCERGS#830 --- .../CoreBundle/EventListener/RequestListener.php | 9 ++------- .../Tests/EventListener/RequestListenerTest.php | 6 ++++-- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/src/LoginCidadao/CoreBundle/EventListener/RequestListener.php b/src/LoginCidadao/CoreBundle/EventListener/RequestListener.php index 6b9eb1a71..68e73e6bd 100644 --- a/src/LoginCidadao/CoreBundle/EventListener/RequestListener.php +++ b/src/LoginCidadao/CoreBundle/EventListener/RequestListener.php @@ -12,7 +12,6 @@ use FOS\UserBundle\Model\FosUserInterface; use Psr\Log\LoggerInterface; -use Symfony\Component\HttpFoundation\HeaderBag; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpKernel\Event\GetResponseEvent; use Symfony\Component\Routing\RouterInterface; @@ -52,12 +51,8 @@ public function onKernelRequest(GetResponseEvent $event) private function logReferer(GetResponseEvent $event) { - /** @var HeaderBag $headers */ - $headers = $event->getRequest()->headers; - - /** @var string|false $referer */ - $referer = $headers instanceof HeaderBag ? $headers->get('referer', false) : false; - if (false !== $referer) { + $referer = $event->getRequest()->headers->get('referer', null); + if (null !== $referer) { $this->logger->info("Request referrer: {$referer}"); } } diff --git a/src/LoginCidadao/CoreBundle/Tests/EventListener/RequestListenerTest.php b/src/LoginCidadao/CoreBundle/Tests/EventListener/RequestListenerTest.php index c31982264..98d9a09ff 100644 --- a/src/LoginCidadao/CoreBundle/Tests/EventListener/RequestListenerTest.php +++ b/src/LoginCidadao/CoreBundle/Tests/EventListener/RequestListenerTest.php @@ -11,7 +11,6 @@ namespace LoginCidadao\CoreBundle\Tests\EventListener; use FOS\UserBundle\Model\FosUserInterface; -use FOS\UserBundle\Model\UserInterface; use LoginCidadao\CoreBundle\EventListener\RequestListener; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; @@ -127,7 +126,10 @@ private function getEvent(bool $isMasterRequest, Request $request = null) $event = $this->createMock(GetResponseEvent::class); $event->expects($this->once())->method('isMasterRequest')->willReturn($isMasterRequest); - if (null !== $request) { + if (null === $request) { + $request = new Request(); + $event->expects($this->any())->method('getRequest')->willReturn($request); + } else { $event->expects($this->once())->method('getRequest')->willReturn($request); } From 2b89b28d194b0b242bd771a6bcb9975e00160bd8 Mon Sep 17 00:00:00 2001 From: Guilherme Donato Date: Mon, 28 Jan 2019 16:18:49 -0200 Subject: [PATCH 20/40] removed unused import PROCERGS#830 --- .../Controller/AdminBlocklistController.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/LoginCidadao/PhoneVerificationBundle/Controller/AdminBlocklistController.php b/src/LoginCidadao/PhoneVerificationBundle/Controller/AdminBlocklistController.php index 66bbcd5c7..fcebbfd15 100644 --- a/src/LoginCidadao/PhoneVerificationBundle/Controller/AdminBlocklistController.php +++ b/src/LoginCidadao/PhoneVerificationBundle/Controller/AdminBlocklistController.php @@ -16,7 +16,6 @@ use LoginCidadao\PhoneVerificationBundle\Form\BlockPhoneFormType; use LoginCidadao\PhoneVerificationBundle\Form\SearchPhoneNumberType; use LoginCidadao\PhoneVerificationBundle\Model\BlockPhoneNumberRequest; -use LoginCidadao\PhoneVerificationBundle\Service\Blocklist; use LoginCidadao\PhoneVerificationBundle\Service\BlocklistInterface; use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Symfony\Component\HttpFoundation\Request; From bd6d2426ba6369980f28b372250d80ddee8e898e Mon Sep 17 00:00:00 2001 From: Guilherme Donato Date: Mon, 28 Jan 2019 16:34:17 -0200 Subject: [PATCH 21/40] added date formating PROCERGS#830 --- .../Resources/translations/messages.en.yml | 1 + .../Resources/translations/messages.pt_BR.yml | 1 + .../Resources/views/AdminBlocklist/grid.html.twig | 2 +- 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/LoginCidadao/PhoneVerificationBundle/Resources/translations/messages.en.yml b/src/LoginCidadao/PhoneVerificationBundle/Resources/translations/messages.en.yml index 588772512..8b067d8af 100644 --- a/src/LoginCidadao/PhoneVerificationBundle/Resources/translations/messages.en.yml +++ b/src/LoginCidadao/PhoneVerificationBundle/Resources/translations/messages.en.yml @@ -51,6 +51,7 @@ admin: phone_number: Phone number blocked_by: Banned by date_blocked: Date banned + date_blocked_format: m/d/Y H:i:s new: title: Ban users by phone form: diff --git a/src/LoginCidadao/PhoneVerificationBundle/Resources/translations/messages.pt_BR.yml b/src/LoginCidadao/PhoneVerificationBundle/Resources/translations/messages.pt_BR.yml index d8f34abbd..a64f640e7 100644 --- a/src/LoginCidadao/PhoneVerificationBundle/Resources/translations/messages.pt_BR.yml +++ b/src/LoginCidadao/PhoneVerificationBundle/Resources/translations/messages.pt_BR.yml @@ -51,6 +51,7 @@ admin: phone_number: Telefone blocked_by: Banido por date_blocked: Banido em + date_blocked_format: d/m/Y H:i:s new: title: Banir usuários por telefone form: diff --git a/src/LoginCidadao/PhoneVerificationBundle/Resources/views/AdminBlocklist/grid.html.twig b/src/LoginCidadao/PhoneVerificationBundle/Resources/views/AdminBlocklist/grid.html.twig index ffe4409ed..5bd23d120 100644 --- a/src/LoginCidadao/PhoneVerificationBundle/Resources/views/AdminBlocklist/grid.html.twig +++ b/src/LoginCidadao/PhoneVerificationBundle/Resources/views/AdminBlocklist/grid.html.twig @@ -39,7 +39,7 @@
  • {{ 'admin.blocklist.list.table.header.date_blocked' | trans }}
    -
    {{ row.createdAt | date }}
    +
    {{ row.createdAt | date('admin.blocklist.list.table.date_blocked_format' | trans) }}
    {% endblock grid_row_data %} From 92d36bed4e812ec56f916385cf7b719fb4bc80b6 Mon Sep 17 00:00:00 2001 From: Guilherme Donato Date: Mon, 28 Jan 2019 17:53:57 -0200 Subject: [PATCH 22/40] added details page PROCERGS#830 --- .../CoreBundle/Entity/PersonRepository.php | 8 +++ .../Controller/AdminBlocklistController.php | 63 +++++++++++++++++++ .../Resources/translations/messages.en.yml | 14 +++++ .../Resources/translations/messages.pt_BR.yml | 14 +++++ .../blockedUsers.grid.html.twig | 58 +++++++++++++++++ .../views/AdminBlocklist/details.html.twig | 34 ++++++++++ .../views/AdminBlocklist/grid.html.twig | 9 +-- .../Service/Blocklist.php | 14 +++++ .../Service/BlocklistInterface.php | 6 ++ .../Tests/Service/BlocklistTest.php | 17 +++++ 10 files changed, 233 insertions(+), 4 deletions(-) create mode 100644 src/LoginCidadao/PhoneVerificationBundle/Resources/views/AdminBlocklist/blockedUsers.grid.html.twig create mode 100644 src/LoginCidadao/PhoneVerificationBundle/Resources/views/AdminBlocklist/details.html.twig diff --git a/src/LoginCidadao/CoreBundle/Entity/PersonRepository.php b/src/LoginCidadao/CoreBundle/Entity/PersonRepository.php index 037280a80..13df1cf15 100644 --- a/src/LoginCidadao/CoreBundle/Entity/PersonRepository.php +++ b/src/LoginCidadao/CoreBundle/Entity/PersonRepository.php @@ -18,6 +18,7 @@ use libphonenumber\PhoneNumber; use libphonenumber\PhoneNumberFormat; use libphonenumber\PhoneNumberUtil; +use Misd\PhoneNumberBundle\Doctrine\DBAL\Types\PhoneNumberType; class PersonRepository extends EntityRepository { @@ -213,6 +214,13 @@ public function getNameSearchQuery($name) ->setParameter('name', "%{$sanitized}%"); } + public function getPhoneSearchQuery(PhoneNumber $phoneNumber) + { + return $this->getBaseSearchQuery() + ->where('p.mobile = :mobile') + ->setParameter('mobile', $phoneNumber, PhoneNumberType::NAME); + } + /** * This will return the appropriate query for the input given. * @param $query diff --git a/src/LoginCidadao/PhoneVerificationBundle/Controller/AdminBlocklistController.php b/src/LoginCidadao/PhoneVerificationBundle/Controller/AdminBlocklistController.php index fcebbfd15..04092d965 100644 --- a/src/LoginCidadao/PhoneVerificationBundle/Controller/AdminBlocklistController.php +++ b/src/LoginCidadao/PhoneVerificationBundle/Controller/AdminBlocklistController.php @@ -10,16 +10,24 @@ namespace LoginCidadao\PhoneVerificationBundle\Controller; +use libphonenumber\NumberParseException; +use libphonenumber\PhoneNumber; +use libphonenumber\PhoneNumberType; +use libphonenumber\PhoneNumberUtil; +use LoginCidadao\CoreBundle\Entity\PersonRepository; use LoginCidadao\CoreBundle\Helper\GridHelper; use LoginCidadao\PhoneVerificationBundle\Entity\BlockedPhoneNumber; use LoginCidadao\PhoneVerificationBundle\Entity\BlockedPhoneNumberRepository; use LoginCidadao\PhoneVerificationBundle\Form\BlockPhoneFormType; use LoginCidadao\PhoneVerificationBundle\Form\SearchPhoneNumberType; +use LoginCidadao\PhoneVerificationBundle\Model\BlockedPhoneNumberInterface; use LoginCidadao\PhoneVerificationBundle\Model\BlockPhoneNumberRequest; use LoginCidadao\PhoneVerificationBundle\Service\BlocklistInterface; use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Session\Session; +use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use Symfony\Component\Routing\Annotation\Route; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template; @@ -104,6 +112,21 @@ public function newAction(Request $request) ]; } + /** + * @Route("/admin/phones/blocklist/{phone}", name="phone_blocklist_details", requirements={"phone": "[0-9+]+"}) + * @Template() + */ + public function detailsAction(Request $request, string $phone) + { + $phoneNumber = $this->parsePhone($phone); + $blockedPhone = $this->getOr404($phoneNumber); + + return [ + 'blockedPhone' => $blockedPhone, + 'grid' => $this->getUsersByPhoneGrid($request, $phoneNumber), + ]; + } + /** * @return BlocklistInterface */ @@ -114,4 +137,44 @@ private function getBlocklistService(): BlocklistInterface return $blocklistService; } + + private function parsePhone($phone): PhoneNumber + { + $phoneUtils = PhoneNumberUtil::getInstance(); + try { + return $phoneUtils->parse($phone); + } catch (NumberParseException $e) { + throw new BadRequestHttpException("Invalid phone number"); + } + } + + private function getOr404(PhoneNumber $phoneNumber): BlockedPhoneNumberInterface + { + $blocklistService = $this->getBlocklistService(); + $blockedPhone = $blocklistService->getBlockedPhoneNumberByPhone($phoneNumber); + + if (null === $blockedPhone) { + throw new NotFoundHttpException("Blocked Phone Number not found"); + } + + return $blockedPhone; + } + + private function getUsersByPhoneGrid(Request $request, PhoneNumber $phoneNumber) + { + $grid = new GridHelper(); + $grid->setId('person-grid'); + $grid->setPerPage(5); + $grid->setMaxResult(5); + $grid->setInfiniteGrid(true); + $grid->setRoute('phone_blocklist_details'); + $grid->setRouteParams(['phone']); + + /** @var PersonRepository $repo */ + $repo = $this->getDoctrine()->getRepository('LoginCidadaoCoreBundle:Person'); + $query = $repo->getPhoneSearchQuery($phoneNumber); + $grid->setQueryBuilder($query); + + return $grid->createView($request); + } } diff --git a/src/LoginCidadao/PhoneVerificationBundle/Resources/translations/messages.en.yml b/src/LoginCidadao/PhoneVerificationBundle/Resources/translations/messages.en.yml index 8b067d8af..04a3f1602 100644 --- a/src/LoginCidadao/PhoneVerificationBundle/Resources/translations/messages.en.yml +++ b/src/LoginCidadao/PhoneVerificationBundle/Resources/translations/messages.en.yml @@ -51,6 +51,8 @@ admin: phone_number: Phone number blocked_by: Banned by date_blocked: Date banned + load_more: Load more + details: Details date_blocked_format: m/d/Y H:i:s new: title: Ban users by phone @@ -59,3 +61,15 @@ admin: label: Phone number that will be banned placeholder: Type the phone number that will be banned submit.label: Ban Phone Number + details: + title: "Banned Phone Details: %phone%" + blocked_by: Banned by: + date_blocked: Date banned: + date_blocked_format: m/d/Y H:i:s + users_table: + header: + full_name: Full Name + cpf: "Tax Payer's ID (CPF)" + email: Email + user_details: Details + load_more: Load more diff --git a/src/LoginCidadao/PhoneVerificationBundle/Resources/translations/messages.pt_BR.yml b/src/LoginCidadao/PhoneVerificationBundle/Resources/translations/messages.pt_BR.yml index a64f640e7..e50356750 100644 --- a/src/LoginCidadao/PhoneVerificationBundle/Resources/translations/messages.pt_BR.yml +++ b/src/LoginCidadao/PhoneVerificationBundle/Resources/translations/messages.pt_BR.yml @@ -51,6 +51,8 @@ admin: phone_number: Telefone blocked_by: Banido por date_blocked: Banido em + load_more: Carregar mais + details: Detalhes date_blocked_format: d/m/Y H:i:s new: title: Banir usuários por telefone @@ -59,3 +61,15 @@ admin: label: Telefone que será banido placeholder: Digite o telefone a ser banido submit.label: Banir Telefone + details: + title: "Detalhes de Telefone Banido: %phone%" + blocked_by: Banido por: + date_blocked: Banido em: + date_blocked_format: d/m/Y H:i:s + users_table: + header: + full_name: Nome Completo + cpf: CPF + email: Email + user_details: Detalhes + load_more: Carregar mais diff --git a/src/LoginCidadao/PhoneVerificationBundle/Resources/views/AdminBlocklist/blockedUsers.grid.html.twig b/src/LoginCidadao/PhoneVerificationBundle/Resources/views/AdminBlocklist/blockedUsers.grid.html.twig new file mode 100644 index 000000000..90f820904 --- /dev/null +++ b/src/LoginCidadao/PhoneVerificationBundle/Resources/views/AdminBlocklist/blockedUsers.grid.html.twig @@ -0,0 +1,58 @@ +{% extends "LoginCidadaoCoreBundle::grid_layout.html.twig" %} + +{% block grid_header_data %} +
    {{ 'admin.blocklist.details.users_table.header.full_name' | trans }}
    +
    {{ 'admin.blocklist.details.users_table.header.cpf' | trans }}
    +
    {{ 'admin.blocklist.details.users_table.header.email' | trans }}
    +{% endblock grid_header_data %} + +{% block grid_row_action %} + {% set formattedPhone = row.phoneNumber | phone_number_format('E164') %} + +{% endblock grid_row_action %} + +{% block grid_row_data %} + {% if row.phoneNumber.countryCode == 55 %} + {% set format = 'NATIONAL' %} + {% else %} + {% set format = 'INTERNATIONAL' %} + {% endif %} + +
    +
    {{ 'admin.blocklist.details.users_table.header.full_name' | trans }}
    +
    {{ row.fullName }}
    +
    +
    +
    {{ 'admin.blocklist.details.users_table.header.cpf' | trans }}
    +
    {{ row.cpf }}
    +
    +
    +
    {{ 'admin.blocklist.details.users_table.header.email' | trans }}
    +
    {{ row.email }}
    +
    +{% endblock grid_row_data %} + +{% block grid_infinite_pagination %} +
    + {% if not grid.getRlast and grid.isInfiniteGrid %} + {% set routeParams = { 'page': grid.page + 1 } %} +
    + +
    + {% endif %} +
    +{% endblock grid_infinite_pagination %} diff --git a/src/LoginCidadao/PhoneVerificationBundle/Resources/views/AdminBlocklist/details.html.twig b/src/LoginCidadao/PhoneVerificationBundle/Resources/views/AdminBlocklist/details.html.twig new file mode 100644 index 000000000..5eb13123b --- /dev/null +++ b/src/LoginCidadao/PhoneVerificationBundle/Resources/views/AdminBlocklist/details.html.twig @@ -0,0 +1,34 @@ +{% extends "LoginCidadaoCoreBundle:Admin:base.html.twig" %} + +{% block content %} + {% if blockedPhone.phoneNumber.countryCode == 55 %} + {% set format = 'NATIONAL' %} + {% else %} + {% set format = 'INTERNATIONAL' %} + {% endif %} + {% set formattedPhone = blockedPhone.phoneNumber | phone_number_format(format) %} +
    +
    +
    +

    {{ 'admin.blocklist.details.title' | trans({"%phone%": formattedPhone }) }}

    +
    +
    +
    +
    +
    +
    {{ 'admin.blocklist.details.blocked_by' | trans }}
    +
    {{ blockedPhone.blockedBy.fullName }}
    + +
    {{ 'admin.blocklist.details.date_blocked' | trans }}
    +
    {{ blockedPhone.createdAt | date('admin.blocklist.details.date_blocked_format' | trans) }}
    +
    +
    +
    +
    +
    + + {% if grid %} + {{ include("LoginCidadaoPhoneVerificationBundle:AdminBlocklist:blockedUsers.grid.html.twig") }} + {% endif %} +
    +{% endblock %} diff --git a/src/LoginCidadao/PhoneVerificationBundle/Resources/views/AdminBlocklist/grid.html.twig b/src/LoginCidadao/PhoneVerificationBundle/Resources/views/AdminBlocklist/grid.html.twig index 5bd23d120..b8d153fa2 100644 --- a/src/LoginCidadao/PhoneVerificationBundle/Resources/views/AdminBlocklist/grid.html.twig +++ b/src/LoginCidadao/PhoneVerificationBundle/Resources/views/AdminBlocklist/grid.html.twig @@ -7,14 +7,15 @@ {% endblock grid_header_data %} {% block grid_row_action %} + {% set formattedPhone = row.phoneNumber | phone_number_format('E164') %}
    diff --git a/src/LoginCidadao/PhoneVerificationBundle/Service/Blocklist.php b/src/LoginCidadao/PhoneVerificationBundle/Service/Blocklist.php index fe23967b4..9eea57a8a 100644 --- a/src/LoginCidadao/PhoneVerificationBundle/Service/Blocklist.php +++ b/src/LoginCidadao/PhoneVerificationBundle/Service/Blocklist.php @@ -94,6 +94,9 @@ public function checkPhoneNumber(PhoneNumber $phoneNumber): array return $blocked; } + /** + * @inheritDoc + */ public function addBlockedPhoneNumber( PhoneNumber $phoneNumber, PersonInterface $blockedBy @@ -105,6 +108,17 @@ public function addBlockedPhoneNumber( return $blockedPhoneNumber; } + /** + * @inheritDoc + */ + public function getBlockedPhoneNumberByPhone(PhoneNumber $phoneNumber): ?BlockedPhoneNumberInterface + { + /** @var BlockedPhoneNumberInterface $blockedPhoneNumber */ + $blockedPhoneNumber = $this->blockedPhoneRepository->findByPhone($phoneNumber); + + return $blockedPhoneNumber; + } + /** * @param PersonInterface[] $blockedUsers */ diff --git a/src/LoginCidadao/PhoneVerificationBundle/Service/BlocklistInterface.php b/src/LoginCidadao/PhoneVerificationBundle/Service/BlocklistInterface.php index fac7b831f..f81c6a413 100644 --- a/src/LoginCidadao/PhoneVerificationBundle/Service/BlocklistInterface.php +++ b/src/LoginCidadao/PhoneVerificationBundle/Service/BlocklistInterface.php @@ -41,4 +41,10 @@ public function addBlockedPhoneNumber( PhoneNumber $phoneNumber, PersonInterface $blockedBy ): BlockedPhoneNumberInterface; + + /** + * @param PhoneNumber $phoneNumber + * @return BlockedPhoneNumberInterface|null + */ + public function getBlockedPhoneNumberByPhone(PhoneNumber $phoneNumber): ?BlockedPhoneNumberInterface; } diff --git a/src/LoginCidadao/PhoneVerificationBundle/Tests/Service/BlocklistTest.php b/src/LoginCidadao/PhoneVerificationBundle/Tests/Service/BlocklistTest.php index 29e938c80..b5051d4e0 100644 --- a/src/LoginCidadao/PhoneVerificationBundle/Tests/Service/BlocklistTest.php +++ b/src/LoginCidadao/PhoneVerificationBundle/Tests/Service/BlocklistTest.php @@ -168,6 +168,23 @@ public function testCheckPhoneNumber() $service->checkPhoneNumber($phoneNumber); } + public function testGetBlockedPhoneNumberByPhone() + { + /** @var PhoneNumber|MockObject $phoneNumber */ + $phoneNumber = $this->createMock(PhoneNumber::class); + $blockedPhone = $this->createMock(BlockedPhoneNumberInterface::class); + + $blockedPhoneRepository = $this->createMock(BlockedPhoneNumberRepository::class); + $blockedPhoneRepository->expects($this->once())->method('findByPhone')->willReturn($blockedPhone); + + $em = $this->getEntityManager($blockedPhoneRepository); + $options = new BlocklistOptions(0); + + $service = new Blocklist($this->getUserManager(), $this->getMailer(), $em, + $this->getPhoneVerificationService(), $options); + $this->assertSame($blockedPhone, $service->getBlockedPhoneNumberByPhone($phoneNumber)); + } + /** * @return MockObject|UserManager */ From 584694e862e9c643ae4a0702284f56695d364371 Mon Sep 17 00:00:00 2001 From: Guilherme Donato Date: Mon, 28 Jan 2019 17:57:46 -0200 Subject: [PATCH 23/40] fixed privacy PROCERGS#830 --- .../Resources/views/Admin/Person/grid.html.twig | 12 +++++++++++- .../views/AdminBlocklist/blockedUsers.grid.html.twig | 12 +++++++++++- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/src/LoginCidadao/CoreBundle/Resources/views/Admin/Person/grid.html.twig b/src/LoginCidadao/CoreBundle/Resources/views/Admin/Person/grid.html.twig index d293a07d6..b926e7788 100644 --- a/src/LoginCidadao/CoreBundle/Resources/views/Admin/Person/grid.html.twig +++ b/src/LoginCidadao/CoreBundle/Resources/views/Admin/Person/grid.html.twig @@ -46,7 +46,17 @@
    {{ 'cpf'|trans }}
    -
    {{ row.cpf }}
    +
    + {% if row.cpf is not null %} + {% if is_granted('ROLE_VIEW_USERS_CPF') %} + {{ row.cpf }} + {% else %} + {{ 'lc.admin.person.metadata.hasCpf.yes' | trans }} + {% endif %} + {% else %} + {{ 'lc.admin.person.metadata.hasCpf.no' | trans }} + {% endif %} +
    diff --git a/src/LoginCidadao/PhoneVerificationBundle/Resources/views/AdminBlocklist/blockedUsers.grid.html.twig b/src/LoginCidadao/PhoneVerificationBundle/Resources/views/AdminBlocklist/blockedUsers.grid.html.twig index 90f820904..27d65e3e1 100644 --- a/src/LoginCidadao/PhoneVerificationBundle/Resources/views/AdminBlocklist/blockedUsers.grid.html.twig +++ b/src/LoginCidadao/PhoneVerificationBundle/Resources/views/AdminBlocklist/blockedUsers.grid.html.twig @@ -36,7 +36,17 @@
    {{ 'admin.blocklist.details.users_table.header.cpf' | trans }}
    -
    {{ row.cpf }}
    +
    + {% if row.cpf is not null %} + {% if is_granted('ROLE_VIEW_USERS_CPF') %} + {{ row.cpf }} + {% else %} + {{ 'lc.admin.person.metadata.hasCpf.yes' | trans }} + {% endif %} + {% else %} + {{ 'lc.admin.person.metadata.hasCpf.no' | trans }} + {% endif %} +
    {{ 'admin.blocklist.details.users_table.header.email' | trans }}
    From 5f59bba8959bbfc051d9127c3d18e7db39173e03 Mon Sep 17 00:00:00 2001 From: Guilherme Donato Date: Wed, 30 Jan 2019 09:52:08 -0200 Subject: [PATCH 24/40] added button to manually ban PROCERGS#830 --- .../APIBundle/Controller/PersonController.php | 4 +--- .../Resources/translations/messages.en.yml | 3 ++- .../Resources/translations/messages.pt_BR.yml | 3 ++- .../Resources/views/AdminBlocklist/list.html.twig | 11 ++++++++++- 4 files changed, 15 insertions(+), 6 deletions(-) diff --git a/src/LoginCidadao/APIBundle/Controller/PersonController.php b/src/LoginCidadao/APIBundle/Controller/PersonController.php index 1fd4e99eb..c5246ac5c 100644 --- a/src/LoginCidadao/APIBundle/Controller/PersonController.php +++ b/src/LoginCidadao/APIBundle/Controller/PersonController.php @@ -145,7 +145,7 @@ public function getLogoutKeyAction($id) $em->persist($logoutKey); $em->flush(); - $result = [ + return [ 'key' => $logoutKey->getKey(), 'url' => $this->generateUrl( 'lc_logout_not_remembered_safe', @@ -153,7 +153,5 @@ public function getLogoutKeyAction($id) UrlGeneratorInterface::ABSOLUTE_URL ), ]; - - return $result; } } diff --git a/src/LoginCidadao/PhoneVerificationBundle/Resources/translations/messages.en.yml b/src/LoginCidadao/PhoneVerificationBundle/Resources/translations/messages.en.yml index 04a3f1602..a139edcb6 100644 --- a/src/LoginCidadao/PhoneVerificationBundle/Resources/translations/messages.en.yml +++ b/src/LoginCidadao/PhoneVerificationBundle/Resources/translations/messages.en.yml @@ -40,7 +40,8 @@ admin: blocklist: menu_item: Ban Phone Numbers list: - title: Search Banned Phone Numbers + title: Search Manually Banned Phone Numbers + new: Ban a Phone Number form: phone: label: Phone to be searched diff --git a/src/LoginCidadao/PhoneVerificationBundle/Resources/translations/messages.pt_BR.yml b/src/LoginCidadao/PhoneVerificationBundle/Resources/translations/messages.pt_BR.yml index e50356750..90c8eccbf 100644 --- a/src/LoginCidadao/PhoneVerificationBundle/Resources/translations/messages.pt_BR.yml +++ b/src/LoginCidadao/PhoneVerificationBundle/Resources/translations/messages.pt_BR.yml @@ -40,7 +40,8 @@ admin: blocklist: menu_item: Bloqueio de Telefones list: - title: Busca de Telefones Banidos + title: Busca de Telefones Banidos Manualmente + new: Banir um Telefone form: phone: label: Telefone a ser pesquisado diff --git a/src/LoginCidadao/PhoneVerificationBundle/Resources/views/AdminBlocklist/list.html.twig b/src/LoginCidadao/PhoneVerificationBundle/Resources/views/AdminBlocklist/list.html.twig index e937b1536..1c9f31149 100644 --- a/src/LoginCidadao/PhoneVerificationBundle/Resources/views/AdminBlocklist/list.html.twig +++ b/src/LoginCidadao/PhoneVerificationBundle/Resources/views/AdminBlocklist/list.html.twig @@ -3,7 +3,16 @@
    -

    {{ 'admin.blocklist.list.title'|trans }}

    +
    +
    +

    {{ 'admin.blocklist.list.title'|trans }}

    +
    + +
    From b5349a7946efd4fa45f60b5d883eb12db648eaf8 Mon Sep 17 00:00:00 2001 From: Guilherme Donato Date: Wed, 30 Jan 2019 10:45:08 -0200 Subject: [PATCH 25/40] persisting User before blocking PROCERGS#830 --- src/LoginCidadao/PhoneVerificationBundle/Service/Blocklist.php | 1 + .../PhoneVerificationBundle/Tests/Service/BlocklistTest.php | 1 + 2 files changed, 2 insertions(+) diff --git a/src/LoginCidadao/PhoneVerificationBundle/Service/Blocklist.php b/src/LoginCidadao/PhoneVerificationBundle/Service/Blocklist.php index 9eea57a8a..9da898f76 100644 --- a/src/LoginCidadao/PhoneVerificationBundle/Service/Blocklist.php +++ b/src/LoginCidadao/PhoneVerificationBundle/Service/Blocklist.php @@ -75,6 +75,7 @@ public function isPhoneBlocked(PhoneNumber $phoneNumber): bool */ public function blockByPhone(PhoneNumber $phoneNumber): array { + $this->em->flush(); $blockedUsers = $this->userManager->blockUsersByPhone($phoneNumber, UserManager::FLUSH_STRATEGY_ONCE); $this->notifyBlockedUsers($blockedUsers); diff --git a/src/LoginCidadao/PhoneVerificationBundle/Tests/Service/BlocklistTest.php b/src/LoginCidadao/PhoneVerificationBundle/Tests/Service/BlocklistTest.php index b5051d4e0..0aeebe875 100644 --- a/src/LoginCidadao/PhoneVerificationBundle/Tests/Service/BlocklistTest.php +++ b/src/LoginCidadao/PhoneVerificationBundle/Tests/Service/BlocklistTest.php @@ -107,6 +107,7 @@ public function testBlockByPhone() $blockedPhoneRepository = $this->createMock(BlockedPhoneNumberRepository::class); $em = $this->getEntityManager($blockedPhoneRepository); + $em->expects($this->once())->method('flush'); $options = new BlocklistOptions(0); From f99eb3f87f792e7ad2e95cc09760a5cb3b1028d6 Mon Sep 17 00:00:00 2001 From: Guilherme Donato Date: Wed, 30 Jan 2019 10:59:58 -0200 Subject: [PATCH 26/40] promoted phone blocking to beta PROCERGS#830 --- app/config/security.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/config/security.yml b/app/config/security.yml index 1a1bef9e8..51c7f7a0e 100644 --- a/app/config/security.yml +++ b/app/config/security.yml @@ -59,8 +59,8 @@ security: ROLE_PERSON_BLOCK: ROLE_EDIT_CLIENT_SUBJECT_TYPE: ROLE_EDIT_BLOCKED_PHONES: - FEATURE_ALPHA: FEATURE_BETA, FEATURE_SHOW_PROFILE_VIEWS, FEATURE_ORGANIZATIONS, FEATURE_PHONE_BLOCKLIST - FEATURE_BETA: FEATURE_PROD, FEATURE_REMOTE_CLAIMS + FEATURE_ALPHA: FEATURE_BETA, FEATURE_SHOW_PROFILE_VIEWS, FEATURE_ORGANIZATIONS + FEATURE_BETA: FEATURE_PROD, FEATURE_REMOTE_CLAIMS, FEATURE_PHONE_BLOCKLIST FEATURE_PROD: FEATURE_2FACTOR_AUTH, FEATURE_INVALIDATE_SESSIONS FEATURE_IGP_VALIDATION: FEATURE_ORGANIZATIONS: From feac425e214f11b778a79b79bb34f3f48141c018 Mon Sep 17 00:00:00 2001 From: Guilherme Donato Date: Wed, 30 Jan 2019 14:17:37 -0200 Subject: [PATCH 27/40] promoted phone blocklist to production feature-set PROCERGS#830 --- app/config/security.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/config/security.yml b/app/config/security.yml index 71e8ca930..e80a7b87a 100644 --- a/app/config/security.yml +++ b/app/config/security.yml @@ -64,8 +64,8 @@ security: ROLE_EDIT_CLIENT_SUBJECT_TYPE: ROLE_EDIT_BLOCKED_PHONES: FEATURE_ALPHA: FEATURE_BETA, FEATURE_SHOW_PROFILE_VIEWS - FEATURE_BETA: FEATURE_PROD, FEATURE_ORGANIZATIONS, FEATURE_PHONE_BLOCKLIST - FEATURE_PROD: FEATURE_2FACTOR_AUTH, FEATURE_INVALIDATE_SESSIONS, FEATURE_REMOTE_CLAIMS + FEATURE_BETA: FEATURE_PROD, FEATURE_ORGANIZATIONS + FEATURE_PROD: FEATURE_2FACTOR_AUTH, FEATURE_INVALIDATE_SESSIONS, FEATURE_REMOTE_CLAIMS, FEATURE_PHONE_BLOCKLIST FEATURE_IGP_VALIDATION: FEATURE_ORGANIZATIONS: FEATURE_IMPERSONATION_REPORTS: From 7155bc667178463cdd658f549db53df70fa9e1b3 Mon Sep 17 00:00:00 2001 From: Guilherme Donato Date: Tue, 5 Feb 2019 17:40:14 -0200 Subject: [PATCH 28/40] created support interface PROCERGS#699 --- app/AppKernel.php | 1 + app/config/config.yml | 1 + app/config/routing.yml | 5 + app/config/security.yml | 14 + .../Controller/Admin/PersonController.php | 8 + .../Form/Type/PersonFilterFormType.php | 8 + .../CoreBundle/Resources/config/services.yml | 6 + .../Controller/PersonSupportController.php | 100 +++++++ .../Form/PersonSearchFormType.php | 38 +++ .../LoginCidadaoSupportBundle.php | 9 + .../Model/PersonSearchRequest.php | 51 ++++ .../SupportBundle/Model/PersonalData.php | 145 ++++++++++ .../SupportBundle/Model/SupportPerson.php | 247 ++++++++++++++++++ .../Resources/config/services.yml | 7 + .../Resources/public/js/sha512.js | 38 +++ .../Resources/translations/messages.en.yml | 0 .../Resources/translations/messages.pt_BR.yml | 46 ++++ .../views/PersonSupport/index.html.twig | 28 ++ .../views/PersonSupport/view.html.twig | 139 ++++++++++ .../Resources/views/base.html.twig | 11 + .../views/extensible.sidebar.html.twig | 1 + .../Resources/views/sidebar.html.twig | 21 ++ .../SupportBundle/Service/SupportHandler.php | 89 +++++++ .../Tests/Model/PersonalDataTest.php | 47 ++++ 24 files changed, 1060 insertions(+) create mode 100644 src/LoginCidadao/SupportBundle/Controller/PersonSupportController.php create mode 100644 src/LoginCidadao/SupportBundle/Form/PersonSearchFormType.php create mode 100644 src/LoginCidadao/SupportBundle/LoginCidadaoSupportBundle.php create mode 100644 src/LoginCidadao/SupportBundle/Model/PersonSearchRequest.php create mode 100644 src/LoginCidadao/SupportBundle/Model/PersonalData.php create mode 100644 src/LoginCidadao/SupportBundle/Model/SupportPerson.php create mode 100644 src/LoginCidadao/SupportBundle/Resources/config/services.yml create mode 100644 src/LoginCidadao/SupportBundle/Resources/public/js/sha512.js create mode 100644 src/LoginCidadao/SupportBundle/Resources/translations/messages.en.yml create mode 100644 src/LoginCidadao/SupportBundle/Resources/translations/messages.pt_BR.yml create mode 100644 src/LoginCidadao/SupportBundle/Resources/views/PersonSupport/index.html.twig create mode 100644 src/LoginCidadao/SupportBundle/Resources/views/PersonSupport/view.html.twig create mode 100644 src/LoginCidadao/SupportBundle/Resources/views/base.html.twig create mode 100644 src/LoginCidadao/SupportBundle/Resources/views/extensible.sidebar.html.twig create mode 100644 src/LoginCidadao/SupportBundle/Resources/views/sidebar.html.twig create mode 100644 src/LoginCidadao/SupportBundle/Service/SupportHandler.php create mode 100644 src/LoginCidadao/SupportBundle/Tests/Model/PersonalDataTest.php diff --git a/app/AppKernel.php b/app/AppKernel.php index cf861e708..bfd433f87 100644 --- a/app/AppKernel.php +++ b/app/AppKernel.php @@ -67,6 +67,7 @@ public function registerBundles() new Knp\Bundle\PaginatorBundle\KnpPaginatorBundle(), new Snc\RedisBundle\SncRedisBundle(), new Http\HttplugBundle\HttplugBundle(), + new LoginCidadao\SupportBundle\LoginCidadaoSupportBundle(), ); if (in_array($this->getEnvironment(), array('dev', 'test'))) { diff --git a/app/config/config.yml b/app/config/config.yml index f597c2c55..a0e4c8390 100644 --- a/app/config/config.yml +++ b/app/config/config.yml @@ -6,6 +6,7 @@ imports: #- { resource: logstash.yml } - { resource: "@LoginCidadaoTOSBundle/Resources/config/config.yml" } - { resource: 'workflows/*.{yml,yaml}' } + - { resource: "@LoginCidadaoSupportBundle/Resources/config/services.yml" } framework: #esi: ~ diff --git a/app/config/routing.yml b/app/config/routing.yml index eee64b88b..e9a27b9b7 100644 --- a/app/config/routing.yml +++ b/app/config/routing.yml @@ -1,3 +1,8 @@ +support: + resource: "@LoginCidadaoSupportBundle/Controller/" + type: annotation + prefix: / + # Remote Claims lc_remote_claims_remote_claim: type: rest diff --git a/app/config/security.yml b/app/config/security.yml index 51c7f7a0e..01ba1d4bb 100644 --- a/app/config/security.yml +++ b/app/config/security.yml @@ -34,6 +34,7 @@ security: - ROLE_VIEW_3RD_PARTY_CONNECTIONS - ROLE_EDIT_CLIENT_SUBJECT_TYPE - ROLE_EDIT_BLOCKED_PHONES + - ROLE_SUPPORT_MANAGER ROLE_SUPER_ADMIN: - ROLE_ADMIN - ROLE_ALLOWED_TO_SWITCH @@ -70,6 +71,19 @@ security: FEATURE_REMOTE_CLAIMS: FEATURE_PHONE_BLOCKLIST: + # Support Roles + ROLE_SUPPORT_AGENT: + ROLE_SUPPORT_MANAGER: + - ROLE_SUPPORT_AGENT + - ROLE_SUPPORT_MANAGE_AGENTS + - ROLE_SUPPORT_VIEW_EMAIL + - ROLE_SUPPORT_VIEW_PHONE + - ROLE_SUPPORT_VIEW_BIRTHDAY + ROLE_SUPPORT_MANAGE_AGENTS: + ROLE_SUPPORT_VIEW_EMAIL: + ROLE_SUPPORT_VIEW_PHONE: + ROLE_SUPPORT_VIEW_BIRTHDAY: + providers: chainprovider: chain: diff --git a/src/LoginCidadao/CoreBundle/Controller/Admin/PersonController.php b/src/LoginCidadao/CoreBundle/Controller/Admin/PersonController.php index a051ac68e..8b018ddfd 100644 --- a/src/LoginCidadao/CoreBundle/Controller/Admin/PersonController.php +++ b/src/LoginCidadao/CoreBundle/Controller/Admin/PersonController.php @@ -1,4 +1,12 @@ + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ namespace LoginCidadao\CoreBundle\Controller\Admin; diff --git a/src/LoginCidadao/CoreBundle/Form/Type/PersonFilterFormType.php b/src/LoginCidadao/CoreBundle/Form/Type/PersonFilterFormType.php index fb9ae660c..ed0e01088 100644 --- a/src/LoginCidadao/CoreBundle/Form/Type/PersonFilterFormType.php +++ b/src/LoginCidadao/CoreBundle/Form/Type/PersonFilterFormType.php @@ -1,4 +1,12 @@ + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ namespace LoginCidadao\CoreBundle\Form\Type; diff --git a/src/LoginCidadao/CoreBundle/Resources/config/services.yml b/src/LoginCidadao/CoreBundle/Resources/config/services.yml index e82106eed..e1633e588 100644 --- a/src/LoginCidadao/CoreBundle/Resources/config/services.yml +++ b/src/LoginCidadao/CoreBundle/Resources/config/services.yml @@ -508,3 +508,9 @@ services: - "@?snc_redis.default" tags: - { name: liip_monitor.check, alias: redis_predis } + + lc.sent_email.repository: + class: Doctrine\ORM\EntityRepository + factory: ["@doctrine.orm.entity_manager", getRepository] + arguments: + - LoginCidadao\CoreBundle\Entity\SentEmail diff --git a/src/LoginCidadao/SupportBundle/Controller/PersonSupportController.php b/src/LoginCidadao/SupportBundle/Controller/PersonSupportController.php new file mode 100644 index 000000000..cf9e77b03 --- /dev/null +++ b/src/LoginCidadao/SupportBundle/Controller/PersonSupportController.php @@ -0,0 +1,100 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace LoginCidadao\SupportBundle\Controller; + +use Doctrine\ORM\NonUniqueResultException; +use LoginCidadao\CoreBundle\Entity\PersonRepository; +use LoginCidadao\CoreBundle\Entity\SentEmail; +use LoginCidadao\CoreBundle\Model\PersonInterface; +use LoginCidadao\SupportBundle\Form\PersonSearchFormType; +use LoginCidadao\SupportBundle\Model\PersonSearchRequest; +use LoginCidadao\SupportBundle\Service\SupportHandler; +use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; +use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security; +use Symfony\Bundle\FrameworkBundle\Controller\Controller; +use Symfony\Component\HttpFoundation\Request; + +/** + * Class PersonSupportController + * @package LoginCidadao\SupportBundle\Controller + * + * @Security("has_role('ROLE_SUPPORT_AGENT')") + */ +class PersonSupportController extends Controller +{ + /** + * @Route("/support/search", name="lc_support_person_search") + */ + public function indexAction(Request $request) + { + $search = new PersonSearchRequest(); + + $search->smartSearch = $request->get('search', null); + + $form = $this->createForm(PersonSearchFormType::class, $search); + $form->handleRequest($request); + + if ($form->isSubmitted() && $form->isValid()) { + /** @var PersonRepository $repo */ + $repo = $this->getDoctrine()->getRepository('LoginCidadaoCoreBundle:Person'); + try { + $person = $repo->getSmartSearchQuery($search->smartSearch) + ->getQuery()->getOneOrNullResult(); + + if ($person instanceof PersonInterface) { + return $this->redirectToRoute('lc_support_person_view', [ + 'id' => $person->getId(), + 'ticket' => $search->supportTicket, + ]); + } + } catch (NonUniqueResultException $e) { + // TODO: regular search + } + + dump("FORM OK"); + } + + return $this->render('LoginCidadaoSupportBundle:PersonSupport:index.html.twig', [ + 'form' => $form->createView(), + ]); + } + + /** + * @Route("/support/person/{id}", name="lc_support_person_view") + */ + public function viewAction(Request $request, $id) + { + $supportRequest = $this->validateSupportTicketId($request->get('ticket')); + + /** @var SupportHandler $supportHandler */ + $supportHandler = $this->get(SupportHandler::class); + + $person = $supportHandler->getSupportPerson($id); + + return $this->render('LoginCidadaoSupportBundle:PersonSupport:view.html.twig', [ + 'person' => $person, + 'supportRequest' => $supportRequest, + 'dataValidation' => $supportHandler->getValidationMap($person), + ]); + } + + private function validateSupportTicketId(string $ticket = null): SentEmail + { + /** @var SupportHandler $supportHandler */ + $supportHandler = $this->get(SupportHandler::class); + $sentEmail = $ticket ? $supportHandler->getInitialMessage($ticket) : null; + if (!$sentEmail instanceof SentEmail) { + throw $this->createNotFoundException("Invalid Support Ticket ID"); + } + + return $sentEmail; + } +} diff --git a/src/LoginCidadao/SupportBundle/Form/PersonSearchFormType.php b/src/LoginCidadao/SupportBundle/Form/PersonSearchFormType.php new file mode 100644 index 000000000..7b710bd0b --- /dev/null +++ b/src/LoginCidadao/SupportBundle/Form/PersonSearchFormType.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace LoginCidadao\SupportBundle\Form; + +use LoginCidadao\SupportBundle\Model\PersonSearchRequest; +use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\Extension\Core\Type\TextType; +use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\OptionsResolver\OptionsResolver; + +class PersonSearchFormType extends AbstractType +{ + public function buildForm(FormBuilderInterface $builder, array $options) + { + $builder + ->setMethod('GET') + ->add('supportTicket', TextType::class) + ->add('smartSearch', TextType::class, [ + 'label' => 'security.login.username', + ]); + } + + public function configureOptions(OptionsResolver $resolver) + { + $resolver->setDefaults([ + 'data_class' => PersonSearchRequest::class, + 'csrf_protection' => false, + ]); + } +} diff --git a/src/LoginCidadao/SupportBundle/LoginCidadaoSupportBundle.php b/src/LoginCidadao/SupportBundle/LoginCidadaoSupportBundle.php new file mode 100644 index 000000000..862bd7d36 --- /dev/null +++ b/src/LoginCidadao/SupportBundle/LoginCidadaoSupportBundle.php @@ -0,0 +1,9 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace LoginCidadao\SupportBundle\Model; + +use Symfony\Component\Validator\Constraints as Assert; +use LoginCidadao\ValidationBundle\Validator\Constraints as LCAssert; + +class PersonSearchRequest +{ + /** + * @Assert\NotBlank() + * + * @var string + */ + public $supportTicket; + + /** + * @Assert\NotBlank() + * @Assert\Type("string") + * + * @var string + */ + public $smartSearch; + + /** + * @var string + */ + public $phoneNumber; + + /** + * @Assert\Email(checkHost=false, checkMX=false, strict=false) + * + * @var string + */ + public $email; + + /** + * @LCAssert\CPF() + * + * @var string + */ + public $cpf; +} diff --git a/src/LoginCidadao/SupportBundle/Model/PersonalData.php b/src/LoginCidadao/SupportBundle/Model/PersonalData.php new file mode 100644 index 000000000..40f47c5dd --- /dev/null +++ b/src/LoginCidadao/SupportBundle/Model/PersonalData.php @@ -0,0 +1,145 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace LoginCidadao\SupportBundle\Model; + +class PersonalData +{ + const HASH_ALGO = 'sha512'; + + /** @var string */ + private $name; + + /** @var string */ + private $hash; + + /** @var string */ + private $challenge; + + /** @var string */ + private $value; + + /** @var bool */ + private $isValueFilled; + + /** + * PersonalData constructor. + * @param string $name + * @param string $hash + * @param string $value + * @param bool $isValueFilled + * @param string $challenge + */ + public function __construct( + string $name, + string $value = null, + bool $isValueFilled = null, + string $hash = null, + string $challenge = null + ) { + $this->name = $name; + $this->value = $value; + $this->isValueFilled = $isValueFilled ?? (bool)$value; + $this->challenge = self::enforceChallenge($challenge); + $this->setHash($hash); + } + + public static function createWithValue(string $name, ?string $value, string $challenge = null): PersonalData + { + if (null === $value) { + $value = ''; + } + return new self($name, $value, (bool)$value, null, $challenge); + } + + public static function createWithoutValue(string $name, ?string $value, string $challenge = null): PersonalData + { + $challenge = self::enforceChallenge($challenge); + $hash = self::generateHash(self::enforceChallenge($challenge), $value); + + return new self($name, null, (bool)$value, $hash, $challenge); + } + + public function checkValue(string $value): bool + { + $userHash = $this->generateHash($this->getChallenge(), $value); + + return hash_equals($this->getHash(), $userHash); + } + + private function setHash(string $hash = null) + { + if ($hash === null) { + if ($this->value !== null) { + $hash = $this->generateHash($this->getChallenge(), $this->getValue()); + } else { + throw new \InvalidArgumentException("Hash and Value can't both be null"); + } + } + + $this->hash = $hash; + } + + /** + * @return string + */ + public function getName(): string + { + return $this->name; + } + + /** + * @return string + */ + public function getHash(): string + { + return $this->hash; + } + + /** + * @return string + */ + public function getChallenge(): string + { + return $this->challenge; + } + + /** + * @return string + */ + public function getValue(): ?string + { + return $this->value; + } + + public function isValueFilled(): bool + { + return $this->isValueFilled; + } + + private static function generateHash(string $challenge, ?string $value): string + { + return hash_hmac(self::HASH_ALGO, $challenge, $value); + } + + private static function enforceChallenge(string $challenge = null): string + { + return $challenge ?? bin2hex(random_bytes(10)); + } + + public function __toString(): string + { + if ($this->getValue() !== null) { + return $this->getValue(); + } else { + return $this->isValueFilled ? 'Yes' : 'No'; + } + } +} diff --git a/src/LoginCidadao/SupportBundle/Model/SupportPerson.php b/src/LoginCidadao/SupportBundle/Model/SupportPerson.php new file mode 100644 index 000000000..983b15aa9 --- /dev/null +++ b/src/LoginCidadao/SupportBundle/Model/SupportPerson.php @@ -0,0 +1,247 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace LoginCidadao\SupportBundle\Model; + +use libphonenumber\PhoneNumber; +use libphonenumber\PhoneNumberFormat; +use libphonenumber\PhoneNumberUtil; +use LoginCidadao\CoreBundle\Entity\Person; +use LoginCidadao\CoreBundle\Model\PersonInterface; +use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; + +class SupportPerson +{ + /** @var mixed */ + private $id; + + /** @var string */ + private $firstName; + + /** @var string */ + private $lastName; + + /** @var PersonalData */ + private $birthday; + + /** @var PersonalData */ + private $cpf; + + /** @var PersonalData */ + private $email; + + /** @var \DateTimeInterface */ + private $emailVerifiedAt; + + /** @var PersonalData */ + private $phoneNumber; + + /** @var \DateTimeInterface */ + private $lastPasswordResetRequest; + + /** @var bool */ + private $has2FA; + + /** @var array */ + private $thirdPartyConnections = []; + + /** @var bool */ + private $isEnabled; + + /** @var \DateTimeInterface */ + private $lastUpdate; + + /** @var \DateTimeInterface */ + private $createdAt; + + /** + * SupportPerson constructor. + * @param PersonInterface|Person $person + * @param AuthorizationCheckerInterface $authorizationChecker + */ + public function __construct(PersonInterface $person, AuthorizationCheckerInterface $authorizationChecker) + { + $this->id = $person->getId(); + $this->firstName = $person->getFirstName(); + $this->lastName = $person->getSurname(); + $this->emailVerifiedAt = $person->getEmailConfirmedAt(); + $this->lastPasswordResetRequest = $person->getPasswordRequestedAt(); + $this->has2FA = $person->getGoogleAuthenticatorSecret() !== null; + $this->isEnabled = $person->isEnabled(); + $this->lastUpdate = $person->getUpdatedAt(); + $this->createdAt = $person->getCreatedAt(); + + $this->thirdPartyConnections = [ + 'facebook' => $person->getFacebookId() !== null, + 'google' => $person->getGoogleId() !== null, + 'twitter' => $person->getTwitterId() !== null, + ]; + + if ($authorizationChecker->isGranted('ROLE_VIEW_USERS_CPF')) { + $this->cpf = PersonalData::createWithValue('cpf', $person->getCpf()); + } else { + $this->cpf = PersonalData::createWithoutValue('cpf', $person->getCpf()); + } + if ($authorizationChecker->isGranted('ROLE_SUPPORT_VIEW_EMAIL')) { + $this->email = PersonalData::createWithValue('email', $person->getEmailCanonical()); + } else { + $this->email = PersonalData::createWithoutValue('email', $person->getEmailCanonical()); + } + $this->setPhoneNumber($person, $authorizationChecker); + $this->setBirthday($person, $authorizationChecker); + } + + public function checkCpf(string $cpf) + { + return $this->cpf->checkValue($cpf); + } + + private function setPhoneNumber(PersonInterface $person, AuthorizationCheckerInterface $authorizationChecker) + { + $phoneNumber = $person->getMobile(); + if ($phoneNumber instanceof PhoneNumber) { + $phoneUtil = PhoneNumberUtil::getInstance(); + $phoneNumber = $phoneUtil->format($person->getMobile(), PhoneNumberFormat::E164); + } + if ($authorizationChecker->isGranted('ROLE_SUPPORT_VIEW_PHONE')) { + $this->phoneNumber = PersonalData::createWithValue('phoneNumber', $phoneNumber); + } else { + $this->phoneNumber = PersonalData::createWithoutValue('phoneNumber', $phoneNumber); + } + } + + private function setBirthday(PersonInterface $person, AuthorizationCheckerInterface $authorizationChecker) + { + $birthday = $person->getBirthdate(); + if ($birthday instanceof \DateTimeInterface) { + $birthday = $birthday->format('Y-m-d'); + } + + if ($authorizationChecker->isGranted('ROLE_SUPPORT_VIEW_BIRTHDAY')) { + $this->birthday = PersonalData::createWithValue('birthday', $birthday); + } else { + $this->birthday = PersonalData::createWithoutValue('birthday', $birthday); + } + } + + public function getName(): string + { + return $this->getFirstName() ?? $this->getId(); + } + + public function getId() + { + return $this->id; + } + + /** + * @return string + */ + public function getFirstName(): ?string + { + return $this->firstName; + } + + /** + * @return string + */ + public function getLastName(): ?string + { + return $this->lastName; + } + + /** + * @return PersonalData + */ + public function getBirthday(): PersonalData + { + return $this->birthday; + } + + /** + * @return PersonalData + */ + public function getCpf(): PersonalData + { + return $this->cpf; + } + + /** + * @return PersonalData + */ + public function getEmail(): PersonalData + { + return $this->email; + } + + /** + * @return \DateTimeInterface + */ + public function getEmailVerifiedAt(): ?\DateTimeInterface + { + return $this->emailVerifiedAt; + } + + /** + * @return PersonalData + */ + public function getPhoneNumber(): PersonalData + { + return $this->phoneNumber; + } + + /** + * @return \DateTimeInterface + */ + public function getLastPasswordResetRequest(): ?\DateTimeInterface + { + return $this->lastPasswordResetRequest; + } + + /** + * @return bool + */ + public function isHas2FA(): bool + { + return $this->has2FA; + } + + /** + * @return array + */ + public function getThirdPartyConnections(): array + { + return $this->thirdPartyConnections; + } + + /** + * @return bool + */ + public function isEnabled(): bool + { + return $this->isEnabled; + } + + /** + * @return \DateTimeInterface + */ + public function getLastUpdate(): \DateTimeInterface + { + return $this->lastUpdate; + } + + /** + * @return \DateTimeInterface + */ + public function getCreatedAt(): \DateTimeInterface + { + return $this->createdAt; + } +} diff --git a/src/LoginCidadao/SupportBundle/Resources/config/services.yml b/src/LoginCidadao/SupportBundle/Resources/config/services.yml new file mode 100644 index 000000000..d31eb5313 --- /dev/null +++ b/src/LoginCidadao/SupportBundle/Resources/config/services.yml @@ -0,0 +1,7 @@ +services: + LoginCidadao\SupportBundle\Service\SupportHandler: + class: LoginCidadao\SupportBundle\Service\SupportHandler + arguments: + - "@security.authorization_checker" + - "@lc.person.repository" + - "@lc.sent_email.repository" diff --git a/src/LoginCidadao/SupportBundle/Resources/public/js/sha512.js b/src/LoginCidadao/SupportBundle/Resources/public/js/sha512.js new file mode 100644 index 000000000..1c72e94a3 --- /dev/null +++ b/src/LoginCidadao/SupportBundle/Resources/public/js/sha512.js @@ -0,0 +1,38 @@ +/* + Downloaded from: https://cdnjs.cloudflare.com/ajax/libs/jsSHA/2.3.1/sha512.js + + A JavaScript implementation of the SHA family of hashes, as + defined in FIPS PUB 180-4 and FIPS PUB 202, as well as the corresponding + HMAC implementation as defined in FIPS PUB 198a + + Copyright Brian Turek 2008-2017 + Distributed under the BSD License + See http://caligatio.github.com/jsSHA/ for more information + + Several functions taken from Paul Johnston +*/ +'use strict';(function(K){function x(b,a,d){var e=0,g=[],k=0,f,c,m,h,l,p,t,q,y=!1,u=[],r=[],v,A=!1;d=d||{};f=d.encoding||"UTF8";v=d.numRounds||1;if(v!==parseInt(v,10)||1>v)throw Error("numRounds must a integer >= 1");if(0===b.lastIndexOf("SHA-",0))if(p=function(a,d){return B(a,d,b)},t=function(a,d,g,e){var c,k;if("SHA-384"===b||"SHA-512"===b)c=(d+129>>>10<<5)+31,k=32;else throw Error("Unexpected error in SHA-2 implementation");for(;a.length<=c;)a.push(0);a[d>>>5]|=128<<24-d%32;d=d+g;a[c]=d&4294967295; + a[c-1]=d/4294967296|0;g=a.length;for(d=0;d>>3;g=k/4-1;if(ka/8){for(;d.length<=g;)d.push(0);d[g]&=4294967040}for(a=0;a<=g;a+=1)u[a]=d[a]^909522486,r[a]=d[a]^1549556828;c=p(u,c);e=l;y= + !0};this.update=function(a){var d,b,n,f=0,h=l>>>5;d=m(a,g,k);a=d.binLen;b=d.value;d=a>>>5;for(n=0;n>>5);k=a%l;A=!0};this.getHash=function(a,d){var f,l,m,p;if(!0===y)throw Error("Cannot call getHash after setting HMAC key");m=D(d);switch(a){case "HEX":f=function(a){return E(a,h,m)};break;case "B64":f=function(a){return F(a,h,m)};break;case "BYTES":f=function(a){return G(a,h)};break;case "ARRAYBUFFER":try{l=new ArrayBuffer(0)}catch(w){throw Error("ARRAYBUFFER not supported by this environment"); +}f=function(a){return H(a,h)};break;default:throw Error("format must be HEX, B64, BYTES, or ARRAYBUFFER");}p=t(g.slice(),k,e,q(c));for(l=1;l>>2]>>>8*(3+g%4*-1),e+="0123456789abcdef".charAt(k>>>4&15)+"0123456789abcdef".charAt(k&15);return d.outputUpper?e.toUpperCase():e}function F(b,a,d){var e="",g=a/8,k,f,c;for(k=0;k>>2]:0,c=k+2>>2]: + 0,c=(b[k>>>2]>>>8*(3+k%4*-1)&255)<<16|(f>>>8*(3+(k+1)%4*-1)&255)<<8|c>>>8*(3+(k+2)%4*-1)&255,f=0;4>f;f+=1)8*k+6*f<=a?e+="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".charAt(c>>>6*(3-f)&63):e+=d.b64Pad;return e}function G(b,a){var d="",e=a/8,g,c;for(g=0;g>>2]>>>8*(3+g%4*-1)&255,d+=String.fromCharCode(c);return d}function H(b,a){var d=a/8,e,g=new ArrayBuffer(d),c;c=new Uint8Array(g);for(e=0;e>>2]>>>8*(3+e%4*-1)&255;return g}function D(b){var a={outputUpper:!1, + b64Pad:"=",shakeLen:-1};b=b||{};a.outputUpper=b.outputUpper||!1;!0===b.hasOwnProperty("b64Pad")&&(a.b64Pad=b.b64Pad);if("boolean"!==typeof a.outputUpper)throw Error("Invalid outputUpper formatting option");if("string"!==typeof a.b64Pad)throw Error("Invalid b64Pad formatting option");return a}function C(b,a){var d;switch(a){case "UTF8":case "UTF16BE":case "UTF16LE":break;default:throw Error("encoding must be UTF8, UTF16BE, or UTF16LE");}switch(b){case "HEX":d=function(a,b,d){var f=a.length,c,n,h,l, + p;if(0!==f%2)throw Error("String of HEX type must be in byte increments");b=b||[0];d=d||0;p=d>>>3;for(c=0;c>>1)+p;for(h=l>>>2;b.length<=h;)b.push(0);b[h]|=n<<8*(3+l%4*-1)}return{value:b,binLen:4*f+d}};break;case "TEXT":d=function(b,d,c){var f,n,m=0,h,l,p,t,q,r;d=d||[0];c=c||0;p=c>>>3;if("UTF8"===a)for(r=3,h=0;hf?n.push(f):2048>f?(n.push(192| + f>>>6),n.push(128|f&63)):55296>f||57344<=f?n.push(224|f>>>12,128|f>>>6&63,128|f&63):(h+=1,f=65536+((f&1023)<<10|b.charCodeAt(h)&1023),n.push(240|f>>>18,128|f>>>12&63,128|f>>>6&63,128|f&63)),l=0;l>>2;d.length<=t;)d.push(0);d[t]|=n[l]<<8*(r+q%4*-1);m+=1}else if("UTF16BE"===a||"UTF16LE"===a)for(r=2,n="UTF16LE"===a&&!0||"UTF16LE"!==a&&!1,h=0;h>>8);q=m+p;for(t=q>>>2;d.length<=t;)d.push(0);d[t]|=f<<8*(r+q%4*-1);m+= + 2}return{value:d,binLen:8*m+c}};break;case "B64":d=function(a,b,d){var c=0,n,m,h,l,p,t,q;if(-1===a.search(/^[a-zA-Z0-9=+\/]+$/))throw Error("Invalid character in base-64 string");m=a.indexOf("=");a=a.replace(/\=/g,"");if(-1!==m&&m +
    +
    +
    +
    + {{ form_start(form) }} + {{ form_errors(form) }} +
    +
    + {{ form_row(form.supportTicket) }} + {{ form_row(form.smartSearch) }} + +
    +
    + {{ form_end(form) }} +
    +
    +
    +
    +
    +{% endblock %} diff --git a/src/LoginCidadao/SupportBundle/Resources/views/PersonSupport/view.html.twig b/src/LoginCidadao/SupportBundle/Resources/views/PersonSupport/view.html.twig new file mode 100644 index 000000000..4a5fb829e --- /dev/null +++ b/src/LoginCidadao/SupportBundle/Resources/views/PersonSupport/view.html.twig @@ -0,0 +1,139 @@ +{% extends "LoginCidadaoSupportBundle::base.html.twig" %} +{% block content %} +
    +
    +
    +

    {{ 'support.person.title' | trans({'%first_name%': person.name}) }}

    +
    +
    +
    +
    +
    +
    {{ 'support.person.view.first_name.label' | trans }}
    +
    {{ person.firstName }}
    +
    {{ 'support.person.view.last_name.label' | trans }}
    +
    {{ person.lastName }}
    +
    {{ 'support.person.view.birthday.label' | trans }}
    +
    {{ person.birthday }}
    +
    {{ 'support.person.view.cpf.label' | trans }}
    +
    {{ person.cpf }}
    +
    {{ 'support.person.view.email.label' | trans }}
    +
    {{ person.email }}
    +
    {{ 'support.person.view.emailVerifiedAt.label' | trans }}
    +
    {{ person.emailVerifiedAt | date('support.person.view.datetime_format' | trans) }}
    +
    {{ 'support.person.view.phoneNumber.label' | trans }}
    +
    {{ person.phoneNumber }}
    +
    {{ 'support.person.view.lastPasswordResetRequest.label' | trans }}
    +
    {{ person.lastPasswordResetRequest | date('support.person.view.datetime_format' | trans) }}
    +
    {{ 'support.person.view.2FA.label' | trans }}
    +
    {{ (person.has2FA ? 'support.person.view.2FA.enabled' : 'support.person.view.2FA.disabled') | trans }}
    +
    {{ 'support.person.view.enabled.label' | trans }}
    +
    {{ (person.enabled ? 'support.person.view.enabled.enabled' : 'support.person.view.enabled.disabled') | trans }}
    +
    {{ 'support.person.view.lastUpdate.label' | trans }}
    +
    {{ person.lastUpdate | date('support.person.view.datetime_format' | trans) }}
    +
    {{ 'support.person.view.createdAt.label' | trans }}
    +
    {{ person.createdAt | date('support.person.view.datetime_format' | trans) }}
    +
    + +
    + {% for thirdParty, connected in person.thirdPartyConnections %} +
    {{ ('support.person.view.connections.' ~ thirdParty ~ '.label') | trans }}
    +
    {{ (connected ? 'support.person.view.connections.enabled' : 'support.person.view.connections.disabled') | trans }}
    + {% endfor %} +
    +
    +
    +
    +
    + + {% if dataValidation | length > 0 %} +
    +
    +

    {{ 'support.person.validation.title' | trans }}

    +
    +
    +
    +
    +
    + {% for validation in dataValidation %} +
    + +
    +
    + +
    + {% endfor %} +
    +
    +
    +
    +
    + {% endif %} + +
    +
    +

    {{ 'support.person.initialContact.title' | trans }}

    +
    +
    +
    +
    +

    {{ 'support.person.initialContact.about' | trans }}

    +
    +
    {{ 'support.person.initialContact.subject' | trans }}
    +
    {{ supportRequest.subject }}
    +
    {{ 'support.person.initialContact.date' | trans }}
    +
    {{ supportRequest.date | date }}
    +
    {{ 'support.person.initialContact.message' | trans }}
    +
    {{ supportRequest.message }}
    +
    +
    +
    +
    +
    +
    +{% endblock %} + +{% block javascripts %} + {{ parent() }} + + {% javascripts + '@LoginCidadaoSupportBundle/Resources/public/js/sha512.js' + filter='uglifyjs2' %} + + {% endjavascripts %} + +{% endblock javascripts %} diff --git a/src/LoginCidadao/SupportBundle/Resources/views/base.html.twig b/src/LoginCidadao/SupportBundle/Resources/views/base.html.twig new file mode 100644 index 000000000..13402d2d4 --- /dev/null +++ b/src/LoginCidadao/SupportBundle/Resources/views/base.html.twig @@ -0,0 +1,11 @@ +{% extends "LoginCidadaoCoreBundle::base.loggedIn.html.twig" %} + +{% block sidebar %} + {{ include("LoginCidadaoSupportBundle::extensible.sidebar.html.twig") }} +{% endblock %} + +{% block content %} +
    + EMPTY +
    +{% endblock content %} diff --git a/src/LoginCidadao/SupportBundle/Resources/views/extensible.sidebar.html.twig b/src/LoginCidadao/SupportBundle/Resources/views/extensible.sidebar.html.twig new file mode 100644 index 000000000..bca1a50a4 --- /dev/null +++ b/src/LoginCidadao/SupportBundle/Resources/views/extensible.sidebar.html.twig @@ -0,0 +1 @@ +{% extends "LoginCidadaoSupportBundle::sidebar.html.twig" %} diff --git a/src/LoginCidadao/SupportBundle/Resources/views/sidebar.html.twig b/src/LoginCidadao/SupportBundle/Resources/views/sidebar.html.twig new file mode 100644 index 000000000..afeff505a --- /dev/null +++ b/src/LoginCidadao/SupportBundle/Resources/views/sidebar.html.twig @@ -0,0 +1,21 @@ +{% set myController = app.request.attributes.get("_controller") | trim %} +
    + +
    diff --git a/src/LoginCidadao/SupportBundle/Service/SupportHandler.php b/src/LoginCidadao/SupportBundle/Service/SupportHandler.php new file mode 100644 index 000000000..a69717c71 --- /dev/null +++ b/src/LoginCidadao/SupportBundle/Service/SupportHandler.php @@ -0,0 +1,89 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace LoginCidadao\SupportBundle\Service; + +use LoginCidadao\CoreBundle\Entity\PersonRepository; +use LoginCidadao\CoreBundle\Entity\SentEmail; +use LoginCidadao\CoreBundle\Entity\SentEmailRepository; +use LoginCidadao\CoreBundle\Model\PersonInterface; +use LoginCidadao\SupportBundle\Model\PersonalData; +use LoginCidadao\SupportBundle\Model\SupportPerson; +use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; + +class SupportHandler +{ + /** @var AuthorizationCheckerInterface */ + private $authChecker; + + /** @var PersonRepository */ + private $personRepository; + + /** @var SentEmailRepository */ + private $sentEmailRepository; + + /** + * SupportHandler constructor. + * @param AuthorizationCheckerInterface $authChecker + * @param PersonRepository $personRepository + * @param SentEmailRepository $sentEmailRepository + */ + public function __construct( + AuthorizationCheckerInterface $authChecker, + PersonRepository $personRepository, + SentEmailRepository $sentEmailRepository + ) { + $this->authChecker = $authChecker; + $this->personRepository = $personRepository; + $this->sentEmailRepository = $sentEmailRepository; + } + + public function getSupportPerson($id): SupportPerson + { + $person = $this->personRepository->find($id); + if ($person instanceof PersonInterface) { + return new SupportPerson($person, $this->authChecker); + } + } + + public function getInitialMessage($id): ?SentEmail + { + /** @var SentEmail $sentEmail */ + $sentEmail = $this->sentEmailRepository->find($id); + + return $sentEmail; + } + + public function getValidationMap(SupportPerson $person): array + { + return array_filter([ + 'cpf' => $this->personalDataToValidationArray($person->getCpf(), true), + 'birthday' => $this->personalDataToValidationArray($person->getBirthday(), true), + 'email' => $this->personalDataToValidationArray($person->getEmail(), true), + 'phoneNumber' => $this->personalDataToValidationArray($person->getPhoneNumber(), true), + ]); + } + + private function personalDataToValidationArray(PersonalData $data, bool $skipIfValueSet = false): ?array + { + if (false === $data->isValueFilled()) { + return null; + } + if ($skipIfValueSet && $data->getValue() !== null) { + return null; + } + + return [ + 'name' => $data->getName(), + 'hash' => $data->getHash(), + 'challenge' => $data->getChallenge(), + ]; + } +} diff --git a/src/LoginCidadao/SupportBundle/Tests/Model/PersonalDataTest.php b/src/LoginCidadao/SupportBundle/Tests/Model/PersonalDataTest.php new file mode 100644 index 000000000..f44cc0743 --- /dev/null +++ b/src/LoginCidadao/SupportBundle/Tests/Model/PersonalDataTest.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace LoginCidadao\SupportBundle\Tests\Model; + +use LoginCidadao\SupportBundle\Model\PersonalData; +use PHPUnit\Framework\TestCase; + +class PersonalDataTest extends TestCase +{ + public function testKnownValue() + { + $data = PersonalData::createWithValue($name = 'privateInfo', $value = 'my secret value'); + + $this->assertSame($name, $data->getName()); + $this->assertSame($value, $data->getValue()); + $this->assertNotNull($data->getHash()); + $this->assertNotNull($data->getChallenge()); + $this->assertTrue($data->checkValue($value)); + } + + public function testUnknownValue() + { + $value = 'my unknown secret value'; + $data = PersonalData::createWithoutValue($name = 'privateInfo', $value); + + $this->assertSame($name, $data->getName()); + $this->assertNull($data->getValue()); + $this->assertNotNull($data->getHash()); + $this->assertNotNull($data->getChallenge()); + $this->assertTrue($data->checkValue($value)); + } + + public function testCreateInvalidObject() + { + $this->expectException(\InvalidArgumentException::class); + + new PersonalData('invalid', null, true, null); + } +} From 575dce2ebd135066f9f98505dbd3117f2cc676e9 Mon Sep 17 00:00:00 2001 From: Guilherme Donato Date: Wed, 6 Feb 2019 09:21:04 -0200 Subject: [PATCH 29/40] added partial phone search to smart query PROCERGS#699 --- .../CoreBundle/Entity/PersonRepository.php | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/LoginCidadao/CoreBundle/Entity/PersonRepository.php b/src/LoginCidadao/CoreBundle/Entity/PersonRepository.php index 13df1cf15..cc796da74 100644 --- a/src/LoginCidadao/CoreBundle/Entity/PersonRepository.php +++ b/src/LoginCidadao/CoreBundle/Entity/PersonRepository.php @@ -221,6 +221,13 @@ public function getPhoneSearchQuery(PhoneNumber $phoneNumber) ->setParameter('mobile', $phoneNumber, PhoneNumberType::NAME); } + public function getPartialPhoneSearchQuery(string $phoneNumber) + { + return $this->getBaseSearchQuery() + ->where('p.mobile LIKE :mobile') + ->setParameter('mobile', "{$phoneNumber}%"); + } + /** * This will return the appropriate query for the input given. * @param $query @@ -248,6 +255,10 @@ public function getSmartSearchQuery($query) return $this->getEmailSearchQuery($query); } + if ($query[0] === '+') { + return $this->getPartialPhoneSearchQuery($query); + } + // Defaults to name search return $this->getNameSearchQuery($query); } From d519537704a11749aa6dfb5d6785a9d69498d8fa Mon Sep 17 00:00:00 2001 From: Guilherme Donato Date: Wed, 6 Feb 2019 09:22:15 -0200 Subject: [PATCH 30/40] added Twig masking filter PROCERGS#699 --- .../Resources/config/services.yml | 5 +++ .../SupportBundle/Twig/SupportExtensions.php | 37 +++++++++++++++++++ 2 files changed, 42 insertions(+) create mode 100644 src/LoginCidadao/SupportBundle/Twig/SupportExtensions.php diff --git a/src/LoginCidadao/SupportBundle/Resources/config/services.yml b/src/LoginCidadao/SupportBundle/Resources/config/services.yml index d31eb5313..5dc6ce73e 100644 --- a/src/LoginCidadao/SupportBundle/Resources/config/services.yml +++ b/src/LoginCidadao/SupportBundle/Resources/config/services.yml @@ -5,3 +5,8 @@ services: - "@security.authorization_checker" - "@lc.person.repository" - "@lc.sent_email.repository" + + LoginCidadao\SupportBundle\Twig\SupportExtensions: + clas: LoginCidadao\SupportBundle\Twig\SupportExtensions + tags: + - { name: 'twig.extension' } diff --git a/src/LoginCidadao/SupportBundle/Twig/SupportExtensions.php b/src/LoginCidadao/SupportBundle/Twig/SupportExtensions.php new file mode 100644 index 000000000..accf67141 --- /dev/null +++ b/src/LoginCidadao/SupportBundle/Twig/SupportExtensions.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace LoginCidadao\SupportBundle\Twig; + +use Twig\Extension\AbstractExtension; +use Twig\TwigFilter; + +class SupportExtensions extends AbstractExtension +{ + public function getFilters() + { + return [ + new TwigFilter('support_mask', [$this, 'mask']), + ]; + } + + public function mask($value, bool $mask = true, $hideLength = false) + { + if (!$mask) { + return $value; + } + + if (!$hideLength) { + return preg_replace('/\w/', '*', $value); + } else { + return str_repeat('*', $hideLength); + } + } +} From 6826992fe42f5cdf4bd43cc238f0baf22bdbda39 Mon Sep 17 00:00:00 2001 From: Guilherme Donato Date: Wed, 6 Feb 2019 09:23:24 -0200 Subject: [PATCH 31/40] added Person grid for search PROCERGS#699 --- .../Controller/PersonSupportController.php | 28 ++++++-- .../Resources/translations/messages.pt_BR.yml | 8 +++ .../views/PersonSupport/grid.html.twig | 67 +++++++++++++++++++ .../views/PersonSupport/index.html.twig | 4 ++ 4 files changed, 102 insertions(+), 5 deletions(-) create mode 100644 src/LoginCidadao/SupportBundle/Resources/views/PersonSupport/grid.html.twig diff --git a/src/LoginCidadao/SupportBundle/Controller/PersonSupportController.php b/src/LoginCidadao/SupportBundle/Controller/PersonSupportController.php index cf9e77b03..b6659c010 100644 --- a/src/LoginCidadao/SupportBundle/Controller/PersonSupportController.php +++ b/src/LoginCidadao/SupportBundle/Controller/PersonSupportController.php @@ -11,8 +11,10 @@ namespace LoginCidadao\SupportBundle\Controller; use Doctrine\ORM\NonUniqueResultException; +use Doctrine\ORM\QueryBuilder; use LoginCidadao\CoreBundle\Entity\PersonRepository; use LoginCidadao\CoreBundle\Entity\SentEmail; +use LoginCidadao\CoreBundle\Helper\GridHelper; use LoginCidadao\CoreBundle\Model\PersonInterface; use LoginCidadao\SupportBundle\Form\PersonSearchFormType; use LoginCidadao\SupportBundle\Model\PersonSearchRequest; @@ -35,6 +37,7 @@ class PersonSupportController extends Controller */ public function indexAction(Request $request) { + $gridView = null; $search = new PersonSearchRequest(); $search->smartSearch = $request->get('search', null); @@ -45,9 +48,9 @@ public function indexAction(Request $request) if ($form->isSubmitted() && $form->isValid()) { /** @var PersonRepository $repo */ $repo = $this->getDoctrine()->getRepository('LoginCidadaoCoreBundle:Person'); + $query = $repo->getSmartSearchQuery($search->smartSearch); try { - $person = $repo->getSmartSearchQuery($search->smartSearch) - ->getQuery()->getOneOrNullResult(); + $person = $query->getQuery()->getOneOrNullResult(); if ($person instanceof PersonInterface) { return $this->redirectToRoute('lc_support_person_view', [ @@ -56,14 +59,15 @@ public function indexAction(Request $request) ]); } } catch (NonUniqueResultException $e) { - // TODO: regular search + $grid = $this->getPersonGrid($query, $form); + $gridView = $grid->createView($request); } - - dump("FORM OK"); } return $this->render('LoginCidadaoSupportBundle:PersonSupport:index.html.twig', [ 'form' => $form->createView(), + 'grid' => $gridView, + 'search' => $search, ]); } @@ -97,4 +101,18 @@ private function validateSupportTicketId(string $ticket = null): SentEmail return $sentEmail; } + + private function getPersonGrid(QueryBuilder $query, $form): GridHelper + { + $grid = new GridHelper(); + $grid->setId('person-grid'); + $grid->setPerPage(5); + $grid->setMaxResult(5); + $grid->setInfiniteGrid(true); + $grid->setRoute('lc_support_person_search'); + $grid->setRouteParams([$form->getName()]); + $grid->setQueryBuilder($query); + + return $grid; + } } diff --git a/src/LoginCidadao/SupportBundle/Resources/translations/messages.pt_BR.yml b/src/LoginCidadao/SupportBundle/Resources/translations/messages.pt_BR.yml index eaf8e2c66..693b4a587 100644 --- a/src/LoginCidadao/SupportBundle/Resources/translations/messages.pt_BR.yml +++ b/src/LoginCidadao/SupportBundle/Resources/translations/messages.pt_BR.yml @@ -3,6 +3,14 @@ support: menu: "Pesquisar Usuários" title: "Pesquisar Usuário" submit: "Pesquisar" + grid: + first_name: "Nome" + last_name: "Sobrenome" + cpf: "CPF" + phone_number: "Telefone" + email: "Email" + details: "Ver Detalhes" + load_more: "Carregar mais" person: title: "Informações de %first_name%" view: diff --git a/src/LoginCidadao/SupportBundle/Resources/views/PersonSupport/grid.html.twig b/src/LoginCidadao/SupportBundle/Resources/views/PersonSupport/grid.html.twig new file mode 100644 index 000000000..c32734c26 --- /dev/null +++ b/src/LoginCidadao/SupportBundle/Resources/views/PersonSupport/grid.html.twig @@ -0,0 +1,67 @@ +{% extends "LoginCidadaoCoreBundle::grid_layout.html.twig" %} + +{% block grid_header_data %} +
    {{ 'support.search.grid.first_name' | trans }}
    +
    {{ 'support.search.grid.last_name' | trans }}
    +
    {{ 'support.search.grid.cpf' | trans }}
    +
    {{ 'support.search.grid.phone_number' | trans }}
    +
    {{ 'support.search.grid.email' | trans }}
    +{% endblock grid_header_data %} + +{% block grid_row_action %} + +{% endblock grid_row_action %} + +{% block grid_row_data %} + {% if row.phoneNumber.countryCode == 55 %} + {% set format = 'NATIONAL' %} + {% else %} + {% set format = 'INTERNATIONAL' %} + {% endif %} + +
    +
    {{ 'support.search.grid.first_name' | trans }}
    +
    {{ row.firstName }}
    +
    +
    +
    {{ 'support.search.grid.last_name' | trans }}
    +
    {{ row.surname }}
    +
    +
    +
    {{ 'support.search.grid.cpf' | trans }}
    +
    {{ row.cpf | formatCpf | support_mask(not is_granted('ROLE_VIEW_USERS_CPF')) }}
    +
    +
    +
    {{ 'support.search.grid.phone_number' | trans }}
    +
    {{ row.phoneNumber | phone_number_format(format) | support_mask(not is_granted('ROLE_SUPPORT_VIEW_PHONE')) }}
    +
    +
    +
    {{ 'support.search.grid.email' | trans }}
    +
    {{ row.email | support_mask(not is_granted('ROLE_SUPPORT_VIEW_EMAIL')) }}
    +
    +{% endblock grid_row_data %} + +{% block grid_infinite_pagination %} +
    + {% if not grid.getRlast and grid.isInfiniteGrid %} + {% set routeParams = { 'page': grid.page + 1 } %} +
    + +
    + {% endif %} +
    +{% endblock grid_infinite_pagination %} diff --git a/src/LoginCidadao/SupportBundle/Resources/views/PersonSupport/index.html.twig b/src/LoginCidadao/SupportBundle/Resources/views/PersonSupport/index.html.twig index 515a29b47..771c28cc4 100644 --- a/src/LoginCidadao/SupportBundle/Resources/views/PersonSupport/index.html.twig +++ b/src/LoginCidadao/SupportBundle/Resources/views/PersonSupport/index.html.twig @@ -25,4 +25,8 @@
    + + {% if grid %} + {{ include("LoginCidadaoSupportBundle:PersonSupport:grid.html.twig") }} + {% endif %} {% endblock %} From 11c53adf22b31b10c6566e34aec5ab185cccf4b7 Mon Sep 17 00:00:00 2001 From: Guilherme Donato Date: Wed, 6 Feb 2019 09:24:14 -0200 Subject: [PATCH 32/40] fixed dates and added CPF formating PROCERGS#699 --- .../Resources/views/PersonSupport/view.html.twig | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/LoginCidadao/SupportBundle/Resources/views/PersonSupport/view.html.twig b/src/LoginCidadao/SupportBundle/Resources/views/PersonSupport/view.html.twig index 4a5fb829e..eb77c98f7 100644 --- a/src/LoginCidadao/SupportBundle/Resources/views/PersonSupport/view.html.twig +++ b/src/LoginCidadao/SupportBundle/Resources/views/PersonSupport/view.html.twig @@ -16,21 +16,21 @@
    {{ 'support.person.view.birthday.label' | trans }}
    {{ person.birthday }}
    {{ 'support.person.view.cpf.label' | trans }}
    -
    {{ person.cpf }}
    +
    {{ person.cpf | formatCpf }}
    {{ 'support.person.view.email.label' | trans }}
    {{ person.email }}
    {{ 'support.person.view.emailVerifiedAt.label' | trans }}
    -
    {{ person.emailVerifiedAt | date('support.person.view.datetime_format' | trans) }}
    +
    {{ person.emailVerifiedAt is empty ? "-" : person.emailVerifiedAt | date('support.person.view.datetime_format' | trans) }}
    {{ 'support.person.view.phoneNumber.label' | trans }}
    {{ person.phoneNumber }}
    {{ 'support.person.view.lastPasswordResetRequest.label' | trans }}
    -
    {{ person.lastPasswordResetRequest | date('support.person.view.datetime_format' | trans) }}
    +
    {{ person.lastPasswordResetRequest is empty ? "-" : person.lastPasswordResetRequest | date('support.person.view.datetime_format' | trans) }}
    {{ 'support.person.view.2FA.label' | trans }}
    {{ (person.has2FA ? 'support.person.view.2FA.enabled' : 'support.person.view.2FA.disabled') | trans }}
    {{ 'support.person.view.enabled.label' | trans }}
    {{ (person.enabled ? 'support.person.view.enabled.enabled' : 'support.person.view.enabled.disabled') | trans }}
    {{ 'support.person.view.lastUpdate.label' | trans }}
    -
    {{ person.lastUpdate | date('support.person.view.datetime_format' | trans) }}
    +
    {{ person.lastUpdate is empty ? "-" : person.lastUpdate | date('support.person.view.datetime_format' | trans) }}
    {{ 'support.person.view.createdAt.label' | trans }}
    {{ person.createdAt | date('support.person.view.datetime_format' | trans) }}
    From fbd8dd24257ace6528935771afda0e9d77cb648e Mon Sep 17 00:00:00 2001 From: Guilherme Donato Date: Wed, 6 Feb 2019 14:06:21 -0200 Subject: [PATCH 33/40] added unit tests PROCERGS#699 --- .../Controller/PersonSupportController.php | 1 + .../Exception/PersonNotFoundException.php | 16 ++ .../SupportBundle/Model/PersonalData.php | 6 +- .../SupportBundle/Model/SupportPerson.php | 9 +- .../SupportBundle/Service/SupportHandler.php | 7 +- .../Tests/Form/PersonSearchFormTypeTest.php | 49 ++++++ .../Tests/Model/PersonalDataTest.php | 17 ++ .../Tests/Model/SupportPersonTest.php | 160 ++++++++++++++++++ .../Tests/Service/SupportHandlerTest.php | 147 ++++++++++++++++ .../Tests/Twig/SupportExtensionsTest.php | 39 +++++ 10 files changed, 441 insertions(+), 10 deletions(-) create mode 100644 src/LoginCidadao/SupportBundle/Exception/PersonNotFoundException.php create mode 100644 src/LoginCidadao/SupportBundle/Tests/Form/PersonSearchFormTypeTest.php create mode 100644 src/LoginCidadao/SupportBundle/Tests/Model/SupportPersonTest.php create mode 100644 src/LoginCidadao/SupportBundle/Tests/Service/SupportHandlerTest.php create mode 100644 src/LoginCidadao/SupportBundle/Tests/Twig/SupportExtensionsTest.php diff --git a/src/LoginCidadao/SupportBundle/Controller/PersonSupportController.php b/src/LoginCidadao/SupportBundle/Controller/PersonSupportController.php index b6659c010..0d12d92a0 100644 --- a/src/LoginCidadao/SupportBundle/Controller/PersonSupportController.php +++ b/src/LoginCidadao/SupportBundle/Controller/PersonSupportController.php @@ -29,6 +29,7 @@ * @package LoginCidadao\SupportBundle\Controller * * @Security("has_role('ROLE_SUPPORT_AGENT')") + * @codeCoverageIgnore */ class PersonSupportController extends Controller { diff --git a/src/LoginCidadao/SupportBundle/Exception/PersonNotFoundException.php b/src/LoginCidadao/SupportBundle/Exception/PersonNotFoundException.php new file mode 100644 index 000000000..45995c8f9 --- /dev/null +++ b/src/LoginCidadao/SupportBundle/Exception/PersonNotFoundException.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace LoginCidadao\SupportBundle\Exception; + +class PersonNotFoundException extends \RuntimeException +{ + // +} diff --git a/src/LoginCidadao/SupportBundle/Model/PersonalData.php b/src/LoginCidadao/SupportBundle/Model/PersonalData.php index 40f47c5dd..068c397cd 100644 --- a/src/LoginCidadao/SupportBundle/Model/PersonalData.php +++ b/src/LoginCidadao/SupportBundle/Model/PersonalData.php @@ -55,8 +55,12 @@ public static function createWithValue(string $name, ?string $value, string $cha { if (null === $value) { $value = ''; + $filled = false; + } else { + $filled = (bool)$value; } - return new self($name, $value, (bool)$value, null, $challenge); + + return new self($name, $value, $filled, null, $challenge); } public static function createWithoutValue(string $name, ?string $value, string $challenge = null): PersonalData diff --git a/src/LoginCidadao/SupportBundle/Model/SupportPerson.php b/src/LoginCidadao/SupportBundle/Model/SupportPerson.php index 983b15aa9..79c39e6ad 100644 --- a/src/LoginCidadao/SupportBundle/Model/SupportPerson.php +++ b/src/LoginCidadao/SupportBundle/Model/SupportPerson.php @@ -98,17 +98,12 @@ public function __construct(PersonInterface $person, AuthorizationCheckerInterfa $this->setBirthday($person, $authorizationChecker); } - public function checkCpf(string $cpf) - { - return $this->cpf->checkValue($cpf); - } - private function setPhoneNumber(PersonInterface $person, AuthorizationCheckerInterface $authorizationChecker) { $phoneNumber = $person->getMobile(); if ($phoneNumber instanceof PhoneNumber) { $phoneUtil = PhoneNumberUtil::getInstance(); - $phoneNumber = $phoneUtil->format($person->getMobile(), PhoneNumberFormat::E164); + $phoneNumber = $phoneUtil->format($phoneNumber, PhoneNumberFormat::E164); } if ($authorizationChecker->isGranted('ROLE_SUPPORT_VIEW_PHONE')) { $this->phoneNumber = PersonalData::createWithValue('phoneNumber', $phoneNumber); @@ -208,7 +203,7 @@ public function getLastPasswordResetRequest(): ?\DateTimeInterface /** * @return bool */ - public function isHas2FA(): bool + public function has2FA(): bool { return $this->has2FA; } diff --git a/src/LoginCidadao/SupportBundle/Service/SupportHandler.php b/src/LoginCidadao/SupportBundle/Service/SupportHandler.php index a69717c71..39035dffc 100644 --- a/src/LoginCidadao/SupportBundle/Service/SupportHandler.php +++ b/src/LoginCidadao/SupportBundle/Service/SupportHandler.php @@ -14,6 +14,7 @@ use LoginCidadao\CoreBundle\Entity\SentEmail; use LoginCidadao\CoreBundle\Entity\SentEmailRepository; use LoginCidadao\CoreBundle\Model\PersonInterface; +use LoginCidadao\SupportBundle\Exception\PersonNotFoundException; use LoginCidadao\SupportBundle\Model\PersonalData; use LoginCidadao\SupportBundle\Model\SupportPerson; use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; @@ -48,9 +49,11 @@ public function __construct( public function getSupportPerson($id): SupportPerson { $person = $this->personRepository->find($id); - if ($person instanceof PersonInterface) { - return new SupportPerson($person, $this->authChecker); + if (!$person instanceof PersonInterface) { + throw new PersonNotFoundException(); } + + return new SupportPerson($person, $this->authChecker); } public function getInitialMessage($id): ?SentEmail diff --git a/src/LoginCidadao/SupportBundle/Tests/Form/PersonSearchFormTypeTest.php b/src/LoginCidadao/SupportBundle/Tests/Form/PersonSearchFormTypeTest.php new file mode 100644 index 000000000..61d8cf6dc --- /dev/null +++ b/src/LoginCidadao/SupportBundle/Tests/Form/PersonSearchFormTypeTest.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace LoginCidadao\SupportBundle\Tests\Form; + +use LoginCidadao\SupportBundle\Form\PersonSearchFormType; +use LoginCidadao\SupportBundle\Model\PersonSearchRequest; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; +use Symfony\Component\Form\Extension\Core\Type\TextType; +use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\OptionsResolver\OptionsResolver; + +class PersonSearchFormTypeTest extends TestCase +{ + public function testForm() + { + /** @var OptionsResolver|MockObject $resolver */ + $resolver = $this->createMock(OptionsResolver::class); + $resolver->expects($this->once())->method('setDefaults')->with([ + 'data_class' => PersonSearchRequest::class, + 'csrf_protection' => false, + ]); + + $builder = $this->createMock(FormBuilderInterface::class); + $builder->expects($this->once())->method('setMethod')->with('GET')->willReturn($builder); + $builder->expects($this->exactly(2))->method('add') + ->willReturnCallback(function ($name, $type, $options) use ($builder) { + $this->assertContains($name, ['supportTicket', 'smartSearch']); + $this->assertEquals(TextType::class, $type); + if (!empty($options)) { + $this->assertArrayHasKey('label', $options); + } + + return $builder; + }); + + $form = new PersonSearchFormType(); + $form->configureOptions($resolver); + $form->buildForm($builder, []); + } +} diff --git a/src/LoginCidadao/SupportBundle/Tests/Model/PersonalDataTest.php b/src/LoginCidadao/SupportBundle/Tests/Model/PersonalDataTest.php index f44cc0743..f61e39c52 100644 --- a/src/LoginCidadao/SupportBundle/Tests/Model/PersonalDataTest.php +++ b/src/LoginCidadao/SupportBundle/Tests/Model/PersonalDataTest.php @@ -24,6 +24,21 @@ public function testKnownValue() $this->assertNotNull($data->getHash()); $this->assertNotNull($data->getChallenge()); $this->assertTrue($data->checkValue($value)); + $this->assertTrue($data->isValueFilled()); + $this->assertEquals($value, $data->__toString()); + } + + public function testKnownEmptyValue() + { + $data = PersonalData::createWithValue($name = 'privateInfo', $value = null); + + $this->assertSame($name, $data->getName()); + $this->assertSame('', $data->getValue()); + $this->assertNotNull($data->getHash()); + $this->assertNotNull($data->getChallenge()); + $this->assertTrue($data->checkValue('')); + $this->assertFalse($data->isValueFilled()); + $this->assertEquals('', $data->__toString()); } public function testUnknownValue() @@ -36,6 +51,8 @@ public function testUnknownValue() $this->assertNotNull($data->getHash()); $this->assertNotNull($data->getChallenge()); $this->assertTrue($data->checkValue($value)); + $this->assertTrue($data->isValueFilled()); + $this->assertEquals('Yes', $data->__toString()); } public function testCreateInvalidObject() diff --git a/src/LoginCidadao/SupportBundle/Tests/Model/SupportPersonTest.php b/src/LoginCidadao/SupportBundle/Tests/Model/SupportPersonTest.php new file mode 100644 index 000000000..deaac7d76 --- /dev/null +++ b/src/LoginCidadao/SupportBundle/Tests/Model/SupportPersonTest.php @@ -0,0 +1,160 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace LoginCidadao\SupportBundle\Tests\Model; + +use libphonenumber\PhoneNumber; +use LoginCidadao\CoreBundle\Entity\Person; +use LoginCidadao\CoreBundle\Model\PersonInterface; +use LoginCidadao\SupportBundle\Model\PersonalData; +use LoginCidadao\SupportBundle\Model\SupportPerson; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; +use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; + +class SupportPersonTest extends TestCase +{ + public function testMinimumAccess() + { + $authChecker = $this->getAuthChecker([]); + + $phone = (new PhoneNumber())->setNationalNumber(55)->setNationalNumber(51999998888); + + /** @var MockObject|PersonInterface $person */ + $person = $this->createMock(Person::class); + $person->expects($this->once())->method('getId')->willReturn($id = 123); + $person->expects($this->once())->method('getFirstName')->willReturn($firstName = 'John'); + $person->expects($this->once())->method('getSurname')->willReturn($lastName = 'Doe'); + $person->expects($this->once())->method('getBirthdate')->willReturn(new \DateTime()); + $person->expects($this->once())->method('getEmailConfirmedAt')->willReturn($emailVerified = new \DateTime()); + $person->expects($this->once())->method('getPasswordRequestedAt')->willReturn($lastPwReset = null); + $person->expects($this->once())->method('getGoogleAuthenticatorSecret')->willReturn('1234'); + $person->expects($this->once())->method('isEnabled')->willReturn(true); + $person->expects($this->once())->method('getUpdatedAt')->willReturn($lastUpdate = new \DateTime()); + $person->expects($this->once())->method('getCreatedAt')->willReturn($createdAt = new \DateTime()); + $person->expects($this->once())->method('getFacebookId')->willReturn('facebook'); + $person->expects($this->once())->method('getGoogleId')->willReturn('google'); + $person->expects($this->once())->method('getTwitterId')->willReturn('twitter'); + $person->expects($this->once())->method('getMobile')->willReturn($phone); + + $supportPerson = new SupportPerson($person, $authChecker); + $this->assertInstanceOf(PersonalData::class, $supportPerson->getBirthday()); + $this->assertNull($supportPerson->getBirthday()->getValue()); + + $this->assertInstanceOf(PersonalData::class, $supportPerson->getCpf()); + $this->assertNull($supportPerson->getCpf()->getValue()); + + $this->assertInstanceOf(PersonalData::class, $supportPerson->getEmail()); + $this->assertNull($supportPerson->getEmail()->getValue()); + + $this->assertInstanceOf(PersonalData::class, $supportPerson->getPhoneNumber()); + $this->assertNull($supportPerson->getPhoneNumber()->getValue()); + + $this->assertSame($id, $supportPerson->getId()); + $this->assertSame($firstName, $supportPerson->getFirstName()); + $this->assertSame($lastName, $supportPerson->getLastName()); + $this->assertSame($emailVerified, $supportPerson->getEmailVerifiedAt()); + $this->assertSame($lastPwReset, $supportPerson->getLastPasswordResetRequest()); + $this->assertSame($lastUpdate, $supportPerson->getLastUpdate()); + $this->assertSame($createdAt, $supportPerson->getCreatedAt()); + $this->assertTrue($supportPerson->has2FA()); + $this->assertTrue($supportPerson->isEnabled()); + $this->assertEquals([ + 'facebook' => true, + 'google' => true, + 'twitter' => true, + ], $supportPerson->getThirdPartyConnections()); + } + + public function testFullAccess() + { + $authChecker = $this->getAuthChecker([ + 'ROLE_VIEW_USERS_CPF', + 'ROLE_SUPPORT_VIEW_EMAIL', + 'ROLE_SUPPORT_VIEW_PHONE', + 'ROLE_SUPPORT_VIEW_BIRTHDAY' + ]); + + $phone = (new PhoneNumber())->setNationalNumber(55)->setNationalNumber(51999998888); + + /** @var MockObject|PersonInterface $person */ + $person = $this->createMock(Person::class); + $person->expects($this->once())->method('getId')->willReturn($id = 123); + $person->expects($this->once())->method('getFirstName')->willReturn($firstName = 'John'); + $person->expects($this->once())->method('getSurname')->willReturn($lastName = 'Doe'); + $person->expects($this->once())->method('getBirthdate')->willReturn(new \DateTime()); + $person->expects($this->once())->method('getEmailConfirmedAt')->willReturn($emailVerified = new \DateTime()); + $person->expects($this->once())->method('getPasswordRequestedAt')->willReturn($lastPwReset = null); + $person->expects($this->once())->method('getGoogleAuthenticatorSecret')->willReturn('1234'); + $person->expects($this->once())->method('isEnabled')->willReturn(true); + $person->expects($this->once())->method('getUpdatedAt')->willReturn($lastUpdate = new \DateTime()); + $person->expects($this->once())->method('getCreatedAt')->willReturn($createdAt = new \DateTime()); + $person->expects($this->once())->method('getFacebookId')->willReturn('facebook'); + $person->expects($this->once())->method('getGoogleId')->willReturn('google'); + $person->expects($this->once())->method('getTwitterId')->willReturn('twitter'); + $person->expects($this->once())->method('getMobile')->willReturn($phone); + + $supportPerson = new SupportPerson($person, $authChecker); + $this->assertInstanceOf(PersonalData::class, $supportPerson->getBirthday()); + $this->assertNotNull($supportPerson->getBirthday()->getValue()); + + $this->assertInstanceOf(PersonalData::class, $supportPerson->getCpf()); + $this->assertNotNull($supportPerson->getCpf()->getValue()); + + $this->assertInstanceOf(PersonalData::class, $supportPerson->getEmail()); + $this->assertNotNull($supportPerson->getEmail()->getValue()); + + $this->assertInstanceOf(PersonalData::class, $supportPerson->getPhoneNumber()); + $this->assertNotNull($supportPerson->getPhoneNumber()->getValue()); + + $this->assertSame($id, $supportPerson->getId()); + $this->assertSame($firstName, $supportPerson->getFirstName()); + $this->assertSame($lastName, $supportPerson->getLastName()); + $this->assertSame($emailVerified, $supportPerson->getEmailVerifiedAt()); + $this->assertSame($lastPwReset, $supportPerson->getLastPasswordResetRequest()); + $this->assertSame($lastUpdate, $supportPerson->getLastUpdate()); + $this->assertSame($createdAt, $supportPerson->getCreatedAt()); + $this->assertTrue($supportPerson->has2FA()); + $this->assertTrue($supportPerson->isEnabled()); + $this->assertEquals([ + 'facebook' => true, + 'google' => true, + 'twitter' => true, + ], $supportPerson->getThirdPartyConnections()); + } + + public function testGetName() + { + $person = $this->createMock(Person::class); + $person->expects($this->once())->method('getId')->willReturn($id = 123); + $supportPerson = new SupportPerson($person, $this->getAuthChecker([])); + + $this->assertEquals($id, $supportPerson->getName()); + + $person = $this->createMock(Person::class); + $person->expects($this->once())->method('getId')->willReturn(123); + $person->expects($this->once())->method('getFirstName')->willReturn($firstName = 'John'); + $supportPerson = new SupportPerson($person, $this->getAuthChecker([])); + + $this->assertEquals($firstName, $supportPerson->getName()); + } + + private function getAuthChecker(array $roles = []) + { + /** @var MockObject|AuthorizationCheckerInterface $authChecker */ + $authChecker = $this->createMock(AuthorizationCheckerInterface::class); + $authChecker->expects($this->exactly(4))->method('isGranted') + ->willReturnCallback(function ($role) use ($roles) { + return false !== array_search($role, $roles); + }); + + return $authChecker; + } +} diff --git a/src/LoginCidadao/SupportBundle/Tests/Service/SupportHandlerTest.php b/src/LoginCidadao/SupportBundle/Tests/Service/SupportHandlerTest.php new file mode 100644 index 000000000..4867d9fab --- /dev/null +++ b/src/LoginCidadao/SupportBundle/Tests/Service/SupportHandlerTest.php @@ -0,0 +1,147 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace LoginCidadao\SupportBundle\Tests\Service; + +use LoginCidadao\CoreBundle\Entity\Person; +use LoginCidadao\CoreBundle\Entity\PersonRepository; +use LoginCidadao\CoreBundle\Entity\SentEmail; +use LoginCidadao\CoreBundle\Entity\SentEmailRepository; +use LoginCidadao\SupportBundle\Exception\PersonNotFoundException; +use LoginCidadao\SupportBundle\Model\PersonalData; +use LoginCidadao\SupportBundle\Model\SupportPerson; +use LoginCidadao\SupportBundle\Service\SupportHandler; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; +use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; + +class SupportHandlerTest extends TestCase +{ + public function testGetSupportPerson() + { + $id = 123; + $person = $this->createMock(Person::class); + + $handler = new SupportHandler($this->getAuthChecker(), $this->getPersonRepo($id, $person), + $this->getSentEmailRepo()); + $supportPerson = $handler->getSupportPerson($id); + + $this->assertInstanceOf(SupportPerson::class, $supportPerson); + } + + public function testGetNonExistingSupportPerson() + { + $this->expectException(PersonNotFoundException::class); + $id = 123; + + $handler = new SupportHandler($this->getAuthChecker(), $this->getPersonRepo($id, null), + $this->getSentEmailRepo()); + $handler->getSupportPerson($id); + } + + public function testGetInitialMessage() + { + $id = 123; + $sentEmail = $this->createMock(SentEmail::class); + + $handler = new SupportHandler($this->getAuthChecker(), $this->getPersonRepo(), + $this->getSentEmailRepo($id, $sentEmail)); + + $this->assertSame($sentEmail, $handler->getInitialMessage($id)); + } + + public function testGetValidationMap() + { + $person = $this->createMock(SupportPerson::class); + $person->expects($this->once())->method('getCpf') + ->willReturn($this->getPersonalData(null, null, null, 'cpf', true)); + + $person->expects($this->once())->method('getBirthday') + ->willReturn($this->getPersonalData(null, null, null, null, false)); + + $person->expects($this->once())->method('getEmail') + ->willReturn($this->getPersonalData('email', 'hash', 'challenge', null, true)); + + $person->expects($this->once())->method('getPhoneNumber') + ->willReturn($this->getPersonalData('phoneNumber', 'hash', 'challenge', null, true)); + + $handler = new SupportHandler($this->getAuthChecker(), $this->getPersonRepo(), $this->getSentEmailRepo()); + + $map = $handler->getValidationMap($person); + + $this->assertNotEmpty($map); + $this->assertArrayNotHasKey('birthday', $map); + $this->assertArrayNotHasKey('cpf', $map); + // Phone + $this->assertEquals('challenge', $map['phoneNumber']['challenge']); + $this->assertEquals('hash', $map['phoneNumber']['hash']); + // Email + $this->assertEquals('challenge', $map['email']['challenge']); + $this->assertEquals('hash', $map['email']['hash']); + } + + private function getPersonalData(?string $name, ?string $hash, ?string $challenge, $value, bool $filled) + { + $data = $this->createMock(PersonalData::class); + $data->expects($this->once())->method('isValueFilled')->willReturn($filled); + if (null !== $value) { + $data->expects($this->once())->method('getValue')->willReturn($value); + } + if (null !== $challenge) { + $data->expects($this->once())->method('getChallenge')->willReturn($challenge); + } + if (null !== $name) { + $data->expects($this->once())->method('getName')->willReturn($name); + } + if (null !== $hash) { + $data->expects($this->once())->method('getHash')->willReturn($hash); + } + + return $data; + } + + /** + * @return MockObject|AuthorizationCheckerInterface + */ + private function getAuthChecker() + { + return $this->createMock(AuthorizationCheckerInterface::class); + } + + /** + * @param null $id + * @param null $person + * @return MockObject|PersonRepository + */ + private function getPersonRepo($id = null, $person = null) + { + $personRepo = $this->createMock(PersonRepository::class); + if (null !== $id) { + $personRepo->expects($this->once())->method('find')->with($id)->willReturn($person); + } + + return $personRepo; + } + + /** + * @param null $id + * @param null $sentEmail + * @return MockObject|SentEmailRepository + */ + private function getSentEmailRepo($id = null, $sentEmail = null) + { + $sentEmailRepo = $this->createMock(SentEmailRepository::class); + if (null !== $id) { + $sentEmailRepo->expects($this->once())->method('find')->with($id)->willReturn($sentEmail); + } + + return $sentEmailRepo; + } +} diff --git a/src/LoginCidadao/SupportBundle/Tests/Twig/SupportExtensionsTest.php b/src/LoginCidadao/SupportBundle/Tests/Twig/SupportExtensionsTest.php new file mode 100644 index 000000000..2bc9013f5 --- /dev/null +++ b/src/LoginCidadao/SupportBundle/Tests/Twig/SupportExtensionsTest.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace LoginCidadao\SupportBundle\Tests\Twig; + +use LoginCidadao\SupportBundle\Twig\SupportExtensions; +use PHPUnit\Framework\TestCase; +use Twig\TwigFilter; + +class SupportExtensionsTest extends TestCase +{ + public function testMask() + { + $ext = new SupportExtensions(); + + $filters = $ext->getFilters(); + + /** @var TwigFilter $filter */ + $filter = $filters[0]; + + $this->assertCount(1, $filters); + $this->assertInstanceOf(TwigFilter::class, $filter); + $this->assertEquals('support_mask', $filter->getName()); + + $this->assertEquals('test', $ext->mask('test', false)); + $this->assertEquals('****', $ext->mask('test', true)); + $this->assertEquals('*', $ext->mask('test', true, 1)); + $this->assertEquals('****, *****.', $ext->mask('Test, hello.', true)); + $this->assertEquals('*****', $ext->mask('12345', true)); + $this->assertEquals('****@*******.***', $ext->mask('mask@example.com', true)); + } +} From 0bf7c94d71d1d765d5d40dede1596206c42811c1 Mon Sep 17 00:00:00 2001 From: Guilherme Donato Date: Wed, 6 Feb 2019 14:20:11 -0200 Subject: [PATCH 34/40] logging profile views PROCERGS#699 --- .../APIBundle/Security/Audit/ActionLogger.php | 13 +++++++------ .../CoreBundle/Controller/DefaultController.php | 7 ++++--- .../Model/IdentifiablePersonInterface.php | 16 ++++++++++++++++ .../CoreBundle/Model/PersonInterface.php | 4 +--- .../Controller/PersonSupportController.php | 5 +++++ .../SupportBundle/Model/SupportPerson.php | 3 ++- 6 files changed, 35 insertions(+), 13 deletions(-) create mode 100644 src/LoginCidadao/CoreBundle/Model/IdentifiablePersonInterface.php diff --git a/src/LoginCidadao/APIBundle/Security/Audit/ActionLogger.php b/src/LoginCidadao/APIBundle/Security/Audit/ActionLogger.php index 60f3cb6d8..08ac9d2a0 100644 --- a/src/LoginCidadao/APIBundle/Security/Audit/ActionLogger.php +++ b/src/LoginCidadao/APIBundle/Security/Audit/ActionLogger.php @@ -10,6 +10,7 @@ namespace LoginCidadao\APIBundle\Security\Audit; +use LoginCidadao\CoreBundle\Model\IdentifiablePersonInterface; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\HttpFoundation\Request; @@ -188,14 +189,14 @@ public function registerImpersonate( /** * @param Request $request - * @param PersonInterface $person - * @param PersonInterface $viewer + * @param IdentifiablePersonInterface $person + * @param IdentifiablePersonInterface $viewer * @param array $controllerAction */ public function registerProfileView( Request $request, - PersonInterface $person, - PersonInterface $viewer, + IdentifiablePersonInterface $person, + IdentifiablePersonInterface $viewer, array $controllerAction ) { $auditUsername = $this->auditConfig->getCurrentUsername(); @@ -206,8 +207,8 @@ public function registerProfileView( private function registerActionLog( Request $request, - PersonInterface $person, - PersonInterface $actor, + IdentifiablePersonInterface $person, + IdentifiablePersonInterface $actor, array $controllerAction, $auditUsername, $actionType diff --git a/src/LoginCidadao/CoreBundle/Controller/DefaultController.php b/src/LoginCidadao/CoreBundle/Controller/DefaultController.php index 86094bd24..136326c3d 100644 --- a/src/LoginCidadao/CoreBundle/Controller/DefaultController.php +++ b/src/LoginCidadao/CoreBundle/Controller/DefaultController.php @@ -92,19 +92,20 @@ public function dashboardAction() // logs $em = $this->getDoctrine()->getManager(); + $showProfileViews = $this->isGranted('FEATURE_SHOW_PROFILE_VIEWS'); /** @var ActionLogRepository $logRepo */ $logRepo = $em->getRepository('LoginCidadaoAPIBundle:ActionLog'); $logs['logins'] = $logRepo->findLoginsByPerson($this->getUser(), 5); - $logs['activity'] = $logRepo->getActivityLogsByTarget($this->getUser(), 4); + $logs['activity'] = $logRepo->getActivityLogsByTarget($this->getUser(), 4, $showProfileViews); $defaultClientUid = $this->container->getParameter('oauth_default_client.uid'); - return array( + return [ 'allBadges' => $badges, 'userBadges' => $userBadges, 'logs' => $logs, 'defaultClientUid' => $defaultClientUid, - ); + ]; } /** diff --git a/src/LoginCidadao/CoreBundle/Model/IdentifiablePersonInterface.php b/src/LoginCidadao/CoreBundle/Model/IdentifiablePersonInterface.php new file mode 100644 index 000000000..06d71a9d5 --- /dev/null +++ b/src/LoginCidadao/CoreBundle/Model/IdentifiablePersonInterface.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace LoginCidadao\CoreBundle\Model; + +interface IdentifiablePersonInterface +{ + public function getId(); +} diff --git a/src/LoginCidadao/CoreBundle/Model/PersonInterface.php b/src/LoginCidadao/CoreBundle/Model/PersonInterface.php index 20f7b5efe..e3ab9b950 100644 --- a/src/LoginCidadao/CoreBundle/Model/PersonInterface.php +++ b/src/LoginCidadao/CoreBundle/Model/PersonInterface.php @@ -24,10 +24,8 @@ use Symfony\Component\Security\Core\Encoder\EncoderAwareInterface; use JMS\Serializer\Annotation as JMS; -interface PersonInterface extends EncoderAwareInterface, UserInterface, LocationAwareInterface, LongPollableInterface, TwoFactorInterface +interface PersonInterface extends IdentifiablePersonInterface, EncoderAwareInterface, UserInterface, LocationAwareInterface, LongPollableInterface, TwoFactorInterface { - public function getId(); - public function getEmail(); /** diff --git a/src/LoginCidadao/SupportBundle/Controller/PersonSupportController.php b/src/LoginCidadao/SupportBundle/Controller/PersonSupportController.php index 0d12d92a0..e4391d5a6 100644 --- a/src/LoginCidadao/SupportBundle/Controller/PersonSupportController.php +++ b/src/LoginCidadao/SupportBundle/Controller/PersonSupportController.php @@ -12,6 +12,7 @@ use Doctrine\ORM\NonUniqueResultException; use Doctrine\ORM\QueryBuilder; +use LoginCidadao\APIBundle\Security\Audit\ActionLogger; use LoginCidadao\CoreBundle\Entity\PersonRepository; use LoginCidadao\CoreBundle\Entity\SentEmail; use LoginCidadao\CoreBundle\Helper\GridHelper; @@ -84,6 +85,10 @@ public function viewAction(Request $request, $id) $person = $supportHandler->getSupportPerson($id); + /** @var ActionLogger $actionLogger */ + $actionLogger = $this->get('lc.action_logger'); + $actionLogger->registerProfileView($request, $person, $this->getUser(), [$this, 'viewAction']); + return $this->render('LoginCidadaoSupportBundle:PersonSupport:view.html.twig', [ 'person' => $person, 'supportRequest' => $supportRequest, diff --git a/src/LoginCidadao/SupportBundle/Model/SupportPerson.php b/src/LoginCidadao/SupportBundle/Model/SupportPerson.php index 79c39e6ad..8e11b5721 100644 --- a/src/LoginCidadao/SupportBundle/Model/SupportPerson.php +++ b/src/LoginCidadao/SupportBundle/Model/SupportPerson.php @@ -14,10 +14,11 @@ use libphonenumber\PhoneNumberFormat; use libphonenumber\PhoneNumberUtil; use LoginCidadao\CoreBundle\Entity\Person; +use LoginCidadao\CoreBundle\Model\IdentifiablePersonInterface; use LoginCidadao\CoreBundle\Model\PersonInterface; use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; -class SupportPerson +class SupportPerson implements IdentifiablePersonInterface { /** @var mixed */ private $id; From 500e2db4c663a06193afac7c076930666385c541 Mon Sep 17 00:00:00 2001 From: Guilherme Donato Date: Wed, 6 Feb 2019 15:30:47 -0200 Subject: [PATCH 35/40] created support ticket UUID, updated tests and added menu item PROCERGS#699 --- app/config/security.yml | 2 ++ .../Controller/DefaultController.php | 5 +++-- .../CoreBundle/Entity/SentEmail.php | 21 +++++++++++++++++++ .../CoreBundle/Model/PersonInterface.php | 5 +++++ .../Resources/views/sidebar.html.twig | 8 +++++++ .../Controller/PersonSupportController.php | 3 ++- .../SupportBundle/Model/SupportPerson.php | 3 +-- .../Resources/views/sidebar.html.twig | 4 ++-- .../SupportBundle/Service/SupportHandler.php | 4 ++-- .../Tests/Service/SupportHandlerTest.php | 3 ++- 10 files changed, 48 insertions(+), 10 deletions(-) diff --git a/app/config/security.yml b/app/config/security.yml index 01ba1d4bb..3612a488d 100644 --- a/app/config/security.yml +++ b/app/config/security.yml @@ -73,6 +73,7 @@ security: # Support Roles ROLE_SUPPORT_AGENT: + - ROLE_SUPPORT_SEARCH_USERS ROLE_SUPPORT_MANAGER: - ROLE_SUPPORT_AGENT - ROLE_SUPPORT_MANAGE_AGENTS @@ -80,6 +81,7 @@ security: - ROLE_SUPPORT_VIEW_PHONE - ROLE_SUPPORT_VIEW_BIRTHDAY ROLE_SUPPORT_MANAGE_AGENTS: + ROLE_SUPPORT_SEARCH_USERS: ROLE_SUPPORT_VIEW_EMAIL: ROLE_SUPPORT_VIEW_PHONE: ROLE_SUPPORT_VIEW_BIRTHDAY: diff --git a/src/LoginCidadao/CoreBundle/Controller/DefaultController.php b/src/LoginCidadao/CoreBundle/Controller/DefaultController.php index 136326c3d..f23959ef2 100644 --- a/src/LoginCidadao/CoreBundle/Controller/DefaultController.php +++ b/src/LoginCidadao/CoreBundle/Controller/DefaultController.php @@ -171,9 +171,10 @@ private function getEmail(SupportMessage $supportMessage, TranslatorInterface $t { $message = $supportMessage->getFormattedMessage($translator); - $email = (new SentEmail()) + $email = new SentEmail(); + $email ->setType('contact-mail') - ->setSubject('Fale conosco - '.$supportMessage->getName()) + ->setSubject('Fale conosco - '.$supportMessage->getName()." - Ticket: {$email->getSupportTicket()}") ->setSender($supportMessage->getEmail()) ->setReceiver($this->container->getParameter('contact_form.email')) ->setMessage($message); diff --git a/src/LoginCidadao/CoreBundle/Entity/SentEmail.php b/src/LoginCidadao/CoreBundle/Entity/SentEmail.php index ca622f893..e7faeaae9 100644 --- a/src/LoginCidadao/CoreBundle/Entity/SentEmail.php +++ b/src/LoginCidadao/CoreBundle/Entity/SentEmail.php @@ -11,6 +11,7 @@ namespace LoginCidadao\CoreBundle\Entity; use Doctrine\ORM\Mapping as ORM; +use Ramsey\Uuid\Uuid; /** * SentEmail @@ -71,9 +72,21 @@ class SentEmail */ private $type; + /** + * @var string + * + * @ORM\Column(name="support_ticket", type="string", length=255, unique=true) + */ + private $supportTicket; + public function __construct() { $this->setDate(new \DateTime()); + try { + $this->supportTicket = Uuid::uuid4(); + } catch (\Exception $e) { + $this->supportTicket = bin2hex(random_bytes(4)); + } } /** @@ -224,6 +237,14 @@ public function getType() return $this->type; } + /** + * @return string + */ + public function getSupportTicket(): string + { + return $this->supportTicket; + } + public function getSwiftMail() { return (new \Swift_Message($this->getSubject())) diff --git a/src/LoginCidadao/CoreBundle/Model/PersonInterface.php b/src/LoginCidadao/CoreBundle/Model/PersonInterface.php index e3ab9b950..ec7a746ed 100644 --- a/src/LoginCidadao/CoreBundle/Model/PersonInterface.php +++ b/src/LoginCidadao/CoreBundle/Model/PersonInterface.php @@ -292,4 +292,9 @@ public function getBackupCodes(); * @return PersonInterface */ public function setUpdatedAt($updatedAt = null); + + /** + * @return \DateTimeInterface|null + */ + public function getPasswordRequestedAt(); } diff --git a/src/LoginCidadao/CoreBundle/Resources/views/sidebar.html.twig b/src/LoginCidadao/CoreBundle/Resources/views/sidebar.html.twig index c54e46e72..e9ba1e5d7 100644 --- a/src/LoginCidadao/CoreBundle/Resources/views/sidebar.html.twig +++ b/src/LoginCidadao/CoreBundle/Resources/views/sidebar.html.twig @@ -81,6 +81,14 @@ {% endif %} + {% if app.user is not null and is_granted('ROLE_SUPPORT_SEARCH_USERS') %} +
  • + + {{ 'support.search.menu' | trans }} + + +
  • + {% endif %} {% endblock %}
    diff --git a/src/LoginCidadao/SupportBundle/Controller/PersonSupportController.php b/src/LoginCidadao/SupportBundle/Controller/PersonSupportController.php index e4391d5a6..99245f2a2 100644 --- a/src/LoginCidadao/SupportBundle/Controller/PersonSupportController.php +++ b/src/LoginCidadao/SupportBundle/Controller/PersonSupportController.php @@ -36,8 +36,9 @@ class PersonSupportController extends Controller { /** * @Route("/support/search", name="lc_support_person_search") + * @Security("has_role('ROLE_SUPPORT_SEARCH_USERS')") */ - public function indexAction(Request $request) + public function searchAction(Request $request) { $gridView = null; $search = new PersonSearchRequest(); diff --git a/src/LoginCidadao/SupportBundle/Model/SupportPerson.php b/src/LoginCidadao/SupportBundle/Model/SupportPerson.php index 8e11b5721..c45602ddf 100644 --- a/src/LoginCidadao/SupportBundle/Model/SupportPerson.php +++ b/src/LoginCidadao/SupportBundle/Model/SupportPerson.php @@ -13,7 +13,6 @@ use libphonenumber\PhoneNumber; use libphonenumber\PhoneNumberFormat; use libphonenumber\PhoneNumberUtil; -use LoginCidadao\CoreBundle\Entity\Person; use LoginCidadao\CoreBundle\Model\IdentifiablePersonInterface; use LoginCidadao\CoreBundle\Model\PersonInterface; use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; @@ -64,7 +63,7 @@ class SupportPerson implements IdentifiablePersonInterface /** * SupportPerson constructor. - * @param PersonInterface|Person $person + * @param PersonInterface $person * @param AuthorizationCheckerInterface $authorizationChecker */ public function __construct(PersonInterface $person, AuthorizationCheckerInterface $authorizationChecker) diff --git a/src/LoginCidadao/SupportBundle/Resources/views/sidebar.html.twig b/src/LoginCidadao/SupportBundle/Resources/views/sidebar.html.twig index afeff505a..59f01b49a 100644 --- a/src/LoginCidadao/SupportBundle/Resources/views/sidebar.html.twig +++ b/src/LoginCidadao/SupportBundle/Resources/views/sidebar.html.twig @@ -8,11 +8,11 @@ {{ 'Back to main menu'|trans }} - {% if is_granted('FEATURE_IMPERSONATION_REPORTS') %} + {% if is_granted('ROLE_SUPPORT_SEARCH_USERS') %}
  • {{ 'support.search.menu' | trans }} - +
  • {% endif %} diff --git a/src/LoginCidadao/SupportBundle/Service/SupportHandler.php b/src/LoginCidadao/SupportBundle/Service/SupportHandler.php index 39035dffc..beb8a677f 100644 --- a/src/LoginCidadao/SupportBundle/Service/SupportHandler.php +++ b/src/LoginCidadao/SupportBundle/Service/SupportHandler.php @@ -56,10 +56,10 @@ public function getSupportPerson($id): SupportPerson return new SupportPerson($person, $this->authChecker); } - public function getInitialMessage($id): ?SentEmail + public function getInitialMessage($ticket): ?SentEmail { /** @var SentEmail $sentEmail */ - $sentEmail = $this->sentEmailRepository->find($id); + $sentEmail = $this->sentEmailRepository->findOneBy(['supportTicket' => $ticket]); return $sentEmail; } diff --git a/src/LoginCidadao/SupportBundle/Tests/Service/SupportHandlerTest.php b/src/LoginCidadao/SupportBundle/Tests/Service/SupportHandlerTest.php index 4867d9fab..e541574e1 100644 --- a/src/LoginCidadao/SupportBundle/Tests/Service/SupportHandlerTest.php +++ b/src/LoginCidadao/SupportBundle/Tests/Service/SupportHandlerTest.php @@ -139,7 +139,8 @@ private function getSentEmailRepo($id = null, $sentEmail = null) { $sentEmailRepo = $this->createMock(SentEmailRepository::class); if (null !== $id) { - $sentEmailRepo->expects($this->once())->method('find')->with($id)->willReturn($sentEmail); + $sentEmailRepo->expects($this->once())->method('findOneBy') + ->with(['supportTicket' => $id])->willReturn($sentEmail); } return $sentEmailRepo; From 22c862fdfccaf3267b4de8c4864038aa15ebfb67 Mon Sep 17 00:00:00 2001 From: Guilherme Donato Date: Thu, 7 Feb 2019 11:46:23 -0200 Subject: [PATCH 36/40] added more information to support interface PROCERGS#841 --- app/config/security.yml | 2 + .../CoreBundle/Model/PersonInterface.php | 2 + .../Resources/views/sidebar.html.twig | 4 +- .../Controller/PersonSupportController.php | 22 ++- .../SupportBundle/Model/SupportPerson.php | 12 ++ .../Resources/config/services.yml | 1 + .../Resources/translations/messages.pt_BR.yml | 12 +- .../views/PersonSupport/view.html.twig | 5 + .../Resources/views/sidebar.html.twig | 2 +- .../SupportBundle/Service/SupportHandler.php | 26 ++++ .../Tests/Model/SupportPersonTest.php | 2 + .../Tests/Service/SupportHandlerTest.php | 126 +++++++++++++++++- 12 files changed, 204 insertions(+), 12 deletions(-) diff --git a/app/config/security.yml b/app/config/security.yml index 3612a488d..b4feadf73 100644 --- a/app/config/security.yml +++ b/app/config/security.yml @@ -50,6 +50,7 @@ security: - ROLE_AUDIT_PROFILE_VIEWS - ROLE_VIEW_3RD_PARTY_CONNECTIONS_USERNAMES - ROLE_PERSON_BLOCK + - ROLE_SKIP_SUPPORT_TOKEN_VALIDATION ROLE_ALLOWED_TO_SWITCH: FEATURE_IMPERSONATION_REPORTS ROLE_EDIT_CLIENT_ALLOWED_RESTRICTED_SCOPES: ROLE_EDIT_CLIENT_ALLOWED_SCOPES @@ -60,6 +61,7 @@ security: ROLE_PERSON_BLOCK: ROLE_EDIT_CLIENT_SUBJECT_TYPE: ROLE_EDIT_BLOCKED_PHONES: + ROLE_SKIP_SUPPORT_TOKEN_VALIDATION: FEATURE_ALPHA: FEATURE_BETA, FEATURE_SHOW_PROFILE_VIEWS, FEATURE_ORGANIZATIONS FEATURE_BETA: FEATURE_PROD, FEATURE_REMOTE_CLAIMS, FEATURE_PHONE_BLOCKLIST FEATURE_PROD: FEATURE_2FACTOR_AUTH, FEATURE_INVALIDATE_SESSIONS diff --git a/src/LoginCidadao/CoreBundle/Model/PersonInterface.php b/src/LoginCidadao/CoreBundle/Model/PersonInterface.php index ec7a746ed..f8f79087c 100644 --- a/src/LoginCidadao/CoreBundle/Model/PersonInterface.php +++ b/src/LoginCidadao/CoreBundle/Model/PersonInterface.php @@ -297,4 +297,6 @@ public function setUpdatedAt($updatedAt = null); * @return \DateTimeInterface|null */ public function getPasswordRequestedAt(); + + public function getLastLogin(); } diff --git a/src/LoginCidadao/CoreBundle/Resources/views/sidebar.html.twig b/src/LoginCidadao/CoreBundle/Resources/views/sidebar.html.twig index e9ba1e5d7..9f81ccc48 100644 --- a/src/LoginCidadao/CoreBundle/Resources/views/sidebar.html.twig +++ b/src/LoginCidadao/CoreBundle/Resources/views/sidebar.html.twig @@ -84,8 +84,8 @@ {% if app.user is not null and is_granted('ROLE_SUPPORT_SEARCH_USERS') %}
  • - {{ 'support.search.menu' | trans }} - + {{ 'support.search.menu.support' | trans }} +
  • {% endif %} diff --git a/src/LoginCidadao/SupportBundle/Controller/PersonSupportController.php b/src/LoginCidadao/SupportBundle/Controller/PersonSupportController.php index 99245f2a2..54bb18760 100644 --- a/src/LoginCidadao/SupportBundle/Controller/PersonSupportController.php +++ b/src/LoginCidadao/SupportBundle/Controller/PersonSupportController.php @@ -24,6 +24,7 @@ use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security; use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; /** * Class PersonSupportController @@ -79,12 +80,20 @@ public function searchAction(Request $request) */ public function viewAction(Request $request, $id) { - $supportRequest = $this->validateSupportTicketId($request->get('ticket')); + try { + $supportRequest = $this->validateSupportTicketId($request->get('ticket')); + } catch (NotFoundHttpException $e) { + if (!$this->isGranted('ROLE_SKIP_SUPPORT_TOKEN_VALIDATION')) { + throw $e; + } + $supportRequest = $this->getDummySupportMessage(); + } /** @var SupportHandler $supportHandler */ $supportHandler = $this->get(SupportHandler::class); $person = $supportHandler->getSupportPerson($id); + $phoneMetadata = $supportHandler->getPhoneMetadata($person); /** @var ActionLogger $actionLogger */ $actionLogger = $this->get('lc.action_logger'); @@ -94,6 +103,7 @@ public function viewAction(Request $request, $id) 'person' => $person, 'supportRequest' => $supportRequest, 'dataValidation' => $supportHandler->getValidationMap($person), + 'phoneMetadata' => $phoneMetadata, ]); } @@ -122,4 +132,14 @@ private function getPersonGrid(QueryBuilder $query, $form): GridHelper return $grid; } + + private function getDummySupportMessage() + { + return (new SentEmail()) + ->setType('dummy') + ->setDate(new \DateTime()) + ->setSubject('Dummy message') + ->setMessage('Dummy message') + ->setSender('Dummy'); + } } diff --git a/src/LoginCidadao/SupportBundle/Model/SupportPerson.php b/src/LoginCidadao/SupportBundle/Model/SupportPerson.php index c45602ddf..1ca23ef83 100644 --- a/src/LoginCidadao/SupportBundle/Model/SupportPerson.php +++ b/src/LoginCidadao/SupportBundle/Model/SupportPerson.php @@ -61,6 +61,9 @@ class SupportPerson implements IdentifiablePersonInterface /** @var \DateTimeInterface */ private $createdAt; + /** @var \DateTimeInterface */ + private $lastLogin; + /** * SupportPerson constructor. * @param PersonInterface $person @@ -77,6 +80,7 @@ public function __construct(PersonInterface $person, AuthorizationCheckerInterfa $this->isEnabled = $person->isEnabled(); $this->lastUpdate = $person->getUpdatedAt(); $this->createdAt = $person->getCreatedAt(); + $this->lastLogin = $person->getLastLogin(); $this->thirdPartyConnections = [ 'facebook' => $person->getFacebookId() !== null, @@ -239,4 +243,12 @@ public function getCreatedAt(): \DateTimeInterface { return $this->createdAt; } + + /** + * @return \DateTimeInterface + */ + public function getLastLogin(): \DateTimeInterface + { + return $this->lastLogin; + } } diff --git a/src/LoginCidadao/SupportBundle/Resources/config/services.yml b/src/LoginCidadao/SupportBundle/Resources/config/services.yml index 5dc6ce73e..ab6046c97 100644 --- a/src/LoginCidadao/SupportBundle/Resources/config/services.yml +++ b/src/LoginCidadao/SupportBundle/Resources/config/services.yml @@ -3,6 +3,7 @@ services: class: LoginCidadao\SupportBundle\Service\SupportHandler arguments: - "@security.authorization_checker" + - "@phone_verification" - "@lc.person.repository" - "@lc.sent_email.repository" diff --git a/src/LoginCidadao/SupportBundle/Resources/translations/messages.pt_BR.yml b/src/LoginCidadao/SupportBundle/Resources/translations/messages.pt_BR.yml index 693b4a587..62c197133 100644 --- a/src/LoginCidadao/SupportBundle/Resources/translations/messages.pt_BR.yml +++ b/src/LoginCidadao/SupportBundle/Resources/translations/messages.pt_BR.yml @@ -1,6 +1,8 @@ support: search: - menu: "Pesquisar Usuários" + menu: + support: "Painel de Suporte" + search: "Pesquisar Usuários" title: "Pesquisar Usuário" submit: "Pesquisar" grid: @@ -22,7 +24,12 @@ support: cpf.label: "CPF" email.label: "Email" emailVerifiedAt.label: "Data de verificação do email" - phoneNumber.label: "Telefone" + phoneNumber: + label: "Telefone" + usersWithSamePhone: "{0} Nenhuma conta utiliza esse telefone|{1} Apenas essa conta utiliza esse telefone|]1,Inf[ %count% pessoas estão usando esse mesmo número!" + phoneNumberVerification: + label: "Data de Verificação do Telefone" + not_verified: "NÃO verificado" lastPasswordResetRequest.label: "Última solicitação de recuperação de senha" 2FA: label: "Autenticação de 2 Fatores ativa?" @@ -34,6 +41,7 @@ support: disabled: "Usuário BLOQUEADO!" lastUpdate.label: "Data da Última Atualização" createdAt.label: "Data de Criação da Conta" + lastLogin.label: "Último Login" connections: enabled: "Conectado" disabled: "Desconectado" diff --git a/src/LoginCidadao/SupportBundle/Resources/views/PersonSupport/view.html.twig b/src/LoginCidadao/SupportBundle/Resources/views/PersonSupport/view.html.twig index eb77c98f7..7d3869e57 100644 --- a/src/LoginCidadao/SupportBundle/Resources/views/PersonSupport/view.html.twig +++ b/src/LoginCidadao/SupportBundle/Resources/views/PersonSupport/view.html.twig @@ -23,6 +23,9 @@
    {{ person.emailVerifiedAt is empty ? "-" : person.emailVerifiedAt | date('support.person.view.datetime_format' | trans) }}
    {{ 'support.person.view.phoneNumber.label' | trans }}
    {{ person.phoneNumber }}
    +
    {{ 'support.person.view.phoneNumber.usersWithSamePhone' | transchoice(phoneMetadata.samePhoneCount) }}
    +
    {{ 'support.person.view.phoneNumberVerification.label' | trans }}
    +
    {{ phoneMetadata.verification is empty ? 'support.person.view.phoneNumberVerification.not_verified' | trans : phoneMetadata.verification.verifiedAt | date('support.person.view.datetime_format' | trans) }}
    {{ 'support.person.view.lastPasswordResetRequest.label' | trans }}
    {{ person.lastPasswordResetRequest is empty ? "-" : person.lastPasswordResetRequest | date('support.person.view.datetime_format' | trans) }}
    {{ 'support.person.view.2FA.label' | trans }}
    @@ -33,6 +36,8 @@
    {{ person.lastUpdate is empty ? "-" : person.lastUpdate | date('support.person.view.datetime_format' | trans) }}
    {{ 'support.person.view.createdAt.label' | trans }}
    {{ person.createdAt | date('support.person.view.datetime_format' | trans) }}
    +
    {{ 'support.person.view.lastLogin.label' | trans }}
    +
    {{ person.lastLogin | date('support.person.view.datetime_format' | trans) }}
    diff --git a/src/LoginCidadao/SupportBundle/Resources/views/sidebar.html.twig b/src/LoginCidadao/SupportBundle/Resources/views/sidebar.html.twig index 59f01b49a..df16b96ec 100644 --- a/src/LoginCidadao/SupportBundle/Resources/views/sidebar.html.twig +++ b/src/LoginCidadao/SupportBundle/Resources/views/sidebar.html.twig @@ -11,7 +11,7 @@ {% if is_granted('ROLE_SUPPORT_SEARCH_USERS') %}
  • - {{ 'support.search.menu' | trans }} + {{ 'support.search.menu.search' | trans }}
  • diff --git a/src/LoginCidadao/SupportBundle/Service/SupportHandler.php b/src/LoginCidadao/SupportBundle/Service/SupportHandler.php index beb8a677f..d6f3b3537 100644 --- a/src/LoginCidadao/SupportBundle/Service/SupportHandler.php +++ b/src/LoginCidadao/SupportBundle/Service/SupportHandler.php @@ -10,10 +10,13 @@ namespace LoginCidadao\SupportBundle\Service; +use libphonenumber\PhoneNumber; use LoginCidadao\CoreBundle\Entity\PersonRepository; use LoginCidadao\CoreBundle\Entity\SentEmail; use LoginCidadao\CoreBundle\Entity\SentEmailRepository; +use LoginCidadao\CoreBundle\Model\IdentifiablePersonInterface; use LoginCidadao\CoreBundle\Model\PersonInterface; +use LoginCidadao\PhoneVerificationBundle\Service\PhoneVerificationServiceInterface; use LoginCidadao\SupportBundle\Exception\PersonNotFoundException; use LoginCidadao\SupportBundle\Model\PersonalData; use LoginCidadao\SupportBundle\Model\SupportPerson; @@ -24,6 +27,9 @@ class SupportHandler /** @var AuthorizationCheckerInterface */ private $authChecker; + /** @var PhoneVerificationServiceInterface */ + private $phoneVerificationService; + /** @var PersonRepository */ private $personRepository; @@ -33,15 +39,18 @@ class SupportHandler /** * SupportHandler constructor. * @param AuthorizationCheckerInterface $authChecker + * @param PhoneVerificationServiceInterface $phoneVerificationService * @param PersonRepository $personRepository * @param SentEmailRepository $sentEmailRepository */ public function __construct( AuthorizationCheckerInterface $authChecker, + PhoneVerificationServiceInterface $phoneVerificationService, PersonRepository $personRepository, SentEmailRepository $sentEmailRepository ) { $this->authChecker = $authChecker; + $this->phoneVerificationService = $phoneVerificationService; $this->personRepository = $personRepository; $this->sentEmailRepository = $sentEmailRepository; } @@ -56,6 +65,23 @@ public function getSupportPerson($id): SupportPerson return new SupportPerson($person, $this->authChecker); } + public function getPhoneMetadata(IdentifiablePersonInterface $person): array + { + /** @var PersonInterface $person */ + $person = $this->personRepository->find($person->getId()); + $phone = $person->getMobile(); + + if ($phone instanceof PhoneNumber) { + $samePhoneCount = $this->personRepository->countByPhone($phone); + $phoneVerification = $this->phoneVerificationService->getPhoneVerification($person, $phone); + } + + return [ + 'samePhoneCount' => $samePhoneCount ?? 0, + 'verification' => $phoneVerification ?? null, + ]; + } + public function getInitialMessage($ticket): ?SentEmail { /** @var SentEmail $sentEmail */ diff --git a/src/LoginCidadao/SupportBundle/Tests/Model/SupportPersonTest.php b/src/LoginCidadao/SupportBundle/Tests/Model/SupportPersonTest.php index deaac7d76..131191ba6 100644 --- a/src/LoginCidadao/SupportBundle/Tests/Model/SupportPersonTest.php +++ b/src/LoginCidadao/SupportBundle/Tests/Model/SupportPersonTest.php @@ -39,6 +39,7 @@ public function testMinimumAccess() $person->expects($this->once())->method('isEnabled')->willReturn(true); $person->expects($this->once())->method('getUpdatedAt')->willReturn($lastUpdate = new \DateTime()); $person->expects($this->once())->method('getCreatedAt')->willReturn($createdAt = new \DateTime()); + $person->expects($this->once())->method('getLastLogin')->willReturn($lastLogin = new \DateTime()); $person->expects($this->once())->method('getFacebookId')->willReturn('facebook'); $person->expects($this->once())->method('getGoogleId')->willReturn('google'); $person->expects($this->once())->method('getTwitterId')->willReturn('twitter'); @@ -64,6 +65,7 @@ public function testMinimumAccess() $this->assertSame($lastPwReset, $supportPerson->getLastPasswordResetRequest()); $this->assertSame($lastUpdate, $supportPerson->getLastUpdate()); $this->assertSame($createdAt, $supportPerson->getCreatedAt()); + $this->assertSame($lastLogin, $supportPerson->getLastLogin()); $this->assertTrue($supportPerson->has2FA()); $this->assertTrue($supportPerson->isEnabled()); $this->assertEquals([ diff --git a/src/LoginCidadao/SupportBundle/Tests/Service/SupportHandlerTest.php b/src/LoginCidadao/SupportBundle/Tests/Service/SupportHandlerTest.php index e541574e1..fba46ca98 100644 --- a/src/LoginCidadao/SupportBundle/Tests/Service/SupportHandlerTest.php +++ b/src/LoginCidadao/SupportBundle/Tests/Service/SupportHandlerTest.php @@ -10,10 +10,13 @@ namespace LoginCidadao\SupportBundle\Tests\Service; +use libphonenumber\PhoneNumber; use LoginCidadao\CoreBundle\Entity\Person; use LoginCidadao\CoreBundle\Entity\PersonRepository; use LoginCidadao\CoreBundle\Entity\SentEmail; use LoginCidadao\CoreBundle\Entity\SentEmailRepository; +use LoginCidadao\PhoneVerificationBundle\Model\PhoneVerificationInterface; +use LoginCidadao\PhoneVerificationBundle\Service\PhoneVerificationServiceInterface; use LoginCidadao\SupportBundle\Exception\PersonNotFoundException; use LoginCidadao\SupportBundle\Model\PersonalData; use LoginCidadao\SupportBundle\Model\SupportPerson; @@ -29,8 +32,12 @@ public function testGetSupportPerson() $id = 123; $person = $this->createMock(Person::class); - $handler = new SupportHandler($this->getAuthChecker(), $this->getPersonRepo($id, $person), - $this->getSentEmailRepo()); + $handler = new SupportHandler( + $this->getAuthChecker(), + $this->getPhoneVerificationService(), + $this->getPersonRepo($id, $person), + $this->getSentEmailRepo() + ); $supportPerson = $handler->getSupportPerson($id); $this->assertInstanceOf(SupportPerson::class, $supportPerson); @@ -41,7 +48,8 @@ public function testGetNonExistingSupportPerson() $this->expectException(PersonNotFoundException::class); $id = 123; - $handler = new SupportHandler($this->getAuthChecker(), $this->getPersonRepo($id, null), + $handler = new SupportHandler($this->getAuthChecker(), $this->getPhoneVerificationService(), + $this->getPersonRepo($id, null), $this->getSentEmailRepo()); $handler->getSupportPerson($id); } @@ -51,8 +59,12 @@ public function testGetInitialMessage() $id = 123; $sentEmail = $this->createMock(SentEmail::class); - $handler = new SupportHandler($this->getAuthChecker(), $this->getPersonRepo(), - $this->getSentEmailRepo($id, $sentEmail)); + $handler = new SupportHandler( + $this->getAuthChecker(), + $this->getPhoneVerificationService(), + $this->getPersonRepo(), + $this->getSentEmailRepo($id, $sentEmail) + ); $this->assertSame($sentEmail, $handler->getInitialMessage($id)); } @@ -72,7 +84,12 @@ public function testGetValidationMap() $person->expects($this->once())->method('getPhoneNumber') ->willReturn($this->getPersonalData('phoneNumber', 'hash', 'challenge', null, true)); - $handler = new SupportHandler($this->getAuthChecker(), $this->getPersonRepo(), $this->getSentEmailRepo()); + $handler = new SupportHandler( + $this->getAuthChecker(), + $this->getPhoneVerificationService(), + $this->getPersonRepo(), + $this->getSentEmailRepo() + ); $map = $handler->getValidationMap($person); @@ -87,6 +104,95 @@ public function testGetValidationMap() $this->assertEquals('hash', $map['email']['hash']); } + public function testGetPhoneMetadata() + { + $id = 321; + $phoneNumber = new PhoneNumber(); + + $person = $this->createMock(Person::class); + $person->expects($this->once())->method('getMobile')->willReturn($phoneNumber); + + $supportPerson = $this->createMock(SupportPerson::class); + $supportPerson->expects($this->once())->method('getId')->willReturn($id); + + $personRepo = $this->getPersonRepo($id, $person); + $personRepo->expects($this->once())->method('countByPhone')->with($phoneNumber)->willReturn(3); + + $verification = $this->createMock(PhoneVerificationInterface::class); + $verificationService = $this->getPhoneVerificationService(); + $verificationService->expects($this->once()) + ->method('getPhoneVerification')->with($person, $phoneNumber)->willReturn($verification); + + $handler = new SupportHandler( + $this->getAuthChecker(), + $verificationService, + $personRepo, + $this->getSentEmailRepo() + ); + $metadata = $handler->getPhoneMetadata($supportPerson); + + $this->assertEquals(3, $metadata['samePhoneCount']); + $this->assertSame($verification, $metadata['verification']); + } + + public function testGetPhoneMetadataNoVerification() + { + $id = 321; + $phoneNumber = new PhoneNumber(); + + $person = $this->createMock(Person::class); + $person->expects($this->once())->method('getMobile')->willReturn($phoneNumber); + + $supportPerson = $this->createMock(SupportPerson::class); + $supportPerson->expects($this->once())->method('getId')->willReturn($id); + + $personRepo = $this->getPersonRepo($id, $person); + $personRepo->expects($this->once())->method('countByPhone')->with($phoneNumber)->willReturn(3); + + $verificationService = $this->getPhoneVerificationService(); + $verificationService->expects($this->once()) + ->method('getPhoneVerification')->with($person, $phoneNumber)->willReturn(null); + + $handler = new SupportHandler( + $this->getAuthChecker(), + $verificationService, + $personRepo, + $this->getSentEmailRepo() + ); + $metadata = $handler->getPhoneMetadata($supportPerson); + + $this->assertEquals(3, $metadata['samePhoneCount']); + $this->assertNull($metadata['verification']); + } + + public function testGetPhoneMetadataNoPhone() + { + $id = 321; + + $person = $this->createMock(Person::class); + $person->expects($this->once())->method('getMobile')->willReturn(null); + + $supportPerson = $this->createMock(SupportPerson::class); + $supportPerson->expects($this->once())->method('getId')->willReturn($id); + + $personRepo = $this->getPersonRepo($id, $person); + $personRepo->expects($this->never())->method('countByPhone'); + + $verificationService = $this->getPhoneVerificationService(); + $verificationService->expects($this->never())->method('getPhoneVerification'); + + $handler = new SupportHandler( + $this->getAuthChecker(), + $verificationService, + $personRepo, + $this->getSentEmailRepo() + ); + $metadata = $handler->getPhoneMetadata($supportPerson); + + $this->assertEquals(0, $metadata['samePhoneCount']); + $this->assertNull($metadata['verification']); + } + private function getPersonalData(?string $name, ?string $hash, ?string $challenge, $value, bool $filled) { $data = $this->createMock(PersonalData::class); @@ -145,4 +251,12 @@ private function getSentEmailRepo($id = null, $sentEmail = null) return $sentEmailRepo; } + + /** + * @return MockObject|PhoneVerificationServiceInterface + */ + private function getPhoneVerificationService() + { + return $this->createMock(PhoneVerificationServiceInterface::class); + } } From 72cd59853e3814062bc587c22969f1d796f19942 Mon Sep 17 00:00:00 2001 From: Guilherme Donato Date: Thu, 7 Feb 2019 15:40:17 -0200 Subject: [PATCH 37/40] allowing admins to test the interface without ticket id PROCERGS#841 --- .../Controller/PersonSupportController.php | 12 +------ .../views/PersonSupport/view.html.twig | 36 ++++++++++--------- 2 files changed, 20 insertions(+), 28 deletions(-) diff --git a/src/LoginCidadao/SupportBundle/Controller/PersonSupportController.php b/src/LoginCidadao/SupportBundle/Controller/PersonSupportController.php index 54bb18760..7d99ea0ea 100644 --- a/src/LoginCidadao/SupportBundle/Controller/PersonSupportController.php +++ b/src/LoginCidadao/SupportBundle/Controller/PersonSupportController.php @@ -86,7 +86,7 @@ public function viewAction(Request $request, $id) if (!$this->isGranted('ROLE_SKIP_SUPPORT_TOKEN_VALIDATION')) { throw $e; } - $supportRequest = $this->getDummySupportMessage(); + $supportRequest = null; } /** @var SupportHandler $supportHandler */ @@ -132,14 +132,4 @@ private function getPersonGrid(QueryBuilder $query, $form): GridHelper return $grid; } - - private function getDummySupportMessage() - { - return (new SentEmail()) - ->setType('dummy') - ->setDate(new \DateTime()) - ->setSubject('Dummy message') - ->setMessage('Dummy message') - ->setSender('Dummy'); - } } diff --git a/src/LoginCidadao/SupportBundle/Resources/views/PersonSupport/view.html.twig b/src/LoginCidadao/SupportBundle/Resources/views/PersonSupport/view.html.twig index 7d3869e57..57b68bec7 100644 --- a/src/LoginCidadao/SupportBundle/Resources/views/PersonSupport/view.html.twig +++ b/src/LoginCidadao/SupportBundle/Resources/views/PersonSupport/view.html.twig @@ -79,26 +79,28 @@ {% endif %} -
    -
    -

    {{ 'support.person.initialContact.title' | trans }}

    -
    -
    -
    -
    -

    {{ 'support.person.initialContact.about' | trans }}

    -
    -
    {{ 'support.person.initialContact.subject' | trans }}
    -
    {{ supportRequest.subject }}
    -
    {{ 'support.person.initialContact.date' | trans }}
    -
    {{ supportRequest.date | date }}
    -
    {{ 'support.person.initialContact.message' | trans }}
    -
    {{ supportRequest.message }}
    -
    + {% if supportRequest is not null %} +
    +
    +

    {{ 'support.person.initialContact.title' | trans }}

    +
    +
    +
    +
    +

    {{ 'support.person.initialContact.about' | trans }}

    +
    +
    {{ 'support.person.initialContact.subject' | trans }}
    +
    {{ supportRequest.subject }}
    +
    {{ 'support.person.initialContact.date' | trans }}
    +
    {{ supportRequest.date | date }}
    +
    {{ 'support.person.initialContact.message' | trans }}
    +
    {{ supportRequest.message }}
    +
    +
    -
    + {% endif %}
    {% endblock %} From 66eea1178498017258488f4aedd776ed4cd5e2aa Mon Sep 17 00:00:00 2001 From: Guilherme Donato Date: Thu, 7 Feb 2019 16:44:17 -0200 Subject: [PATCH 38/40] making it easy to extend third party connections PROCERGS#841 --- .../Controller/PersonSupportController.php | 8 +++ .../SupportBundle/Model/SupportPerson.php | 17 ----- .../views/PersonSupport/view.html.twig | 2 +- .../SupportBundle/Service/SupportHandler.php | 19 ++++++ .../Tests/Model/SupportPersonTest.php | 16 ----- .../Tests/Service/SupportHandlerTest.php | 63 +++++++++++++++++++ 6 files changed, 91 insertions(+), 34 deletions(-) diff --git a/src/LoginCidadao/SupportBundle/Controller/PersonSupportController.php b/src/LoginCidadao/SupportBundle/Controller/PersonSupportController.php index 7d99ea0ea..da7c3748b 100644 --- a/src/LoginCidadao/SupportBundle/Controller/PersonSupportController.php +++ b/src/LoginCidadao/SupportBundle/Controller/PersonSupportController.php @@ -24,6 +24,7 @@ use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security; use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; /** @@ -38,6 +39,8 @@ class PersonSupportController extends Controller /** * @Route("/support/search", name="lc_support_person_search") * @Security("has_role('ROLE_SUPPORT_SEARCH_USERS')") + * @param Request $request + * @return Response */ public function searchAction(Request $request) { @@ -77,6 +80,9 @@ public function searchAction(Request $request) /** * @Route("/support/person/{id}", name="lc_support_person_view") + * @param Request $request + * @param $id + * @return Response */ public function viewAction(Request $request, $id) { @@ -94,6 +100,7 @@ public function viewAction(Request $request, $id) $person = $supportHandler->getSupportPerson($id); $phoneMetadata = $supportHandler->getPhoneMetadata($person); + $thirdPartyConnections = $supportHandler->getThirdPartyConnections($person); /** @var ActionLogger $actionLogger */ $actionLogger = $this->get('lc.action_logger'); @@ -101,6 +108,7 @@ public function viewAction(Request $request, $id) return $this->render('LoginCidadaoSupportBundle:PersonSupport:view.html.twig', [ 'person' => $person, + 'thirdPartyConnections' => $thirdPartyConnections, 'supportRequest' => $supportRequest, 'dataValidation' => $supportHandler->getValidationMap($person), 'phoneMetadata' => $phoneMetadata, diff --git a/src/LoginCidadao/SupportBundle/Model/SupportPerson.php b/src/LoginCidadao/SupportBundle/Model/SupportPerson.php index 1ca23ef83..9c6914a45 100644 --- a/src/LoginCidadao/SupportBundle/Model/SupportPerson.php +++ b/src/LoginCidadao/SupportBundle/Model/SupportPerson.php @@ -49,9 +49,6 @@ class SupportPerson implements IdentifiablePersonInterface /** @var bool */ private $has2FA; - /** @var array */ - private $thirdPartyConnections = []; - /** @var bool */ private $isEnabled; @@ -82,12 +79,6 @@ public function __construct(PersonInterface $person, AuthorizationCheckerInterfa $this->createdAt = $person->getCreatedAt(); $this->lastLogin = $person->getLastLogin(); - $this->thirdPartyConnections = [ - 'facebook' => $person->getFacebookId() !== null, - 'google' => $person->getGoogleId() !== null, - 'twitter' => $person->getTwitterId() !== null, - ]; - if ($authorizationChecker->isGranted('ROLE_VIEW_USERS_CPF')) { $this->cpf = PersonalData::createWithValue('cpf', $person->getCpf()); } else { @@ -212,14 +203,6 @@ public function has2FA(): bool return $this->has2FA; } - /** - * @return array - */ - public function getThirdPartyConnections(): array - { - return $this->thirdPartyConnections; - } - /** * @return bool */ diff --git a/src/LoginCidadao/SupportBundle/Resources/views/PersonSupport/view.html.twig b/src/LoginCidadao/SupportBundle/Resources/views/PersonSupport/view.html.twig index 57b68bec7..d538100ae 100644 --- a/src/LoginCidadao/SupportBundle/Resources/views/PersonSupport/view.html.twig +++ b/src/LoginCidadao/SupportBundle/Resources/views/PersonSupport/view.html.twig @@ -41,7 +41,7 @@
    - {% for thirdParty, connected in person.thirdPartyConnections %} + {% for thirdParty, connected in thirdPartyConnections %}
    {{ ('support.person.view.connections.' ~ thirdParty ~ '.label') | trans }}
    {{ (connected ? 'support.person.view.connections.enabled' : 'support.person.view.connections.disabled') | trans }}
    {% endfor %} diff --git a/src/LoginCidadao/SupportBundle/Service/SupportHandler.php b/src/LoginCidadao/SupportBundle/Service/SupportHandler.php index d6f3b3537..74b8e7c98 100644 --- a/src/LoginCidadao/SupportBundle/Service/SupportHandler.php +++ b/src/LoginCidadao/SupportBundle/Service/SupportHandler.php @@ -65,6 +65,25 @@ public function getSupportPerson($id): SupportPerson return new SupportPerson($person, $this->authChecker); } + public function getThirdPartyConnections(IdentifiablePersonInterface $person): array + { + $connections = []; + if (!$person instanceof PersonInterface) { + /** @var PersonInterface $person */ + $person = $this->personRepository->find($person->getId()); + } + + if ($person instanceof PersonInterface) { + $connections = [ + 'facebook' => $person->getFacebookId() !== null, + 'google' => $person->getGoogleId() !== null, + 'twitter' => $person->getTwitterId() !== null, + ]; + } + + return $connections; + } + public function getPhoneMetadata(IdentifiablePersonInterface $person): array { /** @var PersonInterface $person */ diff --git a/src/LoginCidadao/SupportBundle/Tests/Model/SupportPersonTest.php b/src/LoginCidadao/SupportBundle/Tests/Model/SupportPersonTest.php index 131191ba6..61484249e 100644 --- a/src/LoginCidadao/SupportBundle/Tests/Model/SupportPersonTest.php +++ b/src/LoginCidadao/SupportBundle/Tests/Model/SupportPersonTest.php @@ -40,9 +40,6 @@ public function testMinimumAccess() $person->expects($this->once())->method('getUpdatedAt')->willReturn($lastUpdate = new \DateTime()); $person->expects($this->once())->method('getCreatedAt')->willReturn($createdAt = new \DateTime()); $person->expects($this->once())->method('getLastLogin')->willReturn($lastLogin = new \DateTime()); - $person->expects($this->once())->method('getFacebookId')->willReturn('facebook'); - $person->expects($this->once())->method('getGoogleId')->willReturn('google'); - $person->expects($this->once())->method('getTwitterId')->willReturn('twitter'); $person->expects($this->once())->method('getMobile')->willReturn($phone); $supportPerson = new SupportPerson($person, $authChecker); @@ -68,11 +65,6 @@ public function testMinimumAccess() $this->assertSame($lastLogin, $supportPerson->getLastLogin()); $this->assertTrue($supportPerson->has2FA()); $this->assertTrue($supportPerson->isEnabled()); - $this->assertEquals([ - 'facebook' => true, - 'google' => true, - 'twitter' => true, - ], $supportPerson->getThirdPartyConnections()); } public function testFullAccess() @@ -98,9 +90,6 @@ public function testFullAccess() $person->expects($this->once())->method('isEnabled')->willReturn(true); $person->expects($this->once())->method('getUpdatedAt')->willReturn($lastUpdate = new \DateTime()); $person->expects($this->once())->method('getCreatedAt')->willReturn($createdAt = new \DateTime()); - $person->expects($this->once())->method('getFacebookId')->willReturn('facebook'); - $person->expects($this->once())->method('getGoogleId')->willReturn('google'); - $person->expects($this->once())->method('getTwitterId')->willReturn('twitter'); $person->expects($this->once())->method('getMobile')->willReturn($phone); $supportPerson = new SupportPerson($person, $authChecker); @@ -125,11 +114,6 @@ public function testFullAccess() $this->assertSame($createdAt, $supportPerson->getCreatedAt()); $this->assertTrue($supportPerson->has2FA()); $this->assertTrue($supportPerson->isEnabled()); - $this->assertEquals([ - 'facebook' => true, - 'google' => true, - 'twitter' => true, - ], $supportPerson->getThirdPartyConnections()); } public function testGetName() diff --git a/src/LoginCidadao/SupportBundle/Tests/Service/SupportHandlerTest.php b/src/LoginCidadao/SupportBundle/Tests/Service/SupportHandlerTest.php index fba46ca98..57b5b51ff 100644 --- a/src/LoginCidadao/SupportBundle/Tests/Service/SupportHandlerTest.php +++ b/src/LoginCidadao/SupportBundle/Tests/Service/SupportHandlerTest.php @@ -15,6 +15,7 @@ use LoginCidadao\CoreBundle\Entity\PersonRepository; use LoginCidadao\CoreBundle\Entity\SentEmail; use LoginCidadao\CoreBundle\Entity\SentEmailRepository; +use LoginCidadao\CoreBundle\Model\IdentifiablePersonInterface; use LoginCidadao\PhoneVerificationBundle\Model\PhoneVerificationInterface; use LoginCidadao\PhoneVerificationBundle\Service\PhoneVerificationServiceInterface; use LoginCidadao\SupportBundle\Exception\PersonNotFoundException; @@ -193,6 +194,68 @@ public function testGetPhoneMetadataNoPhone() $this->assertNull($metadata['verification']); } + public function testThirdPartyConnections() + { + /** @var IdentifiablePersonInterface|MockObject $identifiablePerson */ + $identifiablePerson = $this->createMock(IdentifiablePersonInterface::class); + $identifiablePerson->expects($this->once())->method('getId')->willReturn($id = 666); + + $person = $this->createMock(Person::class); + $person->expects($this->once())->method('getFacebookId')->willReturn('facebook'); + $person->expects($this->once())->method('getGoogleId')->willReturn('google'); + $person->expects($this->once())->method('getTwitterId')->willReturn('twitter'); + + $handler = new SupportHandler( + $this->getAuthChecker(), + $this->getPhoneVerificationService(), + $this->getPersonRepo($id, $person), + $this->getSentEmailRepo() + ); + $this->assertEquals([ + 'google' => true, + 'facebook' => true, + 'twitter' => true, + ], $handler->getThirdPartyConnections($identifiablePerson)); + } + + public function testThirdPartyConnectionsWithPersonInterface() + { + $person = $this->createMock(Person::class); + $person->expects($this->once())->method('getFacebookId')->willReturn('facebook'); + $person->expects($this->once())->method('getGoogleId')->willReturn('google'); + $person->expects($this->once())->method('getTwitterId')->willReturn('twitter'); + + $personRepo = $this->getPersonRepo(); + $personRepo->expects($this->never())->method('find'); + + $handler = new SupportHandler( + $this->getAuthChecker(), + $this->getPhoneVerificationService(), + $personRepo, + $this->getSentEmailRepo() + ); + $this->assertEquals([ + 'google' => true, + 'facebook' => true, + 'twitter' => true, + ], $handler->getThirdPartyConnections($person)); + } + + public function testThirdPartyConnectionsUserNotFound() + { + /** @var IdentifiablePersonInterface|MockObject $identifiablePerson */ + $identifiablePerson = $this->createMock(IdentifiablePersonInterface::class); + $identifiablePerson->expects($this->once())->method('getId')->willReturn($id = 666); + + $handler = new SupportHandler( + $this->getAuthChecker(), + $this->getPhoneVerificationService(), + $this->getPersonRepo($id, null), + $this->getSentEmailRepo() + ); + $this->assertEmpty($handler->getThirdPartyConnections($identifiablePerson)); + } + private function getPersonalData(?string $name, ?string $hash, ?string $challenge, $value, bool $filled) { $data = $this->createMock(PersonalData::class); From fd28e7c4af98324b79fe56c2b17486c63a02861e Mon Sep 17 00:00:00 2001 From: Guilherme Donato Date: Fri, 8 Feb 2019 10:03:34 -0200 Subject: [PATCH 39/40] extended SupportHandler to include NFG status PROCERGS#841 --- app/Resources/translations/messages.pt_BR.yml | 6 +- app/config/config.yml | 8 ++ .../CoreBundle/Helper/MeuRSHelper.php | 27 +++---- .../Service/SupportHandlerDecorator.php | 81 +++++++++++++++++++ .../Service/SupportHandlerDecoratorTest.php | 76 +++++++++++++++++ 5 files changed, 178 insertions(+), 20 deletions(-) create mode 100644 src/PROCERGS/LoginCidadao/CoreBundle/Service/SupportHandlerDecorator.php create mode 100644 src/PROCERGS/LoginCidadao/CoreBundle/Tests/Service/SupportHandlerDecoratorTest.php diff --git a/app/Resources/translations/messages.pt_BR.yml b/app/Resources/translations/messages.pt_BR.yml index f4eaa9764..f966c6186 100644 --- a/app/Resources/translations/messages.pt_BR.yml +++ b/app/Resources/translations/messages.pt_BR.yml @@ -1,4 +1,6 @@ password_hint: range: - with_reqs: Senha de %min% ou mais %reqs%. - no_reqs: Senha de %min% ou mais caracteres. + with_reqs: "Senha de %min% ou mais %reqs%." + no_reqs: "Senha de %min% ou mais caracteres." + +support.person.view.connections.nfg.label: "Nota Fiscal Gaúcha" diff --git a/app/config/config.yml b/app/config/config.yml index c4202d153..4010e7db6 100644 --- a/app/config/config.yml +++ b/app/config/config.yml @@ -607,6 +607,14 @@ services: fos_user.doctrine_registry: alias: doctrine + # Extend Third Party Connections in the Support Page + PROCERGS\LoginCidadao\CoreBundle\Service\SupportHandlerDecorator: + decorates: 'LoginCidadao\SupportBundle\Service\SupportHandler' + arguments: + - '@PROCERGS\LoginCidadao\CoreBundle\Service\SupportHandlerDecorator.inner' + - '@meurs.helper' + - '@lc.person.repository' + donato_path_well: enabled: '%check_pathwell_topologies%' diff --git a/src/PROCERGS/LoginCidadao/CoreBundle/Helper/MeuRSHelper.php b/src/PROCERGS/LoginCidadao/CoreBundle/Helper/MeuRSHelper.php index d0d58ed5e..97df5fa34 100644 --- a/src/PROCERGS/LoginCidadao/CoreBundle/Helper/MeuRSHelper.php +++ b/src/PROCERGS/LoginCidadao/CoreBundle/Helper/MeuRSHelper.php @@ -10,7 +10,7 @@ namespace PROCERGS\LoginCidadao\CoreBundle\Helper; -use Doctrine\ORM\EntityManager; +use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityRepository; use LoginCidadao\CoreBundle\Entity\PersonRepository; use LoginCidadao\CoreBundle\Model\PersonInterface; @@ -19,38 +19,32 @@ class MeuRSHelper { - /** @var EntityManager */ + /** @var EntityManagerInterface */ protected $em; /** @var PersonMeuRSRepository */ protected $personMeuRSRepository; - public function __construct( - EntityManager $em, - EntityRepository $personMeuRSRepository - ) { + public function __construct(EntityManagerInterface $em, EntityRepository $personMeuRSRepository) + { $this->em = $em; $this->personMeuRSRepository = $personMeuRSRepository; } /** - * @param \LoginCidadao\CoreBundle\Model\PersonInterface $person + * @param PersonInterface $person * @param boolean $create * @return PersonMeuRS */ public function getPersonMeuRS(PersonInterface $person, $create = false) { - $personMeuRS = $this->personMeuRSRepository->findOneBy( - array( - 'person' => $person, - ) - ); + $personMeuRS = $this->personMeuRSRepository->findOneBy(['person' => $person]); if ($create && !($personMeuRS instanceof PersonMeuRS)) { $personMeuRS = new PersonMeuRS(); $personMeuRS->setPerson($person); $this->em->persist($personMeuRS); - $this->em->flush($personMeuRS); + $this->em->flush(); } return $personMeuRS; @@ -63,11 +57,8 @@ public function getVoterRegistration(PersonInterface $person) return $personMeuRS ? $personMeuRS->getVoterRegistration() : null; } - public function setVoterRegistration( - EntityManager $em, - PersonInterface $person, - $value - ) { + public function setVoterRegistration(EntityManagerInterface $em, PersonInterface $person, $value) + { $personMeuRS = $this->getPersonMeuRS($person, true); $personMeuRS->setVoterRegistration($value); diff --git a/src/PROCERGS/LoginCidadao/CoreBundle/Service/SupportHandlerDecorator.php b/src/PROCERGS/LoginCidadao/CoreBundle/Service/SupportHandlerDecorator.php new file mode 100644 index 000000000..21d816f3c --- /dev/null +++ b/src/PROCERGS/LoginCidadao/CoreBundle/Service/SupportHandlerDecorator.php @@ -0,0 +1,81 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace PROCERGS\LoginCidadao\CoreBundle\Service; + +use LoginCidadao\CoreBundle\Entity\PersonRepository; +use LoginCidadao\CoreBundle\Entity\SentEmail; +use LoginCidadao\CoreBundle\Model\IdentifiablePersonInterface; +use LoginCidadao\CoreBundle\Model\PersonInterface; +use LoginCidadao\SupportBundle\Model\SupportPerson; +use LoginCidadao\SupportBundle\Service\SupportHandlerInterface; +use PROCERGS\LoginCidadao\CoreBundle\Helper\MeuRSHelper; + +class SupportHandlerDecorator implements SupportHandlerInterface +{ + /** @var SupportHandlerInterface */ + private $parent; + + /** @var MeuRSHelper */ + private $rsHelper; + + /** @var PersonRepository */ + private $personRepo; + + /** + * SupportHandlerDecorator constructor. + * @param SupportHandlerInterface $parent + * @param MeuRSHelper $rsHelper + * @param PersonRepository $personRepo + */ + public function __construct(SupportHandlerInterface $parent, MeuRSHelper $rsHelper, PersonRepository $personRepo) + { + $this->parent = $parent; + $this->rsHelper = $rsHelper; + $this->personRepo = $personRepo; + } + + public function getThirdPartyConnections(IdentifiablePersonInterface $person): array + { + if (!$person instanceof PersonInterface) { + $person = $this->personRepo->find($person->getId()); + } + + $connections = $this->parent->getThirdPartyConnections($person); + + if ($person instanceof PersonInterface) { + $personRS = $this->rsHelper->getPersonMeuRS($person, true); + + $connections['nfg'] = $personRS->getNfgAccessToken() !== null; + } + + return $connections; + } + + public function getSupportPerson($id): SupportPerson + { + return $this->parent->getSupportPerson($id); + } + + public function getPhoneMetadata(IdentifiablePersonInterface $person): array + { + return $this->parent->getPhoneMetadata($person); + } + + public function getInitialMessage($ticket): ?SentEmail + { + return $this->parent->getInitialMessage($ticket); + } + + public function getValidationMap(SupportPerson $person): array + { + return $this->parent->getValidationMap($person); + } +} diff --git a/src/PROCERGS/LoginCidadao/CoreBundle/Tests/Service/SupportHandlerDecoratorTest.php b/src/PROCERGS/LoginCidadao/CoreBundle/Tests/Service/SupportHandlerDecoratorTest.php new file mode 100644 index 000000000..f39fe727e --- /dev/null +++ b/src/PROCERGS/LoginCidadao/CoreBundle/Tests/Service/SupportHandlerDecoratorTest.php @@ -0,0 +1,76 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace PROCERGS\LoginCidadao\CoreBundle\Tests\Service; + +use LoginCidadao\CoreBundle\Entity\PersonRepository; +use LoginCidadao\CoreBundle\Model\IdentifiablePersonInterface; +use LoginCidadao\CoreBundle\Model\PersonInterface; +use LoginCidadao\SupportBundle\Model\SupportPerson; +use LoginCidadao\SupportBundle\Service\SupportHandlerInterface; +use PHPUnit\Framework\TestCase; +use PROCERGS\LoginCidadao\CoreBundle\Entity\PersonMeuRS; +use PROCERGS\LoginCidadao\CoreBundle\Service\SupportHandlerDecorator; +use PROCERGS\LoginCidadao\CoreBundle\Helper\MeuRSHelper; + +class SupportHandlerDecoratorTest extends TestCase +{ + public function testGetThirdPartyConnections() + { + $identifiablePerson = $this->createMock(IdentifiablePersonInterface::class); + $identifiablePerson->expects($this->once())->method('getId')->willReturn($id = 321); + + $person = $this->createMock(PersonInterface::class); + + $personRS = $this->createMock(PersonMeuRS::class); + $personRS->expects($this->once())->method('getNfgAccessToken')->willReturn('123'); + + $supportHandler = $this->createMock(SupportHandlerInterface::class); + $supportHandler->expects($this->once())->method('getThirdPartyConnections')->with($person); + + $rsHelper = $this->createMock(MeuRSHelper::class); + $rsHelper->expects($this->once())->method('getPersonMeuRS')->willReturn($personRS); + $personRepo = $this->createMock(PersonRepository::class); + $personRepo->expects($this->once())->method('find')->with($id)->willReturn($person); + + $decorator = new SupportHandlerDecorator($supportHandler, $rsHelper, $personRepo); + $decorator->getThirdPartyConnections($identifiablePerson); + } + + public function testDecorator() + { + $id = 123; + $idPerson = $this->createMock(IdentifiablePersonInterface::class); + $ticket = 'random_ticket'; + $supportPerson = $this->createMock(SupportPerson::class); + + $supportHandler = $this->createMock(SupportHandlerInterface::class); + $supportHandler->expects($this->once())->method('getSupportPerson')->with($id); + $supportHandler->expects($this->once())->method('getPhoneMetadata')->with($idPerson); + $supportHandler->expects($this->once())->method('getInitialMessage')->with($ticket); + $supportHandler->expects($this->once())->method('getValidationMap')->with($supportPerson); + + $decorator = new SupportHandlerDecorator($supportHandler, $this->getRsHelper(), $this->getPersonRepo()); + $decorator->getSupportPerson($id); + $decorator->getPhoneMetadata($idPerson); + $decorator->getInitialMessage($ticket); + $decorator->getValidationMap($supportPerson); + } + + private function getRsHelper() + { + return $this->createMock(MeuRSHelper::class); + } + + private function getPersonRepo() + { + return $this->createMock(PersonRepository::class); + } +} From 4d7fd9a890581577680200a9f34d6a62b3dd348f Mon Sep 17 00:00:00 2001 From: Guilherme Donato Date: Fri, 8 Feb 2019 13:30:47 -0200 Subject: [PATCH 40/40] added SupportHandlerInterface PROCERGS#841 --- .../SupportBundle/Service/SupportHandler.php | 2 +- .../Service/SupportHandlerInterface.php | 36 +++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) create mode 100644 src/LoginCidadao/SupportBundle/Service/SupportHandlerInterface.php diff --git a/src/LoginCidadao/SupportBundle/Service/SupportHandler.php b/src/LoginCidadao/SupportBundle/Service/SupportHandler.php index 74b8e7c98..274bf0516 100644 --- a/src/LoginCidadao/SupportBundle/Service/SupportHandler.php +++ b/src/LoginCidadao/SupportBundle/Service/SupportHandler.php @@ -22,7 +22,7 @@ use LoginCidadao\SupportBundle\Model\SupportPerson; use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; -class SupportHandler +class SupportHandler implements SupportHandlerInterface { /** @var AuthorizationCheckerInterface */ private $authChecker; diff --git a/src/LoginCidadao/SupportBundle/Service/SupportHandlerInterface.php b/src/LoginCidadao/SupportBundle/Service/SupportHandlerInterface.php new file mode 100644 index 000000000..9ebfb6199 --- /dev/null +++ b/src/LoginCidadao/SupportBundle/Service/SupportHandlerInterface.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace LoginCidadao\SupportBundle\Service; + +use libphonenumber\PhoneNumber; +use LoginCidadao\CoreBundle\Entity\PersonRepository; +use LoginCidadao\CoreBundle\Entity\SentEmail; +use LoginCidadao\CoreBundle\Entity\SentEmailRepository; +use LoginCidadao\CoreBundle\Model\IdentifiablePersonInterface; +use LoginCidadao\CoreBundle\Model\PersonInterface; +use LoginCidadao\PhoneVerificationBundle\Service\PhoneVerificationServiceInterface; +use LoginCidadao\SupportBundle\Exception\PersonNotFoundException; +use LoginCidadao\SupportBundle\Model\PersonalData; +use LoginCidadao\SupportBundle\Model\SupportPerson; +use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; + +interface SupportHandlerInterface +{ + public function getSupportPerson($id): SupportPerson; + + public function getThirdPartyConnections(IdentifiablePersonInterface $person): array; + + public function getPhoneMetadata(IdentifiablePersonInterface $person): array; + + public function getInitialMessage($ticket): ?SentEmail; + + public function getValidationMap(SupportPerson $person): array; +}