diff options
author | Christoph Wurst <ChristophWurst@users.noreply.github.com> | 2017-01-09 22:08:16 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-01-09 22:08:16 +0300 |
commit | 67e1412d14b791e2f283dd15e80ee3a9532704bd (patch) | |
tree | 1a08c259e5901d64940e42b3c9ac13ed02df36f3 | |
parent | 2f1151372dd2c01b11eb07bfdd7f724766c8f33f (diff) | |
parent | 290994a0cb2105f8f22be2a16796a538dfa7f0cd (diff) |
Merge pull request #96 from nextcloud/activities
Add simple activity publishing functionality
-rw-r--r-- | appinfo/app.php | 1 | ||||
-rw-r--r-- | appinfo/info.xml | 8 | ||||
-rw-r--r-- | lib/Activity/Provider.php | 68 | ||||
-rw-r--r-- | lib/Activity/Setting.php | 65 | ||||
-rw-r--r-- | lib/AppInfo/Application.php | 12 | ||||
-rw-r--r-- | lib/Controller/SettingsController.php | 1 | ||||
-rw-r--r-- | lib/Provider/TotpProvider.php | 128 | ||||
-rw-r--r-- | lib/Service/Totp.php | 143 | ||||
-rw-r--r-- | tests/unit/Activity/ProviderTest.php | 112 | ||||
-rw-r--r-- | tests/unit/Activity/SettingTest.php | 58 | ||||
-rw-r--r-- | tests/unit/Controller/SettingsControllerTest.php | 27 |
11 files changed, 482 insertions, 141 deletions
diff --git a/appinfo/app.php b/appinfo/app.php index b45f697..cf8ef51 100644 --- a/appinfo/app.php +++ b/appinfo/app.php @@ -18,7 +18,6 @@ * along with this program. If not, see <http://www.gnu.org/licenses/> * */ - use OCA\TwoFactorTOTP\AppInfo\Application; include_once __DIR__ . '/../vendor/autoload.php'; diff --git a/appinfo/info.xml b/appinfo/info.xml index 0fb0650..c3b8117 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -23,4 +23,12 @@ <two-factor-providers> <provider>OCA\TwoFactorTOTP\Provider\TotpProvider</provider> </two-factor-providers> + <activity> + <settings> + <setting>OCA\TwoFactorTOTP\Activity\Setting</setting> + </settings> + <providers> + <provider>OCA\TwoFactorTOTP\Activity\Provider</provider> + </providers> + </activity> </info> diff --git a/lib/Activity/Provider.php b/lib/Activity/Provider.php new file mode 100644 index 0000000..fb7c6c0 --- /dev/null +++ b/lib/Activity/Provider.php @@ -0,0 +1,68 @@ +<?php + +/** + * @author Christoph Wurst <christoph@winzerhof-wurst.at> + * @copyright Copyright (c) 2016 Christoph Wurst <christoph@winzerhof-wurst.at> + * + * Two-factor TOTP + * + * 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 <http://www.gnu.org/licenses/> + * + */ + +namespace OCA\TwoFactorTOTP\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_totp') { + throw new InvalidArgumentException(); + } + + $l = $this->l10n->get('twofactor_totp', $language); + + $event->setIcon($this->urlGenerator->getAbsoluteURL($this->urlGenerator->imagePath('core', 'actions/password.svg'))); + switch ($event->getSubject()) { + case 'totp_enabled_subject': + $event->setSubject($l->t('You enabled TOTP two-factor authentication for your account')); + break; + case 'totp_disabled_subject': + $event->setSubject($l->t('You disabled TOTP two-factor authentication for your account')); + break; + } + return $event; + } + +} diff --git a/lib/Activity/Setting.php b/lib/Activity/Setting.php new file mode 100644 index 0000000..d38b998 --- /dev/null +++ b/lib/Activity/Setting.php @@ -0,0 +1,65 @@ +<?php + +/** + * @author Christoph Wurst <christoph@winzerhof-wurst.at> + * @copyright Copyright (c) 2016 Christoph Wurst <christoph@winzerhof-wurst.at> + * + * Two-factor TOTP + * + * 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 <http://www.gnu.org/licenses/> + * + */ + +namespace OCA\TwoFactorTOTP\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_totp'; + } + + public function getName() { + return $this->l10n->t('TOTP (Google Authenticator)'); + } + + public function getPriority() { + return 10; + } + + public function isDefaultEnabledMail() { + return true; + } + + public function isDefaultEnabledStream() { + return true; + } + +} diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php index f18563e..77daa26 100644 --- a/lib/AppInfo/Application.php +++ b/lib/AppInfo/Application.php @@ -21,15 +21,17 @@ namespace OCA\TwoFactorTOTP\AppInfo; +use OCA\TwoFactorTOTP\Service\ITotp; +use OCA\TwoFactorTOTP\Service\Totp; use OCP\AppFramework\App; class Application extends App { - public function __construct($urlParams = []) { - parent::__construct('twofactor_totp', $urlParams); + public function __construct($urlParams = []) { + parent::__construct('twofactor_totp', $urlParams); - $container = $this->getContainer(); - $container->registerAlias('\OCA\TwoFactorTOTP\Service\ITotp', '\OCA\TwoFactorTOTP\Service\Totp'); - } + $container = $this->getContainer(); + $container->registerAlias(ITotp::class, Totp::class); + } } diff --git a/lib/Controller/SettingsController.php b/lib/Controller/SettingsController.php index 4b56a53..7d9af43 100644 --- a/lib/Controller/SettingsController.php +++ b/lib/Controller/SettingsController.php @@ -27,7 +27,6 @@ use OCP\AppFramework\Controller; use OCP\AppFramework\Http\JSONResponse; use OCP\Defaults; use OCP\IRequest; -use OCP\IURLGenerator; use OCP\IUserSession; class SettingsController extends Controller { diff --git a/lib/Provider/TotpProvider.php b/lib/Provider/TotpProvider.php index e4e85f6..30c3377 100644 --- a/lib/Provider/TotpProvider.php +++ b/lib/Provider/TotpProvider.php @@ -29,77 +29,77 @@ use OCP\Template; class TotpProvider implements IProvider { - /** @var ITotp */ - private $totp; + /** @var ITotp */ + private $totp; - /** @var IL10N */ - private $l10n; + /** @var IL10N */ + private $l10n; - /** - * @param Totp $totp - * @param IL10N $l10n - */ - public function __construct(ITotp $totp, IL10N $l10n) { - $this->totp = $totp; - $this->l10n = $l10n; - } + /** + * @param ITotp $totp + * @param IL10N $l10n + */ + public function __construct(ITotp $totp, IL10N $l10n) { + $this->totp = $totp; + $this->l10n = $l10n; + } - /** - * Get unique identifier of this 2FA provider - * - * @return string - */ - public function getId() { - return 'totp'; - } + /** + * Get unique identifier of this 2FA provider + * + * @return string + */ + public function getId() { + return 'totp'; + } - /** - * Get the display name for selecting the 2FA provider - * - * @return string - */ - public function getDisplayName() { - return 'TOTP (Google Authenticator)'; - } + /** + * Get the display name for selecting the 2FA provider + * + * @return string + */ + public function getDisplayName() { + return 'TOTP (Google Authenticator)'; + } - /** - * Get the description for selecting the 2FA provider - * - * @return string - */ - public function getDescription() { - return $this->l10n->t('Authenticate with a TOTP app'); - } + /** + * Get the description for selecting the 2FA provider + * + * @return string + */ + public function getDescription() { + return $this->l10n->t('Authenticate with a TOTP app'); + } - /** - * Get the template for rending the 2FA provider view - * - * @param IUser $user - * @return Template - */ - public function getTemplate(IUser $user) { - $tmpl = new Template('twofactor_totp', 'challenge'); - return $tmpl; - } + /** + * Get the template for rending the 2FA provider view + * + * @param IUser $user + * @return Template + */ + public function getTemplate(IUser $user) { + $tmpl = new Template('twofactor_totp', 'challenge'); + return $tmpl; + } - /** - * Verify the given challenge - * - * @param IUser $user - * @param string $challenge - */ - public function verifyChallenge(IUser $user, $challenge) { - return $this->totp->validateSecret($user, $challenge); - } + /** + * Verify the given challenge + * + * @param IUser $user + * @param string $challenge + */ + public function verifyChallenge(IUser $user, $challenge) { + return $this->totp->validateSecret($user, $challenge); + } - /** - * Decides whether 2FA is enabled for the given user - * - * @param IUser $user - * @return boolean - */ - public function isTwoFactorAuthEnabledForUser(IUser $user) { - return $this->totp->hasSecret($user); - } + /** + * Decides whether 2FA is enabled for the given user + * + * @param IUser $user + * @return boolean + */ + public function isTwoFactorAuthEnabledForUser(IUser $user) { + return $this->totp->hasSecret($user); + } } diff --git a/lib/Service/Totp.php b/lib/Service/Totp.php index cf9d78c..fa3a1d8 100644 --- a/lib/Service/Totp.php +++ b/lib/Service/Totp.php @@ -2,6 +2,7 @@ /** * @author Christoph Wurst <christoph@winzerhof-wurst.at> + * @copyright Copyright (c) 2016 Christoph Wurst <christoph@winzerhof-wurst.at> * * Two-factor TOTP * @@ -30,67 +31,91 @@ use OCP\IUser; use OCP\Security\ICrypto; use Otp\GoogleAuthenticator; use Otp\Otp; +use OCP\Activity\IManager as ActivityManager; class Totp implements ITotp { - /** @var TotpSecretMapper */ - private $secretMapper; - - /** @var ICrypto */ - private $crypto; - - public function __construct(TotpSecretMapper $secretMapper, ICrypto $crypto) { - $this->secretMapper = $secretMapper; - $this->crypto = $crypto; - } - - public function hasSecret(IUser $user) { - try { - $this->secretMapper->getSecret($user); - } catch (DoesNotExistException $ex) { - return false; - } - return true; - } - - /** - * @todo prevent duplicates - * - * @param IUser $user - */ - public function createSecret(IUser $user) { - $secret = GoogleAuthenticator::generateRandom(); - - $dbSecret = new TotpSecret(); - $dbSecret->setUserId($user->getUID()); - $dbSecret->setSecret($this->crypto->encrypt($secret)); - - $this->secretMapper->insert($dbSecret); - - return $secret; - } - - public function deleteSecret(IUser $user) { - try { - // TODO: execute DELETE sql in mapper instead - $dbSecret = $this->secretMapper->getSecret($user); - $this->secretMapper->delete($dbSecret); - } catch (DoesNotExistException $ex) { - - } - } - - public function validateSecret(IUser $user, $key) { - try { - $dbSecret = $this->secretMapper->getSecret($user); - } catch (DoesNotExistException $ex) { - throw new NoTotpSecretFoundException(); - } - - $secret = $this->crypto->decrypt($dbSecret->getSecret()); - - $otp = new Otp(); - return $otp->checkTotp(Base32::decode($secret), $key, 3); - } + /** @var TotpSecretMapper */ + private $secretMapper; + + /** @var ICrypto */ + private $crypto; + + /** @var ActivityManager */ + private $activityManager; + + public function __construct(TotpSecretMapper $secretMapper, ICrypto $crypto, ActivityManager $activityManager) { + $this->secretMapper = $secretMapper; + $this->crypto = $crypto; + $this->activityManager = $activityManager; + } + + public function hasSecret(IUser $user) { + try { + $this->secretMapper->getSecret($user); + } catch (DoesNotExistException $ex) { + return false; + } + return true; + } + + /** + * @todo prevent duplicates + * + * @param IUser $user + */ + public function createSecret(IUser $user) { + $secret = GoogleAuthenticator::generateRandom(); + + $dbSecret = new TotpSecret(); + $dbSecret->setUserId($user->getUID()); + $dbSecret->setSecret($this->crypto->encrypt($secret)); + + $this->secretMapper->insert($dbSecret); + $this->publishEvent($user, 'totp_enabled'); + + return $secret; + } + + /** + * Push an TOTP 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_totp') + ->setType('twofactor') + ->setAuthor($user->getUID()) + ->setAffectedUser($user->getUID()); + $activity->setSubject($event . '_subject'); + $this->activityManager->publish($activity); + } + + public function deleteSecret(IUser $user) { + try { + // TODO: execute DELETE sql in mapper instead + $dbSecret = $this->secretMapper->getSecret($user); + $this->secretMapper->delete($dbSecret); + } catch (DoesNotExistException $ex) { + // Ignore + } + $this->publishEvent($user, 'totp_disabled'); + } + + public function validateSecret(IUser $user, $key) { + try { + $dbSecret = $this->secretMapper->getSecret($user); + } catch (DoesNotExistException $ex) { + throw new NoTotpSecretFoundException(); + } + + $secret = $this->crypto->decrypt($dbSecret->getSecret()); + + $otp = new Otp(); + return $otp->checkTotp(Base32::decode($secret), $key, 3); + } } diff --git a/tests/unit/Activity/ProviderTest.php b/tests/unit/Activity/ProviderTest.php new file mode 100644 index 0000000..bf13dc8 --- /dev/null +++ b/tests/unit/Activity/ProviderTest.php @@ -0,0 +1,112 @@ +<?php + +/** + * @author Christoph Wurst <christoph@winzerhof-wurst.at> + * @copyright Copyright (c) 2016 Christoph Wurst <christoph@winzerhof-wurst.at> + * + * Two-factor TOTP + * + * 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 <http://www.gnu.org/licenses/> + * + */ + +namespace OCA\TwoFactorTOTP\Test\Unit\Activity; + +use InvalidArgumentException; +use OCA\TwoFactorTOTP\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 [ + ['totp_enabled_subject'], + ['totp_disabled_subject'], + [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_totp')); + $this->l10n->expects($this->once()) + ->method('get') + ->with('twofactor_totp', $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..b92bb64 --- /dev/null +++ b/tests/unit/Activity/SettingTest.php @@ -0,0 +1,58 @@ +<?php + +/** + * @author Christoph Wurst <christoph@winzerhof-wurst.at> + * @copyright Copyright (c) 2016 Christoph Wurst <christoph@winzerhof-wurst.at> + * + * Two-factor TOTP + * + * 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 <http://www.gnu.org/licenses/> + * + */ + +namespace OCA\TwoFactorTOTP\Test\Unit\Activity; + +use OCA\TwoFactorTOTP\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_totp', $this->setting->getIdentifier()); + $this->l10n->expects($this->once()) + ->method('t') + ->with('TOTP (Google Authenticator)') + ->will($this->returnValue('TOTP (Google Authentifizierer)')); + $this->assertEquals('TOTP (Google Authentifizierer)', $this->setting->getName()); + $this->assertEquals(10, $this->setting->getPriority()); + $this->assertEquals(true, $this->setting->isDefaultEnabledMail()); + $this->assertEquals(true, $this->setting->isDefaultEnabledStream()); + } + +} diff --git a/tests/unit/Controller/SettingsControllerTest.php b/tests/unit/Controller/SettingsControllerTest.php index e6ea5d8..f011c5d 100644 --- a/tests/unit/Controller/SettingsControllerTest.php +++ b/tests/unit/Controller/SettingsControllerTest.php @@ -2,6 +2,7 @@ /** * @author Christoph Wurst <christoph@winzerhof-wurst.at> + * @copyright Copyright (c) 2016 Christoph Wurst <christoph@winzerhof-wurst.at> * * Two-factor TOTP * @@ -23,7 +24,11 @@ namespace OCA\TwoFactorTOTP\Unit\Controller; use Endroid\QrCode\QrCode; use OCA\TwoFactorTOTP\Controller\SettingsController; +use OCA\TwoFactorTOTP\Service\Totp; use OCP\Defaults; +use OCP\IRequest; +use OCP\IUser; +use OCP\IUserSession; use Test\TestCase; class SettingsControllerTest extends TestCase { @@ -39,16 +44,16 @@ class SettingsControllerTest extends TestCase { protected function setUp() { parent::setUp(); - $this->request = $this->createMock('\OCP\IRequest'); - $this->userSession = $this->createMock('\OCP\IUserSession'); - $this->totp = $this->createMock('\OCA\TwoFactorTOTP\Service\ITotp'); + $this->request = $this->createMock(IRequest::class); + $this->userSession = $this->createMock(IUserSession::class); + $this->totp = $this->createMock(Totp::class); $this->defaults = new Defaults(); $this->controller = new SettingsController('twofactor_totp', $this->request, $this->userSession, $this->totp, $this->defaults); } public function testNothing() { - $user = $this->createMock('\OCP\IUser'); + $user = $this->createMock(IUser::class); $this->userSession->expects($this->once()) ->method('getUser') ->will($this->returnValue($user)); @@ -58,14 +63,14 @@ class SettingsControllerTest extends TestCase { ->will($this->returnValue(true)); $expected = [ - 'enabled' => true, + 'enabled' => true, ]; $this->assertEquals($expected, $this->controller->state()); } public function testEnable() { - $user = $this->createMock('\OCP\IUser'); + $user = $this->createMock(IUser::class); $this->userSession->expects($this->exactly(2)) ->method('getUser') ->will($this->returnValue($user)); @@ -84,16 +89,16 @@ class SettingsControllerTest extends TestCase { ->getDataUri(); $expected = [ - 'enabled' => true, - 'secret' => 'newsecret', - 'qr' => $qr, + 'enabled' => true, + 'secret' => 'newsecret', + 'qr' => $qr, ]; $this->assertEquals($expected, $this->controller->enable(true)); } public function testEnableDisable() { - $user = $this->createMock('\OCP\IUser'); + $user = $this->createMock(IUser::class); $this->userSession->expects($this->once()) ->method('getUser') ->will($this->returnValue($user)); @@ -101,7 +106,7 @@ class SettingsControllerTest extends TestCase { ->method('deleteSecret'); $expected = [ - 'enabled' => false, + 'enabled' => false, ]; $this->assertEquals($expected, $this->controller->enable(false)); |