Repository navigation
feat(auth): limit scoped app passwords to the routes they open - #65340
pringelmann wants to merge 9 commits into
Conversation
| $dispatcher->registerMiddleware($c->get(PublicShareMiddleware::class)); | ||
| $dispatcher->registerMiddleware($c->get(AdditionalScriptsMiddleware::class)); | ||
| // After AdditionalScriptsMiddleware, so pages are refused before their listeners run | ||
| $dispatcher->registerMiddleware($c->get(TokenScopeMiddleware::class)); |
There was a problem hiding this comment.
Own middleware instead of one more check in SecurityMiddleware, which is hand-built and already does a lot. Also registered last on purpose, the test in DIContainerTest checks the order.
| * Page renders run every app's listeners and initial state, so none reach a token with scopes | ||
| */ | ||
| #[\Override] | ||
| public function afterController(Controller $controller, string $methodName, Response $response): Response { |
There was a problem hiding this comment.
We only know it's a page once the controller returns a TemplateResponse, so it has to check after controller runs. Page GETs don't change data, so letting the controller run is fine though. What we block is the render, which pulls in every app's listeners and initial state.
d4d67af to
a2b3655
Compare
|
Drafting for now, got feedback that necessitates major changes |
bd4dc21 to
0823522
Compare
|
Reworked after a design discussion with @artonge : the route list is gone, controllers now declare their scope with an attribute. Comments on |
0823522 to
cf1ad78
Compare
c1b26e4 to
bdac8ee
Compare
|
@artonge I think adding multiple scopes later will be quite easy. For example we can do something like:
Either way I've moved the attribute to NCU so it can stay flexible until 37 cc @CarlSchwan |
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>
…opeRequired Signed-off-by: Peter Ringelmann <peter.ringelmann@nextcloud.com>
Signed-off-by: Peter Ringelmann <peter.ringelmann@nextcloud.com>
2081409 to
fab26ff
Compare
…ble API Signed-off-by: Peter Ringelmann <peter.ringelmann@nextcloud.com>
fab26ff to
f7e03f6
Compare
Signed-off-by: Peter Ringelmann <peter.ringelmann@nextcloud.com>
| * @psalm-import-type Files_SharingRemoteShare from ResponseDefinitions | ||
| * @api | ||
| */ | ||
| #[TokenScope(scope: TokenScopes::FILES_WRITE)] |
There was a problem hiding this comment.
shouldnt this be allowing to write shares?
There was a problem hiding this comment.
Accepting mounts the share into your own files, so I decided to go with files write. The sharing write scope is for creating shares only, and a client without it should still be able to accept incoming shares. Happy to change it if you disagree?
| /** | ||
| * Apps that aren't shipped may only require scopes named after themselves, | ||
| * so they can't open their routes to app passwords limited to another app | ||
| */ | ||
| private function mayRequire(string $scope): bool { | ||
| return $this->isShipped() || str_starts_with($scope, $this->appName . ':'); | ||
| } |
There was a problem hiding this comment.
We kind of need this relaxed. E.g. Talk is integrating with calendar on scheduling meetings. The endpoint in Talk should require dav write, but would currently not be allowed.
Also calendar has the feature to generate a Talk conversation, it should require a talk permission of some kind, but neither Talk nor Calendar is shipped?
There was a problem hiding this comment.
Good point, missed those.
In general @artonge and I agreed to keep checks only at the route and collection level for 36, and not try to follow what an app does with other apps' data within the controller. Also, scoped keys are created manually in settings for now, the Talk and Calendar clients still log in with full access, so these integrations keep working. I'd maybe revisit this once clients can request scopes, probably with a list of required scopes on the attribute.
Something like:
#[TokenScope(scope: ['spreed:conversations:write', 'dav:calendar:write'])]
public function scheduleMeeting(…) {Is OK for you?
There was a problem hiding this comment.
Probably the constraint should be applied to all apps, event those shipped to prevent scope colision.
I am not sure to get your use case Joas. For context, the use case for scopes are mostly scripts, or apps that have really limited needs, so the web UI and the Talk clients should keep the all access token for now
There was a problem hiding this comment.
Makes sense for collisions, but a strict version would break some shipped routes that need another app's scope: previews in core, trash and versions need files read, the sharing API needs files read/write, etc.
So what about: an app can always use its own scopes, and shipped apps can also use other shipped apps' scopes, but never those of an app store app. That way core can't take deck: or spreed:, and the existing routes keep working as before.
There was a problem hiding this comment.
shipped apps can also use other shipped apps' scopes, but never those of an app store app
But do we have a use case where we need that?
There was a problem hiding this comment.
You mean a shipped app needing the scope of an app store app? Or shipped apps needing each other's scopes?
Not sure about the former, but the latter we do have a quite a few (listed in my previous comment)
Signed-off-by: Peter Ringelmann <peter.ringelmann@nextcloud.com>
Signed-off-by: Peter Ringelmann <peter.ringelmann@nextcloud.com>
Summary
Second step for limited app passwords: a token with scopes can only call the index.php and OCS routes its scopes open. Everything else gets a 403. Tokens without scopes aren't affected. DAV is in #65552.
How it works
#[TokenScope]on a method or the class (unstable API inNCU\, so apps can use it). A method's attribute replaces the class one. No attribute means refused, so new endpoints start closed<appid>:scopesfiles:files:read/files:files:write, shares, sharee search and direct linksfiles_sharing:shares:write. Calendar and contacts OCS routes in dav need the matchingdav:scopeKnown limit: an admin's calendar or contacts token can import into other users' calendars and address books via the
userparameter, and a files token can transfer ownership of files to another account.Tests
Unit tests for the middleware and its place in the stack. They pin the scopes of the share, direct link, preview and download endpoints, and check that templates, direct editing, conversion and page methods stay unannotated. Integration tests cover allowed and refused calls with a scoped token.
Next step
DAV enforcement, then the access picker in Settings.
Checklist
3. to review, feature component)stable32)AI (if applicable)