aboutsummaryrefslogtreecommitdiffstats
path: root/apps/settings/src/store/users.js
diff options
context:
space:
mode:
Diffstat (limited to 'apps/settings/src/store/users.js')
-rw-r--r--apps/settings/src/store/users.js184
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'))
},
/**