From 415e22f5a217f0aae8ea1656bc2ccbd6f4ab9bb9 Mon Sep 17 00:00:00 2001 From: simonredfern Date: Mon, 5 Jan 2026 23:47:21 +0100 Subject: [PATCH] Log cache separate endpoints and different Role names --- .../scala/code/api/cache/RedisLogger.scala | 12 +- .../main/scala/code/api/util/ApiRole.scala | 36 +-- .../scala/code/api/v5_1_0/APIMethods510.scala | 213 +++++++++++++++--- .../api/v5_1_0/LogCacheEndpointTest.scala | 74 +++--- 4 files changed, 236 insertions(+), 99 deletions(-) diff --git a/obp-api/src/main/scala/code/api/cache/RedisLogger.scala b/obp-api/src/main/scala/code/api/cache/RedisLogger.scala index ee9b58c3a..02db0209c 100644 --- a/obp-api/src/main/scala/code/api/cache/RedisLogger.scala +++ b/obp-api/src/main/scala/code/api/cache/RedisLogger.scala @@ -73,12 +73,12 @@ object RedisLogger { /** Map a LogLevel to its required entitlements */ def requiredRoles(level: LogLevel): List[ApiRole] = level match { - case TRACE => List(canGetTraceLevelLogsAtAllBanks, canGetAllLevelLogsAtAllBanks) - case DEBUG => List(canGetDebugLevelLogsAtAllBanks, canGetAllLevelLogsAtAllBanks) - case INFO => List(canGetInfoLevelLogsAtAllBanks, canGetAllLevelLogsAtAllBanks) - case WARNING => List(canGetWarningLevelLogsAtAllBanks, canGetAllLevelLogsAtAllBanks) - case ERROR => List(canGetErrorLevelLogsAtAllBanks, canGetAllLevelLogsAtAllBanks) - case ALL => List(canGetAllLevelLogsAtAllBanks) + case TRACE => List(canGetSystemLogCacheTrace, canGetSystemLogCacheAll) + case DEBUG => List(canGetSystemLogCacheDebug, canGetSystemLogCacheAll) + case INFO => List(canGetSystemLogCacheInfo, canGetSystemLogCacheAll) + case WARNING => List(canGetSystemLogCacheWarning, canGetSystemLogCacheAll) + case ERROR => List(canGetSystemLogCacheError, canGetSystemLogCacheAll) + case ALL => List(canGetSystemLogCacheAll) } } diff --git a/obp-api/src/main/scala/code/api/util/ApiRole.scala b/obp-api/src/main/scala/code/api/util/ApiRole.scala index c025fe7c2..9c7a990be 100644 --- a/obp-api/src/main/scala/code/api/util/ApiRole.scala +++ b/obp-api/src/main/scala/code/api/util/ApiRole.scala @@ -107,35 +107,23 @@ object ApiRole extends MdcLoggable{ // TRACE - case class CanGetTraceLevelLogsAtOneBank(requiresBankId: Boolean = true) extends ApiRole - lazy val canGetTraceLevelLogsAtOneBank = CanGetTraceLevelLogsAtOneBank() - case class CanGetTraceLevelLogsAtAllBanks(requiresBankId: Boolean = false) extends ApiRole - lazy val canGetTraceLevelLogsAtAllBanks = CanGetTraceLevelLogsAtAllBanks() + case class CanGetSystemLogCacheTrace(requiresBankId: Boolean = false) extends ApiRole + lazy val canGetSystemLogCacheTrace = CanGetSystemLogCacheTrace() // DEBUG - case class CanGetDebugLevelLogsAtOneBank(requiresBankId: Boolean = true) extends ApiRole - lazy val canGetDebugLevelLogsAtOneBank = CanGetDebugLevelLogsAtOneBank() - case class CanGetDebugLevelLogsAtAllBanks(requiresBankId: Boolean = false) extends ApiRole - lazy val canGetDebugLevelLogsAtAllBanks = CanGetDebugLevelLogsAtAllBanks() + case class CanGetSystemLogCacheDebug(requiresBankId: Boolean = false) extends ApiRole + lazy val canGetSystemLogCacheDebug = CanGetSystemLogCacheDebug() // INFO - case class CanGetInfoLevelLogsAtOneBank(requiresBankId: Boolean = true) extends ApiRole - lazy val canGetInfoLevelLogsAtOneBank = CanGetInfoLevelLogsAtOneBank() - case class CanGetInfoLevelLogsAtAllBanks(requiresBankId: Boolean = false) extends ApiRole - lazy val canGetInfoLevelLogsAtAllBanks = CanGetInfoLevelLogsAtAllBanks() + case class CanGetSystemLogCacheInfo(requiresBankId: Boolean = false) extends ApiRole + lazy val canGetSystemLogCacheInfo = CanGetSystemLogCacheInfo() // WARNING - case class CanGetWarningLevelLogsAtOneBank(requiresBankId: Boolean = true) extends ApiRole - lazy val canGetWarningLevelLogsAtOneBank = CanGetWarningLevelLogsAtOneBank() - case class CanGetWarningLevelLogsAtAllBanks(requiresBankId: Boolean = false) extends ApiRole - lazy val canGetWarningLevelLogsAtAllBanks = CanGetWarningLevelLogsAtAllBanks() + case class CanGetSystemLogCacheWarning(requiresBankId: Boolean = false) extends ApiRole + lazy val canGetSystemLogCacheWarning = CanGetSystemLogCacheWarning() // ERROR - case class CanGetErrorLevelLogsAtOneBank(requiresBankId: Boolean = true) extends ApiRole - lazy val canGetErrorLevelLogsAtOneBank = CanGetErrorLevelLogsAtOneBank() - case class CanGetErrorLevelLogsAtAllBanks(requiresBankId: Boolean = false) extends ApiRole - lazy val canGetErrorLevelLogsAtAllBanks = CanGetErrorLevelLogsAtAllBanks() + case class CanGetSystemLogCacheError(requiresBankId: Boolean = false) extends ApiRole + lazy val canGetSystemLogCacheError = CanGetSystemLogCacheError() // ALL - case class CanGetAllLevelLogsAtOneBank(requiresBankId: Boolean = true) extends ApiRole - lazy val canGetAllLevelLogsAtOneBank = CanGetAllLevelLogsAtOneBank() - case class CanGetAllLevelLogsAtAllBanks(requiresBankId: Boolean = false) extends ApiRole - lazy val canGetAllLevelLogsAtAllBanks = CanGetAllLevelLogsAtAllBanks() + case class CanGetSystemLogCacheAll(requiresBankId: Boolean = false) extends ApiRole + lazy val canGetSystemLogCacheAll = CanGetSystemLogCacheAll() case class CanUpdateAgentStatusAtAnyBank(requiresBankId: Boolean = false) extends ApiRole lazy val canUpdateAgentStatusAtAnyBank = CanUpdateAgentStatusAtAnyBank() diff --git a/obp-api/src/main/scala/code/api/v5_1_0/APIMethods510.scala b/obp-api/src/main/scala/code/api/v5_1_0/APIMethods510.scala index 7357f474f..8f8968e45 100644 --- a/obp-api/src/main/scala/code/api/v5_1_0/APIMethods510.scala +++ b/obp-api/src/main/scala/code/api/v5_1_0/APIMethods510.scala @@ -238,55 +238,204 @@ trait APIMethods510 { } } + // Helper function to avoid code duplication + private def getLogCacheHelper(level: RedisLogger.LogLevel.Value, cc: CallContext): Future[(RedisLogger.LogTail, Option[CallContext])] = { + implicit val ec = EndpointContext(Some(cc)) + for { + httpParams <- NewStyle.function.extractHttpParamsFromUrl(cc.url) + (obpQueryParams, callContext) <- createQueriesByHttpParamsFuture(httpParams, cc.callContext) + limit = obpQueryParams.collectFirst { case OBPLimit(value) => value } + offset = obpQueryParams.collectFirst { case OBPOffset(value) => value } + logs <- Future(RedisLogger.getLogTail(level, limit, offset)) + } yield { + (logs, HttpCode.`200`(callContext)) + } + } + staticResourceDocs += ResourceDoc( - logCacheEndpoint, + logCacheTraceEndpoint, implementedInApiVersion, - nameOf(logCacheEndpoint), + nameOf(logCacheTraceEndpoint), "GET", - "/system/log-cache/LOG_LEVEL", - "Get Log Cache", - """Returns information about: - | - |* Log Cache + "/system/log-cache/trace", + "Get Trace Level Log Cache", + """Returns TRACE level logs from the system log cache. | |This endpoint supports pagination via the following optional query parameters: |* limit - Maximum number of log entries to return |* offset - Number of log entries to skip (for pagination) | - |Example: GET /system/log-cache/INFO?limit=50&offset=100 + |Example: GET /system/log-cache/trace?limit=50&offset=100 """, EmptyBody, EmptyBody, List($UserNotLoggedIn, UnknownError), apiTagSystem :: apiTagApi :: Nil, - Some(List(canGetAllLevelLogsAtAllBanks))) + Some(List(canGetSystemLogCacheTrace, canGetSystemLogCacheAll))) - lazy val logCacheEndpoint: OBPEndpoint = { - case "system" :: "log-cache" :: logLevel :: Nil JsonGet _ => + lazy val logCacheTraceEndpoint: OBPEndpoint = { + case "system" :: "log-cache" :: "trace" :: Nil JsonGet _ => cc => implicit val ec = EndpointContext(Some(cc)) for { - // Parse and validate log level - level <- NewStyle.function.tryons(ErrorMessages.invalidLogLevel, 400, cc.callContext) { - RedisLogger.LogLevel.valueOf(logLevel) - } - // Check entitlements using helper - _ <- NewStyle.function.handleEntitlementsAndScopes( - bankId = "", - userId = cc.userId, - roles = RedisLogger.LogLevel.requiredRoles(level), - callContext = cc.callContext - ) - httpParams <- NewStyle.function.extractHttpParamsFromUrl(cc.url) - (obpQueryParams, callContext) <- createQueriesByHttpParamsFuture(httpParams, cc.callContext) - // Extract limit and offset from query parameters - limit = obpQueryParams.collectFirst { case OBPLimit(value) => value } - offset = obpQueryParams.collectFirst { case OBPOffset(value) => value } - // Fetch logs with pagination - logs <- Future(RedisLogger.getLogTail(level, limit, offset)) - } yield { - (logs, HttpCode.`200`(cc.callContext)) - } + _ <- NewStyle.function.handleEntitlementsAndScopes("", cc.userId, List(canGetSystemLogCacheTrace, canGetSystemLogCacheAll), cc.callContext) + result <- getLogCacheHelper(RedisLogger.LogLevel.TRACE, cc) + } yield result + } + + staticResourceDocs += ResourceDoc( + logCacheDebugEndpoint, + implementedInApiVersion, + nameOf(logCacheDebugEndpoint), + "GET", + "/system/log-cache/debug", + "Get Debug Level Log Cache", + """Returns DEBUG level logs from the system log cache. + | + |This endpoint supports pagination via the following optional query parameters: + |* limit - Maximum number of log entries to return + |* offset - Number of log entries to skip (for pagination) + | + |Example: GET /system/log-cache/debug?limit=50&offset=100 + """, + EmptyBody, + EmptyBody, + List($UserNotLoggedIn, UnknownError), + apiTagSystem :: apiTagApi :: Nil, + Some(List(canGetSystemLogCacheDebug, canGetSystemLogCacheAll))) + + lazy val logCacheDebugEndpoint: OBPEndpoint = { + case "system" :: "log-cache" :: "debug" :: Nil JsonGet _ => + cc => + implicit val ec = EndpointContext(Some(cc)) + for { + _ <- NewStyle.function.handleEntitlementsAndScopes("", cc.userId, List(canGetSystemLogCacheDebug, canGetSystemLogCacheAll), cc.callContext) + result <- getLogCacheHelper(RedisLogger.LogLevel.DEBUG, cc) + } yield result + } + + staticResourceDocs += ResourceDoc( + logCacheInfoEndpoint, + implementedInApiVersion, + nameOf(logCacheInfoEndpoint), + "GET", + "/system/log-cache/info", + "Get Info Level Log Cache", + """Returns INFO level logs from the system log cache. + | + |This endpoint supports pagination via the following optional query parameters: + |* limit - Maximum number of log entries to return + |* offset - Number of log entries to skip (for pagination) + | + |Example: GET /system/log-cache/info?limit=50&offset=100 + """, + EmptyBody, + EmptyBody, + List($UserNotLoggedIn, UnknownError), + apiTagSystem :: apiTagApi :: Nil, + Some(List(canGetSystemLogCacheInfo, canGetSystemLogCacheAll))) + + lazy val logCacheInfoEndpoint: OBPEndpoint = { + case "system" :: "log-cache" :: "info" :: Nil JsonGet _ => + cc => + implicit val ec = EndpointContext(Some(cc)) + for { + _ <- NewStyle.function.handleEntitlementsAndScopes("", cc.userId, List(canGetSystemLogCacheInfo, canGetSystemLogCacheAll), cc.callContext) + result <- getLogCacheHelper(RedisLogger.LogLevel.INFO, cc) + } yield result + } + + staticResourceDocs += ResourceDoc( + logCacheWarningEndpoint, + implementedInApiVersion, + nameOf(logCacheWarningEndpoint), + "GET", + "/system/log-cache/warning", + "Get Warning Level Log Cache", + """Returns WARNING level logs from the system log cache. + | + |This endpoint supports pagination via the following optional query parameters: + |* limit - Maximum number of log entries to return + |* offset - Number of log entries to skip (for pagination) + | + |Example: GET /system/log-cache/warning?limit=50&offset=100 + """, + EmptyBody, + EmptyBody, + List($UserNotLoggedIn, UnknownError), + apiTagSystem :: apiTagApi :: Nil, + Some(List(canGetSystemLogCacheWarning, canGetSystemLogCacheAll))) + + lazy val logCacheWarningEndpoint: OBPEndpoint = { + case "system" :: "log-cache" :: "warning" :: Nil JsonGet _ => + cc => + implicit val ec = EndpointContext(Some(cc)) + for { + _ <- NewStyle.function.handleEntitlementsAndScopes("", cc.userId, List(canGetSystemLogCacheWarning, canGetSystemLogCacheAll), cc.callContext) + result <- getLogCacheHelper(RedisLogger.LogLevel.WARNING, cc) + } yield result + } + + staticResourceDocs += ResourceDoc( + logCacheErrorEndpoint, + implementedInApiVersion, + nameOf(logCacheErrorEndpoint), + "GET", + "/system/log-cache/error", + "Get Error Level Log Cache", + """Returns ERROR level logs from the system log cache. + | + |This endpoint supports pagination via the following optional query parameters: + |* limit - Maximum number of log entries to return + |* offset - Number of log entries to skip (for pagination) + | + |Example: GET /system/log-cache/error?limit=50&offset=100 + """, + EmptyBody, + EmptyBody, + List($UserNotLoggedIn, UnknownError), + apiTagSystem :: apiTagApi :: Nil, + Some(List(canGetSystemLogCacheError, canGetSystemLogCacheAll))) + + lazy val logCacheErrorEndpoint: OBPEndpoint = { + case "system" :: "log-cache" :: "error" :: Nil JsonGet _ => + cc => + implicit val ec = EndpointContext(Some(cc)) + for { + _ <- NewStyle.function.handleEntitlementsAndScopes("", cc.userId, List(canGetSystemLogCacheError, canGetSystemLogCacheAll), cc.callContext) + result <- getLogCacheHelper(RedisLogger.LogLevel.ERROR, cc) + } yield result + } + + staticResourceDocs += ResourceDoc( + logCacheAllEndpoint, + implementedInApiVersion, + nameOf(logCacheAllEndpoint), + "GET", + "/system/log-cache/all", + "Get All Level Log Cache", + """Returns logs of all levels from the system log cache. + | + |This endpoint supports pagination via the following optional query parameters: + |* limit - Maximum number of log entries to return + |* offset - Number of log entries to skip (for pagination) + | + |Example: GET /system/log-cache/all?limit=50&offset=100 + """, + EmptyBody, + EmptyBody, + List($UserNotLoggedIn, UnknownError), + apiTagSystem :: apiTagApi :: Nil, + Some(List(canGetSystemLogCacheAll))) + + lazy val logCacheAllEndpoint: OBPEndpoint = { + case "system" :: "log-cache" :: "all" :: Nil JsonGet _ => + cc => + implicit val ec = EndpointContext(Some(cc)) + for { + _ <- NewStyle.function.handleEntitlementsAndScopes("", cc.userId, List(canGetSystemLogCacheAll), cc.callContext) + result <- getLogCacheHelper(RedisLogger.LogLevel.ALL, cc) + } yield result } diff --git a/obp-api/src/test/scala/code/api/v5_1_0/LogCacheEndpointTest.scala b/obp-api/src/test/scala/code/api/v5_1_0/LogCacheEndpointTest.scala index 70f1a8e56..690464e06 100644 --- a/obp-api/src/test/scala/code/api/v5_1_0/LogCacheEndpointTest.scala +++ b/obp-api/src/test/scala/code/api/v5_1_0/LogCacheEndpointTest.scala @@ -1,7 +1,7 @@ package code.api.v5_1_0 import code.api.util.APIUtil.OAuth._ -import code.api.util.ApiRole.CanGetAllLevelLogsAtAllBanks +import code.api.util.ApiRole.CanGetSystemLogCacheAll import code.api.util.ErrorMessages.{UserHasMissingRoles, UserNotLoggedIn} import code.api.v5_1_0.OBPAPI5_1_0.Implementations5_1_0 import code.entitlement.Entitlement @@ -21,12 +21,12 @@ class LogCacheEndpointTest extends V510ServerSetup { * This is made possible by the scalatest maven plugin */ object VersionOfApi extends Tag(ApiVersion.v5_1_0.toString) - object ApiEndpoint1 extends Tag(nameOf(Implementations5_1_0.logCacheEndpoint)) + object ApiEndpoint1 extends Tag(nameOf(Implementations5_1_0.logCacheInfoEndpoint)) feature(s"test $ApiEndpoint1 version $VersionOfApi - Unauthorized access") { scenario("We will call the endpoint without user credentials", ApiEndpoint1, VersionOfApi) { When("We make a request v5.1.0") - val request = (v5_1_0_Request / "system" / "log-cache" / "INFO").GET + val request = (v5_1_0_Request / "system" / "log-cache" / "info").GET val response = makeGetRequest(request) Then("We should get a 401") response.code should equal(401) @@ -37,21 +37,21 @@ class LogCacheEndpointTest extends V510ServerSetup { feature(s"test $ApiEndpoint1 version $VersionOfApi - Missing entitlement") { scenario("We will call the endpoint with user credentials but without proper entitlement", ApiEndpoint1, VersionOfApi) { When("We make a request v5.1.0") - val request = (v5_1_0_Request / "system" / "log-cache" / "INFO").GET <@(user1) + val request = (v5_1_0_Request / "system" / "log-cache" / "info").GET <@(user1) val response = makeGetRequest(request) - Then("error should be " + UserHasMissingRoles + CanGetAllLevelLogsAtAllBanks) + Then("error should be " + UserHasMissingRoles + CanGetSystemLogCacheAll) response.code should equal(403) - response.body.extract[ErrorMessage].message should be(UserHasMissingRoles + CanGetAllLevelLogsAtAllBanks) + response.body.extract[ErrorMessage].message should be(UserHasMissingRoles + CanGetSystemLogCacheAll) } } feature(s"test $ApiEndpoint1 version $VersionOfApi - Authorized access without pagination") { scenario("We get log cache without pagination parameters", ApiEndpoint1, VersionOfApi) { Given("We have a user with proper entitlement") - Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, CanGetAllLevelLogsAtAllBanks.toString) + Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, CanGetSystemLogCacheAll.toString) When("We make a request to get log cache") - val request = (v5_1_0_Request / "system" / "log-cache" / "INFO").GET <@(user1) + val request = (v5_1_0_Request / "system" / "log-cache" / "info").GET <@(user1) val response = makeGetRequest(request) Then("We should get a successful response") @@ -66,10 +66,10 @@ class LogCacheEndpointTest extends V510ServerSetup { feature(s"test $ApiEndpoint1 version $VersionOfApi - Authorized access with limit parameter") { scenario("We get log cache with limit parameter only", ApiEndpoint1, VersionOfApi) { Given("We have a user with proper entitlement") - Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, CanGetAllLevelLogsAtAllBanks.toString) + Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, CanGetSystemLogCacheAll.toString) When("We make a request with limit parameter") - val request = (v5_1_0_Request / "system" / "log-cache" / "INFO").GET <@(user1) < val request = (v5_1_0_Request / "system" / "log-cache" / logLevel).GET <@(user1) <