diff --git a/obp-api/src/main/scala/bootstrap/liftweb/Boot.scala b/obp-api/src/main/scala/bootstrap/liftweb/Boot.scala index c6ab440ad..8042e7435 100644 --- a/obp-api/src/main/scala/bootstrap/liftweb/Boot.scala +++ b/obp-api/src/main/scala/bootstrap/liftweb/Boot.scala @@ -611,7 +611,7 @@ class Boot extends MdcLoggable { // Check to see if the user explicitly requests a new locale // In case it's true we use that value to set up a new cookie value - S.param("locale") match { + S.param(PARAM_LOCALE) match { case Full(requestedLocale) if requestedLocale != null => { val computedLocale: Locale = I18NUtil.computeLocale(requestedLocale) val id: Long = AuthUser.getCurrentUser.map(_.user.userPrimaryKey.value).getOrElse(0) diff --git a/obp-api/src/main/scala/code/accountattribute/MappedAccountAttributeProvider.scala b/obp-api/src/main/scala/code/accountattribute/MappedAccountAttributeProvider.scala index 0761b14a1..dc565624c 100644 --- a/obp-api/src/main/scala/code/accountattribute/MappedAccountAttributeProvider.scala +++ b/obp-api/src/main/scala/code/accountattribute/MappedAccountAttributeProvider.scala @@ -1,5 +1,6 @@ package code.accountattribute +import code.api.Constant.{PARAM_LOCALE, PARAM_TIMESTAMP} import code.api.attributedefinition.AttributeDefinition import code.products.MappedProduct import code.util.{AttributeQueryTrait, MappedUUID, UUIDString} @@ -154,11 +155,14 @@ object MappedAccountAttributeProvider extends AccountAttributeProvider { } override def getAccountIdsByParams(bankId: BankId, params: Map[String, List[String]]): Future[Box[List[String]]] = Future { + val paramFiltered = params.filterNot(_._1 == PARAM_TIMESTAMP) // ignore `_timestamp_` parameter, it is for invalid Browser caching + .filterNot(_._1 == PARAM_LOCALE) + Box !! { - if (params.isEmpty) { + if (paramFiltered.isEmpty) { MappedAccountAttribute.findAll(By(MappedAccountAttribute.mBankIdId, bankId.value)).map(_.accountId.value) } else { - val paramList = params.toList + val paramList = paramFiltered.toList.filterNot(_._1 == PARAM_TIMESTAMP).filterNot(_._1 == PARAM_LOCALE) val parameters: List[String] = MappedAccountAttribute.getParameters(paramList) val sqlParametersFilter = MappedAccountAttribute.getSqlParametersFilter(paramList) val accountIdList = paramList.isEmpty match { diff --git a/obp-api/src/main/scala/code/api/OBPRestHelper.scala b/obp-api/src/main/scala/code/api/OBPRestHelper.scala index 7f6ba17d1..226d544bd 100644 --- a/obp-api/src/main/scala/code/api/OBPRestHelper.scala +++ b/obp-api/src/main/scala/code/api/OBPRestHelper.scala @@ -83,7 +83,7 @@ case class APIFailureNewStyle(failMsg: String, val errorCode = extractErrorMessageCode(failMsg) val errorBody = extractErrorMessageBody(failMsg) - val localeUrlParameter = getHttpRequestUrlParam(ccl.map(_.url).getOrElse(""),"locale") + val localeUrlParameter = getHttpRequestUrlParam(ccl.map(_.url).getOrElse(""),PARAM_LOCALE) val localeFromUrl = I18NUtil.computeLocale(localeUrlParameter) diff --git a/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/ResourceDocsAPIMethods.scala b/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/ResourceDocsAPIMethods.scala index 70eb34b50..9c3656608 100644 --- a/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/ResourceDocsAPIMethods.scala +++ b/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/ResourceDocsAPIMethods.scala @@ -1,5 +1,7 @@ package code.api.ResourceDocs1_4_0 +import code.api.Constant.PARAM_LOCALE + import java.util.UUID.randomUUID import code.api.OBPRestHelper import code.api.builder.OBP_APIBuilder @@ -989,7 +991,7 @@ object ResourceDocsAPIMethodsUtil extends MdcLoggable{ // So we can produce a reduced list of resource docs to prevent manual editing of swagger files. val languageParam = for { - x <- S.param("language").or(S.param("locale")) + x <- S.param("language").or(S.param(PARAM_LOCALE)) y <- stringToLanguageParam(x) } yield y logger.info(s"languageParam is $languageParam") diff --git a/obp-api/src/main/scala/code/api/constant/constant.scala b/obp-api/src/main/scala/code/api/constant/constant.scala index f5368643d..98d4b292f 100644 --- a/obp-api/src/main/scala/code/api/constant/constant.scala +++ b/obp-api/src/main/scala/code/api/constant/constant.scala @@ -45,6 +45,8 @@ object Constant extends MdcLoggable { final val OUTGOING_SETTLEMENT_ACCOUNT_ID = "OBP-OUTGOING-SETTLEMENT-ACCOUNT" final val ALL_CONSUMERS = "ALL_CONSUMERS" + final val PARAM_LOCALE = "locale" + final val PARAM_TIMESTAMP = "_timestamp_" } diff --git a/obp-api/src/main/scala/code/api/dynamic/entity/APIMethodsDynamicEntity.scala b/obp-api/src/main/scala/code/api/dynamic/entity/APIMethodsDynamicEntity.scala index 66d39c56b..0e4c938d0 100644 --- a/obp-api/src/main/scala/code/api/dynamic/entity/APIMethodsDynamicEntity.scala +++ b/obp-api/src/main/scala/code/api/dynamic/entity/APIMethodsDynamicEntity.scala @@ -1,6 +1,7 @@ package code.api.dynamic.entity import code.DynamicData.{DynamicData, DynamicDataProvider} +import code.api.Constant.PARAM_LOCALE import code.api.dynamic.endpoint.helper.{DynamicEndpointHelper, MockResponseHolder} import code.api.dynamic.endpoint.helper.DynamicEndpointHelper.DynamicReq import code.api.dynamic.endpoint.helper.MockResponseHolder @@ -65,7 +66,7 @@ trait APIMethodsDynamicEntity { case map if map.isEmpty => resultList case params => val filteredWithFieldValue = resultList.arr.filter { jValue => - params.filter(_._1!="locale").forall { kv => + params.filter(_._1!=PARAM_LOCALE).forall { kv => val (path, values) = kv values.exists(JsonUtils.isFieldEquals(jValue, path, _)) } diff --git a/obp-api/src/main/scala/code/api/util/Glossary.scala b/obp-api/src/main/scala/code/api/util/Glossary.scala index 90030570f..da9761121 100644 --- a/obp-api/src/main/scala/code/api/util/Glossary.scala +++ b/obp-api/src/main/scala/code/api/util/Glossary.scala @@ -1,5 +1,6 @@ package code.api.util +import code.api.Constant.PARAM_LOCALE import code.api.util.APIUtil.{getOAuth2ServerUrl, getObpApiRoot, getServerUrl} import code.api.util.ExampleValue.{accountIdExample, bankIdExample, customerIdExample, userIdExample} import code.util.Helper.MdcLoggable @@ -2126,7 +2127,7 @@ object Glossary extends MdcLoggable { | "picture": "https://lh5.googleusercontent.com/-Xd44hnJ6TDo/AAAAAAAAAAI/AAAAAAAAAAA/AKxrwcadwzhm4N4tWk5E8Avxi-ZK6ks4qg/s96-c/photo.jpg", | "given_name": "Marko", | "family_name": "Milić", -| "locale": "en", +| $PARAM_LOCALE: "en", | "iat": 1547705691, | "exp": 1547709291 | } diff --git a/obp-api/src/main/scala/code/api/util/I18NUtil.scala b/obp-api/src/main/scala/code/api/util/I18NUtil.scala index 0a6191e24..37eec956c 100644 --- a/obp-api/src/main/scala/code/api/util/I18NUtil.scala +++ b/obp-api/src/main/scala/code/api/util/I18NUtil.scala @@ -1,5 +1,7 @@ package code.api.util +import code.api.Constant.PARAM_LOCALE + import java.util.{Date, Locale} import code.webuiprops.MappedWebUiPropsProvider.getWebUiPropsValue @@ -25,7 +27,7 @@ object I18NUtil { def currentLocale() : Locale = { // Cookie name val localeCookieName = "SELECTED_LOCALE" - S.param("locale") match { + S.param(PARAM_LOCALE) match { // 1st choice: Use query parameter as a source of truth if any case Full(requestedLocale) if requestedLocale != null => { val computedLocale = I18NUtil.computeLocale(requestedLocale) diff --git a/obp-api/src/main/scala/code/api/v3_0_0/APIMethods300.scala b/obp-api/src/main/scala/code/api/v3_0_0/APIMethods300.scala index c32750667..36c6c1e27 100644 --- a/obp-api/src/main/scala/code/api/v3_0_0/APIMethods300.scala +++ b/obp-api/src/main/scala/code/api/v3_0_0/APIMethods300.scala @@ -1,10 +1,10 @@ package code.api.v3_0_0 import java.util.regex.Pattern - import code.accountattribute.AccountAttributeX import code.accountholders.AccountHolders import code.api.APIFailureNewStyle +import code.api.Constant.{PARAM_LOCALE, PARAM_TIMESTAMP} import code.api.ResourceDocs1_4_0.SwaggerDefinitionsJSON import code.api.ResourceDocs1_4_0.SwaggerDefinitionsJSON.{bankJSON, banksJSON, branchJsonV300, _} import code.api.util.APIUtil.{getGlossaryItems, _} @@ -501,7 +501,8 @@ trait APIMethods300 { availableBankIdAccountIdList <- Future { Views.views.vend.getAllFirehoseAccounts(bank.bankId).map(a => BankIdAccountId(a.bankId,a.accountId)) } - params = req.params.filterNot(_._1 == "_timestamp_") // ignore `_timestamp_` parameter, it is for invalid Browser caching + params = req.params.filterNot(_._1 == PARAM_TIMESTAMP) // ignore `_timestamp_` parameter, it is for invalid Browser caching + .filterNot(_._1 == PARAM_LOCALE) availableBankIdAccountIdList2 <- if(params.isEmpty) { Future.successful(availableBankIdAccountIdList) } else { @@ -571,18 +572,21 @@ trait APIMethods300 { transactionsJsonV300, List(UserNotLoggedIn, AccountFirehoseNotAllowedOnThisInstance, UserHasMissingRoles, UnknownError), List(apiTagTransaction, apiTagAccountFirehose, apiTagTransactionFirehose, apiTagFirehoseData, apiTagNewStyle), - Some(List(canUseAccountFirehoseAtAnyBank))) + Some(List(canUseAccountFirehoseAtAnyBank, ApiRole.canUseAccountFirehose)) + ) lazy val getFirehoseTransactionsForBankAccount : OBPEndpoint = { //get private accounts for all banks case "banks" :: BankId(bankId):: "firehose" :: "accounts" :: AccountId(accountId) :: "views" :: ViewId(viewId) :: "transactions" :: Nil JsonGet req => { cc => + val allowedEntitlements = canUseAccountFirehoseAtAnyBank :: ApiRole.canUseAccountFirehose :: Nil + val allowedEntitlementsTxt = allowedEntitlements.mkString(" or ") for { (Full(u), callContext) <- authenticatedAccess(cc) _ <- Helper.booleanToFuture(failMsg = AccountFirehoseNotAllowedOnThisInstance , cc=callContext) { allowAccountFirehose } - _ <- NewStyle.function.hasEntitlement("", u.userId, ApiRole.canUseAccountFirehoseAtAnyBank, callContext) + _ <- NewStyle.function.hasAtLeastOneEntitlement(failMsg = UserHasMissingRoles + allowedEntitlementsTxt)(bankId.value, u.userId, allowedEntitlements, callContext) (bank, callContext) <- NewStyle.function.getBank(bankId, callContext) (bankAccount, callContext) <- NewStyle.function.getBankAccount(bankId, accountId, callContext) view <- NewStyle.function.checkViewAccessAndReturnView(viewId, BankIdAccountId(bankAccount.bankId, bankAccount.accountId),Some(u), callContext) diff --git a/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala b/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala index e6e17dec8..2c5a0fb27 100644 --- a/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala +++ b/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala @@ -4,10 +4,10 @@ import java.net.URLEncoder import java.text.SimpleDateFormat import java.util import java.util.{Calendar, Date} - import code.DynamicData.{DynamicData, DynamicDataProvider} import code.DynamicEndpoint.DynamicEndpointSwagger import code.accountattribute.AccountAttributeX +import code.api.Constant.{PARAM_LOCALE, PARAM_TIMESTAMP} import code.api.ResourceDocs1_4_0.SwaggerDefinitionsJSON import code.api.ResourceDocs1_4_0.SwaggerDefinitionsJSON.{jsonDynamicResourceDoc, _} import code.api.UKOpenBanking.v2_0_0.OBP_UKOpenBanking_200 @@ -3447,7 +3447,8 @@ trait APIMethods400 { availableBankIdAccountIdList <- Future { Views.views.vend.getAllFirehoseAccounts(bank.bankId).map(a => BankIdAccountId(a.bankId,a.accountId)) } - params = req.params.filterNot(_._1 == "_timestamp_") // ignore `_timestamp_` parameter, it is for invalid Browser caching + params = req.params.filterNot(_._1 == PARAM_TIMESTAMP) // ignore `_timestamp_` parameter, it is for invalid Browser caching + .filterNot(_._1 == PARAM_LOCALE) availableBankIdAccountIdList2 <- if(params.isEmpty) { Future.successful(availableBankIdAccountIdList) } else { @@ -5156,13 +5157,14 @@ trait APIMethods400 { for { (Full(u), bank, callContext) <- SS.userBank (privateViewsUserCanAccessAtOneBank, privateAccountAccess) = Views.views.vend.privateViewsUserCanAccessAtBank(u, bankId) - params = req.params + params = req.params.filterNot(_._1 == PARAM_TIMESTAMP) // ignore `_timestamp_` parameter, it is for invalid Browser caching + .filterNot(_._1 == PARAM_LOCALE) privateAccountAccess2 <- if(params.isEmpty || privateAccountAccess.isEmpty) { Future.successful(privateAccountAccess) } else { AccountAttributeX.accountAttributeProvider.vend - .getAccountIdsByParams(bankId, req.params) - .map { boxedAccountIds => + .getAccountIdsByParams(bankId, params) + .map { boxedAccountIds => val accountIds = boxedAccountIds.getOrElse(Nil) privateAccountAccess.filter(aa => accountIds.contains(aa.account_id.get)) } diff --git a/obp-api/src/test/scala/code/api/v3_0_0/FirehoseTest.scala b/obp-api/src/test/scala/code/api/v3_0_0/FirehoseTest.scala index bfce847f4..870a6bc44 100644 --- a/obp-api/src/test/scala/code/api/v3_0_0/FirehoseTest.scala +++ b/obp-api/src/test/scala/code/api/v3_0_0/FirehoseTest.scala @@ -2,7 +2,7 @@ package code.api.v3_0_0 import code.api.util.APIUtil.OAuth._ import code.api.util.ApiRole -import code.api.util.ApiRole.CanUseAccountFirehoseAtAnyBank +import code.api.util.ApiRole.{CanUseAccountFirehose, CanUseAccountFirehoseAtAnyBank} import code.api.util.ErrorMessages.AccountFirehoseNotAllowedOnThisInstance import code.api.v3_0_0.OBPAPI3_0_0.Implementations3_0_0 import code.entitlement.Entitlement @@ -83,6 +83,18 @@ class FirehoseTest extends V300ServerSetup with PropsReset{ response.code should equal(200) response.body.extract[ModeratedCoreAccountsJsonV300] } + + scenario("We will call the endpoint with user credentials - bank level role", VersionOfApi, ApiEndpoint4) { + setPropsValues("allow_account_firehose" -> "true") + setPropsValues("enable.force_error" -> "true") + Entitlement.entitlement.vend.addEntitlement(testBankId1.value, resourceUser1.userId, ApiRole.CanUseAccountFirehose.toString) + When("We send the request") + val request = (v3_0Request / "banks" / testBankId1.value / "firehose" / "accounts" / testAccountId1.value / "views" / "owner" / "transactions").GET <@ (user1) + val response = makeGetRequest(request) + Then("We should get a 200 and check the response body") + response.code should equal(200) + response.body.extract[ModeratedCoreAccountsJsonV300] + } scenario("We will call the endpoint with user credentials, props alias", VersionOfApi, ApiEndpoint4) { setPropsValues("allow_firehose_views" -> "true") @@ -104,6 +116,7 @@ class FirehoseTest extends V300ServerSetup with PropsReset{ Then("We should get a 403 and check the response body") response.code should equal(403) response.body.toString contains (CanUseAccountFirehoseAtAnyBank.toString()) should be(true) + response.body.toString contains (CanUseAccountFirehose.toString()) should be(true) } scenario("We will call the endpoint missing props ", VersionOfApi, ApiEndpoint4) { diff --git a/obp-api/src/test/scala/code/api/v4_0_0/FirehoseTest.scala b/obp-api/src/test/scala/code/api/v4_0_0/FirehoseTest.scala index 14d5a8755..cb2e3bed8 100644 --- a/obp-api/src/test/scala/code/api/v4_0_0/FirehoseTest.scala +++ b/obp-api/src/test/scala/code/api/v4_0_0/FirehoseTest.scala @@ -1,5 +1,6 @@ package code.api.v4_0_0 +import code.api.Constant.{PARAM_LOCALE, PARAM_TIMESTAMP} import code.api.util.APIUtil.OAuth._ import code.api.util.ApiRole import code.api.util.ApiRole.CanUseAccountFirehoseAtAnyBank @@ -64,6 +65,46 @@ class FirehoseTest extends V400ServerSetup with PropsReset{ response.code should equal(400) response.body.toString contains (AccountFirehoseNotAllowedOnThisInstance) should be (true) } + + scenario("We will test the endpoint URL Params", VersionOfApi, ApiEndpoint1) { + setPropsValues("allow_account_firehose" -> "true") + Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, ApiRole.CanUseAccountFirehoseAtAnyBank.toString) + When("We send the request") + val request = (v4_0_0_Request / "banks" / testBankId1.value / "firehose" / "accounts" / "views" / "firehose").GET <@ (user1) + val response = makeGetRequest(request) + Then("We should get a 200 and check the response body") + response.code should equal(200) + val accounts = response.body.extract[ModeratedFirehoseAccountsJsonV400] + accounts.accounts.length > (0) shouldBe(true) + + { + val request = (v4_0_0_Request / "banks" / testBankId1.value / "firehose" / "accounts" / "views" / "firehose").GET <@ (user1) < (0) shouldBe (true) + } + + { + val request = (v4_0_0_Request / "banks" / testBankId1.value / "firehose" / "accounts" / "views" / "firehose").GET <@ (user1) < (0) shouldBe (true) + } + } + } feature(s"test $ApiEndpoint2 version $VersionOfApi - Authorized access") {