Browse Source

Make it possible to enforce mandatory 2FA for groups

Signed-off-by: Christoph Wurst <christoph@winzerhof-wurst.at>
tags/v15.0.0beta1
Christoph Wurst 5 years ago
parent
commit
83e994c11f
No account linked to committer's email address

+ 31
- 6
core/Command/TwoFactorAuth/Enforce.php 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);
}

/**

+ 1
- 0
lib/composer/composer/autoload_classmap.php 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',

+ 1
- 0
lib/composer/composer/autoload_static.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',

+ 85
- 0
lib/private/Authentication/TwoFactorAuth/EnforcementState.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,
];
}

}

+ 1
- 1
lib/private/Authentication/TwoFactorAuth/Manager.php 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;
}


+ 72
- 5
lib/private/Authentication/TwoFactorAuth/MandatoryTwoFactor.php 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;
}


}

+ 8
- 11
settings/Controller/TwoFactorSettingsController.php 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());
}

}

+ 2
- 2
settings/js/0.js
File diff suppressed because it is too large
View File


+ 1
- 1
settings/js/0.js.map
File diff suppressed because it is too large
View File


+ 1
- 0
settings/js/1.js.map
File diff suppressed because it is too large
View File


+ 1
- 1
settings/js/3.js
File diff suppressed because it is too large
View File


+ 1
- 1
settings/js/3.js.map
File diff suppressed because it is too large
View File


+ 1
- 1
settings/js/4.js
File diff suppressed because it is too large
View File


+ 1
- 1
settings/js/4.js.map
File diff suppressed because it is too large
View File


+ 2
- 2
settings/js/5.js
File diff suppressed because it is too large
View File


+ 33
- 0
settings/js/6.js
File diff suppressed because it is too large
View File


+ 1
- 0
settings/js/6.js.map
File diff suppressed because it is too large
View File


+ 101
- 8
settings/js/settings-admin-security.js
File diff suppressed because it is too large
View File


+ 1
- 1
settings/js/settings-admin-security.js.map
File diff suppressed because it is too large
View File


+ 4
- 4
settings/js/settings-vue.js
File diff suppressed because it is too large
View File


+ 1
- 1
settings/js/settings-vue.js.map
File diff suppressed because it is too large
View File


+ 14
- 34
settings/package-lock.json 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",

+ 1
- 0
settings/package.json 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",

+ 85
- 21
settings/src/components/AdminTwoFactor.vue 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)
}
}
}

+ 49
- 12
tests/Core/Command/TwoFactorAuth/EnforceTest.php 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([]);


+ 12
- 10
tests/Settings/Controller/TwoFactorSettingsControllerTest.php 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);


+ 67
- 0
tests/lib/Authentication/TwoFactorAuth/EnforcementStateTest.php 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);
}
}

+ 23
- 18
tests/lib/Authentication/TwoFactorAuth/ManagerTest.php 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')

+ 126
- 16
tests/lib/Authentication/TwoFactorAuth/MandatoryTwoFactorTest.php 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($isEnforced);
$this->assertFalse($state->isEnforced());
}

public function testIsEnforced() {
$this->config->expects($this->once())
$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')
->with('twofactor_enforced', 'false')
->willReturn('true');
->willReturnMap([
['twofactor_enforced', 'false', 'false'],
['twofactor_enforced_groups', [], []],
['twofactor_enforced_excluded_groups', [], []],
]);

$isEnforced = $this->mandatoryTwoFactor->isEnforced();
$isEnforced = $this->mandatoryTwoFactor->isEnforcedFor($user);

$this->assertFalse($isEnforced);
}

public function testIsEnforcedForAGroupMember() {
$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')
->willReturnCallback(function($user, $group) {
return $user === 'user123' && $group ==='twofactorers';
});

$isEnforced = $this->mandatoryTwoFactor->isEnforcedFor($user);

$this->assertTrue($isEnforced);
}

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);

$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->once())
$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')
->with('twofactor_enforced', 'true');
->willReturnMap([
['twofactor_enforced', 'true'],
['twofactor_enforced_groups', ['twofactorers']],
['twofactor_enforced_excluded_groups', ['yoloers']],
]);

$this->mandatoryTwoFactor->setEnforced(true);
$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));
}

}

Loading…
Cancel
Save