Refactor app to use the event system to act on state changes

Signed-off-by: Christoph Wurst <christoph@winzerhof-wurst.at>
This commit is contained in:
Christoph Wurst 2018-07-31 08:05:20 +02:00
Родитель 3878a822e7
Коммит 9259ca5622
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: CC42AC2A7F0E56D8
8 изменённых файлов: 343 добавлений и 61 удалений

Просмотреть файл

@ -0,0 +1,43 @@
<?php
declare(strict_types=1);
/**
* Nextcloud - U2F 2FA
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @copyright Christoph Wurst 2018
*/
namespace OCA\TwoFactorU2F\AppInfo;
use OCA\TwoFactorU2F\Event\StateChanged;
use OCA\TwoFactorU2F\Listener\IListener;
use OCA\TwoFactorU2F\Listener\StateChangeActivity;
use OCP\AppFramework\App;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
class Application extends App {
public function __construct(array $urlParams = []) {
parent::__construct('twofactor_u2f', $urlParams);
$container = $this->getContainer();
/** @var EventDispatcherInterface $eventDispatcher */
$eventDispatcher = $container->getServer()->getEventDispatcher();
$eventDispatcher->addListener(StateChanged::class, function (StateChanged $event) use ($container) {
/** @var IListener[] $listeners */
$listeners = [
$container->query(StateChangeActivity::class),
];
foreach ($listeners as $listener) {
$listener->handle($event);
}
});
}
}

Просмотреть файл

@ -0,0 +1,47 @@
<?php
declare(strict_types=1);
/**
* Nextcloud - U2F 2FA
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @copyright Christoph Wurst 2018
*/
namespace OCA\TwoFactorU2F\Event;
use OCP\IUser;
use Symfony\Component\EventDispatcher\Event;
class StateChanged extends Event {
/** @var IUser */
private $user;
/** @var bool */
private $enabled;
public function __construct(IUser $user, bool $enabled) {
$this->user = $user;
$this->enabled = $enabled;
}
/**
* @return IUser
*/
public function getUser(): IUser {
return $this->user;
}
/**
* @return bool
*/
public function isEnabled(): bool {
return $this->enabled;
}
}

Просмотреть файл

@ -0,0 +1,23 @@
<?php
declare(strict_types=1);
/**
* Nextcloud - U2F 2FA
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @copyright Christoph Wurst 2018
*/
namespace OCA\TwoFactorU2F\Listener;
use Symfony\Component\EventDispatcher\Event;
interface IListener {
public function handle(Event $event);
}

Просмотреть файл

@ -0,0 +1,43 @@
<?php
declare(strict_types=1);
/**
* Nextcloud - U2F 2FA
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @copyright Christoph Wurst 2018
*/
namespace OCA\TwoFactorU2F\Listener;
use OCA\TwoFactorU2F\Event\StateChanged;
use OCP\Activity\IManager;
use Symfony\Component\EventDispatcher\Event;
class StateChangeActivity implements IListener {
/** @var IManager */
private $activityManager;
public function __construct(IManager $activityManager) {
$this->activityManager = $activityManager;
}
public function handle(Event $event) {
if ($event instanceof StateChanged) {
$subject = $event->isEnabled() ? 'u2f_device_added' : 'u2f_device_removed';
$activity = $this->activityManager->generateEvent();
$activity->setApp('twofactor_u2f')
->setType('security')
->setAuthor($event->getUser()->getUID())
->setAffectedUser($event->getUser()->getUID())
->setSubject($subject);
$this->activityManager->publish($activity);
}
}
}

Просмотреть файл

@ -19,11 +19,12 @@ require_once(__DIR__ . '/../../vendor/yubico/u2flib-server/src/u2flib_server/U2F
use InvalidArgumentException;
use OCA\TwoFactorU2F\Db\Registration;
use OCA\TwoFactorU2F\Db\RegistrationMapper;
use OCP\Activity\IManager;
use OCA\TwoFactorU2F\Event\StateChanged;
use OCP\ILogger;
use OCP\IRequest;
use OCP\ISession;
use OCP\IUser;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use u2flib_server\Error;
use u2flib_server\U2F;
@ -41,15 +42,19 @@ class U2FManager {
/** @var IRequest */
private $request;
/** @var IManager */
private $activityManager;
/** @var EventDispatcherInterface */
private $eventDispatcher;
public function __construct(RegistrationMapper $mapper, ISession $session, ILogger $logger, IRequest $request, IManager $activityManager) {
public function __construct(RegistrationMapper $mapper,
ISession $session,
ILogger $logger,
IRequest $request,
EventDispatcherInterface $eventDispatcher) {
$this->mapper = $mapper;
$this->session = $session;
$this->logger = $logger;
$this->request = $request;
$this->activityManager = $activityManager;
$this->eventDispatcher = $eventDispatcher;
}
private function getU2f(): U2F {
@ -78,7 +83,7 @@ class U2FManager {
public function removeDevice(IUser $user, int $id) {
$reg = $this->mapper->findRegistration($user, $id);
$this->mapper->delete($reg);
$this->publishEvent($user, 'u2f_device_removed');
$this->eventDispatcher->dispatch(StateChanged::class, new StateChanged($user, false));
}
public function startRegistration(IUser $user): array {
@ -117,7 +122,7 @@ class U2FManager {
$registration->setCounter($reg->counter);
$registration->setName($name);
$this->mapper->insert($registration);
$this->publishEvent($user, 'u2f_device_added');
$this->eventDispatcher->dispatch(StateChanged::class, new StateChanged($user, true));
$this->logger->debug(json_encode($reg));
@ -127,19 +132,6 @@ class U2FManager {
];
}
/**
* Push an U2F event the user's activity stream
*/
private function publishEvent(IUser $user, string $event) {
$activity = $this->activityManager->generateEvent();
$activity->setApp('twofactor_u2f')
->setType('security')
->setAuthor($user->getUID())
->setAffectedUser($user->getUID())
->setSubject($event);
$this->activityManager->publish($activity);
}
public function startAuthenticate(IUser $user): array {
$u2f = $this->getU2f();
$reqs = $u2f->getAuthenticateData($this->getRegistrations($user));

Просмотреть файл

@ -0,0 +1,41 @@
<?php
declare(strict_types=1);
/**
* Nextcloud - U2F 2FA
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @copyright Christoph Wurst 2018
*/
namespace OCA\TwoFactorU2F\Tests\Unit\Event;
use OCA\TwoFactorU2F\Event\StateChanged;
use OCP\IUser;
use PHPUnit\Framework\TestCase;
class StateChangedTest extends TestCase {
public function testEnabledState() {
$user = $this->createMock(IUser::class);
$event = new StateChanged($user, true);
$this->assertTrue($event->isEnabled());
$this->assertSame($user, $event->getUser());
}
public function testDisabledState() {
$user = $this->createMock(IUser::class);
$event = new StateChanged($user, false);
$this->assertFalse($event->isEnabled());
$this->assertSame($user, $event->getUser());
}
}

Просмотреть файл

@ -0,0 +1,117 @@
<?php
/**
* Created by PhpStorm.
* User: christoph
* Date: 31.07.18
* Time: 07:48
*/
namespace OCA\TwoFactorU2F\Tests\Unit\Listener;
use OCA\TwoFactorU2F\Event\StateChanged;
use OCA\TwoFactorU2F\Listener\StateChangeActivity;
use OCP\Activity\IEvent;
use OCP\Activity\IManager;
use OCP\IUser;
use PHPUnit\Framework\TestCase;
use PHPUnit_Framework_MockObject_MockObject;
use Symfony\Component\EventDispatcher\Event;
class StateChangeActivityTest extends TestCase {
/** @var IManager|PHPUnit_Framework_MockObject_MockObject */
private $activityManager;
/** @var StateChangeActivity */
private $listener;
protected function setUp() {
parent::setUp();
$this->activityManager = $this->createMock(IManager::class);
$this->listener = new StateChangeActivity($this->activityManager);
}
public function testHandleGenericEvent() {
$event = $this->createMock(Event::class);
$this->activityManager->expects($this->never())
->method('publish');
$this->listener->handle($event);
}
public function testHandleEnableEvent() {
$user = $this->createMock(IUser::class);
$event = new StateChanged($user, true);
$activityEvent = $this->createMock(IEvent::class);
$this->activityManager->expects($this->once())
->method('generateEvent')
->willReturn($activityEvent);
$activityEvent->expects($this->once())
->method('setApp')
->with($this->equalTo('twofactor_u2f'))
->willReturnSelf();
$activityEvent->expects($this->once())
->method('setType')
->with($this->equalTo('security'))
->willReturnSelf();
$user->expects($this->any())
->method('getUID')
->willReturn('ursula');
$activityEvent->expects($this->once())
->method('setAuthor')
->with($this->equalTo('ursula'))
->willReturnSelf();
$activityEvent->expects($this->once())
->method('setAffectedUser')
->with($this->equalTo('ursula'))
->willReturnSelf();
$activityEvent->expects($this->once())
->method('setSubject')
->with($this->equalTo('u2f_device_added'))
->willReturnSelf();
$this->activityManager->expects($this->once())
->method('publish')
->with($this->equalTo($activityEvent));
$this->listener->handle($event);
}
public function testHandleDisableEvent() {
$user = $this->createMock(IUser::class);
$event = new StateChanged($user, false);
$activityEvent = $this->createMock(IEvent::class);
$this->activityManager->expects($this->once())
->method('generateEvent')
->willReturn($activityEvent);
$activityEvent->expects($this->once())
->method('setApp')
->with($this->equalTo('twofactor_u2f'))
->willReturnSelf();
$activityEvent->expects($this->once())
->method('setType')
->with($this->equalTo('security'))
->willReturnSelf();
$user->expects($this->any())
->method('getUID')
->willReturn('ursula');
$activityEvent->expects($this->once())
->method('setAuthor')
->with($this->equalTo('ursula'))
->willReturnSelf();
$activityEvent->expects($this->once())
->method('setAffectedUser')
->with($this->equalTo('ursula'))
->willReturnSelf();
$activityEvent->expects($this->once())
->method('setSubject')
->with($this->equalTo('u2f_device_removed'))
->willReturnSelf();
$this->activityManager->expects($this->once())
->method('publish')
->with($this->equalTo($activityEvent));
$this->listener->handle($event);
}
}

Просмотреть файл

@ -14,15 +14,16 @@ namespace OCA\TwoFactorU2F\Tests\Unit\Service;
use OCA\TwoFactorU2F\Db\Registration;
use OCA\TwoFactorU2F\Db\RegistrationMapper;
use OCA\TwoFactorU2F\Event\StateChanged;
use OCA\TwoFactorU2F\Service\U2FManager;
use OCP\Activity\IEvent;
use OCP\Activity\IManager;
use OCP\ILogger;
use OCP\IRequest;
use OCP\ISession;
use OCP\IUser;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
class U2FManagerTest extends TestCase {
@ -38,12 +39,12 @@ class U2FManagerTest extends TestCase {
/** @var IRequest|MockObject */
private $request;
/** @var IManager|MockObject */
private $activityManager;
/** @var U2FManager */
private $manager;
/** @var EventDispatcherInterface */
private $eventDispatcher;
protected function setUp() {
parent::setUp();
@ -51,9 +52,9 @@ class U2FManagerTest extends TestCase {
$this->session = $this->createMock(ISession::class);
$this->logger = $this->createMock(ILogger::class);
$this->request = $this->createMock(IRequest::class);
$this->activityManager = $this->createMock(IManager::class);
$this->eventDispatcher = $this->createMock(EventDispatcherInterface::class);
$this->manager = new U2FManager($this->mapper, $this->session, $this->logger, $this->request, $this->activityManager);
$this->manager = new U2FManager($this->mapper, $this->session, $this->logger, $this->request, $this->eventDispatcher);
}
/**
@ -88,9 +89,7 @@ class U2FManagerTest extends TestCase {
public function testDisableU2F() {
$user = $this->createMock(IUser::class);
$event = $this->createMock(IEvent::class);
$reg = $this->createMock(Registration::class);
$this->mapper->expects($this->once())
->method('findRegistration')
->with($user, 13)
@ -98,35 +97,12 @@ class U2FManagerTest extends TestCase {
$this->mapper->expects($this->once())
->method('delete')
->with($reg);
$this->activityManager->expects($this->once())
->method('generateEvent')
->willReturn($event);
$event->expects($this->once())
->method('setApp')
->with($this->equalTo('twofactor_u2f'))
->willReturnSelf();
$event->expects($this->once())
->method('setType')
->with($this->equalTo('security'))
->willReturnSelf();
$user->expects($this->any())
->method('getUID')
->willReturn('ursula');
$event->expects($this->once())
->method('setAuthor')
->with($this->equalTo('ursula'))
->willReturnSelf();
$event->expects($this->once())
->method('setAffectedUser')
->with($this->equalTo('ursula'))
->willReturnSelf();
$event->expects($this->once())
->method('setSubject')
->with($this->equalTo('u2f_device_removed'))
->willReturnSelf();
$this->activityManager->expects($this->once())
->method('publish')
->with($this->equalTo($event));
$this->eventDispatcher->expects($this->once())
->method('dispatch')
->with(
$this->equalTo(StateChanged::class),
$this->equalTo(new StateChanged($user, false))
);
$this->manager->removeDevice($user, 13);
}