diff --git a/sipi/scripts/authentication.lua b/sipi/scripts/authentication.lua index a1b363bc84..7068eff15b 100644 --- a/sipi/scripts/authentication.lua +++ b/sipi/scripts/authentication.lua @@ -167,11 +167,22 @@ end -- @return jwt token or nil if the cookie is missing or invalid. function _get_jwt_token_from_cookie() log("authentication: checking for jwt token in cookie header", server.loglevel.LOG_DEBUG) - local cookie_header_value = _get_cookie_header() - if cookie_header_value == nil then + local cookie_name = env_knora_authentication_cookie_name() + local cookies = _get_cookie_header() + if cookies == nil then + log("authentication: no cookie header found", server.loglevel.LOG_DEBUG) return nil end - local cookie_name = env_knora_authentication_cookie_name() - local jwt_token = str_strip_prefix(cookie_header_value, cookie_name .. "=") - return jwt_token + + cookie_name = cookie_name:lower() + for entry in cookies:gmatch("([^,]+)") do + local key, value = entry:match("([^=]+)=(.+)") + if key and value then + if key:lower() == cookie_name then + return value + end + end + end + log("authentication: cookie header does not contain " .. cookie_name, server.loglevel.LOG_DEBUG) + return nil end diff --git a/sipi/scripts/env.lua b/sipi/scripts/env.lua index bb15a38f70..142df70f81 100644 --- a/sipi/scripts/env.lua +++ b/sipi/scripts/env.lua @@ -34,5 +34,5 @@ end -- Returns the name of the cookie used for authentication. function env_knora_authentication_cookie_name() local host_port_base32 = basexx.to_base32Custom(env_dsp_api_host_port()) - return "KnoraAuthentication" .. host_port_base32 + return string.lower("KnoraAuthentication" .. host_port_base32) end diff --git a/sipi/scripts/strings.lua b/sipi/scripts/strings.lua index a6eb4c6e31..3d54337ef0 100644 --- a/sipi/scripts/strings.lua +++ b/sipi/scripts/strings.lua @@ -3,6 +3,26 @@ --- Utility functions for working with strings. +-- Break a string up at occurrences of the first single character. +-- In Lua, there is no built-in function for splitting a string at a specific character. +-- http://lua-users.org/wiki/SplitJoin +-- @param str the string to split. +-- @param separator the separator character. +-- @return a table containing: +--- * the split string or +--- * the original string if the separator was not found. +function str_splitString(str, separator) + local result = {} + local pattern = string.format("([^%s]+)", separator) + + for match in str:gmatch(pattern) do + table.insert(result, match) + end + + return result +end + + --- Checks if a string starts with a specific prefix. -- In Lua, there is no built-in function for checking if a string starts with a specific prefix. -- This function implements this functionality. @@ -33,18 +53,18 @@ end -- @param tbl the table to transform. -- @return a string representation of the table. function tableToString(tbl) - local str = "{" + local str = "{\n" local isFirst = true for key, value in pairs(tbl) do if not isFirst then - str = str .. ", " + str = str .. "\n" end if type(value) == "table" then - str = str .. key .. "=" .. tableToString(value) + str = str .. key .. " = " .. tableToString(value) .. "\n" else - str = str .. key .. "=" .. tostring(value) + str = str .. key .. " = " .. tostring(value) .. "\n" end isFirst = false diff --git a/webapi/src/it/scala/org/knora/sipi/SipiIT.scala b/webapi/src/it/scala/org/knora/sipi/SipiIT.scala index c97f3bd291..8634361b06 100644 --- a/webapi/src/it/scala/org/knora/sipi/SipiIT.scala +++ b/webapi/src/it/scala/org/knora/sipi/SipiIT.scala @@ -14,6 +14,8 @@ import com.github.tomakehurst.wiremock.client.WireMock.getRequestedFor import com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo import com.github.tomakehurst.wiremock.core.WireMockConfiguration.options import com.github.tomakehurst.wiremock.matching.RequestPatternBuilder +import org.apache.commons.codec.binary.Base32 +import spray.json.JsString import zio._ import zio.http._ import zio.http.model.Status @@ -26,9 +28,17 @@ import scala.util.Success import scala.util.Try import org.knora.sipi.MockDspApiServer.verify._ +import org.knora.webapi.messages.StringFormatter import org.knora.webapi.messages.admin.responder.KnoraResponseADM import org.knora.webapi.messages.admin.responder.sipimessages._ +import org.knora.webapi.routing.JwtService +import org.knora.webapi.routing.JwtServiceLive +import org.knora.webapi.sharedtestdata.SharedTestDataADM import org.knora.webapi.testcontainers.SipiTestContainer +import com.github.tomakehurst.wiremock.client.WireMock.equalTo +import com.github.tomakehurst.wiremock.matching.RequestPatternBuilder.newRequestPattern + +import org.knora.sipi.SipiIT.fileEndpointSuite object SipiIT extends ZIOSpecDefault { @@ -43,6 +53,48 @@ object SipiIT extends ZIOSpecDefault { private def getWithoutAuthorization(path: String) = SipiTestContainer.resolveUrl(path).map(Request.get).flatMap(Client.request(_)) + private val jwt = + "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiIwLjAuMC4wOjMzMzMiLCJzdWIiOiJodHRwOi8vcmRmaC5jaC91c2Vycy9yb290IiwiYXVkIjpbIktub3JhIiwiU2lwaSJdLCJleHAiOjE2ODk3NTY1MzksImlhdCI6MTY4NzE2NDUzOSwianRpIjoiSG9SSFg5V1lSZHV6VnVmTXZFT1c4USJ9.tlTqr1NGjsOqnMRxjDW1TokDjGAPO5nvG-pcbn09Hrw" + + private val cookiesSuite = + suite("Given a Request contains multiple auth cookies")( + test( + "When getting an existing file, " + + "then Sipi should extract the correct cookie, send it to dsp-api " + + "and responds with Ok" + ) { + for { + _ <- copyTestFilesToSipi + mockServer <- MockDspApiServer.resetAndStubGetResponse( + s"/admin/files/$prefix/$imageTestfile", + 200, + SipiFileInfoGetResponseADM(permissionCode = 2, restrictedViewSettings = None) + ) + response <- + SipiTestContainer + .resolveUrl(s"/$prefix/$imageTestfile/file") + .map { url => + Request + .get(url) + .withCookie(s"KnoraAuthenticationGAXDALRQFYYDUMZTGMZQ9999aSecondCookie=anotherValueShouldBeIgnored") + .withCookie(s"KnoraAuthenticationGAXDALRQFYYDUMZTGMZQ9999=$jwt") + } + .flatMap(Client.request(_)) + requestToDspApiContainsJwt <- ZIO + .attempt( + mockServer.verify( + // Number of times the request should be received (in this case, only once) + 1, + // The expected request with header and value + newRequestPattern().withHeader("Authorization", equalTo(s"Bearer $jwt")) + ) + ) + .logError + .fold(err => false, succ => true) + } yield assertTrue(response.status == Status.Ok, requestToDspApiContainsJwt) + } + ) + private val knoraJsonEndpointSuite = suite("Endpoint /{prefix}/{identifier}/knora.json")( suite("Given the user is unauthorized")( @@ -208,6 +260,7 @@ object SipiIT extends ZIOSpecDefault { override def spec: Spec[TestEnvironment with Scope, Any] = suite("Sipi integration tests with mocked dsp-api")( + cookiesSuite, knoraJsonEndpointSuite, fileEndpointSuite, iiifEndpoint,