diff --git a/core/Command/TwoFactorAuth/Enforce.php b/core/Command/TwoFactorAuth/Enforce.php new file mode 100644 index 0000000000..44103e718e --- /dev/null +++ b/core/Command/TwoFactorAuth/Enforce.php @@ -0,0 +1,91 @@ + + * + * @author 2018 Christoph Wurst + * + * @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 OC\Core\Command\TwoFactorAuth; + +use OC\Authentication\TwoFactorAuth\MandatoryTwoFactor; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; + +class Enforce extends Command { + + /** @var MandatoryTwoFactor */ + private $mandatoryTwoFactor; + + public function __construct(MandatoryTwoFactor $mandatoryTwoFactor) { + parent::__construct(); + + $this->mandatoryTwoFactor = $mandatoryTwoFactor; + } + + protected function configure() { + $this->setName('twofactorauth:enforce'); + $this->setDescription('Enabled/disable enforced two-factor authentication'); + $this->addOption( + 'on', + null, + InputOption::VALUE_NONE, + 'enforce two-factor authentication' + ); + $this->addOption( + 'off', + null, + InputOption::VALUE_NONE, + 'don\'t enforce two-factor authenticaton' + ); + } + + protected function execute(InputInterface $input, OutputInterface $output) { + if ($input->getOption('on')) { + $this->mandatoryTwoFactor->setEnforced(true); + } elseif ($input->getOption('off')) { + $this->mandatoryTwoFactor->setEnforced(false); + } + + if ($this->mandatoryTwoFactor->isEnforced()) { + $this->writeEnforced($output); + } else { + $this->writeNotEnforced($output); + } + } + + /** + * @param OutputInterface $output + */ + protected function writeEnforced(OutputInterface $output) { + $output->writeln('Two-factor authentication is enforced for all users'); + } + + /** + * @param OutputInterface $output + */ + protected function writeNotEnforced(OutputInterface $output) { + $output->writeln('Two-factor authentication is not enforced'); + } + +} diff --git a/core/register_command.php b/core/register_command.php index ed0220e705..af8d9977c7 100644 --- a/core/register_command.php +++ b/core/register_command.php @@ -67,6 +67,7 @@ if (\OC::$server->getConfig()->getSystemValue('installed', false)) { $application->add(new OC\Core\Command\App\ListApps(\OC::$server->getAppManager())); $application->add(\OC::$server->query(\OC\Core\Command\TwoFactorAuth\Cleanup::class)); + $application->add(\OC::$server->query(\OC\Core\Command\TwoFactorAuth\Enforce::class)); $application->add(\OC::$server->query(\OC\Core\Command\TwoFactorAuth\Enable::class)); $application->add(\OC::$server->query(\OC\Core\Command\TwoFactorAuth\Disable::class)); $application->add(\OC::$server->query(\OC\Core\Command\TwoFactorAuth\State::class)); diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php index 0379b76775..c35dfe4b3a 100644 --- a/lib/composer/composer/autoload_classmap.php +++ b/lib/composer/composer/autoload_classmap.php @@ -456,6 +456,7 @@ return array( 'OC\\Authentication\\Token\\PublicKeyTokenProvider' => $baseDir . '/lib/private/Authentication/Token/PublicKeyTokenProvider.php', 'OC\\Authentication\\TwoFactorAuth\\Db\\ProviderUserAssignmentDao' => $baseDir . '/lib/private/Authentication/TwoFactorAuth/Db/ProviderUserAssignmentDao.php', 'OC\\Authentication\\TwoFactorAuth\\Manager' => $baseDir . '/lib/private/Authentication/TwoFactorAuth/Manager.php', + 'OC\\Authentication\\TwoFactorAuth\\MandatoryTwoFactor' => $baseDir . '/lib/private/Authentication/TwoFactorAuth/MandatoryTwoFactor.php', 'OC\\Authentication\\TwoFactorAuth\\ProviderLoader' => $baseDir . '/lib/private/Authentication/TwoFactorAuth/ProviderLoader.php', 'OC\\Authentication\\TwoFactorAuth\\ProviderManager' => $baseDir . '/lib/private/Authentication/TwoFactorAuth/ProviderManager.php', 'OC\\Authentication\\TwoFactorAuth\\ProviderSet' => $baseDir . '/lib/private/Authentication/TwoFactorAuth/ProviderSet.php', @@ -576,6 +577,7 @@ return array( 'OC\\Core\\Command\\TwoFactorAuth\\Cleanup' => $baseDir . '/core/Command/TwoFactorAuth/Cleanup.php', 'OC\\Core\\Command\\TwoFactorAuth\\Disable' => $baseDir . '/core/Command/TwoFactorAuth/Disable.php', 'OC\\Core\\Command\\TwoFactorAuth\\Enable' => $baseDir . '/core/Command/TwoFactorAuth/Enable.php', + 'OC\\Core\\Command\\TwoFactorAuth\\Enforce' => $baseDir . '/core/Command/TwoFactorAuth/Enforce.php', 'OC\\Core\\Command\\TwoFactorAuth\\State' => $baseDir . '/core/Command/TwoFactorAuth/State.php', 'OC\\Core\\Command\\Upgrade' => $baseDir . '/core/Command/Upgrade.php', 'OC\\Core\\Command\\User\\Add' => $baseDir . '/core/Command/User/Add.php', diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php index 0456e78442..acffc1c842 100644 --- a/lib/composer/composer/autoload_static.php +++ b/lib/composer/composer/autoload_static.php @@ -486,6 +486,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c 'OC\\Authentication\\Token\\PublicKeyTokenProvider' => __DIR__ . '/../../..' . '/lib/private/Authentication/Token/PublicKeyTokenProvider.php', 'OC\\Authentication\\TwoFactorAuth\\Db\\ProviderUserAssignmentDao' => __DIR__ . '/../../..' . '/lib/private/Authentication/TwoFactorAuth/Db/ProviderUserAssignmentDao.php', 'OC\\Authentication\\TwoFactorAuth\\Manager' => __DIR__ . '/../../..' . '/lib/private/Authentication/TwoFactorAuth/Manager.php', + 'OC\\Authentication\\TwoFactorAuth\\MandatoryTwoFactor' => __DIR__ . '/../../..' . '/lib/private/Authentication/TwoFactorAuth/MandatoryTwoFactor.php', 'OC\\Authentication\\TwoFactorAuth\\ProviderLoader' => __DIR__ . '/../../..' . '/lib/private/Authentication/TwoFactorAuth/ProviderLoader.php', 'OC\\Authentication\\TwoFactorAuth\\ProviderManager' => __DIR__ . '/../../..' . '/lib/private/Authentication/TwoFactorAuth/ProviderManager.php', 'OC\\Authentication\\TwoFactorAuth\\ProviderSet' => __DIR__ . '/../../..' . '/lib/private/Authentication/TwoFactorAuth/ProviderSet.php', @@ -606,6 +607,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c 'OC\\Core\\Command\\TwoFactorAuth\\Cleanup' => __DIR__ . '/../../..' . '/core/Command/TwoFactorAuth/Cleanup.php', 'OC\\Core\\Command\\TwoFactorAuth\\Disable' => __DIR__ . '/../../..' . '/core/Command/TwoFactorAuth/Disable.php', 'OC\\Core\\Command\\TwoFactorAuth\\Enable' => __DIR__ . '/../../..' . '/core/Command/TwoFactorAuth/Enable.php', + 'OC\\Core\\Command\\TwoFactorAuth\\Enforce' => __DIR__ . '/../../..' . '/core/Command/TwoFactorAuth/Enforce.php', 'OC\\Core\\Command\\TwoFactorAuth\\State' => __DIR__ . '/../../..' . '/core/Command/TwoFactorAuth/State.php', 'OC\\Core\\Command\\Upgrade' => __DIR__ . '/../../..' . '/core/Command/Upgrade.php', 'OC\\Core\\Command\\User\\Add' => __DIR__ . '/../../..' . '/core/Command/User/Add.php', diff --git a/lib/private/Authentication/TwoFactorAuth/Manager.php b/lib/private/Authentication/TwoFactorAuth/Manager.php index 71cc5874e5..2307a73100 100644 --- a/lib/private/Authentication/TwoFactorAuth/Manager.php +++ b/lib/private/Authentication/TwoFactorAuth/Manager.php @@ -57,6 +57,9 @@ class Manager { /** @var IRegistry */ private $providerRegistry; + /** @var MandatoryTwoFactor */ + private $mandatoryTwoFactor; + /** @var ISession */ private $session; @@ -79,10 +82,14 @@ class Manager { private $dispatcher; public function __construct(ProviderLoader $providerLoader, - IRegistry $providerRegistry, ISession $session, IConfig $config, + IRegistry $providerRegistry, + MandatoryTwoFactor $mandatoryTwoFactor, + ISession $session, IConfig $config, IManager $activityManager, ILogger $logger, TokenProvider $tokenProvider, ITimeFactory $timeFactory, EventDispatcherInterface $eventDispatcher) { $this->providerLoader = $providerLoader; + $this->providerRegistry = $providerRegistry; + $this->mandatoryTwoFactor = $mandatoryTwoFactor; $this->session = $session; $this->config = $config; $this->activityManager = $activityManager; @@ -90,7 +97,6 @@ class Manager { $this->tokenProvider = $tokenProvider; $this->timeFactory = $timeFactory; $this->dispatcher = $eventDispatcher; - $this->providerRegistry = $providerRegistry; } /** @@ -100,6 +106,10 @@ class Manager { * @return boolean */ public function isTwoFactorAuthenticated(IUser $user): bool { + if ($this->mandatoryTwoFactor->isEnforced()) { + return true; + } + $providerStates = $this->providerRegistry->getProviderStates($user); $providers = $this->providerLoader->getProviders($user); $fixedStates = $this->fixMissingProviderStates($providerStates, $providers, $user); diff --git a/lib/private/Authentication/TwoFactorAuth/MandatoryTwoFactor.php b/lib/private/Authentication/TwoFactorAuth/MandatoryTwoFactor.php new file mode 100644 index 0000000000..a23a10a1be --- /dev/null +++ b/lib/private/Authentication/TwoFactorAuth/MandatoryTwoFactor.php @@ -0,0 +1,48 @@ + + * + * @author 2018 Christoph Wurst + * + * @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 OC\Authentication\TwoFactorAuth; + +use OCP\IConfig; + +class MandatoryTwoFactor { + + /** @var IConfig */ + private $config; + + public function __construct(IConfig $config) { + $this->config = $config; + } + + public function isEnforced(): bool { + return $this->config->getSystemValue('twofactor_enforced', 'false') === 'true'; + } + + public function setEnforced(bool $enforced) { + $this->config->setSystemValue('twofactor_enforced', $enforced ? 'true' : 'false'); + } + +} diff --git a/tests/Core/Command/TwoFactorAuth/EnforceTest.php b/tests/Core/Command/TwoFactorAuth/EnforceTest.php new file mode 100644 index 0000000000..8d896f2f45 --- /dev/null +++ b/tests/Core/Command/TwoFactorAuth/EnforceTest.php @@ -0,0 +1,110 @@ + + * + * @author 2018 Christoph Wurst + * + * @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 Tests\Core\Command\TwoFactorAuth; + +use OC\Authentication\TwoFactorAuth\MandatoryTwoFactor; +use OC\Core\Command\TwoFactorAuth\Enforce; +use PHPUnit\Framework\MockObject\MockObject; +use Symfony\Component\Console\Tester\CommandTester; +use Test\TestCase; + +class EnforceTest extends TestCase { + + /** @var MandatoryTwoFactor|MockObject */ + private $mandatoryTwoFactor; + + /** @var CommandTester */ + private $command; + + protected function setUp() { + parent::setUp(); + + $this->mandatoryTwoFactor = $this->createMock(MandatoryTwoFactor::class); + $command = new Enforce($this->mandatoryTwoFactor); + + $this->command = new CommandTester($command); + } + + public function testEnforce() { + $this->mandatoryTwoFactor->expects($this->once()) + ->method('setEnforced') + ->with(true); + $this->mandatoryTwoFactor->expects($this->once()) + ->method('isEnforced') + ->willReturn(true); + + $rc = $this->command->execute([ + '--on' => true, + ]); + + $this->assertEquals(0, $rc); + $display = $this->command->getDisplay(); + $this->assertContains("Two-factor authentication is enforced for all users", $display); + } + + public function testDisableEnforced() { + $this->mandatoryTwoFactor->expects($this->once()) + ->method('setEnforced') + ->with(false); + $this->mandatoryTwoFactor->expects($this->once()) + ->method('isEnforced') + ->willReturn(false); + + $rc = $this->command->execute([ + '--off' => true, + ]); + + $this->assertEquals(0, $rc); + $display = $this->command->getDisplay(); + $this->assertContains("Two-factor authentication is not enforced", $display); + } + + public function testCurrentStateEnabled() { + $this->mandatoryTwoFactor->expects($this->once()) + ->method('isEnforced') + ->willReturn(true); + + $rc = $this->command->execute([]); + + $this->assertEquals(0, $rc); + $display = $this->command->getDisplay(); + $this->assertContains("Two-factor authentication is enforced for all users", $display); + } + + public function testCurrentStateDisabled() { + $this->mandatoryTwoFactor->expects($this->once()) + ->method('isEnforced') + ->willReturn(false); + + $rc = $this->command->execute([]); + + $this->assertEquals(0, $rc); + $display = $this->command->getDisplay(); + $this->assertContains("Two-factor authentication is not enforced", $display); + } + +} diff --git a/tests/lib/Authentication/TwoFactorAuth/ManagerTest.php b/tests/lib/Authentication/TwoFactorAuth/ManagerTest.php index 301b4cc09d..acc0f0d3e9 100644 --- a/tests/lib/Authentication/TwoFactorAuth/ManagerTest.php +++ b/tests/lib/Authentication/TwoFactorAuth/ManagerTest.php @@ -26,6 +26,7 @@ use Exception; use OC; use OC\Authentication\Token\IProvider as TokenProvider; use OC\Authentication\TwoFactorAuth\Manager; +use OC\Authentication\TwoFactorAuth\MandatoryTwoFactor; use OC\Authentication\TwoFactorAuth\ProviderLoader; use OCP\Activity\IEvent; use OCP\Activity\IManager; @@ -50,6 +51,9 @@ class ManagerTest extends TestCase { /** @var IRegistry|\PHPUnit_Framework_MockObject_MockObject */ private $providerRegistry; + /** @var MandatoryTwoFactor|\PHPUnit_Framework_MockObject_MockObject */ + private $mandatoryTwoFactor; + /** @var ISession|\PHPUnit_Framework_MockObject_MockObject */ private $session; @@ -86,6 +90,7 @@ class ManagerTest extends TestCase { $this->user = $this->createMock(IUser::class); $this->providerLoader = $this->createMock(\OC\Authentication\TwoFactorAuth\ProviderLoader::class); $this->providerRegistry = $this->createMock(IRegistry::class); + $this->mandatoryTwoFactor = $this->createMock(MandatoryTwoFactor::class); $this->session = $this->createMock(ISession::class); $this->config = $this->createMock(IConfig::class); $this->activityManager = $this->createMock(IManager::class); @@ -97,6 +102,7 @@ class ManagerTest extends TestCase { $this->manager = new Manager( $this->providerLoader, $this->providerRegistry, + $this->mandatoryTwoFactor, $this->session, $this->config, $this->activityManager, @@ -142,7 +148,20 @@ class ManagerTest extends TestCase { ]); } + public function testIsTwoFactorAuthenticatedEnforced() { + $this->mandatoryTwoFactor->expects($this->once()) + ->method('isEnforced') + ->willReturn(true); + + $enabled = $this->manager->isTwoFactorAuthenticated($this->user); + + $this->assertTrue($enabled); + } + public function testIsTwoFactorAuthenticatedNoProviders() { + $this->mandatoryTwoFactor->expects($this->once()) + ->method('isEnforced') + ->willReturn(false); $this->providerRegistry->expects($this->once()) ->method('getProviderStates') ->willReturn([]); // No providers registered @@ -154,6 +173,9 @@ class ManagerTest extends TestCase { } public function testIsTwoFactorAuthenticatedOnlyBackupCodes() { + $this->mandatoryTwoFactor->expects($this->once()) + ->method('isEnforced') + ->willReturn(false); $this->providerRegistry->expects($this->once()) ->method('getProviderStates') ->willReturn([ @@ -173,6 +195,9 @@ class ManagerTest extends TestCase { } public function testIsTwoFactorAuthenticatedFailingProviders() { + $this->mandatoryTwoFactor->expects($this->once()) + ->method('isEnforced') + ->willReturn(false); $this->providerRegistry->expects($this->once()) ->method('getProviderStates') ->willReturn([ @@ -474,6 +499,7 @@ class ManagerTest extends TestCase { ->setConstructorArgs([ $this->providerLoader, $this->providerRegistry, + $this->mandatoryTwoFactor, $this->session, $this->config, $this->activityManager, diff --git a/tests/lib/Authentication/TwoFactorAuth/MandatoryTwoFactorTest.php b/tests/lib/Authentication/TwoFactorAuth/MandatoryTwoFactorTest.php new file mode 100644 index 0000000000..1cacbd5f78 --- /dev/null +++ b/tests/lib/Authentication/TwoFactorAuth/MandatoryTwoFactorTest.php @@ -0,0 +1,88 @@ + + * + * @author 2018 Christoph Wurst + * + * @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 Tests\Authentication\TwoFactorAuth; + +use OC\Authentication\TwoFactorAuth\MandatoryTwoFactor; +use OCP\IConfig; +use PHPUnit\Framework\MockObject\MockObject; +use Test\TestCase; + +class MandatoryTwoFactorTest extends TestCase { + + /** @var IConfig|MockObject */ + private $config; + + /** @var MandatoryTwoFactor */ + private $mandatoryTwoFactor; + + protected function setUp() { + parent::setUp(); + + $this->config = $this->createMock(IConfig::class); + + $this->mandatoryTwoFactor = new MandatoryTwoFactor($this->config); + } + + public function testIsNotEnforced() { + $this->config->expects($this->once()) + ->method('getSystemValue') + ->with('twofactor_enforced', 'false') + ->willReturn('false'); + + $isEnforced = $this->mandatoryTwoFactor->isEnforced(); + + $this->assertFalse($isEnforced); + } + + public function testIsEnforced() { + $this->config->expects($this->once()) + ->method('getSystemValue') + ->with('twofactor_enforced', 'false') + ->willReturn('true'); + + $isEnforced = $this->mandatoryTwoFactor->isEnforced(); + + $this->assertTrue($isEnforced); + } + + public function testSetEnforced() { + $this->config->expects($this->once()) + ->method('setSystemValue') + ->with('twofactor_enforced', 'true'); + + $this->mandatoryTwoFactor->setEnforced(true); + } + + public function testSetNotEnforced() { + $this->config->expects($this->once()) + ->method('setSystemValue') + ->with('twofactor_enforced', 'false'); + + $this->mandatoryTwoFactor->setEnforced(false); + } + +}