Skip to content

Commit

Permalink
Merge pull request #2371 from hongwei1/develop
Browse files Browse the repository at this point in the history
Feature/OBPv510 refactor the Account Access endpoints
  • Loading branch information
simonredfern authored Apr 4, 2024
2 parents f90bbad + 613c06d commit 32e9fd2
Show file tree
Hide file tree
Showing 21 changed files with 505 additions and 104 deletions.
2 changes: 1 addition & 1 deletion obp-api/src/main/scala/code/api/OBPRestHelper.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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))
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
124 changes: 81 additions & 43 deletions obp-api/src/main/scala/code/api/util/APIUtil.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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)
}
}


@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)

//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){
//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) = {
Expand Down Expand Up @@ -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{

}
15 changes: 14 additions & 1 deletion obp-api/src/main/scala/code/api/util/ErrorMessages.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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!"
Expand Down Expand Up @@ -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."
Expand Down
8 changes: 4 additions & 4 deletions obp-api/src/main/scala/code/api/util/NewStyle.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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] = {
Expand All @@ -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] = {
Expand All @@ -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}")
}
}

Expand Down
2 changes: 1 addition & 1 deletion obp-api/src/main/scala/code/api/v1_2_1/APIMethods121.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion obp-api/src/main/scala/code/api/v2_2_0/APIMethods220.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
2 changes: 1 addition & 1 deletion obp-api/src/main/scala/code/api/v3_0_0/APIMethods300.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
2 changes: 1 addition & 1 deletion obp-api/src/main/scala/code/api/v3_1_0/APIMethods310.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading

0 comments on commit 32e9fd2

Please sign in to comment.