Skip to content

Commit 1dfee07

Browse files
committed
Upgrade keycloak version to 14 (keycloak download url now points to github releases page)
1 parent ad23459 commit 1dfee07

File tree

10 files changed

+86
-63
lines changed

10 files changed

+86
-63
lines changed

.scalafmt.conf

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
version = "2.5.3"
1+
version = "2.7.5"
22

33
align = most
44
newlines.alwaysBeforeElseAfterCurlyIf = true

build.sbt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,5 +36,5 @@ lazy val `embedded-keycloak` = (project in file("embedded-keycloak"))
3636
//TEST
3737
"org.scalatest" %% "scalatest" % "3.2.9" % Test
3838
),
39-
parallelExecution in Test in ThisBuild := false
39+
ThisBuild / Test / parallelExecution := false
4040
)

embedded-keycloak/src/main/scala/org/tmt/embedded_keycloak/Settings.scala

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,25 @@ package org.tmt.embedded_keycloak
22

33
import os.{Inherit, Pipe, ProcessOutput}
44

5+
case class InvalidVersion(version: String) extends Exception(s"Unable to parse $version, first part of version number is not Int")
6+
57
case class Settings(
68
port: Int = 8081,
79
host: String = "0.0.0.0",
810
keycloakDirectory: String = System.getProperty("user.home") + "/embedded-keycloak/",
911
cleanPreviousData: Boolean = true,
1012
alwaysDownload: Boolean = false,
11-
version: String = "11.0.0",
13+
version: String = "14.0.0",
1214
printProcessLogs: Boolean = true
1315
) {
1416
val stdOutLogger: ProcessOutput = if (printProcessLogs) Inherit else Pipe
17+
18+
private val versionPrefix = version.split('.').headOption.getOrElse(throw InvalidVersion(version)).toInt
19+
20+
val keycloakDownloadUrl: String =
21+
if (versionPrefix < 12) s"https://downloads.jboss.org/keycloak/$version/keycloak-$version.tar.gz"
22+
else s"https://github.com/keycloak/keycloak/releases/download/$version/keycloak-$version.tar.gz"
23+
1524
}
1625

1726
object Settings {

embedded-keycloak/src/main/scala/org/tmt/embedded_keycloak/impl/data/DataFetcher.scala

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ private[embedded_keycloak] class DataFetcher(settings: Settings) extends FeederB
1414
val json = ujson.read(response.bytes).arr
1515
json
1616
.map(_.obj)
17-
.map(obj => {
17+
.map { obj =>
1818
val realmName = obj.getStr("realm")
1919
val clients = getClients(realmName)
2020
Realm(
@@ -23,7 +23,7 @@ private[embedded_keycloak] class DataFetcher(settings: Settings) extends FeederB
2323
users = getUsers(realmName, clients),
2424
realmRoles = getRealmRoles(realmName)
2525
)
26-
})
26+
}
2727
.toSet
2828
}
2929

@@ -94,10 +94,7 @@ private[embedded_keycloak] class DataFetcher(settings: Settings) extends FeederB
9494
.map(_.getStr("name"))
9595
)
9696
.toMap
97-
.flatMap {
98-
case (k, v) =>
99-
v.map(roleName => ClientRole(clientIds(k).name, roleName))
100-
}
97+
.flatMap { case (k, v) => v.map(roleName => ClientRole(clientIds(k).name, roleName)) }
10198
.toSet
10299
}
103100

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

119116
private[this] implicit class RichLinkedHashMap(map: mutable.LinkedHashMap[String, Value]) {
120-
def getStr(key: String): String = {
121-
map.get(key).map(_.str).getOrElse("")
122-
}
117+
def getStr(key: String): String = map.get(key).map(_.str).getOrElse("")
123118

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

embedded-keycloak/src/main/scala/org/tmt/embedded_keycloak/impl/data/FeederBase.scala

Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ private[embedded_keycloak] abstract class FeederBase(settings: Settings) {
1313

1414
import settings._
1515

16-
protected val jTrue = Bool(true)
17-
protected val jFalse = Bool(false)
16+
protected val jTrue: Bool = Bool(true)
17+
protected val jFalse: Bool = Bool(false)
1818

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

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

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

39-
protected def realmUrl(realmName: String): String =
40-
s"http://localhost:$port/auth/admin/realms/$realmName"
39+
protected def realmUrl(realmName: String): String = s"http://localhost:$port/auth/admin/realms/$realmName"
4140

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

53-
protected implicit def toString(map: Map[String, Value]): String =
54-
ujson.write(Obj(map))
52+
protected implicit def toString(map: Map[String, Value]): String = ujson.write(Obj(map))
5553

56-
protected implicit def requester(method: String): Requester = {
57-
method match {
58-
case "POST" => post
59-
case "GET" => get
60-
case "PUT" => put
61-
}
54+
protected implicit def requester(method: String): Requester = method match {
55+
case "POST" => post
56+
case "GET" => get
57+
case "PUT" => put
6258
}
6359

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

7872
lazy val error = s"""

embedded-keycloak/src/main/scala/org/tmt/embedded_keycloak/impl/data/RoleMapper.scala

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -30,17 +30,14 @@ private[embedded_keycloak] class RoleMapper(
3030

3131
clientRoles
3232
.groupBy(x => x.clientName)
33-
.map {
34-
case (k, v) => (k, v.map(_.roleName))
35-
}
36-
.foreach {
37-
case (clientName, groupedRoles) =>
38-
val clientId = clientIds(clientName)
33+
.map { case (k, v) => (k, v.map(_.roleName)) }
34+
.foreach { case (clientName, groupedRoles) =>
35+
val clientId = clientIds(clientName)
3936

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

43-
kPost(realmUrl(realm.name) + s"/users/$userId/role-mappings/clients/$clientId", upickle.default.write(clientRoles))
40+
kPost(realmUrl(realm.name) + s"/users/$userId/role-mappings/clients/$clientId", upickle.default.write(clientRoles))
4441
}
4542
}
4643
}

embedded-keycloak/src/main/scala/org/tmt/embedded_keycloak/impl/download/AkkaDownloader.scala

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package org.tmt.embedded_keycloak.impl.download
22

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

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

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

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

35-
val responseFuture = Http().singleRequest(HttpRequest(uri = keycloakDownloadUrl))
34+
val responseFuture = AkkaHttpUtils.singleRequestWithRedirect(HttpRequest(uri = keycloakDownloadUrl))
3635
val contentLength = responseFuture.map(getContentLength)
3736

3837
val source: Source[DownloadProgress, Future[Done]] =
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package org.tmt.embedded_keycloak.impl.download
2+
3+
import akka.actor.ActorSystem
4+
import akka.http.scaladsl.Http
5+
import akka.http.scaladsl.model.headers.Location
6+
import akka.http.scaladsl.model.{HttpRequest, HttpResponse, StatusCodes}
7+
8+
import scala.concurrent.{ExecutionContext, Future}
9+
10+
object AkkaHttpUtils {
11+
12+
private val maxRedirectCount = 3
13+
14+
// akka-http does not support redirects - https://github.com/akka/akka-http/issues/195
15+
def singleRequestWithRedirect(req: HttpRequest)(implicit system: ActorSystem): Future[HttpResponse] = {
16+
implicit val ec: ExecutionContext = system.dispatcher
17+
18+
def go(req: HttpRequest, count: Int): Future[HttpResponse] =
19+
Http().singleRequest(req).flatMap { resp =>
20+
resp.status match {
21+
case StatusCodes.Found =>
22+
resp
23+
.header[Location]
24+
.map { loc =>
25+
val newReq = req.withUri(loc.uri)
26+
if (count < maxRedirectCount) go(newReq, count + 1) else Http().singleRequest(newReq)
27+
}
28+
.getOrElse(throw new RuntimeException(s"location not found on 302 for ${req.uri}"))
29+
case _ => Future(resp)
30+
}
31+
}
32+
33+
go(req, 0)
34+
}
35+
36+
}

embedded-keycloak/src/main/scala/org/tmt/embedded_keycloak/utils/BearerToken.scala

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package org.tmt.embedded_keycloak.utils
22

3-
import requests.{post, RequestAuth}
3+
import requests.{RequestAuth, post}
44

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

36-
val tokenString =
37-
ujson.read(response.bytes).obj.get("access_token").map(_.str).get
36+
val tokenString = ujson.read(response.bytes).obj.get("access_token").map(_.str).get
3837
BearerToken(tokenString)
3938
}
4039
}

embedded-keycloak/src/test/scala/org/tmt/embedded_keycloak/impl/data/DataFeederTest.scala

Lines changed: 15 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -4,24 +4,28 @@ import org.scalatest.BeforeAndAfterAll
44
import org.scalatest.funsuite.AnyFunSuite
55
import org.scalatest.matchers.should.Matchers
66
import org.tmt.embedded_keycloak.KeycloakData.{ApplicationUser, ClientRole}
7+
import org.tmt.embedded_keycloak.impl.StopHandle
78
import org.tmt.embedded_keycloak.{EmbeddedKeycloak, KeycloakData, Settings}
89

910
import scala.concurrent.Await
1011
import scala.concurrent.ExecutionContext.Implicits._
1112
import scala.concurrent.duration.DurationDouble
1213

1314
class DataFeederTest extends AnyFunSuite with Matchers with BeforeAndAfterAll {
15+
private var stopHandle = Option.empty[StopHandle]
16+
override protected def afterAll(): Unit = stopHandle.foreach(_.stop())
17+
1418
test("test data integration") {
1519

1620
val settings = Settings.default.copy(port = 9005)
1721
val keycloakData = KeycloakData.fromConfig
1822
val keycloak = new EmbeddedKeycloak(keycloakData, settings)
19-
val stopHandle = Await.result(keycloak.startServer(), 2.minutes)
23+
stopHandle = Some(Await.result(keycloak.startServer(), 2.minutes))
2024

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

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

2630
mayBeActualRealm shouldBe defined
2731

@@ -36,34 +40,24 @@ class DataFeederTest extends AnyFunSuite with Matchers with BeforeAndAfterAll {
3640
username = "user1",
3741
password = "[HIDDEN]",
3842
firstName = "john",
39-
realmRoles = Set("super-admin", "uma_authorization", "offline_access"),
40-
clientRoles = Set(ClientRole("${client_account}", "view-profile"), ClientRole("${client_account}", "manage-account"))
43+
realmRoles = Set("super-admin", "default-roles-example-realm")
4144
),
4245
ApplicationUser(
4346
"user2",
4447
"[HIDDEN]",
45-
realmRoles = Set("uma_authorization", "offline_access"),
46-
clientRoles = Set(
47-
ClientRole(clientName = "some-server", roleName = "server-user"),
48-
ClientRole("${client_account}", "view-profile"),
49-
ClientRole("${client_account}", "manage-account")
50-
)
48+
realmRoles = Set("default-roles-example-realm"),
49+
clientRoles = Set(ClientRole(clientName = "some-server", roleName = "server-user"))
5150
)
5251
)
5352

54-
clients.find(c => {
53+
clients.find { c =>
5554
c.name == "some-server" &&
56-
!c.authorizationEnabled &&
57-
c.clientRoles.contains("server-admin") &&
58-
c.clientRoles.contains("server-user")
59-
}) should not be empty
60-
61-
clients.find(c => {
62-
c.name == "some-client" &&
63-
!c.authorizationEnabled
64-
}) should not be empty
55+
!c.authorizationEnabled &&
56+
c.clientRoles.contains("server-admin") &&
57+
c.clientRoles.contains("server-user")
58+
} should not be empty
6559

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

0 commit comments

Comments
 (0)