Skip to content

feat(auth): store scopes on app passwords - #65268

Open
pringelmann wants to merge 4 commits into
masterfrom
feat/noid/app-password-scope-storage
Open

pringelmann wants to merge 4 commits into
masterfrom
feat/noid/app-password-scope-storage

Conversation

@pringelmann

@pringelmann pringelmann commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor

Summary

First step towards limited app passwords: a token can now carry a list of scopes. This PR only stores them. Apart from files access nothing enforces them yet, and there's no UI.

How scopes work

  • Stored as a scopes key in the existing scope JSON column, so no migration
  • OAuth-style names, defined by the app that owns the data: files:read, files:share in files, calendar:read, contacts:write etc. in dav
  • Each scope stands on its own. Files are the exception: every files scope needs files:read, and files:read needs files:write until read-only files exist
  • No scopes key means full access. That's every existing token, unchanged
  • Empty list or anything malformed means no access
  • Existing filesystem flag stays and is derived from files:read, so code reading it keeps working
  • Per-app scopes come later (app:<appid>)

Change description

  • A new internal helper reads and validates the scope list
  • The files access check uses it for tokens that have scopes
  • Saving a token no longer wipes its scopes. Before, every save replaced the stored permissions with the single filesystem flag sent by the browser. Now the server merges the change, ignores that flag on scoped tokens and only checks the shape of the list. Setting scopes to null resets the token to full access
  • The occ token list shows the scopes

Next step

Enforcement on routes and DAV, then the access picker in Settings.

Checklist

AI (if applicable)

  • The content of this PR was partly or fully generated using AI (tests, reviewed)

@pringelmann pringelmann added this to the Nextcloud 36 milestone Oct 7, 2026
@pringelmann pringelmann self-assigned this Oct 7, 2026
@pringelmann
pringelmann requested a review from a team as a code owner October 7, 2026 09:47
@pringelmann
pringelmann requested review from Altahrim, CarlSchwan and leftybournes and removed request for a team October 7, 2026 09:47
@pringelmann
pringelmann requested a review from come-nc October 7, 2026 09:47
@pringelmann
pringelmann requested a review from artonge October 7, 2026 09:52
Comment thread lib/private/Authentication/Token/Scopes.php Outdated
Comment thread apps/settings/lib/Controller/AuthSettingsController.php Outdated
Comment thread apps/settings/lib/Controller/AuthSettingsController.php Outdated
Comment thread lib/private/Authentication/Token/TokenScopes.php Outdated
Comment thread lib/private/Authentication/Token/Scopes.php Outdated
@pringelmann
pringelmann force-pushed the feat/noid/app-password-scope-storage branch from f50f496 to dd472f6 Compare October 7, 2026 10:53
@pringelmann
pringelmann requested a review from come-nc October 7, 2026 11:01
Comment thread apps/settings/lib/Controller/AuthSettingsController.php
@pringelmann
pringelmann force-pushed the feat/noid/app-password-scope-storage branch from dd472f6 to e3ec082 Compare October 7, 2026 12:27
@pringelmann
pringelmann requested a review from come-nc October 8, 2026 07:44
@pringelmann
pringelmann added this pull request to stack #65341 October 9, 2026 11:15
Signed-off-by: Peter Ringelmann <peter.ringelmann@nextcloud.com>
Signed-off-by: Peter Ringelmann <peter.ringelmann@nextcloud.com>
Signed-off-by: Peter Ringelmann <peter.ringelmann@nextcloud.com>
@pringelmann
pringelmann force-pushed the feat/noid/app-password-scope-storage branch from e3ec082 to 3f0d7ab Compare October 9, 2026 11:25
Comment on lines +28 to +38
private const VOCABULARY = [
// Read-only files need a permissions mask on the mounts, which does not exist yet
self::FILES_READ => self::FILES_WRITE,
self::FILES_WRITE => self::FILES_READ,
self::FILES_SHARE => self::FILES_READ,
self::CALENDAR_READ => null,
self::CALENDAR_WRITE => self::CALENDAR_READ,
self::CONTACTS_READ => null,
self::CONTACTS_WRITE => self::CONTACTS_READ,
];

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

If we do not have a valid use case behind scope's dependency, then we can probably drop that.


public const FILES_READ = 'files:read';
public const FILES_WRITE = 'files:write';
public const FILES_SHARE = 'files:share';

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Suggested change
public const FILES_SHARE = 'files:share';
public const FILES_SHARE = 'files_sharing:write';

Comment on lines +19 to +25
public const FILES_READ = 'files:read';
public const FILES_WRITE = 'files:write';
public const FILES_SHARE = 'files:share';
public const CALENDAR_READ = 'calendar:read';
public const CALENDAR_WRITE = 'calendar:write';
public const CONTACTS_READ = 'contacts:read';
public const CONTACTS_WRITE = 'contacts:write';

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I think those should be move within their respective app to keep this service ressource agnostic.
Unless we have a good reason to have those here.

*
* @throws \InvalidArgumentException
*/
public function mergeUpdate(array $stored, array $requested): array {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

It might make it more explicit regarding what the method actually does:

Suggested change
public function mergeUpdate(array $stored, array $requested): array {
public function mergeScopes(array $scope1, array $scope2): array {

Signed-off-by: Peter Ringelmann <peter.ringelmann@nextcloud.com>

This branch has not been deployed

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

Scoped app passwords: store scopes

3 participants