yuqi1129 opened a new issue, #13748: URL: https://github.com/apache/gravitino/issues/13748
### Version main branch ### Describe what's wrong The built-in JCasbin authorizer can make an authorization decision using roles that belong to another in-flight request of the same user. A request can then be allowed access it does not have, or lose a DENY that should apply. Background: - With OAuth/JWT authentication, Gravitino does not store which groups a user belongs to. Each request carries its own groups, parsed from the token's groups claim (`StaticSignKeyValidator` / `JwksTokenValidator`) into the request's `UserPrincipal`. - Gravitino stores which roles are granted to each group, and which privileges each role has. - The same stored user (one user id) can therefore send concurrent requests with different groups. For example, after an administrator adds the user to an IdP group or removes them from one, new tokens reflect the change while an older token is still valid, such as in a long-running Spark job. The authorizer keeps the user's effective roles in shared JCasbin grouping rows `g(userId, roleId)` and evaluates `enforce(userId, ...)` against all roles currently bound to that user id. When a request loads its roles, it (1) adds its own roles to `g(userId, ...)` and (2) prunes roles in `g(userId, ...)` that it does not hold. Two requests of the same user read and write the same rows, so one request's decision can depend on the other's group claims. This applies to single-object checks, the DENY existence check (`hasDenyPolicy`), and list filtering. List filtering evaluates every listed object separately over a longer window, so interleaving is more likely there. ### Error message and/or stacktrace No error is raised. The authorization decision is wrong. ### How to reproduce Setup: - User `alice` (user id 100) has no directly granted roles. - Group `analysts` is granted role `read_sales`: ALLOW `SELECT_TABLE` on table `sales.orders`. - Group `contractors` is granted role `no_pii`: DENY `SELECT_TABLE` on table `sales.orders`. Case 1: a DENY is lost. - Request A uses a token with `groups=[analysts, contractors]`. Expected: denied, because the DENY on `no_pii` takes precedence. - Request B uses an older token with `groups=[analysts]`. | Step | Event | Shared `g(100, ...)` | |---|---|---| | 1 | A loads roles and binds `read_sales` and `no_pii` | read_sales, no_pii | | 2 | B loads roles and prunes `no_pii`, which it does not hold | read_sales | | 3 | A checks `SELECT_TABLE` on `sales.orders` | sees only `read_sales`: **allowed** | Case 2: an ALLOW is injected. - Request A uses a token with `groups=[analysts]`. - Request B uses a newer token with `groups=[]` (alice was removed from `analysts`). Expected: denied. | Step | Event | Shared `g(100, ...)` | |---|---|---| | 1 | B loads roles; it holds none, so it prunes `read_sales` | (empty) | | 2 | A loads roles (or reloads them during list filtering) and binds `read_sales` | read_sales | | 3 | B checks `SELECT_TABLE` on `sales.orders` | sees `read_sales`: **allowed** | The same interleaving in list filtering can return a table that the request is denied. Deterministic reproductions: `testInterleavedGroupRequestsMustNotInjectAllow`, `testInterleavedGroupRequestsMustNotRemoveDeny` and `testInterleavedGroupRequestsMustNotExposeDeniedTable` in `TestJcasbinAuthorizer` (added in #13747). They fail on main and pass with the fix. ### Additional context Fix direction (#13747): stop publishing user membership to shared JCasbin state. Each request keeps the union of its direct and group-inherited role ids as an immutable set in `AuthorizationRequestContext`. Policies are evaluated with a request-aware matcher, `hasRole(r.sub, p.sub)`, against that set instead of `g(r.sub, p.sub)`. Active-role narrowing applies to ALLOW only; DENY still uses the request's full role union, as before. Found while working on #13593. This is a correctness and security fix, separate from the list-authorization performance work tracked there. -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected]
