Skip to content

Commit 8569413

Browse files
authored
Merge pull request #779 from naphelps/issue-778
Issue 778: Added a second attempt authentication mechanism that forces a cache synchronization with the database.
2 parents 44c8f70 + 7b6a334 commit 8569413

3 files changed

Lines changed: 34 additions & 11 deletions

File tree

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@ All notable changes to this project will be documented in this file.
44

55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
66

7+
## [2.129.0](https://github.com/open-horizon/exchange-api/pull/779) - 2025-06-20
8+
- Issue 778: Added a second attempt authentication mechanism that forces a cache synchronization with the database.
9+
This is only useful when using multiple Exchange instances in a container orchestration cluster.
10+
Cache invalidation already works as intended for a single instance.
11+
712
## [2.128.1](https://github.com/open-horizon/exchange-api/pull/777) - 2025-06-18
813
- Issue 776: Fixed log level of basic response information.
914

src/main/resources/version.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
2.128.1
1+
2.129.0

src/main/scala/org/openhorizon/exchangeapi/ExchangeApiApp.scala

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -366,7 +366,13 @@ object ExchangeApiApp extends App
366366

367367
val reg: Regex = """^(\S*?)/(\S*)$""".r
368368

369-
369+
def getIdentityCacheValueFromDB(organization: String, username: String): Future[(Identity2, String)] = {
370+
getResourceIdentityAndPassword(organization, username).map {
371+
// Yield this Resource's identity metadata and construct a future identity object. Resource's stored credential tags along.
372+
// This object will be returned and used if authenticated.
373+
identityMetadata: ((Boolean, Option[UUID], Boolean, Option[UUID], Int), String) => (new Identity2(organization, identityMetadata._1, username), identityMetadata._2)
374+
}
375+
}
370376

371377
def myUserPassAuthenticator(credentials: Credentials): Future[Option[Identity2]] = {
372378
credentials match {
@@ -418,16 +424,13 @@ object ExchangeApiApp extends App
418424
}
419425
} yield result
420426
verifiedIdentityFut
421-
} else {
427+
}
428+
else {
422429
def identityCacheGet (resource: String): Future[(Identity2, String)] = {
423430
cacheResourceIdentity.cachingF(resource)(ttl = Option(Configuration.getConfig.getInt("api.cache.idsTtlSeconds").seconds)) {
424431
// On cache miss execute this block to try and find what the value should be.
425432
// If found add to the cache for next time.
426-
getResourceIdentityAndPassword(organization, username).map {
427-
// Yield this Resource's identity metadata and construct a future identity object. Resource's stored credential tags along.
428-
// This object will be returned and used if authenticated.
429-
identityMetadata: ((Boolean, Option[UUID], Boolean, Option[UUID], Int), String) => (new Identity2(organization, identityMetadata._1, username), identityMetadata._2)
430-
}
433+
getIdentityCacheValueFromDB(organization: String, username: String)
431434
}
432435
}
433436

@@ -469,8 +472,8 @@ object ExchangeApiApp extends App
469472
Password.check(requestPassword, rehashedCredential)
470473
}
471474
else
472-
/*logger.debug("Line 333: credential: " + requestPassword + " secret: " + storedSecret);*/
473-
Password.check(requestPassword, storedSecret)
475+
/*logger.debug("Line 333: credential: " + requestPassword + " secret: " + storedSecret);*/
476+
Password.check(requestPassword, storedSecret)
474477
})) {
475478
// Root level permissions are used in test environments, any other user should not have root level access in a production environment.
476479
if (resourceIdentityAndCred._1.role == AuthRoles.SuperUser &&
@@ -490,7 +493,22 @@ object ExchangeApiApp extends App
490493
fetchedIdentity <- identityCacheGet(resource)
491494
//_ = logger.debug(s"Async returns: ${a._1.toString}, ${a._1.identifier.getOrElse("None")}")
492495
authenticatedIdentity <- AuthenticationCheck(Option(fetchedIdentity)) fallbackTo(Future{None})
493-
} yield authenticatedIdentity
496+
authenticatedIdentityRetry <-
497+
// On failure of the first attempt clear the cache value for this caller's key and run the two functions again. This will force a database pull.
498+
if (authenticatedIdentity.isEmpty) {
499+
cacheResourceIdentity.remove(resource)
500+
identityCacheGet(resource).flatMap( fetchedIdentity => AuthenticationCheck(Option(fetchedIdentity)) fallbackTo(Future{None}))
501+
}
502+
else
503+
Future { None }
504+
authenticatedIdentityWithCacheInvalidation <-
505+
Future {
506+
if (authenticatedIdentity.isDefined)
507+
authenticatedIdentity
508+
else
509+
authenticatedIdentityRetry
510+
}
511+
} yield authenticatedIdentityWithCacheInvalidation
494512
}
495513
}.flatMap(x => x) // Flattens the nested futures.
496514
case _ =>

0 commit comments

Comments
 (0)