Skip to content

Commit e229102

Browse files
authored
Merge pull request #52417 from nextcloud/fix/group-admin-new-user
fix(settings): only provide groups the subadmin has access to
2 parents 8df3310 + 1ba88da commit e229102

File tree

10 files changed

+251
-27
lines changed

10 files changed

+251
-27
lines changed

apps/settings/lib/Controller/UsersController.php

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,9 @@
4040
use OCP\BackgroundJob\IJobList;
4141
use OCP\Encryption\IManager;
4242
use OCP\EventDispatcher\IEventDispatcher;
43+
use OCP\Group\ISubAdmin;
4344
use OCP\IConfig;
45+
use OCP\IGroup;
4446
use OCP\IGroupManager;
4547
use OCP\IL10N;
4648
use OCP\INavigationManager;
@@ -49,7 +51,6 @@
4951
use OCP\IUserSession;
5052
use OCP\L10N\IFactory;
5153
use OCP\Mail\IMailer;
52-
use OCP\Server;
5354
use OCP\Util;
5455
use function in_array;
5556

@@ -88,8 +89,8 @@ public function __construct(
8889
*/
8990
#[NoAdminRequired]
9091
#[NoCSRFRequired]
91-
public function usersListByGroup(): TemplateResponse {
92-
return $this->usersList();
92+
public function usersListByGroup(INavigationManager $navigationManager, ISubAdmin $subAdmin): TemplateResponse {
93+
return $this->usersList($navigationManager, $subAdmin);
9394
}
9495

9596
/**
@@ -99,13 +100,13 @@ public function usersListByGroup(): TemplateResponse {
99100
*/
100101
#[NoAdminRequired]
101102
#[NoCSRFRequired]
102-
public function usersList(): TemplateResponse {
103+
public function usersList(INavigationManager $navigationManager, ISubAdmin $subAdmin): TemplateResponse {
103104
$user = $this->userSession->getUser();
104105
$uid = $user->getUID();
105106
$isAdmin = $this->groupManager->isAdmin($uid);
106107
$isDelegatedAdmin = $this->groupManager->isDelegatedAdmin($uid);
107108

108-
Server::get(INavigationManager::class)->setActiveEntry('core_users');
109+
$navigationManager->setActiveEntry('core_users');
109110

110111
/* SORT OPTION: SORT_USERCOUNT or SORT_GROUPNAME */
111112
$sortGroupsBy = MetaData::SORT_USERCOUNT;
@@ -181,6 +182,14 @@ public function usersList(): TemplateResponse {
181182
'usercount' => $disabledUsers
182183
];
183184

185+
if (!$isAdmin && !$isDelegatedAdmin) {
186+
$subAdminGroups = array_map(
187+
fn (IGroup $group) => ['id' => $group->getGID(), 'name' => $group->getDisplayName()],
188+
$subAdmin->getSubAdminsGroups($user),
189+
);
190+
$subAdminGroups = array_values($subAdminGroups);
191+
}
192+
184193
/* QUOTAS PRESETS */
185194
$quotaPreset = $this->parseQuotaPreset($this->config->getAppValue('files', 'quota_preset', '1 GB, 5 GB, 10 GB'));
186195
$allowUnlimitedQuota = $this->config->getAppValue('files', 'allow_unlimited_quota', '1') === '1';
@@ -204,6 +213,7 @@ public function usersList(): TemplateResponse {
204213
$serverData = [];
205214
// groups
206215
$serverData['systemGroups'] = [$adminGroupData, $recentUsersGroup, $disabledUsersGroup];
216+
$serverData['subAdminGroups'] = $subAdminGroups ?? [];
207217
// Various data
208218
$serverData['isAdmin'] = $isAdmin;
209219
$serverData['isDelegatedAdmin'] = $isDelegatedAdmin;

apps/settings/src/components/AppNavigationGroupList.vue

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -57,12 +57,16 @@
5757
</template>
5858

5959
<script setup lang="ts">
60+
import type CancelablePromise from 'cancelable-promise'
61+
import type { IGroup } from '../views/user-types.d.ts'
62+
63+
import { mdiAccountGroup, mdiPlus } from '@mdi/js'
64+
import { showError } from '@nextcloud/dialogs'
65+
import { t } from '@nextcloud/l10n'
66+
import { useElementVisibility } from '@vueuse/core'
6067
import { computed, ref, watch, onBeforeMount } from 'vue'
6168
import { Fragment } from 'vue-frag'
6269
import { useRoute, useRouter } from 'vue-router/composables'
63-
import { useElementVisibility } from '@vueuse/core'
64-
import { showError } from '@nextcloud/dialogs'
65-
import { mdiAccountGroup, mdiPlus } from '@mdi/js'
6670

6771
import NcActionInput from '@nextcloud/vue/components/NcActionInput'
6872
import NcActionText from '@nextcloud/vue/components/NcActionText'
@@ -137,12 +141,16 @@ watch(groupsSearchQuery, async () => {
137141
})
138142

139143
/** Cancelable promise for search groups request */
140-
const promise = ref(null)
144+
const promise = ref<CancelablePromise<IGroup[]>>()
141145

142146
/**
143147
* Load groups
144148
*/
145149
async function loadGroups() {
150+
if (!isAdminOrDelegatedAdmin.value) {
151+
return
152+
}
153+
146154
if (promise.value) {
147155
promise.value.cancel()
148156
}
@@ -163,7 +171,7 @@ async function loadGroups() {
163171
} catch (error) {
164172
logger.error(t('settings', 'Failed to load groups'), { error })
165173
}
166-
promise.value = null
174+
promise.value = undefined
167175
loadingGroups.value = false
168176
}
169177

apps/settings/src/components/UserList.vue

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -350,11 +350,13 @@ export default {
350350
setNewUserDefaultGroup(value) {
351351
// Is no value set, but user is a line manager we set their group as this is a requirement for line manager
352352
if (!value && !this.settings.isAdmin && !this.settings.isDelegatedAdmin) {
353+
const groups = this.$store.getters.getSubAdminGroups
353354
// if there are multiple groups we do not know which to add,
354355
// so we cannot make the managers life easier by preselecting it.
355-
if (this.groups.length === 1) {
356-
value = this.groups[0].id
356+
if (groups.length === 1) {
357+
this.newUser.groups = [...groups]
357358
}
359+
return
358360
}
359361

360362
if (value) {

apps/settings/src/components/Users/NewUserDialog.vue

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@
6161
:required="newUser.password === '' || settings.newUserRequireEmail" />
6262
<div class="dialog__item">
6363
<NcSelect class="dialog__select"
64+
data-test="groups"
6465
:input-label="!settings.isAdmin && !settings.isDelegatedAdmin ? t('settings', 'Member of the following groups (required)') : t('settings', 'Member of the following groups')"
6566
:placeholder="t('settings', 'Set account groups')"
6667
:disabled="loading.groups || loading.all"
@@ -69,7 +70,7 @@
6970
label="name"
7071
:close-on-select="false"
7172
:multiple="true"
72-
:taggable="true"
73+
:taggable="settings.isAdmin || settings.isDelegatedAdmin"
7374
:required="!settings.isAdmin && !settings.isDelegatedAdmin"
7475
:create-option="(value) => ({ id: value, name: value, isCreating: true })"
7576
@search="searchGroups"
@@ -178,7 +179,7 @@ export default {
178179

179180
data() {
180181
return {
181-
availableGroups: this.$store.getters.getSortedGroups.filter(group => group.id !== '__nc_internal_recent' && group.id !== 'disabled'),
182+
availableGroups: [],
182183
possibleManagers: [],
183184
// TRANSLATORS This string describes a manager in the context of an organization
184185
managerInputLabel: t('settings', 'Manager'),
@@ -235,6 +236,13 @@ export default {
235236
},
236237

237238
mounted() {
239+
// admins also can assign the system groups
240+
if (this.isAdmin || this.isDelegatedAdmin) {
241+
this.availableGroups = this.$store.getters.getSortedGroups.filter(group => group.id !== '__nc_internal_recent' && group.id !== 'disabled')
242+
} else {
243+
this.availableGroups = [...this.$store.getters.getSubAdminGroups]
244+
}
245+
238246
this.$refs.username?.focus?.()
239247
},
240248

@@ -273,6 +281,11 @@ export default {
273281
},
274282

275283
async searchGroups(query, toggleLoading) {
284+
if (!this.isAdmin && !this.isDelegatedAdmin) {
285+
// managers cannot search for groups
286+
return
287+
}
288+
276289
if (this.promise) {
277290
this.promise.cancel()
278291
}

apps/settings/src/store/users.js

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,10 @@ const defaults = {
3636

3737
const state = {
3838
users: [],
39-
groups: [...(usersSettings.systemGroups ?? [])],
39+
groups: [
40+
...(usersSettings.getSubAdminGroups ?? []),
41+
...(usersSettings.systemGroups ?? []),
42+
],
4043
orderBy: usersSettings.sortGroups ?? GroupSorting.UserCount,
4144
minPasswordLength: 0,
4245
usersOffset: 0,
@@ -232,12 +235,10 @@ const mutations = {
232235
* @param {object} state the store state
233236
*/
234237
resetGroups(state) {
235-
const systemGroups = state.groups.filter(group => [
236-
'admin',
237-
'__nc_internal_recent',
238-
'disabled',
239-
].includes(group.id))
240-
state.groups = [...systemGroups]
238+
state.groups = [
239+
...(usersSettings.getSubAdminGroups ?? []),
240+
...(usersSettings.systemGroups ?? []),
241+
]
241242
},
242243

243244
setShowConfig(state, { key, value }) {
@@ -270,6 +271,10 @@ const getters = {
270271
getGroups(state) {
271272
return state.groups
272273
},
274+
getSubAdminGroups() {
275+
return usersSettings.subAdminGroups ?? []
276+
},
277+
273278
getSortedGroups(state) {
274279
const groups = [...state.groups]
275280
if (state.orderBy === GroupSorting.UserCount) {

0 commit comments

Comments
 (0)