From 148873d9f029e34ffd2ce312c7f6dab711579d6a Mon Sep 17 00:00:00 2001 From: Yicong Huang <17627829+Yicong-Huang@users.noreply.github.com> Date: Sun, 24 May 2026 17:14:10 -0700 Subject: [PATCH] fix: enforce @RolesAllowed on microservice resources Re-applies #5049 (revert in #5173) and additionally marks the two pre-login ConfigResource endpoints (/config/gui, /config/user-system) as @PermitAll so the frontend's APP_INITIALIZER can still bootstrap once role enforcement is on. Adds an HTTP-pipeline regression test that fires unauthenticated requests through JwtAuthFilter + RolesAllowedDynamicFeature and asserts the two pre-login endpoints return 200 while a sibling @RolesAllowed probe returns 403. Co-Authored-By: Claude Opus 4.7 (1M context) --- build.sbt | 2 +- computing-unit-managing-service/build.sbt | 7 ++ .../ComputingUnitManagingService.scala | 29 ++++-- .../ComputingUnitManagingServiceRunSpec.scala | 48 ++++++++++ .../apache/texera/service/ConfigService.scala | 4 + .../service/resource/ConfigResource.scala | 13 ++- .../texera/service/ConfigServiceRunSpec.scala | 55 +++++++++++ .../resource/ConfigResourceAuthSpec.scala | 93 +++++++++++++++++++ workflow-compiling-service/LICENSE-binary | 3 + workflow-compiling-service/build.sbt | 1 + .../service/WorkflowCompilingService.scala | 23 ++++- .../WorkflowCompilingServiceRunSpec.scala | 48 ++++++++++ 12 files changed, 310 insertions(+), 16 deletions(-) create mode 100644 computing-unit-managing-service/src/test/scala/org/apache/texera/service/ComputingUnitManagingServiceRunSpec.scala create mode 100644 config-service/src/test/scala/org/apache/texera/service/ConfigServiceRunSpec.scala create mode 100644 config-service/src/test/scala/org/apache/texera/service/resource/ConfigResourceAuthSpec.scala create mode 100644 workflow-compiling-service/src/test/scala/org/apache/texera/service/WorkflowCompilingServiceRunSpec.scala diff --git a/build.sbt b/build.sbt index b7b6b3cfb20..8b38e2e009f 100644 --- a/build.sbt +++ b/build.sbt @@ -113,7 +113,7 @@ lazy val FileService = (project in file("file-service")) lazy val WorkflowOperator = (project in file("common/workflow-operator")).settings(asfLicensingSettingsWithVendored).dependsOn(WorkflowCore) lazy val WorkflowCompilingService = (project in file("workflow-compiling-service")) - .dependsOn(WorkflowOperator, Config) + .dependsOn(WorkflowOperator, Auth, Config) .settings(asfLicensingSettings) .settings( dependencyOverrides ++= Seq( diff --git a/computing-unit-managing-service/build.sbt b/computing-unit-managing-service/build.sbt index 3d385d33d30..1c39a6b03d9 100644 --- a/computing-unit-managing-service/build.sbt +++ b/computing-unit-managing-service/build.sbt @@ -34,6 +34,13 @@ Universal / mappings := AddMetaInfLicenseFiles.distMappings( // Dependency Versions val dropwizardVersion = "4.0.7" +val mockitoVersion = "5.4.0" + +// Test Dependencies +libraryDependencies ++= Seq( + "org.scalatest" %% "scalatest" % "3.2.17" % Test, + "org.mockito" % "mockito-core" % mockitoVersion % Test +) // Dependencies libraryDependencies ++= Seq( diff --git a/computing-unit-managing-service/src/main/scala/org/apache/texera/service/ComputingUnitManagingService.scala b/computing-unit-managing-service/src/main/scala/org/apache/texera/service/ComputingUnitManagingService.scala index a15ced30a29..6184cf545a2 100644 --- a/computing-unit-managing-service/src/main/scala/org/apache/texera/service/ComputingUnitManagingService.scala +++ b/computing-unit-managing-service/src/main/scala/org/apache/texera/service/ComputingUnitManagingService.scala @@ -32,6 +32,7 @@ import org.apache.texera.service.resource.{ ComputingUnitManagingResource, HealthCheckResource } +import org.glassfish.jersey.server.filter.RolesAllowedDynamicFeature import java.nio.file.Path class ComputingUnitManagingService extends Application[ComputingUnitManagingServiceConfiguration] { @@ -53,21 +54,16 @@ class ComputingUnitManagingService extends Application[ComputingUnitManagingServ configuration: ComputingUnitManagingServiceConfiguration, environment: Environment ): Unit = { - SqlServer.initConnection( - StorageConfig.jdbcUrl, - StorageConfig.jdbcUsername, - StorageConfig.jdbcPassword - ) // Register http resources environment.jersey.setUrlPattern("/api/*") environment.jersey.register(classOf[HealthCheckResource]) - // Register JWT authentication filter - environment.jersey.register(new AuthDynamicFeature(classOf[JwtAuthFilter])) + ComputingUnitManagingService.registerAuthFeatures(environment) - // Enable @Auth annotation for injecting SessionUser - environment.jersey.register( - new io.dropwizard.auth.AuthValueFactoryProvider.Binder(classOf[SessionUser]) + SqlServer.initConnection( + StorageConfig.jdbcUrl, + StorageConfig.jdbcUsername, + StorageConfig.jdbcPassword ) environment.jersey().register(new ComputingUnitManagingResource) @@ -79,6 +75,19 @@ class ComputingUnitManagingService extends Application[ComputingUnitManagingServ } object ComputingUnitManagingService { + // Registers JWT auth, @Auth injection, and @RolesAllowed enforcement. + def registerAuthFeatures(environment: Environment): Unit = { + // Register JWT authentication filter + environment.jersey.register(new AuthDynamicFeature(classOf[JwtAuthFilter])) + + // Enable @Auth annotation for injecting SessionUser + environment.jersey.register( + new io.dropwizard.auth.AuthValueFactoryProvider.Binder(classOf[SessionUser]) + ) + + // Enforce @RolesAllowed annotations on resource methods + environment.jersey.register(classOf[RolesAllowedDynamicFeature]) + } def main(args: Array[String]): Unit = { val configFilePath = Path diff --git a/computing-unit-managing-service/src/test/scala/org/apache/texera/service/ComputingUnitManagingServiceRunSpec.scala b/computing-unit-managing-service/src/test/scala/org/apache/texera/service/ComputingUnitManagingServiceRunSpec.scala new file mode 100644 index 00000000000..d27f5725ac9 --- /dev/null +++ b/computing-unit-managing-service/src/test/scala/org/apache/texera/service/ComputingUnitManagingServiceRunSpec.scala @@ -0,0 +1,48 @@ +/* + * 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.service + +import io.dropwizard.auth.{AuthDynamicFeature, AuthValueFactoryProvider} +import io.dropwizard.core.setup.Environment +import io.dropwizard.jersey.setup.JerseyEnvironment +import org.glassfish.jersey.server.filter.RolesAllowedDynamicFeature +import org.mockito.Mockito.{mock, verify, when} +import org.scalatest.flatspec.AnyFlatSpec +import org.scalatest.matchers.should.Matchers + +class ComputingUnitManagingServiceRunSpec extends AnyFlatSpec with Matchers { + + // Verifies that the @RolesAllowed annotations on resource methods are actually + // enforced by Jersey, which requires RolesAllowedDynamicFeature, AuthDynamicFeature, + // and AuthValueFactoryProvider.Binder to be registered on the Jersey environment. + "ComputingUnitManagingService.registerAuthFeatures" should "register auth + RolesAllowedDynamicFeature on the Jersey environment" in { + val jersey = mock(classOf[JerseyEnvironment]) + val env = mock(classOf[Environment]) + when(env.jersey).thenReturn(jersey) + + ComputingUnitManagingService.registerAuthFeatures(env) + + verify(jersey).register(classOf[RolesAllowedDynamicFeature]) + verify(jersey).register(org.mockito.ArgumentMatchers.any(classOf[AuthDynamicFeature])) + verify(jersey).register( + org.mockito.ArgumentMatchers.any(classOf[AuthValueFactoryProvider.Binder[_]]) + ) + } +} diff --git a/config-service/src/main/scala/org/apache/texera/service/ConfigService.scala b/config-service/src/main/scala/org/apache/texera/service/ConfigService.scala index c787016c270..545aac494b3 100644 --- a/config-service/src/main/scala/org/apache/texera/service/ConfigService.scala +++ b/config-service/src/main/scala/org/apache/texera/service/ConfigService.scala @@ -31,6 +31,7 @@ import org.apache.texera.config.DefaultsConfig import org.apache.texera.dao.SqlServer import org.apache.texera.service.resource.{ConfigResource, HealthCheckResource} import org.eclipse.jetty.server.session.SessionHandler +import org.glassfish.jersey.server.filter.RolesAllowedDynamicFeature import org.jooq.impl.DSL import java.nio.file.Path @@ -71,6 +72,9 @@ class ConfigService extends Application[ConfigServiceConfiguration] with LazyLog new io.dropwizard.auth.AuthValueFactoryProvider.Binder(classOf[SessionUser]) ) + // Enforce @RolesAllowed annotations on resource methods + environment.jersey.register(classOf[RolesAllowedDynamicFeature]) + environment.jersey.register(new ConfigResource) // Preload default.conf into site_setting tables diff --git a/config-service/src/main/scala/org/apache/texera/service/resource/ConfigResource.scala b/config-service/src/main/scala/org/apache/texera/service/resource/ConfigResource.scala index 2614719040c..80ee63b862c 100644 --- a/config-service/src/main/scala/org/apache/texera/service/resource/ConfigResource.scala +++ b/config-service/src/main/scala/org/apache/texera/service/resource/ConfigResource.scala @@ -19,7 +19,7 @@ package org.apache.texera.service.resource -import jakarta.annotation.security.RolesAllowed +import jakarta.annotation.security.PermitAll import jakarta.ws.rs.core.MediaType import jakarta.ws.rs.{GET, Path, Produces} import org.apache.texera.config.{AuthConfig, ComputingUnitConfig, GuiConfig, UserSystemConfig} @@ -28,8 +28,15 @@ import org.apache.texera.config.{AuthConfig, ComputingUnitConfig, GuiConfig, Use @Produces(Array(MediaType.APPLICATION_JSON)) class ConfigResource { + // The frontend loads /config/gui and /config/user-system as an APP_INITIALIZER + // (GuiConfigService.load() in gui-config.service.ts) — i.e. before any login. They + // must answer unauthenticated callers so the login page can render. PR #5049 left + // @RolesAllowed on both endpoints, which once RolesAllowedDynamicFeature was + // registered started returning 403 during bootstrap and broke the whole app; that + // PR was reverted in #5173. @PermitAll keeps enforcement on for the rest of the + // service while explicitly whitelisting these two pre-login endpoints. @GET - @RolesAllowed(Array("REGULAR", "ADMIN")) + @PermitAll @Path("/gui") def getGuiConfig: Map[String, Any] = Map( @@ -64,7 +71,7 @@ class ConfigResource { ) @GET - @RolesAllowed(Array("REGULAR", "ADMIN")) + @PermitAll @Path("/user-system") def getUserSystemConfig: Map[String, Any] = Map( diff --git a/config-service/src/test/scala/org/apache/texera/service/ConfigServiceRunSpec.scala b/config-service/src/test/scala/org/apache/texera/service/ConfigServiceRunSpec.scala new file mode 100644 index 00000000000..e3982e37750 --- /dev/null +++ b/config-service/src/test/scala/org/apache/texera/service/ConfigServiceRunSpec.scala @@ -0,0 +1,55 @@ +/* + * 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.service + +import io.dropwizard.core.setup.Environment +import io.dropwizard.jersey.setup.JerseyEnvironment +import io.dropwizard.jetty.MutableServletContextHandler +import io.dropwizard.jetty.setup.ServletEnvironment +import org.glassfish.jersey.server.filter.RolesAllowedDynamicFeature +import org.mockito.Mockito.{mock, verify, when} +import org.scalatest.flatspec.AnyFlatSpec +import org.scalatest.matchers.should.Matchers + +class ConfigServiceRunSpec extends AnyFlatSpec with Matchers { + + // Verifies that the @RolesAllowed annotations on ConfigResource are actually + // enforced by Jersey, which requires RolesAllowedDynamicFeature to be + // registered on the Jersey environment. + "ConfigService.run" should "register RolesAllowedDynamicFeature on the Jersey environment" in { + val jersey = mock(classOf[JerseyEnvironment]) + val servlets = mock(classOf[ServletEnvironment]) + val context = mock(classOf[MutableServletContextHandler]) + val env = mock(classOf[Environment]) + when(env.jersey).thenReturn(jersey) + when(env.servlets).thenReturn(servlets) + when(env.getApplicationContext).thenReturn(context) + + val service = new ConfigService + // run() reaches into SqlServer near the end to preload defaults; that throws + // here because no real DB is wired up. By that point all Jersey registrations + // have already executed, so the verification below is still valid. + intercept[Exception] { + service.run(mock(classOf[ConfigServiceConfiguration]), env) + } + + verify(jersey).register(classOf[RolesAllowedDynamicFeature]) + } +} diff --git a/config-service/src/test/scala/org/apache/texera/service/resource/ConfigResourceAuthSpec.scala b/config-service/src/test/scala/org/apache/texera/service/resource/ConfigResourceAuthSpec.scala new file mode 100644 index 00000000000..3d8aea6e8b9 --- /dev/null +++ b/config-service/src/test/scala/org/apache/texera/service/resource/ConfigResourceAuthSpec.scala @@ -0,0 +1,93 @@ +/* + * 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.service.resource + +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.module.scala.DefaultScalaModule +import io.dropwizard.jackson.Jackson +import io.dropwizard.testing.junit5.ResourceExtension +import jakarta.annotation.security.RolesAllowed +import jakarta.ws.rs.core.MediaType +import jakarta.ws.rs.{GET, Path, Produces} +import org.apache.texera.auth.JwtAuthFilter +import org.glassfish.jersey.server.filter.RolesAllowedDynamicFeature +import org.scalatest.BeforeAndAfterAll +import org.scalatest.flatspec.AnyFlatSpec +import org.scalatest.matchers.should.Matchers + +// Wires ConfigResource through the same Jersey auth pipeline production uses +// (JwtAuthFilter + RolesAllowedDynamicFeature) and fires HTTP requests with no +// Authorization header. Regression guard for the bootstrap break that caused +// PR #5049 to be reverted in #5173: /config/gui and /config/user-system are +// loaded by the frontend's APP_INITIALIZER before any login, so they must +// return 200 to unauthenticated callers even with role enforcement enabled. +class ConfigResourceAuthSpec extends AnyFlatSpec with Matchers with BeforeAndAfterAll { + + // Companion to ConfigResource that's deliberately @RolesAllowed, so the same + // setup also proves the feature actually rejects when it should — a 200 on + // the @PermitAll endpoints would otherwise be consistent with the feature + // being silently no-op'd. + // Mirror production's mapper: ConfigService bootstraps Dropwizard's default mapper + // (Jackson.newObjectMapper) and registers DefaultScalaModule on top. Same call here. + private val testMapper: ObjectMapper = + Jackson.newObjectMapper().registerModule(DefaultScalaModule) + + private val resources: ResourceExtension = ResourceExtension + .builder() + .setMapper(testMapper) + .addProvider(classOf[JwtAuthFilter]) + .addProvider(classOf[RolesAllowedDynamicFeature]) + .addResource(new ConfigResource) + .addResource(new ConfigResourceAuthSpec.ProtectedProbe) + .build() + + override protected def beforeAll(): Unit = resources.before() + override protected def afterAll(): Unit = resources.after() + + "GET /config/gui" should "return 200 without an Authorization header" in { + val response = resources.target("/config/gui").request(MediaType.APPLICATION_JSON).get() + response.getStatus shouldBe 200 + } + + "GET /config/user-system" should "return 200 without an Authorization header" in { + val response = + resources.target("/config/user-system").request(MediaType.APPLICATION_JSON).get() + response.getStatus shouldBe 200 + } + + "GET an @RolesAllowed endpoint" should "return 403 without an Authorization header" in { + // Sanity: with no SecurityContext set by JwtAuthFilter, RolesAllowedDynamicFeature + // must reject. Catches the case where the feature is registered but somehow + // disabled (e.g. swallowed exception during setup). + val response = + resources.target("/auth-probe").request(MediaType.APPLICATION_JSON).get() + response.getStatus shouldBe 403 + } +} + +object ConfigResourceAuthSpec { + @Path("/auth-probe") + @Produces(Array(MediaType.APPLICATION_JSON)) + class ProtectedProbe { + @GET + @RolesAllowed(Array("REGULAR", "ADMIN")) + def probe: String = "should never reach this" + } +} diff --git a/workflow-compiling-service/LICENSE-binary b/workflow-compiling-service/LICENSE-binary index 5b7548a4edc..ed6a9e1d266 100644 --- a/workflow-compiling-service/LICENSE-binary +++ b/workflow-compiling-service/LICENSE-binary @@ -281,6 +281,7 @@ Scala/Java jars: - commons-pool.commons-pool-1.6.jar - dev.failsafe.failsafe-3.3.2.jar - io.airlift.aircompressor-0.27.jar + - io.dropwizard.dropwizard-auth-4.0.7.jar - io.dropwizard.dropwizard-configuration-4.0.7.jar - io.dropwizard.dropwizard-core-4.0.7.jar - io.dropwizard.dropwizard-health-4.0.7.jar @@ -296,6 +297,7 @@ Scala/Java jars: - io.dropwizard.dropwizard-validation-4.0.7.jar - io.dropwizard.logback.logback-throttling-appender-1.4.2.jar - io.dropwizard.metrics.metrics-annotation-4.2.25.jar + - io.dropwizard.metrics.metrics-caffeine-4.2.25.jar - io.dropwizard.metrics.metrics-core-4.2.25.jar - io.dropwizard.metrics.metrics-healthchecks-4.2.25.jar - io.dropwizard.metrics.metrics-jakarta-servlets-4.2.25.jar @@ -419,6 +421,7 @@ Scala/Java jars: - org.apache.yetus.audience-annotations-0.13.0.jar - org.apache.zookeeper.zookeeper-3.5.6.jar - org.apache.zookeeper.zookeeper-jute-3.5.6.jar + - org.bitbucket.b_c.jose4j-0.9.6.jar - org.eclipse.jetty.jetty-http-11.0.20.jar - org.eclipse.jetty.jetty-io-11.0.20.jar - org.eclipse.jetty.jetty-security-11.0.20.jar diff --git a/workflow-compiling-service/build.sbt b/workflow-compiling-service/build.sbt index 9560751d00b..95a69269927 100644 --- a/workflow-compiling-service/build.sbt +++ b/workflow-compiling-service/build.sbt @@ -84,5 +84,6 @@ libraryDependencies ++= Seq( // Core Dependencies libraryDependencies ++= Seq( "io.dropwizard" % "dropwizard-core" % dropwizardVersion, + "io.dropwizard" % "dropwizard-auth" % dropwizardVersion, // Dropwizard Authentication module "com.fasterxml.jackson.module" %% "jackson-module-scala" % "2.18.6" ) diff --git a/workflow-compiling-service/src/main/scala/org/apache/texera/service/WorkflowCompilingService.scala b/workflow-compiling-service/src/main/scala/org/apache/texera/service/WorkflowCompilingService.scala index 40fb3a2dd8f..8dc573aaf8b 100644 --- a/workflow-compiling-service/src/main/scala/org/apache/texera/service/WorkflowCompilingService.scala +++ b/workflow-compiling-service/src/main/scala/org/apache/texera/service/WorkflowCompilingService.scala @@ -20,14 +20,17 @@ package org.apache.texera.service import com.fasterxml.jackson.module.scala.DefaultScalaModule +import io.dropwizard.auth.AuthDynamicFeature import io.dropwizard.configuration.{EnvironmentVariableSubstitutor, SubstitutingSourceProvider} import io.dropwizard.core.Application import io.dropwizard.core.setup.{Bootstrap, Environment} import org.apache.texera.amber.config.StorageConfig import org.apache.texera.amber.util.ObjectMapperUtils +import org.apache.texera.auth.{JwtAuthFilter, SessionUser} import org.apache.texera.dao.SqlServer import org.apache.texera.service.resource.{HealthCheckResource, WorkflowCompilationResource} import org.eclipse.jetty.servlet.FilterHolder +import org.glassfish.jersey.server.filter.RolesAllowedDynamicFeature import java.nio.file.Path @@ -53,14 +56,16 @@ class WorkflowCompilingService extends Application[WorkflowCompilingServiceConfi // serve backend at /api environment.jersey.setUrlPattern("/api/*") + environment.jersey.register(classOf[HealthCheckResource]) + + WorkflowCompilingService.registerAuthFeatures(environment) + SqlServer.initConnection( StorageConfig.jdbcUrl, StorageConfig.jdbcUsername, StorageConfig.jdbcPassword ) - environment.jersey.register(classOf[HealthCheckResource]) - // register the compilation endpoint environment.jersey.register(classOf[WorkflowCompilationResource]) @@ -90,6 +95,20 @@ class WorkflowCompilingService extends Application[WorkflowCompilingServiceConfi } object WorkflowCompilingService { + // Registers JWT auth, @Auth injection, and @RolesAllowed enforcement. + def registerAuthFeatures(environment: Environment): Unit = { + // Register JWT authentication filter + environment.jersey.register(new AuthDynamicFeature(classOf[JwtAuthFilter])) + + // Enable @Auth annotation for injecting SessionUser + environment.jersey.register( + new io.dropwizard.auth.AuthValueFactoryProvider.Binder(classOf[SessionUser]) + ) + + // Enforce @RolesAllowed annotations on resource methods + environment.jersey.register(classOf[RolesAllowedDynamicFeature]) + } + def main(args: Array[String]): Unit = { // set the configuration file's path val configFilePath = Path diff --git a/workflow-compiling-service/src/test/scala/org/apache/texera/service/WorkflowCompilingServiceRunSpec.scala b/workflow-compiling-service/src/test/scala/org/apache/texera/service/WorkflowCompilingServiceRunSpec.scala new file mode 100644 index 00000000000..ff5da1b5613 --- /dev/null +++ b/workflow-compiling-service/src/test/scala/org/apache/texera/service/WorkflowCompilingServiceRunSpec.scala @@ -0,0 +1,48 @@ +/* + * 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.service + +import io.dropwizard.auth.{AuthDynamicFeature, AuthValueFactoryProvider} +import io.dropwizard.core.setup.Environment +import io.dropwizard.jersey.setup.JerseyEnvironment +import org.glassfish.jersey.server.filter.RolesAllowedDynamicFeature +import org.mockito.Mockito.{mock, verify, when} +import org.scalatest.flatspec.AnyFlatSpec +import org.scalatest.matchers.should.Matchers + +class WorkflowCompilingServiceRunSpec extends AnyFlatSpec with Matchers { + + // Verifies that the @RolesAllowed annotations on resource methods are actually + // enforced by Jersey, which requires RolesAllowedDynamicFeature, AuthDynamicFeature, + // and AuthValueFactoryProvider.Binder to be registered on the Jersey environment. + "WorkflowCompilingService.registerAuthFeatures" should "register auth + RolesAllowedDynamicFeature on the Jersey environment" in { + val jersey = mock(classOf[JerseyEnvironment]) + val env = mock(classOf[Environment]) + when(env.jersey).thenReturn(jersey) + + WorkflowCompilingService.registerAuthFeatures(env) + + verify(jersey).register(classOf[RolesAllowedDynamicFeature]) + verify(jersey).register(org.mockito.ArgumentMatchers.any(classOf[AuthDynamicFeature])) + verify(jersey).register( + org.mockito.ArgumentMatchers.any(classOf[AuthValueFactoryProvider.Binder[_]]) + ) + } +}