diff options
Diffstat (limited to 'apps/settings/src/store/users.js')
-rw-r--r-- | apps/settings/src/store/users.js | 184 |
1 files changed, 111 insertions, 73 deletions
diff --git a/apps/settings/src/store/users.js b/apps/settings/src/store/users.js index bfc0f4e119f..7e4b9c4aebb 100644 --- a/apps/settings/src/store/users.js +++ b/apps/settings/src/store/users.js @@ -1,30 +1,6 @@ /** - * @copyright Copyright (c) 2018 John Molakvoæ <skjnldsv@protonmail.com> - * - * @author Arthur Schiwon <blizzz@arthur-schiwon.de> - * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * @author Daniel Calviño Sánchez <danxuliu@gmail.com> - * @author John Molakvoæ <skjnldsv@protonmail.com> - * @author Julius Härtl <jus@bitgrid.net> - * @author Roeland Jago Douma <roeland@famdouma.nl> - * @author Vincent Petry <vincent@nextcloud.com> - * @author Stephan Orbaugh <stephan.orbaugh@nextcloud.com> - * - * @license AGPL-3.0-or-later - * - * 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/>. - * + * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ import { getBuilder } from '@nextcloud/browser-storage' @@ -32,15 +8,22 @@ import { getCapabilities } from '@nextcloud/capabilities' import { parseFileSize } from '@nextcloud/files' import { showError } from '@nextcloud/dialogs' import { generateOcsUrl, generateUrl } from '@nextcloud/router' +import { loadState } from '@nextcloud/initial-state' import axios from '@nextcloud/axios' import { GroupSorting } from '../constants/GroupManagement.ts' +import { naturalCollator } from '../utils/sorting.ts' import api from './api.js' import logger from '../logger.ts' +const usersSettings = loadState('settings', 'usersSettings', {}) + const localStorage = getBuilder('settings').persist(true).build() const defaults = { + /** + * @type {import('../views/user-types').IGroup} + */ group: { id: '', name: '', @@ -53,17 +36,21 @@ const defaults = { const state = { users: [], - groups: [], - orderBy: GroupSorting.UserCount, + groups: [ + ...(usersSettings.getSubAdminGroups ?? []), + ...(usersSettings.systemGroups ?? []), + ], + orderBy: usersSettings.sortGroups ?? GroupSorting.UserCount, minPasswordLength: 0, usersOffset: 0, usersLimit: 25, disabledUsersOffset: 0, disabledUsersLimit: 25, - userCount: 0, + userCount: usersSettings.userCount ?? 0, showConfig: { showStoragePath: localStorage.getItem('account_settings__showStoragePath') === 'true', showUserBackend: localStorage.getItem('account_settings__showUserBackend') === 'true', + showFirstLogin: localStorage.getItem('account_settings__showFirstLogin') === 'true', showLastLogin: localStorage.getItem('account_settings__showLastLogin') === 'true', showNewUserForm: localStorage.getItem('account_settings__showNewUserForm') === 'true', showLanguages: localStorage.getItem('account_settings__showLanguages') === 'true', @@ -86,21 +73,17 @@ const mutations = { setPasswordPolicyMinLength(state, length) { state.minPasswordLength = length !== '' ? length : 0 }, - initGroups(state, { groups, orderBy, userCount }) { - state.groups = groups.map(group => Object.assign({}, defaults.group, group)) - state.orderBy = orderBy - state.userCount = userCount - }, - addGroup(state, { gid, displayName }) { + /** + * @param {object} state store state + * @param {import('../views/user-types.js').IGroup} newGroup new group + */ + addGroup(state, newGroup) { try { - if (typeof state.groups.find((group) => group.id === gid) !== 'undefined') { + if (typeof state.groups.find((group) => group.id === newGroup.id) !== 'undefined') { return } // extend group to default values - const group = Object.assign({}, defaults.group, { - id: gid, - name: displayName, - }) + const group = Object.assign({}, defaults.group, newGroup) state.groups.unshift(group) } catch (e) { console.error('Can\'t create group', e) @@ -170,28 +153,37 @@ const mutations = { return } + const recentGroup = state.groups.find(group => group.id === '__nc_internal_recent') const disabledGroup = state.groups.find(group => group.id === 'disabled') switch (actionType) { case 'enable': case 'disable': disabledGroup.usercount += user.enabled ? -1 : 1 // update Disabled Users count + recentGroup.usercount += user.enabled ? 1 : -1 state.userCount += user.enabled ? 1 : -1 // update Active Users count user.groups.forEach(userGroup => { const group = state.groups.find(groupSearch => groupSearch.id === userGroup) + if (!group) { + return + } group.disabled += user.enabled ? -1 : 1 // update group disabled count }) break case 'create': + recentGroup.usercount++ state.userCount++ // increment Active Users count user.groups.forEach(userGroup => { - state.groups - .find(groupSearch => groupSearch.id === userGroup) - .usercount++ // increment group total count + const group = state.groups.find(groupSearch => groupSearch.id === userGroup) + if (!group) { + return + } + group.usercount++ // increment group total count }) break case 'remove': if (user.enabled) { + recentGroup.usercount-- state.userCount-- // decrement Active Users count user.groups.forEach(userGroup => { const group = state.groups.find(groupSearch => groupSearch.id === userGroup) @@ -205,6 +197,9 @@ const mutations = { disabledGroup.usercount-- // decrement Disabled Users count user.groups.forEach(userGroup => { const group = state.groups.find(groupSearch => groupSearch.id === userGroup) + if (!group) { + return + } group.disabled-- // decrement group disabled count }) } @@ -234,6 +229,18 @@ const mutations = { state.disabledUsersOffset = 0 }, + /** + * Reset group list + * + * @param {object} state the store state + */ + resetGroups(state) { + state.groups = [ + ...(usersSettings.getSubAdminGroups ?? []), + ...(usersSettings.systemGroups ?? []), + ] + }, + setShowConfig(state, { key, value }) { localStorage.setItem(`account_settings__${key}`, JSON.stringify(value)) state.showConfig[key] = value @@ -264,20 +271,20 @@ const getters = { getGroups(state) { return state.groups }, - getSubadminGroups(state) { - // Can't be subadmin of admin or disabled - return state.groups.filter(group => group.id !== 'admin' && group.id !== 'disabled') + getSubAdminGroups() { + return usersSettings.subAdminGroups ?? [] }, + getSortedGroups(state) { const groups = [...state.groups] if (state.orderBy === GroupSorting.UserCount) { return groups.sort((a, b) => { const numA = a.usercount - a.disabled const numB = b.usercount - b.disabled - return (numA < numB) ? 1 : (numB < numA ? -1 : a.name.localeCompare(b.name)) + return (numA < numB) ? 1 : (numB < numA ? -1 : naturalCollator.compare(a.name, b.name)) }) } else { - return groups.sort((a, b) => a.name.localeCompare(b.name)) + return groups.sort((a, b) => naturalCollator.compare(a.name, b.name)) } }, getGroupSorting(state) { @@ -408,16 +415,41 @@ const actions = { }, /** + * Get recent users with full details + * + * @param {object} context store context + * @param {object} options destructuring object + * @param {number} options.offset List offset to request + * @param {number} options.limit List number to return from offset + * @param {string} options.search Search query + * @return {Promise<number>} + */ + async getRecentUsers(context, { offset, limit, search }) { + const url = generateOcsUrl('cloud/users/recent?offset={offset}&limit={limit}&search={search}', { offset, limit, search }) + try { + const response = await api.get(url) + const usersCount = Object.keys(response.data.ocs.data.users).length + if (usersCount > 0) { + context.commit('appendUsers', response.data.ocs.data.users) + } + return usersCount + } catch (error) { + context.commit('API_FAILURE', error) + } + }, + + /** * Get disabled users with full details * * @param {object} context store context * @param {object} options destructuring object * @param {number} options.offset List offset to request * @param {number} options.limit List number to return from offset + * @param options.search * @return {Promise<number>} */ - async getDisabledUsers(context, { offset, limit }) { - const url = generateOcsUrl('cloud/users/disabled?offset={offset}&limit={limit}', { offset, limit }) + async getDisabledUsers(context, { offset, limit, search }) { + const url = generateOcsUrl('cloud/users/disabled?offset={offset}&limit={limit}&search={search}', { offset, limit, search }) try { const response = await api.get(url) const usersCount = Object.keys(response.data.ocs.data.users).length @@ -438,7 +470,7 @@ const actions = { .then((response) => { if (Object.keys(response.data.ocs.data.groups).length > 0) { response.data.ocs.data.groups.forEach(function(group) { - context.commit('addGroup', { gid: group, displayName: group }) + context.commit('addGroup', { id: group, name: group }) }) return true } @@ -505,7 +537,7 @@ const actions = { return api.requireAdmin().then((response) => { return api.post(generateOcsUrl('cloud/groups'), { groupid: gid }) .then((response) => { - context.commit('addGroup', { gid, displayName: gid }) + context.commit('addGroup', { id: gid, name: gid }) return { gid, displayName: gid } }) .catch((error) => { throw error }) @@ -636,11 +668,14 @@ const actions = { * @param {string} userid User id * @return {Promise} */ - wipeUserDevices(context, userid) { - return api.requireAdmin().then((response) => { - return api.post(generateOcsUrl('cloud/users/{userid}/wipe', { userid })) - .catch((error) => { throw error }) - }).catch((error) => context.commit('API_FAILURE', { userid, error })) + async wipeUserDevices(context, userid) { + try { + await api.requireAdmin() + return await api.post(generateOcsUrl('cloud/users/{userid}/wipe', { userid })) + } catch (error) { + context.commit('API_FAILURE', { userid, error }) + return Promise.reject(new Error('Failed to wipe user devices')) + } }, /** @@ -730,24 +765,27 @@ const actions = { * @param {string} options.value Value of the change * @return {Promise} */ - setUserData(context, { userid, key, value }) { + async setUserData(context, { userid, key, value }) { const allowedEmpty = ['email', 'displayname', 'manager'] - if (['email', 'language', 'quota', 'displayname', 'password', 'manager'].indexOf(key) !== -1) { - // We allow empty email or displayname - if (typeof value === 'string' - && ( - (allowedEmpty.indexOf(key) === -1 && value.length > 0) - || allowedEmpty.indexOf(key) !== -1 - ) - ) { - return api.requireAdmin().then((response) => { - return api.put(generateOcsUrl('cloud/users/{userid}', { userid }), { key, value }) - .then((response) => context.commit('setUserData', { userid, key, value })) - .catch((error) => { throw error }) - }).catch((error) => context.commit('API_FAILURE', { userid, error })) - } + const validKeys = ['email', 'language', 'quota', 'displayname', 'password', 'manager'] + + if (!validKeys.includes(key)) { + throw new Error('Invalid request data') + } + + // If value is empty and the key doesn't allow empty values, throw error + if (value === '' && !allowedEmpty.includes(key)) { + throw new Error('Value cannot be empty for this field') + } + + try { + await api.requireAdmin() + await api.put(generateOcsUrl('cloud/users/{userid}', { userid }), { key, value }) + return context.commit('setUserData', { userid, key, value }) + } catch (error) { + context.commit('API_FAILURE', { userid, error }) + throw error } - return Promise.reject(new Error('Invalid request data')) }, /** |