Browse Source

Add admin interface to enforce 2FA

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

+ 1
- 0
lib/composer/composer/autoload_classmap.php View File

@@ -993,6 +993,7 @@ return array(
'OC\\Settings\\Controller\\LogSettingsController' => $baseDir . '/settings/Controller/LogSettingsController.php',
'OC\\Settings\\Controller\\MailSettingsController' => $baseDir . '/settings/Controller/MailSettingsController.php',
'OC\\Settings\\Controller\\PersonalSettingsController' => $baseDir . '/settings/Controller/PersonalSettingsController.php',
'OC\\Settings\\Controller\\TwoFactorSettingsController' => $baseDir . '/settings/Controller/TwoFactorSettingsController.php',
'OC\\Settings\\Controller\\UsersController' => $baseDir . '/settings/Controller/UsersController.php',
'OC\\Settings\\Hooks' => $baseDir . '/settings/Hooks.php',
'OC\\Settings\\Mailer\\NewUserMailHelper' => $baseDir . '/settings/Mailer/NewUserMailHelper.php',

+ 1
- 0
lib/composer/composer/autoload_static.php View File

@@ -1023,6 +1023,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c
'OC\\Settings\\Controller\\LogSettingsController' => __DIR__ . '/../../..' . '/settings/Controller/LogSettingsController.php',
'OC\\Settings\\Controller\\MailSettingsController' => __DIR__ . '/../../..' . '/settings/Controller/MailSettingsController.php',
'OC\\Settings\\Controller\\PersonalSettingsController' => __DIR__ . '/../../..' . '/settings/Controller/PersonalSettingsController.php',
'OC\\Settings\\Controller\\TwoFactorSettingsController' => __DIR__ . '/../../..' . '/settings/Controller/TwoFactorSettingsController.php',
'OC\\Settings\\Controller\\UsersController' => __DIR__ . '/../../..' . '/settings/Controller/UsersController.php',
'OC\\Settings\\Hooks' => __DIR__ . '/../../..' . '/settings/Hooks.php',
'OC\\Settings\\Mailer\\NewUserMailHelper' => __DIR__ . '/../../..' . '/settings/Mailer/NewUserMailHelper.php',

+ 63
- 0
settings/Controller/TwoFactorSettingsController.php View File

@@ -0,0 +1,63 @@
<?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\Settings\Controller;

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 {

/** @var MandatoryTwoFactor */
private $mandatoryTwoFactor;

public function __construct(string $appName,
IRequest $request,
MandatoryTwoFactor $mandatoryTwoFactor) {
parent::__construct($appName, $request);

$this->mandatoryTwoFactor = $mandatoryTwoFactor;
}

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

public function update(bool $enabled): Response {
$this->mandatoryTwoFactor->setEnforced($enabled);

return new JSONResponse([
'enabled' => $enabled
]);
}

}

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


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


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


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


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


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


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


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


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


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


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


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


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


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


+ 1
- 0
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


+ 4
- 4
settings/package-lock.json View File

@@ -3196,11 +3196,11 @@
}
},
"follow-redirects": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.4.1.tgz",
"integrity": "sha512-uxYePVPogtya1ktGnAAXOacnbIuRMB4dkvqeNz2qTtTQsuzSfbDolV+wMMKxAmCx0bLgAKLbBOkjItMbbkR1vg==",
"version": "1.5.8",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.8.tgz",
"integrity": "sha512-sy1mXPmv7kLAMKW/8XofG7o9T+6gAjzdZK4AJF6ryqQYUa/hnzgiypoeUecZ53x7XiqKNEpNqLtS97MshW2nxg==",
"requires": {
"debug": "^3.1.0"
"debug": "=3.1.0"
}
},
"for-in": {

+ 3
- 1
settings/routes.php View File

@@ -80,7 +80,9 @@ $application->registerRoutes($this, [
['name' => 'AdminSettings#index', 'url' => '/settings/admin/{section}', 'verb' => 'GET', 'defaults' => ['section' => 'server']],
['name' => 'AdminSettings#form', 'url' => '/settings/admin/{section}', 'verb' => 'GET'],
['name' => 'ChangePassword#changePersonalPassword', 'url' => '/settings/personal/changepassword', 'verb' => 'POST'],
['name' => 'ChangePassword#changeUserPassword', 'url' => '/settings/users/changepassword', 'verb' => 'POST']
['name' => 'ChangePassword#changeUserPassword', 'url' => '/settings/users/changepassword', 'verb' => 'POST'],
['name' => 'TwoFactorSettings#index', 'url' => '/settings/api/admin/twofactorauth', 'verb' => 'GET'],
['name' => 'TwoFactorSettings#update', 'url' => '/settings/api/admin/twofactorauth', 'verb' => 'PUT'],
]
]);


+ 76
- 0
settings/src/components/AdminTwoFactor.vue View File

@@ -0,0 +1,76 @@
<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.') }}
</p>
<p v-if="loading">
<span class="icon-loading-small two-factor-loading"></span>
<span>{{ t('settings', 'Enforce two-factor authentication') }}</span>
</p>
<p v-else>
<input type="checkbox"
id="two-factor-enforced"
class="checkbox"
v-model="enabled"
v-on:change="onEnforcedChanged">
<label for="two-factor-enforced">{{ t('settings', 'Enforce two-factor authentication') }}</label>
</p>
</div>
</template>

<script>
import Axios from 'nextcloud-axios'

export default {
name: "AdminTwoFactor",
data () {
return {
enabled: false,
loading: false
}
},
mounted () {
this.loading = true
Axios.get(OC.generateUrl('/settings/api/admin/twofactorauth'))
.then(resp => resp.data)
.then(state => {
this.enabled = state.enabled
this.loading = false
console.info('loaded')
})
.catch(err => {
console.error(error)
this.loading = false
throw err
})
},
methods: {
onEnforcedChanged () {
this.loading = true
const data = {
enabled: this.enabled
}
Axios.put(OC.generateUrl('/settings/api/admin/twofactorauth'), data)
.then(resp => resp.data)
.then(state => {
this.enabled = state.enabled
this.loading = false
})
.catch(err => {
console.error(error)
this.loading = false
throw err
})
}
}
}
</script>

<style>
.two-factor-loading {
display: inline-block;
vertical-align: sub;
margin-left: -2px;
margin-right: 1px;
}
</style>

+ 13
- 0
settings/src/main-admin-security.js View File

@@ -0,0 +1,13 @@
import Vue from 'vue'

import AdminTwoFactor from './components/AdminTwoFactor'

Vue.prototype.t = t;

new Vue({
el: '#two-factor-auth-settings',
template: '<AdminTwoFactor/>',
components: {
AdminTwoFactor
}
})

+ 7
- 0
settings/templates/settings/admin/security.php View File

@@ -24,8 +24,15 @@
/** @var \OCP\IL10N $l */
/** @var array $_ */

script('settings', 'settings-admin-security');

?>

<div id="two-factor-auth" class="section">
<h2><?php p($l->t('Two-Factor Authentication'));?></h2>
<div id="two-factor-auth-settings"></div>
</div>

<div class="section" id='encryptionAPI'>
<h2><?php p($l->t('Server-side encryption')); ?></h2>
<a target="_blank" rel="noreferrer noopener" class="icon-info"

+ 5
- 2
settings/webpack.common.js View File

@@ -2,11 +2,14 @@ const path = require('path')
const { VueLoaderPlugin } = require('vue-loader');

module.exports = {
entry: './src/main.js',
entry: {
'settings-vue': './src/main.js',
'settings-admin-security': './src/main-admin-security'
},
output: {
path: path.resolve(__dirname, './js'),
publicPath: '/',
filename: 'settings-vue.js'
filename: '[name].js'
},
module: {
rules: [

+ 82
- 0
tests/Settings/Controller/TwoFactorSettingsControllerTest.php View File

@@ -0,0 +1,82 @@
<?php
/**
* @copyright 2018 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @author 2018 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

namespace Tests\Settings\Controller;

use OC\Authentication\TwoFactorAuth\MandatoryTwoFactor;
use OC\Settings\Controller\TwoFactorSettingsController;
use OCP\AppFramework\Http\JSONResponse;
use OCP\IRequest;
use PHPUnit\Framework\MockObject\MockObject;
use Test\TestCase;

class TwoFactorSettingsControllerTest extends TestCase {

/** @var IRequest|MockObject */
private $request;

/** @var MandatoryTwoFactor|MockObject */
private $mandatoryTwoFactor;

/** @var TwoFactorSettingsController */
private $controller;

protected function setUp() {
parent::setUp();

$this->request = $this->createMock(IRequest::class);
$this->mandatoryTwoFactor = $this->createMock(MandatoryTwoFactor::class);

$this->controller = new TwoFactorSettingsController(
'settings',
$this->request,
$this->mandatoryTwoFactor
);
}

public function testIndex() {
$this->mandatoryTwoFactor->expects($this->once())
->method('isEnforced')
->willReturn(true);
$expected = new JSONResponse([
'enabled' => true,
]);

$resp = $this->controller->index();

$this->assertEquals($expected, $resp);
}

public function testUpdate() {
$this->mandatoryTwoFactor->expects($this->once())
->method('setEnforced')
->with(true);
$expected = new JSONResponse([
'enabled' => true,
]);

$resp = $this->controller->update(true);

$this->assertEquals($expected, $resp);
}

}

Loading…
Cancel
Save