From dd257c12d4588d81238c7f393225bc6607294527 Mon Sep 17 00:00:00 2001 From: hongwei Date: Tue, 2 Apr 2024 09:28:18 +0200 Subject: [PATCH 01/11] refactor/tweaked the error code --- obp-api/src/main/scala/code/api/util/ErrorMessages.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/obp-api/src/main/scala/code/api/util/ErrorMessages.scala b/obp-api/src/main/scala/code/api/util/ErrorMessages.scala index 5d4cb704b..8b76d7948 100644 --- a/obp-api/src/main/scala/code/api/util/ErrorMessages.scala +++ b/obp-api/src/main/scala/code/api/util/ErrorMessages.scala @@ -195,7 +195,7 @@ object ErrorMessages { s"if target viewId is custom view, the current view.can_grant_access_to_custom_views is false." val UserLacksPermissionCanRevokeAccessToViewForTargetAccount = - s"OBP-20047: If target viewId is system view, the current view.can_revoke_access_to_views does not contains it. Or" + + s"OBP-20048: If target viewId is system view, the current view.can_revoke_access_to_views does not contains it. Or" + s"if target viewId is custom view, the current view.can_revoke_access_to_custom_views is false." val UserNotSuperAdmin = "OBP-20050: Current User is not a Super Admin!" From ef3528bb1d52653f0f55af37da321aeaa6bd4f96 Mon Sep 17 00:00:00 2001 From: hongwei Date: Tue, 2 Apr 2024 15:45:40 +0200 Subject: [PATCH 02/11] refactor/seperate the checkSystemViewIdOrName and checkCustomViewIdOrName methods --- .../main/scala/code/api/util/APIUtil.scala | 89 +++++++++++-------- .../scala/code/api/v1_2_1/APIMethods121.scala | 2 +- .../scala/code/api/v2_2_0/APIMethods220.scala | 2 +- .../scala/code/api/v3_0_0/APIMethods300.scala | 2 +- .../scala/code/api/v3_1_0/APIMethods310.scala | 2 +- .../scala/code/api/v5_0_0/APIMethods500.scala | 2 +- .../code/api/v5_0_0/JSONFactory5.0.0.scala | 4 +- .../main/scala/code/views/MapperViews.scala | 6 +- .../code/views/system/ViewDefinition.scala | 17 ++-- .../scala/code/api/v1_2_1/API1_2_1Test.scala | 4 +- ...onnectorSetupWithStandardPermissions.scala | 4 +- .../commons/model/ViewModel.scala | 6 +- 12 files changed, 78 insertions(+), 62 deletions(-) diff --git a/obp-api/src/main/scala/code/api/util/APIUtil.scala b/obp-api/src/main/scala/code/api/util/APIUtil.scala index c2bc7b8e8..46c5c43cb 100644 --- a/obp-api/src/main/scala/code/api/util/APIUtil.scala +++ b/obp-api/src/main/scala/code/api/util/APIUtil.scala @@ -4059,28 +4059,34 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{ //we need set guard to easily distinguish the system view and custom view, // customer view must start with '_', system can not - // viewName and viewId are the same value, just with different format, eg: createViewIdByName(view.name) - def checkSystemViewIdOrName(viewId: String): Boolean = !checkCustomViewIdOrName(viewId: String) + // viewId is created by viewName, please check method : createViewIdByName(view.name) + // so here we can use isSystemViewName method to check viewId + def isValidatedSystemViewId(viewId: String): Boolean = isValidatedSystemViewName(viewId: String) + + def isValidatedSystemViewName(viewName: String): Boolean = !isValidatedCustomViewName(viewName: String) + + // viewId is created by viewName, please check method : createViewIdByName(view.name) + // so here we can use isCustomViewName method to check viewId + def isValidatedCustomViewId(viewId: String): Boolean = isValidatedCustomViewName(viewId) //customer views are started ith `_`,eg _life, _work, and System views startWith letter, eg: owner - // viewName and viewId are the same value, just with different format, eg: createViewIdByName(view.name) - def checkCustomViewIdOrName(name: String): Boolean = name match { + def isValidatedCustomViewName(name: String): Boolean = name match { case x if x.startsWith("_") => true // Allowed case case _ => false } - def canGrantAccessToView(bankId: BankId, accountId: AccountId, targetViewId : ViewId, user: User, callContext: Option[CallContext]): Boolean = { + def canGrantAccessToView(bankId: BankId, accountId: AccountId, viewIdTobeGranted : ViewId, user: User, callContext: Option[CallContext]): Boolean = { //all the permission this user have for the bankAccount val permission: Box[Permission] = Views.views.vend.permission(BankIdAccountId(bankId, accountId), user) - //1. if targetViewId is systemView. just compare all the permissions - if(checkSystemViewIdOrName(targetViewId.value)){ + //1. if viewIdTobeGranted is systemView. just compare all the permissions + if(isValidatedSystemViewId(viewIdTobeGranted.value)){ val allCanGrantAccessToViewsPermissions: List[String] = permission - .map(_.views.map(_.canGrantAccessToViews.getOrElse(Nil)).flatten).getOrElse(Nil).distinct + .map(_.views.map(_.canGrantAccessToSystemViews.getOrElse(Nil)).flatten).getOrElse(Nil).distinct - allCanGrantAccessToViewsPermissions.contains(targetViewId.value) + allCanGrantAccessToViewsPermissions.contains(viewIdTobeGranted.value) } else{ - //2. if targetViewId is customView, we only need to check the `canGrantAccessToCustomViews`. + //2. if viewIdTobeGranted is customView, we only need to check the `canGrantAccessToCustomViews`. val allCanGrantAccessToCustomViewsPermissions: List[Boolean] = permission.map(_.views.map(_.canGrantAccessToCustomViews)).getOrElse(Nil) allCanGrantAccessToCustomViewsPermissions.contains(true) @@ -4092,10 +4098,10 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{ //1st: get the view val view: Box[View] = Views.views.vend.getViewByBankIdAccountIdViewIdUserPrimaryKey(bankIdAccountIdViewId, user.userPrimaryKey) - //2rd: f targetViewId is systemView. we need to check `view.canGrantAccessToViews` field. - if(checkSystemViewIdOrName(targetViewId.value)){ - val canGrantAccessToView: Box[List[String]] = view.map(_.canGrantAccessToViews.getOrElse(Nil)) - canGrantAccessToView.getOrElse(Nil).contains(targetViewId.value) + //2rd: f targetViewId is systemView. we need to check `view.canGrantAccessToSystemViews` field. + if(isValidatedSystemViewId(targetViewId.value)){ + val canGrantAccessToSystemViews: Box[List[String]] = view.map(_.canGrantAccessToSystemViews.getOrElse(Nil)) + canGrantAccessToSystemViews.getOrElse(Nil).contains(targetViewId.value) } else{ //3rd. if targetViewId is customView, we need to check `view.canGrantAccessToCustomViews` field. view.map(_.canGrantAccessToCustomViews).getOrElse(false) @@ -4106,19 +4112,22 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{ //all the permission this user have for the bankAccount val permissionBox = Views.views.vend.permission(BankIdAccountId(bankId, accountId), user) - //check if we can grant all systemViews Access - val allCanGrantAccessToViewsPermissions: List[String] = permissionBox.map(_.views.map(_.canGrantAccessToViews.getOrElse(Nil)).flatten).getOrElse(Nil).distinct - val allSystemViewsAccessTobeGranted: List[String] = viewIdsTobeGranted.map(_.value).distinct.filter(checkSystemViewIdOrName) - val canGrantAccessToAllSystemViews = allSystemViewsAccessTobeGranted.forall(allCanGrantAccessToViewsPermissions.contains) + //Retrieve all views from the 'canRevokeAccessToViews' list within each view from the permission views. + val allCanGrantAccessToSystemViews: List[String] = permissionBox.map(_.views.map(_.canGrantAccessToSystemViews.getOrElse(Nil)).flatten).getOrElse(Nil).distinct + + val allSystemViewsIdsTobeGranted: List[String] = viewIdsTobeGranted.map(_.value).distinct.filter(isValidatedSystemViewId) + + val canGrantAllSystemViewsIdsTobeGranted = allSystemViewsIdsTobeGranted.forall(allCanGrantAccessToSystemViews.contains) - if (viewIdsTobeGranted.map(_.value).distinct.find(checkCustomViewIdOrName).isDefined){ - //check if we can grant all customViews Access + //if the viewIdsTobeGranted contains custom view ids, we need to check the both canGrantAccessToCustomViews and canGrantAccessToSystemViews + if (viewIdsTobeGranted.map(_.value).distinct.find(isValidatedCustomViewId).isDefined){ + //check if we can grant all customViews Access. val allCanGrantAccessToCustomViewsPermissions: List[Boolean] = permissionBox.map(_.views.map(_.canGrantAccessToCustomViews)).getOrElse(Nil) val canGrantAccessToAllCustomViews = allCanGrantAccessToCustomViewsPermissions.contains(true) //we need merge both system and custom access - canGrantAccessToAllSystemViews && canGrantAccessToAllCustomViews - } else { - canGrantAccessToAllSystemViews + canGrantAllSystemViewsIdsTobeGranted && canGrantAccessToAllCustomViews + } else {// if viewIdsTobeGranted only contains system view ids, we only need to check `canGrantAccessToSystemViews` + canGrantAllSystemViewsIdsTobeGranted } } @@ -4127,11 +4136,11 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{ val permission: Box[Permission] = Views.views.vend.permission(BankIdAccountId(bankId, accountId), user) //1. if viewIdTobeRevoked is systemView. just compare all the permissions - if (checkSystemViewIdOrName(viewIdToBeRevoked.value)) { - val allCanRevokeAccessToViewsPermissions: List[String] = permission - .map(_.views.map(_.canRevokeAccessToViews.getOrElse(Nil)).flatten).getOrElse(Nil).distinct + if (isValidatedSystemViewId(viewIdToBeRevoked.value)) { + val allCanRevokeAccessToSystemViews: List[String] = permission + .map(_.views.map(_.canRevokeAccessToSystemViews.getOrElse(Nil)).flatten).getOrElse(Nil).distinct - allCanRevokeAccessToViewsPermissions.contains(viewIdToBeRevoked.value) + allCanRevokeAccessToSystemViews.contains(viewIdToBeRevoked.value) } else { //2. if viewIdTobeRevoked is customView, we only need to check the `canRevokeAccessToCustomViews`. val allCanRevokeAccessToCustomViewsPermissions: List[Boolean] = permission.map(_.views.map(_.canRevokeAccessToCustomViews)).getOrElse(Nil) @@ -4144,25 +4153,27 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{ val permissionBox = Views.views.vend.permission(BankIdAccountId(bankId, accountId), user) - //check if we can revoke all systemViews Access - val allCanRevokeAccessToViewsPermissions: List[String] = permissionBox.map(_.views.map(_.canRevokeAccessToViews.getOrElse(Nil)).flatten).getOrElse(Nil).distinct - val allAccountAccessSystemViews: List[String] = permissionBox.map(_.views.map(_.viewId.value)).getOrElse(Nil).distinct.filter(checkSystemViewIdOrName) - val canRevokeAccessToAllSystemViews = allAccountAccessSystemViews.forall(allCanRevokeAccessToViewsPermissions.contains) + //Retrieve all views from the 'canRevokeAccessToViews' list within each view from the permission views. + val allCanRevokeAccessToViews: List[String] = permissionBox.map(_.views.map(_.canRevokeAccessToSystemViews.getOrElse(Nil)).flatten).getOrElse(Nil).distinct + + //All targetViewIds: + val allTargetViewIds: List[String] = permissionBox.map(_.views.map(_.viewId.value)).getOrElse(Nil).distinct + + val allSystemTargetViewIs: List[String] = allTargetViewIds.filter(isValidatedSystemViewId) + + val canRevokeAccessToAllSystemTargetViews = allSystemTargetViewIs.forall(allCanRevokeAccessToViews.contains) - if (allAccountAccessSystemViews.find(checkCustomViewIdOrName).isDefined){ + //if allTargetViewIds contains customViewId,we need to check both `canRevokeAccessToCustomViews` and `canRevokeAccessToSystemViews` fields + if (allTargetViewIds.find(isValidatedCustomViewId).isDefined){ //check if we can revoke all customViews Access val allCanRevokeAccessToCustomViewsPermissions: List[Boolean] = permissionBox.map(_.views.map(_.canRevokeAccessToCustomViews)).getOrElse(Nil) val canRevokeAccessToAllCustomViews = allCanRevokeAccessToCustomViewsPermissions.contains(true) //we need merge both system and custom access - canRevokeAccessToAllSystemViews && canRevokeAccessToAllCustomViews - }else if(allAccountAccessSystemViews.find(checkSystemViewIdOrName).isDefined){ - canRevokeAccessToAllSystemViews - }else{ - false + canRevokeAccessToAllSystemTargetViews && canRevokeAccessToAllCustomViews + }else + canRevokeAccessToAllSystemTargetViews } - } - def getJValueFromJsonFile(path: String) = { val stream = getClass().getClassLoader().getResourceAsStream(path) try { diff --git a/obp-api/src/main/scala/code/api/v1_2_1/APIMethods121.scala b/obp-api/src/main/scala/code/api/v1_2_1/APIMethods121.scala index 891b4eb63..adde070f8 100644 --- a/obp-api/src/main/scala/code/api/v1_2_1/APIMethods121.scala +++ b/obp-api/src/main/scala/code/api/v1_2_1/APIMethods121.scala @@ -595,7 +595,7 @@ trait APIMethods121 { u <- cc.user ?~ UserNotLoggedIn createViewJsonV121 <- tryo{json.extract[CreateViewJsonV121]} ?~ InvalidJsonFormat //customer views are started ith `_`,eg _life, _work, and System views startWith letter, eg: owner - _<- booleanToBox(checkCustomViewIdOrName(createViewJsonV121.name), InvalidCustomViewFormat+s"Current view_name (${createViewJsonV121.name})") + _<- booleanToBox(isValidatedCustomViewName(createViewJsonV121.name), InvalidCustomViewFormat+s"Current view_name (${createViewJsonV121.name})") account <- BankAccountX(bankId, accountId) ?~! BankAccountNotFound createViewJson = CreateViewJson( createViewJsonV121.name, diff --git a/obp-api/src/main/scala/code/api/v2_2_0/APIMethods220.scala b/obp-api/src/main/scala/code/api/v2_2_0/APIMethods220.scala index f35fc4223..bff3dc82d 100644 --- a/obp-api/src/main/scala/code/api/v2_2_0/APIMethods220.scala +++ b/obp-api/src/main/scala/code/api/v2_2_0/APIMethods220.scala @@ -191,7 +191,7 @@ trait APIMethods220 { for { createViewJsonV121 <- tryo{json.extract[CreateViewJsonV121]} ?~!InvalidJsonFormat //customer views are started ith `_`,eg _life, _work, and System views startWith letter, eg: owner - _<- booleanToBox(checkCustomViewIdOrName(createViewJsonV121.name), InvalidCustomViewFormat+s"Current view_name (${createViewJsonV121.name})") + _<- booleanToBox(isValidatedCustomViewName(createViewJsonV121.name), InvalidCustomViewFormat+s"Current view_name (${createViewJsonV121.name})") u <- cc.user ?~!UserNotLoggedIn account <- BankAccountX(bankId, accountId) ?~! BankAccountNotFound createViewJson = CreateViewJson( 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 387ff149a..b1692ed97 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 @@ -210,7 +210,7 @@ trait APIMethods300 { } //customer views are started ith `_`,eg _life, _work, and System views startWith letter, eg: owner _ <- Helper.booleanToFuture(failMsg = InvalidCustomViewFormat+s"Current view_name (${createViewJson.name})", cc=callContext) { - checkCustomViewIdOrName(createViewJson.name) + isValidatedCustomViewName(createViewJson.name) } (account, callContext) <- NewStyle.function.getBankAccount(bankId, accountId, callContext) diff --git a/obp-api/src/main/scala/code/api/v3_1_0/APIMethods310.scala b/obp-api/src/main/scala/code/api/v3_1_0/APIMethods310.scala index 6448a4596..9ef7c67a6 100644 --- a/obp-api/src/main/scala/code/api/v3_1_0/APIMethods310.scala +++ b/obp-api/src/main/scala/code/api/v3_1_0/APIMethods310.scala @@ -3985,7 +3985,7 @@ trait APIMethods310 { } //System views can not startwith '_' _ <- Helper.booleanToFuture(failMsg = InvalidSystemViewFormat+s"Current view_name (${createViewJson.name})", cc = callContext) { - checkSystemViewIdOrName(createViewJson.name) + isValidatedSystemViewName(createViewJson.name) } _ <- Helper.booleanToFuture(SystemViewCannotBePublicError, failCode=400, cc=callContext) { createViewJson.is_public == false diff --git a/obp-api/src/main/scala/code/api/v5_0_0/APIMethods500.scala b/obp-api/src/main/scala/code/api/v5_0_0/APIMethods500.scala index c9688837b..0d023fb75 100644 --- a/obp-api/src/main/scala/code/api/v5_0_0/APIMethods500.scala +++ b/obp-api/src/main/scala/code/api/v5_0_0/APIMethods500.scala @@ -1889,7 +1889,7 @@ trait APIMethods500 { } // custom views are started with `_`,eg _ life, _ work, and System views can not, eg: owner. _ <- Helper.booleanToFuture(failMsg = InvalidSystemViewFormat +s"Current view_name (${createViewJson.name})", cc = cc.callContext) { - checkSystemViewIdOrName(createViewJson.name) + isValidatedSystemViewName(createViewJson.name) } view <- NewStyle.function.createSystemView(createViewJson.toCreateViewJson, cc.callContext) } yield { diff --git a/obp-api/src/main/scala/code/api/v5_0_0/JSONFactory5.0.0.scala b/obp-api/src/main/scala/code/api/v5_0_0/JSONFactory5.0.0.scala index b424dc291..7a9e025ec 100644 --- a/obp-api/src/main/scala/code/api/v5_0_0/JSONFactory5.0.0.scala +++ b/obp-api/src/main/scala/code/api/v5_0_0/JSONFactory5.0.0.scala @@ -888,8 +888,8 @@ object JSONFactory500 { can_create_direct_debit = view.canCreateDirectDebit, can_create_standing_order = view.canCreateStandingOrder, // Version 5.0.0 - can_grant_access_to_views = view.canGrantAccessToViews.getOrElse(Nil), - can_revoke_access_to_views = view.canRevokeAccessToViews.getOrElse(Nil), + can_grant_access_to_views = view.canGrantAccessToSystemViews.getOrElse(Nil), + can_revoke_access_to_views = view.canRevokeAccessToSystemViews.getOrElse(Nil), ) } def createViewsJsonV500(views : List[View]) : ViewsJsonV500 = { diff --git a/obp-api/src/main/scala/code/views/MapperViews.scala b/obp-api/src/main/scala/code/views/MapperViews.scala index 20bbfba62..36aa651ee 100644 --- a/obp-api/src/main/scala/code/views/MapperViews.scala +++ b/obp-api/src/main/scala/code/views/MapperViews.scala @@ -47,7 +47,7 @@ object MapperViews extends Views with MdcLoggable { } private def getViewFromAccountAccess(accountAccess: AccountAccess) = { - if (checkSystemViewIdOrName(accountAccess.view_id.get)) { + if (isValidatedSystemViewId(accountAccess.view_id.get)) { ViewDefinition.findSystemView(accountAccess.view_id.get) .map(v => v.bank_id(accountAccess.bank_id.get).account_id(accountAccess.account_id.get)) // in case system view do not contains the bankId, and accountId. } else { @@ -381,7 +381,7 @@ object MapperViews extends Views with MdcLoggable { def createSystemView(view: CreateViewJson) : Future[Box[View]] = Future { if(view.is_public) { Failure(SystemViewCannotBePublicError) - }else if (!checkSystemViewIdOrName(view.name)) { + }else if (!isValidatedSystemViewName(view.name)) { Failure(InvalidSystemViewFormat+s"Current view_name (${view.name})") } else { view.name.contentEquals("") match { @@ -415,7 +415,7 @@ object MapperViews extends Views with MdcLoggable { * */ def createCustomView(bankAccountId: BankIdAccountId, view: CreateViewJson): Box[View] = { - if(!checkCustomViewIdOrName(view.name)) { + if(!isValidatedCustomViewName(view.name)) { return Failure(InvalidCustomViewFormat) } diff --git a/obp-api/src/main/scala/code/views/system/ViewDefinition.scala b/obp-api/src/main/scala/code/views/system/ViewDefinition.scala index ac0094101..0726d434b 100644 --- a/obp-api/src/main/scala/code/views/system/ViewDefinition.scala +++ b/obp-api/src/main/scala/code/views/system/ViewDefinition.scala @@ -1,12 +1,12 @@ package code.views.system -import code.api.util.APIUtil.{checkCustomViewIdOrName, checkSystemViewIdOrName} +import code.api.util.APIUtil.{isValidatedCustomViewId, isValidatedCustomViewName, isValidatedSystemViewId} import code.api.util.ErrorMessages.{CreateSystemViewError, InvalidCustomViewFormat, InvalidSystemViewFormat} import code.util.{AccountIdString, UUIDString} import com.openbankproject.commons.model._ import net.liftweb.common.Box import net.liftweb.common.Box.tryo -import net.liftweb.mapper.{MappedBoolean, _} +import net.liftweb.mapper._ import scala.collection.immutable.List @@ -51,12 +51,17 @@ class ViewDefinition extends View with LongKeyedMapper[ViewDefinition] with Many object hideOtherAccountMetadataIfAlias_ extends MappedBoolean(this){ override def defaultValue = false } + + //This is the system views list, custom views please check `canGrantAccessToCustomViews_` field object canGrantAccessToViews_ extends MappedText(this){ override def defaultValue = "" } + + //This is the system views list.custom views please check `canRevokeAccessToCustomViews_` field object canRevokeAccessToViews_ extends MappedText(this){ override def defaultValue = "" } + object canRevokeAccessToCustomViews_ extends MappedBoolean(this){ override def defaultValue = false } @@ -467,7 +472,7 @@ class ViewDefinition extends View with LongKeyedMapper[ViewDefinition] with Many def hideOtherAccountMetadataIfAlias: Boolean = hideOtherAccountMetadataIfAlias_.get //This current view can grant access to other views. - override def canGrantAccessToViews : Option[List[String]] = { + override def canGrantAccessToSystemViews : Option[List[String]] = { canGrantAccessToViews_.get == null || canGrantAccessToViews_.get.isEmpty() match { case true => None case _ => Some(canGrantAccessToViews_.get.split(",").toList.map(_.trim)) @@ -477,7 +482,7 @@ class ViewDefinition extends View with LongKeyedMapper[ViewDefinition] with Many def canGrantAccessToCustomViews : Boolean = canGrantAccessToCustomViews_.get //the current view can revoke access to other views. - override def canRevokeAccessToViews : Option[List[String]] = { + override def canRevokeAccessToSystemViews : Option[List[String]] = { canRevokeAccessToViews_.get == null || canRevokeAccessToViews_.get.isEmpty() match { case true => None case _ => Some(canRevokeAccessToViews_.get.split(",").toList.map(_.trim)) @@ -599,10 +604,10 @@ object ViewDefinition extends ViewDefinition with LongKeyedMetaMapper[ViewDefini t.composite_unique_key(compositeUniqueKey) } - if (t.isSystem && !checkSystemViewIdOrName(t.view_id.get)) { + if (t.isSystem && !isValidatedSystemViewId(t.view_id.get)) { throw new RuntimeException(InvalidSystemViewFormat+s"Current view_id (${t.view_id.get})") } - if (!t.isSystem && !checkCustomViewIdOrName(t.view_id.get)) { + if (!t.isSystem && !isValidatedCustomViewId(t.view_id.get)) { throw new RuntimeException(InvalidCustomViewFormat+s"Current view_id (${t.view_id.get})") } diff --git a/obp-api/src/test/scala/code/api/v1_2_1/API1_2_1Test.scala b/obp-api/src/test/scala/code/api/v1_2_1/API1_2_1Test.scala index b8c5f7201..cef6321ff 100644 --- a/obp-api/src/test/scala/code/api/v1_2_1/API1_2_1Test.scala +++ b/obp-api/src/test/scala/code/api/v1_2_1/API1_2_1Test.scala @@ -31,7 +31,7 @@ import _root_.net.liftweb.json.Serialization.write import code.api.ResourceDocs1_4_0.SwaggerDefinitionsJSON import code.api.util.APIUtil import code.api.util.APIUtil.OAuth._ -import code.api.util.APIUtil.checkSystemViewIdOrName +import code.api.util.APIUtil.isValidatedSystemViewId import code.bankconnectors.Connector import code.setup.{APIResponse, DefaultUsers, PrivateUser2AccountsAndSetUpWithTestData, ServerSetupWithTestData} import code.views.Views @@ -165,7 +165,7 @@ class API1_2_1Test extends ServerSetupWithTestData with DefaultUsers with Privat val reply = makeGetRequest(request) val possibleViewsPermalinks = reply.body.extract[ViewsJSONV121].views .filterNot(_.is_public==true) - .filterNot(view=> checkSystemViewIdOrName(view.id)) + .filterNot(view=> isValidatedSystemViewId(view.id)) val randomPosition = nextInt(possibleViewsPermalinks.size) possibleViewsPermalinks(randomPosition).id } diff --git a/obp-api/src/test/scala/code/setup/TestConnectorSetupWithStandardPermissions.scala b/obp-api/src/test/scala/code/setup/TestConnectorSetupWithStandardPermissions.scala index a1ec88473..405e7cd31 100644 --- a/obp-api/src/test/scala/code/setup/TestConnectorSetupWithStandardPermissions.scala +++ b/obp-api/src/test/scala/code/setup/TestConnectorSetupWithStandardPermissions.scala @@ -3,7 +3,7 @@ package code.setup import bootstrap.liftweb.ToSchemify import code.accountholders.AccountHolders import code.api.Constant.{CUSTOM_PUBLIC_VIEW_ID, SYSTEM_OWNER_VIEW_ID} -import code.api.util.APIUtil.checkCustomViewIdOrName +import code.api.util.APIUtil.isValidatedCustomViewName import code.api.util.ErrorMessages._ import code.model._ import code.model.dataAccess._ @@ -40,7 +40,7 @@ trait TestConnectorSetupWithStandardPermissions extends TestConnectorSetup { val viewId = MapperViews.createViewIdByName(viewName) val description = randomString(40) - if (!checkCustomViewIdOrName(viewName)) { + if (!isValidatedCustomViewName(viewName)) { throw new RuntimeException(InvalidCustomViewFormat) } diff --git a/obp-commons/src/main/scala/com/openbankproject/commons/model/ViewModel.scala b/obp-commons/src/main/scala/com/openbankproject/commons/model/ViewModel.scala index ccdee4fb1..84ae7e026 100644 --- a/obp-commons/src/main/scala/com/openbankproject/commons/model/ViewModel.scala +++ b/obp-commons/src/main/scala/com/openbankproject/commons/model/ViewModel.scala @@ -250,10 +250,10 @@ trait View { def usePrivateAliasIfOneExists: Boolean def hideOtherAccountMetadataIfAlias: Boolean - //TODO, in progress, we only make the system view work, the custom views are VIP. - def canGrantAccessToViews : Option[List[String]] = None + + def canGrantAccessToSystemViews : Option[List[String]] = None def canGrantAccessToCustomViews : Boolean // if this true, we can grant custom views, if it is false, no one can grant custom views. - def canRevokeAccessToViews : Option[List[String]] = None + def canRevokeAccessToSystemViews : Option[List[String]] = None def canRevokeAccessToCustomViews : Boolean // if this true, we can revoke custom views,if it is false, no one can revoke custom views. //reading access From 1e580c5aad889a2f6711c63250207b3353090c0a Mon Sep 17 00:00:00 2001 From: hongwei Date: Wed, 3 Apr 2024 09:54:13 +0200 Subject: [PATCH 03/11] refactor/tweaked isValidated to isValid --- .../main/scala/code/api/util/APIUtil.scala | 77 +++++++++++++------ .../scala/code/api/v1_2_1/APIMethods121.scala | 2 +- .../scala/code/api/v2_2_0/APIMethods220.scala | 2 +- .../scala/code/api/v3_0_0/APIMethods300.scala | 2 +- .../scala/code/api/v3_1_0/APIMethods310.scala | 2 +- .../scala/code/api/v4_0_0/APIMethods400.scala | 2 +- .../scala/code/api/v5_0_0/APIMethods500.scala | 2 +- .../main/scala/code/views/MapperViews.scala | 6 +- .../code/views/system/ViewDefinition.scala | 6 +- .../scala/code/api/v1_2_1/API1_2_1Test.scala | 4 +- ...onnectorSetupWithStandardPermissions.scala | 4 +- 11 files changed, 69 insertions(+), 40 deletions(-) diff --git a/obp-api/src/main/scala/code/api/util/APIUtil.scala b/obp-api/src/main/scala/code/api/util/APIUtil.scala index 46c5c43cb..3fd6daad5 100644 --- a/obp-api/src/main/scala/code/api/util/APIUtil.scala +++ b/obp-api/src/main/scala/code/api/util/APIUtil.scala @@ -4061,32 +4061,33 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{ // customer view must start with '_', system can not // viewId is created by viewName, please check method : createViewIdByName(view.name) // so here we can use isSystemViewName method to check viewId - def isValidatedSystemViewId(viewId: String): Boolean = isValidatedSystemViewName(viewId: String) + def isValidSystemViewId(viewId: String): Boolean = isValidSystemViewName(viewId: String) - def isValidatedSystemViewName(viewName: String): Boolean = !isValidatedCustomViewName(viewName: String) + def isValidSystemViewName(viewName: String): Boolean = !isValidCustomViewName(viewName: String) // viewId is created by viewName, please check method : createViewIdByName(view.name) // so here we can use isCustomViewName method to check viewId - def isValidatedCustomViewId(viewId: String): Boolean = isValidatedCustomViewName(viewId) + def isValidCustomViewId(viewId: String): Boolean = isValidCustomViewName(viewId) //customer views are started ith `_`,eg _life, _work, and System views startWith letter, eg: owner - def isValidatedCustomViewName(name: String): Boolean = name match { + def isValidCustomViewName(name: String): Boolean = name match { case x if x.startsWith("_") => true // Allowed case case _ => false } - def canGrantAccessToView(bankId: BankId, accountId: AccountId, viewIdTobeGranted : ViewId, user: User, callContext: Option[CallContext]): Boolean = { + @deprecated("now need bankIdAccountIdViewId and targetViewId explicitly, please check the other `canGrantAccessToView` method","02-04-2024") + def canGrantAccessToView(bankId: BankId, accountId: AccountId, targetViewId : ViewId, user: User, callContext: Option[CallContext]): Boolean = { //all the permission this user have for the bankAccount val permission: Box[Permission] = Views.views.vend.permission(BankIdAccountId(bankId, accountId), user) - //1. if viewIdTobeGranted is systemView. just compare all the permissions - if(isValidatedSystemViewId(viewIdTobeGranted.value)){ + //1. if targetViewId is systemView. just compare all the permissions + if(isValidSystemViewId(targetViewId.value)){ val allCanGrantAccessToViewsPermissions: List[String] = permission .map(_.views.map(_.canGrantAccessToSystemViews.getOrElse(Nil)).flatten).getOrElse(Nil).distinct - allCanGrantAccessToViewsPermissions.contains(viewIdTobeGranted.value) + allCanGrantAccessToViewsPermissions.contains(targetViewId.value) } else{ - //2. if viewIdTobeGranted is customView, we only need to check the `canGrantAccessToCustomViews`. + //2. if targetViewId is customView, we only need to check the `canGrantAccessToCustomViews`. val allCanGrantAccessToCustomViewsPermissions: List[Boolean] = permission.map(_.views.map(_.canGrantAccessToCustomViews)).getOrElse(Nil) allCanGrantAccessToCustomViewsPermissions.contains(true) @@ -4099,7 +4100,7 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{ val view: Box[View] = Views.views.vend.getViewByBankIdAccountIdViewIdUserPrimaryKey(bankIdAccountIdViewId, user.userPrimaryKey) //2rd: f targetViewId is systemView. we need to check `view.canGrantAccessToSystemViews` field. - if(isValidatedSystemViewId(targetViewId.value)){ + if(isValidSystemViewId(targetViewId.value)){ val canGrantAccessToSystemViews: Box[List[String]] = view.map(_.canGrantAccessToSystemViews.getOrElse(Nil)) canGrantAccessToSystemViews.getOrElse(Nil).contains(targetViewId.value) } else{ @@ -4107,48 +4108,65 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{ view.map(_.canGrantAccessToCustomViews).getOrElse(false) } } - - def canGrantAccessToMultipleViews(bankId: BankId, accountId: AccountId, viewIdsTobeGranted : List[ViewId], user: User, callContext: Option[CallContext]): Boolean = { + + @deprecated("now need bankIdAccountIdViewId and targetViewId explicitly","02-04-2024") + def canGrantAccessToMultipleViews(bankId: BankId, accountId: AccountId, targetViewIds : List[ViewId], user: User, callContext: Option[CallContext]): Boolean = { //all the permission this user have for the bankAccount val permissionBox = Views.views.vend.permission(BankIdAccountId(bankId, accountId), user) //Retrieve all views from the 'canRevokeAccessToViews' list within each view from the permission views. val allCanGrantAccessToSystemViews: List[String] = permissionBox.map(_.views.map(_.canGrantAccessToSystemViews.getOrElse(Nil)).flatten).getOrElse(Nil).distinct - val allSystemViewsIdsTobeGranted: List[String] = viewIdsTobeGranted.map(_.value).distinct.filter(isValidatedSystemViewId) + val allSystemViewsIdsTobeGranted: List[String] = targetViewIds.map(_.value).distinct.filter(isValidSystemViewId) val canGrantAllSystemViewsIdsTobeGranted = allSystemViewsIdsTobeGranted.forall(allCanGrantAccessToSystemViews.contains) - //if the viewIdsTobeGranted contains custom view ids, we need to check the both canGrantAccessToCustomViews and canGrantAccessToSystemViews - if (viewIdsTobeGranted.map(_.value).distinct.find(isValidatedCustomViewId).isDefined){ + //if the targetViewIds contains custom view ids, we need to check the both canGrantAccessToCustomViews and canGrantAccessToSystemViews + if (targetViewIds.map(_.value).distinct.find(isValidCustomViewId).isDefined){ //check if we can grant all customViews Access. val allCanGrantAccessToCustomViewsPermissions: List[Boolean] = permissionBox.map(_.views.map(_.canGrantAccessToCustomViews)).getOrElse(Nil) val canGrantAccessToAllCustomViews = allCanGrantAccessToCustomViewsPermissions.contains(true) //we need merge both system and custom access canGrantAllSystemViewsIdsTobeGranted && canGrantAccessToAllCustomViews - } else {// if viewIdsTobeGranted only contains system view ids, we only need to check `canGrantAccessToSystemViews` + } else {// if targetViewIds only contains system view ids, we only need to check `canGrantAccessToSystemViews` canGrantAllSystemViewsIdsTobeGranted } } + + def canRevokeAccessToView(bankIdAccountIdViewId: BankIdAccountIdViewId, targetViewId : ViewId, user: User, callContext: Option[CallContext]): Boolean = { + //1st: get the view + val view: Box[View] = Views.views.vend.getViewByBankIdAccountIdViewIdUserPrimaryKey(bankIdAccountIdViewId, user.userPrimaryKey) + + //2rd: f targetViewId is systemView. we need to check `view.canGrantAccessToSystemViews` field. + if (isValidSystemViewId(targetViewId.value)) { + val canRevokeAccessToSystemViews: Box[List[String]] = view.map(_.canRevokeAccessToSystemViews.getOrElse(Nil)) + canRevokeAccessToSystemViews.getOrElse(Nil).contains(targetViewId.value) + } else { + //3rd. if targetViewId is customView, we need to check `view.canGrantAccessToCustomViews` field. + view.map(_.canRevokeAccessToCustomViews).getOrElse(false) + } + } - def canRevokeAccessToView(bankId: BankId, accountId: AccountId, viewIdToBeRevoked : ViewId, user: User, callContext: Option[CallContext]): Boolean = { + @deprecated("now need bankIdAccountIdViewId and targetViewId explicitly","02-04-2024") + def canRevokeAccessToView(bankId: BankId, accountId: AccountId, targetViewId : ViewId, user: User, callContext: Option[CallContext]): Boolean = { //all the permission this user have for the bankAccount val permission: Box[Permission] = Views.views.vend.permission(BankIdAccountId(bankId, accountId), user) - //1. if viewIdTobeRevoked is systemView. just compare all the permissions - if (isValidatedSystemViewId(viewIdToBeRevoked.value)) { + //1. if targetViewId is systemView. just compare all the permissions + if (isValidSystemViewId(targetViewId.value)) { val allCanRevokeAccessToSystemViews: List[String] = permission .map(_.views.map(_.canRevokeAccessToSystemViews.getOrElse(Nil)).flatten).getOrElse(Nil).distinct - allCanRevokeAccessToSystemViews.contains(viewIdToBeRevoked.value) + allCanRevokeAccessToSystemViews.contains(targetViewId.value) } else { - //2. if viewIdTobeRevoked is customView, we only need to check the `canRevokeAccessToCustomViews`. + //2. if targetViewId is customView, we only need to check the `canRevokeAccessToCustomViews`. val allCanRevokeAccessToCustomViewsPermissions: List[Boolean] = permission.map(_.views.map(_.canRevokeAccessToCustomViews)).getOrElse(Nil) allCanRevokeAccessToCustomViewsPermissions.contains(true) } } - + + @deprecated("now need bankIdAccountIdViewId and targetViewId explicitly","02-04-2024") def canRevokeAccessToAllViews(bankId: BankId, accountId: AccountId, user: User, callContext: Option[CallContext]): Boolean = { val permissionBox = Views.views.vend.permission(BankIdAccountId(bankId, accountId), user) @@ -4159,12 +4177,12 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{ //All targetViewIds: val allTargetViewIds: List[String] = permissionBox.map(_.views.map(_.viewId.value)).getOrElse(Nil).distinct - val allSystemTargetViewIs: List[String] = allTargetViewIds.filter(isValidatedSystemViewId) + val allSystemTargetViewIs: List[String] = allTargetViewIds.filter(isValidSystemViewId) val canRevokeAccessToAllSystemTargetViews = allSystemTargetViewIs.forall(allCanRevokeAccessToViews.contains) //if allTargetViewIds contains customViewId,we need to check both `canRevokeAccessToCustomViews` and `canRevokeAccessToSystemViews` fields - if (allTargetViewIds.find(isValidatedCustomViewId).isDefined){ + if (allTargetViewIds.find(isValidCustomViewId).isDefined){ //check if we can revoke all customViews Access val allCanRevokeAccessToCustomViewsPermissions: List[Boolean] = permissionBox.map(_.views.map(_.canRevokeAccessToCustomViews)).getOrElse(Nil) val canRevokeAccessToAllCustomViews = allCanRevokeAccessToCustomViewsPermissions.contains(true) @@ -4894,6 +4912,17 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{ ) = s"requestedApiVersionString:$requestedApiVersionString-bankId:$bankId-tags:$tags-partialFunctions:$partialFunctions-locale:${locale.toString}" + s"-contentParam:$contentParam-apiCollectionIdParam:$apiCollectionIdParam-isVersion4OrHigher:$isVersion4OrHigher-isStaticResource:$isStaticResource".intern() + def getUserLacksRevokePermissionErrorMessage(sourceViewId: ViewId, targetViewId: ViewId) = + if (isValidSystemViewId(targetViewId.value)) + UserLacksPermissionCanRevokeAccessToSystemViewForTargetAccount + s"Current source viewId(${sourceViewId.value} ) and target viewId (${targetViewId.value})" + else + UserLacksPermissionCanRevokeAccessToCustomViewForTargetAccount + s"Current source viewId(${sourceViewId.value}) and target viewId (${targetViewId.value})" + + def getUserLacksGrantPermissionErrorMessage(sourceViewId: ViewId, targetViewId: ViewId) = + if (isValidSystemViewId(targetViewId.value)) + UserLacksPermissionCanGrantAccessToSystemViewForTargetAccount + s"Current source viewId(${sourceViewId.value} ) and target viewId (${targetViewId.value})" + else + UserLacksPermissionCanGrantAccessToCustomViewForTargetAccount + s"Current source viewId(${sourceViewId.value}) and target viewId (${targetViewId.value})" } diff --git a/obp-api/src/main/scala/code/api/v1_2_1/APIMethods121.scala b/obp-api/src/main/scala/code/api/v1_2_1/APIMethods121.scala index adde070f8..6ffec8325 100644 --- a/obp-api/src/main/scala/code/api/v1_2_1/APIMethods121.scala +++ b/obp-api/src/main/scala/code/api/v1_2_1/APIMethods121.scala @@ -595,7 +595,7 @@ trait APIMethods121 { u <- cc.user ?~ UserNotLoggedIn createViewJsonV121 <- tryo{json.extract[CreateViewJsonV121]} ?~ InvalidJsonFormat //customer views are started ith `_`,eg _life, _work, and System views startWith letter, eg: owner - _<- booleanToBox(isValidatedCustomViewName(createViewJsonV121.name), InvalidCustomViewFormat+s"Current view_name (${createViewJsonV121.name})") + _<- booleanToBox(isValidCustomViewName(createViewJsonV121.name), InvalidCustomViewFormat+s"Current view_name (${createViewJsonV121.name})") account <- BankAccountX(bankId, accountId) ?~! BankAccountNotFound createViewJson = CreateViewJson( createViewJsonV121.name, diff --git a/obp-api/src/main/scala/code/api/v2_2_0/APIMethods220.scala b/obp-api/src/main/scala/code/api/v2_2_0/APIMethods220.scala index bff3dc82d..2c811b04d 100644 --- a/obp-api/src/main/scala/code/api/v2_2_0/APIMethods220.scala +++ b/obp-api/src/main/scala/code/api/v2_2_0/APIMethods220.scala @@ -191,7 +191,7 @@ trait APIMethods220 { for { createViewJsonV121 <- tryo{json.extract[CreateViewJsonV121]} ?~!InvalidJsonFormat //customer views are started ith `_`,eg _life, _work, and System views startWith letter, eg: owner - _<- booleanToBox(isValidatedCustomViewName(createViewJsonV121.name), InvalidCustomViewFormat+s"Current view_name (${createViewJsonV121.name})") + _<- booleanToBox(isValidCustomViewName(createViewJsonV121.name), InvalidCustomViewFormat+s"Current view_name (${createViewJsonV121.name})") u <- cc.user ?~!UserNotLoggedIn account <- BankAccountX(bankId, accountId) ?~! BankAccountNotFound createViewJson = CreateViewJson( 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 b1692ed97..992c76ed3 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 @@ -210,7 +210,7 @@ trait APIMethods300 { } //customer views are started ith `_`,eg _life, _work, and System views startWith letter, eg: owner _ <- Helper.booleanToFuture(failMsg = InvalidCustomViewFormat+s"Current view_name (${createViewJson.name})", cc=callContext) { - isValidatedCustomViewName(createViewJson.name) + isValidCustomViewName(createViewJson.name) } (account, callContext) <- NewStyle.function.getBankAccount(bankId, accountId, callContext) diff --git a/obp-api/src/main/scala/code/api/v3_1_0/APIMethods310.scala b/obp-api/src/main/scala/code/api/v3_1_0/APIMethods310.scala index 9ef7c67a6..77f64364b 100644 --- a/obp-api/src/main/scala/code/api/v3_1_0/APIMethods310.scala +++ b/obp-api/src/main/scala/code/api/v3_1_0/APIMethods310.scala @@ -3985,7 +3985,7 @@ trait APIMethods310 { } //System views can not startwith '_' _ <- Helper.booleanToFuture(failMsg = InvalidSystemViewFormat+s"Current view_name (${createViewJson.name})", cc = callContext) { - isValidatedSystemViewName(createViewJson.name) + isValidSystemViewName(createViewJson.name) } _ <- Helper.booleanToFuture(SystemViewCannotBePublicError, failCode=400, cc=callContext) { createViewJson.is_public == false 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 4f5fe911c..c91f0edba 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 @@ -4492,7 +4492,7 @@ trait APIMethods400 extends MdcLoggable { revokedJsonV400, List( $UserNotLoggedIn, - UserLacksPermissionCanGrantAccessToViewForTargetAccount, + UserLacksPermissionCanRevokeAccessToViewForTargetAccount, InvalidJsonFormat, UserNotFoundById, SystemViewNotFound, diff --git a/obp-api/src/main/scala/code/api/v5_0_0/APIMethods500.scala b/obp-api/src/main/scala/code/api/v5_0_0/APIMethods500.scala index 0d023fb75..221925173 100644 --- a/obp-api/src/main/scala/code/api/v5_0_0/APIMethods500.scala +++ b/obp-api/src/main/scala/code/api/v5_0_0/APIMethods500.scala @@ -1889,7 +1889,7 @@ trait APIMethods500 { } // custom views are started with `_`,eg _ life, _ work, and System views can not, eg: owner. _ <- Helper.booleanToFuture(failMsg = InvalidSystemViewFormat +s"Current view_name (${createViewJson.name})", cc = cc.callContext) { - isValidatedSystemViewName(createViewJson.name) + isValidSystemViewName(createViewJson.name) } view <- NewStyle.function.createSystemView(createViewJson.toCreateViewJson, cc.callContext) } yield { diff --git a/obp-api/src/main/scala/code/views/MapperViews.scala b/obp-api/src/main/scala/code/views/MapperViews.scala index 36aa651ee..00519eafd 100644 --- a/obp-api/src/main/scala/code/views/MapperViews.scala +++ b/obp-api/src/main/scala/code/views/MapperViews.scala @@ -47,7 +47,7 @@ object MapperViews extends Views with MdcLoggable { } private def getViewFromAccountAccess(accountAccess: AccountAccess) = { - if (isValidatedSystemViewId(accountAccess.view_id.get)) { + if (isValidSystemViewId(accountAccess.view_id.get)) { ViewDefinition.findSystemView(accountAccess.view_id.get) .map(v => v.bank_id(accountAccess.bank_id.get).account_id(accountAccess.account_id.get)) // in case system view do not contains the bankId, and accountId. } else { @@ -381,7 +381,7 @@ object MapperViews extends Views with MdcLoggable { def createSystemView(view: CreateViewJson) : Future[Box[View]] = Future { if(view.is_public) { Failure(SystemViewCannotBePublicError) - }else if (!isValidatedSystemViewName(view.name)) { + }else if (!isValidSystemViewName(view.name)) { Failure(InvalidSystemViewFormat+s"Current view_name (${view.name})") } else { view.name.contentEquals("") match { @@ -415,7 +415,7 @@ object MapperViews extends Views with MdcLoggable { * */ def createCustomView(bankAccountId: BankIdAccountId, view: CreateViewJson): Box[View] = { - if(!isValidatedCustomViewName(view.name)) { + if(!isValidCustomViewName(view.name)) { return Failure(InvalidCustomViewFormat) } diff --git a/obp-api/src/main/scala/code/views/system/ViewDefinition.scala b/obp-api/src/main/scala/code/views/system/ViewDefinition.scala index 0726d434b..08201678f 100644 --- a/obp-api/src/main/scala/code/views/system/ViewDefinition.scala +++ b/obp-api/src/main/scala/code/views/system/ViewDefinition.scala @@ -1,6 +1,6 @@ package code.views.system -import code.api.util.APIUtil.{isValidatedCustomViewId, isValidatedCustomViewName, isValidatedSystemViewId} +import code.api.util.APIUtil.{isValidCustomViewId, isValidCustomViewName, isValidSystemViewId} import code.api.util.ErrorMessages.{CreateSystemViewError, InvalidCustomViewFormat, InvalidSystemViewFormat} import code.util.{AccountIdString, UUIDString} import com.openbankproject.commons.model._ @@ -604,10 +604,10 @@ object ViewDefinition extends ViewDefinition with LongKeyedMetaMapper[ViewDefini t.composite_unique_key(compositeUniqueKey) } - if (t.isSystem && !isValidatedSystemViewId(t.view_id.get)) { + if (t.isSystem && !isValidSystemViewId(t.view_id.get)) { throw new RuntimeException(InvalidSystemViewFormat+s"Current view_id (${t.view_id.get})") } - if (!t.isSystem && !isValidatedCustomViewId(t.view_id.get)) { + if (!t.isSystem && !isValidCustomViewId(t.view_id.get)) { throw new RuntimeException(InvalidCustomViewFormat+s"Current view_id (${t.view_id.get})") } diff --git a/obp-api/src/test/scala/code/api/v1_2_1/API1_2_1Test.scala b/obp-api/src/test/scala/code/api/v1_2_1/API1_2_1Test.scala index cef6321ff..01caebe9b 100644 --- a/obp-api/src/test/scala/code/api/v1_2_1/API1_2_1Test.scala +++ b/obp-api/src/test/scala/code/api/v1_2_1/API1_2_1Test.scala @@ -31,7 +31,7 @@ import _root_.net.liftweb.json.Serialization.write import code.api.ResourceDocs1_4_0.SwaggerDefinitionsJSON import code.api.util.APIUtil import code.api.util.APIUtil.OAuth._ -import code.api.util.APIUtil.isValidatedSystemViewId +import code.api.util.APIUtil.isValidSystemViewId import code.bankconnectors.Connector import code.setup.{APIResponse, DefaultUsers, PrivateUser2AccountsAndSetUpWithTestData, ServerSetupWithTestData} import code.views.Views @@ -165,7 +165,7 @@ class API1_2_1Test extends ServerSetupWithTestData with DefaultUsers with Privat val reply = makeGetRequest(request) val possibleViewsPermalinks = reply.body.extract[ViewsJSONV121].views .filterNot(_.is_public==true) - .filterNot(view=> isValidatedSystemViewId(view.id)) + .filterNot(view=> isValidSystemViewId(view.id)) val randomPosition = nextInt(possibleViewsPermalinks.size) possibleViewsPermalinks(randomPosition).id } diff --git a/obp-api/src/test/scala/code/setup/TestConnectorSetupWithStandardPermissions.scala b/obp-api/src/test/scala/code/setup/TestConnectorSetupWithStandardPermissions.scala index 405e7cd31..dcc67510b 100644 --- a/obp-api/src/test/scala/code/setup/TestConnectorSetupWithStandardPermissions.scala +++ b/obp-api/src/test/scala/code/setup/TestConnectorSetupWithStandardPermissions.scala @@ -3,7 +3,7 @@ package code.setup import bootstrap.liftweb.ToSchemify import code.accountholders.AccountHolders import code.api.Constant.{CUSTOM_PUBLIC_VIEW_ID, SYSTEM_OWNER_VIEW_ID} -import code.api.util.APIUtil.isValidatedCustomViewName +import code.api.util.APIUtil.isValidCustomViewName import code.api.util.ErrorMessages._ import code.model._ import code.model.dataAccess._ @@ -40,7 +40,7 @@ trait TestConnectorSetupWithStandardPermissions extends TestConnectorSetup { val viewId = MapperViews.createViewIdByName(viewName) val description = randomString(40) - if (!isValidatedCustomViewName(viewName)) { + if (!isValidCustomViewName(viewName)) { throw new RuntimeException(InvalidCustomViewFormat) } From f47d1f7cd21d0089559abae9eeb860002eb8ecf4 Mon Sep 17 00:00:00 2001 From: hongwei Date: Wed, 3 Apr 2024 09:56:01 +0200 Subject: [PATCH 04/11] refactor/added new error messages --- .../main/scala/code/api/util/ErrorMessages.scala | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/obp-api/src/main/scala/code/api/util/ErrorMessages.scala b/obp-api/src/main/scala/code/api/util/ErrorMessages.scala index 8b76d7948..500c7c97d 100644 --- a/obp-api/src/main/scala/code/api/util/ErrorMessages.scala +++ b/obp-api/src/main/scala/code/api/util/ErrorMessages.scala @@ -232,6 +232,19 @@ object ErrorMessages { val MissingDirectLoginHeader = "OBP-20082: Missing DirectLogin or Authorization header." val InvalidDirectLoginHeader = "OBP-20083: Missing DirectLogin word at the value of Authorization header." + + val UserLacksPermissionCanGrantAccessToSystemViewForTargetAccount = + s"OBP-20084: The current source view.can_grant_access_to_views does not contains target view." + + val UserLacksPermissionCanGrantAccessToCustomViewForTargetAccount = + s"OBP-20085: The current source view.can_grant_access_to_custom_views is false." + + val UserLacksPermissionCanRevokeAccessToSystemViewForTargetAccount = + s"OBP-20086: The current source view.can_revoke_access_to_views does not contains target view." + + val UserLacksPermissionCanRevokeAccessToCustomViewForTargetAccount = + s"OBP-20087: The current source view.can_revoke_access_to_custom_views is false." + val UserNotSuperAdminOrMissRole = "OBP-20101: Current User is not super admin or is missing entitlements:" val CannotGetOrCreateUser = "OBP-20102: Cannot get or create user." val InvalidUserProvider = "OBP-20103: Invalid DAuth User Provider." From 88ad6c396d606c243ecad85a8109fd1580da5638 Mon Sep 17 00:00:00 2001 From: hongwei Date: Wed, 3 Apr 2024 10:54:57 +0200 Subject: [PATCH 05/11] feature/OBPv510 added new endpoints --- .../main/scala/code/api/util/APIUtil.scala | 4 +- .../scala/code/api/v5_1_0/APIMethods510.scala | 147 +++++++++++++++++- .../code/api/v5_1_0/JSONFactory5.1.0.scala | 4 +- .../scala/code/api/v5_1_0/OBPAPI5_1_0.scala | 8 +- 4 files changed, 153 insertions(+), 10 deletions(-) diff --git a/obp-api/src/main/scala/code/api/util/APIUtil.scala b/obp-api/src/main/scala/code/api/util/APIUtil.scala index 3fd6daad5..6fd912946 100644 --- a/obp-api/src/main/scala/code/api/util/APIUtil.scala +++ b/obp-api/src/main/scala/code/api/util/APIUtil.scala @@ -4914,13 +4914,13 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{ def getUserLacksRevokePermissionErrorMessage(sourceViewId: ViewId, targetViewId: ViewId) = if (isValidSystemViewId(targetViewId.value)) - UserLacksPermissionCanRevokeAccessToSystemViewForTargetAccount + s"Current source viewId(${sourceViewId.value} ) and target viewId (${targetViewId.value})" + UserLacksPermissionCanRevokeAccessToSystemViewForTargetAccount + s"Current source viewId(${sourceViewId.value}) and target viewId (${targetViewId.value})" else UserLacksPermissionCanRevokeAccessToCustomViewForTargetAccount + s"Current source viewId(${sourceViewId.value}) and target viewId (${targetViewId.value})" def getUserLacksGrantPermissionErrorMessage(sourceViewId: ViewId, targetViewId: ViewId) = if (isValidSystemViewId(targetViewId.value)) - UserLacksPermissionCanGrantAccessToSystemViewForTargetAccount + s"Current source viewId(${sourceViewId.value} ) and target viewId (${targetViewId.value})" + UserLacksPermissionCanGrantAccessToSystemViewForTargetAccount + s"Current source viewId(${sourceViewId.value}) and target viewId (${targetViewId.value})" else UserLacksPermissionCanGrantAccessToCustomViewForTargetAccount + s"Current source viewId(${sourceViewId.value}) and target viewId (${targetViewId.value})" 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 a2145470d..1e1849590 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 @@ -19,7 +19,7 @@ import code.api.v3_0_0.JSONFactory300 import code.api.v3_0_0.JSONFactory300.createAggregateMetricJson import code.api.v3_1_0.ConsentJsonV310 import code.api.v3_1_0.JSONFactory310.createBadLoginStatusJson -import code.api.v4_0_0.{JSONFactory400, PostAccountAccessJsonV400, PostApiCollectionJson400} +import code.api.v4_0_0.{JSONFactory400, PostAccountAccessJsonV400, PostApiCollectionJson400, RevokedJsonV400} import code.api.v5_1_0.JSONFactory510.{createRegulatedEntitiesJson, createRegulatedEntityJson} import code.atmattribute.AtmAttribute import code.bankconnectors.Connector @@ -1925,7 +1925,7 @@ trait APIMethods510 { nameOf(grantUserAccessToViewById), "POST", "/banks/BANK_ID/accounts/ACCOUNT_ID/VIEW_ID/account-access/grant", - "Grant User access to View By Id", + "Grant User access to View", s"""Grants the User identified by USER_ID access to the view identified by VIEW_ID. | |${authenticationRequiredMessage(true)} and the user needs to be account holder. @@ -1935,7 +1935,8 @@ trait APIMethods510 { viewJsonV300, List( $UserNotLoggedIn, - UserLacksPermissionCanGrantAccessToViewForTargetAccount, + UserLacksPermissionCanGrantAccessToSystemViewForTargetAccount, + UserLacksPermissionCanGrantAccessToCustomViewForTargetAccount, InvalidJsonFormat, UserNotFoundById, SystemViewNotFound, @@ -1956,8 +1957,10 @@ trait APIMethods510 { postJson <- NewStyle.function.tryons(failMsg, 400, cc.callContext) { json.extract[PostAccountAccessJsonV400] } - _ <- Helper.booleanToFuture(s"$UserLacksPermissionCanGrantAccessToViewForTargetAccount Current ViewId(${viewId.value}), Target ViewId(${postJson.view.view_id}))", cc = cc.callContext) { - APIUtil.canGrantAccessToView(BankIdAccountIdViewId(bankId,accountId,viewId),ViewId(postJson.view.view_id), u, callContext) + targetViewId = ViewId(postJson.view.view_id) + msg = getUserLacksGrantPermissionErrorMessage(viewId, targetViewId) + _ <- Helper.booleanToFuture(msg, cc = cc.callContext) { + APIUtil.canGrantAccessToView(BankIdAccountIdViewId(bankId,accountId,viewId),targetViewId, u, callContext) } (user, callContext) <- NewStyle.function.findByUserId(postJson.user_id, callContext) view <- JSONFactory400.getView(bankId, accountId, postJson.view, callContext) @@ -1971,6 +1974,140 @@ trait APIMethods510 { } + staticResourceDocs += ResourceDoc( + revokeUserAccessToViewById, + implementedInApiVersion, + nameOf(revokeUserAccessToViewById), + "POST", + "/banks/BANK_ID/accounts/ACCOUNT_ID/VIEW_ID/account-access/revoke", + "Revoke User access to View", + s"""Revoke the User identified by USER_ID access to the view identified by VIEW_ID. + | + |${authenticationRequiredMessage(true)}. + | + |""", + postAccountAccessJsonV400, + revokedJsonV400, + List( + $UserNotLoggedIn, + UserLacksPermissionCanRevokeAccessToCustomViewForTargetAccount, + UserLacksPermissionCanRevokeAccessToSystemViewForTargetAccount, + InvalidJsonFormat, + UserNotFoundById, + SystemViewNotFound, + ViewNotFound, + CannotRevokeAccountAccess, + CannotFindAccountAccess, + UnknownError + ), + List(apiTagAccountAccess, apiTagView, apiTagAccount, apiTagUser, apiTagOwnerRequired)) + + lazy val revokeUserAccessToViewById: OBPEndpoint = { + //add access for specific user to a specific system view + case "banks" :: BankId(bankId) :: "accounts" :: AccountId(accountId) :: ViewId(viewId) :: "account-access" :: "revoke" :: Nil JsonPost json -> _ => { + cc => + implicit val ec = EndpointContext(Some(cc)) + val failMsg = s"$InvalidJsonFormat The Json body should be the $PostAccountAccessJsonV400 " + for { + (Full(u), callContext) <- SS.user + postJson <- NewStyle.function.tryons(failMsg, 400, cc.callContext) { + json.extract[PostAccountAccessJsonV400] + } + targetViewId = ViewId(postJson.view.view_id) + + msg = getUserLacksRevokePermissionErrorMessage(viewId, targetViewId) + + _ <- Helper.booleanToFuture(msg, cc = cc.callContext) { + APIUtil.canRevokeAccessToView(BankIdAccountIdViewId(bankId, accountId, viewId),targetViewId, u, callContext) + } + (user, callContext) <- NewStyle.function.findByUserId(postJson.user_id, cc.callContext) + view <- postJson.view.is_system match { + case true => NewStyle.function.systemView(targetViewId, callContext) + case false => NewStyle.function.customView(targetViewId, BankIdAccountId(bankId, accountId), callContext) + } + revoked <- postJson.view.is_system match { + case true => NewStyle.function.revokeAccessToSystemView(bankId, accountId, view, user, callContext) + case false => NewStyle.function.revokeAccessToCustomView(view, user, callContext) + } + } yield { + (RevokedJsonV400(revoked), HttpCode.`201`(callContext)) + } + } + } + + staticResourceDocs += ResourceDoc( + createUserWithAccountAccessById, + implementedInApiVersion, + nameOf(createUserWithAccountAccessById), + "POST", + "/banks/BANK_ID/accounts/ACCOUNT_ID/VIEW_ID/user-account-access", + "Create (DAuth) User with Account Access", + s"""This endpoint is used as part of the DAuth solution to grant access to account and transaction data to a smart contract on the blockchain. + | + |Put the smart contract address in username + | + |For provider use "dauth" + | + |This endpoint will create the (DAuth) User with username and provider if the User does not already exist. + | + |${authenticationRequiredMessage(true)} and the logged in user needs to be account holder. + | + |For information about DAuth see below: + | + |${Glossary.getGlossaryItem("DAuth")} + | + |""", + postCreateUserAccountAccessJsonV400, + List(viewJsonV300), + List( + $UserNotLoggedIn, + UserLacksPermissionCanGrantAccessToSystemViewForTargetAccount, + UserLacksPermissionCanGrantAccessToCustomViewForTargetAccount, + InvalidJsonFormat, + SystemViewNotFound, + ViewNotFound, + CannotGrantAccountAccess, + UnknownError + ), + List(apiTagAccountAccess, apiTagView, apiTagAccount, apiTagUser, apiTagOwnerRequired, apiTagDAuth)) + + lazy val createUserWithAccountAccessById: OBPEndpoint = { + case "banks" :: BankId(bankId) :: "accounts" :: AccountId(accountId) :: ViewId(viewId) :: "user-account-access" :: Nil JsonPost json -> _ => { + cc => + implicit val ec = EndpointContext(Some(cc)) + val failMsg = s"$InvalidJsonFormat The Json body should be the $PostCreateUserAccountAccessJsonV510 " + for { + (Full(u), callContext) <- SS.user + postJson <- NewStyle.function.tryons(failMsg, 400, cc.callContext) { + json.extract[PostCreateUserAccountAccessJsonV510] + } + //provider must start with dauth., can not create other provider users. + _ <- Helper.booleanToFuture(s"$InvalidUserProvider The user.provider must be start with 'dauth.'", cc = Some(cc)) { + postJson.provider.startsWith("dauth.") + } + targetViewId = ViewId(postJson.view.view_id) + msg = getUserLacksGrantPermissionErrorMessage(viewId, targetViewId) + + _ <- Helper.booleanToFuture(msg, 403, cc = Some(cc)) { + APIUtil.canGrantAccessToView(BankIdAccountIdViewId(bankId, accountId, viewId) ,targetViewId, u, callContext) + } + (targetUser, callContext) <- NewStyle.function.getOrCreateResourceUser(postJson.provider, postJson.username, cc.callContext) + view <- postJson.view.is_system match { + case true => NewStyle.function.systemView(targetViewId, callContext) + case false => NewStyle.function.customView(targetViewId, BankIdAccountId(bankId, accountId), callContext) + } + addedView <- postJson.view.is_system match { + case true => NewStyle.function.grantAccessToSystemView(bankId, accountId, view, targetUser, callContext) + case false => NewStyle.function.grantAccessToCustomView(view, targetUser, callContext) + } + } yield { + val viewsJson = JSONFactory300.createViewJSON(addedView) + (viewsJson, HttpCode.`201`(callContext)) + } + } + } + + } } diff --git a/obp-api/src/main/scala/code/api/v5_1_0/JSONFactory5.1.0.scala b/obp-api/src/main/scala/code/api/v5_1_0/JSONFactory5.1.0.scala index 1b5384c9d..688479660 100644 --- a/obp-api/src/main/scala/code/api/v5_1_0/JSONFactory5.1.0.scala +++ b/obp-api/src/main/scala/code/api/v5_1_0/JSONFactory5.1.0.scala @@ -33,7 +33,7 @@ import code.api.v1_4_0.JSONFactory1_4_0.{LocationJsonV140, MetaJsonV140, transfo import code.api.v2_1_0.ResourceUserJSON import code.api.v3_0_0.JSONFactory300.{createLocationJson, createMetaJson, transformToAddressFromV300} import code.api.v3_0_0.{AccountIdJson, AccountsIdsJsonV300, AddressJsonV300, OpeningTimesV300} -import code.api.v4_0_0.{EnergySource400, HostedAt400, HostedBy400} +import code.api.v4_0_0.{EnergySource400, HostedAt400, HostedBy400, PostViewJsonV400} import code.atmattribute.AtmAttribute import code.atms.Atms.Atm import code.users.{UserAttribute, Users} @@ -307,6 +307,8 @@ case class ConsumerJsonV510(consumer_id: String, created: Date ) +case class PostCreateUserAccountAccessJsonV510(username: String, provider:String, view: PostViewJsonV400) + object JSONFactory510 extends CustomJsonFormats { def createCustomersIds(customers : List[Customer]): CustomersIdsJsonV510 = diff --git a/obp-api/src/main/scala/code/api/v5_1_0/OBPAPI5_1_0.scala b/obp-api/src/main/scala/code/api/v5_1_0/OBPAPI5_1_0.scala index d56431b42..5d4971fd0 100644 --- a/obp-api/src/main/scala/code/api/v5_1_0/OBPAPI5_1_0.scala +++ b/obp-api/src/main/scala/code/api/v5_1_0/OBPAPI5_1_0.scala @@ -72,11 +72,15 @@ object OBPAPI5_1_0 extends OBPRestHelper // e.g getEndpoints(Implementations5_0_0) -- List(Implementations5_0_0.genericEndpoint, Implementations5_0_0.root) lazy val endpointsOf5_1_0 = getEndpoints(Implementations5_1_0) - lazy val bugEndpoints = // these endpoints miss Provider parameter in the URL, we introduce new ones in V510. - nameOf(Implementations3_0_0.getUserByUsername) :: + lazy val bugEndpoints = + nameOf(Implementations3_0_0.getUserByUsername) :: // following 4 endpoints miss Provider parameter in the URL, we introduce new ones in V510. nameOf(Implementations3_1_0.getBadLoginStatus) :: nameOf(Implementations3_1_0.unlockUser) :: nameOf(Implementations4_0_0.lockUser) :: + nameOf(Implementations4_0_0.createUserWithAccountAccess) :: // following 4 endpoints miss ViewId parameter in the URL, we introduce new ones in V510. + nameOf(Implementations4_0_0.grantUserAccessToView) :: + nameOf(Implementations4_0_0.revokeUserAccessToView) :: + nameOf(Implementations4_0_0.revokeGrantUserAccessToViews) :: Nil // if old version ResourceDoc objects have the same name endpoint with new version, omit old version ResourceDoc. From f70207a2e3d10d93b1ff12548ee1218e380554a0 Mon Sep 17 00:00:00 2001 From: hongwei Date: Wed, 3 Apr 2024 10:55:26 +0200 Subject: [PATCH 06/11] refactor/code clean --- obp-api/src/main/scala/code/api/util/APIUtil.scala | 5 ----- 1 file changed, 5 deletions(-) diff --git a/obp-api/src/main/scala/code/api/util/APIUtil.scala b/obp-api/src/main/scala/code/api/util/APIUtil.scala index 6fd912946..509ba27d0 100644 --- a/obp-api/src/main/scala/code/api/util/APIUtil.scala +++ b/obp-api/src/main/scala/code/api/util/APIUtil.scala @@ -4924,9 +4924,4 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{ else UserLacksPermissionCanGrantAccessToCustomViewForTargetAccount + s"Current source viewId(${sourceViewId.value}) and target viewId (${targetViewId.value})" -} - - -object createDependentConnectorMethod extends App{ - } \ No newline at end of file From fda090c53b9015c26fdfb8955c51a7ad56f273ff Mon Sep 17 00:00:00 2001 From: hongwei Date: Thu, 4 Apr 2024 11:40:05 +0200 Subject: [PATCH 07/11] test/OBPv510 added the tests for AccountAccessTest --- .../main/scala/code/api/OBPRestHelper.scala | 2 +- .../SwaggerDefinitionsJSON.scala | 3 + .../main/scala/code/api/util/APIUtil.scala | 12 +- .../main/scala/code/api/util/NewStyle.scala | 8 +- .../code/api/v5_0_0/JSONFactory5.0.0.scala | 4 +- .../scala/code/api/v5_1_0/APIMethods510.scala | 58 +++-- .../code/api/v5_1_0/JSONFactory5.1.0.scala | 4 +- .../main/scala/code/model/BankingData.scala | 2 +- .../code/views/system/ViewDefinition.scala | 4 +- .../scala/code/api/v1_2_1/API1_2_1Test.scala | 2 +- .../code/api/v5_1_0/AccountAccessTest.scala | 220 ++++++++++++++++-- .../commons/model/ViewModel.scala | 5 +- 12 files changed, 263 insertions(+), 61 deletions(-) diff --git a/obp-api/src/main/scala/code/api/OBPRestHelper.scala b/obp-api/src/main/scala/code/api/OBPRestHelper.scala index 3bab099ce..36063c008 100644 --- a/obp-api/src/main/scala/code/api/OBPRestHelper.scala +++ b/obp-api/src/main/scala/code/api/OBPRestHelper.scala @@ -647,7 +647,7 @@ trait OBPRestHelper extends RestHelper with MdcLoggable { apiPrefix:OBPEndpoint => OBPEndpoint, autoValidateAll: Boolean = false): Unit = { - def isAutoValidate(doc: ResourceDoc): Boolean = { //note: only support v5.0.0 and v4.0.0 at the moment. + def isAutoValidate(doc: ResourceDoc): Boolean = { //note: only support v5.1.0, v5.0.0 and v4.0.0 at the moment. doc.isValidateEnabled || (autoValidateAll && !doc.isValidateDisabled && List(OBPAPI5_1_0.version,OBPAPI5_0_0.version,OBPAPI4_0_0.version).contains(doc.implementedInApiVersion)) } diff --git a/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/SwaggerDefinitionsJSON.scala b/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/SwaggerDefinitionsJSON.scala index 431abdcb8..5a5f6a8f8 100644 --- a/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/SwaggerDefinitionsJSON.scala +++ b/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/SwaggerDefinitionsJSON.scala @@ -5399,6 +5399,9 @@ object SwaggerDefinitionsJSON { val atmsJsonV510 = AtmsJsonV510( atms = List(atmJsonV510) ) + + val postAccountAccessJsonV510 = PostAccountAccessJsonV510(userIdExample.value,viewIdExample.value) + //The common error or success format. //Just some helper format to use in Json case class NotSupportedYet() diff --git a/obp-api/src/main/scala/code/api/util/APIUtil.scala b/obp-api/src/main/scala/code/api/util/APIUtil.scala index 509ba27d0..90c2b1ced 100644 --- a/obp-api/src/main/scala/code/api/util/APIUtil.scala +++ b/obp-api/src/main/scala/code/api/util/APIUtil.scala @@ -4083,7 +4083,7 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{ //1. if targetViewId is systemView. just compare all the permissions if(isValidSystemViewId(targetViewId.value)){ val allCanGrantAccessToViewsPermissions: List[String] = permission - .map(_.views.map(_.canGrantAccessToSystemViews.getOrElse(Nil)).flatten).getOrElse(Nil).distinct + .map(_.views.map(_.canGrantAccessToViews.getOrElse(Nil)).flatten).getOrElse(Nil).distinct allCanGrantAccessToViewsPermissions.contains(targetViewId.value) } else{ @@ -4101,7 +4101,7 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{ //2rd: f targetViewId is systemView. we need to check `view.canGrantAccessToSystemViews` field. if(isValidSystemViewId(targetViewId.value)){ - val canGrantAccessToSystemViews: Box[List[String]] = view.map(_.canGrantAccessToSystemViews.getOrElse(Nil)) + val canGrantAccessToSystemViews: Box[List[String]] = view.map(_.canGrantAccessToViews.getOrElse(Nil)) canGrantAccessToSystemViews.getOrElse(Nil).contains(targetViewId.value) } else{ //3rd. if targetViewId is customView, we need to check `view.canGrantAccessToCustomViews` field. @@ -4115,7 +4115,7 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{ val permissionBox = Views.views.vend.permission(BankIdAccountId(bankId, accountId), user) //Retrieve all views from the 'canRevokeAccessToViews' list within each view from the permission views. - val allCanGrantAccessToSystemViews: List[String] = permissionBox.map(_.views.map(_.canGrantAccessToSystemViews.getOrElse(Nil)).flatten).getOrElse(Nil).distinct + val allCanGrantAccessToSystemViews: List[String] = permissionBox.map(_.views.map(_.canGrantAccessToViews.getOrElse(Nil)).flatten).getOrElse(Nil).distinct val allSystemViewsIdsTobeGranted: List[String] = targetViewIds.map(_.value).distinct.filter(isValidSystemViewId) @@ -4139,7 +4139,7 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{ //2rd: f targetViewId is systemView. we need to check `view.canGrantAccessToSystemViews` field. if (isValidSystemViewId(targetViewId.value)) { - val canRevokeAccessToSystemViews: Box[List[String]] = view.map(_.canRevokeAccessToSystemViews.getOrElse(Nil)) + val canRevokeAccessToSystemViews: Box[List[String]] = view.map(_.canRevokeAccessToViews.getOrElse(Nil)) canRevokeAccessToSystemViews.getOrElse(Nil).contains(targetViewId.value) } else { //3rd. if targetViewId is customView, we need to check `view.canGrantAccessToCustomViews` field. @@ -4155,7 +4155,7 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{ //1. if targetViewId is systemView. just compare all the permissions if (isValidSystemViewId(targetViewId.value)) { val allCanRevokeAccessToSystemViews: List[String] = permission - .map(_.views.map(_.canRevokeAccessToSystemViews.getOrElse(Nil)).flatten).getOrElse(Nil).distinct + .map(_.views.map(_.canRevokeAccessToViews.getOrElse(Nil)).flatten).getOrElse(Nil).distinct allCanRevokeAccessToSystemViews.contains(targetViewId.value) } else { @@ -4172,7 +4172,7 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{ val permissionBox = Views.views.vend.permission(BankIdAccountId(bankId, accountId), user) //Retrieve all views from the 'canRevokeAccessToViews' list within each view from the permission views. - val allCanRevokeAccessToViews: List[String] = permissionBox.map(_.views.map(_.canRevokeAccessToSystemViews.getOrElse(Nil)).flatten).getOrElse(Nil).distinct + val allCanRevokeAccessToViews: List[String] = permissionBox.map(_.views.map(_.canRevokeAccessToViews.getOrElse(Nil)).flatten).getOrElse(Nil).distinct //All targetViewIds: val allTargetViewIds: List[String] = permissionBox.map(_.views.map(_.viewId.value)).getOrElse(Nil).distinct diff --git a/obp-api/src/main/scala/code/api/util/NewStyle.scala b/obp-api/src/main/scala/code/api/util/NewStyle.scala index 399aed7b4..a8b459b18 100644 --- a/obp-api/src/main/scala/code/api/util/NewStyle.scala +++ b/obp-api/src/main/scala/code/api/util/NewStyle.scala @@ -544,14 +544,14 @@ object NewStyle extends MdcLoggable{ Future{ APIUtil.checkViewAccessAndReturnView(viewId, bankAccountId, user, callContext) } map { - unboxFullOrFail(_, callContext, s"$UserNoPermissionAccessView") + unboxFullOrFail(_, callContext, s"$UserNoPermissionAccessView Current ViewId is ${viewId.value}") } } def checkAccountAccessAndGetView(viewId : ViewId, bankAccountId: BankIdAccountId, user: Option[User], callContext: Option[CallContext]) : Future[View] = { Future{ APIUtil.checkViewAccessAndReturnView(viewId, bankAccountId, user, callContext) } map { - unboxFullOrFail(_, callContext, s"$UserNoPermissionAccessView ${viewId.value}", 403) + unboxFullOrFail(_, callContext, s"$UserNoPermissionAccessView Current ViewId is ${viewId.value}", 403) } } def checkViewsAccessAndReturnView(firstView : ViewId, secondView : ViewId, bankAccountId: BankIdAccountId, user: Option[User], callContext: Option[CallContext]) : Future[View] = { @@ -560,7 +560,7 @@ object NewStyle extends MdcLoggable{ APIUtil.checkViewAccessAndReturnView(secondView, bankAccountId, user, callContext) ) } map { - unboxFullOrFail(_, callContext, s"$UserNoPermissionAccessView") + unboxFullOrFail(_, callContext, s"$UserNoPermissionAccessView Current ViewId is ${firstView.value} or ${secondView.value}") } } def checkBalancingTransactionAccountAccessAndReturnView(doubleEntryTransaction: DoubleEntryTransaction, user: Option[User], callContext: Option[CallContext]) : Future[View] = { @@ -578,7 +578,7 @@ object NewStyle extends MdcLoggable{ APIUtil.checkViewAccessAndReturnView(ownerViewId, creditBankAccountId, user, callContext) ) } map { - unboxFullOrFail(_, callContext, s"$UserNoPermissionAccessView") + unboxFullOrFail(_, callContext, s"$UserNoPermissionAccessView Current ViewId is ${ownerViewId.value}") } } diff --git a/obp-api/src/main/scala/code/api/v5_0_0/JSONFactory5.0.0.scala b/obp-api/src/main/scala/code/api/v5_0_0/JSONFactory5.0.0.scala index 7a9e025ec..b424dc291 100644 --- a/obp-api/src/main/scala/code/api/v5_0_0/JSONFactory5.0.0.scala +++ b/obp-api/src/main/scala/code/api/v5_0_0/JSONFactory5.0.0.scala @@ -888,8 +888,8 @@ object JSONFactory500 { can_create_direct_debit = view.canCreateDirectDebit, can_create_standing_order = view.canCreateStandingOrder, // Version 5.0.0 - can_grant_access_to_views = view.canGrantAccessToSystemViews.getOrElse(Nil), - can_revoke_access_to_views = view.canRevokeAccessToSystemViews.getOrElse(Nil), + can_grant_access_to_views = view.canGrantAccessToViews.getOrElse(Nil), + can_revoke_access_to_views = view.canRevokeAccessToViews.getOrElse(Nil), ) } def createViewsJsonV500(views : List[View]) : ViewsJsonV500 = { 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 1e1849590..5648f03f1 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 @@ -1924,17 +1924,20 @@ trait APIMethods510 { implementedInApiVersion, nameOf(grantUserAccessToViewById), "POST", - "/banks/BANK_ID/accounts/ACCOUNT_ID/VIEW_ID/account-access/grant", + "/banks/BANK_ID/accounts/ACCOUNT_ID/views/VIEW_ID/account-access/grant", "Grant User access to View", - s"""Grants the User identified by USER_ID access to the view identified by VIEW_ID. + s"""Grants the User identified by USER_ID access to the view identified. | |${authenticationRequiredMessage(true)} and the user needs to be account holder. | |""", - postAccountAccessJsonV400, + postAccountAccessJsonV510, viewJsonV300, List( $UserNotLoggedIn, + $BankNotFound, + $BankAccountNotFound, + $UserNoPermissionAccessView, UserLacksPermissionCanGrantAccessToSystemViewForTargetAccount, UserLacksPermissionCanGrantAccessToCustomViewForTargetAccount, InvalidJsonFormat, @@ -1948,22 +1951,25 @@ trait APIMethods510 { lazy val grantUserAccessToViewById: OBPEndpoint = { //add access for specific user to a specific system view - case "banks" :: BankId(bankId) :: "accounts" :: AccountId(accountId) :: ViewId(viewId):: "account-access" :: "grant" :: Nil JsonPost json -> _ => { + case "banks" :: BankId(bankId) :: "accounts" :: AccountId(accountId) ::"views":: ViewId(viewId):: "account-access" :: "grant" :: Nil JsonPost json -> _ => { cc => implicit val ec = EndpointContext(Some(cc)) - val failMsg = s"$InvalidJsonFormat The Json body should be the $PostAccountAccessJsonV400 " + val failMsg = s"$InvalidJsonFormat The Json body should be the $PostAccountAccessJsonV510 " for { (Full(u), callContext) <- SS.user postJson <- NewStyle.function.tryons(failMsg, 400, cc.callContext) { - json.extract[PostAccountAccessJsonV400] + json.extract[PostAccountAccessJsonV510] } - targetViewId = ViewId(postJson.view.view_id) + targetViewId = ViewId(postJson.view_id) msg = getUserLacksGrantPermissionErrorMessage(viewId, targetViewId) - _ <- Helper.booleanToFuture(msg, cc = cc.callContext) { + _ <- Helper.booleanToFuture(msg, 403, cc = cc.callContext) { APIUtil.canGrantAccessToView(BankIdAccountIdViewId(bankId,accountId,viewId),targetViewId, u, callContext) } (user, callContext) <- NewStyle.function.findByUserId(postJson.user_id, callContext) - view <- JSONFactory400.getView(bankId, accountId, postJson.view, callContext) + view <- isValidSystemViewId(targetViewId.value) match { + case true => NewStyle.function.systemView(targetViewId, callContext) + case false => NewStyle.function.customView(targetViewId, BankIdAccountId(bankId, accountId), callContext) + } addedView <- JSONFactory400.grantAccountAccessToUser(bankId, accountId, user, view, callContext) } yield { @@ -1979,17 +1985,20 @@ trait APIMethods510 { implementedInApiVersion, nameOf(revokeUserAccessToViewById), "POST", - "/banks/BANK_ID/accounts/ACCOUNT_ID/VIEW_ID/account-access/revoke", + "/banks/BANK_ID/accounts/ACCOUNT_ID/views/VIEW_ID/account-access/revoke", "Revoke User access to View", - s"""Revoke the User identified by USER_ID access to the view identified by VIEW_ID. + s"""Revoke the User identified by USER_ID access to the view identified. | |${authenticationRequiredMessage(true)}. | |""", - postAccountAccessJsonV400, + postAccountAccessJsonV510, revokedJsonV400, List( $UserNotLoggedIn, + $BankNotFound, + $BankAccountNotFound, + $UserNoPermissionAccessView, UserLacksPermissionCanRevokeAccessToCustomViewForTargetAccount, UserLacksPermissionCanRevokeAccessToSystemViewForTargetAccount, InvalidJsonFormat, @@ -2004,28 +2013,28 @@ trait APIMethods510 { lazy val revokeUserAccessToViewById: OBPEndpoint = { //add access for specific user to a specific system view - case "banks" :: BankId(bankId) :: "accounts" :: AccountId(accountId) :: ViewId(viewId) :: "account-access" :: "revoke" :: Nil JsonPost json -> _ => { + case "banks" :: BankId(bankId) :: "accounts" :: AccountId(accountId) :: "views" ::ViewId(viewId) :: "account-access" :: "revoke" :: Nil JsonPost json -> _ => { cc => implicit val ec = EndpointContext(Some(cc)) val failMsg = s"$InvalidJsonFormat The Json body should be the $PostAccountAccessJsonV400 " for { (Full(u), callContext) <- SS.user postJson <- NewStyle.function.tryons(failMsg, 400, cc.callContext) { - json.extract[PostAccountAccessJsonV400] + json.extract[PostAccountAccessJsonV510] } - targetViewId = ViewId(postJson.view.view_id) + targetViewId = ViewId(postJson.view_id) msg = getUserLacksRevokePermissionErrorMessage(viewId, targetViewId) - _ <- Helper.booleanToFuture(msg, cc = cc.callContext) { + _ <- Helper.booleanToFuture(msg, 403, cc = cc.callContext) { APIUtil.canRevokeAccessToView(BankIdAccountIdViewId(bankId, accountId, viewId),targetViewId, u, callContext) } (user, callContext) <- NewStyle.function.findByUserId(postJson.user_id, cc.callContext) - view <- postJson.view.is_system match { + view <- isValidSystemViewId(targetViewId.value) match { case true => NewStyle.function.systemView(targetViewId, callContext) case false => NewStyle.function.customView(targetViewId, BankIdAccountId(bankId, accountId), callContext) } - revoked <- postJson.view.is_system match { + revoked <- isValidSystemViewId(targetViewId.value) match { case true => NewStyle.function.revokeAccessToSystemView(bankId, accountId, view, user, callContext) case false => NewStyle.function.revokeAccessToCustomView(view, user, callContext) } @@ -2040,7 +2049,7 @@ trait APIMethods510 { implementedInApiVersion, nameOf(createUserWithAccountAccessById), "POST", - "/banks/BANK_ID/accounts/ACCOUNT_ID/VIEW_ID/user-account-access", + "/banks/BANK_ID/accounts/ACCOUNT_ID/views/VIEW_ID/user-account-access", "Create (DAuth) User with Account Access", s"""This endpoint is used as part of the DAuth solution to grant access to account and transaction data to a smart contract on the blockchain. | @@ -2061,6 +2070,9 @@ trait APIMethods510 { List(viewJsonV300), List( $UserNotLoggedIn, + $BankNotFound, + $BankAccountNotFound, + $UserNoPermissionAccessView, UserLacksPermissionCanGrantAccessToSystemViewForTargetAccount, UserLacksPermissionCanGrantAccessToCustomViewForTargetAccount, InvalidJsonFormat, @@ -2072,7 +2084,7 @@ trait APIMethods510 { List(apiTagAccountAccess, apiTagView, apiTagAccount, apiTagUser, apiTagOwnerRequired, apiTagDAuth)) lazy val createUserWithAccountAccessById: OBPEndpoint = { - case "banks" :: BankId(bankId) :: "accounts" :: AccountId(accountId) :: ViewId(viewId) :: "user-account-access" :: Nil JsonPost json -> _ => { + case "banks" :: BankId(bankId) :: "accounts" :: AccountId(accountId) :: "views" ::ViewId(viewId) :: "user-account-access" :: Nil JsonPost json -> _ => { cc => implicit val ec = EndpointContext(Some(cc)) val failMsg = s"$InvalidJsonFormat The Json body should be the $PostCreateUserAccountAccessJsonV510 " @@ -2085,18 +2097,18 @@ trait APIMethods510 { _ <- Helper.booleanToFuture(s"$InvalidUserProvider The user.provider must be start with 'dauth.'", cc = Some(cc)) { postJson.provider.startsWith("dauth.") } - targetViewId = ViewId(postJson.view.view_id) + targetViewId = ViewId(postJson.view_id) msg = getUserLacksGrantPermissionErrorMessage(viewId, targetViewId) _ <- Helper.booleanToFuture(msg, 403, cc = Some(cc)) { APIUtil.canGrantAccessToView(BankIdAccountIdViewId(bankId, accountId, viewId) ,targetViewId, u, callContext) } (targetUser, callContext) <- NewStyle.function.getOrCreateResourceUser(postJson.provider, postJson.username, cc.callContext) - view <- postJson.view.is_system match { + view <- isValidSystemViewId(targetViewId.value) match { case true => NewStyle.function.systemView(targetViewId, callContext) case false => NewStyle.function.customView(targetViewId, BankIdAccountId(bankId, accountId), callContext) } - addedView <- postJson.view.is_system match { + addedView <- isValidSystemViewId(targetViewId.value) match { case true => NewStyle.function.grantAccessToSystemView(bankId, accountId, view, targetUser, callContext) case false => NewStyle.function.grantAccessToCustomView(view, targetUser, callContext) } diff --git a/obp-api/src/main/scala/code/api/v5_1_0/JSONFactory5.1.0.scala b/obp-api/src/main/scala/code/api/v5_1_0/JSONFactory5.1.0.scala index 688479660..fe92e53e7 100644 --- a/obp-api/src/main/scala/code/api/v5_1_0/JSONFactory5.1.0.scala +++ b/obp-api/src/main/scala/code/api/v5_1_0/JSONFactory5.1.0.scala @@ -307,7 +307,9 @@ case class ConsumerJsonV510(consumer_id: String, created: Date ) -case class PostCreateUserAccountAccessJsonV510(username: String, provider:String, view: PostViewJsonV400) +case class PostCreateUserAccountAccessJsonV510(username: String, provider:String, view_id:String) + +case class PostAccountAccessJsonV510(user_id: String, view_id: String) object JSONFactory510 extends CustomJsonFormats { diff --git a/obp-api/src/main/scala/code/model/BankingData.scala b/obp-api/src/main/scala/code/model/BankingData.scala index e6469bf38..feb2985c9 100644 --- a/obp-api/src/main/scala/code/model/BankingData.scala +++ b/obp-api/src/main/scala/code/model/BankingData.scala @@ -209,7 +209,7 @@ case class BankAccountExtended(val bankAccount: BankAccount) extends MdcLoggable customerList.toSet } - private def viewNotAllowed(view : View ) = Failure(s"${UserNoPermissionAccessView} Current VIEW_ID (${view.viewId.value})") + private def viewNotAllowed(view : View) = Failure(s"${UserNoPermissionAccessView} Current ViewId is ${view.viewId.value}") /** * @param user the user that wants to grant another user access to a view on this account diff --git a/obp-api/src/main/scala/code/views/system/ViewDefinition.scala b/obp-api/src/main/scala/code/views/system/ViewDefinition.scala index 08201678f..336f03a69 100644 --- a/obp-api/src/main/scala/code/views/system/ViewDefinition.scala +++ b/obp-api/src/main/scala/code/views/system/ViewDefinition.scala @@ -472,7 +472,7 @@ class ViewDefinition extends View with LongKeyedMapper[ViewDefinition] with Many def hideOtherAccountMetadataIfAlias: Boolean = hideOtherAccountMetadataIfAlias_.get //This current view can grant access to other views. - override def canGrantAccessToSystemViews : Option[List[String]] = { + override def canGrantAccessToViews : Option[List[String]] = { canGrantAccessToViews_.get == null || canGrantAccessToViews_.get.isEmpty() match { case true => None case _ => Some(canGrantAccessToViews_.get.split(",").toList.map(_.trim)) @@ -482,7 +482,7 @@ class ViewDefinition extends View with LongKeyedMapper[ViewDefinition] with Many def canGrantAccessToCustomViews : Boolean = canGrantAccessToCustomViews_.get //the current view can revoke access to other views. - override def canRevokeAccessToSystemViews : Option[List[String]] = { + override def canRevokeAccessToViews : Option[List[String]] = { canRevokeAccessToViews_.get == null || canRevokeAccessToViews_.get.isEmpty() match { case true => None case _ => Some(canRevokeAccessToViews_.get.split(",").toList.map(_.trim)) diff --git a/obp-api/src/test/scala/code/api/v1_2_1/API1_2_1Test.scala b/obp-api/src/test/scala/code/api/v1_2_1/API1_2_1Test.scala index 01caebe9b..01b62b9d4 100644 --- a/obp-api/src/test/scala/code/api/v1_2_1/API1_2_1Test.scala +++ b/obp-api/src/test/scala/code/api/v1_2_1/API1_2_1Test.scala @@ -6505,7 +6505,7 @@ class API1_2_1Test extends ServerSetupWithTestData with DefaultUsers with Privat Then("we should get a 403 code") reply.code should equal (403) And("we should get an error message") - reply.body.extract[ErrorMessage].message should equal (UserNoPermissionAccessView) + reply.body.extract[ErrorMessage].message contains (UserNoPermissionAccessView) shouldBe (true) } scenario("we will not get get the other bank account of a random transaction because the transaction does not exist", API1_2_1, GetTransactionAccount){ diff --git a/obp-api/src/test/scala/code/api/v5_1_0/AccountAccessTest.scala b/obp-api/src/test/scala/code/api/v5_1_0/AccountAccessTest.scala index 0722da155..98924bb88 100644 --- a/obp-api/src/test/scala/code/api/v5_1_0/AccountAccessTest.scala +++ b/obp-api/src/test/scala/code/api/v5_1_0/AccountAccessTest.scala @@ -5,10 +5,10 @@ import code.api.ResourceDocs1_4_0.SwaggerDefinitionsJSON import code.api.ResourceDocs1_4_0.SwaggerDefinitionsJSON.createViewJsonV300 import code.api.util.APIUtil.OAuth._ import code.api.util.ApiRole -import code.api.util.ErrorMessages.{UserLacksPermissionCanGrantAccessToViewForTargetAccount, UserNotLoggedIn} +import code.api.util.ErrorMessages.{UserLacksPermissionCanGrantAccessToCustomViewForTargetAccount, UserLacksPermissionCanGrantAccessToSystemViewForTargetAccount, UserLacksPermissionCanGrantAccessToViewForTargetAccount, UserLacksPermissionCanRevokeAccessToCustomViewForTargetAccount, UserLacksPermissionCanRevokeAccessToSystemViewForTargetAccount, UserLacksPermissionCanRevokeAccessToViewForTargetAccount, UserNotLoggedIn} import code.api.v3_0_0.ViewJsonV300 import code.api.v3_1_0.CreateAccountResponseJsonV310 -import code.api.v4_0_0.{PostAccountAccessJsonV400, PostViewJsonV400} +import code.api.v4_0_0.RevokedJsonV400 import code.api.v5_1_0.OBPAPI5_1_0.Implementations5_1_0 import code.entitlement.Entitlement import com.github.dwickern.macros.NameOf.nameOf @@ -28,13 +28,16 @@ class AccountAccessTest extends V510ServerSetup { */ object VersionOfApi extends Tag(ApiVersion.v5_1_0.toString) object ApiEndpoint1 extends Tag(nameOf(Implementations5_1_0.grantUserAccessToViewById)) + object ApiEndpoint2 extends Tag(nameOf(Implementations5_1_0.revokeUserAccessToViewById)) + object ApiEndpoint3 extends Tag(nameOf(Implementations5_1_0.createUserWithAccountAccessById)) lazy val bankId = randomBankId lazy val bankAccount = randomPrivateAccountViaEndpoint(bankId) lazy val ownerView = SYSTEM_OWNER_VIEW_ID lazy val managerCustomView = SYSTEM_MANAGE_CUSTOM_VIEWS_VIEW_ID - lazy val postAccountAccessJson = PostAccountAccessJsonV400(resourceUser2.userId, PostViewJsonV400("_test_view", false)) + lazy val postAccountAccessJson = PostAccountAccessJsonV510(resourceUser2.userId, "_test_view") + lazy val postCreateUserAccountAccessJsonV510 = PostCreateUserAccountAccessJsonV510(resourceUser2.userId, "dauth."+resourceUser2.provider, "_test_view") lazy val postBodyViewJson = createViewJsonV300.toCreateViewJson def createAnAccount(bankId: String, user: Option[(Consumer,Token)]): CreateAccountResponseJsonV310 = { @@ -55,7 +58,7 @@ class AccountAccessTest extends V510ServerSetup { scenario("We will call the endpoint without user credentials", ApiEndpoint1, VersionOfApi) { When("We make a request v4.0.0") - val request510 = (v5_1_0_Request / "banks" / bankId / "accounts" / bankAccount.id / ownerView /"account-access" / "grant").POST + val request510 = (v5_1_0_Request / "banks" / bankId / "accounts" / bankAccount.id /"views" / ownerView /"account-access" / "grant").POST val response510 = makePostRequest(request510, write(postAccountAccessJson)) Then("We should get a 401") response510.code should equal(401) @@ -71,13 +74,13 @@ class AccountAccessTest extends V510ServerSetup { } val view = createViewForAnAccount(bankId, account.account_id) - val postJson = PostAccountAccessJsonV400(resourceUser2.userId, PostViewJsonV400(view.id, view.is_system)) + val postJson = PostAccountAccessJsonV510(resourceUser2.userId, view.id) When("We send the request") - val request = (v5_1_0_Request / "banks" / bankId / "accounts" / account.account_id / ownerView / "account-access" / "grant").POST <@ (user1) + val request = (v5_1_0_Request / "banks" / bankId / "accounts" / account.account_id /"views"/ ownerView / "account-access" / "grant").POST <@ (user1) val response = makePostRequest(request, write(postJson)) - Then("We should get a 400 and check the response body") - response.code should equal(400) - response.body.toString.contains(UserLacksPermissionCanGrantAccessToViewForTargetAccount) + Then("We should get a 403 and check the response body") + response.code should equal(403) + response.body.toString.contains(UserLacksPermissionCanGrantAccessToCustomViewForTargetAccount) should be (true) } scenario("We will call the endpoint with user credentials and managerCustomView view, but try to grant system view access", VersionOfApi, ApiEndpoint1) { @@ -88,13 +91,13 @@ class AccountAccessTest extends V510ServerSetup { Entitlement.entitlement.vend.deleteEntitlement(addedEntitlement) } - val postJson = PostAccountAccessJsonV400(resourceUser2.userId, PostViewJsonV400(SYSTEM_AUDITOR_VIEW_ID, true)) + val postJson = PostAccountAccessJsonV510(resourceUser2.userId, SYSTEM_AUDITOR_VIEW_ID) When("We send the request") - val request = (v5_1_0_Request / "banks" / bankId / "accounts" / account.account_id / managerCustomView / "account-access" / "grant").POST <@ (user1) + val request = (v5_1_0_Request / "banks" / bankId / "accounts" / account.account_id /"views"/ managerCustomView / "account-access" / "grant").POST <@ (user1) val response = makePostRequest(request, write(postJson)) - Then("We should get a 400 and check the response body") - response.code should equal(400) - response.body.toString.contains(UserLacksPermissionCanGrantAccessToViewForTargetAccount) + Then("We should get a 403 and check the response body") + response.code should equal(403) + response.body.toString.contains(UserLacksPermissionCanGrantAccessToSystemViewForTargetAccount) should be (true) } scenario("We will call the endpoint with user credentials and system view permission", VersionOfApi, ApiEndpoint1) { @@ -105,9 +108,9 @@ class AccountAccessTest extends V510ServerSetup { Entitlement.entitlement.vend.deleteEntitlement(addedEntitlement) } - val postJson = PostAccountAccessJsonV400(resourceUser2.userId, PostViewJsonV400(SYSTEM_AUDITOR_VIEW_ID, true)) + val postJson = PostAccountAccessJsonV510(resourceUser2.userId, SYSTEM_AUDITOR_VIEW_ID) When("We send the request") - val request = (v5_1_0_Request / "banks" / bankId / "accounts" / account.account_id / ownerView / "account-access" / "grant").POST <@ (user1) + val request = (v5_1_0_Request / "banks" / bankId / "accounts" / account.account_id /"views"/ ownerView / "account-access" / "grant").POST <@ (user1) val response = makePostRequest(request, write(postJson)) Then("We should get a 201 and check the response body") response.code should equal(201) @@ -123,9 +126,190 @@ class AccountAccessTest extends V510ServerSetup { } val view = createViewForAnAccount(bankId, account.account_id) - val postJson = PostAccountAccessJsonV400(resourceUser2.userId, PostViewJsonV400(view.id, view.is_system)) + val postJson = PostAccountAccessJsonV510(resourceUser2.userId, view.id) When("We send the request") - val request = (v5_1_0_Request / "banks" / bankId / "accounts" / account.account_id / managerCustomView / "account-access" / "grant").POST <@ (user1) + val request = (v5_1_0_Request / "banks" / bankId / "accounts" / account.account_id /"views"/ managerCustomView / "account-access" / "grant").POST <@ (user1) + val response = makePostRequest(request, write(postJson)) + Then("We should get a 201 and check the response body") + response.code should equal(201) + response.body.extract[ViewJsonV300] + } + } + + feature(s"test $ApiEndpoint2 Authorized access") { + + scenario("We will call the endpoint without user credentials", ApiEndpoint2, VersionOfApi) { + When("We make a request v4.0.0") + val request510 = (v5_1_0_Request / "banks" / bankId / "accounts" / bankAccount.id /"views" / ownerView /"account-access" / "revoke").POST + val response510 = makePostRequest(request510, write(postAccountAccessJson)) + Then("We should get a 401") + response510.code should equal(401) + response510.body.extract[ErrorMessage].message should equal(UserNotLoggedIn) + } + + scenario("We will call the endpoint with user credentials and system view, but try to grant custom view access", VersionOfApi, ApiEndpoint1) { + val addedEntitlement: Box[Entitlement] = Entitlement.entitlement.vend.addEntitlement(bankId, resourceUser1.userId, ApiRole.CanCreateAccount.toString) + val account = try { + createAnAccount(bankId, user1) + } finally { + Entitlement.entitlement.vend.deleteEntitlement(addedEntitlement) + } + + val view = createViewForAnAccount(bankId, account.account_id) + val postJson = PostAccountAccessJsonV510(resourceUser2.userId, view.id) + When("We send the request") + val request = (v5_1_0_Request / "banks" / bankId / "accounts" / account.account_id /"views"/ ownerView / "account-access" / "revoke").POST <@ (user1) + val response = makePostRequest(request, write(postJson)) + Then("We should get a 403 and check the response body") + response.code should equal(403) + response.body.toString.contains(UserLacksPermissionCanRevokeAccessToCustomViewForTargetAccount) should be (true) + } + + scenario("We will call the endpoint with user credentials and managerCustomView view, but try to revoke system view access", VersionOfApi, ApiEndpoint1) { + val addedEntitlement: Box[Entitlement] = Entitlement.entitlement.vend.addEntitlement(bankId, resourceUser1.userId, ApiRole.CanCreateAccount.toString) + val account = try { + createAnAccount(bankId, user1) + } finally { + Entitlement.entitlement.vend.deleteEntitlement(addedEntitlement) + } + + val postJson = PostAccountAccessJsonV510(resourceUser2.userId, SYSTEM_AUDITOR_VIEW_ID) + When("We send the request") + val request = (v5_1_0_Request / "banks" / bankId / "accounts" / account.account_id /"views"/ managerCustomView / "account-access" / "revoke").POST <@ (user1) + val response = makePostRequest(request, write(postJson)) + Then("We should get a 403 and check the response body") + response.code should equal(403) + response.body.toString.contains(UserLacksPermissionCanRevokeAccessToSystemViewForTargetAccount) should be (true) + } + + scenario("We will call the endpoint with user credentials and system view permission", VersionOfApi, ApiEndpoint1) { + val addedEntitlement: Box[Entitlement] = Entitlement.entitlement.vend.addEntitlement(bankId, resourceUser1.userId, ApiRole.CanCreateAccount.toString) + val account = try { + createAnAccount(bankId, user1) + } finally { + Entitlement.entitlement.vend.deleteEntitlement(addedEntitlement) + } + + val postJson = PostAccountAccessJsonV510(resourceUser2.userId, SYSTEM_AUDITOR_VIEW_ID) + + When("We 1st grant the account access the request") + val request = (v5_1_0_Request / "banks" / bankId / "accounts" / account.account_id /"views"/ ownerView / "account-access" / "grant").POST <@ (user1) + val responseGrant = makePostRequest(request, write(postJson)) + Then("We should get a 201 and check the response body") + responseGrant.code should equal(201) + responseGrant.body.extract[ViewJsonV300] + + When("We send the Revoke request") + val requestRevoke = (v5_1_0_Request / "banks" / bankId / "accounts" / account.account_id /"views"/ ownerView / "account-access" / "revoke").POST <@ (user1) + val response = makePostRequest(requestRevoke, write(postJson)) + Then("We should get a 201 and check the response body") + response.code should equal(201) + response.body.extract[RevokedJsonV400].revoked should be (true) + } + + scenario("We will call the endpoint with user credentials and custom view permission", VersionOfApi, ApiEndpoint1) { + val addedEntitlement: Box[Entitlement] = Entitlement.entitlement.vend.addEntitlement(bankId, resourceUser1.userId, ApiRole.CanCreateAccount.toString) + val account = try { + createAnAccount(bankId, user1) + } finally { + Entitlement.entitlement.vend.deleteEntitlement(addedEntitlement) + } + + val view = createViewForAnAccount(bankId, account.account_id) + val postJson = PostAccountAccessJsonV510(resourceUser2.userId, view.id) + val requestGrant = (v5_1_0_Request / "banks" / bankId / "accounts" / account.account_id /"views"/ managerCustomView / "account-access" / "grant").POST <@ (user1) + + When("We 1st grant the account access the request") + val responseGrant = makePostRequest(requestGrant, write(postJson)) + Then("We should get a 201 and check the response body") + responseGrant.code should equal(201) + responseGrant.body.extract[ViewJsonV300] + + When("We send the Revoke request") + val requestRevoke = (v5_1_0_Request / "banks" / bankId / "accounts" / account.account_id /"views"/ managerCustomView / "account-access" / "revoke").POST <@ (user1) + val response = makePostRequest(requestRevoke, write(postJson)) + Then("We should get a 201 and check the response body") + response.code should equal(201) + response.body.extract[RevokedJsonV400].revoked should be (true) + } + } + + feature(s"test $ApiEndpoint3 Authorized access") { + + scenario("We will call the endpoint without user credentials", ApiEndpoint1, VersionOfApi) { + When("We make a request v4.0.0") + val request510 = (v5_1_0_Request / "banks" / bankId / "accounts" / bankAccount.id /"views" / ownerView /"user-account-access").POST + val response510 = makePostRequest(request510, write(postAccountAccessJson)) + Then("We should get a 401") + response510.code should equal(401) + response510.body.extract[ErrorMessage].message should equal(UserNotLoggedIn) + } + + scenario("We will call the endpoint with user credentials and system view, but try to grant custom view access", VersionOfApi, ApiEndpoint1) { + val addedEntitlement: Box[Entitlement] = Entitlement.entitlement.vend.addEntitlement(bankId, resourceUser1.userId, ApiRole.CanCreateAccount.toString) + val account = try { + createAnAccount(bankId, user1) + } finally { + Entitlement.entitlement.vend.deleteEntitlement(addedEntitlement) + } + + val view = createViewForAnAccount(bankId, account.account_id) + val postJson = PostCreateUserAccountAccessJsonV510(resourceUser2.userId, "dauth."+resourceUser2.provider, view.id) + + When("We send the request") + val request = (v5_1_0_Request / "banks" / bankId / "accounts" / account.account_id /"views"/ ownerView / "user-account-access").POST <@ (user1) + val response = makePostRequest(request, write(postJson)) + Then("We should get a 403 and check the response body") + response.code should equal(403) + response.body.toString.contains(UserLacksPermissionCanGrantAccessToCustomViewForTargetAccount) should be (true) + } + + scenario("We will call the endpoint with user credentials and managerCustomView view, but try to grant system view access", VersionOfApi, ApiEndpoint1) { + val addedEntitlement: Box[Entitlement] = Entitlement.entitlement.vend.addEntitlement(bankId, resourceUser1.userId, ApiRole.CanCreateAccount.toString) + val account = try { + createAnAccount(bankId, user1) + } finally { + Entitlement.entitlement.vend.deleteEntitlement(addedEntitlement) + } + + val postJson = PostCreateUserAccountAccessJsonV510(resourceUser2.userId, "dauth."+resourceUser2.provider, ownerView) + When("We send the request") + val request = (v5_1_0_Request / "banks" / bankId / "accounts" / account.account_id /"views"/ managerCustomView / "user-account-access").POST <@ (user1) + val response = makePostRequest(request, write(postJson)) + Then("We should get a 403 and check the response body") + response.code should equal(403) + response.body.toString.contains(UserLacksPermissionCanGrantAccessToSystemViewForTargetAccount) should be (true) + } + + scenario("We will call the endpoint with user credentials and system view permission", VersionOfApi, ApiEndpoint1) { + val addedEntitlement: Box[Entitlement] = Entitlement.entitlement.vend.addEntitlement(bankId, resourceUser1.userId, ApiRole.CanCreateAccount.toString) + val account = try { + createAnAccount(bankId, user1) + } finally { + Entitlement.entitlement.vend.deleteEntitlement(addedEntitlement) + } + + val postJson = PostCreateUserAccountAccessJsonV510(resourceUser2.userId,"dauth."+resourceUser2.provider, SYSTEM_AUDITOR_VIEW_ID) + When("We send the request") + val request = (v5_1_0_Request / "banks" / bankId / "accounts" / account.account_id /"views"/ ownerView / "user-account-access").POST <@ (user1) + val response = makePostRequest(request, write(postJson)) + Then("We should get a 201 and check the response body") + response.code should equal(201) + response.body.extract[ViewJsonV300] + } + + scenario("We will call the endpoint with user credentials and custom view permission", VersionOfApi, ApiEndpoint1) { + val addedEntitlement: Box[Entitlement] = Entitlement.entitlement.vend.addEntitlement(bankId, resourceUser1.userId, ApiRole.CanCreateAccount.toString) + val account = try { + createAnAccount(bankId, user1) + } finally { + Entitlement.entitlement.vend.deleteEntitlement(addedEntitlement) + } + + val view = createViewForAnAccount(bankId, account.account_id) + val postJson = PostCreateUserAccountAccessJsonV510(resourceUser2.userId,"dauth."+resourceUser2.provider, view.id) + When("We send the request") + val request = (v5_1_0_Request / "banks" / bankId / "accounts" / account.account_id /"views"/ managerCustomView / "user-account-access").POST <@ (user1) val response = makePostRequest(request, write(postJson)) Then("We should get a 201 and check the response body") response.code should equal(201) diff --git a/obp-commons/src/main/scala/com/openbankproject/commons/model/ViewModel.scala b/obp-commons/src/main/scala/com/openbankproject/commons/model/ViewModel.scala index 84ae7e026..45147f229 100644 --- a/obp-commons/src/main/scala/com/openbankproject/commons/model/ViewModel.scala +++ b/obp-commons/src/main/scala/com/openbankproject/commons/model/ViewModel.scala @@ -251,9 +251,10 @@ trait View { def hideOtherAccountMetadataIfAlias: Boolean - def canGrantAccessToSystemViews : Option[List[String]] = None + def canGrantAccessToViews : Option[List[String]] = None + def canRevokeAccessToViews : Option[List[String]] = None + def canGrantAccessToCustomViews : Boolean // if this true, we can grant custom views, if it is false, no one can grant custom views. - def canRevokeAccessToSystemViews : Option[List[String]] = None def canRevokeAccessToCustomViews : Boolean // if this true, we can revoke custom views,if it is false, no one can revoke custom views. //reading access From 33ab82a0f3b89bd8f39fef36c4966bc34919e44d Mon Sep 17 00:00:00 2001 From: hongwei Date: Thu, 4 Apr 2024 12:04:10 +0200 Subject: [PATCH 08/11] test/fixed the failed test --- .../src/main/scala/code/api/util/APIUtil.scala | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/obp-api/src/main/scala/code/api/util/APIUtil.scala b/obp-api/src/main/scala/code/api/util/APIUtil.scala index 90c2b1ced..e1e7b4612 100644 --- a/obp-api/src/main/scala/code/api/util/APIUtil.scala +++ b/obp-api/src/main/scala/code/api/util/APIUtil.scala @@ -4168,29 +4168,32 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{ @deprecated("now need bankIdAccountIdViewId and targetViewId explicitly","02-04-2024") def canRevokeAccessToAllViews(bankId: BankId, accountId: AccountId, user: User, callContext: Option[CallContext]): Boolean = { - + val permissionBox = Views.views.vend.permission(BankIdAccountId(bankId, accountId), user) - + //Retrieve all views from the 'canRevokeAccessToViews' list within each view from the permission views. val allCanRevokeAccessToViews: List[String] = permissionBox.map(_.views.map(_.canRevokeAccessToViews.getOrElse(Nil)).flatten).getOrElse(Nil).distinct - + //All targetViewIds: val allTargetViewIds: List[String] = permissionBox.map(_.views.map(_.viewId.value)).getOrElse(Nil).distinct - + val allSystemTargetViewIs: List[String] = allTargetViewIds.filter(isValidSystemViewId) - + val canRevokeAccessToAllSystemTargetViews = allSystemTargetViewIs.forall(allCanRevokeAccessToViews.contains) //if allTargetViewIds contains customViewId,we need to check both `canRevokeAccessToCustomViews` and `canRevokeAccessToSystemViews` fields - if (allTargetViewIds.find(isValidCustomViewId).isDefined){ + if (allTargetViewIds.find(isValidCustomViewId).isDefined) { //check if we can revoke all customViews Access val allCanRevokeAccessToCustomViewsPermissions: List[Boolean] = permissionBox.map(_.views.map(_.canRevokeAccessToCustomViews)).getOrElse(Nil) val canRevokeAccessToAllCustomViews = allCanRevokeAccessToCustomViewsPermissions.contains(true) //we need merge both system and custom access canRevokeAccessToAllSystemTargetViews && canRevokeAccessToAllCustomViews - }else + } else if (allTargetViewIds.find(isValidSystemViewId).isDefined) { canRevokeAccessToAllSystemTargetViews + } else {//if both allCanRevokeAccessToViews and allSystemTargetViewIs are empty, + false } + } def getJValueFromJsonFile(path: String) = { val stream = getClass().getClassLoader().getResourceAsStream(path) From 25a5ccbbe76b1ed15e1cdd6f0ae8404908099dd3 Mon Sep 17 00:00:00 2001 From: hongwei Date: Thu, 4 Apr 2024 13:54:58 +0200 Subject: [PATCH 09/11] refactor/tweak the comments --- obp-api/src/main/scala/code/api/util/APIUtil.scala | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/obp-api/src/main/scala/code/api/util/APIUtil.scala b/obp-api/src/main/scala/code/api/util/APIUtil.scala index e1e7b4612..c9c6675bd 100644 --- a/obp-api/src/main/scala/code/api/util/APIUtil.scala +++ b/obp-api/src/main/scala/code/api/util/APIUtil.scala @@ -4080,7 +4080,7 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{ //all the permission this user have for the bankAccount val permission: Box[Permission] = Views.views.vend.permission(BankIdAccountId(bankId, accountId), user) - //1. if targetViewId is systemView. just compare all the permissions + //1. If targetViewId is systemView. just compare all the permissions if(isValidSystemViewId(targetViewId.value)){ val allCanGrantAccessToViewsPermissions: List[String] = permission .map(_.views.map(_.canGrantAccessToViews.getOrElse(Nil)).flatten).getOrElse(Nil).distinct @@ -4099,7 +4099,7 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{ //1st: get the view val view: Box[View] = Views.views.vend.getViewByBankIdAccountIdViewIdUserPrimaryKey(bankIdAccountIdViewId, user.userPrimaryKey) - //2rd: f targetViewId is systemView. we need to check `view.canGrantAccessToSystemViews` field. + //2rd: If targetViewId is systemView. we need to check `view.canGrantAccessToViews` field. if(isValidSystemViewId(targetViewId.value)){ val canGrantAccessToSystemViews: Box[List[String]] = view.map(_.canGrantAccessToViews.getOrElse(Nil)) canGrantAccessToSystemViews.getOrElse(Nil).contains(targetViewId.value) @@ -4137,7 +4137,7 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{ //1st: get the view val view: Box[View] = Views.views.vend.getViewByBankIdAccountIdViewIdUserPrimaryKey(bankIdAccountIdViewId, user.userPrimaryKey) - //2rd: f targetViewId is systemView. we need to check `view.canGrantAccessToSystemViews` field. + //2rd: If targetViewId is systemView. we need to check `view.canGrantAccessToViews` field. if (isValidSystemViewId(targetViewId.value)) { val canRevokeAccessToSystemViews: Box[List[String]] = view.map(_.canRevokeAccessToViews.getOrElse(Nil)) canRevokeAccessToSystemViews.getOrElse(Nil).contains(targetViewId.value) @@ -4152,7 +4152,7 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{ //all the permission this user have for the bankAccount val permission: Box[Permission] = Views.views.vend.permission(BankIdAccountId(bankId, accountId), user) - //1. if targetViewId is systemView. just compare all the permissions + //1. If targetViewId is systemView. just compare all the permissions if (isValidSystemViewId(targetViewId.value)) { val allCanRevokeAccessToSystemViews: List[String] = permission .map(_.views.map(_.canRevokeAccessToViews.getOrElse(Nil)).flatten).getOrElse(Nil).distinct From 40009182478905ece1e45a7b39fdcf532880577d Mon Sep 17 00:00:00 2001 From: hongwei Date: Thu, 4 Apr 2024 14:04:13 +0200 Subject: [PATCH 10/11] refactor/tweaked the comments --- obp-api/src/main/scala/code/api/v5_1_0/OBPAPI5_1_0.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/obp-api/src/main/scala/code/api/v5_1_0/OBPAPI5_1_0.scala b/obp-api/src/main/scala/code/api/v5_1_0/OBPAPI5_1_0.scala index 5d4971fd0..ea204ee7d 100644 --- a/obp-api/src/main/scala/code/api/v5_1_0/OBPAPI5_1_0.scala +++ b/obp-api/src/main/scala/code/api/v5_1_0/OBPAPI5_1_0.scala @@ -77,10 +77,10 @@ object OBPAPI5_1_0 extends OBPRestHelper nameOf(Implementations3_1_0.getBadLoginStatus) :: nameOf(Implementations3_1_0.unlockUser) :: nameOf(Implementations4_0_0.lockUser) :: - nameOf(Implementations4_0_0.createUserWithAccountAccess) :: // following 4 endpoints miss ViewId parameter in the URL, we introduce new ones in V510. + nameOf(Implementations4_0_0.createUserWithAccountAccess) :: // following 3 endpoints miss ViewId parameter in the URL, we introduce new ones in V510. nameOf(Implementations4_0_0.grantUserAccessToView) :: nameOf(Implementations4_0_0.revokeUserAccessToView) :: - nameOf(Implementations4_0_0.revokeGrantUserAccessToViews) :: + nameOf(Implementations4_0_0.revokeGrantUserAccessToViews) ::// this endpoint is forbidden in V510, we do not support multi views in one endpoint from V510. Nil // if old version ResourceDoc objects have the same name endpoint with new version, omit old version ResourceDoc. From 613c06d07607f10629f53748742b8dc201f6139e Mon Sep 17 00:00:00 2001 From: hongwei Date: Thu, 4 Apr 2024 14:30:05 +0200 Subject: [PATCH 11/11] refactor/rename bugEndpoints to excludeEndpoints --- obp-api/src/main/scala/code/api/v5_1_0/OBPAPI5_1_0.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/obp-api/src/main/scala/code/api/v5_1_0/OBPAPI5_1_0.scala b/obp-api/src/main/scala/code/api/v5_1_0/OBPAPI5_1_0.scala index ea204ee7d..804e2eb5e 100644 --- a/obp-api/src/main/scala/code/api/v5_1_0/OBPAPI5_1_0.scala +++ b/obp-api/src/main/scala/code/api/v5_1_0/OBPAPI5_1_0.scala @@ -72,7 +72,7 @@ object OBPAPI5_1_0 extends OBPRestHelper // e.g getEndpoints(Implementations5_0_0) -- List(Implementations5_0_0.genericEndpoint, Implementations5_0_0.root) lazy val endpointsOf5_1_0 = getEndpoints(Implementations5_1_0) - lazy val bugEndpoints = + lazy val excludeEndpoints = nameOf(Implementations3_0_0.getUserByUsername) :: // following 4 endpoints miss Provider parameter in the URL, we introduce new ones in V510. nameOf(Implementations3_1_0.getBadLoginStatus) :: nameOf(Implementations3_1_0.unlockUser) :: @@ -87,7 +87,7 @@ object OBPAPI5_1_0 extends OBPRestHelper def allResourceDocs = collectResourceDocs( OBPAPI5_0_0.allResourceDocs, Implementations5_1_0.resourceDocs - ).filterNot(it => it.partialFunctionName.matches(bugEndpoints.mkString("|"))) + ).filterNot(it => it.partialFunctionName.matches(excludeEndpoints.mkString("|"))) // all endpoints private val endpoints: List[OBPEndpoint] = OBPAPI5_0_0.routes ++ endpointsOf5_1_0