mirror of
https://github.com/OpenBankProject/OBP-API.git
synced 2026-02-06 19:16:53 +00:00
commit
27bdfcce3b
@ -499,7 +499,22 @@
|
||||
<artifactId>amqp-client</artifactId>
|
||||
<version>5.22.0</version>
|
||||
</dependency>
|
||||
|
||||
|
||||
<!-- https://mvnrepository.com/artifact/org.testcontainers/rabbitmq -->
|
||||
<dependency>
|
||||
<groupId>org.testcontainers</groupId>
|
||||
<artifactId>rabbitmq</artifactId>
|
||||
<version>1.20.3</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<!-- https://mvnrepository.com/artifact/org.testcontainers/kafka -->
|
||||
<dependency>
|
||||
<groupId>org.testcontainers</groupId>
|
||||
<artifactId>kafka</artifactId>
|
||||
<version>1.20.3</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
||||
@ -747,9 +747,10 @@ display_internal_errors=false
|
||||
# allow_oauth2_login=false
|
||||
# URL of Public server JWK set used for validating bearer JWT access tokens
|
||||
# It can contain more than one URL i.e. list of uris. Values are comma separated.
|
||||
# If MITREId URL is present it must be at 1st place in the list
|
||||
# because MITREId URL can be an appropirate value and we cannot rely on it.
|
||||
# oauth2.jwk_set.url=http://localhost:8080/jwk.json,https://www.googleapis.com/oauth2/v3/certs
|
||||
# Keycloak Identity Provider Host
|
||||
# oauth2.keycloak.host=http://localhost:7070
|
||||
# oauth2.keycloak.well-known=http://localhost:7070/realms/master/.well-known/openid-configuration
|
||||
# ------------------------------------------------------------------------------ OAuth 2 ------
|
||||
|
||||
## This property is used for documenting at Resource Doc. It may include the port also (but not /obp)
|
||||
|
||||
@ -28,13 +28,12 @@ package code.api
|
||||
|
||||
import java.net.URI
|
||||
import java.util
|
||||
|
||||
import code.api.util.ErrorMessages._
|
||||
import code.api.util.{APIUtil, CallContext, CertificateUtil, JwtUtil}
|
||||
import code.consumer.Consumers
|
||||
import code.consumer.Consumers.consumers
|
||||
import code.loginattempts.LoginAttempt
|
||||
import code.model.Consumer
|
||||
import code.model.{AppType, Consumer}
|
||||
import code.util.HydraUtil._
|
||||
import code.users.Users
|
||||
import code.util.Helper.MdcLoggable
|
||||
@ -75,11 +74,13 @@ object OAuth2Login extends RestHelper with MdcLoggable {
|
||||
case true =>
|
||||
val value = getValueOfOAuh2HeaderField(cc)
|
||||
if (Google.isIssuer(value)) {
|
||||
Google.applyRules(value, cc)
|
||||
Google.applyIdTokenRules(value, cc)
|
||||
} else if (Yahoo.isIssuer(value)) {
|
||||
Yahoo.applyRules(value, cc)
|
||||
Yahoo.applyIdTokenRules(value, cc)
|
||||
} else if (Azure.isIssuer(value)) {
|
||||
Azure.applyRules(value, cc)
|
||||
Azure.applyIdTokenRules(value, cc)
|
||||
} else if (Keycloak.isIssuer(value)) {
|
||||
Keycloak.applyRules(value, cc)
|
||||
} else {
|
||||
Hydra.applyRules(value, cc)
|
||||
}
|
||||
@ -95,11 +96,13 @@ object OAuth2Login extends RestHelper with MdcLoggable {
|
||||
case true =>
|
||||
val value = getValueOfOAuh2HeaderField(cc)
|
||||
if (Google.isIssuer(value)) {
|
||||
Google.applyRulesFuture(value, cc)
|
||||
Google.applyIdTokenRulesFuture(value, cc)
|
||||
} else if (Yahoo.isIssuer(value)) {
|
||||
Yahoo.applyRulesFuture(value, cc)
|
||||
Yahoo.applyIdTokenRulesFuture(value, cc)
|
||||
} else if (Azure.isIssuer(value)) {
|
||||
Azure.applyRulesFuture(value, cc)
|
||||
Azure.applyIdTokenRulesFuture(value, cc)
|
||||
} else if (Keycloak.isIssuer(value)) {
|
||||
Keycloak.applyRulesFuture(value, cc)
|
||||
} else {
|
||||
Hydra.applyRulesFuture(value, cc)
|
||||
}
|
||||
@ -108,12 +111,12 @@ object OAuth2Login extends RestHelper with MdcLoggable {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
object Hydra extends OAuth2Util {
|
||||
override def wellKnownOpenidConfiguration: URI = new URI(hydraPublicUrl)
|
||||
override def urlOfJwkSets: Box[String] = checkUrlOfJwkSets(identityProvider = hydraPublicUrl)
|
||||
|
||||
private def applyAccessTokenRules(value: String, cc: CallContext): (Box[User], Some[CallContext]) = {
|
||||
|
||||
override def applyAccessTokenRules(value: String, cc: CallContext): (Box[User], Some[CallContext]) = {
|
||||
// In case of Hydra issued access tokens are not self-encoded/self-contained like JWT tokens are.
|
||||
// It implies the access token can be revoked at any time.
|
||||
val introspectOAuth2Token: OAuth2TokenIntrospection = hydraAdmin.introspectOAuth2Token(value, null)
|
||||
@ -135,7 +138,7 @@ object OAuth2Login extends RestHelper with MdcLoggable {
|
||||
val errorMessage = Oauth2TokenEndpointAuthMethodForbidden + hydraClient.getTokenEndpointAuthMethod()
|
||||
return (Failure(errorMessage), Some(cc.copy(consumer = Failure(errorMessage))))
|
||||
}
|
||||
|
||||
|
||||
// check access token binding with client certificate
|
||||
{
|
||||
if(consumer.isEmpty) {
|
||||
@ -163,7 +166,7 @@ object OAuth2Login extends RestHelper with MdcLoggable {
|
||||
// hydra update client endpoint have bug, So here delete and create to do update
|
||||
hydraAdmin.deleteOAuth2Client(clientId)
|
||||
hydraAdmin.createOAuth2Client(oAuth2Client)
|
||||
} else if(!CertificateUtil.comparePemX509Certificates(certInConsumer, cert)) {
|
||||
} else if(!CertificateUtil.comparePemX509Certificates(certInConsumer, cert)) {
|
||||
// Cannot mat.ch the value from PSD2-CERT header and the database value Consumer.clientCertificate
|
||||
logger.debug(s"Cert in Consumer with the name ***${foundConsumer.name}*** : " + certInConsumer)
|
||||
logger.debug("Cert in Request: " + cert)
|
||||
@ -178,7 +181,7 @@ object OAuth2Login extends RestHelper with MdcLoggable {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// In case a user is created via OpenID Connect flow implies provider = hydraPublicUrl
|
||||
// In case a user is created via GUI of OBP-API implies provider = Constant.localIdentityProvider
|
||||
val user = Users.users.vend.getUserByProviderAndUsername(introspectOAuth2Token.getIss, introspectOAuth2Token.getSub).or(
|
||||
@ -193,28 +196,24 @@ object OAuth2Login extends RestHelper with MdcLoggable {
|
||||
case _ => (user, Some(cc.copy(consumer = consumer)))
|
||||
}
|
||||
}
|
||||
|
||||
private def applyIdTokenRules(value: String, cc: CallContext): (Box[User], Some[CallContext]) = {
|
||||
super.applyRules(value, cc)
|
||||
}
|
||||
|
||||
override def applyRules(value: String, cc: CallContext): (Box[User], Some[CallContext]) = {
|
||||
isIssuer(jwtToken=value, identityProvider = hydraPublicUrl) match {
|
||||
case true => applyIdTokenRules(value, cc)
|
||||
case false => applyAccessTokenRules(value, cc)
|
||||
def applyRules(token: String, cc: CallContext): (Box[User], Some[CallContext]) = {
|
||||
isIssuer(jwtToken=token, identityProvider = hydraPublicUrl) match {
|
||||
case true => super.applyIdTokenRules(token, cc)
|
||||
case false => applyAccessTokenRules(token, cc)
|
||||
}
|
||||
}
|
||||
|
||||
override def applyRulesFuture(value: String, cc: CallContext): Future[(Box[User], Some[CallContext])] = Future {
|
||||
|
||||
def applyRulesFuture(value: String, cc: CallContext): Future[(Box[User], Some[CallContext])] = Future {
|
||||
applyRules(value, cc)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
trait OAuth2Util {
|
||||
|
||||
|
||||
def wellKnownOpenidConfiguration: URI
|
||||
|
||||
|
||||
def urlOfJwkSets: Box[String] = APIUtil.getPropsValue(nameOfProperty = "oauth2.jwk_set.url")
|
||||
|
||||
def checkUrlOfJwkSets(identityProvider: String) = {
|
||||
@ -226,7 +225,7 @@ object OAuth2Login extends RestHelper with MdcLoggable {
|
||||
case Nil => Failure(Oauth2CannotMatchIssuerAndJwksUriException)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private def getClaim(name: String, idToken: String): Option[String] = {
|
||||
val claim = JwtUtil.getClaim(name = name, jwtToken = idToken)
|
||||
claim match {
|
||||
@ -249,6 +248,18 @@ object OAuth2Login extends RestHelper with MdcLoggable {
|
||||
Failure(Oauth2ThereIsNoUrlOfJwkSet)
|
||||
}
|
||||
}
|
||||
def validateAccessToken(accessToken: String): Box[JWTClaimsSet] = {
|
||||
urlOfJwkSets match {
|
||||
case Full(url) =>
|
||||
JwtUtil.validateAccessToken(accessToken, url)
|
||||
case ParamFailure(a, b, c, apiFailure : APIFailure) =>
|
||||
ParamFailure(a, b, c, apiFailure : APIFailure)
|
||||
case Failure(msg, t, c) =>
|
||||
Failure(msg, t, c)
|
||||
case _ =>
|
||||
Failure(Oauth2ThereIsNoUrlOfJwkSet)
|
||||
}
|
||||
}
|
||||
/** New Style Endpoints
|
||||
* This function creates user based on "iss" and "sub" fields
|
||||
* It is mapped in next way:
|
||||
@ -256,11 +267,11 @@ object OAuth2Login extends RestHelper with MdcLoggable {
|
||||
* sub => ResourceUser.providerId
|
||||
* @param idToken Google's response example:
|
||||
* {
|
||||
* "access_token": "ya29.GluUBg5DflrJciFikW5hqeKEp9r1whWnU5x2JXCm9rKkRMs2WseXX8O5UugFMDsIKuKCZlE7tTm1fMII_YYpvcMX6quyR5DXNHH8Lbx5TrZN__fA92kszHJEVqPc",
|
||||
* "id_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6IjA4ZDMyNDVjNjJmODZiNjM2MmFmY2JiZmZlMWQwNjk4MjZkZDFkYzEiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLCJhenAiOiI0MDc0MDg3MTgxOTIuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJhdWQiOiI0MDc0MDg3MTgxOTIuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJzdWIiOiIxMTM5NjY4NTQyNDU3ODA4OTI5NTkiLCJlbWFpbCI6Im1hcmtvLm1pbGljLnNyYmlqYUBnbWFpbC5jb20iLCJlbWFpbF92ZXJpZmllZCI6dHJ1ZSwiYXRfaGFzaCI6Im5HS1JUb0tOblZBMjhINk1od1hCeHciLCJuYW1lIjoiTWFya28gTWlsacSHIiwicGljdHVyZSI6Imh0dHBzOi8vbGg1Lmdvb2dsZXVzZXJjb250ZW50LmNvbS8tWGQ0NGhuSjZURG8vQUFBQUFBQUFBQUkvQUFBQUFBQUFBQUEvQUt4cndjYWR3emhtNE40dFdrNUU4QXZ4aS1aSzZrczRxZy9zOTYtYy9waG90by5qcGciLCJnaXZlbl9uYW1lIjoiTWFya28iLCJmYW1pbHlfbmFtZSI6Ik1pbGnEhyIsImxvY2FsZSI6ImVuIiwiaWF0IjoxNTQ3NzA1NjkxLCJleHAiOjE1NDc3MDkyOTF9.iUxhF_SU2vi76zPuRqAKJvFOzpb_EeP3lc5u9FO9o5xoXzVq3QooXexTfK2f1YAcWEy9LSftA34PB0QTuCZpkQChZVM359n3a3hplf6oWWkBXZN2_IG10NwEH4g0VVBCsjWBDMp6lvepN_Zn15x8opUB7272m4-smAou_WmUPTeivXRF8yPcp4J55DigcY31YP59dMQr2X-6Rr1vCRnJ6niqqJ1UDldfsgt4L7dXmUCnkDdXHwEQAZwbKbR4dUoEha3QeylCiBErmLdpIyqfKECphC6piGXZB-rRRqLz41WNfuF-3fswQvGmIkzTJDR7lQaletMp7ivsfVw8N5jFxg",
|
||||
* "expires_in": 3600,
|
||||
* "token_type": "Bearer",
|
||||
* "scope": "https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/userinfo.email",
|
||||
* "access_token": "ya29.GluUBg5DflrJciFikW5hqeKEp9r1whWnU5x2JXCm9rKkRMs2WseXX8O5UugFMDsIKuKCZlE7tTm1fMII_YYpvcMX6quyR5DXNHH8Lbx5TrZN__fA92kszHJEVqPc",
|
||||
* "id_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6IjA4ZDMyNDVjNjJmODZiNjM2MmFmY2JiZmZlMWQwNjk4MjZkZDFkYzEiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLCJhenAiOiI0MDc0MDg3MTgxOTIuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJhdWQiOiI0MDc0MDg3MTgxOTIuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJzdWIiOiIxMTM5NjY4NTQyNDU3ODA4OTI5NTkiLCJlbWFpbCI6Im1hcmtvLm1pbGljLnNyYmlqYUBnbWFpbC5jb20iLCJlbWFpbF92ZXJpZmllZCI6dHJ1ZSwiYXRfaGFzaCI6Im5HS1JUb0tOblZBMjhINk1od1hCeHciLCJuYW1lIjoiTWFya28gTWlsacSHIiwicGljdHVyZSI6Imh0dHBzOi8vbGg1Lmdvb2dsZXVzZXJjb250ZW50LmNvbS8tWGQ0NGhuSjZURG8vQUFBQUFBQUFBQUkvQUFBQUFBQUFBQUEvQUt4cndjYWR3emhtNE40dFdrNUU4QXZ4aS1aSzZrczRxZy9zOTYtYy9waG90by5qcGciLCJnaXZlbl9uYW1lIjoiTWFya28iLCJmYW1pbHlfbmFtZSI6Ik1pbGnEhyIsImxvY2FsZSI6ImVuIiwiaWF0IjoxNTQ3NzA1NjkxLCJleHAiOjE1NDc3MDkyOTF9.iUxhF_SU2vi76zPuRqAKJvFOzpb_EeP3lc5u9FO9o5xoXzVq3QooXexTfK2f1YAcWEy9LSftA34PB0QTuCZpkQChZVM359n3a3hplf6oWWkBXZN2_IG10NwEH4g0VVBCsjWBDMp6lvepN_Zn15x8opUB7272m4-smAou_WmUPTeivXRF8yPcp4J55DigcY31YP59dMQr2X-6Rr1vCRnJ6niqqJ1UDldfsgt4L7dXmUCnkDdXHwEQAZwbKbR4dUoEha3QeylCiBErmLdpIyqfKECphC6piGXZB-rRRqLz41WNfuF-3fswQvGmIkzTJDR7lQaletMp7ivsfVw8N5jFxg",
|
||||
* "expires_in": 3600,
|
||||
* "token_type": "Bearer",
|
||||
* "scope": "https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/userinfo.email",
|
||||
* "refresh_token": "1/HkTtUahtUTdG7D6urpPNz6g-_qufF-Y1YppcBf0v3Cs"
|
||||
* }
|
||||
* @return an existing or a new user
|
||||
@ -271,7 +282,7 @@ object OAuth2Login extends RestHelper with MdcLoggable {
|
||||
Users.users.vend.getOrCreateUserByProviderIdFuture(
|
||||
provider = provider,
|
||||
idGivenByProvider = uniqueIdGivenByProvider,
|
||||
consentId = None,
|
||||
consentId = None,
|
||||
name = getClaim(name = "given_name", idToken = idToken).orElse(Some(uniqueIdGivenByProvider)),
|
||||
email = getClaim(name = "email", idToken = idToken)
|
||||
).map(_._1)
|
||||
@ -283,11 +294,11 @@ object OAuth2Login extends RestHelper with MdcLoggable {
|
||||
* sub => ResourceUser.providerId
|
||||
* @param idToken Google's response example:
|
||||
* {
|
||||
* "access_token": "ya29.GluUBg5DflrJciFikW5hqeKEp9r1whWnU5x2JXCm9rKkRMs2WseXX8O5UugFMDsIKuKCZlE7tTm1fMII_YYpvcMX6quyR5DXNHH8Lbx5TrZN__fA92kszHJEVqPc",
|
||||
* "id_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6IjA4ZDMyNDVjNjJmODZiNjM2MmFmY2JiZmZlMWQwNjk4MjZkZDFkYzEiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLCJhenAiOiI0MDc0MDg3MTgxOTIuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJhdWQiOiI0MDc0MDg3MTgxOTIuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJzdWIiOiIxMTM5NjY4NTQyNDU3ODA4OTI5NTkiLCJlbWFpbCI6Im1hcmtvLm1pbGljLnNyYmlqYUBnbWFpbC5jb20iLCJlbWFpbF92ZXJpZmllZCI6dHJ1ZSwiYXRfaGFzaCI6Im5HS1JUb0tOblZBMjhINk1od1hCeHciLCJuYW1lIjoiTWFya28gTWlsacSHIiwicGljdHVyZSI6Imh0dHBzOi8vbGg1Lmdvb2dsZXVzZXJjb250ZW50LmNvbS8tWGQ0NGhuSjZURG8vQUFBQUFBQUFBQUkvQUFBQUFBQUFBQUEvQUt4cndjYWR3emhtNE40dFdrNUU4QXZ4aS1aSzZrczRxZy9zOTYtYy9waG90by5qcGciLCJnaXZlbl9uYW1lIjoiTWFya28iLCJmYW1pbHlfbmFtZSI6Ik1pbGnEhyIsImxvY2FsZSI6ImVuIiwiaWF0IjoxNTQ3NzA1NjkxLCJleHAiOjE1NDc3MDkyOTF9.iUxhF_SU2vi76zPuRqAKJvFOzpb_EeP3lc5u9FO9o5xoXzVq3QooXexTfK2f1YAcWEy9LSftA34PB0QTuCZpkQChZVM359n3a3hplf6oWWkBXZN2_IG10NwEH4g0VVBCsjWBDMp6lvepN_Zn15x8opUB7272m4-smAou_WmUPTeivXRF8yPcp4J55DigcY31YP59dMQr2X-6Rr1vCRnJ6niqqJ1UDldfsgt4L7dXmUCnkDdXHwEQAZwbKbR4dUoEha3QeylCiBErmLdpIyqfKECphC6piGXZB-rRRqLz41WNfuF-3fswQvGmIkzTJDR7lQaletMp7ivsfVw8N5jFxg",
|
||||
* "expires_in": 3600,
|
||||
* "token_type": "Bearer",
|
||||
* "scope": "https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/userinfo.email",
|
||||
* "access_token": "ya29.GluUBg5DflrJciFikW5hqeKEp9r1whWnU5x2JXCm9rKkRMs2WseXX8O5UugFMDsIKuKCZlE7tTm1fMII_YYpvcMX6quyR5DXNHH8Lbx5TrZN__fA92kszHJEVqPc",
|
||||
* "id_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6IjA4ZDMyNDVjNjJmODZiNjM2MmFmY2JiZmZlMWQwNjk4MjZkZDFkYzEiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLCJhenAiOiI0MDc0MDg3MTgxOTIuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJhdWQiOiI0MDc0MDg3MTgxOTIuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJzdWIiOiIxMTM5NjY4NTQyNDU3ODA4OTI5NTkiLCJlbWFpbCI6Im1hcmtvLm1pbGljLnNyYmlqYUBnbWFpbC5jb20iLCJlbWFpbF92ZXJpZmllZCI6dHJ1ZSwiYXRfaGFzaCI6Im5HS1JUb0tOblZBMjhINk1od1hCeHciLCJuYW1lIjoiTWFya28gTWlsacSHIiwicGljdHVyZSI6Imh0dHBzOi8vbGg1Lmdvb2dsZXVzZXJjb250ZW50LmNvbS8tWGQ0NGhuSjZURG8vQUFBQUFBQUFBQUkvQUFBQUFBQUFBQUEvQUt4cndjYWR3emhtNE40dFdrNUU4QXZ4aS1aSzZrczRxZy9zOTYtYy9waG90by5qcGciLCJnaXZlbl9uYW1lIjoiTWFya28iLCJmYW1pbHlfbmFtZSI6Ik1pbGnEhyIsImxvY2FsZSI6ImVuIiwiaWF0IjoxNTQ3NzA1NjkxLCJleHAiOjE1NDc3MDkyOTF9.iUxhF_SU2vi76zPuRqAKJvFOzpb_EeP3lc5u9FO9o5xoXzVq3QooXexTfK2f1YAcWEy9LSftA34PB0QTuCZpkQChZVM359n3a3hplf6oWWkBXZN2_IG10NwEH4g0VVBCsjWBDMp6lvepN_Zn15x8opUB7272m4-smAou_WmUPTeivXRF8yPcp4J55DigcY31YP59dMQr2X-6Rr1vCRnJ6niqqJ1UDldfsgt4L7dXmUCnkDdXHwEQAZwbKbR4dUoEha3QeylCiBErmLdpIyqfKECphC6piGXZB-rRRqLz41WNfuF-3fswQvGmIkzTJDR7lQaletMp7ivsfVw8N5jFxg",
|
||||
* "expires_in": 3600,
|
||||
* "token_type": "Bearer",
|
||||
* "scope": "https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/userinfo.email",
|
||||
* "refresh_token": "1/HkTtUahtUTdG7D6urpPNz6g-_qufF-Y1YppcBf0v3Cs"
|
||||
* }
|
||||
* @return an existing or a new user
|
||||
@ -330,22 +341,22 @@ object OAuth2Login extends RestHelper with MdcLoggable {
|
||||
* We can find consumer by sub and azp => Get
|
||||
* @param idToken Google's response example:
|
||||
* {
|
||||
* "access_token": "ya29.GluUBg5DflrJciFikW5hqeKEp9r1whWnU5x2JXCm9rKkRMs2WseXX8O5UugFMDsIKuKCZlE7tTm1fMII_YYpvcMX6quyR5DXNHH8Lbx5TrZN__fA92kszHJEVqPc",
|
||||
* "id_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6IjA4ZDMyNDVjNjJmODZiNjM2MmFmY2JiZmZlMWQwNjk4MjZkZDFkYzEiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLCJhenAiOiI0MDc0MDg3MTgxOTIuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJhdWQiOiI0MDc0MDg3MTgxOTIuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJzdWIiOiIxMTM5NjY4NTQyNDU3ODA4OTI5NTkiLCJlbWFpbCI6Im1hcmtvLm1pbGljLnNyYmlqYUBnbWFpbC5jb20iLCJlbWFpbF92ZXJpZmllZCI6dHJ1ZSwiYXRfaGFzaCI6Im5HS1JUb0tOblZBMjhINk1od1hCeHciLCJuYW1lIjoiTWFya28gTWlsacSHIiwicGljdHVyZSI6Imh0dHBzOi8vbGg1Lmdvb2dsZXVzZXJjb250ZW50LmNvbS8tWGQ0NGhuSjZURG8vQUFBQUFBQUFBQUkvQUFBQUFBQUFBQUEvQUt4cndjYWR3emhtNE40dFdrNUU4QXZ4aS1aSzZrczRxZy9zOTYtYy9waG90by5qcGciLCJnaXZlbl9uYW1lIjoiTWFya28iLCJmYW1pbHlfbmFtZSI6Ik1pbGnEhyIsImxvY2FsZSI6ImVuIiwiaWF0IjoxNTQ3NzA1NjkxLCJleHAiOjE1NDc3MDkyOTF9.iUxhF_SU2vi76zPuRqAKJvFOzpb_EeP3lc5u9FO9o5xoXzVq3QooXexTfK2f1YAcWEy9LSftA34PB0QTuCZpkQChZVM359n3a3hplf6oWWkBXZN2_IG10NwEH4g0VVBCsjWBDMp6lvepN_Zn15x8opUB7272m4-smAou_WmUPTeivXRF8yPcp4J55DigcY31YP59dMQr2X-6Rr1vCRnJ6niqqJ1UDldfsgt4L7dXmUCnkDdXHwEQAZwbKbR4dUoEha3QeylCiBErmLdpIyqfKECphC6piGXZB-rRRqLz41WNfuF-3fswQvGmIkzTJDR7lQaletMp7ivsfVw8N5jFxg",
|
||||
* "expires_in": 3600,
|
||||
* "token_type": "Bearer",
|
||||
* "scope": "https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/userinfo.email",
|
||||
* "access_token": "ya29.GluUBg5DflrJciFikW5hqeKEp9r1whWnU5x2JXCm9rKkRMs2WseXX8O5UugFMDsIKuKCZlE7tTm1fMII_YYpvcMX6quyR5DXNHH8Lbx5TrZN__fA92kszHJEVqPc",
|
||||
* "id_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6IjA4ZDMyNDVjNjJmODZiNjM2MmFmY2JiZmZlMWQwNjk4MjZkZDFkYzEiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLCJhenAiOiI0MDc0MDg3MTgxOTIuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJhdWQiOiI0MDc0MDg3MTgxOTIuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJzdWIiOiIxMTM5NjY4NTQyNDU3ODA4OTI5NTkiLCJlbWFpbCI6Im1hcmtvLm1pbGljLnNyYmlqYUBnbWFpbC5jb20iLCJlbWFpbF92ZXJpZmllZCI6dHJ1ZSwiYXRfaGFzaCI6Im5HS1JUb0tOblZBMjhINk1od1hCeHciLCJuYW1lIjoiTWFya28gTWlsacSHIiwicGljdHVyZSI6Imh0dHBzOi8vbGg1Lmdvb2dsZXVzZXJjb250ZW50LmNvbS8tWGQ0NGhuSjZURG8vQUFBQUFBQUFBQUkvQUFBQUFBQUFBQUEvQUt4cndjYWR3emhtNE40dFdrNUU4QXZ4aS1aSzZrczRxZy9zOTYtYy9waG90by5qcGciLCJnaXZlbl9uYW1lIjoiTWFya28iLCJmYW1pbHlfbmFtZSI6Ik1pbGnEhyIsImxvY2FsZSI6ImVuIiwiaWF0IjoxNTQ3NzA1NjkxLCJleHAiOjE1NDc3MDkyOTF9.iUxhF_SU2vi76zPuRqAKJvFOzpb_EeP3lc5u9FO9o5xoXzVq3QooXexTfK2f1YAcWEy9LSftA34PB0QTuCZpkQChZVM359n3a3hplf6oWWkBXZN2_IG10NwEH4g0VVBCsjWBDMp6lvepN_Zn15x8opUB7272m4-smAou_WmUPTeivXRF8yPcp4J55DigcY31YP59dMQr2X-6Rr1vCRnJ6niqqJ1UDldfsgt4L7dXmUCnkDdXHwEQAZwbKbR4dUoEha3QeylCiBErmLdpIyqfKECphC6piGXZB-rRRqLz41WNfuF-3fswQvGmIkzTJDR7lQaletMp7ivsfVw8N5jFxg",
|
||||
* "expires_in": 3600,
|
||||
* "token_type": "Bearer",
|
||||
* "scope": "https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/userinfo.email",
|
||||
* "refresh_token": "1/HkTtUahtUTdG7D6urpPNz6g-_qufF-Y1YppcBf0v3Cs"
|
||||
* }
|
||||
* @return an existing or a new consumer
|
||||
*/
|
||||
def getOrCreateConsumer(idToken: String, userId: Box[String]): Box[Consumer] = {
|
||||
def getOrCreateConsumer(idToken: String, userId: Box[String], description: Option[String]): Box[Consumer] = {
|
||||
val aud = Some(JwtUtil.getAudience(idToken).mkString(","))
|
||||
val azp = getClaim(name = "azp", idToken = idToken)
|
||||
val iss = getClaim(name = "iss", idToken = idToken)
|
||||
val sub = getClaim(name = "sub", idToken = idToken)
|
||||
val email = getClaim(name = "email", idToken = idToken)
|
||||
val name = getClaim(name = "name", idToken = idToken)
|
||||
val name = getClaim(name = "name", idToken = idToken).orElse(description)
|
||||
Consumers.consumers.vend.getOrCreateConsumer(
|
||||
consumerId = None,
|
||||
key = Some(Helpers.randomString(40).toLowerCase),
|
||||
@ -356,19 +367,19 @@ object OAuth2Login extends RestHelper with MdcLoggable {
|
||||
sub = sub,
|
||||
Some(true),
|
||||
name = name,
|
||||
appType = None,
|
||||
description = Some(OpenIdConnect.openIdConnect),
|
||||
appType = Some(AppType.Confidential),
|
||||
description = description,
|
||||
developerEmail = email,
|
||||
redirectURL = None,
|
||||
createdByUserId = userId.toOption
|
||||
)
|
||||
}
|
||||
|
||||
def applyRules(value: String, cc: CallContext): (Box[User], Some[CallContext]) = {
|
||||
validateIdToken(value) match {
|
||||
def applyIdTokenRules(token: String, cc: CallContext): (Box[User], Some[CallContext]) = {
|
||||
validateIdToken(token) match {
|
||||
case Full(_) =>
|
||||
val user = IdentityProviderCommon.getOrCreateResourceUser(value)
|
||||
val consumer = IdentityProviderCommon.getOrCreateConsumer(value, user.map(_.userId))
|
||||
val user = getOrCreateResourceUser(token)
|
||||
val consumer = getOrCreateConsumer(token, user.map(_.userId), Some(OpenIdConnect.openIdConnect))
|
||||
LoginAttempt.userIsLocked(user.map(_.provider).getOrElse(""), user.map(_.name).getOrElse("")) match {
|
||||
case true => ((Failure(UsernameHasBeenLocked), Some(cc.copy(consumer = consumer))))
|
||||
case false => (user, Some(cc.copy(consumer = consumer)))
|
||||
@ -381,62 +392,61 @@ object OAuth2Login extends RestHelper with MdcLoggable {
|
||||
(Failure(Oauth2IJwtCannotBeVerified), Some(cc))
|
||||
}
|
||||
}
|
||||
def applyRulesFuture(value: String, cc: CallContext): Future[(Box[User], Some[CallContext])] = {
|
||||
validateIdToken(value) match {
|
||||
def applyIdTokenRulesFuture(value: String, cc: CallContext): Future[(Box[User], Some[CallContext])] = Future {
|
||||
applyIdTokenRules(value, cc)
|
||||
}
|
||||
|
||||
def applyAccessTokenRules(token: String, cc: CallContext): (Box[User], Some[CallContext]) = {
|
||||
validateAccessToken(token) match {
|
||||
case Full(_) =>
|
||||
for {
|
||||
user <- IdentityProviderCommon.getOrCreateResourceUserFuture(value)
|
||||
consumer <- Future{IdentityProviderCommon.getOrCreateConsumer(value, user.map(_.userId))}
|
||||
} yield {
|
||||
LoginAttempt.userIsLocked(user.map(_.provider).getOrElse(""), user.map(_.name).getOrElse("")) match {
|
||||
case true => ((Failure(UsernameHasBeenLocked), Some(cc.copy(consumer = consumer))))
|
||||
case false => (user, Some(cc.copy(consumer = consumer)))
|
||||
}
|
||||
val user = getOrCreateResourceUser(token)
|
||||
val consumer = getOrCreateConsumer(token, user.map(_.userId), Some("OAuth 2.0"))
|
||||
LoginAttempt.userIsLocked(user.map(_.provider).getOrElse(""), user.map(_.name).getOrElse("")) match {
|
||||
case true => ((Failure(UsernameHasBeenLocked), Some(cc.copy(consumer = consumer))))
|
||||
case false => (user, Some(cc.copy(consumer = consumer)))
|
||||
}
|
||||
case ParamFailure(a, b, c, apiFailure : APIFailure) =>
|
||||
Future((ParamFailure(a, b, c, apiFailure : APIFailure), Some(cc)))
|
||||
case ParamFailure(a, b, c, apiFailure: APIFailure) =>
|
||||
(ParamFailure(a, b, c, apiFailure: APIFailure), Some(cc))
|
||||
case Failure(msg, t, c) =>
|
||||
Future((Failure(msg, t, c), Some(cc)))
|
||||
(Failure(msg, t, c), Some(cc))
|
||||
case _ =>
|
||||
Future((Failure(Oauth2IJwtCannotBeVerified), Some(cc)))
|
||||
(Failure(Oauth2IJwtCannotBeVerified), Some(cc))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object IdentityProviderCommon extends OAuth2Util {
|
||||
override def wellKnownOpenidConfiguration: URI = new URI("")
|
||||
override def urlOfJwkSets: Box[String] = checkUrlOfJwkSets(identityProvider = "common")
|
||||
def applyAccessTokenRulesFuture(value: String, cc: CallContext): Future[(Box[User], Some[CallContext])] = Future {
|
||||
applyAccessTokenRules(value, cc)
|
||||
}
|
||||
}
|
||||
|
||||
object Google extends OAuth2Util {
|
||||
val google = "google"
|
||||
/**
|
||||
* OpenID Connect Discovery.
|
||||
* Google exposes OpenID Connect discovery documents ( https://YOUR_DOMAIN/.well-known/openid-configuration ).
|
||||
* Google exposes OpenID Connect discovery documents ( https://YOUR_DOMAIN/.well-known/openid-configuration ).
|
||||
* These can be used to automatically configure applications.
|
||||
*/
|
||||
override def wellKnownOpenidConfiguration: URI = new URI("https://accounts.google.com/.well-known/openid-configuration")
|
||||
override def urlOfJwkSets: Box[String] = checkUrlOfJwkSets(identityProvider = google)
|
||||
def isIssuer(jwt: String): Boolean = isIssuer(jwtToken=jwt, identityProvider = google)
|
||||
}
|
||||
|
||||
|
||||
object Yahoo extends OAuth2Util {
|
||||
val yahoo = "yahoo"
|
||||
/**
|
||||
* OpenID Connect Discovery.
|
||||
* Yahoo exposes OpenID Connect discovery documents ( https://YOUR_DOMAIN/.well-known/openid-configuration ).
|
||||
* Yahoo exposes OpenID Connect discovery documents ( https://YOUR_DOMAIN/.well-known/openid-configuration ).
|
||||
* These can be used to automatically configure applications.
|
||||
*/
|
||||
override def wellKnownOpenidConfiguration: URI = new URI("https://login.yahoo.com/.well-known/openid-configuration")
|
||||
override def urlOfJwkSets: Box[String] = checkUrlOfJwkSets(identityProvider = yahoo)
|
||||
def isIssuer(jwt: String): Boolean = isIssuer(jwtToken=jwt, identityProvider = yahoo)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
object Azure extends OAuth2Util {
|
||||
val microsoft = "microsoft"
|
||||
/**
|
||||
* OpenID Connect Discovery.
|
||||
* Yahoo exposes OpenID Connect discovery documents ( https://YOUR_DOMAIN/.well-known/openid-configuration ).
|
||||
* Yahoo exposes OpenID Connect discovery documents ( https://YOUR_DOMAIN/.well-known/openid-configuration ).
|
||||
* These can be used to automatically configure applications.
|
||||
*/
|
||||
override def wellKnownOpenidConfiguration: URI = new URI("https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration")
|
||||
@ -444,4 +454,30 @@ object OAuth2Login extends RestHelper with MdcLoggable {
|
||||
def isIssuer(jwt: String): Boolean = isIssuer(jwtToken=jwt, identityProvider = microsoft)
|
||||
}
|
||||
|
||||
object Keycloak extends OAuth2Util {
|
||||
val keycloakHost = APIUtil.getPropsValue(nameOfProperty = "oauth2.keycloak.host", "http://localhost:7070")
|
||||
/**
|
||||
* OpenID Connect Discovery.
|
||||
* Yahoo exposes OpenID Connect discovery documents ( https://YOUR_DOMAIN/.well-known/openid-configuration ).
|
||||
* These can be used to automatically configure applications.
|
||||
*/
|
||||
override def wellKnownOpenidConfiguration: URI =
|
||||
new URI(
|
||||
APIUtil.getPropsValue(nameOfProperty = "oauth2.keycloak.well-known", "http://localhost:7070/realms/master/.well-known/openid-configuration")
|
||||
)
|
||||
override def urlOfJwkSets: Box[String] = checkUrlOfJwkSets(identityProvider = keycloakHost)
|
||||
def isIssuer(jwt: String): Boolean = isIssuer(jwtToken=jwt, identityProvider = keycloakHost)
|
||||
|
||||
def applyRules(token: String, cc: CallContext): (Box[User], Some[CallContext]) = {
|
||||
JwtUtil.getClaim("typ", token) match {
|
||||
case "ID" => super.applyIdTokenRules(token, cc)
|
||||
case "Bearer" => super.applyAccessTokenRules(token, cc)
|
||||
case "" => super.applyAccessTokenRules(token, cc)
|
||||
}
|
||||
}
|
||||
def applyRulesFuture(value: String, cc: CallContext): Future[(Box[User], Some[CallContext])] = Future {
|
||||
applyRules(value, cc)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -430,7 +430,12 @@ object MappedConsumersProvider extends ConsumersProvider with MdcLoggable {
|
||||
case None =>
|
||||
}
|
||||
name match {
|
||||
case Some(v) => c.name(v)
|
||||
case Some(v) =>
|
||||
val count = Consumer.findAll(By(Consumer.name, v)).size
|
||||
if (count == 0)
|
||||
c.name(v)
|
||||
else
|
||||
c.name(v + "_" + Helpers.randomString(10).toLowerCase)
|
||||
case None =>
|
||||
}
|
||||
appType match {
|
||||
|
||||
@ -34,12 +34,14 @@ object HydraUtil extends MdcLoggable{
|
||||
val hydraSupportedTokenEndpointAuthMethods =
|
||||
APIUtil.getPropsValue("hydra_supported_token_endpoint_auth_methods", "client_secret_basic,client_secret_post,private_key_jwt")
|
||||
|
||||
lazy val hydraPublicUrl = APIUtil.getPropsValue("hydra_public_url")
|
||||
lazy val hydraPublicUrl: String = APIUtil.getPropsValue("hydra_public_url")
|
||||
.openOrThrowException(s"If props $INTEGRATE_WITH_HYDRA is true, hydra_public_url value should not be blank")
|
||||
// This method is a regular expression operation that removes a trailing slash (/) from a string if one is present.
|
||||
.replaceFirst("/$", "")
|
||||
|
||||
lazy val hydraAdminUrl = APIUtil.getPropsValue("hydra_admin_url")
|
||||
lazy val hydraAdminUrl: String = APIUtil.getPropsValue("hydra_admin_url")
|
||||
.openOrThrowException(s"If props $INTEGRATE_WITH_HYDRA is true, hydra_admin_url value should not be blank")
|
||||
// This method is a regular expression operation that removes a trailing slash (/) from a string if one is present.
|
||||
.replaceFirst("/$", "")
|
||||
|
||||
lazy val hydraConsents = APIUtil.getPropsValue("hydra_consents")
|
||||
|
||||
79
obp-api/src/test/scala/code/container/EmbeddedKafka.scala
Normal file
79
obp-api/src/test/scala/code/container/EmbeddedKafka.scala
Normal file
@ -0,0 +1,79 @@
|
||||
package code.container
|
||||
|
||||
import code.api.v5_0_0.V500ServerSetup
|
||||
import code.setup.DefaultUsers
|
||||
import org.apache.kafka.clients.consumer.{ConsumerConfig, KafkaConsumer}
|
||||
import org.apache.kafka.clients.producer.{KafkaProducer, ProducerConfig, ProducerRecord}
|
||||
import org.apache.kafka.common.serialization.{StringDeserializer, StringSerializer}
|
||||
import org.scalatest.Ignore
|
||||
import org.testcontainers.kafka.KafkaContainer
|
||||
|
||||
import java.util.{Collections, Properties}
|
||||
import scala.jdk.CollectionConverters._
|
||||
|
||||
|
||||
@Ignore
|
||||
class EmbeddedKafka extends V500ServerSetup with DefaultUsers {
|
||||
|
||||
val kafkaContainer: KafkaContainer = new KafkaContainer("apache/kafka-native:3.8.0")
|
||||
// It registers a shutdown hook, which is a block of code (or function) that runs when the application terminates,
|
||||
// - either normally(e.g., when the main method completes)
|
||||
// - or due to an external signal(e.g., Ctrl + C or termination by the operating system).
|
||||
sys.addShutdownHook {
|
||||
kafkaContainer.stop()
|
||||
}
|
||||
override def beforeAll(): Unit = {
|
||||
super.beforeAll()
|
||||
// Start RabbitMQ container
|
||||
kafkaContainer.start()
|
||||
}
|
||||
|
||||
override def afterAll(): Unit = {
|
||||
super.afterAll()
|
||||
kafkaContainer.stop()
|
||||
}
|
||||
|
||||
feature(s"test EmbeddedKafka") {
|
||||
scenario("Publish and Consume Message") {
|
||||
|
||||
val bootstrapServers: String = kafkaContainer.getBootstrapServers
|
||||
|
||||
// Kafka producer properties
|
||||
val producerProps = new Properties()
|
||||
producerProps.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers)
|
||||
producerProps.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, classOf[StringSerializer].getName)
|
||||
producerProps.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, classOf[StringSerializer].getName)
|
||||
|
||||
// Kafka consumer properties
|
||||
val consumerProps = new Properties()
|
||||
consumerProps.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers)
|
||||
consumerProps.put(ConsumerConfig.GROUP_ID_CONFIG, "test-group")
|
||||
consumerProps.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, classOf[StringDeserializer].getName)
|
||||
consumerProps.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, classOf[StringDeserializer].getName)
|
||||
consumerProps.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "earliest")
|
||||
|
||||
// Create a producer
|
||||
val producer = new KafkaProducer[String, String](producerProps)
|
||||
val topic = "test-topic"
|
||||
val key = "test-key"
|
||||
val value = "Hello, Kafka!"
|
||||
|
||||
// Produce a message
|
||||
producer.send(new ProducerRecord[String, String](topic, key, value))
|
||||
producer.close()
|
||||
|
||||
// Create a consumer
|
||||
val consumer = new KafkaConsumer[String, String](consumerProps)
|
||||
consumer.subscribe(Collections.singletonList(topic))
|
||||
|
||||
// Consume the message
|
||||
val records = consumer.poll(5000L)
|
||||
consumer.close()
|
||||
|
||||
val messages = records.asScala.map(record => record.value())
|
||||
messages should contain(value)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
67
obp-api/src/test/scala/code/container/EmbeddedRabbitMQ.scala
Normal file
67
obp-api/src/test/scala/code/container/EmbeddedRabbitMQ.scala
Normal file
@ -0,0 +1,67 @@
|
||||
package code.container
|
||||
|
||||
import code.api.v5_0_0.V500ServerSetup
|
||||
import code.setup.DefaultUsers
|
||||
import com.rabbitmq.client.{Channel, Connection, ConnectionFactory}
|
||||
import org.scalatest.Ignore
|
||||
import org.testcontainers.containers.RabbitMQContainer
|
||||
|
||||
@Ignore
|
||||
class EmbeddedRabbitMQ extends V500ServerSetup with DefaultUsers {
|
||||
|
||||
val rabbitMQContainer = new RabbitMQContainer("rabbitmq:3.7.25-management-alpine")
|
||||
// It registers a shutdown hook, which is a block of code (or function) that runs when the application terminates,
|
||||
// - either normally(e.g., when the main method completes)
|
||||
// - or due to an external signal(e.g., Ctrl + C or termination by the operating system).
|
||||
sys.addShutdownHook {
|
||||
rabbitMQContainer.stop()
|
||||
}
|
||||
override def beforeAll(): Unit = {
|
||||
super.beforeAll()
|
||||
// Start RabbitMQ container
|
||||
rabbitMQContainer.start()
|
||||
}
|
||||
|
||||
override def afterAll(): Unit = {
|
||||
super.afterAll()
|
||||
rabbitMQContainer.stop()
|
||||
}
|
||||
|
||||
feature(s"test EmbeddedRabbitMQ") {
|
||||
scenario("Publish and Consume Message") {
|
||||
|
||||
val rabbitHost = rabbitMQContainer.getHost
|
||||
val rabbitPort = rabbitMQContainer.getAmqpPort
|
||||
|
||||
// Set up RabbitMQ connection
|
||||
val factory = new ConnectionFactory()
|
||||
factory.setHost(rabbitHost)
|
||||
factory.setPort(rabbitPort)
|
||||
|
||||
val connection: Connection = factory.newConnection()
|
||||
val channel: Channel = connection.createChannel()
|
||||
|
||||
// Declare a queue
|
||||
val queueName = "test-queue"
|
||||
channel.queueDeclare(queueName, false, false, false, null)
|
||||
|
||||
// Publish a message
|
||||
val message = "Hello, RabbitMQ!"
|
||||
channel.basicPublish("", queueName, null, message.getBytes)
|
||||
println(s"Published message: $message")
|
||||
|
||||
// Consume the message
|
||||
val delivery = channel.basicGet(queueName, true)
|
||||
val consumedMessage = new String(delivery.getBody)
|
||||
|
||||
println(s"Consumed message: $consumedMessage")
|
||||
consumedMessage shouldBe message
|
||||
|
||||
// Clean up
|
||||
channel.close()
|
||||
connection.close()
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user