Skip to content

Commit

Permalink
Upgrade keycloak version to 14 (keycloak download url now points to g…
Browse files Browse the repository at this point in the history
…ithub releases page)
  • Loading branch information
kpritam committed Jul 1, 2021
1 parent ad23459 commit 1dfee07
Show file tree
Hide file tree
Showing 10 changed files with 86 additions and 63 deletions.
2 changes: 1 addition & 1 deletion .scalafmt.conf
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
version = "2.5.3"
version = "2.7.5"

align = most
newlines.alwaysBeforeElseAfterCurlyIf = true
Expand Down
2 changes: 1 addition & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -36,5 +36,5 @@ lazy val `embedded-keycloak` = (project in file("embedded-keycloak"))
//TEST
"org.scalatest" %% "scalatest" % "3.2.9" % Test
),
parallelExecution in Test in ThisBuild := false
ThisBuild / Test / parallelExecution := false
)
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,25 @@ package org.tmt.embedded_keycloak

import os.{Inherit, Pipe, ProcessOutput}

case class InvalidVersion(version: String) extends Exception(s"Unable to parse $version, first part of version number is not Int")

case class Settings(
port: Int = 8081,
host: String = "0.0.0.0",
keycloakDirectory: String = System.getProperty("user.home") + "/embedded-keycloak/",
cleanPreviousData: Boolean = true,
alwaysDownload: Boolean = false,
version: String = "11.0.0",
version: String = "14.0.0",
printProcessLogs: Boolean = true
) {
val stdOutLogger: ProcessOutput = if (printProcessLogs) Inherit else Pipe

private val versionPrefix = version.split('.').headOption.getOrElse(throw InvalidVersion(version)).toInt

val keycloakDownloadUrl: String =
if (versionPrefix < 12) s"https://downloads.jboss.org/keycloak/$version/keycloak-$version.tar.gz"
else s"https://github.com/keycloak/keycloak/releases/download/$version/keycloak-$version.tar.gz"

}

object Settings {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ private[embedded_keycloak] class DataFetcher(settings: Settings) extends FeederB
val json = ujson.read(response.bytes).arr
json
.map(_.obj)
.map(obj => {
.map { obj =>
val realmName = obj.getStr("realm")
val clients = getClients(realmName)
Realm(
Expand All @@ -23,7 +23,7 @@ private[embedded_keycloak] class DataFetcher(settings: Settings) extends FeederB
users = getUsers(realmName, clients),
realmRoles = getRealmRoles(realmName)
)
})
}
.toSet
}

Expand Down Expand Up @@ -94,10 +94,7 @@ private[embedded_keycloak] class DataFetcher(settings: Settings) extends FeederB
.map(_.getStr("name"))
)
.toMap
.flatMap {
case (k, v) =>
v.map(roleName => ClientRole(clientIds(k).name, roleName))
}
.flatMap { case (k, v) => v.map(roleName => ClientRole(clientIds(k).name, roleName)) }
.toSet
}

Expand All @@ -117,9 +114,7 @@ private[embedded_keycloak] class DataFetcher(settings: Settings) extends FeederB
}

private[this] implicit class RichLinkedHashMap(map: mutable.LinkedHashMap[String, Value]) {
def getStr(key: String): String = {
map.get(key).map(_.str).getOrElse("")
}
def getStr(key: String): String = map.get(key).map(_.str).getOrElse("")

def getBool(key: String): Boolean = map.get(key).exists(_.bool)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ private[embedded_keycloak] abstract class FeederBase(settings: Settings) {

import settings._

protected val jTrue = Bool(true)
protected val jFalse = Bool(false)
protected val jTrue: Bool = Bool(true)
protected val jFalse: Bool = Bool(false)

case class RoleRepresentation(name: String, id: String, containerId: String, composite: Boolean, clientRole: Boolean)

Expand All @@ -36,8 +36,7 @@ private[embedded_keycloak] abstract class FeederBase(settings: Settings) {

protected def realmUrl = s"http://localhost:$port/auth/admin/realms"

protected def realmUrl(realmName: String): String =
s"http://localhost:$port/auth/admin/realms/$realmName"
protected def realmUrl(realmName: String): String = s"http://localhost:$port/auth/admin/realms/$realmName"

protected implicit def toMutableMap(map: Map[String, Value]): MutableMap[String, Value] = {
val mutableMap = MutableMap[String, Value]()
Expand All @@ -50,15 +49,12 @@ private[embedded_keycloak] abstract class FeederBase(settings: Settings) {
url.split("/").last
}

protected implicit def toString(map: Map[String, Value]): String =
ujson.write(Obj(map))
protected implicit def toString(map: Map[String, Value]): String = ujson.write(Obj(map))

protected implicit def requester(method: String): Requester = {
method match {
case "POST" => post
case "GET" => get
case "PUT" => put
}
protected implicit def requester(method: String): Requester = method match {
case "POST" => post
case "GET" => get
case "PUT" => put
}

private def sendRequest(requester: Requester, url: String, stringData: String = null)( // scalastyle:ignore
Expand All @@ -70,9 +66,7 @@ private[embedded_keycloak] abstract class FeederBase(settings: Settings) {
data =
if (stringData == null || stringData.isBlank) RequestBlob.EmptyRequestBlob
else stringData,
headers = Map(
"Content-Type" -> "application/json"
)
headers = Map("Content-Type" -> "application/json")
)

lazy val error = s"""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,17 +30,14 @@ private[embedded_keycloak] class RoleMapper(

clientRoles
.groupBy(x => x.clientName)
.map {
case (k, v) => (k, v.map(_.roleName))
}
.foreach {
case (clientName, groupedRoles) =>
val clientId = clientIds(clientName)
.map { case (k, v) => (k, v.map(_.roleName)) }
.foreach { case (clientName, groupedRoles) =>
val clientId = clientIds(clientName)

val clientRolesResponse = kGet(realmUrl(realm.name) + s"/users/$userId/role-mappings/clients/$clientId/available")
val clientRoles = roleRepresentations(clientRolesResponse.text(), r => groupedRoles.contains(r.name))
val clientRolesResponse = kGet(realmUrl(realm.name) + s"/users/$userId/role-mappings/clients/$clientId/available")
val clientRoles = roleRepresentations(clientRolesResponse.text(), r => groupedRoles.contains(r.name))

kPost(realmUrl(realm.name) + s"/users/$userId/role-mappings/clients/$clientId", upickle.default.write(clientRoles))
kPost(realmUrl(realm.name) + s"/users/$userId/role-mappings/clients/$clientId", upickle.default.write(clientRoles))
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package org.tmt.embedded_keycloak.impl.download

import akka.Done
import akka.actor.ActorSystem
import akka.http.scaladsl.Http
import akka.http.scaladsl.model.{HttpRequest, HttpResponse, StatusCodes}
import akka.stream.scaladsl.Source
import org.tmt.embedded_keycloak.Settings
Expand All @@ -15,7 +14,6 @@ import scala.concurrent.{Await, ExecutionContext, Future}
private[embedded_keycloak] class AkkaDownloader(settings: Settings, fileIO: FileIO)(implicit system: ActorSystem) {
import settings._

private def keycloakDownloadUrl = s"https://downloads.jboss.org/keycloak/$version/keycloak-$version.tar.gz"
private def isKeycloakDownloaded = os.exists(fileIO.tarFilePath)

implicit private lazy val ec: ExecutionContext = system.dispatcher
Expand All @@ -29,10 +27,11 @@ private[embedded_keycloak] class AkkaDownloader(settings: Settings, fileIO: File

def download(): Unit = {
if (alwaysDownload || !isKeycloakDownloaded) {
println(s"[Embedded-Keycloak] Downloading keycloak from URL: [$keycloakDownloadUrl]")
println(s"[Embedded-Keycloak] Downloading keycloak at location: [${fileIO.downloadDirectory}]")
fileIO.deleteVersion()

val responseFuture = Http().singleRequest(HttpRequest(uri = keycloakDownloadUrl))
val responseFuture = AkkaHttpUtils.singleRequestWithRedirect(HttpRequest(uri = keycloakDownloadUrl))
val contentLength = responseFuture.map(getContentLength)

val source: Source[DownloadProgress, Future[Done]] =
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package org.tmt.embedded_keycloak.impl.download

import akka.actor.ActorSystem
import akka.http.scaladsl.Http
import akka.http.scaladsl.model.headers.Location
import akka.http.scaladsl.model.{HttpRequest, HttpResponse, StatusCodes}

import scala.concurrent.{ExecutionContext, Future}

object AkkaHttpUtils {

private val maxRedirectCount = 3

// akka-http does not support redirects - https://github.com/akka/akka-http/issues/195
def singleRequestWithRedirect(req: HttpRequest)(implicit system: ActorSystem): Future[HttpResponse] = {
implicit val ec: ExecutionContext = system.dispatcher

def go(req: HttpRequest, count: Int): Future[HttpResponse] =
Http().singleRequest(req).flatMap { resp =>
resp.status match {
case StatusCodes.Found =>
resp
.header[Location]
.map { loc =>
val newReq = req.withUri(loc.uri)
if (count < maxRedirectCount) go(newReq, count + 1) else Http().singleRequest(newReq)
}
.getOrElse(throw new RuntimeException(s"location not found on 302 for ${req.uri}"))
case _ => Future(resp)
}
}

go(req, 0)
}

}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package org.tmt.embedded_keycloak.utils

import requests.{post, RequestAuth}
import requests.{RequestAuth, post}

case class BearerToken(token: String) extends RequestAuth {
override def header: Option[String] = Some(s"Bearer $token")
Expand Down Expand Up @@ -33,8 +33,7 @@ object BearerToken {
throw new RuntimeException(error)
}

val tokenString =
ujson.read(response.bytes).obj.get("access_token").map(_.str).get
val tokenString = ujson.read(response.bytes).obj.get("access_token").map(_.str).get
BearerToken(tokenString)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,28 @@ import org.scalatest.BeforeAndAfterAll
import org.scalatest.funsuite.AnyFunSuite
import org.scalatest.matchers.should.Matchers
import org.tmt.embedded_keycloak.KeycloakData.{ApplicationUser, ClientRole}
import org.tmt.embedded_keycloak.impl.StopHandle
import org.tmt.embedded_keycloak.{EmbeddedKeycloak, KeycloakData, Settings}

import scala.concurrent.Await
import scala.concurrent.ExecutionContext.Implicits._
import scala.concurrent.duration.DurationDouble

class DataFeederTest extends AnyFunSuite with Matchers with BeforeAndAfterAll {
private var stopHandle = Option.empty[StopHandle]
override protected def afterAll(): Unit = stopHandle.foreach(_.stop())

test("test data integration") {

val settings = Settings.default.copy(port = 9005)
val keycloakData = KeycloakData.fromConfig
val keycloak = new EmbeddedKeycloak(keycloakData, settings)
val stopHandle = Await.result(keycloak.startServer(), 2.minutes)
stopHandle = Some(Await.result(keycloak.startServer(), 2.minutes))

val actualRealms =
KeycloakData.fromServer(settings, "admin", "admin").realms

val mayBeActualRealm = actualRealms.find(x => x.name == "example-realm")
val mayBeActualRealm = actualRealms.find(_.name == "example-realm")

mayBeActualRealm shouldBe defined

Expand All @@ -36,34 +40,24 @@ class DataFeederTest extends AnyFunSuite with Matchers with BeforeAndAfterAll {
username = "user1",
password = "[HIDDEN]",
firstName = "john",
realmRoles = Set("super-admin", "uma_authorization", "offline_access"),
clientRoles = Set(ClientRole("${client_account}", "view-profile"), ClientRole("${client_account}", "manage-account"))
realmRoles = Set("super-admin", "default-roles-example-realm")
),
ApplicationUser(
"user2",
"[HIDDEN]",
realmRoles = Set("uma_authorization", "offline_access"),
clientRoles = Set(
ClientRole(clientName = "some-server", roleName = "server-user"),
ClientRole("${client_account}", "view-profile"),
ClientRole("${client_account}", "manage-account")
)
realmRoles = Set("default-roles-example-realm"),
clientRoles = Set(ClientRole(clientName = "some-server", roleName = "server-user"))
)
)

clients.find(c => {
clients.find { c =>
c.name == "some-server" &&
!c.authorizationEnabled &&
c.clientRoles.contains("server-admin") &&
c.clientRoles.contains("server-user")
}) should not be empty

clients.find(c => {
c.name == "some-client" &&
!c.authorizationEnabled
}) should not be empty
!c.authorizationEnabled &&
c.clientRoles.contains("server-admin") &&
c.clientRoles.contains("server-user")
} should not be empty

stopHandle.stop()
clients.find { c => c.name == "some-client" && !c.authorizationEnabled } should not be empty
}
}
}

0 comments on commit 1dfee07

Please sign in to comment.