Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/nextcloud/server.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/core
diff options
context:
space:
mode:
authorChristoph Wurst <christoph@winzerhof-wurst.at>2018-05-22 09:52:16 +0300
committerChristoph Wurst <christoph@winzerhof-wurst.at>2018-06-20 09:30:26 +0300
commit13d93f5b25aa3e663146349583a0a8e01b216f7a (patch)
tree494950eefa4b27c980ebce22eeafa58eab08892d /core
parentcad8824a8e7da7fcf61960b6502b307672651c2b (diff)
Make 2FA providers stateful
This adds persistence to the Nextcloud server 2FA logic so that the server knows which 2FA providers are enabled for a specific user at any time, even when the provider is not available. The `IStatefulProvider` interface was added as tagging interface for providers that are compatible with this new API. Signed-off-by: Christoph Wurst <christoph@winzerhof-wurst.at>
Diffstat (limited to 'core')
-rw-r--r--core/Command/TwoFactorAuth/State.php110
-rw-r--r--core/Controller/LoginController.php2
-rw-r--r--core/Controller/TwoFactorChallengeController.php29
-rw-r--r--core/Migrations/Version14000Date20180522074438.php62
-rw-r--r--core/register_command.php1
-rw-r--r--core/templates/twofactorselectchallenge.php5
6 files changed, 204 insertions, 5 deletions
diff --git a/core/Command/TwoFactorAuth/State.php b/core/Command/TwoFactorAuth/State.php
new file mode 100644
index 00000000000..076e2211a12
--- /dev/null
+++ b/core/Command/TwoFactorAuth/State.php
@@ -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);
+ }
+ }
+
+}
diff --git a/core/Controller/LoginController.php b/core/Controller/LoginController.php
index 2235439d956..7bf2555819d 100644
--- a/core/Controller/LoginController.php
+++ b/core/Controller/LoginController.php
@@ -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 */
diff --git a/core/Controller/TwoFactorChallengeController.php b/core/Controller/TwoFactorChallengeController.php
index a5d7d14f367..3d14b157f77 100644
--- a/core/Controller/TwoFactorChallengeController.php
+++ b/core/Controller/TwoFactorChallengeController.php
@@ -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;
diff --git a/core/Migrations/Version14000Date20180522074438.php b/core/Migrations/Version14000Date20180522074438.php
new file mode 100644
index 00000000000..bbb805a9cda
--- /dev/null
+++ b/core/Migrations/Version14000Date20180522074438.php
@@ -0,0 +1,62 @@
+<?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 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;
+ }
+
+}
diff --git a/core/register_command.php b/core/register_command.php
index 9df51e517a0..0115c179bf9 100644
--- a/core/register_command.php
+++ b/core/register_command.php
@@ -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()));
diff --git a/core/templates/twofactorselectchallenge.php b/core/templates/twofactorselectchallenge.php
index a1e626567e7..55d315d904d 100644
--- a/core/templates/twofactorselectchallenge.php
+++ b/core/templates/twofactorselectchallenge.php
@@ -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): ?>