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:
Родитель
3878a822e7
Коммит
9259ca5622
|
@ -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);
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче