Merge pull request #2200 from hongwei1/develop

refactor/use viewId instead of view_fk for accountAccess
This commit is contained in:
Simon Redfern 2023-03-09 11:00:36 +01:00 committed by GitHub
commit 95cd455ea6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 340 additions and 270 deletions

View File

@ -3851,8 +3851,14 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
if(range1.end.before(range2.start) || range1.start.after(range2.end)) false else true
}
def checkCustomViewName(name: String): Boolean = name match {
case x if x == "_" + SYSTEM_OWNER_VIEW_ID => false // Reserved name
//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)
//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 {
case x if x.startsWith("_") => true // Allowed case
case _ => false
}

View File

@ -142,7 +142,8 @@ object ErrorMessages {
val InvalidInternalRedirectUrl = "OBP-20018: Login failed, invalid internal redirectUrl."
val UserNoOwnerView = "OBP-20019: User does not have access to owner view. "
val InvalidCustomViewFormat = s"OBP-20020: View name must start with `_`. eg: _work, _life. "
val InvalidCustomViewFormat = s"OBP-20020: Custom view name/view_id must start with `_`. eg: _work, _life. "
val InvalidSystemViewFormat = s"OBP-20020: System view name/view_id can not start with '_'. eg: owner, standard. "
val SystemViewsCanNotBeModified = "OBP-20021: System Views can not be modified. Only the created views can be modified."
val ViewDoesNotPermitAccess = "OBP-20022: View does not permit the access."

View File

@ -56,7 +56,7 @@ trait APIMethods121 {
.map(JSONFactory.createViewJSON(_))
.distinct) ++
(privateViewsUserCanAccess
.filter(v =>v.accountId.value==null && v.bankId.value == null && v.isSystem && v.isPrivate)//plus the system views.
.filter(v =>v.isSystem && v.isPrivate)//plus the system views.
.map(JSONFactory.createViewJSON(_))
.distinct)
JSONFactory.createAccountJSON(account,viewsAvailable)
@ -594,7 +594,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(checkCustomViewName(createViewJsonV121.name), InvalidCustomViewFormat)
_<- booleanToBox(checkCustomViewIdOrName(createViewJsonV121.name), InvalidCustomViewFormat+s"Current view_name (${createViewJsonV121.name})")
account <- BankAccountX(bankId, accountId) ?~! BankAccountNotFound
createViewJson = CreateViewJson(
createViewJsonV121.name,
@ -605,7 +605,7 @@ trait APIMethods121 {
createViewJsonV121.hide_metadata_if_alias_used,
createViewJsonV121.allowed_actions
)
view <- account createView (u, createViewJson)
view <- account createCustomView (u, createViewJson)
} yield {
val viewJSON = JSONFactory.createViewJSON(view)
successJsonResponse(Extraction.decompose(viewJSON), 201)
@ -649,7 +649,7 @@ trait APIMethods121 {
account <- BankAccountX(bankId, accountId) ?~! BankAccountNotFound
u <- cc.user ?~ UserNotLoggedIn
//customer views are started ith `_`,eg _life, _work, and System views startWith letter, eg: owner
_ <- booleanToBox(viewId.value.startsWith("_"), InvalidCustomViewFormat)
_ <- booleanToBox(viewId.value.startsWith("_"), InvalidCustomViewFormat +s"Current view_id (${viewId.value})")
view <- Views.views.vend.customView(viewId, BankIdAccountId(bankId, accountId)) ?~! ViewNotFound
_ <- booleanToBox(!view.isSystem, SystemViewsCanNotBeModified)
updateViewJson = UpdateViewJSON(
@ -697,7 +697,7 @@ trait APIMethods121 {
(_, callContext) <- NewStyle.function.getBank(bankId, callContext)
(account, callContext) <- NewStyle.function.getBankAccount(bankId, accountId, callContext)
// custom views start with `_` eg _play, _work, and System views start with a letter, eg: owner
_ <- Helper.booleanToFuture(InvalidCustomViewFormat, cc=callContext) { viewId.value.startsWith("_") }
_ <- Helper.booleanToFuture(InvalidCustomViewFormat+s"Current view_name (${viewId.value})", cc=callContext) { viewId.value.startsWith("_") }
_ <- NewStyle.function.customView(viewId, BankIdAccountId(bankId, accountId), callContext)
deleted <- NewStyle.function.removeView(account, u, viewId)
} yield {

View File

@ -151,7 +151,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(checkCustomViewName(createViewJsonV121.name), InvalidCustomViewFormat)
_<- booleanToBox(checkCustomViewIdOrName(createViewJsonV121.name), InvalidCustomViewFormat+s"Current view_name (${createViewJsonV121.name})")
u <- cc.user ?~!UserNotLoggedIn
account <- BankAccountX(bankId, accountId) ?~! BankAccountNotFound
createViewJson = CreateViewJson(
@ -163,7 +163,7 @@ trait APIMethods220 {
createViewJsonV121.hide_metadata_if_alias_used,
createViewJsonV121.allowed_actions
)
view <- account createView (u, createViewJson)
view <- account createCustomView (u, createViewJson)
} yield {
val viewJSON = JSONFactory220.createViewJSON(view)
successJsonResponse(Extraction.decompose(viewJSON), 201)
@ -203,7 +203,7 @@ trait APIMethods220 {
for {
updateJsonV121 <- tryo{json.extract[UpdateViewJsonV121]} ?~!InvalidJsonFormat
//customer views are started ith `_`,eg _life, _work, and System views startWith letter, eg: owner
_ <- booleanToBox(viewId.value.startsWith("_"), InvalidCustomViewFormat)
_ <- booleanToBox(viewId.value.startsWith("_"), InvalidCustomViewFormat+s"Current view_name (${viewId.value})")
view <- APIUtil.checkViewAccessAndReturnView(viewId, BankIdAccountId(bankId, accountId), cc.user)
_ <- booleanToBox(!view.isSystem, SystemViewsCanNotBeModified)
u <- cc.user ?~!UserNotLoggedIn

View File

@ -170,13 +170,13 @@ trait APIMethods300 {
x => unboxFullOrFail(x, callContext, msg)
}
//customer views are started ith `_`,eg _life, _work, and System views startWith letter, eg: owner
_ <- Helper.booleanToFuture(failMsg = InvalidCustomViewFormat, cc=callContext) {
checkCustomViewName(createViewJson.name)
_ <- Helper.booleanToFuture(failMsg = InvalidCustomViewFormat+s"Current view_name (${createViewJson.name})", cc=callContext) {
checkCustomViewIdOrName(createViewJson.name)
}
(account, callContext) <- NewStyle.function.getBankAccount(bankId, accountId, callContext)
} yield {
for {
view <- account createView (u, createViewJson)
view <- account createCustomView (u, createViewJson)
} yield {
(JSONFactory300.createViewJSON(view), callContext.map(_.copy(httpCode = Some(201))))
}
@ -256,7 +256,7 @@ trait APIMethods300 {
x => unboxFullOrFail(x, callContext, msg)
}
//customer views are started ith `_`,eg _life, _work, and System views startWith letter, eg: owner
_ <- Helper.booleanToFuture(failMsg = InvalidCustomViewFormat, cc=callContext) {
_ <- Helper.booleanToFuture(failMsg = InvalidCustomViewFormat+s"Current view_name (${viewId.value})", cc=callContext) {
updateJson.metadata_view.startsWith("_")
}
_ <- Views.views.vend.customViewFuture(ViewId(viewId.value), BankIdAccountId(bankId, accountId)) map {

View File

@ -3921,6 +3921,10 @@ trait APIMethods310 {
createViewJson <- NewStyle.function.tryons(failMsg, 400, callContext) {
json.extract[CreateViewJsonV300]
}
//System views can not startwith '_'
_ <- Helper.booleanToFuture(failMsg = InvalidSystemViewFormat+s"Current view_name (${createViewJson.name})", cc = callContext) {
checkSystemViewIdOrName(createViewJson.name)
}
_ <- Helper.booleanToFuture(SystemViewCannotBePublicError, failCode=400, cc=callContext) {
createViewJson.is_public == false
}

View File

@ -1772,6 +1772,10 @@ trait APIMethods500 {
_ <- Helper.booleanToFuture(SystemViewCannotBePublicError, failCode=400, cc=cc.callContext) {
createViewJson.is_public == false
}
// 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)
}
view <- NewStyle.function.createSystemView(createViewJson.toCreateViewJson, cc.callContext)
} yield {
(createViewJsonV500(view), HttpCode.`201`(cc.callContext))

View File

@ -4,7 +4,7 @@ import java.util.Date
import java.util.UUID.randomUUID
import _root_.akka.http.scaladsl.model.HttpMethod
import code.accountholders.{AccountHolders, MapperAccountHolders}
import code.api.Constant.localIdentityProvider
import code.api.Constant.{SYSTEM_ACCOUNTANT_VIEW_ID, SYSTEM_AUDITOR_VIEW_ID, SYSTEM_OWNER_VIEW_ID, localIdentityProvider}
import code.api.attributedefinition.AttributeDefinition
import code.api.cache.Caching
import code.api.util.APIUtil.{OBPReturnType, _}
@ -1767,7 +1767,7 @@ trait Connector extends MdcLoggable {
val ownerView: Box[View] =
if(owner_view)
Views.views.vend.getOrCreateOwnerView(bankId, accountId, "Owner View")
Views.views.vend.getOrCreateSystemView(SYSTEM_OWNER_VIEW_ID)
else Empty
val publicView: Box[View] =
@ -1777,12 +1777,12 @@ trait Connector extends MdcLoggable {
val accountantsView: Box[View] =
if(accountants_view)
Views.views.vend.getOrCreateAccountantsView(bankId, accountId, "Accountants View")
Views.views.vend.getOrCreateSystemView(SYSTEM_ACCOUNTANT_VIEW_ID)
else Empty
val auditorsView: Box[View] =
if(auditors_view)
Views.views.vend.getOrCreateAuditorsView(bankId, accountId, "Auditors View")
Views.views.vend.getOrCreateSystemView(SYSTEM_AUDITOR_VIEW_ID)
else Empty
List(ownerView, publicView, accountantsView, auditorsView).flatten

View File

@ -10,7 +10,7 @@ import code.accountattribute.AccountAttributeX
import code.accountholders.{AccountHolders, MapperAccountHolders}
import code.api.BerlinGroup.{AuthenticationType, ScaStatus}
import code.api.Constant
import code.api.Constant.{INCOMING_SETTLEMENT_ACCOUNT_ID, OUTGOING_SETTLEMENT_ACCOUNT_ID, localIdentityProvider}
import code.api.Constant.{INCOMING_SETTLEMENT_ACCOUNT_ID, OUTGOING_SETTLEMENT_ACCOUNT_ID, SYSTEM_ACCOUNTANT_VIEW_ID, SYSTEM_AUDITOR_VIEW_ID, SYSTEM_OWNER_VIEW_ID, localIdentityProvider}
import code.api.ResourceDocs1_4_0.SwaggerDefinitionsJSON
import code.api.attributedefinition.{AttributeDefinition, AttributeDefinitionDI}
import code.api.cache.Caching
@ -5550,7 +5550,7 @@ object LocalMappedConnector extends Connector with MdcLoggable {
val ownerView: Box[View] =
if (owner_view)
Views.views.vend.getOrCreateOwnerView(bankId, accountId, "Owner View")
Views.views.vend.getOrCreateSystemView(SYSTEM_OWNER_VIEW_ID)
else Empty
val publicView: Box[View] =
@ -5560,12 +5560,12 @@ object LocalMappedConnector extends Connector with MdcLoggable {
val accountantsView: Box[View] =
if (accountants_view)
Views.views.vend.getOrCreateAccountantsView(bankId, accountId, "Accountants View")
Views.views.vend.getOrCreateSystemView(SYSTEM_ACCOUNTANT_VIEW_ID)
else Empty
val auditorsView: Box[View] =
if (auditors_view)
Views.views.vend.getOrCreateAuditorsView(bankId, accountId, "Auditors View")
Views.views.vend.getOrCreateSystemView(SYSTEM_AUDITOR_VIEW_ID)
else Empty
List(ownerView, publicView, accountantsView, auditorsView).flatten

View File

@ -346,11 +346,11 @@ case class BankAccountExtended(val bankAccount: BankAccount) extends MdcLoggable
}
final def createView(userDoingTheCreate : User,v: CreateViewJson): Box[View] = {
final def createCustomView(userDoingTheCreate : User,v: CreateViewJson): Box[View] = {
if(!userDoingTheCreate.hasOwnerViewAccess(BankIdAccountId(bankId,accountId))) {
Failure({"user: " + userDoingTheCreate.idGivenByProvider + " at provider " + userDoingTheCreate.provider + " does not have owner access"})
} else {
val view = Views.views.vend.createView(BankIdAccountId(bankId,accountId), v)
val view = Views.views.vend.createCustomView(BankIdAccountId(bankId,accountId), v)
//if(view.isDefined) {
// logger.debug("user: " + userDoingTheCreate.idGivenByProvider + " at provider " + userDoingTheCreate.provider + " created view: " + view.get +

View File

@ -69,7 +69,7 @@ case class UserExtended(val user: User) extends MdcLoggable {
AccountAccess.find(
By(AccountAccess.bank_id, bankIdAccountId.bankId.value),
By(AccountAccess.account_id, bankIdAccountId.accountId.value),
By(AccountAccess.view_fk, viewDefinition.id),
By(AccountAccess.view_id, viewDefinition.viewId.value),
By(AccountAccess.user_fk, this.userPrimaryKey.value),
By(AccountAccess.consumer_id, consumerId.get)).isDefined
} else {
@ -83,7 +83,7 @@ case class UserExtended(val user: User) extends MdcLoggable {
AccountAccess.find(
By(AccountAccess.bank_id, bankIdAccountId.bankId.value),
By(AccountAccess.account_id, bankIdAccountId.accountId.value),
By(AccountAccess.view_fk, viewDefinition.id),
By(AccountAccess.view_id, viewDefinition.viewId.value),
By(AccountAccess.user_fk, this.userPrimaryKey.value),
By(AccountAccess.consumer_id, ALL_CONSUMERS)
).isDefined

View File

@ -73,7 +73,7 @@ object RemotedataViews extends ObpActorInit with Views {
(actor ? cc.getSystemViews()).mapTo[List[View]]
def createView(bankAccountId: BankIdAccountId, view: CreateViewJson): Box[View] = getValueFromFuture(
def createCustomView(bankAccountId: BankIdAccountId, view: CreateViewJson): Box[View] = getValueFromFuture(
(actor ? cc.createView(bankAccountId, view)).mapTo[Box[View]]
)
def createSystemView(view: CreateViewJson): Future[Box[View]] =
@ -136,20 +136,12 @@ object RemotedataViews extends ObpActorInit with Views {
(actor ? cc.getOrCreateAccountView(bankAccountUID: BankIdAccountId, viewId: String)).mapTo[Box[View]]
)
def getOrCreateOwnerView(bankId: BankId, accountId: AccountId, description: String) : Box[View] = getValueFromFuture(
(actor ? cc.getOrCreateOwnerView(bankId, accountId, description)).mapTo[Box[View]]
)
def getOrCreateSystemView(name: String) : Box[View] = getValueFromFuture(
(actor ? cc.getOrCreateSystemView(name)).mapTo[Box[View]]
)
def getOrCreateFirehoseView(bankId: BankId, accountId: AccountId, description: String) : Box[View] = getValueFromFuture(
(actor ? cc.getOrCreateFirehoseView(bankId, accountId, description)).mapTo[Box[View]]
def getOrCreateSystemView(viewId: String) : Box[View] = getValueFromFuture(
(actor ? cc.getOrCreateSystemView(viewId)).mapTo[Box[View]]
)
def getOrCreateCustomPublicView(bankId: BankId, accountId: AccountId, description: String) : Box[View] = getValueFromFuture(
(actor ? cc.getOrCreatePublicView(bankId, accountId, description)).mapTo[Box[View]]
(actor ? cc.getOrCreatePublicPublicView(bankId, accountId, description)).mapTo[Box[View]]
)
def getOrCreateAccountantsView(bankId: BankId, accountId: AccountId, description: String) : Box[View] = getValueFromFuture(

View File

@ -83,7 +83,7 @@ class RemotedataViewsActor extends Actor with ObpActorHelper with MdcLoggable {
case cc.createView(bankAccountId : BankIdAccountId, view: CreateViewJson) =>
logger.debug("createView(" + bankAccountId +","+ view +")")
sender ! (mapper.createView(bankAccountId, view))
sender ! (mapper.createCustomView(bankAccountId, view))
case cc.createSystemView(view: CreateViewJson) =>
logger.debug("createSystemView(" + view +")")
@ -140,21 +140,13 @@ class RemotedataViewsActor extends Actor with ObpActorHelper with MdcLoggable {
case cc.getOrCreateAccountView(bankAccountUID: BankIdAccountId, viewId: String) =>
logger.debug("getOrCreateAccountView(" + BankIdAccountId +", "+ viewId +")")
sender ! (mapper.getOrCreateAccountView(bankAccountUID: BankIdAccountId, viewId: String))
case cc.getOrCreateOwnerView(bankId, accountId, description) =>
logger.debug("getOrCreateOwnerView(" + bankId +", "+ accountId +", "+ description +")")
sender ! (mapper.getOrCreateOwnerView(bankId, accountId, description))
case cc.getOrCreateSystemView(name) =>
logger.debug("getOrCreateSystemOwnerView(" + name +")")
sender ! (mapper.getOrCreateSystemView(name))
case cc.getOrCreateFirehoseView(bankId, accountId, description) =>
logger.debug("getOrCreateFirehoseView(" + bankId +", "+ accountId +", "+ description +")")
sender ! (mapper.getOrCreateFirehoseView(bankId, accountId, description))
case cc.getOrCreatePublicView(bankId, accountId, description) =>
logger.debug("getOrCreatePublicView(" + bankId +", "+ accountId +", "+ description +")")
case cc.getOrCreatePublicPublicView(bankId, accountId, description) =>
logger.debug("getOrCreatePublicPublicView(" + bankId +", "+ accountId +", "+ description +")")
sender ! (mapper.getOrCreateCustomPublicView(bankId, accountId, description))
case cc.getOrCreateAccountantsView(bankId, accountId, description) =>

View File

@ -30,28 +30,34 @@ object MapperViews extends Views with MdcLoggable {
Schemifier.schemify(true, Schemifier.infoF _, ToSchemify.modelsRemotedata: _*)
private def getViewsForUser(user: User): List[View] = {
val privileges = AccountAccess.findAll(
val accountAccessList = AccountAccess.findAll(
By(AccountAccess.user_fk, user.userPrimaryKey.value),
OrderBy(AccountAccess.bank_id, Ascending),
OrderBy(AccountAccess.account_id, Ascending)
)
getViewsCommonPart(privileges)
getViewsCommonPart(accountAccessList)
}
private def getViewsForUserAndAccount(user: User, account : BankIdAccountId): List[View] = {
val privileges = AccountAccess.findAll(
val accountAccessList = AccountAccess.findAll(
By(AccountAccess.user_fk, user.userPrimaryKey.value),
By(AccountAccess.bank_id, account.bankId.value),
By(AccountAccess.account_id, account.accountId.value)
)
getViewsCommonPart(privileges)
getViewsCommonPart(accountAccessList)
}
private def getViewsCommonPart(privileges: List[AccountAccess]): List[View] = {
val views: List[ViewDefinition] = privileges.flatMap(
a =>
ViewDefinition.find(By(ViewDefinition.id_, a.view_fk.get))
.map(v => v.bank_id(a.bank_id.get).account_id(a.account_id.get))
).filter(
private def getViewFromAccountAccess(accountAccess: AccountAccess) = {
if (checkSystemViewIdOrName(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 {
ViewDefinition.findCustomView(accountAccess.bank_id.get, accountAccess.account_id.get, accountAccess.view_id.get)
}
}
private def getViewsCommonPart(accountAccessList: List[AccountAccess]): List[View] = {
//we need to get views from accountAccess
val views: List[ViewDefinition] = accountAccessList.flatMap(getViewFromAccountAccess).filter(
v =>
if (allowPublicViews) {
true // All views
@ -89,9 +95,9 @@ object MapperViews extends Views with MdcLoggable {
By(AccountAccess.user_fk, user.userPrimaryKey.value),
By(AccountAccess.bank_id, bankId),
By(AccountAccess.account_id, accountId),
By(AccountAccess.view_fk, viewDefinition.id)) == 0) {
//logger.debug(s"saving ViewPrivileges for user ${user.resourceUserId.value} for view ${vImpl.id}")
// SQL Insert ViewPrivileges
By(AccountAccess.view_id, viewDefinition.viewId.value)) == 0) {
//logger.debug(s"saving AccountAccessList for user ${user.resourceUserId.value} for view ${vImpl.id}")
// SQL Insert AccountAccessList
val saved = AccountAccess.create.
user_fk(user.userPrimaryKey.value).
bank_id(bankId).
@ -100,13 +106,13 @@ object MapperViews extends Views with MdcLoggable {
view_fk(viewDefinition.id).
save
if (saved) {
//logger.debug("saved ViewPrivileges")
//logger.debug("saved AccountAccessList")
Full(viewDefinition)
} else {
//logger.debug("failed to save ViewPrivileges")
//logger.debug("failed to save AccountAccessList")
Empty ~> APIFailure("Server error adding permission", 500) //TODO: move message + code logic to api level
}
} else Full(viewDefinition) //privilege already exists, no need to create one
} else Full(viewDefinition) //accountAccess already exists, no need to create one
}
// This is an idempotent function
private def getOrGrantAccessToSystemView(bankId: BankId, accountId: AccountId, user: User, view: View): Box[View] = {
@ -123,9 +129,9 @@ object MapperViews extends Views with MdcLoggable {
viewDefinition match {
case Full(v) => {
if(v.isPublic && !allowPublicViews) return Failure(PublicViewsNotAllowedOnThisInstance)
// SQL Select Count ViewPrivileges where
// SQL Select Count AccountAccessList where
// This is idempotent
getOrGrantAccessToCustomView(user, v, viewIdBankIdAccountId.bankId.value, viewIdBankIdAccountId.accountId.value) //privilege already exists, no need to create one
getOrGrantAccessToCustomView(user, v, viewIdBankIdAccountId.bankId.value, viewIdBankIdAccountId.accountId.value) //accountAccess already exists, no need to create one
}
case _ => {
Empty ~> APIFailure(s"View $viewIdBankIdAccountId. not found", 404) //TODO: move message + code logic to api level
@ -187,25 +193,28 @@ object MapperViews extends Views with MdcLoggable {
def revokeAccess(viewUID : ViewIdBankIdAccountId, user : User) : Box[Boolean] = {
val isRevokedCustomViewAccess =
for {
viewDefinition <- ViewDefinition.findCustomView(viewUID.bankId.value, viewUID.accountId.value, viewUID.viewId.value)
accountAccess <- AccountAccess.find(
By(AccountAccess.user_fk, user.userPrimaryKey.value),
By(AccountAccess.view_fk, viewDefinition.id)
customViewDefinition <- ViewDefinition.findCustomView(viewUID.bankId.value, viewUID.accountId.value, viewUID.viewId.value)
accountAccess <- AccountAccess.findByBankIdAccountIdViewIdUserPrimaryKey(
viewUID.bankId,
viewUID.accountId,
viewUID.viewId,
user.userPrimaryKey
) ?~! CannotFindAccountAccess
// Check if we are allowed to remove the View from the User
_ <- canRevokeAccessAsBox(viewDefinition, user)
} yield {
accountAccess.delete_!
}
val isRevokedSystemViewAccess =
for {
viewDefinition <- ViewDefinition.findSystemView(viewUID.viewId.value)
accountAccess <- AccountAccess.find(
By(AccountAccess.user_fk, user.userPrimaryKey.value),
By(AccountAccess.view_fk, viewDefinition.id)
systemViewDefinition <- ViewDefinition.findSystemView(viewUID.viewId.value)
accountAccess <- AccountAccess.findByBankIdAccountIdViewIdUserPrimaryKey(
viewUID.bankId,
viewUID.accountId,
viewUID.viewId,
user.userPrimaryKey
) ?~! CannotFindAccountAccess
// Check if we are allowed to remove the View from the User
_ <- canRevokeAccessAsBox(viewDefinition, user)
_ <- canRevokeOwnerAccessAsBox(viewUID.bankId, viewUID.accountId,systemViewDefinition, user)
} yield {
accountAccess.delete_!
}
@ -217,16 +226,17 @@ object MapperViews extends Views with MdcLoggable {
def revokeAccessToSystemView(bankId: BankId, accountId: AccountId, view : View, user : User) : Box[Boolean] = {
val res =
for {
viewDefinition <- ViewDefinition.find(By(ViewDefinition.id_, view.id))
aa <- AccountAccess.find(
By(AccountAccess.user_fk, user.userPrimaryKey.value),
By(AccountAccess.bank_id, bankId.value),
By(AccountAccess.account_id, accountId.value),
By(AccountAccess.view_fk, viewDefinition.id)) ?~! CannotFindAccountAccess
systemViewDefinition <- ViewDefinition.find(By(ViewDefinition.id_, view.id))
accountAccess <- AccountAccess.findByBankIdAccountIdViewIdUserPrimaryKey(
bankId,
accountId,
view.viewId,
user.userPrimaryKey
) ?~! CannotFindAccountAccess
// Check if we are allowed to remove the View from the User
_ <- canRevokeAccessAsBox(viewDefinition, user)
_ <- canRevokeOwnerAccessAsBox(bankId: BankId, accountId: AccountId, systemViewDefinition, user)
} yield {
aa.delete_!
accountAccess.delete_!
}
res
}
@ -234,12 +244,12 @@ object MapperViews extends Views with MdcLoggable {
//Custom View will have bankId and accountId inside the `View`, so no need both in the parameters
def revokeAccessToCustomViewForConsumer(view : View, consumerId : String) : Box[Boolean] = {
for {
viewDefinition <- ViewDefinition.findCustomView(view.bankId.value, view.accountId.value, view.viewId.value)
accountAccess <- AccountAccess.find(
By(AccountAccess.consumer_id, consumerId),
By(AccountAccess.view_fk, viewDefinition.id),
By(AccountAccess.bank_id, view.bankId.value),
By(AccountAccess.account_id, view.accountId.value)
customViewDefinition <- ViewDefinition.findCustomView(view.bankId.value, view.accountId.value, view.viewId.value)
accountAccess <- AccountAccess.findByBankIdAccountIdViewIdConsumerId(
customViewDefinition.bankId,
customViewDefinition.accountId,
customViewDefinition.viewId,
consumerId
) ?~! CannotFindAccountAccess
} yield {
accountAccess.delete_!
@ -249,25 +259,26 @@ object MapperViews extends Views with MdcLoggable {
//System View only have the viewId in inside the `View`, both bankId and accountId are empty in the `View`. So we need both in the parameters
def revokeAccessToSystemViewForConsumer(bankId: BankId, accountId: AccountId, view : View, consumerId : String) : Box[Boolean] = {
for {
viewDefinition <- ViewDefinition.find(By(ViewDefinition.id_, view.id))
accountAccess <- AccountAccess.find(
By(AccountAccess.consumer_id, consumerId),
By(AccountAccess.bank_id, bankId.value),
By(AccountAccess.account_id, accountId.value),
By(AccountAccess.view_fk, viewDefinition.id)) ?~! CannotFindAccountAccess
systemViewDefinition <- ViewDefinition.find(By(ViewDefinition.id_, view.id))
accountAccess <- AccountAccess.findByBankIdAccountIdViewIdConsumerId(
bankId,
accountId,
systemViewDefinition.viewId,
consumerId
) ?~! CannotFindAccountAccess
} yield {
accountAccess.delete_!
}
}
//returns Full if deletable, Failure if not
def canRevokeAccessAsBox(viewImpl : ViewDefinition, user : User) : Box[Unit] = {
if(canRevokeAccess(viewImpl, user)) Full(Unit)
def canRevokeOwnerAccessAsBox(bankId: BankId, accountId: AccountId, viewImpl : ViewDefinition, user : User) : Box[Unit] = {
if(canRevokeOwnerAccess(bankId: BankId, accountId: AccountId, viewImpl, user)) Full(Unit)
else Failure("access cannot be revoked")
}
def canRevokeAccess(viewDefinition: ViewDefinition, user : User) : Boolean = {
def canRevokeOwnerAccess(bankId: BankId, accountId: AccountId, viewDefinition: ViewDefinition, user : User) : Boolean = {
if(viewDefinition.viewId == ViewId(SYSTEM_OWNER_VIEW_ID)) {
//if the user is an account holder, we can't revoke access to the owner view
val accountHolders = MapperAccountHolders.getAccountHolders(viewDefinition.bankId, viewDefinition.accountId)
@ -276,7 +287,11 @@ object MapperViews extends Views with MdcLoggable {
} else {
// if it's the owner view, we can only revoke access if there would then still be someone else
// with access
AccountAccess.findAll(By(AccountAccess.view_fk, viewDefinition.id)).length > 1
AccountAccess.findAllByBankIdAccountIdViewId(
bankId: BankId,
accountId: AccountId,
viewDefinition.viewId
).length > 1
}
} else {
true
@ -284,30 +299,31 @@ object MapperViews extends Views with MdcLoggable {
}
/*
This removes the link between a User and a View (Account Access)
/**
* remove all the accountAccess for one user and linked account.
* If the user has `owner` view accountAccess and also the accountHolder, we can not revoke, just return `false`.
* all the other case, we can revoke all the account access for that user.
*/
def revokeAllAccountAccess(bankId : BankId, accountId: AccountId, user : User) : Box[Boolean] = {
//TODO: make this more efficient by using one query (with a join)
val allUserPrivs = AccountAccess.findAll(By(AccountAccess.user_fk, user.userPrimaryKey.value))
val relevantAccountPrivs = allUserPrivs.filter(p => p.bank_id == bankId.value && p.account_id == accountId.value)
val allRelevantPrivsRevokable = relevantAccountPrivs.forall( p => p.view_fk.obj match {
case Full(v) => canRevokeAccess(v, user)
case _ => false
})
if(allRelevantPrivsRevokable) {
relevantAccountPrivs.foreach(_.delete_!)
val userIsAccountHolder_? = MapperAccountHolders.getAccountHolders(bankId, accountId).map(h => h.userPrimaryKey).contains(user.userPrimaryKey)
val userHasOwnerViewAccess_? = AccountAccess.find(
By(AccountAccess.bank_id, bankId.value),
By(AccountAccess.account_id, accountId.value),
By(AccountAccess.view_id, SYSTEM_OWNER_VIEW_ID),
By(AccountAccess.user_fk, user.userPrimaryKey.value),
).isDefined
if(userIsAccountHolder_? && userHasOwnerViewAccess_?){
Full(false)
}else{
AccountAccess.find(
By(AccountAccess.bank_id, bankId.value),
By(AccountAccess.account_id, accountId.value),
By(AccountAccess.user_fk, user.userPrimaryKey.value)
).foreach(_.delete_!)
Full(true)
} else {
Failure("One of the views this user has access to is the owner view, and there would be no one with access" +
" to this owner view if access to the user was revoked. No permissions to any views on the account have been revoked.")
}
}
def revokeAccountAccessByUser(bankId : BankId, accountId: AccountId, user : User) : Box[Boolean] = {
@ -355,7 +371,7 @@ object MapperViews extends Views with MdcLoggable {
}
}
def getNewViewPermalink(name: String) = {
def createViewIdByName(name: String) = {
name.replaceAllLiterally(" ", "").toLowerCase
}
/*
@ -364,24 +380,26 @@ 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)) {
Failure(InvalidSystemViewFormat+s"Current view_name (${view.name})")
} else {
view.name.contentEquals("") match {
case true =>
Failure(EmptyNameOfSystemViewError)
case false =>
//view-permalink is view.name without spaces and lowerCase. (view.name = my life) <---> (view-permalink = mylife)
val newViewPermalink = getNewViewPermalink(view.name)
val viewId = createViewIdByName(view.name)
val existing = ViewDefinition.count(
By(ViewDefinition.view_id, newViewPermalink),
By(ViewDefinition.view_id, viewId),
NullRef(ViewDefinition.bank_id),
NullRef(ViewDefinition.account_id)
) == 1
existing match {
case true =>
Failure(s"$ExistingSystemViewError $newViewPermalink")
Failure(s"$ExistingSystemViewError $viewId")
case false =>
val createdView = ViewDefinition.create.name_(view.name).view_id(newViewPermalink)
val createdView = ViewDefinition.create.name_(view.name).view_id(viewId)
createdView.setFromViewData(view)
createdView.isSystem_(true)
createdView.isPublic_(false)
@ -394,8 +412,12 @@ object MapperViews extends Views with MdcLoggable {
/*
Create View based on the Specification (name, alias behavior, what fields can be seen, actions are allowed etc. )
* */
def createView(bankAccountId: BankIdAccountId, view: CreateViewJson): Box[View] = {
def createCustomView(bankAccountId: BankIdAccountId, view: CreateViewJson): Box[View] = {
if(!checkCustomViewIdOrName(view.name)) {
return Failure(InvalidCustomViewFormat)
}
if(view.is_public && !allowPublicViews) {
return Failure(PublicViewsNotAllowedOnThisInstance)
}
@ -404,19 +426,19 @@ object MapperViews extends Views with MdcLoggable {
return Failure("You cannot create a View with an empty Name")
}
//view-permalink is view.name without spaces and lowerCase. (view.name = my life) <---> (view-permalink = mylife)
val newViewPermalink = getNewViewPermalink(view.name)
val viewId = createViewIdByName(view.name)
val existing = ViewDefinition.count(
By(ViewDefinition.view_id, newViewPermalink) ::
By(ViewDefinition.view_id, viewId) ::
ViewDefinition.accountFilter(bankAccountId.bankId, bankAccountId.accountId): _*
) == 1
if (existing)
Failure(s"There is already a view with permalink $newViewPermalink on this bank account")
Failure(s"There is already a view with permalink $viewId on this bank account")
else {
val createdView = ViewDefinition.create.
name_(view.name).
view_id(newViewPermalink).
view_id(viewId).
bank_id(bankAccountId.bankId.value).
account_id(bankAccountId.accountId.value)
@ -448,19 +470,23 @@ object MapperViews extends Views with MdcLoggable {
def removeCustomView(viewId: ViewId, bankAccountId: BankIdAccountId): Box[Boolean] = {
for {
view <- ViewDefinition.findCustomView(bankAccountId.bankId.value, bankAccountId.accountId.value, viewId.value)
_ <- AccountAccess.find(By(AccountAccess.view_fk, view.id)).isDefined match {
customView <- ViewDefinition.findCustomView(bankAccountId.bankId.value, bankAccountId.accountId.value, viewId.value)
_ <- AccountAccess.findAllByBankIdAccountIdViewId(
bankAccountId.bankId,
bankAccountId.accountId,
viewId
).length > 0 match {
case true => Failure("Account Access record uses this View.") // We want to prevent account access orphans
case false => Full()
}
} yield {
view.delete_!
customView.delete_!
}
}
def removeSystemView(viewId: ViewId): Future[Box[Boolean]] = Future {
for {
view <- ViewDefinition.findSystemView(viewId.value)
_ <- AccountAccess.find(By(AccountAccess.view_fk, view.id)).isDefined match {
_ <- AccountAccess.findAllBySystemViewId(viewId).length > 0 match {
case true => Failure("Account Access record uses this View.") // We want to prevent account access orphans
case false => Full()
}
@ -470,11 +496,10 @@ object MapperViews extends Views with MdcLoggable {
}
def assignedViewsForAccount(bankAccountId : BankIdAccountId) : List[View] = {
AccountAccess.findAll(
By(AccountAccess.bank_id, bankAccountId.bankId.value),
By(AccountAccess.account_id, bankAccountId.accountId.value),
PreCache(AccountAccess.view_fk)
).map(_.view_fk.obj).flatten.distinct
AccountAccess.findAllByBankIdAccountId(
bankAccountId.bankId,
bankAccountId.accountId
).map(getViewFromAccountAccess).flatten.distinct
}
//this is more like possible views, it contains the system views+custom views
@ -492,10 +517,23 @@ object MapperViews extends Views with MdcLoggable {
By(ViewDefinition.isSystem_, true)) // Sandbox specific System views
}
private def getAccountAccessFromPublicViews(publicViews: List[ViewDefinition])={
val publicSystemViews = publicViews.filter(_.isSystem)
val publicCustomViews = publicViews.filter(!_.isSystem)
val publicSystemViewAccountAccess = AccountAccess.findAll(
ByList(AccountAccess.view_id, publicSystemViews.map(_.viewId.value)),
)
val publicCustomViewAccountAccess = AccountAccess.findAll(
ByList(AccountAccess.bank_id, publicCustomViews.map(_.bankId.value)),
ByList(AccountAccess.account_id, publicCustomViews.map(_.accountId.value)),
ByList(AccountAccess.view_id, publicCustomViews.map(_.viewId.value)),
)
publicCustomViewAccountAccess++publicSystemViewAccountAccess
}
def publicViews: (List[View], List[AccountAccess]) = {
if (APIUtil.allowPublicViews) {
val publicViews = ViewDefinition.findAll(By(ViewDefinition.isPublic_, true)) // Custom and System views
val publicAccountAccess = AccountAccess.findAll(ByList(AccountAccess.view_fk, publicViews.map(_.id)))
val publicViews = ViewDefinition.findAll(By(ViewDefinition.isPublic_, true)) //Both Custom and System views
val publicAccountAccess = getAccountAccessFromPublicViews(publicViews)
(publicViews, publicAccountAccess)
} else {
(Nil, Nil)
@ -508,7 +546,7 @@ object MapperViews extends Views with MdcLoggable {
ViewDefinition.findAll(By(ViewDefinition.isPublic_, true), By(ViewDefinition.bank_id, bankId.value), By(ViewDefinition.isSystem_, false)) ::: // Custom views
ViewDefinition.findAll(By(ViewDefinition.isPublic_, true), By(ViewDefinition.isSystem_, true)) ::: // System views
ViewDefinition.findAll(By(ViewDefinition.isPublic_, true), By(ViewDefinition.bank_id, bankId.value), By(ViewDefinition.isSystem_, true)) // System views
val publicAccountAccess = AccountAccess.findAll(ByList(AccountAccess.view_fk, publicViews.map(_.id)))
val publicAccountAccess = getAccountAccessFromPublicViews(publicViews)
(publicViews.distinct, publicAccountAccess)
} else {
(Nil, Nil)
@ -516,30 +554,39 @@ object MapperViews extends Views with MdcLoggable {
}
def privateViewsUserCanAccess(user: User): (List[View], List[AccountAccess]) ={
val accountAccess = AccountAccess.findAll(
By(AccountAccess.user_fk, user.userPrimaryKey.value),
PreCache(AccountAccess.view_fk)
).filter(r => r.view_fk.obj.isDefined && r.view_fk.obj.map(_.isPrivate).getOrElse(false) == true)
val privateViews = accountAccess.map(_.view_fk.obj).flatten.distinct
val accountAccess = AccountAccess.findAllByUserPrimaryKey(user.userPrimaryKey)
.filter(accountAccess => {
val view = getViewFromAccountAccess(accountAccess)
view.isDefined && view.map(_.isPrivate)==Full(true)
})
val privateViews = accountAccess.map(getViewFromAccountAccess).flatten.distinct
(privateViews, accountAccess)
}
def privateViewsUserCanAccess(user: User, viewIds: List[ViewId]): (List[View], List[AccountAccess]) ={
val accountAccess = AccountAccess.findAll(
By(AccountAccess.user_fk, user.userPrimaryKey.value),
ByList(AccountAccess.view_id, viewIds.map(_.value))
).filter(r => r.view_fk.obj.isDefined && r.view_fk.obj.map(_.isPrivate).getOrElse(false) == true)
).filter(accountAccess => {
val view = getViewFromAccountAccess(accountAccess)
view.isDefined && view.map(_.isPrivate) == Full(true)
})
PrivateViewsUserCanAccessCommon(accountAccess)
}
def privateViewsUserCanAccessAtBank(user: User, bankId: BankId): (List[View], List[AccountAccess]) ={
val accountAccess = AccountAccess.findAll(
By(AccountAccess.user_fk, user.userPrimaryKey.value),
By(AccountAccess.bank_id, bankId.value)
).filter(r => r.view_fk.obj.isDefined && r.view_fk.obj.map(_.isPrivate).getOrElse(false) == true)
).filter(accountAccess => {
val view = getViewFromAccountAccess(accountAccess)
view.isDefined && view.map(_.isPrivate) == Full(true)
})
PrivateViewsUserCanAccessCommon(accountAccess)
}
private def PrivateViewsUserCanAccessCommon(accountAccess: List[AccountAccess]): (List[ViewDefinition], List[AccountAccess]) = {
val listOfTuples: List[(AccountAccess, Box[ViewDefinition])] = accountAccess.map(x => (x, x.view_fk.obj))
val listOfTuples: List[(AccountAccess, Box[ViewDefinition])] = accountAccess.map(
accountAccess => (accountAccess, getViewFromAccountAccess(accountAccess))
)
val privateViews = listOfTuples.flatMap(
tuple => tuple._2.map(v => v.bank_id(tuple._1.bank_id.get).account_id(tuple._1.account_id.get))
)
@ -547,25 +594,15 @@ object MapperViews extends Views with MdcLoggable {
}
def privateViewsUserCanAccessForAccount(user: User, bankIdAccountId : BankIdAccountId) : List[View] = {
val accountAccess = AccountAccess.findAll(
By(AccountAccess.user_fk, user.userPrimaryKey.value),
By(AccountAccess.bank_id, bankIdAccountId.bankId.value),
By(AccountAccess.account_id, bankIdAccountId.accountId.value),
PreCache(AccountAccess.view_fk)
val accountAccess = AccountAccess.findByBankIdAccountIdUserPrimaryKey(
bankIdAccountId.bankId,
bankIdAccountId.accountId,
user.userPrimaryKey
)
accountAccess.map(_.view_fk.obj).flatten.filter(view => view.isPrivate == true).distinct
accountAccess.map(getViewFromAccountAccess).flatten.filter(view => view.isPrivate == true).distinct
}
/**
* @param bankIdAccountId the IncomingAccount from Kafka
* @param viewId This field should be selected one from Owner/Public/Accountant/Auditor, only support
* these four values.
* @return This will insert a View (e.g. the owner view) for an Account (BankAccount), and return the view
* Note:
* updateUserAccountViews would call createAccountView once per View specified in the IncomingAccount from Kafka.
* We should cache this function because the available views on an account will change rarely.
*
*/
def getOrCreateAccountView(bankIdAccountId: BankIdAccountId, viewId: String): Box[View] = {
val bankId = bankIdAccountId.bankId
@ -600,43 +637,29 @@ object MapperViews extends Views with MdcLoggable {
theView
}
def getOrCreateOwnerView(bankId: BankId, accountId: AccountId, description: String = "Owner View") : Box[View] = {
getExistingView(bankId, accountId, SYSTEM_OWNER_VIEW_ID) match {
case Empty => createDefaultOwnerView(bankId, accountId, description)
case Full(v) => Full(v)
case Failure(msg, t, c) => Failure(msg, t, c)
case ParamFailure(x,y,z,q) => ParamFailure(x,y,z,q)
}
}
def getOrCreateSystemView(name: String) : Box[View] = {
getExistingSystemView(name) match {
case Empty => createDefaultSystemView(name)
def getOrCreateSystemView(viewId: String) : Box[View] = {
getExistingSystemView(viewId) match {
case Empty => createDefaultSystemView(viewId)
case Full(v) => Full(v)
case Failure(msg, t, c) => Failure(msg, t, c)
case ParamFailure(x,y,z,q) => ParamFailure(x,y,z,q)
}
}
def getOrCreateFirehoseView(bankId: BankId, accountId: AccountId, description: String = "Firehose View") : Box[View] = {
getExistingView(bankId, accountId, "firehose") match {
case Empty => createDefaultFirehoseView(bankId, accountId, description)
case Full(v) => Full(v)
case Failure(msg, t, c) => Failure(msg, t, c)
case ParamFailure(x,y,z,q) => ParamFailure(x,y,z,q)
}
}
/**
* if return the system view owner, it may return all the users, all the user if have its own account, it should have the `owner` view access.
* @param view
* @return
*/
def getOwners(view: View) : Set[User] = {
val id: Long = ViewDefinition.findCustomView(view.uid.bankId.value, view.uid.accountId.value, view.uid.viewId.value)
.or(ViewDefinition.findSystemView(view.viewId.value))
.map(_.id).openOr(0)
val privileges = AccountAccess.findAll(By(AccountAccess.view_fk, id))
val users: List[User] = privileges.flatMap(_.user_fk.obj)
val accountAccessList = AccountAccess.findAllByView(view)
val users: List[User] = accountAccessList.flatMap(_.user_fk.obj)
users.toSet
}
def getOrCreateCustomPublicView(bankId: BankId, accountId: AccountId, description: String = "Public View") : Box[View] = {
getExistingView(bankId, accountId, CUSTOM_PUBLIC_VIEW_ID) match {
getExistingCustomView(bankId, accountId, CUSTOM_PUBLIC_VIEW_ID) match {
case Empty=> createDefaultPublicView(bankId, accountId, description)
case Full(v)=> Full(v)
case Failure(msg, t, c) => Failure(msg, t, c)
@ -645,7 +668,7 @@ object MapperViews extends Views with MdcLoggable {
}
def getOrCreateAccountantsView(bankId: BankId, accountId: AccountId, description: String = "Accountants View") : Box[View] = {
getExistingView(bankId, accountId, "accountant") match {
getExistingCustomView(bankId, accountId, SYSTEM_ACCOUNTANT_VIEW_ID) match {
case Empty => createDefaultAccountantsView(bankId, accountId, description)
case Full(v) => Full(v)
case Failure(msg, t, c) => Failure(msg, t, c)
@ -654,7 +677,7 @@ object MapperViews extends Views with MdcLoggable {
}
def getOrCreateAuditorsView(bankId: BankId, accountId: AccountId, description: String = "Auditors View") : Box[View] = {
getExistingView(bankId, accountId, "auditor") match {
getExistingCustomView(bankId, accountId, SYSTEM_AUDITOR_VIEW_ID) match {
case Empty => createDefaultAuditorsView(bankId, accountId, description)
case Full(v) => Full(v)
case Failure(msg, t, c) => Failure(msg, t, c)
@ -755,11 +778,6 @@ object MapperViews extends Views with MdcLoggable {
Full(entity)
}
def createDefaultFirehoseView(bankId: BankId, accountId: AccountId, name: String): Box[View] = {
createAndSaveFirehoseView(bankId, accountId, "Firehose View")
}
def createDefaultOwnerView(bankId: BankId, accountId: AccountId, name: String): Box[View] = {
createAndSaveOwnerView(bankId, accountId, "Owner View")
}
@ -782,27 +800,22 @@ object MapperViews extends Views with MdcLoggable {
createAndSaveDefaultAuditorsView(bankId, accountId, "Auditors View")
}
def getExistingView(bankId: BankId, accountId: AccountId, name: String): Box[View] = {
val res = ViewDefinition.findCustomView(bankId.value, accountId.value, name)
def getExistingCustomView(bankId: BankId, accountId: AccountId, viewId: String): Box[View] = {
val res = ViewDefinition.findCustomView(bankId.value, accountId.value, viewId)
if(res.isDefined && res.openOrThrowException(attemptedToOpenAnEmptyBox).isPublic && !allowPublicViews) return Failure(PublicViewsNotAllowedOnThisInstance)
res
}
def getExistingSystemView(name: String): Box[View] = {
val res = ViewDefinition.findSystemView(name)
def getExistingSystemView(viewId: String): Box[View] = {
val res = ViewDefinition.findSystemView(viewId)
if(res.isDefined && res.openOrThrowException(attemptedToOpenAnEmptyBox).isPublic && !allowPublicViews) return Failure(PublicViewsNotAllowedOnThisInstance)
res
}
def removeAllPermissions(bankId: BankId, accountId: AccountId) : Boolean = {
val views = ViewDefinition.findAll(
By(ViewDefinition.bank_id, bankId.value),
By(ViewDefinition.account_id, accountId.value)
AccountAccess.bulkDelete_!!(
By(AccountAccess.bank_id, bankId.value),
By(AccountAccess.account_id, accountId.value)
)
var privilegesDeleted = true
views.map (x => {
privilegesDeleted &&= AccountAccess.bulkDelete_!!(By(AccountAccess.view_fk, x.id_.get))
} )
privilegesDeleted
}
def removeAllViews(bankId: BankId, accountId: AccountId) : Boolean = {
@ -907,15 +920,15 @@ object MapperViews extends Views with MdcLoggable {
.canAddTransactionRequestToOwnAccount_(true) //added following two for payments
.canAddTransactionRequestToAnyAccount_(true)
}
def unsavedSystemView(name: String) : ViewDefinition = {
def unsavedSystemView(viewId: String) : ViewDefinition = {
val entity = create
.isSystem_(true)
.isFirehose_(false)
.bank_id(null)
.account_id(null)
.name_(StringHelpers.capify(name))
.view_id(name)
.description_(name)
.name_(StringHelpers.capify(viewId))
.view_id(viewId)
.description_(viewId)
.isPublic_(false) //(default is false anyways)
.usePrivateAliasIfOneExists_(false) //(default is false anyways)
.usePublicAliasIfOneExists_(false) //(default is false anyways)
@ -994,7 +1007,7 @@ object MapperViews extends Views with MdcLoggable {
.canAddTransactionRequestToOwnAccount_(true) //added following two for payments
.canAddTransactionRequestToAnyAccount_(true)
name match {
viewId match {
case SYSTEM_STAGE_ONE_VIEW_ID =>
entity
.canSeeTransactionDescription_(false)

View File

@ -60,7 +60,7 @@ trait Views {
//always return a view id String, not error here.
def getMetadataViewId(bankAccountId: BankIdAccountId, viewId : ViewId) = Views.views.vend.customView(viewId, bankAccountId).map(_.metadataView).openOr(viewId.value)
def createView(bankAccountId: BankIdAccountId, view: CreateViewJson): Box[View]
def createCustomView(bankAccountId: BankIdAccountId, view: CreateViewJson): Box[View]
def createSystemView(view: CreateViewJson): Future[Box[View]]
def removeCustomView(viewId: ViewId, bankAccountId: BankIdAccountId): Box[Boolean]
def removeSystemView(viewId: ViewId): Future[Box[Boolean]]
@ -97,21 +97,23 @@ trait Views {
final def getPrivateBankAccountsFuture(user : User, viewIds: List[ViewId]) : Future[List[BankIdAccountId]] = Future {getPrivateBankAccounts(user, viewIds)}
final def getPrivateBankAccounts(user : User, bankId : BankId) : List[BankIdAccountId] = getPrivateBankAccounts(user).filter(_.bankId == bankId).distinct
final def getPrivateBankAccountsFuture(user : User, bankId : BankId) : Future[List[BankIdAccountId]] = Future {getPrivateBankAccounts(user, bankId)}
/**
* @param bankIdAccountId the IncomingAccount from Kafka
* @param viewId This field should be selected one from Owner/Public/Accountant/Auditor, only support
* these four values.
* @return This will insert a View (e.g. the owner view) for an Account (BankAccount), and return the view
* Note:
* updateUserAccountViews would call createAccountView once per View specified in the IncomingAccount from Kafka.
* We should cache this function because the available views on an account will change rarely.
*
*/
def getOrCreateAccountView(bankAccountUID: BankIdAccountId, viewId: String): Box[View]
def getOrCreateFirehoseView(bankId: BankId, accountId: AccountId, description: String) : Box[View]
def getOrCreateSystemView(name: String) : Box[View]
def getOrCreateSystemView(viewId: String) : Box[View]
def getOrCreateCustomPublicView(bankId: BankId, accountId: AccountId, description: String) : Box[View]
def createCustomRandomView(bankId: BankId, accountId: AccountId) : Box[View]
@deprecated("There is no custom `Accountant` view, only support system owner view now","2020-01-13")
def getOrCreateAccountantsView(bankId: BankId, accountId: AccountId, description: String) : Box[View]
@deprecated("There is no custom `Auditor` view, only support system owner view now","2020-01-13")
def getOrCreateAuditorsView(bankId: BankId, accountId: AccountId, description: String) : Box[View]
@deprecated("There is no custom `owner` view, only support system owner view now","2020-01-13")
def getOrCreateOwnerView(bankId: BankId, accountId: AccountId, description: String) : Box[View]
def getOwners(view: View): Set[User]
def removeAllPermissions(bankId: BankId, accountId: AccountId) : Boolean
@ -158,11 +160,9 @@ class RemotedataViewsCaseClasses {
case class getSystemViews()
case class customViewFuture(viewId : ViewId, bankAccountId: BankIdAccountId)
case class systemViewFuture(viewId : ViewId)
case class getOrCreateAccountView(account: BankIdAccountId, viewName: String)
case class getOrCreateOwnerView(bankId: BankId, accountId: AccountId, description: String)
case class getOrCreateSystemView(name: String)
case class getOrCreateFirehoseView(bankId: BankId, accountId: AccountId, description: String)
case class getOrCreatePublicView(bankId: BankId, accountId: AccountId, description: String)
case class getOrCreateAccountView(account: BankIdAccountId, viewId: String)
case class getOrCreateSystemView(viewId: String)
case class getOrCreatePublicPublicView(bankId: BankId, accountId: AccountId, description: String)
case class getOrCreateAccountantsView(bankId: BankId, accountId: AccountId, description: String)
case class getOrCreateAuditorsView(bankId: BankId, accountId: AccountId, description: String)
case class createRandomView(bankId: BankId, accountId: AccountId)

View File

@ -3,6 +3,7 @@ package code.views.system
import code.api.Constant.ALL_CONSUMERS
import code.model.dataAccess.ResourceUser
import code.util.UUIDString
import com.openbankproject.commons.model.{AccountId, BankId, UserPrimaryKey, View, ViewId}
import net.liftweb.mapper._
/*
This stores the link between A User and a View
@ -12,16 +13,63 @@ class AccountAccess extends LongKeyedMapper[AccountAccess] with IdPK with Create
def getSingleton = AccountAccess
object user_fk extends MappedLongForeignKey(this, ResourceUser)
object bank_id extends MappedString(this, 255)
//If consumer_id is `ALL-CONSUMERS`, any consumers can use this record
//If consumer_id is consumerId (obp UUID), only same consumer can use this record
object consumer_id extends MappedString(this, 255){
override def defaultValue = ALL_CONSUMERS
}
object account_id extends MappedString(this, 255)
object view_id extends UUIDString(this)
//If consumer_id is `ALL-CONSUMERS`, any consumers can use this record
//If consumer_id is consumerId (obp UUID), only same consumer can use this record
object consumer_id extends MappedString(this, 255) {
override def defaultValue = ALL_CONSUMERS
}
@deprecated("we should use bank_id, account_id and view_id instead of the view_fk","07-03-2023")
object view_fk extends MappedLongForeignKey(this, ViewDefinition)
}
object AccountAccess extends AccountAccess with LongKeyedMetaMapper[AccountAccess] {
override def dbIndexes: List[BaseIndex[AccountAccess]] = UniqueIndex(bank_id, account_id, view_fk, user_fk, consumer_id) :: super.dbIndexes
def findAllBySystemViewId(systemViewId:ViewId)= AccountAccess.findAll(
By(AccountAccess.view_id, systemViewId.value)
)
def findAllByView(view: View)=
if(view.isSystem) {
findAllBySystemViewId(view.viewId)
}else{
AccountAccess.findAllByBankIdAccountIdViewId(view.bankId, view.accountId, view.viewId)
}
def findAllByUserPrimaryKey(userPrimaryKey:UserPrimaryKey)= AccountAccess.findAll(
By(AccountAccess.user_fk, userPrimaryKey.value),
PreCache(AccountAccess.view_fk)
)
def findAllByBankIdAccountId(bankId:BankId, accountId:AccountId) = AccountAccess.findAll(
By(AccountAccess.bank_id, bankId.value),
By(AccountAccess.account_id, accountId.value),
PreCache(AccountAccess.view_fk)
)
def findAllByBankIdAccountIdViewId(bankId:BankId, accountId:AccountId, viewId:ViewId)= AccountAccess.findAll(
By(AccountAccess.bank_id, bankId.value),
By(AccountAccess.account_id, accountId.value),
By(AccountAccess.view_id, viewId.value)
)
def findByBankIdAccountIdUserPrimaryKey(bankId: BankId, accountId: AccountId, userPrimaryKey: UserPrimaryKey) = AccountAccess.findAll(
By(AccountAccess.bank_id, bankId.value),
By(AccountAccess.account_id, accountId.value),
By(AccountAccess.user_fk, userPrimaryKey.value),
PreCache(AccountAccess.view_fk)
)
def findByBankIdAccountIdViewIdUserPrimaryKey(bankId: BankId, accountId: AccountId, viewId: ViewId, userPrimaryKey: UserPrimaryKey) = AccountAccess.find(
By(AccountAccess.bank_id, bankId.value),
By(AccountAccess.account_id, accountId.value),
By(AccountAccess.view_id, viewId.value),
By(AccountAccess.user_fk, userPrimaryKey.value)
)
def findByBankIdAccountIdViewIdConsumerId(bankId: BankId, accountId: AccountId, viewId: ViewId, consumerId:String ) = AccountAccess.find(
By(AccountAccess.bank_id, bankId.value),
By(AccountAccess.account_id, accountId.value),
By(AccountAccess.view_id, viewId.value),
By(AccountAccess.consumer_id, consumerId)
)
}

View File

@ -1,5 +1,7 @@
package code.views.system
import code.api.util.APIUtil.{checkCustomViewIdOrName, checkSystemViewIdOrName}
import code.api.util.ErrorMessages.{InvalidCustomViewFormat, InvalidSystemViewFormat}
import code.util.{AccountIdString, UUIDString}
import com.openbankproject.commons.model._
import net.liftweb.common.Box
@ -529,11 +531,19 @@ class ViewDefinition extends View with LongKeyedMapper[ViewDefinition] with Many
object ViewDefinition extends ViewDefinition with LongKeyedMetaMapper[ViewDefinition] {
override def dbIndexes: List[BaseIndex[ViewDefinition]] = UniqueIndex(composite_unique_key) :: super.dbIndexes
override def beforeSave = List(
t =>
t =>{
tryo {
val viewId = getUniqueKey(t.bank_id.get, t.account_id.get, t.view_id.get)
t.composite_unique_key(viewId)
}
if (t.isSystem && !checkSystemViewIdOrName(t.view_id.get)) {
throw new RuntimeException(InvalidSystemViewFormat+s"Current view_id (${t.view_id.get})")
}
if (!t.isSystem && !checkCustomViewIdOrName(t.view_id.get)) {
throw new RuntimeException(InvalidCustomViewFormat+s"Current view_id (${t.view_id.get})")
}
}
)
def findSystemView(viewId: String): Box[ViewDefinition] = {

View File

@ -341,29 +341,29 @@ class API1_2_1Test extends ServerSetupWithTestData with DefaultUsers with Privat
makeGetRequest(request)
}
def getUserAccountPermission(bankId : String, accountId : String, userId : String, consumerAndToken: Option[(Consumer, Token)]) : APIResponse = {
val request = v1_2_1Request / "banks" / bankId / "accounts" / accountId / "permissions" / defaultProvider / userId <@(consumerAndToken)
def getUserAccountPermission(bankId : String, accountId : String, providerId : String, consumerAndToken: Option[(Consumer, Token)]) : APIResponse = {
val request = v1_2_1Request / "banks" / bankId / "accounts" / accountId / "permissions" / defaultProvider / providerId <@(consumerAndToken)
makeGetRequest(request)
}
def grantUserAccessToView(bankId : String, accountId : String, userId : String, viewId : String, consumerAndToken: Option[(Consumer, Token)]) : APIResponse= {
val request = (v1_2_1Request / "banks" / bankId / "accounts" / accountId / "permissions"/ defaultProvider / userId / "views" / viewId).POST <@(consumerAndToken)
def grantUserAccessToView(bankId : String, accountId : String, providerId : String, viewId : String, consumerAndToken: Option[(Consumer, Token)]) : APIResponse= {
val request = (v1_2_1Request / "banks" / bankId / "accounts" / accountId / "permissions"/ defaultProvider / providerId / "views" / viewId).POST <@(consumerAndToken)
makePostRequest(request, "")
}
def grantUserAccessToViews(bankId : String, accountId : String, userId : String, viewIds : List[String], consumerAndToken: Option[(Consumer, Token)]) : APIResponse= {
val request = (v1_2_1Request / "banks" / bankId / "accounts" / accountId / "permissions"/ defaultProvider / userId / "views").POST <@(consumerAndToken)
def grantUserAccessToViews(bankId : String, accountId : String, providerId : String, viewIds : List[String], consumerAndToken: Option[(Consumer, Token)]) : APIResponse= {
val request = (v1_2_1Request / "banks" / bankId / "accounts" / accountId / "permissions"/ defaultProvider / providerId / "views").POST <@(consumerAndToken)
val viewsJson = ViewIdsJson(viewIds)
makePostRequest(request, write(viewsJson))
}
def revokeUserAccessToView(bankId : String, accountId : String, userId : String, viewId : String, consumerAndToken: Option[(Consumer, Token)]) : APIResponse= {
val request = (v1_2_1Request / "banks" / bankId / "accounts" / accountId / "permissions"/ defaultProvider / userId / "views" / viewId).DELETE <@(consumerAndToken)
def revokeUserAccessToView(bankId : String, accountId : String, providerId : String, viewId : String, consumerAndToken: Option[(Consumer, Token)]) : APIResponse= {
val request = (v1_2_1Request / "banks" / bankId / "accounts" / accountId / "permissions"/ defaultProvider / providerId / "views" / viewId).DELETE <@(consumerAndToken)
makeDeleteRequest(request)
}
def revokeUserAccessToAllViews(bankId : String, accountId : String, userId : String, consumerAndToken: Option[(Consumer, Token)]) : APIResponse= {
val request = (v1_2_1Request / "banks" / bankId / "accounts" / accountId / "permissions"/ defaultProvider / userId / "views").DELETE <@(consumerAndToken)
def revokeUserAccessToAllViews(bankId : String, accountId : String, providerId : String, consumerAndToken: Option[(Consumer, Token)]) : APIResponse= {
val request = (v1_2_1Request / "banks" / bankId / "accounts" / accountId / "permissions"/ defaultProvider / providerId / "views").DELETE <@(consumerAndToken)
makeDeleteRequest(request)
}
@ -2053,7 +2053,7 @@ class API1_2_1Test extends ServerSetupWithTestData with DefaultUsers with Privat
reply.code should equal (204)
And("The account holder do not have access to the owner view")
val view = Views.views.vend.customView(ownerViewId, BankIdAccountId(BankId(bankId), AccountId(bankAccount.id))).openOrThrowException(attemptedToOpenAnEmptyBox)
val view = Views.views.vend.systemView(ownerViewId).openOrThrowException(attemptedToOpenAnEmptyBox)
Views.views.vend.getOwners(view).toList should not contain (resourceUser3)
}
@ -2135,7 +2135,7 @@ class API1_2_1Test extends ServerSetupWithTestData with DefaultUsers with Privat
val bankId = randomBank
val bankAccount : AccountJSON = randomPrivateAccount(bankId)
val viewId = ViewId(SYSTEM_OWNER_VIEW_ID)
val view = Views.views.vend.customView(viewId, BankIdAccountId(BankId(bankId), AccountId(bankAccount.id))).openOrThrowException(attemptedToOpenAnEmptyBox)
val view = Views.views.vend.systemView(viewId).openOrThrowException(attemptedToOpenAnEmptyBox)
val userId = resourceUser1.idGivenByProvider
Views.views.vend.getOwners(view).toList.length should equal(1)
@ -2143,8 +2143,8 @@ class API1_2_1Test extends ServerSetupWithTestData with DefaultUsers with Privat
When("the request is sent")
val reply = revokeUserAccessToAllViews(bankId, bankAccount.id, userId, user1)
Then("we should get a 400 code")
reply.code should equal (400)
Then("we should get a 204 code")
reply.code should equal (204)
And("The user should not have had his access revoked")
Views.views.vend.getOwners(view).toList.length should equal(1)
@ -2168,9 +2168,9 @@ class API1_2_1Test extends ServerSetupWithTestData with DefaultUsers with Privat
Then("we should get a 204 code")
reply.code should equal (204)
And("The user should have had his access revoked")
val view = Views.views.vend.customView(ViewId(SYSTEM_OWNER_VIEW_ID), BankIdAccountId(BankId(bankId), AccountId(bankAccount.id))).openOrThrowException(attemptedToOpenAnEmptyBox)
Views.views.vend.getOwners(view).toList should not contain (resourceUser3)
And("The user should not have had his access revoked")
val view = Views.views.vend.systemView(ViewId(SYSTEM_OWNER_VIEW_ID)).openOrThrowException(attemptedToOpenAnEmptyBox)
Views.views.vend.getOwners(view).toList should contain (resourceUser3)
}
}

View File

@ -78,7 +78,7 @@ class ConsentTest extends V310ServerSetup {
response400.code should equal(401)
response400.body.extract[ErrorMessage].message should equal(UserNotLoggedIn)
}
scenario("We will call the endpoint with user credentials but wrong SCA method", ApiEndpoint1, VersionOfApi) {
When("We make a request")
val request400 = (v3_1_0_Request / "banks" / bankId / "my" / "consents" / "NOT_EMAIL_NEITHER_SMS" ).POST <@(user1)
@ -87,7 +87,7 @@ class ConsentTest extends V310ServerSetup {
response400.code should equal(400)
response400.body.extract[ErrorMessage].message should equal(ConsentAllowedScaMethods)
}
scenario("We will call the endpoint with user credentials", ApiEndpoint1, ApiEndpoint3, VersionOfApi, VersionOfApi2) {
wholeFunctionality(RequestHeader.`Consent-JWT`)
}

View File

@ -73,7 +73,7 @@ class SystemViewsTests extends V310ServerSetup {
// System view, owner
val randomSystemViewId = APIUtil.generateUUID()
val postBodySystemViewJson = createSystemViewJsonV300.copy(name=randomSystemViewId).copy(metadata_view = randomSystemViewId).toCreateViewJson
val systemViewId = MapperViews.getNewViewPermalink(postBodySystemViewJson.name)
val systemViewId = MapperViews.createViewIdByName(postBodySystemViewJson.name)
def getSystemView(viewId : String, consumerAndToken: Option[(Consumer, Token)]): APIResponse = {
val request = v3_1_0_Request / "system-views" / viewId <@(consumerAndToken)

View File

@ -75,7 +75,7 @@ class SystemViewsTests extends V500ServerSetup {
val postBodySystemViewJson = createSystemViewJsonV500
.copy(name=randomSystemViewId)
.copy(metadata_view = randomSystemViewId).toCreateViewJson
val systemViewId = MapperViews.getNewViewPermalink(postBodySystemViewJson.name)
val systemViewId = MapperViews.createViewIdByName(postBodySystemViewJson.name)
def getSystemView(viewId : String, consumerAndToken: Option[(Consumer, Token)]): APIResponse = {
val request = v5_0_0_Request / "system-views" / viewId <@(consumerAndToken)

View File

@ -3,6 +3,7 @@ package code.setup
import java.util.{Calendar, Date}
import code.accountholders.AccountHolders
import code.api.Constant.{SYSTEM_ACCOUNTANT_VIEW_ID, SYSTEM_AUDITOR_VIEW_ID, SYSTEM_FIREHOSE_VIEW_ID, SYSTEM_OWNER_VIEW_ID}
import code.api.util.ErrorMessages.attemptedToOpenAnEmptyBox
import code.api.util.{APIUtil, OBPLimit, OBPOffset}
import code.bankconnectors.{Connector, LocalMappedConnector}
import code.model._
@ -14,7 +15,6 @@ trait TestConnectorSetup {
//TODO: implement these right here using Connector.connector.vend and get rid of specific connector setup files
protected def createBank(id : String) : Bank
@deprecated("Please use `createAccountAndOwnerView` instead, we need owner view for each account! ","2018-02-23")
protected def createAccount(bankId: BankId, accountId : AccountId, currency : String) : BankAccount
protected def createTransaction(account : BankAccount, startDate : Date, finishDate : Date)
protected def createTransactionRequest(account: BankAccount): List[MappedTransactionRequest]
@ -25,8 +25,8 @@ trait TestConnectorSetup {
/**
* This method, will do 4 things:
* 1 create account
* 2 create the `owner-view`
* 3 grant the `owner-view` access to the User.
* 2 get or create system the `owner` view
* 3 grant the `owner` view access to the User.
* 4 create the accountHolder for this account.
* @param accountOwner it is just a random user here, the user will have the access to the `owner view`
* @param bankId one bankId
@ -36,9 +36,9 @@ trait TestConnectorSetup {
*/
final protected def createAccountRelevantResource(accountOwner: Option[User], bankId: BankId, accountId : AccountId, currency : String) : BankAccount = {
val account = createAccount(bankId, accountId, currency)
val ownerView = createOwnerView(bankId, accountId)
val ownerView = Views.views.vend.getOrCreateSystemView(SYSTEM_OWNER_VIEW_ID).openOrThrowException(attemptedToOpenAnEmptyBox)
accountOwner.foreach(AccountHolders.accountHolders.vend.getOrCreateAccountHolder(_, BankIdAccountId(account.bankId, account.accountId)))
accountOwner.foreach(Views.views.vend.grantAccessToCustomView(ViewIdBankIdAccountId(ViewId(ownerView.viewId.value), BankId(ownerView.bankId.value), AccountId(ownerView.accountId.value)), _))
accountOwner.foreach(Views.views.vend.grantAccessToSystemView(bankId, accountId, ownerView, _))
account
}
@ -147,8 +147,6 @@ trait TestConnectorSetup {
createBank("payment-test-bank")
protected def getOrCreateSystemView(name: String): View
protected def createOwnerView(bankId: BankId, accountId: AccountId) : View
protected def createPublicView(bankId: BankId, accountId: AccountId) : View
protected def createCustomRandomView(bankId: BankId, accountId: AccountId) : View

View File

@ -2,6 +2,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.ErrorMessages._
import code.model._
import code.model.dataAccess._
@ -24,11 +25,11 @@ trait TestConnectorSetupWithStandardPermissions extends TestConnectorSetup {
Views.views.vend.getOrCreateSystemView(name).openOrThrowException(attemptedToOpenAnEmptyBox)
}
protected def createOwnerView(bankId: BankId, accountId: AccountId ) : View = {
Views.views.vend.getOrCreateOwnerView(bankId, accountId, randomString(3)).openOrThrowException(attemptedToOpenAnEmptyBox)
Views.views.vend.getOrCreateSystemView(SYSTEM_OWNER_VIEW_ID).openOrThrowException(attemptedToOpenAnEmptyBox)
}
protected def createPublicView(bankId: BankId, accountId: AccountId) : View = {
Views.views.vend.getOrCreateCustomPublicView(bankId, accountId, randomString(3)).openOrThrowException(attemptedToOpenAnEmptyBox)
Views.views.vend.getOrCreateCustomPublicView(bankId: BankId, accountId: AccountId, CUSTOM_PUBLIC_VIEW_ID).openOrThrowException(attemptedToOpenAnEmptyBox)
}
protected def createCustomRandomView(bankId: BankId, accountId: AccountId) : View = {

View File

@ -57,6 +57,7 @@ trait User {
def userPrimaryKey : UserPrimaryKey
/** This will be a UUID for Resource User Document */
def userId: String
//this used same as username now
def idGivenByProvider: String
def provider : String
def emailAddress : String