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]

Reply via email to