diff --git a/obp-api/src/main/scala/code/api/util/http4s/ResourceDocMiddleware.scala b/obp-api/src/main/scala/code/api/util/http4s/ResourceDocMiddleware.scala index cee52b861..78e946fb0 100644 --- a/obp-api/src/main/scala/code/api/util/http4s/ResourceDocMiddleware.scala +++ b/obp-api/src/main/scala/code/api/util/http4s/ResourceDocMiddleware.scala @@ -2,13 +2,15 @@ package code.api.util.http4s import cats.data.{EitherT, Kleisli, OptionT} import cats.effect._ +import code.api.v7_0_0.Http4s700 import code.api.APIFailureNewStyle import code.api.util.APIUtil.ResourceDoc import code.api.util.ErrorMessages._ import code.api.util.newstyle.ViewNewStyle -import code.api.util.{APIUtil, CallContext, NewStyle} +import code.api.util.{APIUtil, ApiRole, CallContext, NewStyle} import code.util.Helper.MdcLoggable import com.openbankproject.commons.model._ +import com.github.dwickern.macros.NameOf.nameOf import net.liftweb.common.{Box, Empty, Full} import org.http4s._ import org.http4s.headers.`Content-Type` @@ -70,7 +72,7 @@ object ResourceDocMiddleware extends MdcLoggable { * - Special case: resource-docs endpoint checks resource_docs_requires_role property */ private def needsAuthentication(resourceDoc: ResourceDoc): Boolean = { - if (resourceDoc.partialFunctionName == "getResourceDocsObpV700") { + if (resourceDoc.partialFunctionName == nameOf(Http4s700.Implementations7_0_0.getResourceDocsObpV700)) { APIUtil.getPropsAsBoolValue("resource_docs_requires_role", false) } else { resourceDoc.errorResponseBodies.contains($AuthenticatedUserIsRequired) || resourceDoc.roles.exists(_.nonEmpty) @@ -172,7 +174,14 @@ object ResourceDocMiddleware extends MdcLoggable { private def authorizeRoles(resourceDoc: ResourceDoc, pathParams: Map[String, String], ctx: ValidationContext): Validation[ValidationContext] = { import DSL._ - resourceDoc.roles match { + val rolesToCheck: Option[List[ApiRole]] = + if (resourceDoc.partialFunctionName == nameOf(Http4s700.Implementations7_0_0.getResourceDocsObpV700) && APIUtil.getPropsAsBoolValue("resource_docs_requires_role", false)) { + Some(List(ApiRole.canReadResourceDoc)) + } else { + resourceDoc.roles + } + + rolesToCheck match { case Some(roles) if roles.nonEmpty => ctx.user match { case Full(user) => diff --git a/obp-api/src/main/scala/code/api/v7_0_0/Http4s700.scala b/obp-api/src/main/scala/code/api/v7_0_0/Http4s700.scala index 16c6fc61e..229c61027 100644 --- a/obp-api/src/main/scala/code/api/v7_0_0/Http4s700.scala +++ b/obp-api/src/main/scala/code/api/v7_0_0/Http4s700.scala @@ -6,12 +6,12 @@ import code.api.Constant._ import code.api.ResourceDocs1_4_0.SwaggerDefinitionsJSON._ import code.api.ResourceDocs1_4_0.{ResourceDocs140, ResourceDocsAPIMethodsUtil} import code.api.util.APIUtil.{EmptyBody, _} -import code.api.util.ApiRole.{canGetCardsForBank, canReadResourceDoc} +import code.api.util.ApiRole.canGetCardsForBank import code.api.util.ApiTag._ import code.api.util.ErrorMessages._ -import code.api.util.http4s.{ErrorResponseConverter, Http4sRequestAttributes, ResourceDocMiddleware} +import code.api.util.http4s.{Http4sRequestAttributes, ResourceDocMiddleware} import code.api.util.http4s.Http4sRequestAttributes.{RequestOps, EndpointHelpers} -import code.api.util.{ApiRole, ApiVersionUtils, CallContext, CustomJsonFormats, NewStyle} +import code.api.util.{ApiVersionUtils, CallContext, CustomJsonFormats, NewStyle} import code.api.v1_3_0.JSONFactory1_3_0 import code.api.v1_4_0.JSONFactory1_4_0 import code.api.v4_0_0.JSONFactory400 @@ -201,62 +201,25 @@ object Http4s700 { val getResourceDocsObpV700: HttpRoutes[IO] = HttpRoutes.of[IO] { case req @ GET -> `prefixPath` / "resource-docs" / requestedApiVersionString / "obp" => - implicit val cc: CallContext = req.callContext - val resultF: Future[String] = { - val resourceDocsRequireRole = getPropsAsBoolValue("resource_docs_requires_role", false) + EndpointHelpers.executeAndRespond(req) { _ => + val queryParams = req.uri.query.multiParams + val tags = queryParams + .get("tags") + .map(_.flatMap(_.split(",").toList).map(_.trim).filter(_.nonEmpty).map(ResourceDocTag(_)).toList) + val functions = queryParams + .get("functions") + .map(_.flatMap(_.split(",").toList).map(_.trim).filter(_.nonEmpty).toList) + val localeParam = queryParams + .get("locale") + .flatMap(_.headOption) + .orElse(queryParams.get("language").flatMap(_.headOption)) + .map(_.trim) + .filter(_.nonEmpty) for { - (boxUser, cc1) <- if (resourceDocsRequireRole) - authenticatedAccess(cc) - else - anonymousAccess(cc) - - _ <- if (resourceDocsRequireRole) { - NewStyle.function.hasAtLeastOneEntitlement( - failMsg = UserHasMissingRoles + canReadResourceDoc.toString - )("", boxUser.map(_.userId).getOrElse(""), ApiRole.canReadResourceDoc :: Nil, cc1) - } else { - Future.successful(()) - } - - queryParams = req.uri.query.multiParams - tags = queryParams - .get("tags") - .map(_.flatMap(_.split(",").toList).map(_.trim).filter(_.nonEmpty).map(ResourceDocTag(_)).toList) - functions = queryParams - .get("functions") - .map(_.flatMap(_.split(",").toList).map(_.trim).filter(_.nonEmpty).toList) - localeParam = queryParams - .get("locale") - .flatMap(_.headOption) - .orElse(queryParams.get("language").flatMap(_.headOption)) - .map(_.trim) - .filter(_.nonEmpty) - contentParam = queryParams - .get("content") - .flatMap(_.headOption) - .map(_.trim) - .flatMap(ResourceDocsAPIMethodsUtil.stringToContentParam) requestedApiVersion <- Future(ApiVersionUtils.valueOf(requestedApiVersionString)) resourceDocs = ResourceDocs140.ImplementationsResourceDocs.getResourceDocsList(requestedApiVersion).getOrElse(Nil) filteredDocs = ResourceDocsAPIMethodsUtil.filterResourceDocs(resourceDocs, tags, functions) - resourceDocsJson = JSONFactory1_4_0.createResourceDocsJson(filteredDocs, isVersion4OrHigher = true, localeParam) - } yield convertAnyToJsonString(resourceDocsJson) - } - - IO.fromFuture(IO(resultF)).attempt.flatMap { - case Right(result) => Ok(result) - case Left(e) => - val (code, msg) = try { - import net.liftweb.json._ - implicit val formats = net.liftweb.json.DefaultFormats - val json = parse(e.getMessage) - val failCode = (json \ "failCode").extractOpt[Int].getOrElse(400) - val failMsg = (json \ "failMsg").extractOpt[String].getOrElse(UnknownError) - (failCode, failMsg) - } catch { - case _: Exception => (500, UnknownError) - } - ErrorResponseConverter.createErrorResponse(code, msg, cc) + } yield JSONFactory1_4_0.createResourceDocsJson(filteredDocs, isVersion4OrHigher = true, localeParam) } } diff --git a/obp-api/src/test/scala/code/api/v7_0_0/Http4s700RoutesTest.scala b/obp-api/src/test/scala/code/api/v7_0_0/Http4s700RoutesTest.scala index 93d5f5a9d..c43070d73 100644 --- a/obp-api/src/test/scala/code/api/v7_0_0/Http4s700RoutesTest.scala +++ b/obp-api/src/test/scala/code/api/v7_0_0/Http4s700RoutesTest.scala @@ -3,7 +3,7 @@ package code.api.v7_0_0 import cats.effect.IO import cats.effect.unsafe.implicits.global import code.api.util.ApiRole.{canGetCardsForBank, canReadResourceDoc} -import code.api.util.ErrorMessages.{AuthenticatedUserIsRequired, BankNotFound} +import code.api.util.ErrorMessages.{AuthenticatedUserIsRequired, BankNotFound, UserHasMissingRoles} import code.setup.ServerSetupWithTestData import net.liftweb.json.JValue import net.liftweb.json.JsonAST.{JArray, JField, JObject, JString} @@ -180,10 +180,22 @@ class Http4s700RoutesTest extends ServerSetupWithTestData { val request = withDirectLoginToken(baseRequest, token1.value) When("Running through wrapped routes") - val (status, _) = runAndParseJson(request) + val (status, json) = runAndParseJson(request) Then("Response is 403 Forbidden") status.code shouldBe 403 + json match { + case JObject(fields) => + toFieldMap(fields).get("message") match { + case Some(JString(message)) => + message should include(UserHasMissingRoles) + message should include(canGetCardsForBank.toString) + case _ => + fail("Expected message field as JSON string for missing-role response") + } + case _ => + fail("Expected JSON object for missing-role response") + } } scenario("Return BankNotFound when bank does not exist and user is entitled", Http4s700RoutesTag) { @@ -280,10 +292,22 @@ class Http4s700RoutesTest extends ServerSetupWithTestData { val request = withDirectLoginToken(baseRequest, token1.value) When("Running through wrapped routes") - val (status, _) = runAndParseJson(request) + val (status, json) = runAndParseJson(request) - Then("Response is 400 Bad Request") - status.code shouldBe 400 + Then("Response is 403 Forbidden") + status.code shouldBe 403 + json match { + case JObject(fields) => + toFieldMap(fields).get("message") match { + case Some(JString(message)) => + message should include(UserHasMissingRoles) + message should include(canReadResourceDoc.toString) + case _ => + fail("Expected message field as JSON string for missing-role response") + } + case _ => + fail("Expected JSON object for missing-role response") + } } scenario("Return docs when authenticated and entitled with canReadResourceDoc", Http4s700RoutesTag) {