Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(authentication): make cookie name unique between environments #2095

Merged
2 changes: 1 addition & 1 deletion build.sbt
Expand Up @@ -70,7 +70,7 @@ lazy val sipi: Project = Project(id = "sipi", base = file("sipi"))
Docker / defaultLinuxInstallLocation := "/sipi",
Universal / mappings ++= {
// copy the sipi/scripts folder
directory("sipi/scripts")
directory("sipi/scripts"),
},
// use filterNot to return all items that do NOT meet the criteria
dockerCommands := dockerCommands.value.filterNot {
Expand Down
3 changes: 2 additions & 1 deletion docker-compose.yml
Expand Up @@ -38,7 +38,8 @@ services:
- KNORA_WEBAPI_KNORA_API_EXTERNAL_HOST=0.0.0.0
- KNORA_WEBAPI_KNORA_API_EXTERNAL_PORT=3333
# entrypoint: [ "valgrind", "--leak-check=yes", "/sipi/sipi" ] ## uncomment to run SIPI under valgrind
command: --config=/sipi/config/sipi.docker-config.lua
command: --config=/sipi/config/sipi.docker-test-config.lua ## command variant to start the sipi container with test routes enabled
# command: --config=/sipi/config/sipi.docker-config.lua

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we still need the commented out command? As far as I understand, for tests we need the sipi.docker-test-config.lua. On test/staging/prod/project servers there is another lua config anyway, right?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good catch. no, it is only needed for some manual testing. I reverted the change


api:
image: daschswiss/knora-api:latest
Expand Down
87 changes: 52 additions & 35 deletions sipi/config/sipi.docker-test-config.lua
@@ -1,8 +1,8 @@
-- * Copyright © 2021 - 2022 Swiss National Data and Service Center for the Humanities and/or DaSCH Service Platform contributors.
-- * SPDX-License-Identifier: Apache-2.0
-- Copyright © 2021 - 2022 Swiss National Data and Service Center for the Humanities and/or DaSCH Service Platform contributors.
-- SPDX-License-Identifier: Apache-2.0

--
-- configuration file for use with Knora
-- ATTENTION: This configuration file should only be used for integration testing. It has additional routes defined!!!
--
sipi = {
--
Expand All @@ -22,6 +22,34 @@ sipi = {
--
port = 1024,

--
-- Number of threads to use
--
nthreads = 8,

--
-- SIPI is using libjpeg to generate the JPEG images. libjpeg requires a quality value which
-- corresponds to the compression rate. 100 is (almost) no compression and best quality, 0
-- would be full compression and no quality. Reasonable values are between 30 and 95...
--
jpeg_quality = 60,

--
-- For scaling images, SIPI offers two methods. The value "high" offers best quality using expensive
-- algorithms: bilinear interpolation, if downscaling the image is first scaled up to an integer
-- multiple of the requires size, and then downscaled using averaging. This results in the best
-- image quality. "medium" uses bilinear interpolation but does not do upscaling before
-- downscaling. If scaling quality is set to "low", then just a lookup table and nearest integer
-- interpolation is being used to scale the images.
-- Recognized values are: "high", "medium", "low".
--
scaling_quality = {
jpeg = "medium",
tiff = "high",
png = "high",
j2k = "high"
},

--
-- Number of seconds a connection (socket) remains open
--
Expand All @@ -30,16 +58,16 @@ sipi = {
--
-- Maximal size of a post request
--
max_post_size = '30M',
max_post_size = '250M',

--
--
-- indicates the path to the root of the image directory. Depending on the settings of the variable
-- "prefix_as_path" the images are search at <imgroot>/<prefix>/<imageid> (prefix_as_path = TRUE)
-- or <imgroot>/<imageid> (prefix_as_path = FALSE). Please note that "prefix" and "imageid" are
-- expected to be urlencoded. Both will be decoded. That is, "/" will be recoignized and expanded
-- in the final path the image file!
--
imgroot = './test/_test_data/images', -- directory for Knora Sipi integration testing
imgroot = '/sipi/images', -- make sure that this directory exists

--
-- If FALSE, the prefix is not used to build the path to the image files
Expand Down Expand Up @@ -68,38 +96,44 @@ sipi = {
--
-- Lua script which is executed on initialization of the Lua interpreter
--
initscript = './scripts/sipi.init-test.lua',
initscript = '/sipi/scripts/sipi.init.lua',

--
-- path to the caching directory
--
cachedir = './cache',
cachedir = '/sipi/cache',

--
-- maxcimal size of the cache
-- maximal size of the cache
--
cachesize = '100M',

--
-- if the cache becomes full, the given percentage of file space is marked for reuase

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
-- if the cache becomes full, the given percentage of file space is marked for reuase
-- if the cache becomes full, the given percentage of file space is marked for reuse

--
cache_hysteresis = 0.1,
cache_hysteresis = 0.15,

--
-- Path to the directory where the scripts for the routes defined below are to be found
--
scriptdir = './scripts',
scriptdir = '/sipi/scripts',

---
--- Size of the thumbnails
--- Size of the thumbnails (to be used within Lua)
---
thumb_size = 'pct:4',
thumb_size = '!128,128',

--
-- Path to the temporary directory
--
tmpdir = '/tmp',

--
-- Maximum age of temporary files, in seconds (requires Knora's upload.lua).
-- Defaults to 86400 seconds (1 day).
--
max_temp_file_age = 86400,

--
-- Path to Knora Application
--
Expand All @@ -110,26 +144,6 @@ sipi = {
--
knora_port = '3333',

--
-- If compiled with SSL support, the port the server is listening for secure connections
--
-- ssl_port = 1025,

--
-- If compiled with SSL support, the path to the certificate (must be .pem file)
-- The follow commands can be used to generate a self-signed certificate
-- # openssl genrsa -out key.pem 2048
-- # openssl req -new -key key.pem -out csr.pem
-- #openssl req -x509 -days 365 -key key.pem -in csr.pem -out certificate.pem
--
-- ssl_certificate = './certificate/certificate.pem',

--
-- If compiled with SSL support, the path to the key file (see above to create)
--
-- ssl_key = './certificate/key.pem',


--
-- The secret for generating JWT's (JSON Web Tokens) (42 characters)
--
Expand All @@ -139,20 +153,23 @@ sipi = {
--
-- Name of the logfile (a ".txt" is added...)
--
logfile = "sipi.log",
-- logfile = "sipi.log",


--
-- loglevel, one of "DEBUG", "INFO", "NOTICE", "WARNING", "ERR",
-- "CRIT", "ALERT", "EMERG"
--
loglevel = "DEBUG"

}


fileserver = {
--
-- directory where the documents for the normal webserver are located
--
docroot = './server',
docroot = '/sipi/server',

--
-- route under which the normal webserver shouöd respond to requests
Expand Down
20 changes: 18 additions & 2 deletions sipi/scripts/basexx.lua
Expand Up @@ -109,7 +109,7 @@ end
-- generic function to decode and encode base32/base64
--------------------------------------------------------------------------------

local function from_basexx( str, alphabet, bits )
function from_basexx( str, alphabet, bits )
local result = {}
for i = 1, #str do
local c = string.sub( str, i, i )
Expand All @@ -127,7 +127,7 @@ local function from_basexx( str, alphabet, bits )
return pure_from_bit( string.sub( value, 1, #value - pad ) )
end

local function to_basexx( str, alphabet, bits, pad )
function to_basexx( str, alphabet, bits, pad )
local bitString = basexx.to_bit( str )

local chunks = divide_string( bitString, bits )
Expand Down Expand Up @@ -160,6 +160,22 @@ function basexx.to_base32( str )
return to_basexx( str, base32Alphabet, 5, base32PadMap[ #str % 5 + 1 ] )
end

--------------------------------------------------------------------------------
-- dsp-api custom variant
--------------------------------------------------------------------------------

local base32CustomAlphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"
local base32CustomPadMap = { "", "999999", "9999", "999", "9" }

function basexx.from_base32Custom( str, ignore )
str = ignore_set( str, ignore )
return from_basexx( string.upper( str ), base32CustomAlphabet, 5 )
end

function basexx.to_base32Custom( str )
return to_basexx( str, base32CustomAlphabet, 5, base32CustomPadMap[ #str % 5 + 1 ] )
end

--------------------------------------------------------------------------------
-- crockford: http://www.crockford.com/wrmg/base32.html
--------------------------------------------------------------------------------
Expand Down
12 changes: 9 additions & 3 deletions sipi/scripts/get_knora_session.lua
Expand Up @@ -36,12 +36,14 @@ function get_session_id(cookie)
host_port = webapi_hostname .. ':' .. webapi_port
server.log("host_port: " .. host_port, server.loglevel.LOG_DEBUG)

local customPadMap = { "", "999999", "9999", "999", "9" }
host_port_base32 = basexx.to_basexx(host_port, base32Alphabet, 5, customPadMap)
host_port_base32 = basexx.to_base32Custom(host_port)
server.log("host_port_base32: " .. host_port_base32, server.loglevel.LOG_DEBUG)






-- tries to extract the Knora session id from the cookie:
-- gets the digits between "sid=" and the closing ";" (only given in case of several key value pairs)
-- ";" is expected to separate different key value pairs (https://tools.ietf.org/html/rfc6265#section-4.2.1)
Expand All @@ -51,6 +53,10 @@ function get_session_id(cookie)
local session_id = string.match(cookie, "KnoraAuthentication" .. host_port_base32 .. "=([^%s;]+)")
server.log("extracted session_id: " .. session_id, server.loglevel.LOG_DEBUG)

return session_id
local session = {}
session["id"] = session_id
session["name"] = "KnoraAuthentication" .. host_port_base32

return session

end
12 changes: 6 additions & 6 deletions sipi/scripts/sipi.init.lua
Expand Up @@ -42,17 +42,18 @@ function pre_flight(prefix, identifier, cookie)

if cookie ~='' then

-- tries to extract the Knora session id from the cookie:
-- tries to extract the Knora session name and id from the cookie:
-- gets the digits between "sid=" and the closing ";" (only given in case of several key value pairs)
-- returns nil if it cannot find it
session_id = get_session_id(cookie)
session = get_session_id(cookie)

if session_id == nil then
-- no session_id could be extracted
if session == nil then
-- no session could be extracted
print("cookie key is invalid: " .. cookie)
server.log("cookie key is invalid: " .. cookie, server.loglevel.LOG_ERR)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need both?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nope, removed the print

else
knora_cookie_header = { Cookie = "KnoraAuthentication=" .. session_id }
knora_cookie_header = { Cookie = session["name"] .. "=" .. session["id"] }
server.log("pre_flight - knora_cookie_header: " .. knora_cookie_header["Cookie"], server.loglevel.LOG_DEBUG)
end
end

Expand All @@ -78,7 +79,6 @@ function pre_flight(prefix, identifier, cookie)

-- print("knora_url: " .. knora_url)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can be removed?

server.log("pre_flight - knora_url: " .. knora_url, server.loglevel.LOG_DEBUG)
server.log("pre_flight - knora_cookie_header: " .. tostring(knora_cookie_header), server.loglevel.LOG_DEBUG)

success, result = server.http("GET", knora_url, knora_cookie_header, 5000)

Expand Down
Expand Up @@ -433,7 +433,7 @@ class ApplicationActor(
new ProjectsRouteADM(routeData).knoraApiPath ~
new StoreRouteADM(routeData).knoraApiPath ~
new UsersRouteADM(routeData).knoraApiPath ~
new SipiRouteADM(routeData).knoraApiPath ~
new FilesRouteADM(routeData).knoraApiPath ~
new SwaggerApiDocsRoute(routeData).knoraApiPath
}
}
Expand Down
Expand Up @@ -17,7 +17,7 @@ import org.knora.webapi.routing.RouteUtilADM
/**
* Provides a routing function for the API that Sipi connects to.
*/
class SipiRouteADM(routeData: KnoraRouteData) extends KnoraRoute(routeData) with Authenticator {
class FilesRouteADM(routeData: KnoraRouteData) extends KnoraRoute(routeData) with Authenticator {

/**
* A routing function for the API that Sipi connects to.
Expand Down
10 changes: 4 additions & 6 deletions webapi/src/test/resources/logback-test.xml
Expand Up @@ -37,12 +37,10 @@
<logger name="kamon.metric" level="ERROR"/>
<logger name="org.apache.http" level="INFO"/>
<logger name="org.apache.http.wire" level="INFO"/>
<logger name="org.testcontainers" level="INFO"/>
<logger name="com.github.dockerjava" level="WARN"/>
<logger name="org.testcontainers.dockerclient.DockerClientProviderStrategy" level="WARN" />
<logger name="org.testcontainers.DockerClientFactory" level="WARN" />
<logger name="org.testcontainers.utility.ImageNameSubstitutor" level="WARN" />
<logger name="org.testcontainers.utility.RegistryAuthLocator" level="WARN" />
<logger name="org.testcontainers" level="WARN" />
<logger name="com.github.dockerjava" level="WARN" />
<logger name="ch.qos.logback" level="WARN" />


<!-- Logging inside ZIO -->
<logger name="zio-slf4j-logger" level="INFO"/>
Expand Down
Expand Up @@ -21,6 +21,7 @@ import org.knora.webapi.util.MutableTestString

import scala.concurrent.Await
import scala.concurrent.duration._
import org.knora.webapi.routing.Authenticator

object AuthenticationV2E2ESpec {
val config: Config = ConfigFactory.parseString("""
Expand Down Expand Up @@ -212,6 +213,24 @@ class AuthenticationV2E2ESpec
assert(response.status === StatusCodes.OK)
}

"authenticate with token in cookie" in {
val KnoraAuthenticationCookieName = Authenticator.calculateCookieName(settings)
val cookieHeader = headers.Cookie(KnoraAuthenticationCookieName, token.get)

val request = Get(baseApiUrl + "/v2/authentication") ~> addHeader(cookieHeader)
val response = singleAwaitingRequest(request)
assert(response.status === StatusCodes.OK)
}

"fail authentication with invalid token in cookie" in {
val KnoraAuthenticationCookieName = Authenticator.calculateCookieName(settings)
val cookieHeader = headers.Cookie(KnoraAuthenticationCookieName, "not_a_valid_token")

val request = Get(baseApiUrl + "/v2/authentication") ~> addHeader(cookieHeader)
val response = singleAwaitingRequest(request)
assert(response.status === StatusCodes.Unauthorized)
}

"logout when providing token in header" in {
// do logout with stored token
val request =
Expand Down