Skip to content

Commit

Permalink
Make it possible to enforce mandatory 2FA for groups
Browse files Browse the repository at this point in the history
Signed-off-by: Christoph Wurst <christoph@winzerhof-wurst.at>
  • Loading branch information
ChristophWurst committed Oct 15, 2018
1 parent 82a5833 commit 83e994c
Show file tree
Hide file tree
Showing 29 changed files with 727 additions and 157 deletions.
37 changes: 31 additions & 6 deletions core/Command/TwoFactorAuth/Enforce.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@

namespace OC\Core\Command\TwoFactorAuth;

use function implode;
use OC\Authentication\TwoFactorAuth\EnforcementState;
use OC\Authentication\TwoFactorAuth\MandatoryTwoFactor;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
Expand Down Expand Up @@ -58,17 +60,32 @@ protected function configure() {
InputOption::VALUE_NONE,
'don\'t enforce two-factor authenticaton'
);
$this->addOption(
'group',
null,
InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY,
'enforce only for the given group(s)'
);
$this->addOption(
'exclude',
null,
InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY,
'exclude mandatory two-factor auth for the given group(s)'
);
}

protected function execute(InputInterface $input, OutputInterface $output) {
if ($input->getOption('on')) {
$this->mandatoryTwoFactor->setEnforced(true);
$enforcedGroups = $input->getOption('group');
$excludedGroups = $input->getOption('exclude');
$this->mandatoryTwoFactor->setState(new EnforcementState(true, $enforcedGroups, $excludedGroups));
} elseif ($input->getOption('off')) {
$this->mandatoryTwoFactor->setEnforced(false);
$this->mandatoryTwoFactor->setState(new EnforcementState(false));
}

if ($this->mandatoryTwoFactor->isEnforced()) {
$this->writeEnforced($output);
$state = $this->mandatoryTwoFactor->getState();
if ($state->isEnforced()) {
$this->writeEnforced($output, $state);
} else {
$this->writeNotEnforced($output);
}
Expand All @@ -77,8 +94,16 @@ protected function execute(InputInterface $input, OutputInterface $output) {
/**
* @param OutputInterface $output
*/
protected function writeEnforced(OutputInterface $output) {
$output->writeln('Two-factor authentication is enforced for all users');
protected function writeEnforced(OutputInterface $output, EnforcementState $state) {
if (empty($state->getEnforcedGroups())) {
$message = 'Two-factor authentication is enforced for all users';
} else {
$message = 'Two-factor authentication is enforced for members of the group(s) ' . implode(', ', $state->getEnforcedGroups());
}
if (!empty($state->getExcludedGroups())) {
$message .= ', except members of ' . implode(', ', $state->getExcludedGroups());
}
$output->writeln($message);
}

/**
Expand Down
1 change: 1 addition & 0 deletions lib/composer/composer/autoload_classmap.php
Original file line number Diff line number Diff line change
Expand Up @@ -460,6 +460,7 @@
'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\\EnforcementState' => $baseDir . '/lib/private/Authentication/TwoFactorAuth/EnforcementState.php',
'OC\\Authentication\\TwoFactorAuth\\Manager' => $baseDir . '/lib/private/Authentication/TwoFactorAuth/Manager.php',
'OC\\Authentication\\TwoFactorAuth\\MandatoryTwoFactor' => $baseDir . '/lib/private/Authentication/TwoFactorAuth/MandatoryTwoFactor.php',
'OC\\Authentication\\TwoFactorAuth\\ProviderLoader' => $baseDir . '/lib/private/Authentication/TwoFactorAuth/ProviderLoader.php',
Expand Down
1 change: 1 addition & 0 deletions lib/composer/composer/autoload_static.php
Original file line number Diff line number Diff line change
Expand Up @@ -490,6 +490,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c
'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\\EnforcementState' => __DIR__ . '/../../..' . '/lib/private/Authentication/TwoFactorAuth/EnforcementState.php',
'OC\\Authentication\\TwoFactorAuth\\Manager' => __DIR__ . '/../../..' . '/lib/private/Authentication/TwoFactorAuth/Manager.php',
'OC\\Authentication\\TwoFactorAuth\\MandatoryTwoFactor' => __DIR__ . '/../../..' . '/lib/private/Authentication/TwoFactorAuth/MandatoryTwoFactor.php',
'OC\\Authentication\\TwoFactorAuth\\ProviderLoader' => __DIR__ . '/../../..' . '/lib/private/Authentication/TwoFactorAuth/ProviderLoader.php',
Expand Down
85 changes: 85 additions & 0 deletions lib/private/Authentication/TwoFactorAuth/EnforcementState.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
<?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 JsonSerializable;

class EnforcementState implements JsonSerializable {

/** @var bool */
private $enforced;

/** @var array */
private $enforcedGroups;

/** @var array */
private $excludedGroups;

/**
* EnforcementState constructor.
*
* @param bool $enforced
* @param string[] $enforcedGroups
* @param string[] $excludedGroups
*/
public function __construct(bool $enforced,
array $enforcedGroups = [],
array $excludedGroups = []) {
$this->enforced = $enforced;
$this->enforcedGroups = $enforcedGroups;
$this->excludedGroups = $excludedGroups;
}

/**
* @return string[]
*/
public function isEnforced(): bool {
return $this->enforced;
}

/**
* @return string[]
*/
public function getEnforcedGroups(): array {
return $this->enforcedGroups;
}

/**
* @return string[]
*/
public function getExcludedGroups(): array {
return $this->excludedGroups;
}

public function jsonSerialize(): array {
return [
'enforced' => $this->enforced,
'enforcedGroups' => $this->enforcedGroups,
'excludedGroups' => $this->excludedGroups,
];
}

}
2 changes: 1 addition & 1 deletion lib/private/Authentication/TwoFactorAuth/Manager.php
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ public function __construct(ProviderLoader $providerLoader,
* @return boolean
*/
public function isTwoFactorAuthenticated(IUser $user): bool {
if ($this->mandatoryTwoFactor->isEnforced()) {
if ($this->mandatoryTwoFactor->isEnforcedFor($user)) {
return true;
}

Expand Down
77 changes: 72 additions & 5 deletions lib/private/Authentication/TwoFactorAuth/MandatoryTwoFactor.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,22 +27,89 @@
namespace OC\Authentication\TwoFactorAuth;

use OCP\IConfig;
use OCP\IGroupManager;
use OCP\IUser;

class MandatoryTwoFactor {

/** @var IConfig */
private $config;

public function __construct(IConfig $config) {
/** @var IGroupManager */
private $groupManager;

public function __construct(IConfig $config, IGroupManager $groupManager) {
$this->config = $config;
$this->groupManager = $groupManager;
}

public function isEnforced(): bool {
return $this->config->getSystemValue('twofactor_enforced', 'false') === 'true';
/**
* Get the state of enforced two-factor auth
*/
public function getState(): EnforcementState {
return new EnforcementState(
$this->config->getSystemValue('twofactor_enforced', 'false') === 'true',
$this->config->getSystemValue('twofactor_enforced_groups', []),
$this->config->getSystemValue('twofactor_enforced_excluded_groups', [])
);
}

public function setEnforced(bool $enforced) {
$this->config->setSystemValue('twofactor_enforced', $enforced ? 'true' : 'false');
/**
* Set the state of enforced two-factor auth
*/
public function setState(EnforcementState $state) {
$this->config->setSystemValue('twofactor_enforced', $state->isEnforced() ? 'true' : 'false');
$this->config->setSystemValue('twofactor_enforced_groups', $state->getEnforcedGroups());
$this->config->setSystemValue('twofactor_enforced_excluded_groups', $state->getExcludedGroups());
}

/**
* Check if two-factor auth is enforced for a specific user
*
* The admin(s) can enforce two-factor auth system-wide, for certain groups only
* and also have the option to exclude users of certain groups. This method will
* check their membership of those groups.
*
* @param IUser $user
*
* @return bool
*/
public function isEnforcedFor(IUser $user): bool {
$state = $this->getState();
if (!$state->isEnforced()) {
return false;
}
$uid = $user->getUID();

/*
* If there is a list of enforced groups, we only enforce 2FA for members of those groups.
* For all the other users it is not enforced (overruling the excluded groups list).
*/
if (!empty($state->getEnforcedGroups())) {
foreach ($state->getEnforcedGroups() as $group) {
if ($this->groupManager->isInGroup($uid, $group)) {
return true;
}
}
// Not a member of any of these groups -> no 2FA enforced
return false;
}

/**
* If the user is member of an excluded group, 2FA won't be enforced.
*/
foreach ($state->getExcludedGroups() as $group) {
if ($this->groupManager->isInGroup($uid, $group)) {
return false;
}
}

/**
* No enforced groups configured and user not member of an excluded groups,
* so 2FA is enforced.
*/
return true;
}


}
19 changes: 8 additions & 11 deletions settings/Controller/TwoFactorSettingsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,11 @@

namespace OC\Settings\Controller;

use OC\Authentication\TwoFactorAuth\EnforcementState;
use OC\Authentication\TwoFactorAuth\MandatoryTwoFactor;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http\JSONResponse;
use OCP\AppFramework\Http\Response;
use OCP\IRequest;
use OCP\JSON;

class TwoFactorSettingsController extends Controller {

Expand All @@ -46,18 +45,16 @@ public function __construct(string $appName,
$this->mandatoryTwoFactor = $mandatoryTwoFactor;
}

public function index(): Response {
return new JSONResponse([
'enabled' => $this->mandatoryTwoFactor->isEnforced(),
]);
public function index(): JSONResponse {
return new JSONResponse($this->mandatoryTwoFactor->getState());
}

public function update(bool $enabled): Response {
$this->mandatoryTwoFactor->setEnforced($enabled);
public function update(bool $enforced, array $enforcedGroups = [], array $excludedGroups = []): JSONResponse {
$this->mandatoryTwoFactor->setState(
new EnforcementState($enforced, $enforcedGroups, $excludedGroups)
);

return new JSONResponse([
'enabled' => $enabled
]);
return new JSONResponse($this->mandatoryTwoFactor->getState());
}

}
4 changes: 2 additions & 2 deletions settings/js/0.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion settings/js/0.js.map

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions settings/js/1.js.map

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion settings/js/3.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion settings/js/3.js.map

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion settings/js/4.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion settings/js/4.js.map

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions settings/js/5.js

Large diffs are not rendered by default.

33 changes: 33 additions & 0 deletions settings/js/6.js

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions settings/js/6.js.map

Large diffs are not rendered by default.

109 changes: 101 additions & 8 deletions settings/js/settings-admin-security.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion settings/js/settings-admin-security.js.map

Large diffs are not rendered by default.

Loading

0 comments on commit 83e994c

Please sign in to comment.