gupta-sahil01 opened a new issue, #8353:
URL: https://github.com/apache/texera/issues/8353

   ### What happened?
   
   Three sharing endpoints return an opaque **HTTP 500** when access is revoked 
for an email that has no account:
   
   | Endpoint | Service | Unguarded line |
   | --- | --- | --- |
   | `DELETE /api/access/dataset/revoke/{did}/{email}` | file-service | 
`ResourceAccess.scala:288` |
   | `DELETE /api/access/model/revoke/{mid}/{email}` | file-service | 
`ResourceAccess.scala:288` (shared helper) |
   | `DELETE /api/access/project/revoke/{pid}/{email}` | amber | 
`ProjectAccessResource.scala:172` |
   
   `UserDao.fetchOneByEmail` returns `null` for an unknown address, and both 
call sites dereference it directly:
   
   ```scala
   // ResourceAccess.scala:288  (revoke)
   val granteeUid = new 
UserDao(ctx.configuration()).fetchOneByEmail(email).getUid
   ```
   
   ```scala
   // ProjectAccessResource.scala:172  (revokeAccess)
   PROJECT_USER_ACCESS.UID.eq(userDao.fetchOneByEmail(email).getUid)
   ```
   
   The **grant** path in the very same files already handles this correctly:
   
   ```scala
   // ResourceAccess.scala:255-258  (grant)
   val grantee = new UserDao(ctx.configuration()).fetchOneByEmail(email)
   if (grantee == null || grantee.getIsPlaceholder) {
     throw new BadRequestException(s"No registered user with email $email")
   }
   ```
   
   No `ExceptionMapper` is registered for `NullPointerException` in either 
service — the only mapper in the tree is `UnauthorizedExceptionMapper` 
(`common/auth/.../AuthFeatures.scala:38`) — so the NPE surfaces as a bare 500.
   
   **Expected:** `400` with `No registered user with email {email}`, matching 
the grant path and matching `ComputingUnitAccessResource`.
   
   ### Current behaviour across the four access resources
   
   | Resource | grant | revoke |
   | --- | --- | --- |
   | `ComputingUnitAccessResource` | ✅ `resolveUidByEmail` → 400 | ✅ 
`resolveUidByEmail` → 400 |
   | `WorkflowAccessResource` | ✅ explicit check → 400 | ⚠️ `catch { case _: 
NullPointerException }` → 400 |
   | `ProjectAccessResource` | ✅ explicit check → 400 | ❌ **500** |
   | `ResourceAccess` (dataset + model) | ✅ explicit check → 400 | ❌ **500** |
   
   ### Relationship to #6445
   
   #6445 fixed exactly this defect for the computing-unit endpoints and 
introduced `ComputingUnitAccessResource.resolveUidByEmail` as the fix. Its 
rationale reads:
   
   > This matches how 
DatasetAccessResource/WorkflowAccessResource/ProjectAccessResource already 
behave; the computing-unit resource had diverged.
   
   That premise holds only for the **grant** path. On the **revoke** path, the 
dataset/model and project resources still perform the unguarded dereference, so 
#6445 aligned the computing-unit resource to a standard the reference resources 
do not themselves meet. This issue covers the remaining three endpoints.
   
   Not a regression: the unguarded revoke predates the `ResourceAccess` 
extraction in #7760 (`git show 4842e93f2^` shows the same pattern in 
`DatasetAccessResource.revokeAccess`); the refactor carried the grant-side 
check across and left revoke as it found it.
   
   ### Additional notes
   
   - `ResourceAccess.revoke`'s scaladoc states *"Removes the user's explicit 
grant; a no-op when they hold none."* That is accurate for a **registered** 
user holding no grant, but not for an unregistered address, where the call 500s.
   - Unlike #6445, this is **not reachable from the UI**: the share dialog only 
renders a revoke control beside users already present in the access list, so an 
unregistered email never gets one. The endpoint is reachable only by direct API 
call, which is likely why it has gone unnoticed.
   - The revoke path also does not reject placeholder accounts, which grant 
rejects via `getIsPlaceholder`. Worth aligning in the same change.
   
   ---
   
   ### How to reproduce?
   
   ### Via the API
   
   Reproduced on a local `bin/local-dev.sh up` stack, signed in as the admin 
account (the only registered user, `email = texera`):
   
   ```js
   // browser devtools console on http://localhost:4200
   const t = localStorage.getItem("access_token");
   
   // unregistered email -> 500
   await fetch(`/api/access/dataset/revoke/2/[email protected]`, {
     method: "DELETE", headers: { Authorization: `Bearer ${t}` },
   }).then(r => r.status);   // 500
   
   // same email, same dataset, grant instead of revoke -> 400
   await fetch(`/api/access/dataset/grant/2/[email protected]/READ`, {
     method: "PUT", headers: { Authorization: `Bearer ${t}` },
   }).then(r => r.status);   // 400
   ```
   
   The caller needs write access on the resource, so `requireWriteAccess` 
passes and execution reaches the dereference.
   
   Same result for `/api/access/model/revoke/{mid}/{email}` and 
`/api/access/project/revoke/{pid}/{email}`.
   
   ### As a unit test
   
   Added to 
`file-service/src/test/scala/org/apache/texera/service/resource/DatasetAccessResourceSpec.scala`,
 this fails with `NullPointerException` instead of `BadRequestException`:
   
   ```scala
   it should "reject a revoke for an email with no account" in {
     assertThrows[BadRequestException] {
       accessResource.revokeAccess(
         privateDataset.getDid,
         "[email protected]",
         ownerSession
       )
     }
   }
   ```
   
   ```
   sbt "FileService/testOnly *DatasetAccessResourceSpec"
   ```
   
   The existing neighbouring test, *"succeed as a no-op when the target user 
has no explicit grant"*, passes a **registered** user who holds no grant, which 
is why the gap was not caught.
   
   ---
   
   ### Version/Branch
   
   1.4.0-incubating-SNAPSHOT (main)
   
   ### Commit Hash (Optional)
   
   8d422b7b06
   
   ### What browsers are you seeing the problem on?
   
   _No response_
   
   ### Relevant log output
   
   ```shell
   ! java.lang.NullPointerException: Cannot invoke 
"org.apache.texera.dao.jooq.generated.tables.pojos.User.getUid()" because the 
return value of 
"org.apache.texera.dao.jooq.generated.tables.daos.UserDao.fetchOneByEmail(String)"
 is null
   ! at 
org.apache.texera.service.resource.ResourceAccess$.revoke(ResourceAccess.scala:288)
   ! at 
org.apache.texera.service.resource.DatasetAccessResource.$anonfun$revokeAccess$1(DatasetAccessResource.scala:138)
   ! at 
org.apache.texera.dao.SqlServer$.$anonfun$withTransaction$1(SqlServer.scala:101)
   ! at 
org.jooq.impl.DefaultDSLContext.lambda$transaction$5(DefaultDSLContext.java:593)
   ! at 
org.jooq.impl.DefaultDSLContext.lambda$transactionResult0$3(DefaultDSLContext.java:531)
   ! at org.jooq.impl.Tools$3$1.block(Tools.java:6416)
   ! at 
java.base/java.util.concurrent.ForkJoinPool.unmanagedBlock(ForkJoinPool.java:3463)
   ! at 
java.base/java.util.concurrent.ForkJoinPool.managedBlock(ForkJoinPool.java:3434)
   ! at org.jooq.impl.Tools$3.get(Tools.java:6413)
   ! at 
org.jooq.impl.DefaultDSLContext.transactionResult0(DefaultDSLContext.java:579)
   ! at 
org.jooq.impl.DefaultDSLContext.transactionResult(DefaultDSLContext.java:502)
   ! at org.jooq.impl.DefaultDSLContext.transaction(DefaultDSLContext.java:592)
   ```


-- 
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