Add mandatory 2FA service/class

Signed-off-by: Christoph Wurst <christoph@winzerhof-wurst.at>
This commit is contained in:
Christoph Wurst 2018-09-29 18:57:00 +02:00 committed by Roeland Jago Douma
parent eec7f9ec28
commit 259c0ce11d
No known key found for this signature in database
GPG key ID: F941078878347C0C
9 changed files with 380 additions and 2 deletions

View file

@ -0,0 +1,91 @@
<?php
declare(strict_types=1);
/**
* @copyright 2018 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @author 2018 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @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 <http://www.gnu.org/licenses/>.
*
*/
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');
}
}

View file

@ -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));

View file

@ -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',

View file

@ -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',

View file

@ -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);

View file

@ -0,0 +1,48 @@
<?php
declare(strict_types=1);
/**
* @copyright 2018 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @author 2018 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @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 <http://www.gnu.org/licenses/>.
*
*/
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');
}
}

View file

@ -0,0 +1,110 @@
<?php
declare(strict_types=1);
/**
* @copyright 2018 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @author 2018 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @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 <http://www.gnu.org/licenses/>.
*
*/
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);
}
}

View file

@ -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,

View file

@ -0,0 +1,88 @@
<?php
declare(strict_types=1);
/**
* @copyright 2018 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @author 2018 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @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 <http://www.gnu.org/licenses/>.
*
*/
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);
}
}