From 69a4ca70afaf67c7b35623e407eac4f47b59cce0 Mon Sep 17 00:00:00 2001 From: Christoph Wurst Date: Tue, 13 Dec 2016 14:37:08 +0100 Subject: [PATCH 1/2] publish activities when a device is added/removed Signed-off-by: Christoph Wurst --- appinfo/info.xml | 9 +++ lib/Activity/Provider.php | 68 ++++++++++++++++ lib/Activity/Setting.php | 65 ++++++++++++++++ lib/Service/U2FManager.php | 27 ++++++- nbproject/project.properties | 17 +++- phpunit.integration.xml | 7 -- phpunit.xml | 7 -- tests/phpunit.xml | 2 +- tests/unit/Activity/ProviderTest.php | 112 +++++++++++++++++++++++++++ tests/unit/Activity/SettingTest.php | 58 ++++++++++++++ 10 files changed, 353 insertions(+), 19 deletions(-) create mode 100644 lib/Activity/Provider.php create mode 100644 lib/Activity/Setting.php delete mode 100644 phpunit.integration.xml delete mode 100644 phpunit.xml create mode 100644 tests/unit/Activity/ProviderTest.php create mode 100644 tests/unit/Activity/SettingTest.php diff --git a/appinfo/info.xml b/appinfo/info.xml index 9616045..eace7fe 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -24,4 +24,13 @@ OCA\TwoFactorU2F\Provider\U2FProvider + + + + OCA\TwoFactorU2F\Activity\Setting + + + OCA\TwoFactorU2F\Activity\Provider + + diff --git a/lib/Activity/Provider.php b/lib/Activity/Provider.php new file mode 100644 index 0000000..8e7abfe --- /dev/null +++ b/lib/Activity/Provider.php @@ -0,0 +1,68 @@ + + * @copyright Copyright (c) 2016 Christoph Wurst + * + * Two-factor U2F + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * 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, version 3, + * along with this program. If not, see + * + */ + +namespace OCA\TwoFactorU2F\Activity; + +use InvalidArgumentException; +use OCP\Activity\IEvent; +use OCP\Activity\IProvider; +use OCP\ILogger; +use OCP\IURLGenerator; +use OCP\L10N\IFactory as L10nFactory; + +class Provider implements IProvider { + + /** @var L10nFactory */ + private $l10n; + + /** @var IURLGenerator */ + private $urlGenerator; + + /** @var ILogger */ + private $logger; + + public function __construct(L10nFactory $l10n, IURLGenerator $urlGenerator, ILogger $logger) { + $this->logger = $logger; + $this->urlGenerator = $urlGenerator; + $this->l10n = $l10n; + } + + public function parse($language, IEvent $event, IEvent $previousEvent = null) { + if ($event->getApp() !== 'twofactor_u2f') { + throw new InvalidArgumentException(); + } + + $l = $this->l10n->get('twofactor_u2f', $language); + + $event->setIcon($this->urlGenerator->getAbsoluteURL($this->urlGenerator->imagePath('core', 'actions/password.svg'))); + switch ($event->getSubject()) { + case 'u2f_device_added': + $event->setSubject($l->t('You added an U2F hardware token')); + break; + case 'u2f_device_removed': + $event->setSubject($l->t('You removed an U2F hardware token')); + break; + } + return $event; + } + +} diff --git a/lib/Activity/Setting.php b/lib/Activity/Setting.php new file mode 100644 index 0000000..be0db55 --- /dev/null +++ b/lib/Activity/Setting.php @@ -0,0 +1,65 @@ + + * @copyright Copyright (c) 2016 Christoph Wurst + * + * Two-factor U2F + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * 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, version 3, + * along with this program. If not, see + * + */ + +namespace OCA\TwoFactorU2F\Activity; + +use OCP\Activity\ISetting; +use OCP\IL10N; + +class Setting implements ISetting { + + /** @var IL10N */ + private $l10n; + + public function __construct(IL10N $l10n) { + $this->l10n = $l10n; + } + + public function canChangeMail() { + return false; + } + + public function canChangeStream() { + return false; + } + + public function getIdentifier() { + return 'twofactor_u2f'; + } + + public function getName() { + return $this->l10n->t('U2F device'); + } + + public function getPriority() { + return 30; + } + + public function isDefaultEnabledMail() { + return true; + } + + public function isDefaultEnabledStream() { + return true; + } + +} diff --git a/lib/Service/U2FManager.php b/lib/Service/U2FManager.php index ac586cb..7b1c419 100644 --- a/lib/Service/U2FManager.php +++ b/lib/Service/U2FManager.php @@ -15,13 +15,12 @@ namespace OCA\TwoFactorU2F\Service; require_once(__DIR__ . '/../../vendor/yubico/u2flib-server/src/u2flib_server/U2F.php'); use InvalidArgumentException; -use OC; use OCA\TwoFactorU2F\Db\Registration; use OCA\TwoFactorU2F\Db\RegistrationMapper; +use OCP\Activity\IManager; use OCP\ILogger; use OCP\IRequest; use OCP\ISession; -use OCP\IURLGenerator; use OCP\IUser; use u2flib_server\Error; use u2flib_server\U2F; @@ -40,11 +39,15 @@ class U2FManager { /** @var IRequest */ private $request; - public function __construct(RegistrationMapper $mapper, ISession $session, ILogger $logger, IRequest $request) { + /** @var IManager */ + private $activityManager; + + public function __construct(RegistrationMapper $mapper, ISession $session, ILogger $logger, IRequest $request, IManager $activityManager) { $this->mapper = $mapper; $this->session = $session; $this->logger = $logger; $this->request = $request; + $this->activityManager = $activityManager; } private function getU2f() { @@ -69,6 +72,7 @@ class U2FManager { // TODO: use single query instead foreach ($this->mapper->findRegistrations($user) as $registration) { $this->mapper->delete($registration); + $this->publishEvent($user, 'u2f_device_removed'); } } @@ -107,10 +111,27 @@ class U2FManager { $registration->setCertificate($reg->certificate); $registration->setCounter($reg->counter); $this->mapper->insert($registration); + $this->publishEvent($user, 'u2f_device_added'); $this->logger->debug(json_encode($reg)); } + /** + * Push an U2F 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_u2f') + ->setType('twofactor') + ->setAuthor($user->getUID()) + ->setAffectedUser($user->getUID()); + $activity->setSubject($event); + $this->activityManager->publish($activity); + } + public function startAuthenticate(IUser $user) { $u2f = $this->getU2f(); $reqs = $u2f->getAuthenticateData($this->getRegistrations($user)); diff --git a/nbproject/project.properties b/nbproject/project.properties index d37ef95..e941663 100644 --- a/nbproject/project.properties +++ b/nbproject/project.properties @@ -1,7 +1,22 @@ +auxiliary.org-netbeans-modules-php-phpunit.bootstrap_2e_create_2e_tests=false +auxiliary.org-netbeans-modules-php-phpunit.bootstrap_2e_enabled=false +auxiliary.org-netbeans-modules-php-phpunit.bootstrap_2e_path= +auxiliary.org-netbeans-modules-php-phpunit.configuration_2e_enabled=true +auxiliary.org-netbeans-modules-php-phpunit.configuration_2e_path=tests/phpunit.xml +auxiliary.org-netbeans-modules-php-phpunit.customSuite_2e_enabled=false +auxiliary.org-netbeans-modules-php-phpunit.customSuite_2e_path= +auxiliary.org-netbeans-modules-php-phpunit.phpUnit_2e_enabled=false +auxiliary.org-netbeans-modules-php-phpunit.phpUnit_2e_path= +auxiliary.org-netbeans-modules-php-phpunit.test_2e_groups_2e_ask=false +auxiliary.org-netbeans-modules-php-phpunit.test_2e_run_2e_all=false +auxiliary.org-netbeans-modules-php-phpunit.test_2e_run_2e_phpunit_2e_only=false +file.reference.twofactor_u2f-tests=tests include.path=${php.global.include.path} -php.version=PHP_54 +php.version=PHP_56 source.encoding=UTF-8 src.dir=. tags.asp=false tags.short=false +test.src.dir=${file.reference.twofactor_u2f-tests} +testing.providers=PhpUnit web.root=. diff --git a/phpunit.integration.xml b/phpunit.integration.xml deleted file mode 100644 index c899f23..0000000 --- a/phpunit.integration.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - ./tests/integration - - - \ No newline at end of file diff --git a/phpunit.xml b/phpunit.xml deleted file mode 100644 index 959875b..0000000 --- a/phpunit.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - ./tests/unit - - - \ No newline at end of file diff --git a/tests/phpunit.xml b/tests/phpunit.xml index ae836a2..78154ef 100644 --- a/tests/phpunit.xml +++ b/tests/phpunit.xml @@ -6,7 +6,7 @@ timeoutForLargeTests="900" > - . + . diff --git a/tests/unit/Activity/ProviderTest.php b/tests/unit/Activity/ProviderTest.php new file mode 100644 index 0000000..c5e6722 --- /dev/null +++ b/tests/unit/Activity/ProviderTest.php @@ -0,0 +1,112 @@ + + * @copyright Copyright (c) 2016 Christoph Wurst + * + * Two-factor U2F + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * 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, version 3, + * along with this program. If not, see + * + */ + +namespace OCA\TwoFactorU2F\Test\Unit\Activity; + +use InvalidArgumentException; +use OCA\TwoFactorU2F\Activity\Provider; +use OCP\Activity\IEvent; +use OCP\IL10N; +use OCP\ILogger; +use OCP\IURLGenerator; +use OCP\L10N\IFactory; +use Test\TestCase; + +class ProviderTest extends TestCase { + + private $l10n; + private $urlGenerator; + private $logger; + + /** @var Provider */ + private $provider; + + protected function setUp() { + parent::setUp(); + + $this->l10n = $this->createMock(IFactory::class); + $this->urlGenerator = $this->createMock(IURLGenerator::class); + $this->logger = $this->createMock(ILogger::class); + + $this->provider = new Provider($this->l10n, $this->urlGenerator, $this->logger); + } + + public function testParseUnrelated() { + $lang = 'ru'; + $event = $this->createMock(IEvent::class); + $event->expects($this->once()) + ->method('getApp') + ->will($this->returnValue('comments')); + $this->setExpectedException(InvalidArgumentException::class); + + $this->provider->parse($lang, $event); + } + + public function subjectData() { + return [ + ['u2f_device_added'], + ['u2f_device_removed'], + [null], + ]; + } + + /** + * @dataProvider subjectData + */ + public function testParse($subject) { + $lang = 'ru'; + $event = $this->createMock(IEvent::class); + $l = $this->createMock(IL10N::class); + + $event->expects($this->once()) + ->method('getApp') + ->will($this->returnValue('twofactor_u2f')); + $this->l10n->expects($this->once()) + ->method('get') + ->with('twofactor_u2f', $lang) + ->will($this->returnValue($l)); + $this->urlGenerator->expects($this->once()) + ->method('imagePath') + ->with('core', 'actions/password.svg') + ->will($this->returnValue('path/to/image')); + $this->urlGenerator->expects($this->once()) + ->method('getAbsoluteURL') + ->with('path/to/image') + ->will($this->returnValue('absolute/path/to/image')); + $event->expects($this->once()) + ->method('setIcon') + ->with('absolute/path/to/image'); + $event->expects($this->once()) + ->method('getSubject') + ->will($this->returnValue($subject)); + if (is_null($subject)) { + $event->expects($this->never()) + ->method('setSubject'); + } else { + $event->expects($this->once()) + ->method('setSubject'); + } + + $this->provider->parse($lang, $event); + } + +} diff --git a/tests/unit/Activity/SettingTest.php b/tests/unit/Activity/SettingTest.php new file mode 100644 index 0000000..00ccaf4 --- /dev/null +++ b/tests/unit/Activity/SettingTest.php @@ -0,0 +1,58 @@ + + * @copyright Copyright (c) 2016 Christoph Wurst + * + * Two-factor U2F + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * 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, version 3, + * along with this program. If not, see + * + */ + +namespace OCA\TwoFactorU2F\Test\Unit\Activity; + +use OCA\TwoFactorU2F\Activity\Setting; +use OCP\IL10N; +use Test\TestCase; + +class SettingTest extends TestCase { + + private $l10n; + + /** @var Setting */ + private $setting; + + protected function setUp() { + parent::setUp(); + + $this->l10n = $this->createMock(IL10N::class); + + $this->setting = new Setting($this->l10n); + } + + public function testAll() { + $this->assertEquals(false, $this->setting->canChangeMail()); + $this->assertEquals(false, $this->setting->canChangeStream()); + $this->assertEquals('twofactor_u2f', $this->setting->getIdentifier()); + $this->l10n->expects($this->once()) + ->method('t') + ->with('U2F device') + ->will($this->returnValue('U2F Gerät')); + $this->assertEquals('U2F Gerät', $this->setting->getName()); + $this->assertEquals(30, $this->setting->getPriority()); + $this->assertEquals(true, $this->setting->isDefaultEnabledMail()); + $this->assertEquals(true, $this->setting->isDefaultEnabledStream()); + } + +} From 233c56cfe964375d602e58fa3d5ba11eb7a993ee Mon Sep 17 00:00:00 2001 From: Christoph Wurst Date: Mon, 19 Dec 2016 14:41:50 +0100 Subject: [PATCH 2/2] make Joas happy by adding some comments :-) --- lib/Activity/Provider.php | 12 ++++++++++++ lib/Activity/Setting.php | 24 ++++++++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/lib/Activity/Provider.php b/lib/Activity/Provider.php index 8e7abfe..b08ba99 100644 --- a/lib/Activity/Provider.php +++ b/lib/Activity/Provider.php @@ -40,12 +40,24 @@ class Provider implements IProvider { /** @var ILogger */ private $logger; + /** + * @param L10nFactory $l10n + * @param IURLGenerator $urlGenerator + * @param ILogger $logger + */ public function __construct(L10nFactory $l10n, IURLGenerator $urlGenerator, ILogger $logger) { $this->logger = $logger; $this->urlGenerator = $urlGenerator; $this->l10n = $l10n; } + /** + * @param string $language + * @param IEvent $event + * @param IEvent $previousEvent + * @return IEvent + * @throws InvalidArgumentException + */ public function parse($language, IEvent $event, IEvent $previousEvent = null) { if ($event->getApp() !== 'twofactor_u2f') { throw new InvalidArgumentException(); diff --git a/lib/Activity/Setting.php b/lib/Activity/Setting.php index be0db55..8501d2d 100644 --- a/lib/Activity/Setting.php +++ b/lib/Activity/Setting.php @@ -30,34 +30,58 @@ class Setting implements ISetting { /** @var IL10N */ private $l10n; + /** + * @param IL10N $l10n + */ public function __construct(IL10N $l10n) { $this->l10n = $l10n; } + /** + * @return boolean + */ public function canChangeMail() { return false; } + /** + * @return boolean + */ public function canChangeStream() { return false; } + /** + * @return string + */ public function getIdentifier() { return 'twofactor_u2f'; } + /** + * @return string + */ public function getName() { return $this->l10n->t('U2F device'); } + /** + * @return int + */ public function getPriority() { return 30; } + /** + * @return boolean + */ public function isDefaultEnabledMail() { return true; } + /** + * @return boolean + */ public function isDefaultEnabledStream() { return true; }