From fdfc3cbac1285d46f0fc205c4e5ee29a191c3baa Mon Sep 17 00:00:00 2001 From: Override-6 Date: Sun, 29 Jan 2023 20:36:21 +0100 Subject: [PATCH] JWTEmitter now runs among tests when tests are running --- Core/src/org/tbasket/auth/Authenticator.scala | 4 +-- JWTEmitter/resources/log4j2.xml | 4 +-- JWTEmitter/src/org/tbasket/jwt/Main.scala | 18 ++++++---- target/test-reports-zio/output.json | 22 ++++++------- tests/resources/generate_keys.sh | 22 ++++++------- tests/src/org/tbasket/test/TestEmitter.scala | 33 +++++++++++++++++-- tests/src/org/tbasket/test/TestLayers.scala | 3 +- .../org/tbasket/test/TestServerConfig.scala | 2 +- .../test/pages/LoginPageHandlerTests.scala | 5 ++- 9 files changed, 72 insertions(+), 41 deletions(-) diff --git a/Core/src/org/tbasket/auth/Authenticator.scala b/Core/src/org/tbasket/auth/Authenticator.scala index eae3886..20d5cd0 100644 --- a/Core/src/org/tbasket/auth/Authenticator.scala +++ b/Core/src/org/tbasket/auth/Authenticator.scala @@ -77,11 +77,11 @@ class Authenticator(url: URL, key: PublicKey, algorithm: JwtAsymmetricAlgorithm) import ctx.v.* findByMail(mail) - .someOrFail(UserNotFound)// await one second if password fails to reduce bruteforce + .someOrFail(UserNotFound)// await one second if password fails to reduce bruteforce //FIXME this wont actually reduce bruteforce .filterOrElse(_.passwordHash == hashPassword(password))(ZIO.sleep(1.second) *> ZIO.fail(InvalidPassword)) } - private inline def insert(user: User) = quote { + private def insert(user: User) = quote { query[User].insert( _.id -> user.id, _.name -> user.name, diff --git a/JWTEmitter/resources/log4j2.xml b/JWTEmitter/resources/log4j2.xml index 4323e8a..7dd1a25 100644 --- a/JWTEmitter/resources/log4j2.xml +++ b/JWTEmitter/resources/log4j2.xml @@ -19,11 +19,11 @@ - + - + diff --git a/JWTEmitter/src/org/tbasket/jwt/Main.scala b/JWTEmitter/src/org/tbasket/jwt/Main.scala index 1c3d252..eaa1426 100644 --- a/JWTEmitter/src/org/tbasket/jwt/Main.scala +++ b/JWTEmitter/src/org/tbasket/jwt/Main.scala @@ -18,12 +18,13 @@ import scala.util.chaining.scalaUtilChainingOps object Main extends ZIOAppDefault: private val KeyFactory = java.security.KeyFactory.getInstance("RSA") + private val EmitterPresenceHook = Path.of("/tmp/emitter.presence") private val app = Http.collectZIO[Request] { - case r@(Method.GET | Method.POST) -> _ / "jwt" => + case r@(Method.GET | Method.POST) -> _ => ZIO.serviceWithZIO[JwtGenerator](_.generateTokenResponse(r)) case _ => - ZIO.succeed(Response(status = Status.NotFound)) + ZIO.succeed(Response(status = Status.MethodNotAllowed)) } private def parsePort(port: Option[String]): Task[Int] = @@ -54,7 +55,10 @@ object Main extends ZIOAppDefault: } parsePort(port) <&> loadKey(keyFile) - + private def onStart(port: Int) = + Console.printLine(s"JWT AppToken open on port $port") *> ZIO.attempt { + Files.createFile(EmitterPresenceHook) + } private def startServer(port: Int, key: PrivateKey) = val config = ServerConfig.default @@ -64,10 +68,12 @@ object Main extends ZIOAppDefault: val generator = new JwtGenerator(Duration.ofDays(15), key, JwtAlgorithm.RS256) val configLayer = ServerConfig.live(config) - (Server.install( - app - ) *> Console.printLine(s"JWT AppToken open on port $port") *> ZIO.never) + (Server.install(app) *> onStart(port) *> ZIO.never) .provide(configLayer, Server.live, ZLayer.succeed(generator)) + .catchAllCause(c => { + Files.writeString(EmitterPresenceHook, "A") + ZIO.failCause(c) + }) val run = ZIO.serviceWithZIO[ZIOAppArgs](args => parseArgs(args.getArgs)) diff --git a/target/test-reports-zio/output.json b/target/test-reports-zio/output.json index cf8bdd4..8702013 100644 --- a/target/test-reports-zio/output.json +++ b/target/test-reports-zio/output.json @@ -3,39 +3,39 @@ { "name" : "Test Task name not available here/\/login page handler/login situation tests/login with unknown account", "status" : "Success", - "durationMillis" : "4103", + "durationMillis" : "3877", "annotations" : "", "fullyQualifiedClassName" : "Test Task name not available here", "labels" : ["\/login page handler", "login situation tests", "login with unknown account"] }, { "name" : "Test Task name not available here/\/login page handler/login situation tests/login with known account", - "status" : "Failure", - "durationMillis" : "1", + "status" : "Success", + "durationMillis" : "4862", "annotations" : "", "fullyQualifiedClassName" : "Test Task name not available here", "labels" : ["\/login page handler", "login situation tests", "login with known account"] }, { - "name" : "Test Task name not available here/\/login page handler/erroned request body tests/with no password attribute", + "name" : "Test Task name not available here/\/login page handler/erroned request body tests/empty packet", "status" : "Success", - "durationMillis" : "2681", + "durationMillis" : "2683", "annotations" : "", "fullyQualifiedClassName" : "Test Task name not available here", - "labels" : ["\/login page handler", "erroned request body tests", "with no password attribute"] + "labels" : ["\/login page handler", "erroned request body tests", "empty packet"] }, { - "name" : "Test Task name not available here/\/login page handler/erroned request body tests/empty packet", + "name" : "Test Task name not available here/\/login page handler/erroned request body tests/with no password attribute", "status" : "Success", - "durationMillis" : "2697", + "durationMillis" : "2698", "annotations" : "", "fullyQualifiedClassName" : "Test Task name not available here", - "labels" : ["\/login page handler", "erroned request body tests", "empty packet"] + "labels" : ["\/login page handler", "erroned request body tests", "with no password attribute"] }, { "name" : "Test Task name not available here/\/login page handler/erroned request body tests/with no mail attribute", "status" : "Success", - "durationMillis" : "2702", + "durationMillis" : "2697", "annotations" : "", "fullyQualifiedClassName" : "Test Task name not available here", "labels" : ["\/login page handler", "erroned request body tests", "with no mail attribute"] @@ -43,7 +43,7 @@ { "name" : "Test Task name not available here/\/login page handler/erroned request body tests/with invalid json", "status" : "Success", - "durationMillis" : "2717", + "durationMillis" : "2679", "annotations" : "", "fullyQualifiedClassName" : "Test Task name not available here", "labels" : ["\/login page handler", "erroned request body tests", "with invalid json"] diff --git a/tests/resources/generate_keys.sh b/tests/resources/generate_keys.sh index f56a12a..03ecff2 100755 --- a/tests/resources/generate_keys.sh +++ b/tests/resources/generate_keys.sh @@ -5,23 +5,21 @@ echo GENERATING TEMPORARY KEY PAIRS FOR TESTS rm -r /tmp/keys &> /dev/null mkdir -p /tmp/keys cd /tmp/keys -keytool -genkey -noprompt \ +keytool -genkeypair \ -alias key \ -keyalg RSA \ - -validity 2 \ - -keystore test.keystore \ + -keysize 4069 \ + -sigalg SHA256withRSA \ -storetype PKCS12 \ + -keystore store.p12 \ -dname "CN=x.y.com, OU=TB, O=TBA, L=dzqdz, S=dqzdzq, C=GB" \ -storepass 123456789 \ -keypass 123456789 -keytool -noprompt -export \ - -alias key \ - -keystore test.keystore \ - -rfc \ - -file public.cert \ - -keypass 123456789 \ - -storepass 123456789 +keytool -v -export -file public.cert -keystore store.p12 -alias key -storepass 123456789 + +openssl pkcs12 -in store.p12 -nodes -nocerts -out key.pem -passin pass:123456789 +openssl pkcs8 -topk8 -inform PEM -outform DER -in key.pem -out key.pcqks -nocrypt -openssl pkcs12 -in test.keystore -nodes -nocerts -out private.pcks -passin pass:123456789 -openssl pkcs12 -in test.keystore -nokeys -out public.cert -passin pass:123456789 +#openssl pkcs8 -topk8 -inform PEM -outform DER -in test.keystore -out private.pcks -passin pass:123456789 +#openssl pkcs8 -topk8 -inform PEM -outform DER -in test.keystore -out public.cert -passin pass:123456789 diff --git a/tests/src/org/tbasket/test/TestEmitter.scala b/tests/src/org/tbasket/test/TestEmitter.scala index eb6406c..d170405 100644 --- a/tests/src/org/tbasket/test/TestEmitter.scala +++ b/tests/src/org/tbasket/test/TestEmitter.scala @@ -1,8 +1,35 @@ package org.tbasket.test - +import com.sun.nio.file.ExtendedOpenOption +import io.netty.buffer.ByteBuf + +import java.nio.channels.Pipe +import java.nio.file.{Files, Path, StandardOpenOption} object TestEmitter: - val PORT = 5455 + val PORT = 5457 - \ No newline at end of file + def start(): Unit = { + val emitterPresence = Path.of("/tmp/emitter.presence") + Files.deleteIfExists(emitterPresence) + + val process = new ProcessBuilder( + "bash", + "./mill", "JWTEmitter.run", + "-k", "/tmp/keys/key.pcqks", + "-p", TestServerConfig.emitterURL.port.get.toString + ) + .inheritIO() + .start() + Runtime.getRuntime.addShutdownHook(new Thread((() => process.destroy()): Runnable)) + + //the emitter will create the /tmp/emitter.presence file once it started, this is to inform us that the server is OP + while (Files.notExists(emitterPresence)) + Thread.sleep(500) + + if (Files.readString(emitterPresence) == "A") { + System.err.println("Emitter did not start successfully") + System.exit(1) + } + Files.delete(emitterPresence) + } diff --git a/tests/src/org/tbasket/test/TestLayers.scala b/tests/src/org/tbasket/test/TestLayers.scala index cfef489..bffe1e0 100644 --- a/tests/src/org/tbasket/test/TestLayers.scala +++ b/tests/src/org/tbasket/test/TestLayers.scala @@ -15,6 +15,7 @@ object TestLayers { val auth = { + TestEmitter.start() val publicKey = TestServerConfig.emitterCertificate.getPublicKey val auth = new Authenticator(TestServerConfig.emitterURL, publicKey, TestServerConfig.emitterKeyAlgorithm) ZLayer.succeed(auth) @@ -42,7 +43,7 @@ object TestLayers { val in = getClass.getClassLoader.getResourceAsStream(url) val bytes = in.readAllBytes() in.close() - + val requests = new String(bytes).split(';') requests.foreach(statement.execute) } diff --git a/tests/src/org/tbasket/test/TestServerConfig.scala b/tests/src/org/tbasket/test/TestServerConfig.scala index 0b53ede..6fa64aa 100644 --- a/tests/src/org/tbasket/test/TestServerConfig.scala +++ b/tests/src/org/tbasket/test/TestServerConfig.scala @@ -17,7 +17,7 @@ object TestServerConfig extends ServerConfig { private final val CertFactory = CertificateFactory.getInstance("X509") - override def emitterURL: URL = URL.fromString(s"http://localhost/$PORT").getOrElse(null) + override def emitterURL: URL = URL.fromString(s"http://localhost:$PORT").getOrElse(null) override def emitterCertificate: Certificate = CertFactory.generateCertificate(Files.newInputStream(Path.of("/tmp/keys/public.cert"))) diff --git a/tests/src/org/tbasket/test/pages/LoginPageHandlerTests.scala b/tests/src/org/tbasket/test/pages/LoginPageHandlerTests.scala index a88b881..792aff1 100644 --- a/tests/src/org/tbasket/test/pages/LoginPageHandlerTests.scala +++ b/tests/src/org/tbasket/test/pages/LoginPageHandlerTests.scala @@ -14,7 +14,7 @@ import zio.* import zio.http.netty.client.ConnectionPool import zio.http.* import zio.http.model.{HeaderNames, Headers} -import zio.http.model.Headers.Header +import zio.http.model.Headers.{Header, empty} import zio.json.* import zio.json.ast.{Json, JsonCursor} import zio.test.* @@ -65,9 +65,8 @@ object LoginPageHandlerTests extends ZIOSpecDefault { test("login with known account") { for response <- post(Request.post(Body.fromString("""{"password":"123456","email":"maximebatista18@gmail.com"}"""), URL.empty)) - json <- getJsonBody(response) - //errorType <- parseAttribute(json, "error", JsonCursor.field("error").isString) yield + assert(response)(hasField("body", _.body, equalTo(Body.empty))) //TODO assert that the cookie name is JWT assert(response)(hasField("headers", _.headers, exists(hasField("key", _.key, equalTo(HeaderNames.setCookie))))) } )