diff --git a/apps/twofactor_backupcodes/composer/composer/autoload_classmap.php b/apps/twofactor_backupcodes/composer/composer/autoload_classmap.php index a9d9d3b5d4e3a..c216b6e4e8333 100644 --- a/apps/twofactor_backupcodes/composer/composer/autoload_classmap.php +++ b/apps/twofactor_backupcodes/composer/composer/autoload_classmap.php @@ -11,6 +11,10 @@ 'OCA\\TwoFactorBackupCodes\\Controller\\SettingsController' => $baseDir . '/../lib/Controller/SettingsController.php', 'OCA\\TwoFactorBackupCodes\\Db\\BackupCode' => $baseDir . '/../lib/Db/BackupCode.php', 'OCA\\TwoFactorBackupCodes\\Db\\BackupCodeMapper' => $baseDir . '/../lib/Db/BackupCodeMapper.php', + 'OCA\\TwoFactorBackupCodes\\Event\\CodesGenerated' => $baseDir . '/../lib/Event/CodesGenerated.php', + 'OCA\\TwoFactorBackupCodes\\Listener\\ActivityPublisher' => $baseDir . '/../lib/Listener/ActivityPublisher.php', + 'OCA\\TwoFactorBackupCodes\\Listener\\IListener' => $baseDir . '/../lib/Listener/IListener.php', + 'OCA\\TwoFactorBackupCodes\\Listener\\RegistryUpdater' => $baseDir . '/../lib/Listener/RegistryUpdater.php', 'OCA\\TwoFactorBackupCodes\\Migration\\Version1002Date20170607104347' => $baseDir . '/../lib/Migration/Version1002Date20170607104347.php', 'OCA\\TwoFactorBackupCodes\\Migration\\Version1002Date20170607113030' => $baseDir . '/../lib/Migration/Version1002Date20170607113030.php', 'OCA\\TwoFactorBackupCodes\\Migration\\Version1002Date20170919123342' => $baseDir . '/../lib/Migration/Version1002Date20170919123342.php', diff --git a/apps/twofactor_backupcodes/composer/composer/autoload_static.php b/apps/twofactor_backupcodes/composer/composer/autoload_static.php index abde37697eee3..de528b08b924c 100644 --- a/apps/twofactor_backupcodes/composer/composer/autoload_static.php +++ b/apps/twofactor_backupcodes/composer/composer/autoload_static.php @@ -26,6 +26,10 @@ class ComposerStaticInitTwoFactorBackupCodes 'OCA\\TwoFactorBackupCodes\\Controller\\SettingsController' => __DIR__ . '/..' . '/../lib/Controller/SettingsController.php', 'OCA\\TwoFactorBackupCodes\\Db\\BackupCode' => __DIR__ . '/..' . '/../lib/Db/BackupCode.php', 'OCA\\TwoFactorBackupCodes\\Db\\BackupCodeMapper' => __DIR__ . '/..' . '/../lib/Db/BackupCodeMapper.php', + 'OCA\\TwoFactorBackupCodes\\Event\\CodesGenerated' => __DIR__ . '/..' . '/../lib/Event/CodesGenerated.php', + 'OCA\\TwoFactorBackupCodes\\Listener\\ActivityPublisher' => __DIR__ . '/..' . '/../lib/Listener/ActivityPublisher.php', + 'OCA\\TwoFactorBackupCodes\\Listener\\IListener' => __DIR__ . '/..' . '/../lib/Listener/IListener.php', + 'OCA\\TwoFactorBackupCodes\\Listener\\RegistryUpdater' => __DIR__ . '/..' . '/../lib/Listener/RegistryUpdater.php', 'OCA\\TwoFactorBackupCodes\\Migration\\Version1002Date20170607104347' => __DIR__ . '/..' . '/../lib/Migration/Version1002Date20170607104347.php', 'OCA\\TwoFactorBackupCodes\\Migration\\Version1002Date20170607113030' => __DIR__ . '/..' . '/../lib/Migration/Version1002Date20170607113030.php', 'OCA\\TwoFactorBackupCodes\\Migration\\Version1002Date20170919123342' => __DIR__ . '/..' . '/../lib/Migration/Version1002Date20170919123342.php', diff --git a/apps/twofactor_backupcodes/lib/AppInfo/Application.php b/apps/twofactor_backupcodes/lib/AppInfo/Application.php index 050473f7efe28..d2541d8762750 100644 --- a/apps/twofactor_backupcodes/lib/AppInfo/Application.php +++ b/apps/twofactor_backupcodes/lib/AppInfo/Application.php @@ -1,8 +1,10 @@ * * @author Joas Schilling + * @author Christoph Wurst * * @license GNU AGPL version 3 or any later version * @@ -24,11 +26,16 @@ namespace OCA\TwoFactorBackupCodes\AppInfo; use OCA\TwoFactorBackupCodes\Db\BackupCodeMapper; +use OCA\TwoFactorBackupCodes\Event\CodesGenerated; +use OCA\TwoFactorBackupCodes\Listener\ActivityPublisher; +use OCA\TwoFactorBackupCodes\Listener\IListener; +use OCA\TwoFactorBackupCodes\Listener\RegistryUpdater; use OCP\AppFramework\App; use OCP\Util; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; class Application extends App { - public function __construct () { + public function __construct() { parent::__construct('twofactor_backupcodes'); } @@ -44,6 +51,21 @@ public function register() { */ public function registerHooksAndEvents() { Util::connectHook('OC_User', 'post_deleteUser', $this, 'deleteUser'); + + $container = $this->getContainer(); + /** @var EventDispatcherInterface $eventDispatcher */ + $eventDispatcher = $container->query(EventDispatcherInterface::class); + $eventDispatcher->addListener(CodesGenerated::class, function (CodesGenerated $event) use ($container) { + /** @var IListener[] $listeners */ + $listeners = [ + $container->query(ActivityPublisher::class), + $container->query(RegistryUpdater::class), + ]; + + foreach ($listeners as $listener) { + $listener->handle($event); + } + }); } public function deleteUser($params) { diff --git a/apps/twofactor_backupcodes/lib/Event/CodesGenerated.php b/apps/twofactor_backupcodes/lib/Event/CodesGenerated.php new file mode 100644 index 0000000000000..c4ffcb3760188 --- /dev/null +++ b/apps/twofactor_backupcodes/lib/Event/CodesGenerated.php @@ -0,0 +1,46 @@ + + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OCA\TwoFactorBackupCodes\Event; + +use OCP\IUser; +use Symfony\Component\EventDispatcher\Event; + +class CodesGenerated extends Event { + + /** @var IUser */ + private $user; + + public function __construct(IUser $user) { + $this->user = $user; + } + + /** + * @return IUser + */ + public function getUser(): IUser { + return $this->user; + } + +} diff --git a/apps/twofactor_backupcodes/lib/Listener/ActivityPublisher.php b/apps/twofactor_backupcodes/lib/Listener/ActivityPublisher.php new file mode 100644 index 0000000000000..f4950f555b52c --- /dev/null +++ b/apps/twofactor_backupcodes/lib/Listener/ActivityPublisher.php @@ -0,0 +1,49 @@ +activityManager = $activityManager; + $this->logger = $logger; + } + + /** + * Push an event to the user's activity stream + */ + public function handle(Event $event) { + if ($event instanceof CodesGenerated) { + $activity = $this->activityManager->generateEvent(); + $activity->setApp('twofactor_backupcodes') + ->setType('security') + ->setAuthor($event->getUser()->getUID()) + ->setAffectedUser($event->getUser()->getUID()) + ->setSubject('codes_generated'); + try { + $this->activityManager->publish($activity); + } catch (BadMethodCallException $e) { + $this->logger->warning('could not publish backup code creation activity', ['app' => 'twofactor_backupcodes']); + $this->logger->logException($e, ['app' => 'twofactor_backupcodes']); + } + } + } + +} diff --git a/apps/twofactor_backupcodes/lib/Listener/IListener.php b/apps/twofactor_backupcodes/lib/Listener/IListener.php new file mode 100644 index 0000000000000..549c17bb8d44b --- /dev/null +++ b/apps/twofactor_backupcodes/lib/Listener/IListener.php @@ -0,0 +1,33 @@ + + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OCA\TwoFactorBackupCodes\Listener; + +use Symfony\Component\EventDispatcher\Event; + +interface IListener { + + public function handle(Event $event); + +} \ No newline at end of file diff --git a/apps/twofactor_backupcodes/lib/Listener/RegistryUpdater.php b/apps/twofactor_backupcodes/lib/Listener/RegistryUpdater.php new file mode 100644 index 0000000000000..488f08373a561 --- /dev/null +++ b/apps/twofactor_backupcodes/lib/Listener/RegistryUpdater.php @@ -0,0 +1,50 @@ + + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OCA\TwoFactorBackupCodes\Listener; + +use OCA\TwoFactorBackupCodes\Event\CodesGenerated; +use OCA\TwoFactorBackupCodes\Provider\BackupCodesProvider; +use OCP\Authentication\TwoFactorAuth\IRegistry; +use Symfony\Component\EventDispatcher\Event; + +class RegistryUpdater implements IListener { + + /** @var IRegistry */ + private $registry; + + /** @var BackupCodesProvider */ + private $provider; + + public function __construct(IRegistry $registry, BackupCodesProvider $provider) { + $this->registry = $registry; + $this->provider = $provider; + } + + public function handle(Event $event) { + if ($event instanceof CodesGenerated) { + $this->registry->enableProviderFor($this->provider, $event->getUser()); + } + } +} \ No newline at end of file diff --git a/apps/twofactor_backupcodes/lib/Service/BackupCodeStorage.php b/apps/twofactor_backupcodes/lib/Service/BackupCodeStorage.php index 2fb5fe8a6c012..74a032dcc0aa1 100644 --- a/apps/twofactor_backupcodes/lib/Service/BackupCodeStorage.php +++ b/apps/twofactor_backupcodes/lib/Service/BackupCodeStorage.php @@ -25,11 +25,13 @@ use BadMethodCallException; use OCA\TwoFactorBackupCodes\Db\BackupCode; use OCA\TwoFactorBackupCodes\Db\BackupCodeMapper; +use OCA\TwoFactorBackupCodes\Event\CodesGenerated; use OCP\Activity\IManager; use OCP\ILogger; use OCP\IUser; use OCP\Security\IHasher; use OCP\Security\ISecureRandom; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; class BackupCodeStorage { @@ -44,26 +46,17 @@ class BackupCodeStorage { /** @var ISecureRandom */ private $random; - /** @var IManager */ - private $activityManager; + /** @var EventDispatcherInterface */ + private $eventDispatcher; - /** @var ILogger */ - private $logger; - - /** - * @param BackupCodeMapper $mapper - * @param ISecureRandom $random - * @param IHasher $hasher - * @param IManager $activityManager - * @param ILogger $logger - */ - public function __construct(BackupCodeMapper $mapper, ISecureRandom $random, IHasher $hasher, - IManager $activityManager, ILogger $logger) { + public function __construct(BackupCodeMapper $mapper, + ISecureRandom $random, + IHasher $hasher, + EventDispatcherInterface $eventDispatcher) { $this->mapper = $mapper; $this->hasher = $hasher; $this->random = $random; - $this->activityManager = $activityManager; - $this->logger = $logger; + $this->eventDispatcher = $eventDispatcher; } /** @@ -89,32 +82,11 @@ public function createCodes(IUser $user, $number = 10) { $result[] = $code; } - $this->publishEvent($user, 'codes_generated'); + $this->eventDispatcher->dispatch(CodesGenerated::class, new CodesGenerated($user)); return $result; } - /** - * Push an event the user's activity stream - * - * @param IUser $user - * @param string $event - */ - private function publishEvent(IUser $user, $event) { - $activity = $this->activityManager->generateEvent(); - $activity->setApp('twofactor_backupcodes') - ->setType('security') - ->setAuthor($user->getUID()) - ->setAffectedUser($user->getUID()) - ->setSubject($event); - try { - $this->activityManager->publish($activity); - } catch (BadMethodCallException $e) { - $this->logger->warning('could not publish backup code creation activity', ['app' => 'twofactor_backupcodes']); - $this->logger->logException($e, ['app' => 'twofactor_backupcodes']); - } - } - /** * @param IUser $user * @return bool @@ -133,7 +105,7 @@ public function getBackupCodesState(IUser $user) { $total = count($codes); $used = 0; array_walk($codes, function (BackupCode $code) use (&$used) { - if (1 === (int) $code->getUsed()) { + if (1 === (int)$code->getUsed()) { $used++; } }); @@ -153,7 +125,7 @@ public function validateCode(IUser $user, $code) { $dbCodes = $this->mapper->getBackupCodes($user); foreach ($dbCodes as $dbCode) { - if (0 === (int) $dbCode->getUsed() && $this->hasher->verify($code, $dbCode->getCode())) { + if (0 === (int)$dbCode->getUsed() && $this->hasher->verify($code, $dbCode->getCode())) { $dbCode->setUsed(1); $this->mapper->update($dbCode); return true; diff --git a/apps/twofactor_backupcodes/tests/Unit/Event/CodesGeneratedTest.php b/apps/twofactor_backupcodes/tests/Unit/Event/CodesGeneratedTest.php new file mode 100644 index 0000000000000..fa363dccc9639 --- /dev/null +++ b/apps/twofactor_backupcodes/tests/Unit/Event/CodesGeneratedTest.php @@ -0,0 +1,40 @@ + + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OCA\TwoFactorBackupCodes\Tests\Unit\Event; + +use OCA\TwoFactorBackupCodes\Event\CodesGenerated; +use OCP\IUser; +use Test\TestCase; + +class CodesGeneratedTest extends TestCase { + + public function testCodeGeneratedEvent() { + $user = $this->createMock(IUser::class); + + $event = new CodesGenerated($user); + + $this->assertSame($user, $event->getUser()); + } +} diff --git a/apps/twofactor_backupcodes/tests/Unit/Listener/ActivityPublisherTest.php b/apps/twofactor_backupcodes/tests/Unit/Listener/ActivityPublisherTest.php new file mode 100644 index 0000000000000..2ec0758986a0e --- /dev/null +++ b/apps/twofactor_backupcodes/tests/Unit/Listener/ActivityPublisherTest.php @@ -0,0 +1,77 @@ +activityManager = $this->createMock(IManager::class); + $this->logger = $this->createMock(ILogger::class); + + $this->listener = new ActivityPublisher($this->activityManager, $this->logger); + } + + public function testHandleGenericEvent() { + $event = $this->createMock(Event::class); + $this->activityManager->expects($this->never()) + ->method('publish'); + + $this->listener->handle($event); + } + + public function testHandleCodesGeneratedEvent() { + $user = $this->createMock(IUser::class); + $event = new CodesGenerated($user); + $activityEvent = $this->createMock(IEvent::class); + $this->activityManager->expects($this->once()) + ->method('generateEvent') + ->will($this->returnValue($activityEvent)); + $activityEvent->expects($this->once()) + ->method('setApp') + ->with('twofactor_backupcodes') + ->will($this->returnSelf()); + $activityEvent->expects($this->once()) + ->method('setType') + ->with('security') + ->will($this->returnSelf()); + $activityEvent->expects($this->once()) + ->method('setAuthor') + ->with('fritz') + ->will($this->returnSelf()); + $activityEvent->expects($this->once()) + ->method('setAffectedUser') + ->with('fritz') + ->will($this->returnSelf()); + $this->activityManager->expects($this->once()) + ->method('publish') + ->will($this->returnValue($activityEvent)); + + $this->listener->handle($event); + } + +} diff --git a/apps/twofactor_backupcodes/tests/Unit/Listener/RegistryUpdaterTest.php b/apps/twofactor_backupcodes/tests/Unit/Listener/RegistryUpdaterTest.php new file mode 100644 index 0000000000000..e159c4fda5b46 --- /dev/null +++ b/apps/twofactor_backupcodes/tests/Unit/Listener/RegistryUpdaterTest.php @@ -0,0 +1,59 @@ +registry = $this->createMock(IRegistry::class); + $this->provider = $this->createMock(BackupCodesProvider::class); + + $this->listener = new RegistryUpdater($this->registry, $this->provider); + } + + public function testHandleGenericEvent() { + $event = $this->createMock(Event::class); + $this->registry->expects($this->never()) + ->method('enableProviderFor'); + + $this->listener->handle($event); + } + + public function testHandleCodesGeneratedEvent() { + $user = $this->createMock(IUser::class); + $event = new CodesGenerated($user); + $this->registry->expects($this->once()) + ->method('enableProviderFor') + ->with( + $this->provider, + $user + ); + + $this->listener->handle($event); + } +} diff --git a/apps/twofactor_backupcodes/tests/Unit/Service/BackupCodeStorageTest.php b/apps/twofactor_backupcodes/tests/Unit/Service/BackupCodeStorageTest.php index d2387bd6ccd54..bef504f16e723 100644 --- a/apps/twofactor_backupcodes/tests/Unit/Service/BackupCodeStorageTest.php +++ b/apps/twofactor_backupcodes/tests/Unit/Service/BackupCodeStorageTest.php @@ -24,14 +24,14 @@ use OCA\TwoFactorBackupCodes\Db\BackupCode; use OCA\TwoFactorBackupCodes\Db\BackupCodeMapper; +use OCA\TwoFactorBackupCodes\Event\CodesGenerated; use OCA\TwoFactorBackupCodes\Service\BackupCodeStorage; -use OCP\Activity\IEvent; -use OCP\Activity\IManager; use OCP\ILogger; use OCP\IUser; use OCP\Security\IHasher; use OCP\Security\ISecureRandom; use PHPUnit_Framework_MockObject_MockObject; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Test\TestCase; class BackupCodeStorageTest extends TestCase { @@ -45,11 +45,8 @@ class BackupCodeStorageTest extends TestCase { /** @var IHasher|PHPUnit_Framework_MockObject_MockObject */ private $hasher; - /** @var IManager|PHPUnit_Framework_MockObject_MockObject */ - private $activityManager; - - /** @var ILogger|PHPUnit_Framework_MockObject_MockObject */ - private $logger; + /** @var EventDispatcherInterface|PHPUnit_Framework_MockObject_MockObject */ + private $eventDispatcher; /** @var BackupCodeStorage */ private $storage; @@ -60,20 +57,15 @@ protected function setUp() { $this->mapper = $this->createMock(BackupCodeMapper::class); $this->random = $this->createMock(ISecureRandom::class); $this->hasher = $this->createMock(IHasher::class); - $this->activityManager = $this->createMock(IManager::class); - $this->logger = $this->createMock(ILogger::class); + $this->eventDispatcher = $this->createMock(EventDispatcherInterface::class); - $this->storage = new BackupCodeStorage($this->mapper, $this->random, $this->hasher, $this->activityManager, $this->logger); + $this->storage = new BackupCodeStorage($this->mapper, $this->random, $this->hasher, $this->eventDispatcher); } public function testCreateCodes() { $user = $this->createMock(IUser::class); $number = 5; - $event = $this->createMock(IEvent::class); - - $user->expects($this->any()) - ->method('getUID') - ->will($this->returnValue('fritz')); + $user->method('getUID')->willReturn('fritz'); $this->random->expects($this->exactly($number)) ->method('generate') ->with(16, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789') @@ -89,28 +81,12 @@ public function testCreateCodes() { $this->mapper->expects($this->exactly($number)) ->method('insert') ->with($this->equalTo($row)); - $this->activityManager->expects($this->once()) - ->method('generateEvent') - ->will($this->returnValue($event)); - $event->expects($this->once()) - ->method('setApp') - ->with('twofactor_backupcodes') - ->will($this->returnSelf()); - $event->expects($this->once()) - ->method('setType') - ->with('security') - ->will($this->returnSelf()); - $event->expects($this->once()) - ->method('setAuthor') - ->with('fritz') - ->will($this->returnSelf()); - $event->expects($this->once()) - ->method('setAffectedUser') - ->with('fritz') - ->will($this->returnSelf()); - $this->activityManager->expects($this->once()) - ->method('publish') - ->will($this->returnValue($event)); + $this->eventDispatcher->expects($this->once()) + ->method('dispatch') + ->with( + $this->equalTo(CodesGenerated::class), + $this->equalTo(new CodesGenerated($user)) + ); $codes = $this->storage->createCodes($user, $number); $this->assertCount($number, $codes);