From 68fefca887ecaac965bce06cd8bbb6001d21ce4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Kleinb=C3=B6lting?= Date: Mon, 22 Apr 2024 10:27:34 +0200 Subject: [PATCH] feat: Add dsp-ingest as audience to user issued jwt (#3180) --- .../knora/sipi/SipiClientTestDelegator.scala | 2 +- .../test/scala/org/knora/sipi/SipiIT.scala | 4 +-- .../scala/org/knora/webapi/CoreSpec.scala | 2 +- .../org/knora/webapi/core/LayersTest.scala | 2 ++ .../org/knora/webapi/core/LayersLive.scala | 2 ++ .../knora/webapi/routing/Authenticator.scala | 1 + .../domain/service/DspIngestClient.scala | 2 +- .../slice/infrastructure/JwtService.scala | 8 +++--- .../store/iiif/impl/SipiServiceLive.scala | 4 +-- .../service/DspIngestClientLiveSpec.scala | 4 +-- .../infrastructure/JwtServiceLiveSpec.scala | 27 +++++++++++-------- 11 files changed, 33 insertions(+), 25 deletions(-) diff --git a/integration/src/test/scala/org/knora/sipi/SipiClientTestDelegator.scala b/integration/src/test/scala/org/knora/sipi/SipiClientTestDelegator.scala index 64f302b69b..32cd860eba 100644 --- a/integration/src/test/scala/org/knora/sipi/SipiClientTestDelegator.scala +++ b/integration/src/test/scala/org/knora/sipi/SipiClientTestDelegator.scala @@ -17,12 +17,12 @@ import org.knora.webapi.messages.store.sipimessages.MoveTemporaryFileToPermanent import org.knora.webapi.messages.store.sipimessages.SipiGetTextFileRequest import org.knora.webapi.messages.store.sipimessages.SipiGetTextFileResponse import org.knora.webapi.messages.v2.responder.SuccessResponseV2 -import org.knora.webapi.routing.JwtService import org.knora.webapi.slice.admin.api.model.MaintenanceRequests.AssetId import org.knora.webapi.slice.admin.domain.model.KnoraProject import org.knora.webapi.slice.admin.domain.model.User import org.knora.webapi.slice.admin.domain.service.Asset import org.knora.webapi.slice.admin.domain.service.DspIngestClient +import org.knora.webapi.slice.infrastructure.JwtService import org.knora.webapi.store.iiif.api.FileMetadataSipiResponse import org.knora.webapi.store.iiif.api.SipiService import org.knora.webapi.store.iiif.impl.SipiServiceLive diff --git a/integration/src/test/scala/org/knora/sipi/SipiIT.scala b/integration/src/test/scala/org/knora/sipi/SipiIT.scala index e0151414d6..5cc486593c 100644 --- a/integration/src/test/scala/org/knora/sipi/SipiIT.scala +++ b/integration/src/test/scala/org/knora/sipi/SipiIT.scala @@ -29,13 +29,13 @@ import org.knora.webapi.messages.admin.responder.projectsmessages.ProjectIdentif import org.knora.webapi.messages.admin.responder.projectsmessages.ProjectIdentifierADM.ShortcodeIdentifier import org.knora.webapi.messages.admin.responder.projectsmessages.ProjectIdentifierADM.ShortnameIdentifier import org.knora.webapi.messages.util.KnoraSystemInstances.Users.SystemUser -import org.knora.webapi.routing.JwtService -import org.knora.webapi.routing.JwtServiceLive import org.knora.webapi.slice.admin.domain.model.KnoraProject import org.knora.webapi.slice.admin.domain.model.KnoraProject.ProjectIri import org.knora.webapi.slice.admin.domain.service.KnoraProjectRepo import org.knora.webapi.slice.admin.domain.service.KnoraProjectService import org.knora.webapi.slice.common.repo.service.CrudRepository +import org.knora.webapi.slice.infrastructure.JwtService +import org.knora.webapi.slice.infrastructure.JwtServiceLive import org.knora.webapi.store.cache.CacheService import org.knora.webapi.testcontainers.SharedVolumes import org.knora.webapi.testcontainers.SipiTestContainer diff --git a/integration/src/test/scala/org/knora/webapi/CoreSpec.scala b/integration/src/test/scala/org/knora/webapi/CoreSpec.scala index 3cc7e3715e..21cddab470 100644 --- a/integration/src/test/scala/org/knora/webapi/CoreSpec.scala +++ b/integration/src/test/scala/org/knora/webapi/CoreSpec.scala @@ -25,9 +25,9 @@ import org.knora.webapi.core.AppServer import org.knora.webapi.core.LayersTest.DefaultTestEnvironmentWithoutSipi import org.knora.webapi.core.TestStartupUtils import org.knora.webapi.messages.store.triplestoremessages.RdfDataObject -import org.knora.webapi.routing.JwtService import org.knora.webapi.routing.UnsafeZioRun import org.knora.webapi.slice.admin.domain.model.User +import org.knora.webapi.slice.infrastructure.JwtService import org.knora.webapi.util.LogAspect abstract class CoreSpec diff --git a/integration/src/test/scala/org/knora/webapi/core/LayersTest.scala b/integration/src/test/scala/org/knora/webapi/core/LayersTest.scala index 7445f6f91c..0572525dce 100644 --- a/integration/src/test/scala/org/knora/webapi/core/LayersTest.scala +++ b/integration/src/test/scala/org/knora/webapi/core/LayersTest.scala @@ -42,6 +42,8 @@ import org.knora.webapi.slice.admin.domain.service.ProjectExportStorageService import org.knora.webapi.slice.admin.domain.service._ import org.knora.webapi.slice.common.api._ import org.knora.webapi.slice.common.repo.service.PredicateObjectMapper +import org.knora.webapi.slice.infrastructure.JwtService +import org.knora.webapi.slice.infrastructure.JwtServiceLive import org.knora.webapi.slice.infrastructure.api.ManagementEndpoints import org.knora.webapi.slice.infrastructure.api.ManagementRoutes import org.knora.webapi.slice.ontology.api.service.RestCardinalityService diff --git a/webapi/src/main/scala/org/knora/webapi/core/LayersLive.scala b/webapi/src/main/scala/org/knora/webapi/core/LayersLive.scala index 115fe244bc..647b0169d0 100644 --- a/webapi/src/main/scala/org/knora/webapi/core/LayersLive.scala +++ b/webapi/src/main/scala/org/knora/webapi/core/LayersLive.scala @@ -40,6 +40,8 @@ import org.knora.webapi.slice.admin.api.service.UserRestService import org.knora.webapi.slice.admin.domain.service._ import org.knora.webapi.slice.common.api._ import org.knora.webapi.slice.common.repo.service.PredicateObjectMapper +import org.knora.webapi.slice.infrastructure.JwtService +import org.knora.webapi.slice.infrastructure.JwtServiceLive import org.knora.webapi.slice.infrastructure.api.ManagementEndpoints import org.knora.webapi.slice.infrastructure.api.ManagementRoutes import org.knora.webapi.slice.ontology.api.service.RestCardinalityService diff --git a/webapi/src/main/scala/org/knora/webapi/routing/Authenticator.scala b/webapi/src/main/scala/org/knora/webapi/routing/Authenticator.scala index b7609c5260..e4a0b18cc0 100644 --- a/webapi/src/main/scala/org/knora/webapi/routing/Authenticator.scala +++ b/webapi/src/main/scala/org/knora/webapi/routing/Authenticator.scala @@ -37,6 +37,7 @@ import org.knora.webapi.slice.admin.domain.model.Username import org.knora.webapi.slice.admin.domain.service.KnoraUserRepo import org.knora.webapi.slice.admin.domain.service.PasswordService import org.knora.webapi.slice.admin.domain.service.UserService +import org.knora.webapi.slice.infrastructure.JwtService import org.knora.webapi.util.cache.CacheUtil /** diff --git a/webapi/src/main/scala/org/knora/webapi/slice/admin/domain/service/DspIngestClient.scala b/webapi/src/main/scala/org/knora/webapi/slice/admin/domain/service/DspIngestClient.scala index 64d988ef65..6be33f6845 100644 --- a/webapi/src/main/scala/org/knora/webapi/slice/admin/domain/service/DspIngestClient.scala +++ b/webapi/src/main/scala/org/knora/webapi/slice/admin/domain/service/DspIngestClient.scala @@ -33,9 +33,9 @@ import java.io.IOException import scala.concurrent.duration.DurationInt import org.knora.webapi.config.DspIngestConfig -import org.knora.webapi.routing.JwtService import org.knora.webapi.slice.admin.api.model.MaintenanceRequests.AssetId import org.knora.webapi.slice.admin.domain.model.KnoraProject.Shortcode +import org.knora.webapi.slice.infrastructure.JwtService trait DspIngestClient { def exportProject(shortcode: Shortcode): ZIO[Scope, Throwable, Path] diff --git a/webapi/src/main/scala/org/knora/webapi/slice/infrastructure/JwtService.scala b/webapi/src/main/scala/org/knora/webapi/slice/infrastructure/JwtService.scala index 7df9049147..72d65cb8ff 100644 --- a/webapi/src/main/scala/org/knora/webapi/slice/infrastructure/JwtService.scala +++ b/webapi/src/main/scala/org/knora/webapi/slice/infrastructure/JwtService.scala @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package org.knora.webapi.routing +package org.knora.webapi.slice.infrastructure import com.typesafe.scalalogging.Logger import org.slf4j.LoggerFactory @@ -90,13 +90,11 @@ final case class JwtServiceLive( private val algorithm: JwtAlgorithm = JwtAlgorithm.HS256 private val header: String = """{"typ":"JWT","alg":"HS256"}""" private val logger = Logger(LoggerFactory.getLogger(this.getClass)) + private val audience = Set("Knora", "Sipi", dspIngestConfig.audience) - override def createJwt(user: User, content: Map[String, JsValue] = Map.empty): UIO[Jwt] = { - val audience = if (user.isSystemAdmin) { Set("Knora", "Sipi", dspIngestConfig.audience) } - else { Set("Knora", "Sipi") } + override def createJwt(user: User, content: Map[String, JsValue] = Map.empty): UIO[Jwt] = calculateScope(user) .flatMap(scope => createJwtToken(jwtConfig.issuerAsString(), user.id, audience, scope, Some(JsObject(content)))) - } private def calculateScope(user: User) = if (user.isSystemAdmin || user.isSystemUser) { ZIO.succeed(Scope.admin) } diff --git a/webapi/src/main/scala/org/knora/webapi/store/iiif/impl/SipiServiceLive.scala b/webapi/src/main/scala/org/knora/webapi/store/iiif/impl/SipiServiceLive.scala index 5fd8a45b64..158908286a 100644 --- a/webapi/src/main/scala/org/knora/webapi/store/iiif/impl/SipiServiceLive.scala +++ b/webapi/src/main/scala/org/knora/webapi/store/iiif/impl/SipiServiceLive.scala @@ -37,13 +37,13 @@ import org.knora.webapi.config.Sipi import org.knora.webapi.messages.store.sipimessages._ import org.knora.webapi.messages.util.KnoraSystemInstances import org.knora.webapi.messages.v2.responder.SuccessResponseV2 -import org.knora.webapi.routing.Jwt -import org.knora.webapi.routing.JwtService import org.knora.webapi.slice.admin.api.model.MaintenanceRequests.AssetId import org.knora.webapi.slice.admin.domain.model.KnoraProject.Shortcode import org.knora.webapi.slice.admin.domain.model.User import org.knora.webapi.slice.admin.domain.service.Asset import org.knora.webapi.slice.admin.domain.service.DspIngestClient +import org.knora.webapi.slice.infrastructure.Jwt +import org.knora.webapi.slice.infrastructure.JwtService import org.knora.webapi.store.iiif.api.FileMetadataSipiResponse import org.knora.webapi.store.iiif.api.SipiService import org.knora.webapi.store.iiif.errors.SipiException diff --git a/webapi/src/test/scala/org/knora/webapi/slice/admin/domain/service/DspIngestClientLiveSpec.scala b/webapi/src/test/scala/org/knora/webapi/slice/admin/domain/service/DspIngestClientLiveSpec.scala index 97467116a3..ecb4d268a4 100644 --- a/webapi/src/test/scala/org/knora/webapi/slice/admin/domain/service/DspIngestClientLiveSpec.scala +++ b/webapi/src/test/scala/org/knora/webapi/slice/admin/domain/service/DspIngestClientLiveSpec.scala @@ -42,14 +42,14 @@ import zio.test.assertTrue import org.knora.webapi.IRI import org.knora.webapi.config.DspIngestConfig -import org.knora.webapi.routing.Jwt -import org.knora.webapi.routing.JwtService import org.knora.webapi.slice.admin.api.model.MaintenanceRequests.AssetId import org.knora.webapi.slice.admin.domain.model.KnoraProject.Shortcode import org.knora.webapi.slice.admin.domain.model.User import org.knora.webapi.slice.admin.domain.service.DspIngestClientLiveSpecLayers.dspIngestConfigLayer import org.knora.webapi.slice.admin.domain.service.DspIngestClientLiveSpecLayers.jwtServiceMockLayer import org.knora.webapi.slice.admin.domain.service.HttpMockServer.TestPort +import org.knora.webapi.slice.infrastructure.Jwt +import org.knora.webapi.slice.infrastructure.JwtService object DspIngestClientLiveSpec extends ZIOSpecDefault { diff --git a/webapi/src/test/scala/org/knora/webapi/slice/infrastructure/JwtServiceLiveSpec.scala b/webapi/src/test/scala/org/knora/webapi/slice/infrastructure/JwtServiceLiveSpec.scala index a44227e5e7..4d07a333e4 100644 --- a/webapi/src/test/scala/org/knora/webapi/slice/infrastructure/JwtServiceLiveSpec.scala +++ b/webapi/src/test/scala/org/knora/webapi/slice/infrastructure/JwtServiceLiveSpec.scala @@ -38,8 +38,6 @@ import org.knora.webapi.messages.admin.responder.permissionsmessages.PermissionA import org.knora.webapi.messages.admin.responder.permissionsmessages.PermissionsDataADM import org.knora.webapi.messages.store.triplestoremessages.StringLiteralV2 import org.knora.webapi.routing.Authenticator.AUTHENTICATION_INVALIDATION_CACHE_NAME -import org.knora.webapi.routing.JwtService -import org.knora.webapi.routing.JwtServiceLive import org.knora.webapi.slice.admin.domain.model.KnoraProject import org.knora.webapi.slice.admin.domain.model.KnoraProject.Description import org.knora.webapi.slice.admin.domain.model.KnoraProject.ProjectIri @@ -55,6 +53,8 @@ import org.knora.webapi.slice.admin.domain.repo.KnoraProjectRepoInMemory import org.knora.webapi.slice.admin.domain.service.KnoraGroupRepo import org.knora.webapi.slice.admin.domain.service.KnoraProjectRepo import org.knora.webapi.slice.admin.domain.service.KnoraProjectService +import org.knora.webapi.slice.infrastructure.JwtService +import org.knora.webapi.slice.infrastructure.JwtServiceLive import org.knora.webapi.store.cache.CacheService final case class ScopeJs(scope: String) @@ -106,13 +106,18 @@ object JwtServiceLiveSpec extends ZIOSpecDefault { Map(project1.id.value -> Set(PermissionADM.from(Administrative.ProjectAdminAll))), ) - private val issuerStr = "https://dsp-api" + private val dspIngestAudience = "https://dsp-ingest/audience" + private val dspApiIssuer = "https://dsp-api" private val dspIngestConfigLayer = - ZLayer.succeed(DspIngestConfig("https://dps-ingest", "https://dsp-ingest/audience")) + ZLayer.succeed(DspIngestConfig("https://dps-ingest", dspIngestAudience)) - private val jwtConfigLayer = - ZLayer.succeed(JwtConfig("n76lPIwWKNeTodFfZPPPYFn7V24R14aE63A+XgS8MMA=", Duration.ofSeconds(10), Some(issuerStr))) + private val jwtConfigLayer = ZLayer.succeed( + JwtConfig("n76lPIwWKNeTodFfZPPPYFn7V24R14aE63A+XgS8MMA=", Duration.ofSeconds(10), Some(dspApiIssuer)), + ) + + private val expectedAudience: Set[String] = + Set("Knora", "Sipi", dspIngestAudience) private def decodeToken(token: String): ZIO[JwtConfig, Nothing, (JwtHeader, JwtClaim, String)] = ZIO.serviceWithZIO[JwtConfig] { jwtConfig => @@ -143,7 +148,7 @@ object JwtServiceLiveSpec extends ZIOSpecDefault { scope <- getScopeClaimValue(token.jwtString) } yield assertTrue( userIri.contains(user.id), - audience == Set("Knora", "Sipi"), + audience == expectedAudience, scope == "", ) }, @@ -185,8 +190,8 @@ object JwtServiceLiveSpec extends ZIOSpecDefault { audience <- getClaim(token.jwtString, _.audience.getOrElse(Set.empty)) scope <- getScopeClaimValue(token.jwtString) } yield assertTrue( - userIri.contains(issuerStr), - audience.contains("https://dsp-ingest/audience"), + userIri.contains(dspApiIssuer), + audience == Set(dspIngestAudience), scope == "admin", ) }, @@ -198,9 +203,9 @@ object JwtServiceLiveSpec extends ZIOSpecDefault { }, test("fail to validate an invalid token") { def createClaim( - issuer: Option[String] = Some(issuerStr), + issuer: Option[String] = Some(dspApiIssuer), subject: Option[String] = Some(UserIri.makeNew.value), - audience: Option[Set[String]] = Some(Set("Knora", "Sipi")), + audience: Option[Set[String]] = Some(expectedAudience), issuedAt: Option[Long] = Some(Instant.now.getEpochSecond), expiration: Option[Long] = Some(Instant.now.plusSeconds(10).getEpochSecond), jwtId: Option[String] = Some(UuidUtil.base64Encode(UUID.randomUUID())),