-
-
Notifications
You must be signed in to change notification settings - Fork 4.3k
feat(user-management): add “Add existing account” dialog and provisio… #53114
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -339,6 +339,45 @@ public function getLastLoggedInUsers(string $search = '', | |
} | ||
|
||
|
||
/** | ||
* Search all users by id or display name | ||
* | ||
* Allows subadmins to look up existing users that are not yet part of | ||
* their groups so they can add them. | ||
* | ||
* @param string $search Text to search for | ||
* @param ?int $limit Limit the amount of users returned | ||
* @param int $offset Offset for searching for users | ||
* @return DataResponse<Http::STATUS_OK, array{users: array<string, string>}, array{}> | ||
* | ||
* 200: Users returned | ||
*/ | ||
#[NoAdminRequired] | ||
public function searchAllUsers(string $search = '', ?int $limit = null, int $offset = 0): DataResponse { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this not exactly what There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah there is no need for a new endpoint, the frontend should use the existing ones unless there’s a really good reason. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sorry about this! Changed this and did a much simpler version of what I had before, let me know what you think now! |
||
$currentUser = $this->userSession->getUser(); | ||
|
||
$uid = $currentUser->getUID(); | ||
$subAdminManager = $this->groupManager->getSubAdmin(); | ||
$isAdmin = $this->groupManager->isAdmin($uid); | ||
$isDelegatedAdmin = $this->groupManager->isDelegatedAdmin($uid); | ||
|
||
if ($isAdmin || $isDelegatedAdmin || $subAdminManager->isSubAdmin($currentUser)) { | ||
$users = $this->userManager->searchDisplayName($search, $limit, $offset); | ||
$result = []; | ||
foreach ($users as $user) { | ||
/** @var IUser $user */ | ||
$result[$user->getUID()] = $user->getDisplayName(); | ||
} | ||
|
||
return new DataResponse([ | ||
'users' => $result, | ||
]); | ||
} | ||
|
||
throw new OCSForbiddenException(); | ||
} | ||
|
||
|
||
|
||
/** | ||
* @NoSubAdminRequired | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,178 @@ | ||
<!-- | ||
- SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors | ||
- SPDX-License-Identifier: AGPL-3.0-or-later | ||
--> | ||
<template> | ||
<NcDialog | ||
class="dialog" | ||
size="small" | ||
:name="t('settings', 'Add existing account')" | ||
out-transition | ||
v-on="$listeners" | ||
> | ||
<form | ||
id="add-existing-user-form" | ||
class="dialog__form" | ||
@submit.prevent="submit" | ||
> | ||
<NcSelect | ||
v-model="selectedUser" | ||
class="dialog__select" | ||
:options="possibleUsers" | ||
:placeholder="t('settings', 'Search accounts')" | ||
:user-select="true" | ||
:dropdown-should-open="dropdownShouldOpen" | ||
label="displayname" | ||
@search="searchUsers" | ||
/> | ||
<NcSelect | ||
v-model="selectedGroup" | ||
class="dialog__select" | ||
:options="availableGroups" | ||
:placeholder="t('settings', 'Select group')" | ||
label="name" | ||
:disabled="preselectedGroup" | ||
:clearable="false" | ||
/> | ||
</form> | ||
<template #actions> | ||
<NcButton | ||
class="dialog__submit" | ||
form="add-existing-user-form" | ||
type="primary" | ||
native-type="submit" | ||
> | ||
{{ t('settings', 'Add to group') }} | ||
</NcButton> | ||
</template> | ||
</NcDialog> | ||
</template> | ||
|
||
<script> | ||
import { t } from '@nextcloud/l10n' | ||
import NcDialog from '@nextcloud/vue/components/NcDialog' | ||
import NcSelect from '@nextcloud/vue/components/NcSelect' | ||
import NcButton from '@nextcloud/vue/components/NcButton' | ||
import { showError } from '@nextcloud/dialogs' | ||
import { confirmPassword } from '@nextcloud/password-confirmation' | ||
import logger from '../../logger.ts' | ||
|
||
export default { | ||
name: 'AddExistingUserDialog', | ||
components: { NcDialog, NcSelect, NcButton }, | ||
props: { | ||
group: { | ||
type: Object, | ||
default: null, | ||
}, | ||
}, | ||
data() { | ||
return { | ||
possibleUsers: [], | ||
selectedUser: null, | ||
availableGroups: [], | ||
selectedGroup: null, | ||
preselectedGroup: false, | ||
promise: null, | ||
} | ||
}, | ||
mounted() { | ||
// only subadmins see a filtered list | ||
this.availableGroups = this.$store.getters.getSubAdminGroups | ||
if (this.group) { | ||
this.selectedGroup = this.group | ||
this.preselectedGroup = true | ||
} | ||
}, | ||
methods: { | ||
t, | ||
async searchUsers(query) { | ||
if (this.promise) { | ||
this.promise.cancel?.() | ||
} | ||
try { | ||
this.promise = this.$store.dispatch('searchUsers', { | ||
offset: 0, | ||
limit: 10, | ||
search: query, | ||
}) | ||
const resp = await this.promise | ||
this.possibleUsers = resp?.data | ||
? Object.values(resp.data.ocs.data.users) | ||
: [] | ||
} catch (error) { | ||
logger.error(t('settings', 'Failed to search accounts'), { error }) | ||
} finally { | ||
this.promise = null | ||
} | ||
}, | ||
/** | ||
* Only open the dropdown once there's some text in the search-box. | ||
* @param {object} vm the vue-select instance | ||
* @returns {boolean} | ||
*/ | ||
dropdownShouldOpen(vm) { | ||
return vm.search && vm.search.length > 0; | ||
}, | ||
async submit() { | ||
if (!this.selectedUser || !this.selectedGroup) { | ||
return | ||
} | ||
|
||
// require the admin to re-enter their password | ||
try { | ||
await confirmPassword() | ||
} catch { | ||
showError(t('settings', 'Password confirmation is required')) | ||
return | ||
} | ||
|
||
try { | ||
// now that we've confirmed, call the action (it will include the | ||
// proper header automatically) | ||
await this.$store.dispatch('addUserGroup', { | ||
userid: this.selectedUser.id, | ||
gid: this.selectedGroup.id, | ||
}) | ||
// if this user wasn’t in the list yet, fetch their details | ||
if ( | ||
!this.$store.getters | ||
.getUsers.find((u) => u.id === this.selectedUser.id) | ||
) { | ||
const resp = await this.$store.dispatch( | ||
'getUser', | ||
this.selectedUser.id | ||
) | ||
if (resp) { | ||
this.$store.commit('addUserData', resp) | ||
} | ||
} | ||
this.$emit('closing') | ||
} catch (error) { | ||
logger.error(t('settings', 'Failed to add user to group'), { error }) | ||
showError(t('settings', 'Failed to add user to group')) | ||
} | ||
}, | ||
}, | ||
} | ||
</script> | ||
|
||
<style scoped lang="scss"> | ||
.dialog { | ||
&__form { | ||
display: flex; | ||
flex-direction: column; | ||
align-items: center; | ||
padding: 0 8px; | ||
gap: 4px 0; | ||
} | ||
&__select { | ||
width: 100%; | ||
} | ||
&__submit { | ||
margin-top: 4px; | ||
margin-bottom: 8px; | ||
} | ||
} | ||
</style> | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you run
composer run cs:fix
locally?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done! Thanks for the catch!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmpf the reformating is still there