feat: convert regular accounts to guests#1619
Conversation
|
Hello there, 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.) |
- add "Convert to guest account" for regular, never-logged-in database accounts (admin … menu action + OCS endpoint), keeping the login name and password - add ConversionService for the transactional users -> guests_users move; extract GuestManager::setGuestQuota so converted accounts get the default guest quota - allow a free-form guest login name via `occ guests:add --uid` instead of deriving the user ID from the email address - loosen the guests backend user-id guard to accept any non-empty id Closes #1153 Assisted-by: ClaudeCode:claude-opus-4-8 Signed-off-by: ernolf <raphael.gradenwitz@googlemail.com>
Signed-off-by: ernolf <raphael.gradenwitz@googlemail.com>
- refuse converting members of the admin group and subadmins, both return 409 - add controller tests covering both rejection paths Assisted-by: ClaudeCode:claude-fable-5 Signed-off-by: ernolf <raphael.gradenwitz@googlemail.com>
dc3dcde to
77e31b8
Compare
| * resolved against the guests_users table. | ||
| */ | ||
| protected function potentialGuestUserId(string $userId): bool { | ||
| return str_contains($userId, '@') || preg_match('/^[a-f0-9]{64}$/', $userId); |
There was a problem hiding this comment.
I would prefer if the userId when converting to a guest user is changed instead of changing this method
There was a problem hiding this comment.
Thanks @CarlSchwan , I considered exactly that route and decided against it deliberately, so let me defend the design.
Changing the uid would turn this PR into the opposite of what it closes. #333 (open since 2020, never declined) asks for guests whose login name is not their email address. The registration approval flow is the concrete case: a person signs up with a username the admin deliberately allowed, the admin approves the account and converts it to a guest. Renaming it to a sha256 hash at that moment silently discards the name the person just registered with, and they would have to log in with their email instead. Admins who want uniform email logins already have that choice today, the registration app has a "Force email as login name" setting and even a login name policy regex. I would not want the guests app to override a decision the admin already made there.
There is also a technical reason the uid is preserved: core deliberately offers no uid rename, because the uid is a foreign key in core and app tables alike, group memberships, incoming shares, preferences provisioned before first login, and any other app's tables keyed by uid. An app-side rename can only cover the tables it knows about, everything else keeps pointing at a uid that no longer exists. Guillaume ruled out the direct DB edit in #333 for exactly this reason. Keeping the uid is what makes the conversion a safe, atomic operation, one insert, one delete, one transaction.
On potentialGuestUserId(): the email-or-hash shape was never a correctness boundary, guests_users is. Any regular account with an @ in its uid already passes the check today and falls through to the table lookup, and the email convention stays untouched for invited guests, where the email is the only identity that exists at creation time. The loosened check costs one indexed SELECT per unknown uid, and the miss is cached per request.
So keeping the uid stable is not an implementation shortcut, it is the point of the feature, and I would like to keep it that way.
| // shares) stays valid. | ||
| $this->connection->beginTransaction(); | ||
| try { | ||
| $insert = $this->connection->getQueryBuilder(); |
There was a problem hiding this comment.
Use:
if ($this->config->useHashedEmailAsUserID()) {
$email = strtolower($email);
$username = hash('sha256', $email);
} else {
$username = $email;
}
to create uid, same as in lib/Controller/UsersController.php and lib/Command/AddCommand.php
There was a problem hiding this comment.
Answered in the thread on UserBackend.php, keeping the uid is deliberate, see my reasoning there.
Summary
Adds an admin action to convert a regular account into a guest account, and lets guests have a free-form login name instead of one derived from their email.
Closes #1153
Closes #333
Related #1479
Convert accounts to guests
Create a user (just for testing):
$ occ user:info karltest - user_id: karltest - display_name: Karl Test - email: - cloud_id: karltest@global-social.net - enabled: true - groups: - quota: none - storage: - free: 1184627609600 - used: 0 - total: 1184627609600 - relative: 0 - quota: -3 - first_seen: never - last_seen: never - user_directory: /nc/dat/data/karltest - backend: DatabaseIn Administration settings → Users, the account's
…menu gains Convert to guest account.A confirmation dialog explains the effect: the account keeps its login name and password but becomes a limited guest, restricted to the apps allowed for guests, and receives the default guest quota. The conversion cannot be undone automatically.
It is only possible for accounts that use the database backend, are not already a guest, and have never logged in (enforced in the UI and in the OCS endpoint).
After conversion the account's backend is Guests:
and it logs in with the same password:
It then appears in the guests list:
Use case: self-registration
A typical use case is self-registration through the registration app with "Require administrator approval" enabled: those accounts are created disabled and never logged in, so an administrator can downgrade them to guests instead of enabling them. Once such an account has logged in, conversion is no longer possible.
If you would rather registered users keep their email address as login name, enable "Force email as login name" in the registration app settings.
Custom guest login names
occ guests:addgains--uidto set a free-form login name, instead of always deriving the user ID from the email address (or its hash):Example — create a guest with a chosen login name and verify the user ID:
The guests backend user-id guard is loosened accordingly to accept any non-empty id.
Implementation
ConversionServiceperforms the transactionalusers→guests_usersmove (uid, display name, password hash and email preserved).UsersController::convert()OCS endpoint (admin-only) with the eligibility checks above; routePOST /api/v1/convert.GuestManager::setGuestQuota()extracted so both newly created and converted guests get the configured default quota.occ guests:add --uidfor a free-form login name.--uidoption, the controller endpoint and the conversion service.Testing
Verified on a live instance (screenshots above): created a never-logged-in database account, converted it via the UI, confirmed the backend switched to Guests, the account logs in with the same password, and it appears in the guests list. The action is hidden for accounts that have logged in and for existing guests.
🤖 AI (if applicable)
Assisted-by: ClaudeCode:claude-opus-4-8