Skip to content

feat(settings): let group admins add any existing account to a managed group - #53114

Open
cushdog wants to merge 2 commits into
nextcloud:masterfrom
cushdog:feat/11334-account-endpoint
Open

feat(settings): let group admins add any existing account to a managed group#53114
cushdog wants to merge 2 commits into
nextcloud:masterfrom
cushdog:feat/11334-account-endpoint

Conversation

@cushdog

@cushdogcushdog commented May 26, 2025

Copy link
Copy Markdown

Summary

This PR adds two related features:

  1. Frontend

    • A new “Add existing account” button in the User Management navigation.
    • A modal dialog (AddExistingUserDialog.vue) that lets group-admins search for existing users and assign them to the currently selected group.
    • Vuex state (showAddExistingUserForm) persisted alongside the “New account” form flag to open/close the dialog.
  2. Backend

    • A new Provisioning API endpoint:
      GET /ocs/v2.php/cloud/users/search?search=<query>
      
      Returns a JSON map of userId => displayName for all Nextcloud users matching the search string.
    • Access restricted to admins, delegated admins, and subadmins (throws 403 otherwise).

Screenshots

BeforeAfter
Before ImageAfter

TODO

  • Unit tests
    • Frontend: tests for AddExistingUserDialog.vue (rendering, search behavior, submit).
    • Backend: tests for UsersController::searchAllUsers() (success and 403 cases).
  • Integration/API tests for the new /users/search endpoint.
  • Screenshots (before/after) of the new “Add existing account” button and dialog.
  • Documentation
    • Update the Admin Manual / Provisioning API docs to include /users/search.
    • Add a note in the “Groups” section of the User docs about this UI change.
  • Backport request to the 32 and 31 branches if this lands post-release.

Checklist

  • Code is properly formatted
  • All commits carry a valid DCO sign-off
  • Tests (unit, integration, API) are included
  • Screenshots before/after for front-end changes
  • Documentation (manuals or wiki) updated or noted “not required”
  • Backports requested for stable branches where applicable

Note

I just wanted to quickly note that I didn't update the documentation yet, since I wasn't sure if that should only be updated after this pull-request is potentially accepted!

@cushdog
cushdog requested review from a team and provokateurin as code ownersMay 26, 2025 06:52
@cushdog
cushdog requested review from ArtificialOwl, artonge, skjnldsv and susnux and removed request for a teamMay 26, 2025 06:52
['root' => '/cloud', 'name' => 'Groups#deleteGroup', 'url' => '/groups/{groupId}', 'verb' => 'DELETE', 'requirements' => ['groupId' => '.+']],

// Users
['root' => '/cloud', 'name' => 'Users#getUsers', 'url' => '/users', 'verb' => 'GET'],

Copy link
Copy Markdown
Member

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?

Copy link
Copy Markdown
Author

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!

Copy link
Copy Markdown
Member

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

Comment threadapps/provisioning_api/lib/Controller/UsersController.php
* 200: Users returned
*/
#[NoAdminRequired]
public function searchAllUsers(string $search = '', ?int $limit = null, int $offset = 0): DataResponse {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this not exactly what getUsers does? It also has a search param

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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.
I think getUsersDetails is the one to use as displaynames are needed, I suppose this is what the rest of the frontend uses (I did not check).

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The 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!

</template>

<script>
import { translate as t } from '@nextcloud/l10n'

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
import { translate as t } from '@nextcloud/l10n'
import { t } from '@nextcloud/l10n'

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed!

</NcDialog>
</template>

<script>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would prefer new files to be Typescript.

Suggested change
<script>
<script lang="ts">

(and then use defineComponent below)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also fixed!

<NcIconSvgWrapper :path="mdiCog" />
</template>
{{ t('settings', 'Account management settings') }}
{{ t("settings", "Account management settings") }}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we use single quotes

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Noted and fixed!

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please use npm run lint:fix locally. This contains a lot of fixable issues like usage of semicolon or double quotes.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done!

@sorbaugh

Copy link
Copy Markdown
Contributor

@cushdog for ease of reviewing, can you re-upload the screenshots directly to Github?

Here is a nice example of how you can add the Before/After screenshots: #52423

Thanks again for the PR!

@cushdog
cushdogforce-pushed the feat/11334-account-endpoint branch from 4e16a51 to 66091a4CompareMay 27, 2025 07:04
@cushdog
cushdog requested a review from a team as a code ownerMay 27, 2025 07:04
@cushdog

Copy link
Copy Markdown
Author

Note: just wanted to quickly apologize for the change in commit history, ran into a bit of git trouble that I was able to work out with a force commit!

$isAdmin = $this->groupManager->isAdmin($uid);
$isDelegatedAdmin = $this->groupManager->isDelegatedAdmin($uid);
if ($isAdmin || $isDelegatedAdmin) {
if ($isAdmin || $isDelegatedAdmin || $subAdminManager->isSubAdmin($user)) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This allows group admins to see all users.
@sorbaugh What is the design decision on this? It will be enabled by default? It is a big change.

The linked issue talks about limiting to displayname and a few things, which is not done here, but not sure it would be enough. We might want to safe guard behind an opt-in config flag, at least for upgrades.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

User iteration is not in our threat model, thus not a problem.
What is a problem is that more information as uid and displayname might be available.

Then again this is not a problem, because if we accept this feature, then every group admin can assign users to their group gaining full admin access over them.
So this means a group admin then has the same permissions on user management as admins, as there are not restrictions anymore (they can just add a user to their group and then adjust the user as they like).

For this its good to have approval of @nickvergessen for security point of view.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, the API already allows some extend of it.
Using http requests I as able, as a group admin, to add some users to my groups.

I am not sure why we need to change the API 🤔

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to change the API so that group admins can list all users, to be able to chose which ones they want to add to their groups

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the consensus that I would need to change the API then? I couldn't see any way to go about listing all users without adding in some new endpoint that only group-admins could access, which is why my original commit included a change to the API routes.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to change the API so that group admins can list all users, to be able to chose which ones they want to add to their group

Right, so only the listing API needs a change. Other than that it should be working already.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My thoughts exactly, I'll revert back to the API-change approach I committed originally!

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cushdog You reverted to the added-route version it seems.
I prefer to not add a new route but only fix the existing one, like you did in the version I commented.

Maybe wait for consensus before switching again, but I do not see the point of having 2 routes for listing users? Unless the subadmin one gives access to less things? But as stated by others, as subadmins can add users to their group to gain access to them, they are all user admins in a way.

@cushdogcushdogJun 2, 2025

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! My intention with the new route was for it to act as a "limited access" endpoint. I did explore alternatives that avoided adding a new route, but they would have required changing some fairly fundamental permission or design structures, which I assumed we’d want to avoid.

I realize this is a significant change, and I’ve been trying to stay aligned with the project’s design principles. That said, would it be possible to set up a quick chat (even async) with someone familiar with the architectural constraints here? It would really help to clarify what parts of the existing structure are flexible and what are more set in stone. I think that might be smoother than going back and forth through multiple commits and PR comments.

Totally understand if that's not feasible, just thought I'd ask!

EDIT: I gave this some further thought and compiled a list of implementation ideas that might meet the requirements while aligning better with project direction. Hopefully, this helps us move forward more efficiently (I apologize for all the back-and-forward, I should've asked for clarification earlier and that is on me, and I appreciate your patience tremendously!):

  1. Expand getUsers for subadmins

    • Modify apps/provisioning_api/lib/Controller/UsersController.php in getUsers() to allow subadmins to use userManager->search() when no group filter is provided.
    • This reuses the existing /ocs/v2.php/cloud/users endpoint while returning only user IDs (no new route).
  2. Expand getUsersDetails with limited data

    • Adjust getUsersDetails() so subadmins may search globally.
    • Optionally filter the returned data to { id, displayname } when the caller is a subadmin, preserving privacy.
  3. Add a query parameter to existing route

    • Keep /ocs/v2.php/cloud/users but introduce a parameter like ?scope=all or ?searchAll=true.
    • The controller checks this parameter and, if the requester is a subadmin, lists all users.
  4. Introduce a configuration flag

    • Provide a config option (e.g., allow_group_admin_global_search) that admins can enable.
    • When enabled, getUsers() (and possibly getUsersDetails()) will return all users to subadmins.
  5. Reuse the sharees search API

    • Instead of modifying the provisioning API, call the existing /ocs/v2.php/apps/files_sharing/api/v1/sharees endpoint from the frontend (used by the sharing dialog).
    • It already returns user IDs and display names without requiring admin rights.
  6. Central user-search service

    • Implement a new internal service class (e.g., UserSearchService) that encapsulates permission checks for admins, delegated admins, and subadmins.
    • getUsers() and getUsersDetails() call into this service.
    • Allows future adjustments without new routes.
  7. Keep a dedicated /users/search endpoint (current PR approach)

    • Provide /ocs/v2.php/cloud/users/search returning id → displayname for all users.
    • Restrict access to admins, delegated admins, and subadmins.
    • Frontend uses this endpoint when the current user is only a group admin.
  8. Leverage group endpoints with empty filter

    • Update GroupsController::getGroupUsersDetails() so that when called with an empty group ID by a subadmin, it performs a global search.
    • The frontend then queries this endpoint for user suggestions.
  9. Modify SubAdmin::isUserAccessible

    • Broaden the logic of lib/private/SubAdmin.php so subadmins are considered “accessible” to any user for search purposes.
    • After adding a user to their group, the usual permission checks (e.g., editing) continue to rely on actual group membership.

Comment thread.htaccess Outdated
Comment on lines +126 to +129
#### DO NOT CHANGE ANYTHING ABOVE THIS LINE ####

ErrorDocument 403 /index.php/error/403
ErrorDocument 404 /index.php/error/404

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should not be commited

Comment threadapps/provisioning_api/appinfo/routes.php Outdated
Comment threadapps/settings/l10n/cs.json
Comment threadopenapi.json Outdated
@cushdog

Copy link
Copy Markdown
Author

Apologies, working right now on unstaging the huge amount of extra files that got added in for some reason

@cushdog
cushdogforce-pushed the feat/11334-account-endpoint branch 4 times, most recently from c26dd3b to 64731feCompareMay 29, 2025 05:59
@cushdog
cushdogforce-pushed the feat/11334-account-endpoint branch from 64731fe to 1f8e6c6CompareMay 29, 2025 06:20
@cushdog

Copy link
Copy Markdown
Author

Apologies, working right now on unstaging the huge amount of extra files that got added in for some reason

Ok I think I figured this out, apologies I had to do quite a few git operations I wasn't familiar with!

@github-actions

Copy link
Copy Markdown
Contributor

Hello there,
Thank you so much for taking the time and effort to create a pull request to our Nextcloud project.

We hope that the review process is going smooth and is helpful for you. We want to ensure your pull request is reviewed to your satisfaction. If you have a moment, our community management team would very much appreciate your feedback on your experience with this PR review process.

Your feedback is valuable to us as we continuously strive to improve our community developer experience. Please take a moment to complete our short survey by clicking on the following link: https://cloud.nextcloud.com/apps/forms/s/i9Ago4EQRZ7TWxjfmeEpPkf6

Thank you for contributing to Nextcloud and we hope to hear from you soon!

(If you believe you should not receive this message, you can add yourself to the blocklist.)

@aparcar

Copy link
Copy Markdown

Is there a simple way to test this within an AIO installation? Like can I edit the local files manually and it "just works"? Looking at the changes, this doesn't do any changes to any databases etc

@miaulalala

Copy link
Copy Markdown
Contributor

Is there a simple way to test this within an AIO installation? Like can I edit the local files manually and it "just works"? Looking at the changes, this doesn't do any changes to any databases etc

Since you need to recompile the frontend it won't be that easy unfortuntately.

If you're familiar with Docker you can use https://juliusknorr.github.io/nextcloud-docker-dev/basics/getting-started/

Add your fork as an origin and pull from the origin, then check out your branch, run npm i && npm run build to rebuild the frontend. Don't forget to reload the browser window with shift+ctrl+R to reload without cache.

@susnuxsusnux added the community pull requests from community label Jun 9, 2026
@joshtrichardsjoshtrichards changed the title feat(user-management): add “Add existing account” dialog and provisio…feat(user-management): let group admins add existing accounts to a groupJul 30, 2026
@joshtrichardsjoshtrichards changed the title feat(user-management): let group admins add existing accounts to a groupfeat(settings): let group admins add existing accounts to a groupJul 30, 2026
@joshtrichardsjoshtrichards changed the title feat(settings): let group admins add existing accounts to a groupfeat(settings): let group admins add any existing account to a managed groupJul 30, 2026
@joshtrichardsjoshtrichards added 2. developing Work in progress and removed 3. to review Waiting for reviews labels Jul 30, 2026
@joshtrichards

Copy link
Copy Markdown
Member

I think we should avoid both changing the default behavior of the existing user-listing endpoints for subadmins and adding a generic /users/search route.

Either of these seems reasonable to me:

  • extend the existing endpoint with an explicit opt-in mode and target group, e.g. GET /ocs/v2.php/cloud/users/details?[...]&forGroupAssignment=1&groupId=<gid>; or
  • add a dedicated group-scoped route, e.g. GET /cloud/groups/{groupId}/assignable-users?[...].

The trade-off is mainly a smaller API surface versus a more self-describing route. I do not feel strongly about it, although I slightly lean toward extending /users/details with the opt-in parameter.

Whichever route we choose:

  • regular /users/details requests should keep their current authorization behavior and full response shape;
  • the assignment search should always return only the data this dialog needs (id and displayname), for both full admins and subadmins; and
  • the frontend should use a separate searchGroupAssignmentCandidates action rather than changing the semantics of generic searchUsers, so callers expecting full management records cannot accidentally depend on the restricted response.

For the product/privacy side, I think this should be behind an instance-level setting that is visible in the UI and off by default for both new and upgraded instances. That makes the expanded discovery behavior intentional rather than silently broadening what existing subadmins can see, while giving all instances the same default and avoiding upgrade-specific behavior.

I would also expect the search to match both user IDs and display names, omit users already in the target group, and omit disabled users (assuming comparable user-selection flows also omit them; I did not check).

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

2. developingWork in progresscommunitypull requests from communityfeature: users and groupsfeedback-requested

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Allow group admins to assign any existing account to their managed groups

10 participants

@cushdog@sorbaugh@aparcar@miaulalala@joshtrichards@nickvergessen@susnux@skjnldsv@come-nc@AndyScherzinger