Skip to content

Conversation

@cushdog
Copy link

@cushdog cushdog commented May 26, 2025

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 Image After

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 owners May 26, 2025 06:52
@cushdog cushdog requested review from ArtificialOwl, artonge, skjnldsv and susnux and removed request for a team May 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
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
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
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

* 200: Users returned
*/
#[NoAdminRequired]
public function searchAllUsers(string $search = '', ?int $limit = null, int $offset = 0): DataResponse {
Copy link
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
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
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
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
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
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
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
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
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
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
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
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 cushdog force-pushed the feat/11334-account-endpoint branch from 4e16a51 to 66091a4 Compare May 27, 2025 07:04
@cushdog cushdog requested a review from a team as a code owner May 27, 2025 07:04
@cushdog
Copy link
Author

cushdog commented May 27, 2025

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
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
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
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
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
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
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
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
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.

Copy link
Author

@cushdog cushdog Jun 2, 2025

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.

.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
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

@cushdog
Copy link
Author

cushdog commented May 29, 2025

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

@cushdog cushdog force-pushed the feat/11334-account-endpoint branch 4 times, most recently from c26dd3b to 64731fe Compare May 29, 2025 05:59
@cushdog cushdog force-pushed the feat/11334-account-endpoint branch from 64731fe to 1f8e6c6 Compare May 29, 2025 06:20
@cushdog
Copy link
Author

cushdog commented May 29, 2025

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
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.)

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Groups: Allow group admins to assign existing users

7 participants