Signed-off-by: Christoph Wurst <christoph@winzerhof-wurst.at>tags/v15.0.0beta1
@@ -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); | |||
} | |||
/** |
@@ -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', |
@@ -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', |
@@ -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, | |||
]; | |||
} | |||
} |
@@ -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; | |||
} | |||
@@ -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; | |||
} | |||
} |
@@ -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()); | |||
} | |||
} |
@@ -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", |
@@ -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", |
@@ -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) | |||
} | |||
} | |||
} |
@@ -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([]); | |||
@@ -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); | |||
@@ -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); | |||
} | |||
} |
@@ -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') |
@@ -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)); | |||
} | |||
} |