Stateful 2fa providerstags/v14.0.0beta1
@@ -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 OC\Core\Command\TwoFactorAuth; | |||
use OC\Core\Command\Base; | |||
use OCP\Authentication\TwoFactorAuth\IRegistry; | |||
use OCP\IUserManager; | |||
use Symfony\Component\Console\Input\InputArgument; | |||
use Symfony\Component\Console\Input\InputInterface; | |||
use Symfony\Component\Console\Output\OutputInterface; | |||
class State extends Base { | |||
/** @var IRegistry */ | |||
private $registry; | |||
/** @var IUserManager */ | |||
private $userManager; | |||
public function __construct(IRegistry $registry, IUserManager $userManager) { | |||
parent::__construct('twofactorauth:state'); | |||
$this->registry = $registry; | |||
$this->userManager = $userManager; | |||
} | |||
protected function configure() { | |||
parent::configure(); | |||
$this->setName('twofactorauth:state'); | |||
$this->setDescription('Get the two-factor authentication (2FA) state of a user'); | |||
$this->addArgument('uid', InputArgument::REQUIRED); | |||
} | |||
protected function execute(InputInterface $input, OutputInterface $output) { | |||
$uid = $input->getArgument('uid'); | |||
$user = $this->userManager->get($uid); | |||
if (is_null($user)) { | |||
$output->writeln("<error>Invalid UID</error>"); | |||
return; | |||
} | |||
$providerStates = $this->registry->getProviderStates($user); | |||
$filtered = $this->filterEnabledDisabledUnknownProviders($providerStates); | |||
list ($enabled, $disabled) = $filtered; | |||
if (!empty($enabled)) { | |||
$output->writeln("Two-factor authentication is enabled for user $uid"); | |||
} else { | |||
$output->writeln("Two-factor authentication is not enabled for user $uid"); | |||
} | |||
$output->writeln(""); | |||
$this->printProviders("Enabled providers", $enabled, $output); | |||
$this->printProviders("Disabled providers", $disabled, $output); | |||
} | |||
private function filterEnabledDisabledUnknownProviders(array $providerStates): array { | |||
$enabled = []; | |||
$disabled = []; | |||
foreach ($providerStates as $providerId => $isEnabled) { | |||
if ($isEnabled) { | |||
$enabled[] = $providerId; | |||
} else { | |||
$disabled[] = $providerId; | |||
} | |||
} | |||
return [$enabled, $disabled]; | |||
} | |||
private function printProviders(string $title, array $providers, | |||
OutputInterface $output) { | |||
if (empty($providers)) { | |||
// Ignore and don't print anything | |||
return; | |||
} | |||
$output->writeln($title . ":"); | |||
foreach ($providers as $provider) { | |||
$output->writeln("- " . $provider); | |||
} | |||
} | |||
} |
@@ -302,7 +302,7 @@ class LoginController extends Controller { | |||
if ($this->twoFactorManager->isTwoFactorAuthenticated($loginResult)) { | |||
$this->twoFactorManager->prepareTwoFactorLogin($loginResult, $remember_login); | |||
$providers = $this->twoFactorManager->getProviders($loginResult); | |||
$providers = $this->twoFactorManager->getProviderSet($loginResult)->getProviders(); | |||
if (count($providers) === 1) { | |||
// Single provider, hence we can redirect to that provider's challenge page directly | |||
/* @var $provider IProvider */ |
@@ -32,6 +32,7 @@ use OC_Util; | |||
use OCP\AppFramework\Controller; | |||
use OCP\AppFramework\Http\RedirectResponse; | |||
use OCP\AppFramework\Http\TemplateResponse; | |||
use OCP\Authentication\TwoFactorAuth\IProvider; | |||
use OCP\Authentication\TwoFactorAuth\IProvidesCustomCSP; | |||
use OCP\Authentication\TwoFactorAuth\TwoFactorException; | |||
use OCP\IRequest; | |||
@@ -76,6 +77,23 @@ class TwoFactorChallengeController extends Controller { | |||
protected function getLogoutUrl() { | |||
return OC_User::getLogoutUrl($this->urlGenerator); | |||
} | |||
/** | |||
* @param IProvider[] $providers | |||
*/ | |||
private function splitProvidersAndBackupCodes(array $providers): array { | |||
$regular = []; | |||
$backup = null; | |||
foreach ($providers as $provider) { | |||
if ($provider->getId() === 'backup_codes') { | |||
$backup = $provider; | |||
} else { | |||
$regular[] = $provider; | |||
} | |||
} | |||
return [$regular, $backup]; | |||
} | |||
/** | |||
* @NoAdminRequired | |||
@@ -86,12 +104,14 @@ class TwoFactorChallengeController extends Controller { | |||
*/ | |||
public function selectChallenge($redirect_url) { | |||
$user = $this->userSession->getUser(); | |||
$providers = $this->twoFactorManager->getProviders($user); | |||
$backupProvider = $this->twoFactorManager->getBackupProvider($user); | |||
$providerSet = $this->twoFactorManager->getProviderSet($user); | |||
$allProviders = $providerSet->getProviders(); | |||
list($providers, $backupProvider) = $this->splitProvidersAndBackupCodes($allProviders); | |||
$data = [ | |||
'providers' => $providers, | |||
'backupProvider' => $backupProvider, | |||
'providerMissing' => $providerSet->isProviderMissing(), | |||
'redirect_url' => $redirect_url, | |||
'logout_url' => $this->getLogoutUrl(), | |||
]; | |||
@@ -109,12 +129,13 @@ class TwoFactorChallengeController extends Controller { | |||
*/ | |||
public function showChallenge($challengeProviderId, $redirect_url) { | |||
$user = $this->userSession->getUser(); | |||
$provider = $this->twoFactorManager->getProvider($user, $challengeProviderId); | |||
$providerSet = $this->twoFactorManager->getProviderSet($user); | |||
$provider = $providerSet->getProvider($challengeProviderId); | |||
if (is_null($provider)) { | |||
return new RedirectResponse($this->urlGenerator->linkToRoute('core.TwoFactorChallenge.selectChallenge')); | |||
} | |||
$backupProvider = $this->twoFactorManager->getBackupProvider($user); | |||
$backupProvider = $providerSet->getProvider('backup_codes'); | |||
if (!is_null($backupProvider) && $backupProvider->getId() === $provider->getId()) { | |||
// Don't show the backup provider link if we're already showing that provider's challenge | |||
$backupProvider = null; |
@@ -0,0 +1,63 @@ | |||
<?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\Migrations; | |||
use Closure; | |||
use OCP\DB\ISchemaWrapper; | |||
use OCP\Migration\IOutput; | |||
use OCP\Migration\SimpleMigrationStep; | |||
class Version14000Date20180522074438 extends SimpleMigrationStep { | |||
public function changeSchema(IOutput $output, Closure $schemaClosure, | |||
array $options): ISchemaWrapper { | |||
$schema = $schemaClosure(); | |||
if (!$schema->hasTable('twofactor_providers')) { | |||
$table = $schema->createTable('twofactor_providers'); | |||
$table->addColumn('provider_id', 'string', | |||
[ | |||
'notnull' => true, | |||
'length' => 32, | |||
]); | |||
$table->addColumn('uid', 'string', | |||
[ | |||
'notnull' => true, | |||
'length' => 64, | |||
]); | |||
$table->addColumn('enabled', 'smallint', | |||
[ | |||
'notnull' => true, | |||
'length' => 1, | |||
]); | |||
$table->setPrimaryKey(['provider_id', 'uid']); | |||
} | |||
return $schema; | |||
} | |||
} |
@@ -72,6 +72,7 @@ if (\OC::$server->getConfig()->getSystemValue('installed', false)) { | |||
$application->add(new OC\Core\Command\TwoFactorAuth\Disable( | |||
\OC::$server->getTwoFactorAuthManager(), \OC::$server->getUserManager() | |||
)); | |||
$application->add(\OC::$server->query(\OC\Core\Command\TwoFactorAuth\State::class)); | |||
$application->add(new OC\Core\Command\Background\Cron(\OC::$server->getConfig())); | |||
$application->add(new OC\Core\Command\Background\WebCron(\OC::$server->getConfig())); |
@@ -1,6 +1,11 @@ | |||
<div class="warning"> | |||
<h2 class="two-factor-header"><?php p($l->t('Two-factor authentication')) ?></h2> | |||
<p><?php p($l->t('Enhanced security is enabled for your account. Please authenticate using a second factor.')) ?></p> | |||
<?php if ($_['providerMissing']): ?> | |||
<p> | |||
<strong><?php p($l->t('Could not load at least one of your enabled two-factor auth methods. Please contact your admin.')) ?></strong> | |||
</p> | |||
<?php endif; ?> | |||
<p> | |||
<ul> | |||
<?php foreach ($_['providers'] as $provider): ?> |
@@ -71,6 +71,7 @@ return array( | |||
'OCP\\Authentication\\LoginCredentials\\IStore' => $baseDir . '/lib/public/Authentication/LoginCredentials/IStore.php', | |||
'OCP\\Authentication\\TwoFactorAuth\\IProvider' => $baseDir . '/lib/public/Authentication/TwoFactorAuth/IProvider.php', | |||
'OCP\\Authentication\\TwoFactorAuth\\IProvidesCustomCSP' => $baseDir . '/lib/public/Authentication/TwoFactorAuth/IProvidesCustomCSP.php', | |||
'OCP\\Authentication\\TwoFactorAuth\\IRegistry' => $baseDir . '/lib/public/Authentication/TwoFactorAuth/IRegistry.php', | |||
'OCP\\Authentication\\TwoFactorAuth\\TwoFactorException' => $baseDir . '/lib/public/Authentication/TwoFactorAuth/TwoFactorException.php', | |||
'OCP\\AutoloadNotAllowedException' => $baseDir . '/lib/public/AutoloadNotAllowedException.php', | |||
'OCP\\BackgroundJob' => $baseDir . '/lib/public/BackgroundJob.php', | |||
@@ -429,7 +430,11 @@ return array( | |||
'OC\\Authentication\\Token\\PublicKeyToken' => $baseDir . '/lib/private/Authentication/Token/PublicKeyToken.php', | |||
'OC\\Authentication\\Token\\PublicKeyTokenMapper' => $baseDir . '/lib/private/Authentication/Token/PublicKeyTokenMapper.php', | |||
'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\\ProviderLoader' => $baseDir . '/lib/private/Authentication/TwoFactorAuth/ProviderLoader.php', | |||
'OC\\Authentication\\TwoFactorAuth\\ProviderSet' => $baseDir . '/lib/private/Authentication/TwoFactorAuth/ProviderSet.php', | |||
'OC\\Authentication\\TwoFactorAuth\\Registry' => $baseDir . '/lib/private/Authentication/TwoFactorAuth/Registry.php', | |||
'OC\\Avatar' => $baseDir . '/lib/private/Avatar.php', | |||
'OC\\AvatarManager' => $baseDir . '/lib/private/AvatarManager.php', | |||
'OC\\BackgroundJob\\Job' => $baseDir . '/lib/private/BackgroundJob/Job.php', | |||
@@ -540,6 +545,7 @@ return array( | |||
'OC\\Core\\Command\\TwoFactorAuth\\Base' => $baseDir . '/core/Command/TwoFactorAuth/Base.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\\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', | |||
'OC\\Core\\Command\\User\\Delete' => $baseDir . '/core/Command/User/Delete.php', | |||
@@ -579,6 +585,7 @@ return array( | |||
'OC\\Core\\Migrations\\Version14000Date20180404140050' => $baseDir . '/core/Migrations/Version14000Date20180404140050.php', | |||
'OC\\Core\\Migrations\\Version14000Date20180516101403' => $baseDir . '/core/Migrations/Version14000Date20180516101403.php', | |||
'OC\\Core\\Migrations\\Version14000Date20180518120534' => $baseDir . '/core/Migrations/Version14000Date20180518120534.php', | |||
'OC\\Core\\Migrations\\Version14000Date20180522074438' => $baseDir . '/core/Migrations/Version14000Date20180522074438.php', | |||
'OC\\DB\\Adapter' => $baseDir . '/lib/private/DB/Adapter.php', | |||
'OC\\DB\\AdapterMySQL' => $baseDir . '/lib/private/DB/AdapterMySQL.php', | |||
'OC\\DB\\AdapterOCI8' => $baseDir . '/lib/private/DB/AdapterOCI8.php', |
@@ -101,6 +101,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c | |||
'OCP\\Authentication\\LoginCredentials\\IStore' => __DIR__ . '/../../..' . '/lib/public/Authentication/LoginCredentials/IStore.php', | |||
'OCP\\Authentication\\TwoFactorAuth\\IProvider' => __DIR__ . '/../../..' . '/lib/public/Authentication/TwoFactorAuth/IProvider.php', | |||
'OCP\\Authentication\\TwoFactorAuth\\IProvidesCustomCSP' => __DIR__ . '/../../..' . '/lib/public/Authentication/TwoFactorAuth/IProvidesCustomCSP.php', | |||
'OCP\\Authentication\\TwoFactorAuth\\IRegistry' => __DIR__ . '/../../..' . '/lib/public/Authentication/TwoFactorAuth/IRegistry.php', | |||
'OCP\\Authentication\\TwoFactorAuth\\TwoFactorException' => __DIR__ . '/../../..' . '/lib/public/Authentication/TwoFactorAuth/TwoFactorException.php', | |||
'OCP\\AutoloadNotAllowedException' => __DIR__ . '/../../..' . '/lib/public/AutoloadNotAllowedException.php', | |||
'OCP\\BackgroundJob' => __DIR__ . '/../../..' . '/lib/public/BackgroundJob.php', | |||
@@ -459,7 +460,11 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c | |||
'OC\\Authentication\\Token\\PublicKeyToken' => __DIR__ . '/../../..' . '/lib/private/Authentication/Token/PublicKeyToken.php', | |||
'OC\\Authentication\\Token\\PublicKeyTokenMapper' => __DIR__ . '/../../..' . '/lib/private/Authentication/Token/PublicKeyTokenMapper.php', | |||
'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\\ProviderLoader' => __DIR__ . '/../../..' . '/lib/private/Authentication/TwoFactorAuth/ProviderLoader.php', | |||
'OC\\Authentication\\TwoFactorAuth\\ProviderSet' => __DIR__ . '/../../..' . '/lib/private/Authentication/TwoFactorAuth/ProviderSet.php', | |||
'OC\\Authentication\\TwoFactorAuth\\Registry' => __DIR__ . '/../../..' . '/lib/private/Authentication/TwoFactorAuth/Registry.php', | |||
'OC\\Avatar' => __DIR__ . '/../../..' . '/lib/private/Avatar.php', | |||
'OC\\AvatarManager' => __DIR__ . '/../../..' . '/lib/private/AvatarManager.php', | |||
'OC\\BackgroundJob\\Job' => __DIR__ . '/../../..' . '/lib/private/BackgroundJob/Job.php', | |||
@@ -570,6 +575,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c | |||
'OC\\Core\\Command\\TwoFactorAuth\\Base' => __DIR__ . '/../../..' . '/core/Command/TwoFactorAuth/Base.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\\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', | |||
'OC\\Core\\Command\\User\\Delete' => __DIR__ . '/../../..' . '/core/Command/User/Delete.php', | |||
@@ -609,6 +615,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c | |||
'OC\\Core\\Migrations\\Version14000Date20180404140050' => __DIR__ . '/../../..' . '/core/Migrations/Version14000Date20180404140050.php', | |||
'OC\\Core\\Migrations\\Version14000Date20180516101403' => __DIR__ . '/../../..' . '/core/Migrations/Version14000Date20180516101403.php', | |||
'OC\\Core\\Migrations\\Version14000Date20180518120534' => __DIR__ . '/../../..' . '/core/Migrations/Version14000Date20180518120534.php', | |||
'OC\\Core\\Migrations\\Version14000Date20180522074438' => __DIR__ . '/../../..' . '/core/Migrations/Version14000Date20180522074438.php', | |||
'OC\\DB\\Adapter' => __DIR__ . '/../../..' . '/lib/private/DB/Adapter.php', | |||
'OC\\DB\\AdapterMySQL' => __DIR__ . '/../../..' . '/lib/private/DB/AdapterMySQL.php', | |||
'OC\\DB\\AdapterOCI8' => __DIR__ . '/../../..' . '/lib/private/DB/AdapterOCI8.php', |
@@ -0,0 +1,86 @@ | |||
<?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\Db; | |||
use OCP\DB\QueryBuilder\IQueryBuilder; | |||
use OCP\IDBConnection; | |||
/** | |||
* Data access object to query and assign (provider_id, uid, enabled) tuples of | |||
* 2FA providers | |||
*/ | |||
class ProviderUserAssignmentDao { | |||
const TABLE_NAME = 'twofactor_providers'; | |||
/** @var IDBConnection */ | |||
private $conn; | |||
public function __construct(IDBConnection $dbConn) { | |||
$this->conn = $dbConn; | |||
} | |||
/** | |||
* Get all assigned provider IDs for the given user ID | |||
* | |||
* @return string[] where the array key is the provider ID (string) and the | |||
* value is the enabled state (bool) | |||
*/ | |||
public function getState(string $uid): array { | |||
$qb = $this->conn->getQueryBuilder(); | |||
$query = $qb->select('provider_id', 'enabled') | |||
->from(self::TABLE_NAME) | |||
->where($qb->expr()->eq('uid', $qb->createNamedParameter($uid))); | |||
$result = $query->execute(); | |||
$providers = []; | |||
foreach ($result->fetchAll() as $row) { | |||
$providers[$row['provider_id']] = 1 === (int) $row['enabled']; | |||
} | |||
$result->closeCursor(); | |||
return $providers; | |||
} | |||
/** | |||
* Persist a new/updated (provider_id, uid, enabled) tuple | |||
*/ | |||
public function persist(string $providerId, string $uid, int $enabled) { | |||
$qb = $this->conn->getQueryBuilder(); | |||
// TODO: concurrency? What if (providerId, uid) private key is inserted | |||
// twice at the same time? | |||
$query = $qb->insert(self::TABLE_NAME)->values([ | |||
'provider_id' => $qb->createNamedParameter($providerId), | |||
'uid' => $qb->createNamedParameter($uid), | |||
'enabled' => $qb->createNamedParameter($enabled, IQueryBuilder::PARAM_INT), | |||
]); | |||
$query->execute(); | |||
} | |||
} |
@@ -1,5 +1,6 @@ | |||
<?php | |||
declare(strict_types=1); | |||
declare(strict_types = 1); | |||
/** | |||
* @copyright Copyright (c) 2016, ownCloud, Inc. | |||
* | |||
@@ -28,15 +29,12 @@ namespace OC\Authentication\TwoFactorAuth; | |||
use BadMethodCallException; | |||
use Exception; | |||
use OC; | |||
use OC\App\AppManager; | |||
use OC_App; | |||
use OC\Authentication\Exceptions\InvalidTokenException; | |||
use OC\Authentication\Token\IProvider as TokenProvider; | |||
use OCP\Activity\IManager; | |||
use OCP\AppFramework\QueryException; | |||
use OCP\AppFramework\Utility\ITimeFactory; | |||
use OCP\Authentication\TwoFactorAuth\IProvider; | |||
use OCP\Authentication\TwoFactorAuth\IRegistry; | |||
use OCP\IConfig; | |||
use OCP\ILogger; | |||
use OCP\ISession; | |||
@@ -48,12 +46,13 @@ class Manager { | |||
const SESSION_UID_KEY = 'two_factor_auth_uid'; | |||
const SESSION_UID_DONE = 'two_factor_auth_passed'; | |||
const BACKUP_CODES_APP_ID = 'twofactor_backupcodes'; | |||
const BACKUP_CODES_PROVIDER_ID = 'backup_codes'; | |||
const REMEMBER_LOGIN = 'two_factor_remember_login'; | |||
/** @var AppManager */ | |||
private $appManager; | |||
/** @var ProviderLoader */ | |||
private $providerLoader; | |||
/** @var IRegistry */ | |||
private $providerRegistry; | |||
/** @var ISession */ | |||
private $session; | |||
@@ -76,25 +75,11 @@ class Manager { | |||
/** @var EventDispatcherInterface */ | |||
private $dispatcher; | |||
/** | |||
* @param AppManager $appManager | |||
* @param ISession $session | |||
* @param IConfig $config | |||
* @param IManager $activityManager | |||
* @param ILogger $logger | |||
* @param TokenProvider $tokenProvider | |||
* @param ITimeFactory $timeFactory | |||
* @param EventDispatcherInterface $eventDispatcher | |||
*/ | |||
public function __construct(AppManager $appManager, | |||
ISession $session, | |||
IConfig $config, | |||
IManager $activityManager, | |||
ILogger $logger, | |||
TokenProvider $tokenProvider, | |||
ITimeFactory $timeFactory, | |||
EventDispatcherInterface $eventDispatcher) { | |||
$this->appManager = $appManager; | |||
public function __construct(ProviderLoader $providerLoader, | |||
IRegistry $providerRegistry, ISession $session, IConfig $config, | |||
IManager $activityManager, ILogger $logger, TokenProvider $tokenProvider, | |||
ITimeFactory $timeFactory, EventDispatcherInterface $eventDispatcher) { | |||
$this->providerLoader = $providerLoader; | |||
$this->session = $session; | |||
$this->config = $config; | |||
$this->activityManager = $activityManager; | |||
@@ -102,6 +87,7 @@ class Manager { | |||
$this->tokenProvider = $tokenProvider; | |||
$this->timeFactory = $timeFactory; | |||
$this->dispatcher = $eventDispatcher; | |||
$this->providerRegistry = $providerRegistry; | |||
} | |||
/** | |||
@@ -112,7 +98,15 @@ class Manager { | |||
*/ | |||
public function isTwoFactorAuthenticated(IUser $user): bool { | |||
$twoFactorEnabled = ((int) $this->config->getUserValue($user->getUID(), 'core', 'two_factor_auth_disabled', 0)) === 0; | |||
return $twoFactorEnabled && \count($this->getProviders($user)) > 0; | |||
if (!$twoFactorEnabled) { | |||
return false; | |||
} | |||
$providerStates = $this->providerRegistry->getProviderStates($user); | |||
$enabled = array_filter($providerStates); | |||
return $twoFactorEnabled && !empty($enabled); | |||
} | |||
/** | |||
@@ -141,71 +135,96 @@ class Manager { | |||
* @return IProvider|null | |||
*/ | |||
public function getProvider(IUser $user, string $challengeProviderId) { | |||
$providers = $this->getProviders($user, true); | |||
$providers = $this->getProviderSet($user)->getProviders(); | |||
return $providers[$challengeProviderId] ?? null; | |||
} | |||
/** | |||
* Check if the persistant mapping of enabled/disabled state of each available | |||
* provider is missing an entry and add it to the registry in that case. | |||
* | |||
* @todo remove in Nextcloud 17 as by then all providers should have been updated | |||
* | |||
* @param string[] $providerStates | |||
* @param IProvider[] $providers | |||
* @param IUser $user | |||
* @return IProvider|null the backup provider, if enabled for the given user | |||
* @return string[] the updated $providerStates variable | |||
*/ | |||
public function getBackupProvider(IUser $user) { | |||
$providers = $this->getProviders($user, true); | |||
if (!isset($providers[self::BACKUP_CODES_PROVIDER_ID])) { | |||
return null; | |||
private function fixMissingProviderStates(array $providerStates, | |||
array $providers, IUser $user): array { | |||
foreach ($providers as $provider) { | |||
if (isset($providerStates[$provider->getId()])) { | |||
// All good | |||
continue; | |||
} | |||
$enabled = $provider->isTwoFactorAuthEnabledForUser($user); | |||
if ($enabled) { | |||
$this->providerRegistry->enableProviderFor($provider, $user); | |||
} else { | |||
$this->providerRegistry->disableProviderFor($provider, $user); | |||
} | |||
$providerStates[$provider->getId()] = $enabled; | |||
} | |||
return $providers[self::BACKUP_CODES_PROVIDER_ID]; | |||
return $providerStates; | |||
} | |||
/** | |||
* Get the list of 2FA providers for the given user | |||
* | |||
* @param IUser $user | |||
* @param bool $includeBackupApp | |||
* @return IProvider[] | |||
* @throws Exception | |||
* @param array $states | |||
* @param IProvider $providers | |||
*/ | |||
public function getProviders(IUser $user, bool $includeBackupApp = false): array { | |||
$allApps = $this->appManager->getEnabledAppsForUser($user); | |||
$providers = []; | |||
private function isProviderMissing(array $states, array $providers): bool { | |||
$indexed = []; | |||
foreach ($providers as $provider) { | |||
$indexed[$provider->getId()] = $provider; | |||
} | |||
foreach ($allApps as $appId) { | |||
if (!$includeBackupApp && $appId === self::BACKUP_CODES_APP_ID) { | |||
$missing = []; | |||
foreach ($states as $providerId => $enabled) { | |||
if (!$enabled) { | |||
// Don't care | |||
continue; | |||
} | |||
$info = $this->appManager->getAppInfo($appId); | |||
if (isset($info['two-factor-providers'])) { | |||
/** @var string[] $providerClasses */ | |||
$providerClasses = $info['two-factor-providers']; | |||
foreach ($providerClasses as $class) { | |||
try { | |||
$this->loadTwoFactorApp($appId); | |||
$provider = OC::$server->query($class); | |||
$providers[$provider->getId()] = $provider; | |||
} catch (QueryException $exc) { | |||
// Provider class can not be resolved | |||
throw new Exception("Could not load two-factor auth provider $class"); | |||
} | |||
} | |||
if (!isset($indexed[$providerId])) { | |||
$missing[] = $providerId; | |||
$this->logger->alert("two-factor auth provider '$providerId' failed to load", | |||
[ | |||
'app' => 'core', | |||
]); | |||
} | |||
} | |||
return array_filter($providers, function ($provider) use ($user) { | |||
/* @var $provider IProvider */ | |||
return $provider->isTwoFactorAuthEnabledForUser($user); | |||
}); | |||
if (!empty($missing)) { | |||
// There was at least one provider missing | |||
$this->logger->alert(count($missing) . " two-factor auth providers failed to load", ['app' => 'core']); | |||
return true; | |||
} | |||
// If we reach this, there was not a single provider missing | |||
return false; | |||
} | |||
/** | |||
* Load an app by ID if it has not been loaded yet | |||
* Get the list of 2FA providers for the given user | |||
* | |||
* @param string $appId | |||
* @param IUser $user | |||
* @throws Exception | |||
*/ | |||
protected function loadTwoFactorApp(string $appId) { | |||
if (!OC_App::isAppLoaded($appId)) { | |||
OC_App::loadApp($appId); | |||
} | |||
public function getProviderSet(IUser $user): ProviderSet { | |||
$providerStates = $this->providerRegistry->getProviderStates($user); | |||
$providers = $this->providerLoader->getProviders($user); | |||
$fixedStates = $this->fixMissingProviderStates($providerStates, $providers, $user); | |||
$isProviderMissing = $this->isProviderMissing($fixedStates, $providers); | |||
$enabled = array_filter($providers, function (IProvider $provider) use ($fixedStates) { | |||
return $fixedStates[$provider->getId()]; | |||
}); | |||
return new ProviderSet($enabled, $isProviderMissing); | |||
} | |||
/** | |||
@@ -272,7 +291,7 @@ class Manager { | |||
try { | |||
$this->activityManager->publish($activity); | |||
} catch (BadMethodCallException $e) { | |||
$this->logger->warning('could not publish backup code creation activity', ['app' => 'core']); | |||
$this->logger->warning('could not publish activity', ['app' => 'core']); | |||
$this->logger->logException($e, ['app' => 'core']); | |||
} | |||
} |
@@ -0,0 +1,89 @@ | |||
<?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 Exception; | |||
use OC; | |||
use OC_App; | |||
use OCP\App\IAppManager; | |||
use OCP\AppFramework\QueryException; | |||
use OCP\Authentication\TwoFactorAuth\IProvider; | |||
use OCP\IUser; | |||
class ProviderLoader { | |||
const BACKUP_CODES_APP_ID = 'twofactor_backupcodes'; | |||
/** @var IAppManager */ | |||
private $appManager; | |||
public function __construct(IAppManager $appManager) { | |||
$this->appManager = $appManager; | |||
} | |||
/** | |||
* Get the list of 2FA providers for the given user | |||
* | |||
* @return IProvider[] | |||
* @throws Exception | |||
*/ | |||
public function getProviders(IUser $user): array { | |||
$allApps = $this->appManager->getEnabledAppsForUser($user); | |||
$providers = []; | |||
foreach ($allApps as $appId) { | |||
$info = $this->appManager->getAppInfo($appId); | |||
if (isset($info['two-factor-providers'])) { | |||
/** @var string[] $providerClasses */ | |||
$providerClasses = $info['two-factor-providers']; | |||
foreach ($providerClasses as $class) { | |||
try { | |||
$this->loadTwoFactorApp($appId); | |||
$provider = OC::$server->query($class); | |||
$providers[$provider->getId()] = $provider; | |||
} catch (QueryException $exc) { | |||
// Provider class can not be resolved | |||
throw new Exception("Could not load two-factor auth provider $class"); | |||
} | |||
} | |||
} | |||
} | |||
return $providers; | |||
} | |||
/** | |||
* Load an app by ID if it has not been loaded yet | |||
* | |||
* @param string $appId | |||
*/ | |||
protected function loadTwoFactorApp(string $appId) { | |||
if (!OC_App::isAppLoaded($appId)) { | |||
OC_App::loadApp($appId); | |||
} | |||
} | |||
} |
@@ -0,0 +1,72 @@ | |||
<?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\Authentication\TwoFactorAuth\IProvider; | |||
/** | |||
* Contains all two-factor provider information for the two-factor login challenge | |||
*/ | |||
class ProviderSet { | |||
/** @var IProvider */ | |||
private $providers; | |||
/** @var bool */ | |||
private $providerMissing; | |||
/** | |||
* @param IProvider[] $providers | |||
* @param bool $providerMissing | |||
*/ | |||
public function __construct(array $providers, bool $providerMissing) { | |||
$this->providers = []; | |||
foreach ($providers as $provider) { | |||
$this->providers[$provider->getId()] = $provider; | |||
} | |||
$this->providerMissing = $providerMissing; | |||
} | |||
/** | |||
* @param string $providerId | |||
* @return IProvider|null | |||
*/ | |||
public function getProvider(string $providerId) { | |||
return $this->providers[$providerId] ?? null; | |||
} | |||
/** | |||
* @return IProvider[] | |||
*/ | |||
public function getProviders(): array { | |||
return $this->providers; | |||
} | |||
public function isProviderMissing(): bool { | |||
return $this->providerMissing; | |||
} | |||
} |
@@ -0,0 +1,55 @@ | |||
<?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 OC\Authentication\TwoFactorAuth\Db\ProviderUserAssignmentDao; | |||
use OCP\Authentication\TwoFactorAuth\IProvider; | |||
use OCP\Authentication\TwoFactorAuth\IRegistry; | |||
use OCP\IUser; | |||
class Registry implements IRegistry { | |||
/** @var ProviderUserAssignmentDao */ | |||
private $assignmentDao; | |||
public function __construct(ProviderUserAssignmentDao $assignmentDao) { | |||
$this->assignmentDao = $assignmentDao; | |||
} | |||
public function getProviderStates(IUser $user): array { | |||
return $this->assignmentDao->getState($user->getUID()); | |||
} | |||
public function enableProviderFor(IProvider $provider, IUser $user) { | |||
$this->assignmentDao->persist($provider->getId(), $user->getUID(), 1); | |||
} | |||
public function disableProviderFor(IProvider $provider, IUser $user) { | |||
$this->assignmentDao->persist($provider->getId(), $user->getUID(), 0); | |||
} | |||
} |
@@ -412,18 +412,7 @@ class Server extends ServerContainer implements IServerContainer { | |||
}); | |||
$this->registerAlias('UserSession', \OCP\IUserSession::class); | |||
$this->registerService(\OC\Authentication\TwoFactorAuth\Manager::class, function (Server $c) { | |||
return new \OC\Authentication\TwoFactorAuth\Manager( | |||
$c->getAppManager(), | |||
$c->getSession(), | |||
$c->getConfig(), | |||
$c->getActivityManager(), | |||
$c->getLogger(), | |||
$c->query(IProvider::class), | |||
$c->query(ITimeFactory::class), | |||
$c->query(EventDispatcherInterface::class) | |||
); | |||
}); | |||
$this->registerAlias(\OCP\Authentication\TwoFactorAuth\IRegistry::class, \OC\Authentication\TwoFactorAuth\Registry::class); | |||
$this->registerAlias(\OCP\INavigationManager::class, \OC\NavigationManager::class); | |||
$this->registerAlias('NavigationManager', \OCP\INavigationManager::class); |
@@ -0,0 +1,65 @@ | |||
<?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 OCP\Authentication\TwoFactorAuth; | |||
use OCP\IUser; | |||
/** | |||
* Nextcloud 2FA provider registry for stateful 2FA providers | |||
* | |||
* This service keeps track of which providers are currently active for a specific | |||
* user. Stateful 2FA providers (IStatefulProvider) must use this service to save | |||
* their enabled/disabled state. | |||
* | |||
* @since 14.0.0 | |||
*/ | |||
interface IRegistry { | |||
/** | |||
* Get a key-value map of providers and their enabled/disabled state for | |||
* the given user. | |||
* | |||
* @since 14.0.0 | |||
* @return string[] where the array key is the provider ID (string) and the | |||
* value is the enabled state (bool) | |||
*/ | |||
public function getProviderStates(IUser $user): array; | |||
/** | |||
* Enable the given 2FA provider for the given user | |||
* | |||
* @since 14.0.0 | |||
*/ | |||
public function enableProviderFor(IProvider $provider, IUser $user); | |||
/** | |||
* Disable the given 2FA provider for the given user | |||
* | |||
* @since 14.0.0 | |||
*/ | |||
public function disableProviderFor(IProvider $provider, IUser $user); | |||
} |
@@ -23,11 +23,13 @@ namespace Tests\Core\Controller; | |||
use OC\Authentication\Token\IToken; | |||
use OC\Authentication\TwoFactorAuth\Manager; | |||
use OC\Authentication\TwoFactorAuth\ProviderSet; | |||
use OC\Core\Controller\LoginController; | |||
use OC\Security\Bruteforce\Throttler; | |||
use OC\User\Session; | |||
use OCP\AppFramework\Http\RedirectResponse; | |||
use OCP\AppFramework\Http\TemplateResponse; | |||
use OCP\Authentication\TwoFactorAuth\IProvider; | |||
use OCP\Defaults; | |||
use OCP\IConfig; | |||
use OCP\ILogger; | |||
@@ -414,7 +416,7 @@ class LoginControllerTest extends TestCase { | |||
$user->expects($this->any()) | |||
->method('getUID') | |||
->will($this->returnValue('uid')); | |||
$loginName = 'loginli'; | |||
$loginName = 'loginli'; | |||
$password = 'secret'; | |||
$indexPageUrl = \OC_Util::getDefaultPageUrl(); | |||
@@ -539,7 +541,7 @@ class LoginControllerTest extends TestCase { | |||
$expected = new \OCP\AppFramework\Http\RedirectResponse(urldecode($redirectUrl)); | |||
$this->assertEquals($expected, $this->loginController->tryLogin('Jane', $password, $originalUrl)); | |||
} | |||
public function testLoginWithOneTwoFactorProvider() { | |||
/** @var IUser|\PHPUnit_Framework_MockObject_MockObject $user */ | |||
$user = $this->createMock(IUser::class); | |||
@@ -548,7 +550,7 @@ class LoginControllerTest extends TestCase { | |||
->will($this->returnValue('john')); | |||
$password = 'secret'; | |||
$challengeUrl = 'challenge/url'; | |||
$provider = $this->getMockBuilder('\OCP\Authentication\TwoFactorAuth\IProvider')->getMock(); | |||
$provider = $this->createMock(IProvider::class); | |||
$this->request | |||
->expects($this->once()) | |||
@@ -570,10 +572,11 @@ class LoginControllerTest extends TestCase { | |||
$this->twoFactorManager->expects($this->once()) | |||
->method('prepareTwoFactorLogin') | |||
->with($user); | |||
$providerSet = new ProviderSet([$provider], false); | |||
$this->twoFactorManager->expects($this->once()) | |||
->method('getProviders') | |||
->method('getProviderSet') | |||
->with($user) | |||
->will($this->returnValue([$provider])); | |||
->willReturn($providerSet); | |||
$provider->expects($this->once()) | |||
->method('getId') | |||
->will($this->returnValue('u2f')); | |||
@@ -593,7 +596,7 @@ class LoginControllerTest extends TestCase { | |||
$this->assertEquals($expected, $this->loginController->tryLogin('john@doe.com', $password, null)); | |||
} | |||
public function testLoginWithMultpleTwoFactorProviders() { | |||
public function testLoginWithMultipleTwoFactorProviders() { | |||
/** @var IUser|\PHPUnit_Framework_MockObject_MockObject $user */ | |||
$user = $this->createMock(IUser::class); | |||
$user->expects($this->any()) | |||
@@ -601,8 +604,10 @@ class LoginControllerTest extends TestCase { | |||
->will($this->returnValue('john')); | |||
$password = 'secret'; | |||
$challengeUrl = 'challenge/url'; | |||
$provider1 = $this->getMockBuilder('\OCP\Authentication\TwoFactorAuth\IProvider')->getMock(); | |||
$provider2 = $this->getMockBuilder('\OCP\Authentication\TwoFactorAuth\IProvider')->getMock(); | |||
$provider1 = $this->createMock(IProvider::class); | |||
$provider2 = $this->createMock(IProvider::class); | |||
$provider1->method('getId')->willReturn('prov1'); | |||
$provider2->method('getId')->willReturn('prov2'); | |||
$this->request | |||
->expects($this->once()) | |||
@@ -624,14 +629,11 @@ class LoginControllerTest extends TestCase { | |||
$this->twoFactorManager->expects($this->once()) | |||
->method('prepareTwoFactorLogin') | |||
->with($user); | |||
$providerSet = new ProviderSet([$provider1, $provider2], false); | |||
$this->twoFactorManager->expects($this->once()) | |||
->method('getProviders') | |||
->method('getProviderSet') | |||
->with($user) | |||
->will($this->returnValue([$provider1, $provider2])); | |||
$provider1->expects($this->never()) | |||
->method('getId'); | |||
$provider2->expects($this->never()) | |||
->method('getId'); | |||
->willReturn($providerSet); | |||
$this->urlGenerator->expects($this->once()) | |||
->method('linkToRoute') | |||
->with('core.TwoFactorChallenge.selectChallenge') | |||
@@ -661,7 +663,7 @@ class LoginControllerTest extends TestCase { | |||
->method('checkPassword') | |||
->with('john', 'just wrong') | |||
->willReturn(false); | |||
$this->userManager->expects($this->once()) | |||
->method('getByEmail') | |||
->with('john@doe.com') |
@@ -23,6 +23,7 @@ | |||
namespace Test\Core\Controller; | |||
use OC\Authentication\TwoFactorAuth\Manager; | |||
use OC\Authentication\TwoFactorAuth\ProviderSet; | |||
use OC\Core\Controller\TwoFactorChallengeController; | |||
use OC_Util; | |||
use OCP\AppFramework\Http\RedirectResponse; | |||
@@ -85,26 +86,26 @@ class TwoFactorChallengeControllerTest extends TestCase { | |||
public function testSelectChallenge() { | |||
$user = $this->getMockBuilder(IUser::class)->getMock(); | |||
$providers = [ | |||
'prov1', | |||
'prov2', | |||
]; | |||
$p1 = $this->createMock(IProvider::class); | |||
$p1->method('getId')->willReturn('p1'); | |||
$backupProvider = $this->createMock(IProvider::class); | |||
$backupProvider->method('getId')->willReturn('backup_codes'); | |||
$providerSet = new ProviderSet([$p1, $backupProvider], true); | |||
$this->userSession->expects($this->once()) | |||
->method('getUser') | |||
->will($this->returnValue($user)); | |||
$this->twoFactorManager->expects($this->once()) | |||
->method('getProviders') | |||
->with($user) | |||
->will($this->returnValue($providers)); | |||
$this->twoFactorManager->expects($this->once()) | |||
->method('getBackupProvider') | |||
->method('getProviderSet') | |||
->with($user) | |||
->will($this->returnValue('backup')); | |||
->will($this->returnValue($providerSet)); | |||
$expected = new TemplateResponse('core', 'twofactorselectchallenge', [ | |||
'providers' => $providers, | |||
'backupProvider' => 'backup', | |||
'providers' => [ | |||
$p1, | |||
], | |||
'providerMissing' => true, | |||
'backupProvider' => $backupProvider, | |||
'redirect_url' => '/some/url', | |||
'logout_url' => 'logoutAttribute', | |||
], 'guest'); | |||
@@ -115,20 +116,19 @@ class TwoFactorChallengeControllerTest extends TestCase { | |||
public function testShowChallenge() { | |||
$user = $this->createMock(IUser::class); | |||
$provider = $this->createMock(IProvider::class); | |||
$provider->method('getId')->willReturn('myprovider'); | |||
$backupProvider = $this->createMock(IProvider::class); | |||
$backupProvider->method('getId')->willReturn('backup_codes'); | |||
$tmpl = $this->createMock(Template::class); | |||
$providerSet = new ProviderSet([$provider, $backupProvider], true); | |||
$this->userSession->expects($this->once()) | |||
->method('getUser') | |||
->will($this->returnValue($user)); | |||
$this->twoFactorManager->expects($this->once()) | |||
->method('getProvider') | |||
->with($user, 'myprovider') | |||
->will($this->returnValue($provider)); | |||
$this->twoFactorManager->expects($this->once()) | |||
->method('getBackupProvider') | |||
->method('getProviderSet') | |||
->with($user) | |||
->will($this->returnValue($backupProvider)); | |||
->will($this->returnValue($providerSet)); | |||
$provider->expects($this->once()) | |||
->method('getId') | |||
->will($this->returnValue('u2f')); | |||
@@ -166,14 +166,15 @@ class TwoFactorChallengeControllerTest extends TestCase { | |||
public function testShowInvalidChallenge() { | |||
$user = $this->createMock(IUser::class); | |||
$providerSet = new ProviderSet([], false); | |||
$this->userSession->expects($this->once()) | |||
->method('getUser') | |||
->will($this->returnValue($user)); | |||
$this->twoFactorManager->expects($this->once()) | |||
->method('getProvider') | |||
->with($user, 'myprovider') | |||
->will($this->returnValue(null)); | |||
->method('getProviderSet') | |||
->with($user) | |||
->will($this->returnValue($providerSet)); | |||
$this->urlGenerator->expects($this->once()) | |||
->method('linkToRoute') | |||
->with('core.TwoFactorChallenge.selectChallenge') |
@@ -0,0 +1,96 @@ | |||
<?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 Test\Authentication\TwoFactorAuth\Db; | |||
use OC; | |||
use OC\Authentication\TwoFactorAuth\Db\ProviderUserAssignmentDao; | |||
use OCP\IDBConnection; | |||
use Test\TestCase; | |||
/** | |||
* @group DB | |||
*/ | |||
class ProviderUserAssignmentDaoTest extends TestCase { | |||
/** @var IDBConnection */ | |||
private $dbConn; | |||
/** @var ProviderUserAssignmentDao */ | |||
private $dao; | |||
protected function setUp() { | |||
parent::setUp(); | |||
$this->dbConn = OC::$server->getDatabaseConnection(); | |||
$qb = $this->dbConn->getQueryBuilder(); | |||
$q = $qb->delete(ProviderUserAssignmentDao::TABLE_NAME); | |||
$q->execute(); | |||
$this->dao = new ProviderUserAssignmentDao($this->dbConn); | |||
} | |||
public function testGetState() { | |||
$qb = $this->dbConn->getQueryBuilder(); | |||
$q1 = $qb->insert(ProviderUserAssignmentDao::TABLE_NAME)->values([ | |||
'provider_id' => $qb->createNamedParameter('twofactor_u2f'), | |||
'uid' => $qb->createNamedParameter('user123'), | |||
'enabled' => $qb->createNamedParameter(1), | |||
]); | |||
$q1->execute(); | |||
$q2 = $qb->insert(ProviderUserAssignmentDao::TABLE_NAME)->values([ | |||
'provider_id' => $qb->createNamedParameter('twofactor_totp'), | |||
'uid' => $qb->createNamedParameter('user123'), | |||
'enabled' => $qb->createNamedParameter(0), | |||
]); | |||
$q2->execute(); | |||
$expected = [ | |||
'twofactor_u2f' => true, | |||
'twofactor_totp' => false, | |||
]; | |||
$state = $this->dao->getState('user123'); | |||
$this->assertEquals($expected, $state); | |||
} | |||
public function testPersist() { | |||
$qb = $this->dbConn->getQueryBuilder(); | |||
$this->dao->persist('twofactor_totp', 'user123', 0); | |||
$q = $qb | |||
->select('*') | |||
->from(ProviderUserAssignmentDao::TABLE_NAME) | |||
->where($qb->expr()->eq('provider_id', $qb->createNamedParameter('twofactor_totp'))) | |||
->andWhere($qb->expr()->eq('uid', $qb->createNamedParameter('user123'))) | |||
->andWhere($qb->expr()->eq('enabled', $qb->createNamedParameter(0))); | |||
$res = $q->execute(); | |||
$data = $res->fetchAll(); | |||
$res->closeCursor(); | |||
$this->assertCount(1, $data); | |||
} | |||
} |
@@ -24,13 +24,14 @@ namespace Test\Authentication\TwoFactorAuth; | |||
use Exception; | |||
use OC; | |||
use OC\App\AppManager; | |||
use OC\Authentication\Token\IProvider as TokenProvider; | |||
use OC\Authentication\TwoFactorAuth\Manager; | |||
use OC\Authentication\TwoFactorAuth\ProviderLoader; | |||
use OCP\Activity\IEvent; | |||
use OCP\Activity\IManager; | |||
use OCP\AppFramework\Utility\ITimeFactory; | |||
use OCP\Authentication\TwoFactorAuth\IProvider; | |||
use OCP\Authentication\TwoFactorAuth\IRegistry; | |||
use OCP\IConfig; | |||
use OCP\ILogger; | |||
use OCP\ISession; | |||
@@ -43,8 +44,11 @@ class ManagerTest extends TestCase { | |||
/** @var IUser|\PHPUnit_Framework_MockObject_MockObject */ | |||
private $user; | |||
/** @var AppManager|\PHPUnit_Framework_MockObject_MockObject */ | |||
private $appManager; | |||
/** @var ProviderLoader|\PHPUnit_Framework_MockObject_MockObject */ | |||
private $providerLoader; | |||
/** @var IRegistry|\PHPUnit_Framework_MockObject_MockObject */ | |||
private $providerRegistry; | |||
/** @var ISession|\PHPUnit_Framework_MockObject_MockObject */ | |||
private $session; | |||
@@ -80,7 +84,8 @@ class ManagerTest extends TestCase { | |||
parent::setUp(); | |||
$this->user = $this->createMock(IUser::class); | |||
$this->appManager = $this->createMock(AppManager::class); | |||
$this->providerLoader = $this->createMock(\OC\Authentication\TwoFactorAuth\ProviderLoader::class); | |||
$this->providerRegistry = $this->createMock(IRegistry::class); | |||
$this->session = $this->createMock(ISession::class); | |||
$this->config = $this->createMock(IConfig::class); | |||
$this->activityManager = $this->createMock(IManager::class); | |||
@@ -89,172 +94,145 @@ class ManagerTest extends TestCase { | |||
$this->timeFactory = $this->createMock(ITimeFactory::class); | |||
$this->eventDispatcher = $this->createMock(EventDispatcherInterface::class); | |||
$this->manager = $this->getMockBuilder(Manager::class) | |||
->setConstructorArgs([ | |||
$this->appManager, | |||
$this->session, | |||
$this->config, | |||
$this->activityManager, | |||
$this->logger, | |||
$this->tokenProvider, | |||
$this->timeFactory, | |||
$this->eventDispatcher | |||
]) | |||
->setMethods(['loadTwoFactorApp']) // Do not actually load the apps | |||
->getMock(); | |||
$this->manager = new Manager( | |||
$this->providerLoader, | |||
$this->providerRegistry, | |||
$this->session, | |||
$this->config, | |||
$this->activityManager, | |||
$this->logger, | |||
$this->tokenProvider, | |||
$this->timeFactory, | |||
$this->eventDispatcher | |||
); | |||
$this->fakeProvider = $this->createMock(IProvider::class); | |||
$this->fakeProvider->expects($this->any()) | |||
->method('getId') | |||
->will($this->returnValue('email')); | |||
$this->fakeProvider->expects($this->any()) | |||
->method('isTwoFactorAuthEnabledForUser') | |||
->will($this->returnValue(true)); | |||
OC::$server->registerService('\OCA\MyCustom2faApp\FakeProvider', function() { | |||
return $this->fakeProvider; | |||
}); | |||
$this->fakeProvider->method('getId')->willReturn('email'); | |||
$this->fakeProvider->method('isTwoFactorAuthEnabledForUser')->willReturn(true); | |||
$this->backupProvider = $this->getMockBuilder('\OCP\Authentication\TwoFactorAuth\IProvider')->getMock(); | |||
$this->backupProvider->expects($this->any()) | |||
->method('getId') | |||
->will($this->returnValue('backup_codes')); | |||
$this->backupProvider->expects($this->any()) | |||
->method('isTwoFactorAuthEnabledForUser') | |||
->will($this->returnValue(true)); | |||
OC::$server->registerService('\OCA\TwoFactorBackupCodes\Provider\FakeBackupCodesProvider', function () { | |||
return $this->backupProvider; | |||
}); | |||
$this->backupProvider->method('getId')->willReturn('backup_codes'); | |||
$this->backupProvider->method('isTwoFactorAuthEnabledForUser')->willReturn(true); | |||
} | |||
private function prepareNoProviders() { | |||
$this->appManager->expects($this->any()) | |||
->method('getEnabledAppsForUser') | |||
$this->providerLoader->method('getProviders') | |||
->with($this->user) | |||
->will($this->returnValue([])); | |||
$this->appManager->expects($this->never()) | |||
->method('getAppInfo'); | |||
$this->manager->expects($this->never()) | |||
->method('loadTwoFactorApp'); | |||
} | |||
private function prepareProviders() { | |||
$this->appManager->expects($this->any()) | |||
->method('getEnabledAppsForUser') | |||
$this->providerRegistry->expects($this->once()) | |||
->method('getProviderStates') | |||
->with($this->user) | |||
->will($this->returnValue(['mycustom2faapp'])); | |||
$this->appManager->expects($this->once()) | |||
->method('getAppInfo') | |||
->with('mycustom2faapp') | |||
->will($this->returnValue([ | |||
'two-factor-providers' => [ | |||
'\OCA\MyCustom2faApp\FakeProvider', | |||
], | |||
])); | |||
$this->manager->expects($this->once()) | |||
->method('loadTwoFactorApp') | |||
->with('mycustom2faapp'); | |||
} | |||
private function prepareProvidersWitBackupProvider() { | |||
$this->appManager->expects($this->any()) | |||
->method('getEnabledAppsForUser') | |||
->willReturn([ | |||
$this->fakeProvider->getId() => true, | |||
]); | |||
$this->providerLoader->expects($this->once()) | |||
->method('getProviders') | |||
->with($this->user) | |||
->will($this->returnValue([ | |||
'mycustom2faapp', | |||
'twofactor_backupcodes', | |||
])); | |||
$this->appManager->expects($this->exactly(2)) | |||
->method('getAppInfo') | |||
->will($this->returnValueMap([ | |||
[ | |||
'mycustom2faapp', false, null, | |||
['two-factor-providers' => [ | |||
'\OCA\MyCustom2faApp\FakeProvider', | |||
] | |||
] | |||
], | |||
[ | |||
'twofactor_backupcodes', false, null, | |||
['two-factor-providers' => [ | |||
'\OCA\TwoFactorBackupCodes\Provider\FakeBackupCodesProvider', | |||
] | |||
] | |||
], | |||
])); | |||
$this->manager->expects($this->exactly(2)) | |||
->method('loadTwoFactorApp'); | |||
->willReturn([$this->fakeProvider]); | |||
} | |||
/** | |||
* @expectedException Exception | |||
* @expectedExceptionMessage Could not load two-factor auth provider \OCA\MyFaulty2faApp\DoesNotExist | |||
*/ | |||
public function testFailHardIfProviderCanNotBeLoaded() { | |||
$this->appManager->expects($this->once()) | |||
->method('getEnabledAppsForUser') | |||
private function prepareProvidersWitBackupProvider() { | |||
$this->providerLoader->method('getProviders') | |||
->with($this->user) | |||
->will($this->returnValue(['faulty2faapp'])); | |||
$this->manager->expects($this->once()) | |||
->method('loadTwoFactorApp') | |||
->with('faulty2faapp'); | |||
$this->appManager->expects($this->once()) | |||
->method('getAppInfo') | |||
->with('faulty2faapp') | |||
->will($this->returnValue([ | |||
'two-factor-providers' => [ | |||
'\OCA\MyFaulty2faApp\DoesNotExist', | |||
], | |||
])); | |||
$this->manager->getProviders($this->user); | |||
->willReturn([ | |||
$this->fakeProvider, | |||
$this->backupProvider, | |||
]); | |||
} | |||
public function testIsTwoFactorAuthenticated() { | |||
$this->prepareProviders(); | |||
$this->user->expects($this->once()) | |||
->method('getUID') | |||
->will($this->returnValue('user123')); | |||
$this->config->expects($this->once()) | |||
->method('getUserValue') | |||
->with('user123', 'core', 'two_factor_auth_disabled', 0) | |||
->will($this->returnValue(0)); | |||
->willReturn(0); | |||
$this->providerRegistry->expects($this->once()) | |||
->method('getProviderStates') | |||
->willReturn([ | |||
'twofactor_totp' => true, | |||
'twofactor_u2f' => false, | |||
]); | |||
$this->assertTrue($this->manager->isTwoFactorAuthenticated($this->user)); | |||
} | |||
public function testGetProvider() { | |||
$this->prepareProviders(); | |||
$this->assertSame($this->fakeProvider, $this->manager->getProvider($this->user, 'email')); | |||
} | |||
$this->providerRegistry->expects($this->once()) | |||
->method('getProviderStates') | |||
->with($this->user) | |||
->willReturn([ | |||
$this->fakeProvider->getId() => true, | |||
]); | |||
$this->providerLoader->expects($this->once()) | |||
->method('getProviders') | |||
->with($this->user) | |||
->willReturn([$this->fakeProvider]); | |||
public function testGetBackupProvider() { | |||
$this->prepareProvidersWitBackupProvider(); | |||
$provider = $this->manager->getProvider($this->user, $this->fakeProvider->getId()); | |||
$this->assertSame($this->backupProvider, $this->manager->getBackupProvider($this->user)); | |||
$this->assertSame($this->fakeProvider, $provider); | |||
} | |||
public function testGetInvalidProvider() { | |||
$this->prepareProviders(); | |||
$this->providerRegistry->expects($this->once()) | |||
->method('getProviderStates') | |||
->with($this->user) | |||
->willReturn([]); | |||
$this->providerLoader->expects($this->once()) | |||
->method('getProviders') | |||
->with($this->user) | |||
->willReturn([]); | |||
$this->assertSame(null, $this->manager->getProvider($this->user, 'nonexistent')); | |||
$provider = $this->manager->getProvider($this->user, 'nonexistent'); | |||
$this->assertNull($provider); | |||
} | |||
public function testGetProviders() { | |||
$this->prepareProviders(); | |||
$this->providerRegistry->expects($this->once()) | |||
->method('getProviderStates') | |||
->with($this->user) | |||
->willReturn([ | |||
$this->fakeProvider->getId() => true, | |||
]); | |||
$this->providerLoader->expects($this->once()) | |||
->method('getProviders') | |||
->with($this->user) | |||
->willReturn([$this->fakeProvider]); | |||
$expectedProviders = [ | |||
'email' => $this->fakeProvider, | |||
]; | |||
$this->assertEquals($expectedProviders, $this->manager->getProviders($this->user)); | |||
$providerSet = $this->manager->getProviderSet($this->user); | |||
$providers = $providerSet->getProviders(); | |||
$this->assertEquals($expectedProviders, $providers); | |||
$this->assertFalse($providerSet->isProviderMissing()); | |||
} | |||
public function testGetProvidersOneMissing() { | |||
$this->providerRegistry->expects($this->once()) | |||
->method('getProviderStates') | |||
->with($this->user) | |||
->willReturn([ | |||
$this->fakeProvider->getId() => true, | |||
]); | |||
$this->providerLoader->expects($this->once()) | |||
->method('getProviders') | |||
->with($this->user) | |||
->willReturn([]); | |||
$expectedProviders = [ | |||
'email' => $this->fakeProvider, | |||
]; | |||
$providerSet = $this->manager->getProviderSet($this->user); | |||
$this->assertTrue($providerSet->isProviderMissing()); | |||
} | |||
public function testVerifyChallenge() { | |||
@@ -266,7 +244,6 @@ class ManagerTest extends TestCase { | |||
->method('verifyChallenge') | |||
->with($this->user, $challenge) | |||
->will($this->returnValue(true)); | |||
$this->session->expects($this->once()) | |||
->method('get') | |||
->with('two_factor_remember_login') | |||
@@ -282,15 +259,12 @@ class ManagerTest extends TestCase { | |||
->with(Manager::SESSION_UID_DONE, 'jos'); | |||
$this->session->method('getId') | |||
->willReturn('mysessionid'); | |||
$this->activityManager->expects($this->once()) | |||
->method('generateEvent') | |||
->willReturn($event); | |||
$this->user->expects($this->any()) | |||
->method('getUID') | |||
->willReturn('jos'); | |||
$event->expects($this->once()) | |||
->method('setApp') | |||
->with($this->equalTo('core')) | |||
@@ -316,19 +290,19 @@ class ManagerTest extends TestCase { | |||
'provider' => 'Fake 2FA', | |||
])) | |||
->willReturnSelf(); | |||
$token = $this->createMock(OC\Authentication\Token\IToken::class); | |||
$this->tokenProvider->method('getToken') | |||
->with('mysessionid') | |||
->willReturn($token); | |||
$token->method('getId') | |||
->willReturn(42); | |||
$this->config->expects($this->once()) | |||
->method('deleteUserValue') | |||
->with('jos', 'login_token_2fa', 42); | |||
$this->assertTrue($this->manager->verifyChallenge('email', $this->user, $challenge)); | |||
$result = $this->manager->verifyChallenge('email', $this->user, $challenge); | |||
$this->assertTrue($result); | |||
} | |||
public function testVerifyChallengeInvalidProviderId() { | |||
@@ -424,7 +398,8 @@ class ManagerTest extends TestCase { | |||
$manager = $this->getMockBuilder(Manager::class) | |||
->setConstructorArgs([ | |||
$this->appManager, | |||
$this->providerLoader, | |||
$this->providerRegistry, | |||
$this->session, | |||
$this->config, | |||
$this->activityManager, | |||
@@ -433,7 +408,7 @@ class ManagerTest extends TestCase { | |||
$this->timeFactory, | |||
$this->eventDispatcher | |||
]) | |||
->setMethods(['loadTwoFactorApp','isTwoFactorAuthenticated']) // Do not actually load the apps | |||
->setMethods(['loadTwoFactorApp', 'isTwoFactorAuthenticated'])// Do not actually load the apps | |||
->getMock(); | |||
$manager->method('isTwoFactorAuthenticated') | |||
@@ -531,7 +506,7 @@ class ManagerTest extends TestCase { | |||
->willReturn('user'); | |||
$this->session->method('exists') | |||
->will($this->returnCallback(function($var) { | |||
->will($this->returnCallback(function ($var) { | |||
if ($var === Manager::SESSION_UID_KEY) { | |||
return false; | |||
} else if ($var === 'app_password') { |
@@ -0,0 +1,102 @@ | |||
<?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 lib\Authentication\TwoFactorAuth; | |||
use Exception; | |||
use OC\Authentication\TwoFactorAuth\ProviderLoader; | |||
use OCP\App\IAppManager; | |||
use OCP\Authentication\TwoFactorAuth\IProvider; | |||
use PHPUnit_Framework_MockObject_MockObject; | |||
use Test\TestCase; | |||
class ProviderLoaderTest extends TestCase { | |||
/** @var IAppManager|PHPUnit_Framework_MockObject_MockObject */ | |||
private $appManager; | |||
/** @var IUser|PHPUnit_Framework_MockObject_MockObject */ | |||
private $user; | |||
/** @var ProviderLoader */ | |||
private $loader; | |||
protected function setUp() { | |||
parent::setUp(); | |||
$this->appManager = $this->createMock(IAppManager::class); | |||
$this->user = $this->createMock(\OCP\IUser::class); | |||
$this->loader = new ProviderLoader($this->appManager); | |||
} | |||
/** | |||
* @expectedException Exception | |||
* @expectedExceptionMessage Could not load two-factor auth provider \OCA\MyFaulty2faApp\DoesNotExist | |||
*/ | |||
public function testFailHardIfProviderCanNotBeLoaded() { | |||
$this->appManager->expects($this->once()) | |||
->method('getEnabledAppsForUser') | |||
->with($this->user) | |||
->willReturn(['mail', 'twofactor_totp']); | |||
$this->appManager | |||
->method('getAppInfo') | |||
->will($this->returnValueMap([ | |||
['mail', false, null, []], | |||
['twofactor_totp', false, null, [ | |||
'two-factor-providers' => [ | |||
'\\OCA\\MyFaulty2faApp\\DoesNotExist', | |||
], | |||
]], | |||
])); | |||
$this->loader->getProviders($this->user); | |||
} | |||
public function testGetProviders() { | |||
$provider = $this->createMock(IProvider::class); | |||
$provider->method('getId')->willReturn('test'); | |||
\OC::$server->registerService('\\OCA\\TwoFactorTest\\Provider', function () use ($provider) { | |||
return $provider; | |||
}); | |||
$this->appManager->expects($this->once()) | |||
->method('getEnabledAppsForUser') | |||
->with($this->user) | |||
->willReturn(['twofactor_test']); | |||
$this->appManager | |||
->method('getAppInfo') | |||
->with('twofactor_test') | |||
->willReturn(['two-factor-providers' => [ | |||
'\\OCA\\TwoFactorTest\\Provider', | |||
]]); | |||
$providers = $this->loader->getProviders($this->user); | |||
$this->assertCount(1, $providers); | |||
$this->assertArrayHasKey('test', $providers); | |||
$this->assertSame($provider, $providers['test']); | |||
} | |||
} |
@@ -0,0 +1,75 @@ | |||
<?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 Test\Authentication\TwoFactorAuth; | |||
use OC\Authentication\TwoFactorAuth\ProviderSet; | |||
use OCP\Authentication\TwoFactorAuth\IProvider; | |||
use Test\TestCase; | |||
class ProviderSetTest extends TestCase { | |||
/** @var ProviderSet */ | |||
private $providerSet; | |||
public function testIndexesProviders() { | |||
$p1 = $this->createMock(IProvider::class); | |||
$p1->method('getId')->willReturn('p1'); | |||
$p2 = $this->createMock(IProvider::class); | |||
$p2->method('getId')->willReturn('p2'); | |||
$expected = [ | |||
'p1' => $p1, | |||
'p2' => $p2, | |||
]; | |||
$set = new ProviderSet([$p2, $p1], false); | |||
$this->assertEquals($expected, $set->getProviders()); | |||
} | |||
public function testGetProvider() { | |||
$p1 = $this->createMock(IProvider::class); | |||
$p1->method('getId')->willReturn('p1'); | |||
$set = new ProviderSet([$p1], false); | |||
$provider = $set->getProvider('p1'); | |||
$this->assertEquals($p1, $provider); | |||
} | |||
public function testGetProviderNotFound() { | |||
$set = new ProviderSet([], false); | |||
$provider = $set->getProvider('p1'); | |||
$this->assertNull($provider); | |||
} | |||
public function testIsProviderMissing() { | |||
$set = new ProviderSet([], true); | |||
$this->assertTrue($set->isProviderMissing()); | |||
} | |||
} |
@@ -0,0 +1,85 @@ | |||
<?php | |||
/** | |||
* @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 Test\Authentication\TwoFactorAuth; | |||
use OC\Authentication\TwoFactorAuth\Db\ProviderUserAssignmentDao; | |||
use OC\Authentication\TwoFactorAuth\Registry; | |||
use OCP\Authentication\TwoFactorAuth\IProvider; | |||
use OCP\IUser; | |||
use PHPUnit_Framework_MockObject_MockObject; | |||
use Test\TestCase; | |||
class RegistryTest extends TestCase { | |||
/** @var ProviderUserAssignmentDao|PHPUnit_Framework_MockObject_MockObject */ | |||
private $dao; | |||
/** @var Registry */ | |||
private $registry; | |||
protected function setUp() { | |||
parent::setUp(); | |||
$this->dao = $this->createMock(ProviderUserAssignmentDao::class); | |||
$this->registry = new Registry($this->dao); | |||
} | |||
public function testGetProviderStates() { | |||
$user = $this->createMock(IUser::class); | |||
$user->expects($this->once())->method('getUID')->willReturn('user123'); | |||
$state = [ | |||
'twofactor_totp' => true, | |||
]; | |||
$this->dao->expects($this->once())->method('getState')->willReturn($state); | |||
$actual = $this->registry->getProviderStates($user); | |||
$this->assertEquals($state, $actual); | |||
} | |||
public function testEnableProvider() { | |||
$user = $this->createMock(IUser::class); | |||
$provider = $this->createMock(IProvider::class); | |||
$user->expects($this->once())->method('getUID')->willReturn('user123'); | |||
$provider->expects($this->once())->method('getId')->willReturn('p1'); | |||
$this->dao->expects($this->once())->method('persist')->with('p1', 'user123', | |||
true); | |||
$this->registry->enableProviderFor($provider, $user); | |||
} | |||
public function testDisableProvider() { | |||
$user = $this->createMock(IUser::class); | |||
$provider = $this->createMock(IProvider::class); | |||
$user->expects($this->once())->method('getUID')->willReturn('user123'); | |||
$provider->expects($this->once())->method('getId')->willReturn('p1'); | |||
$this->dao->expects($this->once())->method('persist')->with('p1', 'user123', | |||
false); | |||
$this->registry->disableProviderFor($provider, $user); | |||
} | |||
} |
@@ -29,7 +29,7 @@ | |||
// between betas, final and RCs. This is _not_ the public version number. Reset minor/patchlevel | |||
// when updating major/minor version number. | |||
$OC_Version = array(14, 0, 0, 5); | |||
$OC_Version = array(14, 0, 0, 6); | |||
// The human readable string | |||
$OC_VersionString = '14.0.0 alpha'; |