This is an automated email from the ASF dual-hosted git repository. github-merge-queue[bot] pushed a commit to branch gh-readonly-queue/main/pr-5156-770709b83d47fa7cd302ad5cb3ecf7a6546016de in repository https://gitbox.apache.org/repos/asf/texera.git
commit 0bce181efd0604caa842aaeaa77eff63a6234c9f Author: Minh Vu <[email protected]> AuthorDate: Sun May 24 10:12:22 2026 +0200 fix(project): correct access privilege lookup (#5156) ### What changes were proposed in this PR? Fixes the project access privilege lookup to query `PROJECT_USER_ACCESS` by `(pid, uid)` instead of mixing workflow/dataset access tables. Adds regression coverage for WRITE, READ, and NONE project access rows. ### Any related issues, documentation, discussions? Closes #5155 ### How was this PR tested? - `git diff --check` - `git diff --cached --check` - Attempted: `JAVA_HOME=/opt/homebrew/opt/openjdk@17/libexec/openjdk.jdk/Contents/Home PATH=/opt/homebrew/opt/openjdk@17/bin:$PATH /tmp/texera-sbt "WorkflowExecutionService / Test / testOnly org.apache.texera.web.resource.dashboard.user.project.ProjectAccessResourceSpec"` - Blocked before running the spec because local JOOQ generation could not authenticate to Postgres: `FATAL: password authentication failed for user "postgres"`, causing generated DAO classes to be unavailable. ### Was this PR authored or co-authored using generative AI tooling? No Co-authored-by: Meng Wang <[email protected]> --- .../user/project/ProjectAccessResource.scala | 15 +-- .../user/project/ProjectAccessResourceSpec.scala | 132 +++++++++++++++++++++ 2 files changed, 138 insertions(+), 9 deletions(-) diff --git a/amber/src/main/scala/org/apache/texera/web/resource/dashboard/user/project/ProjectAccessResource.scala b/amber/src/main/scala/org/apache/texera/web/resource/dashboard/user/project/ProjectAccessResource.scala index 1e3340973d..cf7e2cadc4 100644 --- a/amber/src/main/scala/org/apache/texera/web/resource/dashboard/user/project/ProjectAccessResource.scala +++ b/amber/src/main/scala/org/apache/texera/web/resource/dashboard/user/project/ProjectAccessResource.scala @@ -22,17 +22,14 @@ package org.apache.texera.web.resource.dashboard.user.project import io.dropwizard.auth.Auth import org.apache.texera.auth.SessionUser import org.apache.texera.dao.SqlServer -import org.apache.texera.dao.jooq.generated.Tables.{ - DATASET_USER_ACCESS, - PROJECT_USER_ACCESS, - USER, - WORKFLOW_USER_ACCESS -} +import org.apache.texera.dao.jooq.generated.Tables.{PROJECT_USER_ACCESS, USER} import org.apache.texera.dao.jooq.generated.enums.PrivilegeEnum import org.apache.texera.dao.jooq.generated.tables.daos.{ProjectDao, ProjectUserAccessDao, UserDao} import org.apache.texera.dao.jooq.generated.tables.pojos.ProjectUserAccess import org.apache.texera.web.model.common.AccessEntry -import org.apache.texera.web.resource.dashboard.user.project.ProjectAccessResource.userHasWriteAccess +import org.apache.texera.web.resource.dashboard.user.project.ProjectAccessResource.{ + userHasWriteAccess +} import org.jooq.DSLContext import java.util @@ -54,11 +51,11 @@ object ProjectAccessResource { Option( context .select(PROJECT_USER_ACCESS.PRIVILEGE) - .from(WORKFLOW_USER_ACCESS) + .from(PROJECT_USER_ACCESS) .where( PROJECT_USER_ACCESS.PID .eq(pid) - .and(DATASET_USER_ACCESS.UID.eq(uid)) + .and(PROJECT_USER_ACCESS.UID.eq(uid)) ) .fetchOneInto(classOf[PrivilegeEnum]) ).getOrElse(PrivilegeEnum.NONE) diff --git a/amber/src/test/scala/org/apache/texera/web/resource/dashboard/user/project/ProjectAccessResourceSpec.scala b/amber/src/test/scala/org/apache/texera/web/resource/dashboard/user/project/ProjectAccessResourceSpec.scala new file mode 100644 index 0000000000..185d5afcd1 --- /dev/null +++ b/amber/src/test/scala/org/apache/texera/web/resource/dashboard/user/project/ProjectAccessResourceSpec.scala @@ -0,0 +1,132 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.texera.web.resource.dashboard.user.project + +import org.apache.texera.auth.SessionUser +import org.apache.texera.dao.MockTexeraDB +import org.apache.texera.dao.jooq.generated.Tables.{PROJECT, PROJECT_USER_ACCESS, USER} +import org.apache.texera.dao.jooq.generated.enums.{PrivilegeEnum, UserRoleEnum} +import org.apache.texera.dao.jooq.generated.tables.daos.{ProjectUserAccessDao, UserDao} +import org.apache.texera.dao.jooq.generated.tables.pojos.{ProjectUserAccess, User} +import org.scalatest.flatspec.AnyFlatSpec +import org.scalatest.{BeforeAndAfterAll, BeforeAndAfterEach} + +class ProjectAccessResourceSpec + extends AnyFlatSpec + with BeforeAndAfterAll + with BeforeAndAfterEach + with MockTexeraDB { + + private val ownerUid = 7101 + private val readerUid = 7102 + + private var owner: User = _ + private var reader: User = _ + private var userDao: UserDao = _ + private var projectUserAccessDao: ProjectUserAccessDao = _ + private var projectResource: ProjectResource = _ + + override protected def beforeAll(): Unit = { + initializeDBAndReplaceDSLContext() + } + + override protected def beforeEach(): Unit = { + userDao = new UserDao(getDSLContext.configuration()) + projectUserAccessDao = new ProjectUserAccessDao(getDSLContext.configuration()) + projectResource = new ProjectResource() + + owner = createUser(ownerUid, "project_owner", "[email protected]") + reader = createUser(readerUid, "project_reader", "[email protected]") + + cleanupTestData() + + userDao.insert(owner) + userDao.insert(reader) + } + + override protected def afterEach(): Unit = { + cleanupTestData() + } + + override protected def afterAll(): Unit = { + shutdownDB() + } + + private def createUser(uid: Int, name: String, email: String): User = { + val user = new User + user.setUid(uid) + user.setName(name) + user.setEmail(email) + user.setPassword("password") + user.setRole(UserRoleEnum.REGULAR) + user + } + + private def cleanupTestData(): Unit = { + getDSLContext + .deleteFrom(PROJECT_USER_ACCESS) + .where(PROJECT_USER_ACCESS.UID.in(ownerUid, readerUid)) + .execute() + + getDSLContext + .deleteFrom(PROJECT) + .where(PROJECT.OWNER_ID.eq(ownerUid)) + .execute() + + getDSLContext + .deleteFrom(USER) + .where(USER.UID.in(ownerUid, readerUid)) + .execute() + } + + "ProjectAccessResource.getProjectAccessPrivilege" should "return WRITE if granted" in { + val project = projectResource.createProject(new SessionUser(owner), "write-project") + val privilege = ProjectAccessResource.getProjectAccessPrivilege(project.getPid, ownerUid) + + assert(privilege == PrivilegeEnum.WRITE) + assert(ProjectAccessResource.userHasWriteAccess(project.getPid, ownerUid)) + } + + it should "return READ if a project access row grants READ" in { + val project = projectResource.createProject(new SessionUser(owner), "read-project") + projectUserAccessDao.merge( + new ProjectUserAccess(readerUid, project.getPid, PrivilegeEnum.READ) + ) + + val privilege = ProjectAccessResource.getProjectAccessPrivilege(project.getPid, readerUid) + + assert(privilege == PrivilegeEnum.READ) + assert(!ProjectAccessResource.userHasWriteAccess(project.getPid, readerUid)) + } + + it should "return NONE if the user only has access to another project" in { + val sharedProject = projectResource.createProject(new SessionUser(owner), "shared-project") + val privateProject = projectResource.createProject(new SessionUser(owner), "private-project") + projectUserAccessDao.merge( + new ProjectUserAccess(readerUid, sharedProject.getPid, PrivilegeEnum.READ) + ) + + val privilege = + ProjectAccessResource.getProjectAccessPrivilege(privateProject.getPid, readerUid) + + assert(privilege == PrivilegeEnum.NONE) + assert(!ProjectAccessResource.userHasWriteAccess(privateProject.getPid, readerUid)) + } +}
