Make it possible to enforce mandatory 2FA for groups

Signed-off-by: Christoph Wurst <christoph@winzerhof-wurst.at>
This commit is contained in:
Christoph Wurst 2018-10-11 12:20:18 +02:00
parent 82a5833217
commit 83e994c11f
No known key found for this signature in database
GPG Key ID: CC42AC2A7F0E56D8
29 changed files with 729 additions and 159 deletions

View File

@ -26,6 +26,8 @@ declare(strict_types=1);
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;
@ -58,17 +60,32 @@ class Enforce extends Command {
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);
}
@ -77,8 +94,16 @@ class Enforce extends Command {
/**
* @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);
}
/**

View File

@ -460,6 +460,7 @@ return array(
'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',

View File

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

View File

@ -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,
];
}
}

View File

@ -106,7 +106,7 @@ class Manager {
* @return boolean
*/
public function isTwoFactorAuthenticated(IUser $user): bool {
if ($this->mandatoryTwoFactor->isEnforced()) {
if ($this->mandatoryTwoFactor->isEnforcedFor($user)) {
return true;
}

View File

@ -27,22 +27,89 @@ declare(strict_types=1);
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;
}
}

View File

@ -26,12 +26,11 @@ declare(strict_types=1);
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 {
@ -46,18 +45,16 @@ class TwoFactorSettingsController extends Controller {
$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());
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1
settings/js/1.js.map Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

33
settings/js/6.js Normal file

File diff suppressed because one or more lines are too long

1
settings/js/6.js.map Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -3292,8 +3292,7 @@
"ansi-regex": {
"version": "2.1.1",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"aproba": {
"version": "1.2.0",
@ -3314,14 +3313,12 @@
"balanced-match": {
"version": "1.0.0",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"brace-expansion": {
"version": "1.1.11",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@ -3336,20 +3333,17 @@
"code-point-at": {
"version": "1.1.0",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"concat-map": {
"version": "0.0.1",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"console-control-strings": {
"version": "1.1.0",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"core-util-is": {
"version": "1.0.2",
@ -3466,8 +3460,7 @@
"inherits": {
"version": "2.0.3",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"ini": {
"version": "1.3.5",
@ -3479,7 +3472,6 @@
"version": "1.0.0",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"number-is-nan": "^1.0.0"
}
@ -3494,7 +3486,6 @@
"version": "3.0.4",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"brace-expansion": "^1.1.7"
}
@ -3502,14 +3493,12 @@
"minimist": {
"version": "0.0.8",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"minipass": {
"version": "2.2.4",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"safe-buffer": "^5.1.1",
"yallist": "^3.0.0"
@ -3528,7 +3517,6 @@
"version": "0.5.1",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"minimist": "0.0.8"
}
@ -3609,8 +3597,7 @@
"number-is-nan": {
"version": "1.0.1",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"object-assign": {
"version": "4.1.1",
@ -3622,7 +3609,6 @@
"version": "1.4.0",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"wrappy": "1"
}
@ -3708,8 +3694,7 @@
"safe-buffer": {
"version": "5.1.1",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"safer-buffer": {
"version": "2.1.2",
@ -3745,7 +3730,6 @@
"version": "1.0.2",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0",
@ -3765,7 +3749,6 @@
"version": "3.0.1",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"ansi-regex": "^2.0.0"
}
@ -3809,14 +3792,12 @@
"wrappy": {
"version": "1.0.2",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"yallist": {
"version": "3.0.2",
"bundled": true,
"dev": true,
"optional": true
"dev": true
}
}
},
@ -4689,10 +4670,9 @@
}
},
"lodash": {
"version": "4.17.5",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.5.tgz",
"integrity": "sha512-svL3uiZf1RwhH+cWrfZn3A4+U58wbP0tGVTLQPbjplZxZ8ROD9VLuNgsRniTlLe7OlSqR79RUehXgpBW/s0IQw==",
"dev": true
"version": "4.17.11",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz",
"integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg=="
},
"lodash.assign": {
"version": "4.2.0",

View File

@ -12,6 +12,7 @@
},
"dependencies": {
"@babel/polyfill": "^7.0.0",
"lodash": "^4.17.11",
"nextcloud-axios": "^0.1.2",
"nextcloud-vue": "^0.2.0",
"v-tooltip": "^2.0.0-rc.33",

View File

@ -1,7 +1,7 @@
<template>
<div>
<p>
{{ t('settings', 'Two-factor authentication can be enforced for all users. If they do not have a two-factor provider configured, they will be unable to log into the system.') }}
{{ t('settings', 'Two-factor authentication can be enforced for all users and specific groups. If they do not have a two-factor provider configured, they will be unable to log into the system.') }}
</p>
<p v-if="loading">
<span class="icon-loading-small two-factor-loading"></span>
@ -11,22 +11,74 @@
<input type="checkbox"
id="two-factor-enforced"
class="checkbox"
v-model="enabled"
v-on:change="onEnforcedChanged">
v-model="state.enforced"
v-on:change="saveChanges">
<label for="two-factor-enforced">{{ t('settings', 'Enforce two-factor authentication') }}</label>
</p>
<h3>{{ t('settings', 'Limit to groups') }}</h3>
{{ t('settings', 'Enforcement of two-factor authentication can be set for certain groups only.') }}
<p>
{{ t('settings', 'Two-factor authentication is enforced for all members of the following groups.') }}
</p>
<p>
<Multiselect v-model="state.enforcedGroups"
:options="groups"
:placeholder="t('settings', 'Enforced groups')"
:disabled="loading"
:multiple="true"
:searchable="true"
@search-change="searchGroup"
:loading="loadingGroups"
:show-no-options="false"
:close-on-select="false">
</Multiselect>
</p>
<p>
{{ t('settings', 'Two-factor authentication is not enforced for members of the following groups.') }}
</p>
<p>
<Multiselect v-model="state.excludedGroups"
:options="groups"
:placeholder="t('settings', 'Excluded groups')"
:disabled="loading"
:multiple="true"
:searchable="true"
@search-change="searchGroup"
:loading="loadingGroups"
:show-no-options="false"
:close-on-select="false">
</Multiselect>
</p>
<p>
<button class="button primary"
v-on:click="saveChanges"
:disabled="loading">
{{ t('settings', 'Save changes') }}
</button>
</p>
</div>
</template>
<script>
import Axios from 'nextcloud-axios'
import {Multiselect} from 'nextcloud-vue'
import _ from 'lodash'
export default {
name: "AdminTwoFactor",
components: {
Multiselect
},
data () {
return {
enabled: false,
loading: false
state: {
enforced: false,
enforcedGroups: [],
excludedGroups: [],
},
loading: false,
groups: [],
loadingGroups: false,
}
},
mounted () {
@ -34,33 +86,45 @@
Axios.get(OC.generateUrl('/settings/api/admin/twofactorauth'))
.then(resp => resp.data)
.then(state => {
this.enabled = state.enabled
this.state = state
// Groups are loaded dynamically, but the assigned ones *should*
// be valid groups, so let's add them as initial state
this.groups = _.sortedUniq(this.state.enforcedGroups.concat(this.state.excludedGroups))
this.loading = false
console.info('loaded')
})
.catch(err => {
console.error(error)
this.loading = false
console.error('Could not load two-factor state', err)
throw err
})
},
methods: {
onEnforcedChanged () {
searchGroup: _.debounce(function (query) {
this.loadingGroups = true
Axios.get(OC.linkToOCS(`cloud/groups?offset=0&search=${encodeURIComponent(query)}&limit=20`, 2))
.then(res => res.data.ocs)
.then(ocs => ocs.data.groups)
.then(groups => this.groups = _.sortedUniq(this.groups.concat(groups)))
.catch(err => console.error('could not search groups', err))
.then(() => this.loadingGroups = false)
}, 500),
saveChanges () {
this.loading = true
const data = {
enabled: this.enabled
}
Axios.put(OC.generateUrl('/settings/api/admin/twofactorauth'), data)
const oldState = this.state
Axios.put(OC.generateUrl('/settings/api/admin/twofactorauth'), this.state)
.then(resp => resp.data)
.then(state => {
this.enabled = state.enabled
this.loading = false
})
.then(state => this.state = state)
.catch(err => {
console.error(error)
this.loading = false
throw err
console.error('could not save changes', err)
// Restore
this.state = oldState
})
.then(() => this.loading = false)
}
}
}

View File

@ -26,6 +26,7 @@ declare(strict_types=1);
namespace Tests\Core\Command\TwoFactorAuth;
use OC\Authentication\TwoFactorAuth\EnforcementState;
use OC\Authentication\TwoFactorAuth\MandatoryTwoFactor;
use OC\Core\Command\TwoFactorAuth\Enforce;
use PHPUnit\Framework\MockObject\MockObject;
@ -51,11 +52,11 @@ class EnforceTest extends TestCase {
public function testEnforce() {
$this->mandatoryTwoFactor->expects($this->once())
->method('setEnforced')
->with(true);
->method('setState')
->with($this->equalTo(new EnforcementState(true)));
$this->mandatoryTwoFactor->expects($this->once())
->method('isEnforced')
->willReturn(true);
->method('getState')
->willReturn(new EnforcementState(true));
$rc = $this->command->execute([
'--on' => true,
@ -66,13 +67,49 @@ class EnforceTest extends TestCase {
$this->assertContains("Two-factor authentication is enforced for all users", $display);
}
public function testEnforceForOneGroup() {
$this->mandatoryTwoFactor->expects($this->once())
->method('setState')
->with($this->equalTo(new EnforcementState(true, ['twofactorers'])));
$this->mandatoryTwoFactor->expects($this->once())
->method('getState')
->willReturn(new EnforcementState(true, ['twofactorers']));
$rc = $this->command->execute([
'--on' => true,
'--group' => ['twofactorers'],
]);
$this->assertEquals(0, $rc);
$display = $this->command->getDisplay();
$this->assertContains("Two-factor authentication is enforced for members of the group(s) twofactorers", $display);
}
public function testEnforceForAllExceptOneGroup() {
$this->mandatoryTwoFactor->expects($this->once())
->method('setState')
->with($this->equalTo(new EnforcementState(true, [], ['yoloers'])));
$this->mandatoryTwoFactor->expects($this->once())
->method('getState')
->willReturn(new EnforcementState(true, [], ['yoloers']));
$rc = $this->command->execute([
'--on' => true,
'--exclude' => ['yoloers'],
]);
$this->assertEquals(0, $rc);
$display = $this->command->getDisplay();
$this->assertContains("Two-factor authentication is enforced for all users, except members of yoloers", $display);
}
public function testDisableEnforced() {
$this->mandatoryTwoFactor->expects($this->once())
->method('setEnforced')
->with(false);
->method('setState')
->with(new EnforcementState(false));
$this->mandatoryTwoFactor->expects($this->once())
->method('isEnforced')
->willReturn(false);
->method('getState')
->willReturn(new EnforcementState(false));
$rc = $this->command->execute([
'--off' => true,
@ -85,8 +122,8 @@ class EnforceTest extends TestCase {
public function testCurrentStateEnabled() {
$this->mandatoryTwoFactor->expects($this->once())
->method('isEnforced')
->willReturn(true);
->method('getState')
->willReturn(new EnforcementState(true));
$rc = $this->command->execute([]);
@ -97,8 +134,8 @@ class EnforceTest extends TestCase {
public function testCurrentStateDisabled() {
$this->mandatoryTwoFactor->expects($this->once())
->method('isEnforced')
->willReturn(false);
->method('getState')
->willReturn(new EnforcementState(false));
$rc = $this->command->execute([]);

View File

@ -22,6 +22,7 @@
namespace Tests\Settings\Controller;
use OC\Authentication\TwoFactorAuth\EnforcementState;
use OC\Authentication\TwoFactorAuth\MandatoryTwoFactor;
use OC\Settings\Controller\TwoFactorSettingsController;
use OCP\AppFramework\Http\JSONResponse;
@ -54,12 +55,11 @@ class TwoFactorSettingsControllerTest extends TestCase {
}
public function testIndex() {
$state = new EnforcementState(true);
$this->mandatoryTwoFactor->expects($this->once())
->method('isEnforced')
->willReturn(true);
$expected = new JSONResponse([
'enabled' => true,
]);
->method('getState')
->willReturn($state);
$expected = new JSONResponse($state);
$resp = $this->controller->index();
@ -67,12 +67,14 @@ class TwoFactorSettingsControllerTest extends TestCase {
}
public function testUpdate() {
$state = new EnforcementState(true);
$this->mandatoryTwoFactor->expects($this->once())
->method('setEnforced')
->with(true);
$expected = new JSONResponse([
'enabled' => true,
]);
->method('setState')
->with($this->equalTo(new EnforcementState(true)));
$this->mandatoryTwoFactor->expects($this->once())
->method('getState')
->willReturn($state);
$expected = new JSONResponse($state);
$resp = $this->controller->update(true);

View File

@ -0,0 +1,67 @@
<?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/>.
*/
/**
* Created by PhpStorm.
* User: christoph
* Date: 11.10.18
* Time: 13:01
*/
namespace Tests\Authentication\TwoFactorAuth;
use OC\Authentication\TwoFactorAuth\EnforcementState;
use Test\TestCase;
class EnforcementStateTest extends TestCase {
public function testIsEnforced() {
$state = new EnforcementState(true);
$this->assertTrue($state->isEnforced());
}
public function testGetEnforcedGroups() {
$state = new EnforcementState(true, ['twofactorers']);
$this->assertEquals(['twofactorers'], $state->getEnforcedGroups());
}
public function testGetExcludedGroups() {
$state = new EnforcementState(true, [], ['yoloers']);
$this->assertEquals(['yoloers'], $state->getExcludedGroups());
}
public function testJsonSerialize() {
$state = new EnforcementState(true, ['twofactorers'], ['yoloers']);
$expected = [
'enforced' => true,
'enforcedGroups' => ['twofactorers'],
'excludedGroups' => ['yoloers'],
];
$json = $state->jsonSerialize();
$this->assertEquals($expected, $json);
}
}

View File

@ -37,58 +37,59 @@ use OCP\IConfig;
use OCP\ILogger;
use OCP\ISession;
use OCP\IUser;
use PHPUnit\Framework\MockObject\MockObject;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Test\TestCase;
class ManagerTest extends TestCase {
/** @var IUser|\PHPUnit_Framework_MockObject_MockObject */
/** @var IUser|MockObject */
private $user;
/** @var ProviderLoader|\PHPUnit_Framework_MockObject_MockObject */
/** @var ProviderLoader|MockObject */
private $providerLoader;
/** @var IRegistry|\PHPUnit_Framework_MockObject_MockObject */
/** @var IRegistry|MockObject */
private $providerRegistry;
/** @var MandatoryTwoFactor|\PHPUnit_Framework_MockObject_MockObject */
/** @var MandatoryTwoFactor|MockObject */
private $mandatoryTwoFactor;
/** @var ISession|\PHPUnit_Framework_MockObject_MockObject */
/** @var ISession|MockObject */
private $session;
/** @var Manager */
private $manager;
/** @var IConfig|\PHPUnit_Framework_MockObject_MockObject */
/** @var IConfig|MockObject */
private $config;
/** @var IManager|\PHPUnit_Framework_MockObject_MockObject */
/** @var IManager|MockObject */
private $activityManager;
/** @var ILogger|\PHPUnit_Framework_MockObject_MockObject */
/** @var ILogger|MockObject */
private $logger;
/** @var IProvider|\PHPUnit_Framework_MockObject_MockObject */
/** @var IProvider|MockObject */
private $fakeProvider;
/** @var IProvider|\PHPUnit_Framework_MockObject_MockObject */
/** @var IProvider|MockObject */
private $backupProvider;
/** @var TokenProvider|\PHPUnit_Framework_MockObject_MockObject */
/** @var TokenProvider|MockObject */
private $tokenProvider;
/** @var ITimeFactory|\PHPUnit_Framework_MockObject_MockObject */
/** @var ITimeFactory|MockObject */
private $timeFactory;
/** @var EventDispatcherInterface|\PHPUnit_Framework_MockObject_MockObject */
/** @var EventDispatcherInterface|MockObject */
private $eventDispatcher;
protected function setUp() {
parent::setUp();
$this->user = $this->createMock(IUser::class);
$this->providerLoader = $this->createMock(\OC\Authentication\TwoFactorAuth\ProviderLoader::class);
$this->providerLoader = $this->createMock(ProviderLoader::class);
$this->providerRegistry = $this->createMock(IRegistry::class);
$this->mandatoryTwoFactor = $this->createMock(MandatoryTwoFactor::class);
$this->session = $this->createMock(ISession::class);
@ -150,7 +151,8 @@ class ManagerTest extends TestCase {
public function testIsTwoFactorAuthenticatedEnforced() {
$this->mandatoryTwoFactor->expects($this->once())
->method('isEnforced')
->method('isEnforcedFor')
->with($this->user)
->willReturn(true);
$enabled = $this->manager->isTwoFactorAuthenticated($this->user);
@ -160,7 +162,8 @@ class ManagerTest extends TestCase {
public function testIsTwoFactorAuthenticatedNoProviders() {
$this->mandatoryTwoFactor->expects($this->once())
->method('isEnforced')
->method('isEnforcedFor')
->with($this->user)
->willReturn(false);
$this->providerRegistry->expects($this->once())
->method('getProviderStates')
@ -174,7 +177,8 @@ class ManagerTest extends TestCase {
public function testIsTwoFactorAuthenticatedOnlyBackupCodes() {
$this->mandatoryTwoFactor->expects($this->once())
->method('isEnforced')
->method('isEnforcedFor')
->with($this->user)
->willReturn(false);
$this->providerRegistry->expects($this->once())
->method('getProviderStates')
@ -196,7 +200,8 @@ class ManagerTest extends TestCase {
public function testIsTwoFactorAuthenticatedFailingProviders() {
$this->mandatoryTwoFactor->expects($this->once())
->method('isEnforced')
->method('isEnforcedFor')
->with($this->user)
->willReturn(false);
$this->providerRegistry->expects($this->once())
->method('getProviderStates')

View File

@ -26,8 +26,11 @@ declare(strict_types=1);
namespace Tests\Authentication\TwoFactorAuth;
use OC\Authentication\TwoFactorAuth\EnforcementState;
use OC\Authentication\TwoFactorAuth\MandatoryTwoFactor;
use OCP\IConfig;
use OCP\IGroupManager;
use OCP\IUser;
use PHPUnit\Framework\MockObject\MockObject;
use Test\TestCase;
@ -36,6 +39,9 @@ class MandatoryTwoFactorTest extends TestCase {
/** @var IConfig|MockObject */
private $config;
/** @var IGroupManager|MockObject */
private $groupManager;
/** @var MandatoryTwoFactor */
private $mandatoryTwoFactor;
@ -43,46 +49,150 @@ class MandatoryTwoFactorTest extends TestCase {
parent::setUp();
$this->config = $this->createMock(IConfig::class);
$this->groupManager = $this->createMock(IGroupManager::class);
$this->mandatoryTwoFactor = new MandatoryTwoFactor($this->config);
$this->mandatoryTwoFactor = new MandatoryTwoFactor($this->config, $this->groupManager);
}
public function testIsNotEnforced() {
$this->config->expects($this->once())
$this->config
->method('getSystemValue')
->with('twofactor_enforced', 'false')
->willReturn('false');
->willReturnMap([
['twofactor_enforced', 'false', 'false'],
['twofactor_enforced_groups', [], []],
['twofactor_enforced_excluded_groups', [], []],
]);
$isEnforced = $this->mandatoryTwoFactor->isEnforced();
$state = $this->mandatoryTwoFactor->getState();
$this->assertFalse($state->isEnforced());
}
public function testIsEnforced() {
$this->config
->method('getSystemValue')
->willReturnMap([
['twofactor_enforced', 'false', 'true'],
['twofactor_enforced_groups', [], []],
['twofactor_enforced_excluded_groups', [], []],
]);
$state = $this->mandatoryTwoFactor->getState();
$this->assertTrue($state->isEnforced());
}
public function testIsNotEnforcedForAnybody() {
$user = $this->createMock(IUser::class);
$user->method('getUID')->willReturn('user123');
$this->config
->method('getSystemValue')
->willReturnMap([
['twofactor_enforced', 'false', 'false'],
['twofactor_enforced_groups', [], []],
['twofactor_enforced_excluded_groups', [], []],
]);
$isEnforced = $this->mandatoryTwoFactor->isEnforcedFor($user);
$this->assertFalse($isEnforced);
}
public function testIsEnforced() {
$this->config->expects($this->once())
public function testIsEnforcedForAGroupMember() {
$user = $this->createMock(IUser::class);
$user->method('getUID')->willReturn('user123');
$this->config
->method('getSystemValue')
->with('twofactor_enforced', 'false')
->willReturn('true');
->willReturnMap([
['twofactor_enforced', 'false', 'true'],
['twofactor_enforced_groups', [], ['twofactorers']],
['twofactor_enforced_excluded_groups', [], []],
]);
$this->groupManager->method('isInGroup')
->willReturnCallback(function($user, $group) {
return $user === 'user123' && $group ==='twofactorers';
});
$isEnforced = $this->mandatoryTwoFactor->isEnforced();
$isEnforced = $this->mandatoryTwoFactor->isEnforcedFor($user);
$this->assertTrue($isEnforced);
}
public function testSetEnforced() {
$this->config->expects($this->once())
->method('setSystemValue')
->with('twofactor_enforced', 'true');
public function testIsEnforcedForOtherGroups() {
$user = $this->createMock(IUser::class);
$user->method('getUID')->willReturn('user123');
$this->config
->method('getSystemValue')
->willReturnMap([
['twofactor_enforced', 'false', 'true'],
['twofactor_enforced_groups', [], ['twofactorers']],
['twofactor_enforced_excluded_groups', [], []],
]);
$this->groupManager->method('isInGroup')
->willReturn(false);
$this->mandatoryTwoFactor->setEnforced(true);
$isEnforced = $this->mandatoryTwoFactor->isEnforcedFor($user);
$this->assertFalse($isEnforced);
}
public function testIsEnforcedButMemberOfExcludedGroup() {
$user = $this->createMock(IUser::class);
$user->method('getUID')->willReturn('user123');
$this->config
->method('getSystemValue')
->willReturnMap([
['twofactor_enforced', 'false', 'true'],
['twofactor_enforced_groups', [], []],
['twofactor_enforced_excluded_groups', [], ['yoloers']],
]);
$this->groupManager->method('isInGroup')
->willReturnCallback(function($user, $group) {
return $user === 'user123' && $group ==='yoloers';
});
$isEnforced = $this->mandatoryTwoFactor->isEnforcedFor($user);
$this->assertFalse($isEnforced);
}
public function testSetEnforced() {
$this->config
->expects($this->exactly(3))
->method('setSystemValue')
->willReturnMap([
['twofactor_enforced', 'true'],
['twofactor_enforced_groups', []],
['twofactor_enforced_excluded_groups', []],
]);
$this->mandatoryTwoFactor->setState(new EnforcementState(true));
}
public function testSetEnforcedForGroups() {
$this->config
->expects($this->exactly(3))
->method('setSystemValue')
->willReturnMap([
['twofactor_enforced', 'true'],
['twofactor_enforced_groups', ['twofactorers']],
['twofactor_enforced_excluded_groups', ['yoloers']],
]);
$this->mandatoryTwoFactor->setState(new EnforcementState(true, ['twofactorers'], ['yoloers']));
}
public function testSetNotEnforced() {
$this->config->expects($this->once())
$this->config
->expects($this->exactly(3))
->method('setSystemValue')
->with('twofactor_enforced', 'false');
->willReturnMap([
['twofactor_enforced', 'false'],
['twofactor_enforced_groups', []],
['twofactor_enforced_excluded_groups', []],
]);
$this->mandatoryTwoFactor->setEnforced(false);
$this->mandatoryTwoFactor->setState(new EnforcementState(false));
}
}