Skip to content

[WPB-28193] Disentangle oauth scopes (write access should not imply read access). - #5542

Open
fisx wants to merge 29 commits into
developfrom
WPB-28193-disentangle-oauth-scopes-_write-access-should-not-imply-read-access_
Open

fisx wants to merge 29 commits into
developfrom
WPB-28193-disentangle-oauth-scopes-_write-access-should-not-imply-read-access_

Conversation

@fisx

@fisx fisx commented Sep 16, 2026 •

Copy link
Copy Markdown
Contributor

https://wearezeta.atlassian.net/browse/WPB-28193

The problem

We need to express the following oauth access policy: we want to grant oauth access to {PUT,DELETE} /meetings/<domain>/<id>, but not to GET on the same route.

The current implementation of oauth scopes is cumulative: write:conversations implies read:conversations, admin:bla implies write:bla, read:bla. This language cannot express the above policy.

Here is what works: we want to grant write:conversations, so we issue write:conversations tokens and add oauth_scope: conversation to the nginz route, and on PUT,POST requests, the write: is attached to it before checking against the token. We don't want to grant read:conversations, though, but that's ok, because there is no route GET /conversations, and nginz will never construct one.

Now we want to grant write-only and delete-only to /meetings/<id>, which does have a GET route, so this trick does not work any more.

Existing oauth apps and tokens must keep working without interruption.

The solution

Deprecate the oauth_scope nginx directive and introduce a new oauth_scopes directive that supports tiers read, write-only, delete-only instead of read, write, admin.

Checklist

  • Add a new entry in an appropriate subdirectory of changelog.d
  • Read and follow the PR guidelines

@zebot zebot added the ok-to-test Approved for running tests in CI, overrides not-ok-to-test if both labels exist label Sep 16, 2026
@fisx
fisx force-pushed the WPB-28193-disentangle-oauth-scopes-_write-access-should-not-imply-read-access_ branch 6 times, most recently from 5d10243 to aa33c72 Compare September 23, 2026 11:11
Comment thread charts/nginz/values.yaml
- all
oauth_scope: conversations_name
oauth_scope: conversations_name # deprecated, will be ignored if 'oauth_scopes' is present.
oauth_scopes: ["write-only:conversations_name"]

@fisx fisx Sep 23, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Note for the reviewer: there is no ReadConversationName constructor, so we shouldn't list it here or we'll get a unit test failure when matching nginz.conv and swagger.json.

BatchType (BatchLogged, BatchUnLogged),
Blob (Blob),
ColumnType (AsciiColumn, BigIntColumn, BlobColumn, BooleanColumn, DoubleColumn, IntColumn, ListColumn, MaybeColumn, TextColumn, TimestampColumn, UdtColumn, UuidColumn, VarCharColumn),
ColumnType (AsciiColumn, BigIntColumn, BlobColumn, BooleanColumn, DoubleColumn, IntColumn, ListColumn, MaybeColumn, SetColumn, TextColumn, TimestampColumn, UdtColumn, UuidColumn, VarCharColumn),

@fisx fisx Sep 23, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Note for the reviewer: cassandra still contains old tokens, so we return a set of tokens instead of one string, translating new tokens into lists containing themselves only, and old tokens into lists with all the implications.

ngx_str_t oauth_scope;
ngx_flag_t zauth; // 1=on, 0=off
ngx_str_t oauth_scope; // scope base, tier implied by the method (deprecated)
ngx_str_t oauth_scopes; // whole scopes, separated by spaces; supersedes oauth_scope

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Note to self: give this file another read.

import Servant hiding (Handler, JSON, Tagged, addHeader, respond)
import Servant.OpenApi.Internal.Orphans ()
import Test.QuickCheck (Arbitrary (..))
import Test.QuickCheck (Arbitrary (..), listOf1)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Note to self: give this file another read.

instance IsOAuthScope 'ReadFeatureConfigs where
toOAuthScope = ReadFeatureConfigs
instance IsOAuthScope 'ReadConversationsCode where
toOAuthScope = ReadConversationsCode

@fisx fisx Sep 23, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Note to self: I think here the order got garbled. Fix!

@fisx
fisx marked this pull request as ready for review September 23, 2026 13:19
@fisx
fisx requested review from a team as code owners September 23, 2026 13:19
@fisx
fisx requested a lite review from Copilot September 24, 2026 07:41

Copilot AI left a comment

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.

Copilot review overview

🟡 Changes recommended

Unresolved backward-compatibility issues affect legacy scope parsing, refresh-token permissions, empty scopes, and directive validation.

Get a fresh assessment by requesting another Copilot review.

Review effort: Lite
Findings: 1 High severity

Open (1)
What changed in this PR

This PR introduces independent OAuth read, write-only, and delete-only scopes while retaining legacy configuration support.

Changes:

  • Adds oauth_scopes support across nginx, libzauth, OAuth APIs, and charts.
  • Updates persistence, route documentation, tests, and deployment configurations.
  • Documents migration from the deprecated oauth_scope directive.
File Summary
services/​nginz/​third_party/​nginx-zauth-module/​zauth_module.c Adds nginx scope-list handling.
services/​nginz/​integration-test/​conf/​nginz/​nginx.conf Tests new scope configuration.
services/​brig/​test/​integration/​API/​OAuth.hs Updates OAuth integration tests.
services/​brig/​src/​Brig/​API/​OAuth.hs Persists typed scope sets.
libs/​wire-api/​test/​unit/​Test/​Wire/​API/​Routes/​OAuthScopes.hs Validates route scope declarations.
libs/​wire-api/​test/​unit/​Test/​Wire/​API/​OAuth.hs Tests scope parsing and migration.
libs/​wire-api/​src/​Wire/​API/​Routes/​Public/​Galley/​Meetings.hs Updates meeting route scopes.
libs/​wire-api/​src/​Wire/​API/​Routes/​Public/​Galley/​Conversation.hs Updates conversation route scopes.
libs/​wire-api/​src/​Wire/​API/​Routes/​Public.hs Updates scope documentation rendering.
libs/​wire-api/​src/​Wire/​API/​OAuth.hs Defines scopes and legacy conversion.
libs/​libzauth/​libzauth/​src/​oauth.rs Implements tier-aware verification.
libs/​libzauth/​libzauth/​src/​lib.rs Exports verification support.
libs/​libzauth/​libzauth-c/​src/​zauth.h Declares the C API.
libs/​libzauth/​libzauth-c/​src/​lib.rs Exposes the C wrapper.
libs/​cassandra-util/​src/​Cassandra/​CQL.hs Exports Cassandra set support.
libs/​cassandra-util/​src/​Cassandra.hs Re-exports Cassandra support.
integration/​test/​Testlib/​HTTP.hs Adds Host-aware request support.
integration/​test/​Test/​OAuth.hs Adds OAuth integration coverage.
integration/​test/​API/​Nginz.hs Adds nginx OAuth helpers.
docs/​src/​developer/​reference/​config-options.md Documents scope configuration.
deploy/​dockerephemeral/​federation-v2/​nginz/​conf/​nginx.conf Migrates nginx configuration.
deploy/​dockerephemeral/​federation-v1/​nginz/​conf/​nginx.conf Migrates nginx configuration.
deploy/​dockerephemeral/​federation-v0/​nginz/​conf/​nginx.conf Migrates nginx configuration.
charts/​nginz/​values.yaml Defines new route scopes.
charts/​nginz/​templates/​conf/​_nginx.conf.tpl Renders new scope directives.
changelog.d/​1-api-changes/​WPB-28193-disentangle-oauth-scopes-_write-access-should-not-imply-read-access_ Adds API changelog entry.
changelog.d/​0-release-notes/​WPB-28193-nginx-conf-syntax-changed Documents configuration migration.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread libs/wire-api/src/Wire/API/OAuth.hs Outdated
Comment thread charts/nginz/values.yaml
- path: /meetings/([^/]*)/([^/]*)$
envs:
- all
oauth_scopes: [] # https://wearezeta.atlassian.net/browse/WPB-28194: this will be `["write-only:meetings", "delete-only:meetings"]` soon.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Note for the reviewer: https://wearezeta.atlassian.net/browse/WPB-28194 is now just changing this line. If you agree we don't need a separate PR please thumbs-up this, and I'll make another commit.

@battermann battermann left a comment

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.

Can you verify if the docs: https://docs.wire.com/latest/developer/reference/oauth.html are still up to date with this?

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.

Ideally we would move these tests to the integration package. But not sure how practical that is and I also don't want this to block the PR.

Copilot AI left a comment

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.

Copilot review overview

🟡 Changes recommended

Legacy OAuth scope entries need validation against supported scopes.

Get a fresh assessment by requesting another Copilot review.

Review effort: Lite
Findings: 1 Medium severity

Open (1)
Resolved since last review (1)

Comment thread libs/wire-api/test/unit/Test/Wire/API/Routes/OAuthScopes.hs

Copilot AI left a comment

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.

Copilot review overview

🟡 Changes recommended

Critical OAuth compatibility regressions and a test compilation error remain unresolved.

Get a fresh assessment by requesting another Copilot review.

Review effort: Lite
Findings: 6 High severity

Open (6)
Resolved since last review (1)

Comment thread charts/nginz/values.yaml
- all
oauth_scope: conversations_code
oauth_scope: conversations_code # deprecated, will be ignored if 'oauth_scopes' is present.
oauth_scopes: ["read:conversations_code", "write-only:conversations_code"]
location ~* ^(/v[0-9]+)?/conversations/([^/]*)/code {
include common_response_with_zauth.conf;
oauth_scope conversations_code;
oauth_scopes read:conversations_code write-only:conversations_code;
location ~* ^(/v[0-9]+)?/conversations/([^/]*)/code {
include common_response_with_zauth.conf;
oauth_scope conversations_code;
oauth_scopes read:conversations_code write-only:conversations_code;
location ~* ^(/v[0-9]+)?/conversations/([^/]*)/code {
include common_response_with_zauth.conf;
oauth_scope conversations_code;
oauth_scopes read:conversations_code write-only:conversations_code;
Comment thread libs/wire-api/test/unit/Test/Wire/API/Routes/OAuthScopes.hs Outdated
# 'testOAuthScopeTiersAreSeparate' and
# 'testOAuthNewScopesOnDeprecatedAttribute' in
# integration/test/Test/OAuth.hs.
oauth_scopes read:conversations_code write-only:conversations_code;
@battermann
battermann force-pushed the WPB-28193-disentangle-oauth-scopes-_write-access-should-not-imply-read-access_ branch from 3a83dbe to 6f238cc Compare September 24, 2026 15:16
@fisx

fisx commented Sep 25, 2026

Copy link
Copy Markdown
Contributor Author

Can you verify if the docs: https://docs.wire.com/latest/developer/reference/oauth.html are still up to date with this?

https://github.com/wireapp/wire-docs/pull/131/changes

it's on my list...

The change to the values.yaml schema is fully backwards compatible,
but since the new behavior should ignore `oauth_scope` if
`oauth_scopes` is present, this will make wire-api tests fail.  good.
fisx added 24 commits September 28, 2026 08:13
Now values.yaml must match docs according to new oauth scope
semantics.

The actual policy control happens in libzauth and is changed in the
next commit.
Until now, it succeeded with a scope-less, and thus useless, token
issued, setting the user up for a disappointment.
This morally reverts 2bca04e
"Refactor: split up `data OAuthScope` into base and tier."
@fisx
fisx force-pushed the WPB-28193-disentangle-oauth-scopes-_write-access-should-not-imply-read-access_ branch from 6f238cc to 848afbd Compare September 28, 2026 06:13

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

Labels

ok-to-test Approved for running tests in CI, overrides not-ok-to-test if both labels exist

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants