mirror of
https://github.com/nextcloud/server.git
synced 2026-02-03 20:41:22 -05:00
refactor(encryption): migrate to Vue 3 and Typescript and script setup
Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
This commit is contained in:
parent
ab8e4e60ea
commit
108858daef
28 changed files with 664 additions and 430 deletions
|
|
@ -1,32 +0,0 @@
|
|||
/**
|
||||
* SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-FileCopyrightText: 2014-2015 ownCloud, Inc.
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
/**
|
||||
* @namespace OC
|
||||
*/
|
||||
OC.Encryption = _.extend(OC.Encryption || {}, {
|
||||
displayEncryptionWarning: function() {
|
||||
if (!OC.currentUser || !OC.Notification.isHidden()) {
|
||||
return
|
||||
}
|
||||
|
||||
$.get(
|
||||
OC.generateUrl('/apps/encryption/ajax/getStatus'),
|
||||
function(result) {
|
||||
if (result.status === 'interactionNeeded') {
|
||||
OC.Notification.show(result.data.message)
|
||||
}
|
||||
},
|
||||
)
|
||||
},
|
||||
})
|
||||
window.addEventListener('DOMContentLoaded', function() {
|
||||
// wait for other apps/extensions to register their event handlers and file actions
|
||||
// in the "ready" clause
|
||||
_.defer(function() {
|
||||
OC.Encryption.displayEncryptionWarning()
|
||||
})
|
||||
})
|
||||
|
|
@ -1,80 +0,0 @@
|
|||
/**
|
||||
* SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-FileCopyrightText: 2013-2015 ownCloud, Inc.
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
window.addEventListener('DOMContentLoaded', function() {
|
||||
$('input:button[name="enableRecoveryKey"]').click(function() {
|
||||
const recoveryStatus = $(this).attr('status')
|
||||
const newRecoveryStatus = (1 + parseInt(recoveryStatus)) % 2
|
||||
const buttonValue = $(this).attr('value')
|
||||
|
||||
const recoveryPassword = $('#encryptionRecoveryPassword').val()
|
||||
const confirmPassword = $('#repeatEncryptionRecoveryPassword').val()
|
||||
OC.msg.startSaving('#encryptionSetRecoveryKey .msg')
|
||||
$.post(
|
||||
OC.generateUrl('/apps/encryption/ajax/adminRecovery'),
|
||||
{
|
||||
adminEnableRecovery: newRecoveryStatus,
|
||||
recoveryPassword,
|
||||
confirmPassword,
|
||||
},
|
||||
).done(function(data) {
|
||||
OC.msg.finishedSuccess('#encryptionSetRecoveryKey .msg', data.data.message)
|
||||
|
||||
if (newRecoveryStatus === 0) {
|
||||
$('p[name="changeRecoveryPasswordBlock"]').addClass('hidden')
|
||||
$('input:button[name="enableRecoveryKey"]').attr('value', 'Enable recovery key')
|
||||
$('input:button[name="enableRecoveryKey"]').attr('status', '0')
|
||||
} else {
|
||||
$('input:password[name="changeRecoveryPassword"]').val('')
|
||||
$('p[name="changeRecoveryPasswordBlock"]').removeClass('hidden')
|
||||
$('input:button[name="enableRecoveryKey"]').attr('value', 'Disable recovery key')
|
||||
$('input:button[name="enableRecoveryKey"]').attr('status', '1')
|
||||
}
|
||||
})
|
||||
.fail(function(jqXHR) {
|
||||
$('input:button[name="enableRecoveryKey"]').attr('value', buttonValue)
|
||||
$('input:button[name="enableRecoveryKey"]').attr('status', recoveryStatus)
|
||||
OC.msg.finishedError('#encryptionSetRecoveryKey .msg', JSON.parse(jqXHR.responseText).data.message)
|
||||
})
|
||||
})
|
||||
|
||||
$('#repeatEncryptionRecoveryPassword').keyup(function(event) {
|
||||
if (event.keyCode == 13) {
|
||||
$('#enableRecoveryKey').click()
|
||||
}
|
||||
})
|
||||
|
||||
// change recovery password
|
||||
|
||||
$('button:button[name="submitChangeRecoveryKey"]').click(function() {
|
||||
const oldRecoveryPassword = $('#oldEncryptionRecoveryPassword').val()
|
||||
const newRecoveryPassword = $('#newEncryptionRecoveryPassword').val()
|
||||
const confirmNewPassword = $('#repeatedNewEncryptionRecoveryPassword').val()
|
||||
OC.msg.startSaving('#encryptionChangeRecoveryKey .msg')
|
||||
$.post(
|
||||
OC.generateUrl('/apps/encryption/ajax/changeRecoveryPassword'),
|
||||
{
|
||||
oldPassword: oldRecoveryPassword,
|
||||
newPassword: newRecoveryPassword,
|
||||
confirmPassword: confirmNewPassword,
|
||||
},
|
||||
).done(function(data) {
|
||||
OC.msg.finishedSuccess('#encryptionChangeRecoveryKey .msg', data.data.message)
|
||||
})
|
||||
.fail(function(jqXHR) {
|
||||
OC.msg.finishedError('#encryptionChangeRecoveryKey .msg', JSON.parse(jqXHR.responseText).data.message)
|
||||
})
|
||||
})
|
||||
|
||||
$('#encryptHomeStorage').change(function() {
|
||||
$.post(
|
||||
OC.generateUrl('/apps/encryption/ajax/setEncryptHomeStorage'),
|
||||
{
|
||||
encryptHomeStorage: this.checked,
|
||||
},
|
||||
)
|
||||
})
|
||||
})
|
||||
|
|
@ -1,64 +0,0 @@
|
|||
/**
|
||||
* SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-FileCopyrightText: 2013-2015 ownCloud, Inc.
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
OC.Encryption = _.extend(OC.Encryption || {}, {
|
||||
updatePrivateKeyPassword: function() {
|
||||
const oldPrivateKeyPassword = $('input:password[id="oldPrivateKeyPassword"]').val()
|
||||
const newPrivateKeyPassword = $('input:password[id="newPrivateKeyPassword"]').val()
|
||||
OC.msg.startSaving('#ocDefaultEncryptionModule .msg')
|
||||
$.post(
|
||||
OC.generateUrl('/apps/encryption/ajax/updatePrivateKeyPassword'),
|
||||
{
|
||||
oldPassword: oldPrivateKeyPassword,
|
||||
newPassword: newPrivateKeyPassword,
|
||||
},
|
||||
).done(function(data) {
|
||||
OC.msg.finishedSuccess('#ocDefaultEncryptionModule .msg', data.message)
|
||||
}).fail(function(jqXHR) {
|
||||
OC.msg.finishedError('#ocDefaultEncryptionModule .msg', JSON.parse(jqXHR.responseText).message)
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
window.addEventListener('DOMContentLoaded', function() {
|
||||
// Trigger ajax on recoveryAdmin status change
|
||||
$('input:radio[name="userEnableRecovery"]').change(function() {
|
||||
const recoveryStatus = $(this).val()
|
||||
OC.msg.startAction('#userEnableRecovery .msg', 'Updating recovery keys. This can take some time...')
|
||||
$.post(
|
||||
OC.generateUrl('/apps/encryption/ajax/userSetRecovery'),
|
||||
{
|
||||
userEnableRecovery: recoveryStatus,
|
||||
},
|
||||
).done(function(data) {
|
||||
OC.msg.finishedSuccess('#userEnableRecovery .msg', data.data.message)
|
||||
})
|
||||
.fail(function(jqXHR) {
|
||||
OC.msg.finishedError('#userEnableRecovery .msg', JSON.parse(jqXHR.responseText).data.message)
|
||||
})
|
||||
// Ensure page is not reloaded on form submit
|
||||
return false
|
||||
})
|
||||
|
||||
// update private key password
|
||||
|
||||
$('input:password[name="changePrivateKeyPassword"]').keyup(function(event) {
|
||||
const oldPrivateKeyPassword = $('input:password[id="oldPrivateKeyPassword"]').val()
|
||||
const newPrivateKeyPassword = $('input:password[id="newPrivateKeyPassword"]').val()
|
||||
if (newPrivateKeyPassword !== '' && oldPrivateKeyPassword !== '') {
|
||||
$('button:button[name="submitChangePrivateKeyPassword"]').removeAttr('disabled')
|
||||
if (event.which === 13) {
|
||||
OC.Encryption.updatePrivateKeyPassword()
|
||||
}
|
||||
} else {
|
||||
$('button:button[name="submitChangePrivateKeyPassword"]').attr('disabled', 'true')
|
||||
}
|
||||
})
|
||||
|
||||
$('button:button[name="submitChangePrivateKeyPassword"]').click(function() {
|
||||
OC.Encryption.updatePrivateKeyPassword()
|
||||
})
|
||||
})
|
||||
|
|
@ -12,35 +12,23 @@ use OCP\AppFramework\Controller;
|
|||
use OCP\AppFramework\Http;
|
||||
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
|
||||
use OCP\AppFramework\Http\DataResponse;
|
||||
use OCP\IConfig;
|
||||
use OCP\Encryption\Exceptions\GenericEncryptionException;
|
||||
use OCP\IL10N;
|
||||
use OCP\IRequest;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
class RecoveryController extends Controller {
|
||||
/**
|
||||
* @param string $AppName
|
||||
* @param IRequest $request
|
||||
* @param IConfig $config
|
||||
* @param IL10N $l
|
||||
* @param Recovery $recovery
|
||||
*/
|
||||
public function __construct(
|
||||
$appName,
|
||||
string $appName,
|
||||
IRequest $request,
|
||||
private IConfig $config,
|
||||
private IL10N $l,
|
||||
private Recovery $recovery,
|
||||
private LoggerInterface $logger,
|
||||
) {
|
||||
parent::__construct($appName, $request);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $recoveryPassword
|
||||
* @param string $confirmPassword
|
||||
* @param string $adminEnableRecovery
|
||||
* @return DataResponse
|
||||
*/
|
||||
public function adminRecovery($recoveryPassword, $confirmPassword, $adminEnableRecovery) {
|
||||
public function adminRecovery(string $recoveryPassword, string $confirmPassword, bool $adminEnableRecovery): DataResponse {
|
||||
// Check if both passwords are the same
|
||||
if (empty($recoveryPassword)) {
|
||||
$errorMessage = $this->l->t('Missing recovery key password');
|
||||
|
|
@ -60,28 +48,28 @@ class RecoveryController extends Controller {
|
|||
Http::STATUS_BAD_REQUEST);
|
||||
}
|
||||
|
||||
if (isset($adminEnableRecovery) && $adminEnableRecovery === '1') {
|
||||
if ($this->recovery->enableAdminRecovery($recoveryPassword)) {
|
||||
return new DataResponse(['data' => ['message' => $this->l->t('Recovery key successfully enabled')]]);
|
||||
try {
|
||||
if ($adminEnableRecovery) {
|
||||
if ($this->recovery->enableAdminRecovery($recoveryPassword)) {
|
||||
return new DataResponse(['data' => ['message' => $this->l->t('Recovery key successfully enabled')]]);
|
||||
}
|
||||
return new DataResponse(['data' => ['message' => $this->l->t('Could not enable recovery key. Please check your recovery key password!')]], Http::STATUS_BAD_REQUEST);
|
||||
} else {
|
||||
if ($this->recovery->disableAdminRecovery($recoveryPassword)) {
|
||||
return new DataResponse(['data' => ['message' => $this->l->t('Recovery key successfully disabled')]]);
|
||||
}
|
||||
return new DataResponse(['data' => ['message' => $this->l->t('Could not disable recovery key. Please check your recovery key password!')]], Http::STATUS_BAD_REQUEST);
|
||||
}
|
||||
return new DataResponse(['data' => ['message' => $this->l->t('Could not enable recovery key. Please check your recovery key password!')]], Http::STATUS_BAD_REQUEST);
|
||||
} elseif (isset($adminEnableRecovery) && $adminEnableRecovery === '0') {
|
||||
if ($this->recovery->disableAdminRecovery($recoveryPassword)) {
|
||||
return new DataResponse(['data' => ['message' => $this->l->t('Recovery key successfully disabled')]]);
|
||||
} catch (\Exception $e) {
|
||||
$this->logger->error('Error enabling or disabling recovery key', ['exception' => $e]);
|
||||
if ($e instanceof GenericEncryptionException) {
|
||||
return new DataResponse(['data' => ['message' => $e->getMessage()]], Http::STATUS_INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
return new DataResponse(['data' => ['message' => $this->l->t('Could not disable recovery key. Please check your recovery key password!')]], Http::STATUS_BAD_REQUEST);
|
||||
return new DataResponse([], Http::STATUS_INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
// this response should never be sent but just in case.
|
||||
return new DataResponse(['data' => ['message' => $this->l->t('Missing parameters')]], Http::STATUS_BAD_REQUEST);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $newPassword
|
||||
* @param string $oldPassword
|
||||
* @param string $confirmPassword
|
||||
* @return DataResponse
|
||||
*/
|
||||
public function changeRecoveryPassword($newPassword, $oldPassword, $confirmPassword) {
|
||||
public function changeRecoveryPassword(string $newPassword, string $oldPassword, string $confirmPassword): DataResponse {
|
||||
//check if both passwords are the same
|
||||
if (empty($oldPassword)) {
|
||||
$errorMessage = $this->l->t('Please provide the old recovery password');
|
||||
|
|
@ -103,23 +91,30 @@ class RecoveryController extends Controller {
|
|||
return new DataResponse(['data' => ['message' => $errorMessage]], Http::STATUS_BAD_REQUEST);
|
||||
}
|
||||
|
||||
$result = $this->recovery->changeRecoveryKeyPassword($newPassword,
|
||||
$oldPassword);
|
||||
try {
|
||||
$result = $this->recovery->changeRecoveryKeyPassword($newPassword,
|
||||
$oldPassword);
|
||||
|
||||
if ($result) {
|
||||
return new DataResponse(
|
||||
[
|
||||
'data' => [
|
||||
'message' => $this->l->t('Password successfully changed.')]
|
||||
]
|
||||
);
|
||||
}
|
||||
return new DataResponse(
|
||||
[
|
||||
if ($result) {
|
||||
return new DataResponse(
|
||||
[
|
||||
'data' => [
|
||||
'message' => $this->l->t('Password successfully changed.')]
|
||||
]
|
||||
);
|
||||
}
|
||||
return new DataResponse([
|
||||
'data' => [
|
||||
'message' => $this->l->t('Could not change the password. Maybe the old password was not correct.')
|
||||
]
|
||||
], Http::STATUS_BAD_REQUEST);
|
||||
} catch (\Exception $e) {
|
||||
$this->logger->error('Error changing recovery password', ['exception' => $e]);
|
||||
if ($e instanceof GenericEncryptionException) {
|
||||
return new DataResponse(['data' => ['message' => $e->getMessage()]], Http::STATUS_INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
return new DataResponse([], Http::STATUS_INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -68,8 +68,10 @@ class StatusController extends Controller {
|
|||
return new DataResponse(
|
||||
[
|
||||
'status' => $status,
|
||||
'initStatus' => $this->session->getStatus(),
|
||||
'data' => [
|
||||
'message' => $message]
|
||||
'message' => $message,
|
||||
],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,10 +7,13 @@
|
|||
namespace OCA\Encryption\Settings;
|
||||
|
||||
use OC\Files\View;
|
||||
use OCA\Encryption\AppInfo\Application;
|
||||
use OCA\Encryption\Crypto\Crypt;
|
||||
use OCA\Encryption\Session;
|
||||
use OCA\Encryption\Util;
|
||||
use OCP\AppFramework\Http\TemplateResponse;
|
||||
use OCP\AppFramework\Services\IInitialState;
|
||||
use OCP\IAppConfig;
|
||||
use OCP\IConfig;
|
||||
use OCP\IL10N;
|
||||
use OCP\ISession;
|
||||
|
|
@ -27,6 +30,8 @@ class Admin implements ISettings {
|
|||
private IConfig $config,
|
||||
private IUserManager $userManager,
|
||||
private ISession $session,
|
||||
private IInitialState $initialState,
|
||||
private IAppConfig $appConfig,
|
||||
) {
|
||||
}
|
||||
|
||||
|
|
@ -48,19 +53,21 @@ class Admin implements ISettings {
|
|||
$this->userManager);
|
||||
|
||||
// Check if an adminRecovery account is enabled for recovering files after lost pwd
|
||||
$recoveryAdminEnabled = $this->config->getAppValue('encryption', 'recoveryAdminEnabled', '0');
|
||||
$recoveryAdminEnabled = $this->appConfig->getValueBool('encryption', 'recoveryAdminEnabled');
|
||||
$session = new Session($this->session);
|
||||
|
||||
$encryptHomeStorage = $util->shouldEncryptHomeStorage();
|
||||
|
||||
$parameters = [
|
||||
$this->initialState->provideInitialState('adminSettings', [
|
||||
'recoveryEnabled' => $recoveryAdminEnabled,
|
||||
'initStatus' => $session->getStatus(),
|
||||
'encryptHomeStorage' => $encryptHomeStorage,
|
||||
'masterKeyEnabled' => $util->isMasterKeyEnabled(),
|
||||
];
|
||||
]);
|
||||
|
||||
return new TemplateResponse('encryption', 'settings-admin', $parameters, '');
|
||||
\OCP\Util::addStyle(Application::APP_ID, 'settings_admin');
|
||||
\OCP\Util::addScript(Application::APP_ID, 'settings_admin');
|
||||
return new TemplateResponse(Application::APP_ID, 'settings', renderAs: '');
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -6,20 +6,25 @@
|
|||
*/
|
||||
namespace OCA\Encryption\Settings;
|
||||
|
||||
use OCA\Encryption\AppInfo\Application;
|
||||
use OCA\Encryption\Session;
|
||||
use OCA\Encryption\Util;
|
||||
use OCP\AppFramework\Http\TemplateResponse;
|
||||
use OCP\IConfig;
|
||||
use OCP\AppFramework\Services\IInitialState;
|
||||
use OCP\Encryption\IManager;
|
||||
use OCP\IAppConfig;
|
||||
use OCP\IUserSession;
|
||||
use OCP\Settings\ISettings;
|
||||
|
||||
class Personal implements ISettings {
|
||||
|
||||
public function __construct(
|
||||
private IConfig $config,
|
||||
private Session $session,
|
||||
private Util $util,
|
||||
private IUserSession $userSession,
|
||||
private IInitialState $initialState,
|
||||
private IAppConfig $appConfig,
|
||||
private IManager $manager,
|
||||
) {
|
||||
}
|
||||
|
||||
|
|
@ -28,7 +33,7 @@ class Personal implements ISettings {
|
|||
* @since 9.1
|
||||
*/
|
||||
public function getForm() {
|
||||
$recoveryAdminEnabled = $this->config->getAppValue('encryption', 'recoveryAdminEnabled');
|
||||
$recoveryAdminEnabled = $this->appConfig->getValueBool('encryption', 'recoveryAdminEnabled');
|
||||
$privateKeySet = $this->session->isPrivateKeySet();
|
||||
|
||||
if (!$recoveryAdminEnabled && $privateKeySet) {
|
||||
|
|
@ -38,20 +43,23 @@ class Personal implements ISettings {
|
|||
$userId = $this->userSession->getUser()->getUID();
|
||||
$recoveryEnabledForUser = $this->util->isRecoveryEnabledForUser($userId);
|
||||
|
||||
$parameters = [
|
||||
$this->initialState->provideInitialState('personalSettings', [
|
||||
'recoveryEnabled' => $recoveryAdminEnabled,
|
||||
'recoveryEnabledForUser' => $recoveryEnabledForUser,
|
||||
'privateKeySet' => $privateKeySet,
|
||||
'initialized' => $this->session->getStatus(),
|
||||
];
|
||||
return new TemplateResponse('encryption', 'settings-personal', $parameters, '');
|
||||
]);
|
||||
|
||||
\OCP\Util::addStyle(Application::APP_ID, 'settings_personal');
|
||||
\OCP\Util::addScript(Application::APP_ID, 'settings_personal');
|
||||
return new TemplateResponse(Application::APP_ID, 'settings', renderAs: '');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string the section ID, e.g. 'sharing'
|
||||
* @since 9.1
|
||||
*/
|
||||
public function getSection() {
|
||||
if (!$this->manager->isEnabled()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return 'security';
|
||||
}
|
||||
|
||||
|
|
|
|||
46
apps/encryption/src/components/SettingsAdminHomeStorage.vue
Normal file
46
apps/encryption/src/components/SettingsAdminHomeStorage.vue
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
<!--
|
||||
- SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
|
||||
- SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
-->
|
||||
|
||||
<script setup lang="ts">
|
||||
import axios from '@nextcloud/axios'
|
||||
import { t } from '@nextcloud/l10n'
|
||||
import { generateUrl } from '@nextcloud/router'
|
||||
import { watchDebounced } from '@vueuse/core'
|
||||
import { ref, watch } from 'vue'
|
||||
import NcCheckboxRadioSwitch from '@nextcloud/vue/components/NcCheckboxRadioSwitch'
|
||||
|
||||
const encryptHomeStorage = defineModel<boolean>({ required: true })
|
||||
const isSavingHomeStorageEncryption = ref(false)
|
||||
|
||||
watch(encryptHomeStorage, () => {
|
||||
isSavingHomeStorageEncryption.value = true
|
||||
})
|
||||
watchDebounced(encryptHomeStorage, async (encryptHomeStorage, oldValue) => {
|
||||
if (encryptHomeStorage === oldValue) {
|
||||
// user changed their mind (likely quickly toggled), do nothing
|
||||
isSavingHomeStorageEncryption.value = false
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
await axios.post(
|
||||
generateUrl('/apps/encryption/ajax/setEncryptHomeStorage'),
|
||||
{ encryptHomeStorage },
|
||||
)
|
||||
} finally {
|
||||
isSavingHomeStorageEncryption.value = false
|
||||
}
|
||||
}, { debounce: 800 })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NcCheckboxRadioSwitch
|
||||
v-model="encryptHomeStorage"
|
||||
:loading="isSavingHomeStorageEncryption"
|
||||
:description="t('encryption', 'Enabling this option encrypts all files stored on the main storage, otherwise only files on external storage will be encrypted')"
|
||||
type="switch">
|
||||
{{ t('encryption', 'Encrypt the home storage') }}
|
||||
</NcCheckboxRadioSwitch>
|
||||
</template>
|
||||
93
apps/encryption/src/components/SettingsAdminRecoveryKey.vue
Normal file
93
apps/encryption/src/components/SettingsAdminRecoveryKey.vue
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
<!--
|
||||
- SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
|
||||
- SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
-->
|
||||
|
||||
<script setup lang="ts">
|
||||
import axios from '@nextcloud/axios'
|
||||
import { showSuccess } from '@nextcloud/dialogs'
|
||||
import { t } from '@nextcloud/l10n'
|
||||
import { generateUrl } from '@nextcloud/router'
|
||||
import { computed, ref, useTemplateRef } from 'vue'
|
||||
import NcButton from '@nextcloud/vue/components/NcButton'
|
||||
import NcFormGroup from '@nextcloud/vue/components/NcFormGroup'
|
||||
import NcNoteCard from '@nextcloud/vue/components/NcNoteCard'
|
||||
import NcPasswordField from '@nextcloud/vue/components/NcPasswordField'
|
||||
import { logger } from '../utils/logger.ts'
|
||||
|
||||
const recoveryEnabled = defineModel<boolean>({ required: true })
|
||||
const formElement = useTemplateRef('form')
|
||||
|
||||
const isLoading = ref(false)
|
||||
const hasError = ref(false)
|
||||
|
||||
const password = ref('')
|
||||
const confirmPassword = ref('')
|
||||
const passwordMatch = computed(() => password.value === confirmPassword.value)
|
||||
|
||||
/**
|
||||
* Handle the form submission to enable or disable the admin recovery key
|
||||
*/
|
||||
async function onSubmit() {
|
||||
if (isLoading.value) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!passwordMatch.value) {
|
||||
return
|
||||
}
|
||||
|
||||
hasError.value = false
|
||||
isLoading.value = true
|
||||
try {
|
||||
const { data } = await axios.post(
|
||||
generateUrl('/apps/encryption/ajax/adminRecovery'),
|
||||
{
|
||||
adminEnableRecovery: !recoveryEnabled.value,
|
||||
recoveryPassword: password.value,
|
||||
confirmPassword: confirmPassword.value,
|
||||
},
|
||||
)
|
||||
recoveryEnabled.value = !recoveryEnabled.value
|
||||
password.value = confirmPassword.value = ''
|
||||
formElement.value?.reset()
|
||||
if (data.data.message) {
|
||||
showSuccess(data.data.message)
|
||||
}
|
||||
} catch (error) {
|
||||
hasError.value = true
|
||||
logger.error('Failed to update recovery key settings', { error })
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<form ref="form" @submit.prevent="onSubmit">
|
||||
<NcFormGroup
|
||||
:label="recoveryEnabled ? t('encryption', 'Disable recovery key') : t('encryption', 'Enable recovery key')"
|
||||
:description="t('encryption', 'The recovery key is an additional encryption key used to encrypt files. It is used to recover files from an account if the password is forgotten.')">
|
||||
<NcPasswordField
|
||||
v-model="password"
|
||||
required
|
||||
name="password"
|
||||
:label="t('encryption', 'Recovery key password')" />
|
||||
<NcPasswordField
|
||||
v-model="confirmPassword"
|
||||
required
|
||||
name="confirmPassword"
|
||||
:error="!!confirmPassword && !passwordMatch"
|
||||
:helper-text="(passwordMatch || !confirmPassword) ? '' : t('encryption', 'Passwords do not match fields')"
|
||||
:label="t('encryption', 'Repeat recovery key password')" />
|
||||
|
||||
<NcButton type="submit" :variant="recoveryEnabled ? 'error' : 'primary'">
|
||||
{{ recoveryEnabled ? t('encryption', 'Disable recovery key') : t('encryption', 'Enable recovery key') }}
|
||||
</NcButton>
|
||||
|
||||
<NcNoteCard v-if="hasError" type="error">
|
||||
{{ t('encryption', 'An error occurred while updating the recovery key settings. Please try again.') }}
|
||||
</NcNoteCard>
|
||||
</NcFormGroup>
|
||||
</form>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,98 @@
|
|||
<!--
|
||||
- SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
|
||||
- SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
-->
|
||||
|
||||
<script setup lang="ts">
|
||||
import axios from '@nextcloud/axios'
|
||||
import { t } from '@nextcloud/l10n'
|
||||
import { generateUrl } from '@nextcloud/router'
|
||||
import { computed, ref, useTemplateRef } from 'vue'
|
||||
import NcButton from '@nextcloud/vue/components/NcButton'
|
||||
import NcFormGroup from '@nextcloud/vue/components/NcFormGroup'
|
||||
import NcNoteCard from '@nextcloud/vue/components/NcNoteCard'
|
||||
import NcPasswordField from '@nextcloud/vue/components/NcPasswordField'
|
||||
import { logger } from '../utils/logger.ts'
|
||||
|
||||
const formElement = useTemplateRef('form')
|
||||
|
||||
const isLoading = ref(false)
|
||||
const hasError = ref(false)
|
||||
|
||||
const oldPassword = ref('')
|
||||
const password = ref('')
|
||||
const confirmPassword = ref('')
|
||||
const passwordMatch = computed(() => password.value === confirmPassword.value)
|
||||
|
||||
/**
|
||||
* Handle the form submission to change the admin recovery key password
|
||||
*/
|
||||
async function onSubmit() {
|
||||
if (isLoading.value) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!passwordMatch.value) {
|
||||
return
|
||||
}
|
||||
|
||||
hasError.value = false
|
||||
isLoading.value = true
|
||||
try {
|
||||
await axios.post(
|
||||
generateUrl('/apps/encryption/ajax/changeRecoveryPassword'),
|
||||
{
|
||||
oldPassword: oldPassword.value,
|
||||
newPassword: password.value,
|
||||
confirmPassword: confirmPassword.value,
|
||||
},
|
||||
)
|
||||
oldPassword.value = password.value = confirmPassword.value = ''
|
||||
formElement.value?.reset()
|
||||
} catch (error) {
|
||||
hasError.value = true
|
||||
logger.error('Failed to update recovery key settings', { error })
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<form ref="form" :class="$style.settingsAdminRecoveryKeyChange" @submit.prevent="onSubmit">
|
||||
<NcFormGroup
|
||||
:label="t('encryption', 'Change recovery key password')">
|
||||
<NcPasswordField
|
||||
v-model="oldPassword"
|
||||
required
|
||||
name="oldPassword"
|
||||
:label="t('encryption', 'Old recovery key password')" />
|
||||
<NcPasswordField
|
||||
v-model="password"
|
||||
required
|
||||
name="password"
|
||||
:label="t('encryption', 'New recovery key password')" />
|
||||
<NcPasswordField
|
||||
v-model="confirmPassword"
|
||||
required
|
||||
name="confirmPassword"
|
||||
:error="!passwordMatch && !!confirmPassword"
|
||||
:helper-text="(passwordMatch || !confirmPassword) ? '' : t('encryption', 'Passwords do not match fields')"
|
||||
:label="t('encryption', 'Repeat new recovery key password')" />
|
||||
|
||||
<NcButton type="submit" variant="primary">
|
||||
{{ t('encryption', 'Change recovery key password') }}
|
||||
</NcButton>
|
||||
|
||||
<NcNoteCard v-if="hasError" type="error">
|
||||
{{ t('encryption', 'An error occurred while changing the recovery key password. Please try again.') }}
|
||||
</NcNoteCard>
|
||||
</NcFormGroup>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<style module>
|
||||
.settingsAdminRecoveryKeyChange {
|
||||
margin-top: var(--clickable-area-small);
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
<!--
|
||||
- SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
|
||||
- SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
-->
|
||||
|
||||
<script setup lang="ts">
|
||||
import axios, { isAxiosError } from '@nextcloud/axios'
|
||||
import { showError } from '@nextcloud/dialogs'
|
||||
import { t } from '@nextcloud/l10n'
|
||||
import { generateUrl } from '@nextcloud/router'
|
||||
import { NcButton, NcFormGroup, NcNoteCard, NcPasswordField } from '@nextcloud/vue'
|
||||
import { ref, useTemplateRef } from 'vue'
|
||||
|
||||
defineProps<{
|
||||
recoveryEnabledForUser: boolean
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
updated: []
|
||||
}>()
|
||||
|
||||
const formElement = useTemplateRef('form')
|
||||
|
||||
const isLoading = ref(false)
|
||||
const hasError = ref(false)
|
||||
const oldPrivateKeyPassword = ref('')
|
||||
const newPrivateKeyPassword = ref('')
|
||||
|
||||
/**
|
||||
* Handle the form submission to change the private key password
|
||||
*/
|
||||
async function onSubmit() {
|
||||
if (isLoading.value) {
|
||||
return
|
||||
}
|
||||
|
||||
isLoading.value = true
|
||||
hasError.value = false
|
||||
try {
|
||||
await axios.post(
|
||||
generateUrl('/apps/encryption/ajax/updatePrivateKeyPassword'),
|
||||
{
|
||||
oldPassword: oldPrivateKeyPassword.value,
|
||||
newPassword: newPrivateKeyPassword.value,
|
||||
},
|
||||
)
|
||||
oldPrivateKeyPassword.value = newPrivateKeyPassword.value = ''
|
||||
formElement.value?.reset()
|
||||
emit('updated')
|
||||
} catch (error) {
|
||||
if (isAxiosError(error) && error.response && error.response.data?.data?.message) {
|
||||
showError(error.response.data.data.message)
|
||||
}
|
||||
hasError.value = true
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<form ref="form" @submit.prevent="onSubmit">
|
||||
<NcFormGroup
|
||||
:label="t('encryption', 'Update private key password')"
|
||||
:description="t('encryption', 'Your private key password no longer matches your log-in password. Set your old private key password to your current log-in password.')">
|
||||
<NcNoteCard v-if="recoveryEnabledForUser">
|
||||
{{ t('encryption', 'If you do not remember your old password you can ask your administrator to recover your files.') }}
|
||||
</NcNoteCard>
|
||||
|
||||
<NcPasswordField :label="t('encryption', 'Old log-in password')" />
|
||||
<NcPasswordField :label="t('encryption', 'Current log-in password')" />
|
||||
|
||||
<NcButton
|
||||
type="submit"
|
||||
variant="primary">
|
||||
{{ t('encryption', 'Update') }}
|
||||
</NcButton>
|
||||
</NcFormGroup>
|
||||
</form>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
<!--
|
||||
- SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
|
||||
- SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
-->
|
||||
|
||||
<script setup lang="ts">
|
||||
import axios, { isAxiosError } from '@nextcloud/axios'
|
||||
import { showError, showLoading } from '@nextcloud/dialogs'
|
||||
import { t } from '@nextcloud/l10n'
|
||||
import { generateUrl } from '@nextcloud/router'
|
||||
import { watchDebounced } from '@vueuse/core'
|
||||
import { ref, watch } from 'vue'
|
||||
import NcCheckboxRadioSwitch from '@nextcloud/vue/components/NcCheckboxRadioSwitch'
|
||||
|
||||
const userEnableRecovery = defineModel<boolean>({ required: true })
|
||||
const isLoading = ref(false)
|
||||
|
||||
watch(userEnableRecovery, () => {
|
||||
isLoading.value = true
|
||||
})
|
||||
watchDebounced([userEnableRecovery], async ([newValue], [oldValue]) => {
|
||||
if (newValue === oldValue) {
|
||||
// user changed their mind (likely quickly toggled), do nothing
|
||||
isLoading.value = false
|
||||
return
|
||||
}
|
||||
|
||||
const toast = showLoading(t('encryption', 'Updating recovery keys. This can take some time…'))
|
||||
try {
|
||||
await axios.post(
|
||||
generateUrl('/apps/encryption/ajax/userSetRecovery'),
|
||||
{ userEnableRecovery: userEnableRecovery.value },
|
||||
)
|
||||
} catch (error) {
|
||||
userEnableRecovery.value = oldValue
|
||||
if (isAxiosError(error) && error.response && error.response.data?.data?.message) {
|
||||
showError(error.response.data.data.message)
|
||||
}
|
||||
} finally {
|
||||
toast.hideToast()
|
||||
isLoading.value = false
|
||||
}
|
||||
}, { debounce: 800 })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NcCheckboxRadioSwitch
|
||||
v-model="userEnableRecovery"
|
||||
type="switch"
|
||||
:loading="isLoading"
|
||||
:description="t('encryption', 'Enabling this option will allow you to reobtain access to your encrypted files in case of password loss')">
|
||||
{{ t('encryption', 'Enable password recovery') }}
|
||||
</NcCheckboxRadioSwitch>
|
||||
</template>
|
||||
21
apps/encryption/src/encryption.ts
Normal file
21
apps/encryption/src/encryption.ts
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
/*!
|
||||
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { getCurrentUser } from '@nextcloud/auth'
|
||||
import axios from '@nextcloud/axios'
|
||||
import { showWarning } from '@nextcloud/dialogs'
|
||||
import { generateUrl } from '@nextcloud/router'
|
||||
|
||||
window.addEventListener('DOMContentLoaded', async function() {
|
||||
if (getCurrentUser() === null) {
|
||||
// skip for public pages
|
||||
return
|
||||
}
|
||||
|
||||
const { data } = await axios.get(generateUrl('/apps/encryption/ajax/getStatus'))
|
||||
if (data.status === 'interactionNeeded') {
|
||||
showWarning(data.data.message)
|
||||
}
|
||||
})
|
||||
10
apps/encryption/src/settings-admin.ts
Normal file
10
apps/encryption/src/settings-admin.ts
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
/*!
|
||||
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { createApp } from 'vue'
|
||||
import SettingsAdmin from './views/SettingsAdmin.vue'
|
||||
|
||||
const app = createApp(SettingsAdmin)
|
||||
app.mount('#encryption-settings-section')
|
||||
10
apps/encryption/src/settings-personal.ts
Normal file
10
apps/encryption/src/settings-personal.ts
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
/*!
|
||||
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { createApp } from 'vue'
|
||||
import SettingsPersonal from './views/SettingsPersonal.vue'
|
||||
|
||||
const app = createApp(SettingsPersonal)
|
||||
app.mount('#encryption-settings-section')
|
||||
10
apps/encryption/src/utils/logger.ts
Normal file
10
apps/encryption/src/utils/logger.ts
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
/*!
|
||||
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { getLoggerBuilder } from '@nextcloud/logger'
|
||||
|
||||
export const logger = getLoggerBuilder()
|
||||
.setApp('encryption')
|
||||
.build()
|
||||
10
apps/encryption/src/utils/types.ts
Normal file
10
apps/encryption/src/utils/types.ts
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
/*!
|
||||
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
export const InitStatus = Object.freeze({
|
||||
NotInitialized: '0',
|
||||
InitExecuted: '1',
|
||||
InitSuccessful: '2',
|
||||
})
|
||||
40
apps/encryption/src/views/SettingsAdmin.vue
Normal file
40
apps/encryption/src/views/SettingsAdmin.vue
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
<!--
|
||||
- SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
|
||||
- SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
-->
|
||||
|
||||
<script setup lang="ts">
|
||||
import { loadState } from '@nextcloud/initial-state'
|
||||
import { t } from '@nextcloud/l10n'
|
||||
import { NcNoteCard, NcSettingsSection } from '@nextcloud/vue'
|
||||
import { ref } from 'vue'
|
||||
import SettingsAdminHomeStorage from '../components/SettingsAdminHomeStorage.vue'
|
||||
import SettingsAdminRecoveryKey from '../components/SettingsAdminRecoveryKey.vue'
|
||||
import SettingsAdminRecoveryKeyChange from '../components/SettingsAdminRecoveryKeyChange.vue'
|
||||
import { InitStatus } from '../utils/types.ts'
|
||||
|
||||
const adminSettings = loadState<{
|
||||
recoveryEnabled: boolean
|
||||
masterKeyEnabled: boolean
|
||||
encryptHomeStorage: boolean
|
||||
initStatus: typeof InitStatus[keyof typeof InitStatus]
|
||||
}>('encryption', 'adminSettings')
|
||||
|
||||
const encryptHomeStorage = ref(adminSettings.encryptHomeStorage!)
|
||||
const recoveryEnabled = ref(adminSettings.recoveryEnabled!)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NcSettingsSection :name="t('encryption', 'Default encryption module')">
|
||||
<NcNoteCard v-if="adminSettings.initStatus === InitStatus.NotInitialized && !adminSettings.masterKeyEnabled" type="warning">
|
||||
{{ t('encryption', 'Encryption app is enabled but your keys are not initialized, please log-out and log-in again') }}
|
||||
</NcNoteCard>
|
||||
|
||||
<template v-else>
|
||||
<SettingsAdminHomeStorage v-model="encryptHomeStorage" />
|
||||
<br>
|
||||
<SettingsAdminRecoveryKey v-if="adminSettings.masterKeyEnabled" v-model="recoveryEnabled" />
|
||||
<SettingsAdminRecoveryKeyChange v-if="adminSettings.masterKeyEnabled && recoveryEnabled" />
|
||||
</template>
|
||||
</NcSettingsSection>
|
||||
</template>
|
||||
60
apps/encryption/src/views/SettingsPersonal.vue
Normal file
60
apps/encryption/src/views/SettingsPersonal.vue
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
<!--
|
||||
- SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
|
||||
- SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
-->
|
||||
|
||||
<script setup lang="ts">
|
||||
import axios from '@nextcloud/axios'
|
||||
import { showInfo } from '@nextcloud/dialogs'
|
||||
import { loadState } from '@nextcloud/initial-state'
|
||||
import { t } from '@nextcloud/l10n'
|
||||
import { generateUrl } from '@nextcloud/router'
|
||||
import { ref } from 'vue'
|
||||
import NcNoteCard from '@nextcloud/vue/components/NcNoteCard'
|
||||
import NcSettingsSection from '@nextcloud/vue/components/NcSettingsSection'
|
||||
import SettingsPersonalChangePrivateKey from '../components/SettingsPersonalChangePrivateKey.vue'
|
||||
import SettingsPersonalEnableRecovery from '../components/SettingsPersonalEnableRecovery.vue'
|
||||
import { logger } from '../utils/logger.ts'
|
||||
import { InitStatus } from '../utils/types.ts'
|
||||
|
||||
const personalSettings = loadState<{
|
||||
recoveryEnabled: boolean
|
||||
recoveryEnabledForUser: boolean
|
||||
privateKeySet: boolean
|
||||
initialized: typeof InitStatus[keyof typeof InitStatus]
|
||||
}>('encryption', 'personalSettings')
|
||||
|
||||
const initialized = ref(personalSettings.initialized)
|
||||
const recoveryEnabledForUser = ref(personalSettings.recoveryEnabledForUser)
|
||||
|
||||
/**
|
||||
* Reload encryption status
|
||||
*/
|
||||
async function reloadStatus() {
|
||||
try {
|
||||
const { data } = await axios.get(generateUrl('/apps/encryption/ajax/getStatus'))
|
||||
initialized.value = data.initStatus
|
||||
if (data.data.message) {
|
||||
showInfo(data.data.message)
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Failed to fetch current encryption status', { error })
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NcSettingsSection :name="t('encryption', 'Basic encryption module')">
|
||||
<NcNoteCard v-if="initialized === InitStatus.NotInitialized" type="warning">
|
||||
{{ t('encryption', 'Encryption app is enabled but your keys are not initialized, please log-out and log-in again') }}
|
||||
</NcNoteCard>
|
||||
|
||||
<SettingsPersonalChangePrivateKey
|
||||
v-else-if="initialized === InitStatus.InitExecuted"
|
||||
:recovery-enabled-for-user
|
||||
@updated="reloadStatus" />
|
||||
<SettingsPersonalEnableRecovery
|
||||
v-else-if="personalSettings.recoveryEnabled && personalSettings.privateKeySet"
|
||||
v-model="recoveryEnabledForUser" />
|
||||
</NcSettingsSection>
|
||||
</template>
|
||||
|
|
@ -1,83 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-FileCopyrightText: 2016 ownCloud, Inc.
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
/** @var array $_ */
|
||||
/** @var \OCP\IL10N $l */
|
||||
\OCP\Util::addScript('encryption', 'settings-admin', 'core');
|
||||
style('encryption', 'settings-admin');
|
||||
?>
|
||||
<form id="ocDefaultEncryptionModule" class="sub-section">
|
||||
<h3><?php p($l->t('Default encryption module')); ?></h3>
|
||||
<?php if (!$_['initStatus'] && $_['masterKeyEnabled'] === false): ?>
|
||||
<?php p($l->t('Encryption app is enabled but your keys are not initialized, please log-out and log-in again')); ?>
|
||||
<?php else: ?>
|
||||
<p id="encryptHomeStorageSetting">
|
||||
<input type="checkbox" class="checkbox" name="encrypt_home_storage" id="encryptHomeStorage"
|
||||
value="1" <?php if ($_['encryptHomeStorage']) {
|
||||
print_unescaped('checked="checked"');
|
||||
} ?> />
|
||||
<label for="encryptHomeStorage"><?php p($l->t('Encrypt the home storage'));?></label></br>
|
||||
<em><?php p($l->t('Enabling this option encrypts all files stored on the main storage, otherwise only files on external storage will be encrypted')); ?></em>
|
||||
</p>
|
||||
<br />
|
||||
<?php if ($_['masterKeyEnabled'] === false): ?>
|
||||
<p id="encryptionSetRecoveryKey">
|
||||
<?php $_['recoveryEnabled'] === '0' ? p($l->t('Enable recovery key')) : p($l->t('Disable recovery key')); ?>
|
||||
<span class="msg"></span>
|
||||
<br/>
|
||||
<em>
|
||||
<?php p($l->t('The recovery key is an additional encryption key used to encrypt files. It is used to recover files from an account if the password is forgotten.')) ?>
|
||||
</em>
|
||||
<br/>
|
||||
<input type="password"
|
||||
name="encryptionRecoveryPassword"
|
||||
id="encryptionRecoveryPassword"
|
||||
placeholder="<?php p($l->t('Recovery key password')); ?>"/>
|
||||
<input type="password"
|
||||
name="encryptionRecoveryPassword"
|
||||
id="repeatEncryptionRecoveryPassword"
|
||||
placeholder="<?php p($l->t('Repeat recovery key password')); ?>"/>
|
||||
<input type="button"
|
||||
name="enableRecoveryKey"
|
||||
id="enableRecoveryKey"
|
||||
status="<?php p($_['recoveryEnabled']) ?>"
|
||||
value="<?php $_['recoveryEnabled'] === '0' ? p($l->t('Enable recovery key')) : p($l->t('Disable recovery key')); ?>"/>
|
||||
</p>
|
||||
<br/><br/>
|
||||
|
||||
<p name="changeRecoveryPasswordBlock" id="encryptionChangeRecoveryKey" <?php if ($_['recoveryEnabled'] === '0') {
|
||||
print_unescaped('class="hidden"');
|
||||
}?>>
|
||||
<?php p($l->t('Change recovery key password:')); ?>
|
||||
<span class="msg"></span>
|
||||
<br/>
|
||||
<input
|
||||
type="password"
|
||||
name="changeRecoveryPassword"
|
||||
id="oldEncryptionRecoveryPassword"
|
||||
placeholder="<?php p($l->t('Old recovery key password')); ?>"/>
|
||||
<br />
|
||||
<input
|
||||
type="password"
|
||||
name="changeRecoveryPassword"
|
||||
id="newEncryptionRecoveryPassword"
|
||||
placeholder="<?php p($l->t('New recovery key password')); ?>"/>
|
||||
<input
|
||||
type="password"
|
||||
name="changeRecoveryPassword"
|
||||
id="repeatedNewEncryptionRecoveryPassword"
|
||||
placeholder="<?php p($l->t('Repeat new recovery key password')); ?>"/>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
name="submitChangeRecoveryKey">
|
||||
<?php p($l->t('Change Password')); ?>
|
||||
</button>
|
||||
</p>
|
||||
<?php endif; ?>
|
||||
<?php endif; ?>
|
||||
</form>
|
||||
|
|
@ -1,79 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-FileCopyrightText: 2016 ownCloud, Inc.
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
/** @var array $_ */
|
||||
/** @var \OCP\IL10N $l */
|
||||
\OCP\Util::addScript('encryption', 'settings-personal', 'core');
|
||||
?>
|
||||
<form id="ocDefaultEncryptionModule" class="section">
|
||||
<h2 data-anchor-name="basic-encryption-module"><?php p($l->t('Basic encryption module')); ?></h2>
|
||||
|
||||
<?php if ($_['initialized'] === \OCA\Encryption\Session::NOT_INITIALIZED): ?>
|
||||
|
||||
<?php p($l->t('Encryption App is enabled, but your keys are not initialized. Please log-out and log-in again.')); ?>
|
||||
|
||||
<?php elseif ($_['initialized'] === \OCA\Encryption\Session::INIT_EXECUTED): ?>
|
||||
<p>
|
||||
<a name="changePKPasswd" />
|
||||
<label for="changePrivateKeyPasswd">
|
||||
<em><?php p($l->t('Your private key password no longer matches your log-in password.')); ?></em>
|
||||
</label>
|
||||
<br />
|
||||
<?php p($l->t('Set your old private key password to your current log-in password:')); ?>
|
||||
<?php if ($_['recoveryEnabledForUser']):
|
||||
p(' ' . $l->t('If you do not remember your old password you can ask your administrator to recover your files.'));
|
||||
endif; ?>
|
||||
<br />
|
||||
<input
|
||||
type="password"
|
||||
name="changePrivateKeyPassword"
|
||||
id="oldPrivateKeyPassword" />
|
||||
<label for="oldPrivateKeyPassword"><?php p($l->t('Old log-in password')); ?></label>
|
||||
<br />
|
||||
<input
|
||||
type="password"
|
||||
name="changePrivateKeyPassword"
|
||||
id="newPrivateKeyPassword" />
|
||||
<label for="newRecoveryPassword"><?php p($l->t('Current log-in password')); ?></label>
|
||||
<br />
|
||||
<button
|
||||
type="button"
|
||||
name="submitChangePrivateKeyPassword"
|
||||
disabled><?php p($l->t('Update Private Key Password')); ?>
|
||||
</button>
|
||||
<span class="msg"></span>
|
||||
</p>
|
||||
|
||||
<?php elseif ($_['recoveryEnabled'] && $_['privateKeySet'] && $_['initialized'] === \OCA\Encryption\Session::INIT_SUCCESSFUL): ?>
|
||||
<br />
|
||||
<p id="userEnableRecovery">
|
||||
<label for="userEnableRecovery"><?php p($l->t('Enable password recovery:')); ?></label>
|
||||
<span class="msg"></span>
|
||||
<br />
|
||||
<em><?php p($l->t('Enabling this option will allow you to reobtain access to your encrypted files in case of password loss')); ?></em>
|
||||
<br />
|
||||
<input
|
||||
type="radio"
|
||||
class="radio"
|
||||
id="userEnableRecoveryCheckbox"
|
||||
name="userEnableRecovery"
|
||||
value="1"
|
||||
<?php echo($_['recoveryEnabledForUser'] ? 'checked="checked"' : ''); ?> />
|
||||
<label for="userEnableRecoveryCheckbox"><?php p($l->t('Enabled')); ?></label>
|
||||
<br />
|
||||
|
||||
<input
|
||||
type="radio"
|
||||
class="radio"
|
||||
id="userDisableRecoveryCheckbox"
|
||||
name="userEnableRecovery"
|
||||
value="0"
|
||||
<?php echo($_['recoveryEnabledForUser'] === false ? 'checked="checked"' : ''); ?> />
|
||||
<label for="userDisableRecoveryCheckbox"><?php p($l->t('Disabled')); ?></label>
|
||||
</p>
|
||||
<?php endif; ?>
|
||||
</form>
|
||||
10
apps/encryption/templates/settings.php
Normal file
10
apps/encryption/templates/settings.php
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
?>
|
||||
|
||||
<div id="encryption-settings-section"></div>
|
||||
|
|
@ -16,6 +16,7 @@ use OCP\IConfig;
|
|||
use OCP\IL10N;
|
||||
use OCP\IRequest;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Test\TestCase;
|
||||
|
||||
class RecoveryControllerTest extends TestCase {
|
||||
|
|
@ -28,11 +29,11 @@ class RecoveryControllerTest extends TestCase {
|
|||
|
||||
public static function adminRecoveryProvider(): array {
|
||||
return [
|
||||
['test', 'test', '1', 'Recovery key successfully enabled', Http::STATUS_OK],
|
||||
['', 'test', '1', 'Missing recovery key password', Http::STATUS_BAD_REQUEST],
|
||||
['test', '', '1', 'Please repeat the recovery key password', Http::STATUS_BAD_REQUEST],
|
||||
['test', 'something that doesn\'t match', '1', 'Repeated recovery key password does not match the provided recovery key password', Http::STATUS_BAD_REQUEST],
|
||||
['test', 'test', '0', 'Recovery key successfully disabled', Http::STATUS_OK],
|
||||
['test', 'test', true, 'Recovery key successfully enabled', Http::STATUS_OK],
|
||||
['', 'test', true, 'Missing recovery key password', Http::STATUS_BAD_REQUEST],
|
||||
['test', '', true, 'Please repeat the recovery key password', Http::STATUS_BAD_REQUEST],
|
||||
['test', 'something that doesn\'t match', true, 'Repeated recovery key password does not match the provided recovery key password', Http::STATUS_BAD_REQUEST],
|
||||
['test', 'test', false, 'Recovery key successfully disabled', Http::STATUS_OK],
|
||||
];
|
||||
}
|
||||
|
||||
|
|
@ -150,10 +151,12 @@ class RecoveryControllerTest extends TestCase {
|
|||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
|
||||
$this->controller = new RecoveryController('encryption',
|
||||
$this->controller = new RecoveryController(
|
||||
'encryption',
|
||||
$this->requestMock,
|
||||
$this->configMock,
|
||||
$this->l10nMock,
|
||||
$this->recoveryMock);
|
||||
$this->recoveryMock,
|
||||
$this->createMock(LoggerInterface::class),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ class StatusControllerTest extends TestCase {
|
|||
*/
|
||||
#[\PHPUnit\Framework\Attributes\DataProvider('dataTestGetStatus')]
|
||||
public function testGetStatus($status, $expectedStatus): void {
|
||||
$this->sessionMock->expects($this->once())
|
||||
$this->sessionMock->expects($this->atLeastOnce())
|
||||
->method('getStatus')->willReturn($status);
|
||||
$result = $this->controller->getStatus();
|
||||
$data = $result->getData();
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@ namespace OCA\Encryption\Tests\Settings;
|
|||
|
||||
use OCA\Encryption\Settings\Admin;
|
||||
use OCP\AppFramework\Http\TemplateResponse;
|
||||
use OCP\AppFramework\Services\IInitialState;
|
||||
use OCP\IAppConfig;
|
||||
use OCP\IConfig;
|
||||
use OCP\IL10N;
|
||||
use OCP\ISession;
|
||||
|
|
@ -29,16 +31,20 @@ class AdminTest extends TestCase {
|
|||
protected IConfig&MockObject $config;
|
||||
protected IUserManager&MockObject $userManager;
|
||||
protected ISession&MockObject $session;
|
||||
protected IInitialState&MockObject $initialState;
|
||||
protected IAppConfig&MockObject $appConfig;
|
||||
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->l = $this->getMockBuilder(IL10N::class)->getMock();
|
||||
$this->logger = $this->getMockBuilder(LoggerInterface::class)->getMock();
|
||||
$this->userSession = $this->getMockBuilder(IUserSession::class)->getMock();
|
||||
$this->config = $this->getMockBuilder(IConfig::class)->getMock();
|
||||
$this->userManager = $this->getMockBuilder(IUserManager::class)->getMock();
|
||||
$this->session = $this->getMockBuilder(ISession::class)->getMock();
|
||||
$this->l = $this->createMock(IL10N::class);
|
||||
$this->logger = $this->createMock(LoggerInterface::class);
|
||||
$this->userSession = $this->createMock(IUserSession::class);
|
||||
$this->config = $this->createMock(IConfig::class);
|
||||
$this->userManager = $this->createMock(IUserManager::class);
|
||||
$this->session = $this->createMock(ISession::class);
|
||||
$this->initialState = $this->createMock(IInitialState::class);
|
||||
$this->appConfig = $this->createMock(IAppConfig::class);
|
||||
|
||||
$this->admin = new Admin(
|
||||
$this->l,
|
||||
|
|
@ -46,11 +52,18 @@ class AdminTest extends TestCase {
|
|||
$this->userSession,
|
||||
$this->config,
|
||||
$this->userManager,
|
||||
$this->session
|
||||
$this->session,
|
||||
$this->initialState,
|
||||
$this->appConfig,
|
||||
);
|
||||
}
|
||||
|
||||
public function testGetForm(): void {
|
||||
$this->appConfig
|
||||
->method('getValueBool')
|
||||
->willReturnMap([
|
||||
['encryption', 'recoveryAdminEnabled', true]
|
||||
]);
|
||||
$this->config
|
||||
->method('getAppValue')
|
||||
->willReturnCallback(function ($app, $key, $default) {
|
||||
|
|
@ -62,13 +75,17 @@ class AdminTest extends TestCase {
|
|||
}
|
||||
return $default;
|
||||
});
|
||||
$params = [
|
||||
'recoveryEnabled' => '1',
|
||||
'initStatus' => '0',
|
||||
'encryptHomeStorage' => true,
|
||||
'masterKeyEnabled' => true
|
||||
];
|
||||
$expected = new TemplateResponse('encryption', 'settings-admin', $params, '');
|
||||
|
||||
$this->initialState
|
||||
->expects(self::once())
|
||||
->method('provideInitialState')
|
||||
->with('adminSettings', [
|
||||
'recoveryEnabled' => true,
|
||||
'initStatus' => '0',
|
||||
'encryptHomeStorage' => true,
|
||||
'masterKeyEnabled' => true
|
||||
]);
|
||||
$expected = new TemplateResponse('encryption', 'settings', renderAs: '');
|
||||
$this->assertEquals($expected, $this->admin->getForm());
|
||||
}
|
||||
|
||||
|
|
|
|||
1
build/frontend/apps/encryption
Symbolic link
1
build/frontend/apps/encryption
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
../../../apps/encryption
|
||||
|
|
@ -12,6 +12,11 @@ const modules = {
|
|||
'settings-admin-example-content': resolve(import.meta.dirname, 'apps/dav/src', 'settings-admin-example-content.ts'),
|
||||
'settings-personal-availability': resolve(import.meta.dirname, 'apps/dav/src', 'settings-personal-availability.ts'),
|
||||
},
|
||||
encryption: {
|
||||
encryption: resolve(import.meta.dirname, 'apps/encryption/src', 'encryption.ts'),
|
||||
settings_admin: resolve(import.meta.dirname, 'apps/encryption/src', 'settings-admin.ts'),
|
||||
settings_personal: resolve(import.meta.dirname, 'apps/encryption/src', 'settings-personal.ts'),
|
||||
},
|
||||
federation: {
|
||||
'settings-admin': resolve(import.meta.dirname, 'apps/federation/src', 'settings-admin.ts'),
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1135,9 +1135,6 @@
|
|||
</InternalMethod>
|
||||
</file>
|
||||
<file src="apps/encryption/lib/Settings/Admin.php">
|
||||
<DeprecatedMethod>
|
||||
<code><![CDATA[getAppValue]]></code>
|
||||
</DeprecatedMethod>
|
||||
<InternalClass>
|
||||
<code><![CDATA[new View()]]></code>
|
||||
</InternalClass>
|
||||
|
|
@ -1145,11 +1142,6 @@
|
|||
<code><![CDATA[new View()]]></code>
|
||||
</InternalMethod>
|
||||
</file>
|
||||
<file src="apps/encryption/lib/Settings/Personal.php">
|
||||
<DeprecatedMethod>
|
||||
<code><![CDATA[getAppValue]]></code>
|
||||
</DeprecatedMethod>
|
||||
</file>
|
||||
<file src="apps/encryption/lib/Util.php">
|
||||
<DeprecatedMethod>
|
||||
<code><![CDATA[getAppValue]]></code>
|
||||
|
|
|
|||
Loading…
Reference in a new issue