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 c2bc7b8e8..c9c6675bd 100644 --- a/obp-api/src/main/scala/code/api/util/APIUtil.scala +++ b/obp-api/src/main/scala/code/api/util/APIUtil.scala @@ -4059,22 +4059,29 @@ 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 isValidSystemViewId(viewId: String): Boolean = isValidSystemViewName(viewId: 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 isValidCustomViewId(viewId: String): Boolean = isValidCustomViewName(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 isValidCustomViewName(name: String): Boolean = name match { case x if x.startsWith("_") => true // Allowed case case _ => false } + @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 targetViewId is systemView. just compare all the permissions - if(checkSystemViewIdOrName(targetViewId.value)){ + //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 @@ -4092,75 +4099,100 @@ 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: 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) } else{ //3rd. if targetViewId is customView, we need to check `view.canGrantAccessToCustomViews` field. 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) - //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(_.canGrantAccessToViews.getOrElse(Nil)).flatten).getOrElse(Nil).distinct + + val allSystemViewsIdsTobeGranted: List[String] = targetViewIds.map(_.value).distinct.filter(isValidSystemViewId) + + val canGrantAllSystemViewsIdsTobeGranted = allSystemViewsIdsTobeGranted.forall(allCanGrantAccessToSystemViews.contains) - if (viewIdsTobeGranted.map(_.value).distinct.find(checkCustomViewIdOrName).isDefined){ - //check if we can grant all customViews Access + //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 - canGrantAccessToAllSystemViews && canGrantAccessToAllCustomViews + canGrantAllSystemViewsIdsTobeGranted && canGrantAccessToAllCustomViews + } 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: 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) } else { - canGrantAccessToAllSystemViews + //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 (checkSystemViewIdOrName(viewIdToBeRevoked.value)) { - val allCanRevokeAccessToViewsPermissions: List[String] = permission + //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 - allCanRevokeAccessToViewsPermissions.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) } } - - def canRevokeAccessToAllViews(bankId: BankId, accountId: AccountId, user: User, callContext: Option[CallContext]): Boolean = { - - 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) - if (allAccountAccessSystemViews.find(checkCustomViewIdOrName).isDefined){ + @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) { //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{ + canRevokeAccessToAllSystemTargetViews && canRevokeAccessToAllCustomViews + } else if (allTargetViewIds.find(isValidSystemViewId).isDefined) { + canRevokeAccessToAllSystemTargetViews + } else {//if both allCanRevokeAccessToViews and allSystemTargetViewIs are empty, false } - } def getJValueFromJsonFile(path: String) = { @@ -4883,10 +4915,16 @@ 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})" - -object createDependentConnectorMethod extends App{ - } \ No newline at end of file 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..500c7c97d 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!" @@ -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." 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/v1_2_1/APIMethods121.scala b/obp-api/src/main/scala/code/api/v1_2_1/APIMethods121.scala index 891b4eb63..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(checkCustomViewIdOrName(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 f35fc4223..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(checkCustomViewIdOrName(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 387ff149a..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) { - checkCustomViewIdOrName(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 6448a4596..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) { - checkSystemViewIdOrName(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 c9688837b..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) { - checkSystemViewIdOrName(createViewJson.name) + isValidSystemViewName(createViewJson.name) } view <- NewStyle.function.createSystemView(createViewJson.toCreateViewJson, cc.callContext) } yield { 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..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 @@ -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 @@ -1924,18 +1924,22 @@ trait APIMethods510 { implementedInApiVersion, nameOf(grantUserAccessToViewById), "POST", - "/banks/BANK_ID/accounts/ACCOUNT_ID/VIEW_ID/account-access/grant", - "Grant User access to View By Id", - s"""Grants the User identified by USER_ID access to the view identified by VIEW_ID. + "/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. | |${authenticationRequiredMessage(true)} and the user needs to be account holder. | |""", - postAccountAccessJsonV400, + postAccountAccessJsonV510, viewJsonV300, List( $UserNotLoggedIn, - UserLacksPermissionCanGrantAccessToViewForTargetAccount, + $BankNotFound, + $BankAccountNotFound, + $UserNoPermissionAccessView, + UserLacksPermissionCanGrantAccessToSystemViewForTargetAccount, + UserLacksPermissionCanGrantAccessToCustomViewForTargetAccount, InvalidJsonFormat, UserNotFoundById, SystemViewNotFound, @@ -1947,25 +1951,170 @@ 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 $PostAccountAccessJsonV510 " + for { + (Full(u), callContext) <- SS.user + postJson <- NewStyle.function.tryons(failMsg, 400, cc.callContext) { + json.extract[PostAccountAccessJsonV510] + } + targetViewId = ViewId(postJson.view_id) + msg = getUserLacksGrantPermissionErrorMessage(viewId, targetViewId) + _ <- 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 <- 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 { + val viewJson = JSONFactory300.createViewJSON(addedView) + (viewJson, HttpCode.`201`(callContext)) + } + } + } + + + staticResourceDocs += ResourceDoc( + revokeUserAccessToViewById, + implementedInApiVersion, + nameOf(revokeUserAccessToViewById), + "POST", + "/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. + | + |${authenticationRequiredMessage(true)}. + | + |""", + postAccountAccessJsonV510, + revokedJsonV400, + List( + $UserNotLoggedIn, + $BankNotFound, + $BankAccountNotFound, + $UserNoPermissionAccessView, + 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) :: "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] } - _ <- 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) - } - (user, callContext) <- NewStyle.function.findByUserId(postJson.user_id, callContext) - view <- JSONFactory400.getView(bankId, accountId, postJson.view, callContext) - addedView <- JSONFactory400.grantAccountAccessToUser(bankId, accountId, user, view, callContext) + targetViewId = ViewId(postJson.view_id) + + msg = getUserLacksRevokePermissionErrorMessage(viewId, targetViewId) + _ <- 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 <- isValidSystemViewId(targetViewId.value) match { + case true => NewStyle.function.systemView(targetViewId, callContext) + case false => NewStyle.function.customView(targetViewId, BankIdAccountId(bankId, accountId), callContext) + } + revoked <- isValidSystemViewId(targetViewId.value) match { + case true => NewStyle.function.revokeAccessToSystemView(bankId, accountId, view, user, callContext) + case false => NewStyle.function.revokeAccessToCustomView(view, user, callContext) + } } yield { - val viewJson = JSONFactory300.createViewJSON(addedView) - (viewJson, HttpCode.`201`(callContext)) + (RevokedJsonV400(revoked), HttpCode.`201`(callContext)) + } + } + } + + staticResourceDocs += ResourceDoc( + createUserWithAccountAccessById, + implementedInApiVersion, + nameOf(createUserWithAccountAccessById), + "POST", + "/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. + | + |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, + $BankNotFound, + $BankAccountNotFound, + $UserNoPermissionAccessView, + 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) :: "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 " + 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_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 <- isValidSystemViewId(targetViewId.value) match { + case true => NewStyle.function.systemView(targetViewId, callContext) + case false => NewStyle.function.customView(targetViewId, BankIdAccountId(bankId, accountId), callContext) + } + addedView <- isValidSystemViewId(targetViewId.value) 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..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 @@ -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,10 @@ case class ConsumerJsonV510(consumer_id: String, created: Date ) +case class PostCreateUserAccountAccessJsonV510(username: String, provider:String, view_id:String) + +case class PostAccountAccessJsonV510(user_id: String, view_id: String) + 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..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,18 +72,22 @@ 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 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) :: nameOf(Implementations4_0_0.lockUser) :: + 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) ::// 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. 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 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/MapperViews.scala b/obp-api/src/main/scala/code/views/MapperViews.scala index 242dd6d56..17949d580 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 (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 (!checkSystemViewIdOrName(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(!checkCustomViewIdOrName(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 ac0094101..336f03a69 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.{isValidCustomViewId, isValidCustomViewName, isValidSystemViewId} 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 } @@ -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 && !isValidSystemViewId(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 && !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 b8c5f7201..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 @@ -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.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=> checkSystemViewIdOrName(view.id)) + .filterNot(view=> isValidSystemViewId(view.id)) val randomPosition = nextInt(possibleViewsPermalinks.size) possibleViewsPermalinks(randomPosition).id } @@ -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-api/src/test/scala/code/setup/TestConnectorSetupWithStandardPermissions.scala b/obp-api/src/test/scala/code/setup/TestConnectorSetupWithStandardPermissions.scala index a1ec88473..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.checkCustomViewIdOrName +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 (!checkCustomViewIdOrName(viewName)) { + if (!isValidCustomViewName(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..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 @@ -250,10 +250,11 @@ 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 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 canGrantAccessToCustomViews : Boolean // if this true, we can grant custom views, if it is false, no one can grant custom views. def canRevokeAccessToCustomViews : Boolean // if this true, we can revoke custom views,if it is false, no one can revoke custom views. //reading access