Skip to content

Commit

Permalink
Merge pull request #9632 from nextcloud/enhancement/stateful-2fa-prov…
Browse files Browse the repository at this point in the history
…iders

Stateful 2fa providers
  • Loading branch information
MorrisJobke authored Jun 25, 2018
2 parents e3be9ef + 7be465f commit 9444a3f
Show file tree
Hide file tree
Showing 23 changed files with 1,187 additions and 262 deletions.
110 changes: 110 additions & 0 deletions core/Command/TwoFactorAuth/State.php
Original file line number Diff line number Diff line change
@@ -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);
}
}

}
2 changes: 1 addition & 1 deletion core/Controller/LoginController.php
Original file line number Diff line number Diff line change
Expand Up @@ -302,7 +302,7 @@ public function tryLogin($user, $password, $redirect_url, $remember_login = true
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 */
Expand Down
29 changes: 25 additions & 4 deletions core/Controller/TwoFactorChallengeController.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
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;
Expand Down Expand Up @@ -76,6 +77,23 @@ public function __construct($appName, IRequest $request, Manager $twoFactorManag
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
Expand All @@ -86,12 +104,14 @@ protected function getLogoutUrl() {
*/
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(),
];
Expand All @@ -109,12 +129,13 @@ public function selectChallenge($redirect_url) {
*/
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;
Expand Down
63 changes: 63 additions & 0 deletions core/Migrations/Version14000Date20180522074438.php
Original file line number Diff line number Diff line change
@@ -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;
}

}
1 change: 1 addition & 0 deletions core/register_command.php
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@
$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()));
Expand Down
5 changes: 5 additions & 0 deletions core/templates/twofactorselectchallenge.php
Original file line number Diff line number Diff line change
@@ -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): ?>
Expand Down
7 changes: 7 additions & 0 deletions lib/composer/composer/autoload_classmap.php
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@
'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',
Expand Down Expand Up @@ -429,7 +430,11 @@
'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',
Expand Down Expand Up @@ -540,6 +545,7 @@
'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',
Expand Down Expand Up @@ -579,6 +585,7 @@
'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',
Expand Down
7 changes: 7 additions & 0 deletions lib/composer/composer/autoload_static.php
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down Expand Up @@ -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',
Expand Down Expand Up @@ -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',
Expand Down Expand Up @@ -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',
Expand Down
Loading

0 comments on commit 9444a3f

Please sign in to comment.