You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@texera.apache.org by gi...@apache.org on 2026/07/30 13:14:22 UTC
(texera) 01/01: fix(amber, v1.2): require auth on retrieveWorkflowRuntimeStatistics (#7054)
This is an automated email from the ASF dual-hosted git repository.
github-merge-queue[bot] pushed a commit to branch gh-readonly-queue/release/v1.2/pr-7054-551b0baab5c7d462cb4047b383264bd8cca47a38
in repository https://gitbox.apache.org/repos/asf/texera.git
commit dc47197dbfb49680710152d4c0e1b0e383b68756
Author: github-actions[bot] <41...@users.noreply.github.com>
AuthorDate: Thu Jul 30 09:13:59 2026 -0400
fix(amber, v1.2): require auth on retrieveWorkflowRuntimeStatistics (#7054)
### What changes were proposed in this PR?
Automated backport of #7014 to `release/v1.2`.
Source: 0635737cb1cab5c9c0ea9bfb1773becc9d91a69a · [automation
run](https://github.com/apache/texera/actions/runs/30489096963)
### Any related issues, documentation, discussions?
Backport of #7014. Originally linked #6977.
### How was this PR tested?
Release-branch CI runs on this branch once the build is fixed and this
PR is marked ready for review.
### Was this PR authored or co-authored using generative AI tooling?
No.
Signed-off-by: Xinyuan Lin <xi...@uci.edu>
Co-authored-by: Meng Wang <me...@uci.edu>
Co-authored-by: Xinyuan Lin <xi...@uci.edu>
Co-authored-by: Copilot Autofix powered by AI <17...@users.noreply.github.com>
---
.../user/workflow/WorkflowExecutionsResource.scala | 6 +++-
.../workflow/WorkflowExecutionsResourceSpec.scala | 33 ++++++++++++++++++++++
2 files changed, 38 insertions(+), 1 deletion(-)
diff --git a/amber/src/main/scala/org/apache/texera/web/resource/dashboard/user/workflow/WorkflowExecutionsResource.scala b/amber/src/main/scala/org/apache/texera/web/resource/dashboard/user/workflow/WorkflowExecutionsResource.scala
index 8bd5eb504d..8787bf3798 100644
--- a/amber/src/main/scala/org/apache/texera/web/resource/dashboard/user/workflow/WorkflowExecutionsResource.scala
+++ b/amber/src/main/scala/org/apache/texera/web/resource/dashboard/user/workflow/WorkflowExecutionsResource.scala
@@ -704,10 +704,14 @@ class WorkflowExecutionsResource {
@GET
@Produces(Array(MediaType.APPLICATION_JSON))
@Path("/{wid}/stats/{eid}")
+ @RolesAllowed(Array("REGULAR", "ADMIN"))
def retrieveWorkflowRuntimeStatistics(
@PathParam("wid") wid: Integer,
- @PathParam("eid") eid: Integer
+ @PathParam("eid") eid: Integer,
+ @Auth sessionUser: SessionUser
): List[WorkflowRuntimeStatistics] = {
+ validateUserCanAccessWorkflow(sessionUser.getUser.getUid, wid)
+
// Create URI for runtime statistics
val uriString: String = context
.select(WORKFLOW_EXECUTIONS.RUNTIME_STATS_URI)
diff --git a/amber/src/test/scala/org/apache/texera/web/resource/dashboard/user/workflow/WorkflowExecutionsResourceSpec.scala b/amber/src/test/scala/org/apache/texera/web/resource/dashboard/user/workflow/WorkflowExecutionsResourceSpec.scala
index 75efa4da6a..ec35922f4e 100644
--- a/amber/src/test/scala/org/apache/texera/web/resource/dashboard/user/workflow/WorkflowExecutionsResourceSpec.scala
+++ b/amber/src/test/scala/org/apache/texera/web/resource/dashboard/user/workflow/WorkflowExecutionsResourceSpec.scala
@@ -872,5 +872,38 @@ class WorkflowExecutionsResourceSpec
val result = WorkflowExecutionsResource invokePrivate privateMethod(testWorkflowWid, testUser)
assert(result.isEmpty)
}
+ // ─── new: endpoint auth-annotation audit (#6977) ──────────────────────────
+
+ "WorkflowExecutionsResource endpoints" should "all declare @RolesAllowed and take an @Auth user" in {
+ val httpAnnotations: Seq[Class[_ <: java.lang.annotation.Annotation]] =
+ Seq(
+ classOf[javax.ws.rs.GET],
+ classOf[javax.ws.rs.PUT],
+ classOf[javax.ws.rs.POST],
+ classOf[javax.ws.rs.DELETE]
+ )
+ val handlers = classOf[WorkflowExecutionsResource].getDeclaredMethods.toSeq
+ .filter(m => httpAnnotations.exists(a => m.getAnnotation(a) != null))
+ assert(handlers.nonEmpty)
+
+ // exportResultToLocal authenticates manually: it serves a browser form-submit
+ // download, which cannot carry an Authorization header, so the JWT arrives as
+ // a form field and is verified in-method via JwtParser.parseToken (including
+ // the role check). Any other handler must use the declarative annotations.
+ val manuallyAuthenticated = Set("exportResultToLocal")
+
+ val offenders = handlers.filterNot(m => manuallyAuthenticated.contains(m.getName)).filter { m =>
+ val hasRoles =
+ m.getAnnotation(classOf[javax.annotation.security.RolesAllowed]) != null
+ val hasAuthParam = m.getParameterAnnotations.exists(
+ _.exists(_.annotationType() == classOf[io.dropwizard.auth.Auth])
+ )
+ !(hasRoles && hasAuthParam)
+ }
+ assert(
+ offenders.isEmpty,
+ s"endpoints missing @RolesAllowed/@Auth: ${offenders.map(_.getName).sorted.mkString(", ")}"
+ )
+ }
}