mirror of
https://github.com/OpenBankProject/OBP-API.git
synced 2026-02-06 11:06:49 +00:00
Merge pull request #1997 from OpenBankProject/develop
regular code cycle
This commit is contained in:
commit
c3eed98d70
@ -20,6 +20,7 @@ Please structure git commit messages in a way as shown below:
|
||||
4. refactor/Something
|
||||
5. performance/Something
|
||||
6. test/Something
|
||||
7. enhancement/Something
|
||||
|
||||
## Code comments
|
||||
|
||||
|
||||
2
NOTICE
2
NOTICE
@ -1,5 +1,5 @@
|
||||
Open Bank Project API
|
||||
Copyright (C) 2011-2019, TESOBE GmbH
|
||||
Copyright (C) 2011-2021, TESOBE GmbH
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
|
||||
38
branding.md
38
branding.md
@ -4,10 +4,46 @@ Look and feel of the API landing page can be modified via css and with a number
|
||||
|
||||
## CSS
|
||||
|
||||
The default css is located here:
|
||||
### Main CSS
|
||||
|
||||
The main css is located here:
|
||||
|
||||
OBP-API/src/main/webapp/media/css/website.css
|
||||
|
||||
In case you want to keep it outside of the public code you can specify a new URI via props `webui_main_style_sheet`. For instance:
|
||||
|
||||
webui_override_style_sheet = https://static.openbankproject.com/test/css/website.css
|
||||
|
||||
### Override CSS
|
||||
|
||||
The override css is used if you use `OBP-API/src/main/webapp/media/css/website.css` but you want to override some instance specific values.
|
||||
In that case you can do it via props `webui_override_style_sheet` i.e.
|
||||
|
||||
webui_override_style_sheet = https://static.openbankproject.com/test/css/override.css
|
||||
|
||||
where `override.css` could be:
|
||||
|
||||
```css
|
||||
.navbar-default .navbar-nav > li #navitem-logo {
|
||||
padding-top: 11px;
|
||||
padding-bottom: 11px;
|
||||
margin-right: 16px;
|
||||
height: 88px;
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.navbar-default .navbar-nav > li #navitem-logo img {
|
||||
width: 67px;
|
||||
height: 67px;
|
||||
}
|
||||
|
||||
|
||||
|
||||
#main-about {
|
||||
height: 552px;
|
||||
}
|
||||
```
|
||||
|
||||
## Props
|
||||
|
||||
There's a number of props variables starting with webui_* - see OBP-API/src/main/resources/props/sample.props.template for a comprehensive list and default values.
|
||||
|
||||
@ -725,6 +725,7 @@ display_internal_errors=false
|
||||
# -- OAuth 2 ---------------------------------------------------------------------------------
|
||||
# Enable/Disable OAuth 2 workflow at a server instance
|
||||
# In case isn't defined default value is false
|
||||
# NOTE: Make sure there is no space after the word true/false.
|
||||
# allow_oauth2_login=false
|
||||
# URL of Public server JWK set used for validating bearer JWT access tokens
|
||||
# It can contain more than one URL i.e. list of uris. Values are comma separated.
|
||||
|
||||
@ -120,7 +120,7 @@ import code.transactionattribute.MappedTransactionAttribute
|
||||
import code.transactionrequests.{MappedTransactionRequest, MappedTransactionRequestTypeCharge, TransactionRequestReasons}
|
||||
import code.usercustomerlinks.MappedUserCustomerLink
|
||||
import code.userlocks.UserLocks
|
||||
import code.users.{UserAgreement, UserInvitation}
|
||||
import code.users.{UserAgreement, UserInitAction, UserInvitation}
|
||||
import code.util.Helper.MdcLoggable
|
||||
import code.util.{Helper, HydraUtil}
|
||||
import code.validation.JsonSchemaValidation
|
||||
@ -131,7 +131,6 @@ import code.webuiprops.WebUiProps
|
||||
import com.openbankproject.commons.model.ErrorMessage
|
||||
import com.openbankproject.commons.util.Functions.Implicits._
|
||||
import com.openbankproject.commons.util.{ApiVersion, Functions}
|
||||
|
||||
import javax.mail.{Authenticator, PasswordAuthentication}
|
||||
import javax.mail.internet.MimeMessage
|
||||
import net.liftweb.common._
|
||||
@ -963,7 +962,8 @@ object ToSchemify {
|
||||
DynamicResourceDoc,
|
||||
DynamicMessageDoc,
|
||||
EndpointTag,
|
||||
ProductFee
|
||||
ProductFee,
|
||||
UserInitAction
|
||||
)++ APIBuilder_Connector.allAPIBuilderModels
|
||||
|
||||
// start grpc server
|
||||
|
||||
@ -29,7 +29,7 @@ package code.api
|
||||
import java.util.Date
|
||||
|
||||
import code.api.util.APIUtil._
|
||||
import code.api.util.ErrorMessages.InvalidDirectLoginParameters
|
||||
import code.api.util.ErrorMessages.{InvalidDirectLoginParameters, attemptedToOpenAnEmptyBox}
|
||||
import code.api.util.NewStyle.HttpCode
|
||||
import code.api.util._
|
||||
import code.consumer.Consumers._
|
||||
@ -115,6 +115,8 @@ object DirectLogin extends RestHelper with MdcLoggable {
|
||||
val authUser = AuthUser.findUserByUsernameLocally(resourceUser.name).openOrThrowException(s"$InvalidDirectLoginParameters can not find the auth user!")
|
||||
AuthUser.grantEntitlementsToUseDynamicEndpointsInSpaces(authUser)
|
||||
AuthUser.grantEmailDomainEntitlementsToUser(authUser)
|
||||
// User init actions
|
||||
AfterApiAuth.innerLoginUserInitAction(Full(authUser))
|
||||
} catch {
|
||||
case e: Throwable => // error handling, found wrong props value as early as possible.
|
||||
this.logger.error(s"directLogin.grantEntitlementsToUseDynamicEndpointsInSpacesInDirectLogin throw exception, details: $e" );
|
||||
@ -251,6 +253,10 @@ object DirectLogin extends RestHelper with MdcLoggable {
|
||||
}
|
||||
|
||||
S.request match {
|
||||
// Recommended header style i.e. DirectLogin: username=s, password=s, consumer_key=s
|
||||
case Full(a) if a.header("DirectLogin").isDefined == true =>
|
||||
toMap(a.header("DirectLogin").openOrThrowException(attemptedToOpenAnEmptyBox + " => getAllParameters"))
|
||||
// Deprecated header style i.e. Authorization: DirectLogin username=s, password=s, consumer_key=s
|
||||
case Full(a) => a.header("Authorization") match {
|
||||
case Full(header) => {
|
||||
if (header.contains("DirectLogin"))
|
||||
@ -258,11 +264,7 @@ object DirectLogin extends RestHelper with MdcLoggable {
|
||||
else
|
||||
Map("error" -> "header incorrect")
|
||||
}
|
||||
case _ =>
|
||||
a.header("DirectLogin") match {
|
||||
case Full(header) => toMap(header)
|
||||
case _ => Map("error" -> "missing header")
|
||||
}
|
||||
case _ => Map("error" -> "missing header")
|
||||
}
|
||||
case _ => Map("error" -> "request incorrect")
|
||||
}
|
||||
|
||||
@ -29,7 +29,7 @@ package code.api
|
||||
import java.net.HttpURLConnection
|
||||
|
||||
import code.api.util.APIUtil._
|
||||
import code.api.util.{APIUtil, ErrorMessages, JwtUtil}
|
||||
import code.api.util.{APIUtil, AfterApiAuth, ErrorMessages, JwtUtil}
|
||||
import code.consumer.Consumers
|
||||
import code.loginattempts.LoginAttempt
|
||||
import code.model.{AppType, Consumer}
|
||||
@ -126,18 +126,20 @@ object OpenIdConnect extends OBPRestHelper with MdcLoggable {
|
||||
AuthUser.grantEmailDomainEntitlementsToUser(authUser)
|
||||
// Grant roles according to the props email_domain_to_space_mappings
|
||||
AuthUser.grantEntitlementsToUseDynamicEndpointsInSpaces(authUser)
|
||||
// User init actions
|
||||
AfterApiAuth.innerLoginUserInitAction(Full(authUser))
|
||||
// Consumer
|
||||
getOrCreateConsumer(idToken, user.userId) match {
|
||||
case Full(consumer) =>
|
||||
saveAuthorizationToken(tokenType, accessToken, idToken, refreshToken, scope, expiresIn, authUser.id.get) match {
|
||||
case Full(token) => (200, "OK", Some(authUser))
|
||||
case badObj@Failure(_, _, _) => chainErrorMessage(badObj, ErrorMessages.CouldNotHandleOpenIDConnectData)
|
||||
case badObj@Failure(_, _, _) => chainErrorMessage(badObj, ErrorMessages.CouldNotHandleOpenIDConnectData+ "saveAuthorizationToken")
|
||||
case _ => (401, ErrorMessages.CouldNotHandleOpenIDConnectData + "saveAuthorizationToken", Some(authUser))
|
||||
}
|
||||
case badObj@Failure(_, _, _) => chainErrorMessage(badObj, ErrorMessages.CouldNotHandleOpenIDConnectData)
|
||||
case badObj@Failure(_, _, _) => chainErrorMessage(badObj, ErrorMessages.CouldNotHandleOpenIDConnectData + "getOrCreateConsumer")
|
||||
case _ => (401, ErrorMessages.CouldNotHandleOpenIDConnectData + "getOrCreateConsumer", Some(authUser))
|
||||
}
|
||||
case badObj@Failure(_, _, _) => chainErrorMessage(badObj, ErrorMessages.CouldNotHandleOpenIDConnectData)
|
||||
case badObj@Failure(_, _, _) => chainErrorMessage(badObj, ErrorMessages.CouldNotHandleOpenIDConnectData + "getOrCreateAuthUser")
|
||||
case _ => (401, ErrorMessages.CouldNotHandleOpenIDConnectData + "getOrCreateAuthUser", None)
|
||||
}
|
||||
case badObj@Failure(_, _, _) => chainErrorMessage(badObj, ErrorMessages.CouldNotSaveOpenIDConnectUser)
|
||||
|
||||
@ -161,6 +161,8 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
|
||||
def hasDirectLoginHeader(authorization: Box[String]): Boolean = hasHeader("DirectLogin", authorization)
|
||||
|
||||
def has2021DirectLoginHeader(requestHeaders: List[HTTPParam]): Boolean = requestHeaders.find(_.name == "DirectLogin").isDefined
|
||||
|
||||
def hasAuthorizationHeader(requestHeaders: List[HTTPParam]): Boolean = requestHeaders.find(_.name == "Authorization").isDefined
|
||||
|
||||
def hasAnOAuthHeader(authorization: Box[String]): Boolean = hasHeader("OAuth", authorization)
|
||||
|
||||
@ -2760,7 +2762,14 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
|
||||
Future{(cc.user, Some(cc))}
|
||||
}
|
||||
else {
|
||||
Future { (Empty, Some(cc)) }
|
||||
if(hasAuthorizationHeader(reqHeaders)) {
|
||||
// We want to throw error in case of wrong or unsupported header. For instance:
|
||||
// - Authorization: mF_9.B5f-4.1JqM
|
||||
// - Authorization: Basic mF_9.B5f-4.1JqM
|
||||
Future { (Failure(ErrorMessages.InvalidAuthorizationHeader), Some(cc)) }
|
||||
} else {
|
||||
Future { (Empty, Some(cc)) }
|
||||
}
|
||||
}
|
||||
|
||||
// COMMON POST AUTHENTICATION CODE GOES BELOW
|
||||
@ -2769,9 +2778,11 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
|
||||
val userIsLockedOrDeleted: Future[(Box[User], Option[CallContext])] = AfterApiAuth.checkUserIsDeletedOrLocked(res)
|
||||
// Check Rate Limiting
|
||||
val resultWithRateLimiting: Future[(Box[User], Option[CallContext])] = AfterApiAuth.checkRateLimiting(userIsLockedOrDeleted)
|
||||
// User init actions
|
||||
val resultWithUserInitActions: Future[(Box[User], Option[CallContext])] = AfterApiAuth.outerLoginUserInitAction(resultWithRateLimiting)
|
||||
|
||||
// Update Call Context
|
||||
resultWithRateLimiting map {
|
||||
resultWithUserInitActions map {
|
||||
x => (x._1, ApiSession.updateCallContext(Spelling(spelling), x._2))
|
||||
} map {
|
||||
x => (x._1, x._2.map(_.copy(implementedInVersion = implementedInVersion)))
|
||||
|
||||
@ -2,18 +2,62 @@ package code.api.util
|
||||
|
||||
import java.util.Date
|
||||
|
||||
import code.api.Constant
|
||||
import code.api.util.APIUtil.getPropsAsBoolValue
|
||||
import code.api.util.ApiRole.{CanCreateAccount, CanCreateHistoricalTransactionAtBank}
|
||||
import code.api.util.ErrorMessages.{UserIsDeleted, UsernameHasBeenLocked}
|
||||
import code.api.util.RateLimitingJson.CallLimit
|
||||
import code.bankconnectors.Connector
|
||||
import code.entitlement.Entitlement
|
||||
import code.loginattempts.LoginAttempt
|
||||
import code.model.dataAccess.{AuthUser, MappedBankAccount}
|
||||
import code.ratelimiting.{RateLimiting, RateLimitingDI}
|
||||
import com.openbankproject.commons.model.User
|
||||
import code.users.{UserInitActionProvider, Users}
|
||||
import code.util.Helper.MdcLoggable
|
||||
import code.views.Views
|
||||
import com.openbankproject.commons.model.{AccountId, Bank, BankAccount, User, ViewId}
|
||||
import net.liftweb.common.{Box, Empty, Failure, Full}
|
||||
import com.openbankproject.commons.ExecutionContext.Implicits.global
|
||||
import net.liftweb.mapper.By
|
||||
|
||||
import scala.concurrent.Future
|
||||
|
||||
|
||||
object AfterApiAuth {
|
||||
object AfterApiAuth extends MdcLoggable{
|
||||
/**
|
||||
* This function is used to execute actions after an user is authenticated via GUI
|
||||
* Types of authentication: GUI logon(OpenID Connect and OAuth1.0a)
|
||||
* @param authUser the authenticated user
|
||||
*/
|
||||
def innerLoginUserInitAction(authUser: Box[AuthUser]) = {
|
||||
authUser.map { u => // Init actions
|
||||
logger.info("AfterApiAuth.innerLoginUserInitAction started successfully")
|
||||
sofitInitAction(u)
|
||||
} match {
|
||||
case Full(_) => logger.warn("AfterApiAuth.innerLoginUserInitAction completed successfully")
|
||||
case userInitActionFailure => logger.warn("AfterApiAuth.innerLoginUserInitAction: " + userInitActionFailure)
|
||||
}
|
||||
}
|
||||
/**
|
||||
* This function is used to execute actions after an user is authenticated via API
|
||||
* Types of authentication: Direct Login, OpenID Connect, OAuth1.0a, Direct Login, DAuth and Gateway Login
|
||||
*/
|
||||
def outerLoginUserInitAction(result: Future[(Box[User], Option[CallContext])]): Future[(Box[User], Option[CallContext])] = {
|
||||
logger.info("AfterApiAuth.outerLoginUserInitAction started successfully")
|
||||
for {
|
||||
(user: Box[User], cc) <- result
|
||||
} yield {
|
||||
user match {
|
||||
case Full(u) => // There is a user. Apply init actions
|
||||
val authUser: Box[AuthUser] = AuthUser.find(By(AuthUser.user, u.userPrimaryKey.value))
|
||||
innerLoginUserInitAction(authUser)
|
||||
(user, cc)
|
||||
case userInitActionFailure => // There is no user. Just forward the result.
|
||||
logger.warn("AfterApiAuth.outerLoginUserInitAction: " + userInitActionFailure)
|
||||
(user, cc)
|
||||
}
|
||||
}
|
||||
}
|
||||
def checkUserIsDeletedOrLocked(res: Future[(Box[User], Option[CallContext])]): Future[(Box[User], Option[CallContext])] = {
|
||||
for {
|
||||
(user: Box[User], cc) <- res
|
||||
@ -87,4 +131,75 @@ object AfterApiAuth {
|
||||
}
|
||||
}
|
||||
|
||||
private def sofitInitAction(user: AuthUser): Boolean = applyAction("sofit.logon_init_action.enabled") {
|
||||
def getOrCreateBankAccount(bank: Bank, accountId: String, label: String, accountType: String = ""): Box[BankAccount] = {
|
||||
MappedBankAccount.find(
|
||||
By(MappedBankAccount.bank, bank.bankId.value),
|
||||
By(MappedBankAccount.theAccountId, accountId)
|
||||
) match {
|
||||
case Full(bankAccount) => Full(bankAccount)
|
||||
case _ =>
|
||||
val account = Connector.connector.vend.createSandboxBankAccount(
|
||||
bankId = bank.bankId, accountId = AccountId(accountId), accountNumber = label + "-1",
|
||||
accountType = accountType, accountLabel = s"$label",
|
||||
currency = "EUR", initialBalance = 0, accountHolderName = user.username.get,
|
||||
"",
|
||||
List.empty
|
||||
)
|
||||
if(account.isEmpty) logger.warn(s"AfterApiAuth.sofitInitAction. Cannot create the $label: account for user." + user.firstName + " " + user.lastName)
|
||||
account
|
||||
}
|
||||
}
|
||||
|
||||
Users.users.vend.getUserByResourceUserId(user.user.get) match {
|
||||
case Full(resourceUser) =>
|
||||
// Create a bank according to the rule: bankid = user.user_id
|
||||
val bankId = "user." + resourceUser.userId
|
||||
Connector.connector.vend.createOrUpdateBank(
|
||||
bankId = bankId,
|
||||
fullBankName = "user." + resourceUser.userId,
|
||||
shortBankName = "user." + resourceUser.userId,
|
||||
logoURL = "",
|
||||
websiteURL = "",
|
||||
swiftBIC = "",
|
||||
national_identifier = "",
|
||||
bankRoutingScheme = "USER_ID",
|
||||
bankRoutingAddress = resourceUser.userId
|
||||
) match {
|
||||
case Full(bank) =>
|
||||
UserInitActionProvider.createOrUpdateInitAction(resourceUser.userId, "create-or-update-bank", bankId, true)
|
||||
// Add roles
|
||||
val addCanCreateAccount = Entitlement.entitlement.vend.getEntitlement(bank.bankId.value, resourceUser.userId, CanCreateAccount.toString()).or {
|
||||
Entitlement.entitlement.vend.addEntitlement(bank.bankId.value, resourceUser.userId, CanCreateAccount.toString())
|
||||
}.isDefined
|
||||
UserInitActionProvider.createOrUpdateInitAction(resourceUser.userId, "add-entitlement", CanCreateAccount.toString(), addCanCreateAccount)
|
||||
val addCanCreateHistoricalTransactionAtBank = Entitlement.entitlement.vend.getEntitlement(bank.bankId.value, resourceUser.userId, CanCreateHistoricalTransactionAtBank.toString()).or {
|
||||
Entitlement.entitlement.vend.addEntitlement(bank.bankId.value, resourceUser.userId, CanCreateHistoricalTransactionAtBank.toString())
|
||||
}.isDefined
|
||||
UserInitActionProvider.createOrUpdateInitAction(resourceUser.userId, "add-entitlement", CanCreateHistoricalTransactionAtBank.toString(), addCanCreateHistoricalTransactionAtBank)
|
||||
// Create Cash account
|
||||
val bankAccount = getOrCreateBankAccount(bank, "cash", "cash-flow").flatMap( account =>
|
||||
Views.views.vend.systemView(ViewId(Constant.SYSTEM_OWNER_VIEW_ID)).flatMap( view =>
|
||||
// Grant account access
|
||||
Views.views.vend.grantAccessToSystemView(bank.bankId, account.accountId, view, resourceUser)
|
||||
)
|
||||
).isDefined
|
||||
UserInitActionProvider.createOrUpdateInitAction(resourceUser.userId, "add-bank-account", "cache", bankAccount)
|
||||
addCanCreateAccount && addCanCreateHistoricalTransactionAtBank && bankAccount
|
||||
case _ =>
|
||||
logger.warn("AfterApiAuth.sofitInitAction. Cannot create the bank: user." + resourceUser.userId)
|
||||
UserInitActionProvider.createOrUpdateInitAction(resourceUser.userId, "createOrUpdateBank", bankId, false)
|
||||
false
|
||||
}
|
||||
case _ =>
|
||||
logger.warn("AfterApiAuth.sofitInitAction. Cannot find resource user by primary key: " + user.id.get)
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
private def applyAction(propsName: String)(blockOfCode: => Boolean): Boolean = {
|
||||
val enabled = getPropsAsBoolValue(propsName, false)
|
||||
if(enabled) blockOfCode else false
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -688,10 +688,16 @@ object ApiRole {
|
||||
lazy val canDeleteTransactionCascade = CanDeleteTransactionCascade()
|
||||
|
||||
case class CanDeleteAccountCascade(requiresBankId: Boolean = true) extends ApiRole
|
||||
lazy val canDeleteAccountCascade = CanDeleteAccountCascade()
|
||||
lazy val canDeleteAccountCascade = CanDeleteAccountCascade()
|
||||
|
||||
case class CanDeleteBankCascade(requiresBankId: Boolean = true) extends ApiRole
|
||||
lazy val canDeleteBankCascade = CanDeleteBankCascade()
|
||||
|
||||
case class CanDeleteProductCascade(requiresBankId: Boolean = true) extends ApiRole
|
||||
lazy val canDeleteProductCascade = CanDeleteProductCascade()
|
||||
lazy val canDeleteProductCascade = CanDeleteProductCascade()
|
||||
|
||||
case class CanDeleteCustomerCascade(requiresBankId: Boolean = true) extends ApiRole
|
||||
lazy val canDeleteCustomerCascade = CanDeleteCustomerCascade()
|
||||
|
||||
case class CanGetConnectorEndpoint(requiresBankId: Boolean = false) extends ApiRole
|
||||
lazy val canGetConnectorEndpoint = CanGetConnectorEndpoint()
|
||||
|
||||
@ -190,6 +190,9 @@ object ErrorMessages {
|
||||
val DAuthNoJwtForResponse = "OBP-20070: There is no useful value for JWT."
|
||||
val DAuthJwtTokenIsNotValid = "OBP-20071: The DAuth JWT is corrupted/changed during a transport."
|
||||
val InvalidDAuthHeaderToken = "OBP-20072: DAuth Header value should be one single string."
|
||||
|
||||
val InvalidAuthorizationHeader = "OBP-20080: Authorization Header format is not supported at this instance."
|
||||
|
||||
|
||||
val UserNotSuperAdminOrMissRole = "OBP-20101: Current User is not super admin or is missing entitlements: "
|
||||
val CannotGetOrCreateUser = "OBP-20102: Cannot get or create user."
|
||||
@ -413,6 +416,7 @@ object ErrorMessages {
|
||||
val EntitlementAlreadyExists = "OBP-30216: Entitlement already exists for the user."
|
||||
val EntitlementCannotBeDeleted = "OBP-30219: EntitlementId cannot be deleted."
|
||||
val EntitlementCannotBeGranted = "OBP-30220: Entitlement cannot be granted."
|
||||
val EntitlementCannotBeGrantedGrantorIssue = "OBP-30221: Entitlement cannot be granted due to the grantor's insufficient privileges."
|
||||
|
||||
val CreateSystemViewError = "OBP-30250: Could not create the system view"
|
||||
val DeleteSystemViewError = "OBP-30251: Could not delete the system view"
|
||||
|
||||
@ -1958,10 +1958,10 @@ trait APIMethods200 {
|
||||
_ <- Helper.booleanToFuture(failMsg = if (ApiRole.valueOf(postedData.role_name).requiresBankId) EntitlementIsBankRole else EntitlementIsSystemRole, cc=callContext) {
|
||||
ApiRole.valueOf(postedData.role_name).requiresBankId == postedData.bank_id.nonEmpty
|
||||
}
|
||||
allowedEntitlements = canCreateEntitlementAtOneBank :: canCreateEntitlementAtAnyBank :: Nil
|
||||
allowedEntitlementsTxt = UserNotSuperAdmin +" or" + UserHasMissingRoles + canCreateEntitlementAtOneBank + s" BankId(${postedData.bank_id})." + " or" + UserHasMissingRoles + canCreateEntitlementAtAnyBank
|
||||
requiredEntitlements = canCreateEntitlementAtOneBank :: canCreateEntitlementAtAnyBank :: Nil
|
||||
requiredEntitlementsTxt = UserNotSuperAdmin +" or" + UserHasMissingRoles + canCreateEntitlementAtOneBank + s" BankId(${postedData.bank_id})." + " or" + UserHasMissingRoles + canCreateEntitlementAtAnyBank
|
||||
_ <- if(isSuperAdmin(u.userId)) Future.successful(Full(Unit))
|
||||
else NewStyle.function.hasAtLeastOneEntitlement(allowedEntitlementsTxt)(postedData.bank_id, u.userId, allowedEntitlements, callContext)
|
||||
else NewStyle.function.hasAtLeastOneEntitlement(requiredEntitlementsTxt)(postedData.bank_id, u.userId, requiredEntitlements, callContext)
|
||||
|
||||
_ <- Helper.booleanToFuture(failMsg = BankNotFound, cc=callContext) {
|
||||
postedData.bank_id.nonEmpty == false || BankX(BankId(postedData.bank_id), callContext).map(_._1).isEmpty == false
|
||||
|
||||
@ -1,5 +1,9 @@
|
||||
package code.api.v4_0_0
|
||||
|
||||
import java.net.URLEncoder
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.{Calendar, Date}
|
||||
|
||||
import code.DynamicData.{DynamicData, DynamicDataProvider}
|
||||
import code.DynamicEndpoint.DynamicEndpointSwagger
|
||||
import code.accountattribute.AccountAttributeX
|
||||
@ -9,6 +13,7 @@ import code.api.util.ApiRole.{canCreateEntitlementAtAnyBank, _}
|
||||
import code.api.util.ApiTag._
|
||||
import code.api.util.ErrorMessages._
|
||||
import code.api.util.ExampleValue._
|
||||
import code.api.util.Glossary.getGlossaryItem
|
||||
import code.api.util.NewStyle.HttpCode
|
||||
import code.api.util._
|
||||
import code.api.util.migration.Migration
|
||||
@ -25,8 +30,8 @@ import code.api.v3_0_0.JSONFactory300
|
||||
import code.api.v3_1_0._
|
||||
import code.api.v4_0_0.JSONFactory400._
|
||||
import code.api.v4_0_0.dynamic.DynamicEndpointHelper.DynamicReq
|
||||
import code.api.v4_0_0.dynamic.practise.{DynamicEndpointCodeGenerator, PractiseEndpoint}
|
||||
import code.api.v4_0_0.dynamic._
|
||||
import code.api.v4_0_0.dynamic.practise.{DynamicEndpointCodeGenerator, PractiseEndpoint}
|
||||
import code.api.{ChargePolicy, JsonResponseException}
|
||||
import code.apicollection.MappedApiCollectionsProvider
|
||||
import code.apicollectionendpoint.MappedApiCollectionEndpointsProvider
|
||||
@ -41,7 +46,7 @@ import code.endpointMapping.EndpointMappingCommons
|
||||
import code.entitlement.Entitlement
|
||||
import code.metadata.counterparties.{Counterparties, MappedCounterparty}
|
||||
import code.metadata.tags.Tags
|
||||
import code.model.dataAccess.{AuthUser, BankAccountCreation, ResourceUser}
|
||||
import code.model.dataAccess.{AuthUser, BankAccountCreation}
|
||||
import code.model.{toUserExtended, _}
|
||||
import code.ratelimiting.RateLimitingDI
|
||||
import code.snippet.{WebUIPlaceholder, WebUITemplate}
|
||||
@ -51,7 +56,7 @@ import code.transactionrequests.TransactionRequests.TransactionChallengeTypes._
|
||||
import code.transactionrequests.TransactionRequests.TransactionRequestTypes
|
||||
import code.transactionrequests.TransactionRequests.TransactionRequestTypes.{apply => _, _}
|
||||
import code.userlocks.UserLocksProvider
|
||||
import code.users.{UserAgreement, Users}
|
||||
import code.users.Users
|
||||
import code.util.Helper.booleanToFuture
|
||||
import code.util.{Helper, JsonSchemaUtil}
|
||||
import code.validation.JsonValidation
|
||||
@ -64,7 +69,7 @@ import com.openbankproject.commons.model.enums.DynamicEntityOperation._
|
||||
import com.openbankproject.commons.model.enums.{TransactionRequestStatus, _}
|
||||
import com.openbankproject.commons.model.{ListResult, _}
|
||||
import com.openbankproject.commons.util.{ApiVersion, JsonUtils, ScannedApiVersion}
|
||||
import deletion.{DeleteAccountCascade, DeleteProductCascade, DeleteTransactionCascade}
|
||||
import deletion._
|
||||
import net.liftweb.common._
|
||||
import net.liftweb.http.rest.RestHelper
|
||||
import net.liftweb.http.{JsonResponse, Req, S}
|
||||
@ -78,11 +83,6 @@ import net.liftweb.util.Mailer.{From, PlainMailBodyType, Subject, To, XHTMLMailB
|
||||
import net.liftweb.util.{Helpers, Mailer, StringHelpers}
|
||||
import org.apache.commons.collections4.CollectionUtils
|
||||
import org.apache.commons.lang3.StringUtils
|
||||
import java.net.URLEncoder
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.{Calendar, Date}
|
||||
|
||||
import code.api.util.Glossary.getGlossaryItem
|
||||
|
||||
import scala.collection.immutable.{List, Nil}
|
||||
import scala.collection.mutable.ArrayBuffer
|
||||
@ -3888,6 +3888,12 @@ trait APIMethods400 {
|
||||
|The user creating this will be automatically assigned the Role CanCreateEntitlementAtOneBank.
|
||||
|Thus the User can manage the bank they create and assign Roles to other Users.
|
||||
|
|
||||
|Only SANDBOX mode
|
||||
|The settlement accounts are created specified by the bank in the POST body.
|
||||
|Name and account id are created in accordance to the next rules:
|
||||
| - Incoming account (name: Default incoming settlement account, Account ID: OBP_DEFAULT_INCOMING_ACCOUNT_ID, currency: EUR)
|
||||
| - Outgoing account (name: Default outgoing settlement account, Account ID: OBP_DEFAULT_OUTGOING_ACCOUNT_ID, currency: EUR)
|
||||
|
|
||||
|""",
|
||||
bankJson400,
|
||||
bankJson400,
|
||||
@ -7289,6 +7295,41 @@ trait APIMethods400 {
|
||||
(Full(true), HttpCode.`200`(cc))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
staticResourceDocs += ResourceDoc(
|
||||
deleteBankCascade,
|
||||
implementedInApiVersion,
|
||||
nameOf(deleteBankCascade),
|
||||
"DELETE",
|
||||
"/management/cascading/banks/BANK_ID",
|
||||
"Delete Bank Cascade",
|
||||
s"""Delete a Bank Cascade specified by BANK_ID.
|
||||
|
|
||||
|
|
||||
|${authenticationRequiredMessage(true)}
|
||||
|
|
||||
|""",
|
||||
EmptyBody,
|
||||
EmptyBody,
|
||||
List(
|
||||
$UserNotLoggedIn,
|
||||
$BankNotFound,
|
||||
UserHasMissingRoles,
|
||||
UnknownError
|
||||
),
|
||||
List(apiTagBank, apiTagNewStyle),
|
||||
Some(List(canDeleteBankCascade)))
|
||||
|
||||
lazy val deleteBankCascade : OBPEndpoint = {
|
||||
case "management" :: "cascading" :: "banks" :: BankId(bankId) :: Nil JsonDelete _ => {
|
||||
cc =>
|
||||
for {
|
||||
_ <- Future(DeleteBankCascade.atomicDelete(bankId))
|
||||
} yield {
|
||||
(Full(true), HttpCode.`200`(cc))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
staticResourceDocs += ResourceDoc(
|
||||
@ -7327,6 +7368,44 @@ trait APIMethods400 {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
staticResourceDocs += ResourceDoc(
|
||||
deleteCustomerCascade,
|
||||
implementedInApiVersion,
|
||||
nameOf(deleteCustomerCascade),
|
||||
"DELETE",
|
||||
"/management/cascading/banks/BANK_ID/customers/CUSTOMER_ID",
|
||||
"Delete Customer Cascade",
|
||||
s"""Delete a Customer Cascade specified by CUSTOMER_ID.
|
||||
|
|
||||
|
|
||||
|${authenticationRequiredMessage(true)}
|
||||
|
|
||||
|""",
|
||||
EmptyBody,
|
||||
EmptyBody,
|
||||
List(
|
||||
$UserNotLoggedIn,
|
||||
$BankNotFound,
|
||||
CustomerNotFoundByCustomerId,
|
||||
UserHasMissingRoles,
|
||||
UnknownError
|
||||
),
|
||||
List(apiTagCustomer, apiTagNewStyle),
|
||||
Some(List(canDeleteCustomerCascade)))
|
||||
|
||||
lazy val deleteCustomerCascade : OBPEndpoint = {
|
||||
case "management" :: "cascading" :: "banks" :: BankId(bankId) :: "customers" :: CustomerId(customerId) :: Nil JsonDelete _ => {
|
||||
cc =>
|
||||
for {
|
||||
(_, callContext) <- NewStyle.function.getCustomerByCustomerId(customerId.value, Some(cc))
|
||||
_ <- Future(DeleteCustomerCascade.atomicDelete(customerId))
|
||||
} yield {
|
||||
(Full(true), HttpCode.`200`(callContext))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
staticResourceDocs += ResourceDoc(
|
||||
createCounterparty,
|
||||
@ -11052,8 +11131,8 @@ trait APIMethods400 {
|
||||
|
||||
private def createDynamicEndpointMethod(bankId: Option[String], json: JValue, cc: CallContext) = {
|
||||
for {
|
||||
(postedJson, openAPI) <- NewStyle.function.tryons(InvalidJsonFormat, 400, cc.callContext) {
|
||||
//If it is bank level, we manully added /banks/bankId in all the paths:
|
||||
(postedJson, openAPI) <- NewStyle.function.tryons(InvalidJsonFormat+"The request json is not valid OpenAPIV3.0.x or Swagger 2.0.x Please check it in Swagger Editor or similar tools ", 400, cc.callContext) {
|
||||
//If it is bank level, we manually added /banks/bankId in all the paths:
|
||||
val jsonTweakedPath = DynamicEndpointHelper.addedBankToPath(json, bankId)
|
||||
val swaggerContent = compactRender(jsonTweakedPath)
|
||||
|
||||
|
||||
@ -35,7 +35,8 @@ import java.util
|
||||
import java.util.regex.Pattern
|
||||
import java.util.{Date, UUID}
|
||||
import com.openbankproject.commons.model.enums.DynamicEntityOperation.GET_ALL
|
||||
import net.liftweb.json.Formats
|
||||
import io.swagger.v3.oas.models.examples.Example
|
||||
import net.liftweb.json.{Formats, JBool}
|
||||
|
||||
import scala.collection.JavaConverters._
|
||||
import scala.collection.immutable.List
|
||||
@ -57,6 +58,60 @@ object DynamicEndpointHelper extends RestHelper {
|
||||
|
||||
def isDynamicEntityResponse (serverUrl : String) = serverUrl matches (IsDynamicEntityUrl)
|
||||
def isMockedResponse (serverUrl : String) = serverUrl matches (IsMockUrlString)
|
||||
|
||||
/**
|
||||
* 1st: we check if it OpenAPI3.0,
|
||||
* 2rd: if not, we will check Swagger2.0
|
||||
* other case, we will return ""
|
||||
* @param openApiJson it can be swagger2.0 or openApi3.0
|
||||
* @return the openapi
|
||||
*/
|
||||
def getOpenApiVersion(openApiJson: String) ={
|
||||
val jValue = json.parse(openApiJson)
|
||||
val openApiVersion = jValue \ "openapi"
|
||||
val swaggerVersion = jValue \ "swagger"
|
||||
if (openApiVersion != JNothing ) {
|
||||
openApiVersion.values.toString.trim
|
||||
} else if (swaggerVersion != JNothing){
|
||||
swaggerVersion.values.toString.trim
|
||||
}else{
|
||||
""
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 1st: we check if it OpenAPI3.0, we will keep the OpenAPI3.0 format
|
||||
*
|
||||
* 2rd: if not, we will change it as Swagger2.0 format
|
||||
*
|
||||
*/
|
||||
def changeOpenApiVersionHost(openApiJson: String, newHost:String) ={
|
||||
//for this case, there is no host/servers object, we will add the object
|
||||
//https://github.com/OAI/OpenAPI-Specification/blob/main/examples/v3.0/api-with-examples.json
|
||||
val openApiVersion = getOpenApiVersion(openApiJson)
|
||||
val openApiJValue = json.parse(openApiJson)
|
||||
val serversField = openApiJValue \ "servers"
|
||||
val hostField = openApiJValue \ "host"
|
||||
|
||||
if (openApiVersion.startsWith("3.") && serversField != JNothing) {
|
||||
json.compactRender(openApiJValue.replace("servers"::Nil, JArray(List(JObject(List(JField("url",newHost)))))))
|
||||
} else if (openApiVersion.startsWith("3.") && serversField == JNothing) {
|
||||
val newServers = json.parse(s"""{
|
||||
| "servers": [
|
||||
| {
|
||||
| "url": "$newHost"
|
||||
| }
|
||||
| ]
|
||||
|}""".stripMargin)
|
||||
json.compactRender(openApiJValue merge newServers)
|
||||
} else if(hostField != JNothing){
|
||||
json.compactRender(openApiJValue.replace("host" :: Nil, JString(newHost)))
|
||||
} else {
|
||||
val host = json.parse(s"""{"host": "$newHost"}""".stripMargin)
|
||||
json.compactRender(openApiJValue merge host)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private def dynamicEndpointInfos: List[DynamicEndpointInfo] = {
|
||||
val dynamicEndpoints: List[DynamicEndpointT] = DynamicEndpointProvider.connectorMethodProvider.vend.getAll(None)
|
||||
@ -148,10 +203,22 @@ object DynamicEndpointHelper extends RestHelper {
|
||||
|
||||
val mockResponse: Option[(Int, JValue)] = (serverUrl, doc.successResponseBody) match {
|
||||
case (IsMockUrl(), v: PrimaryDataBody[_]) =>
|
||||
Some(code -> v.toJValue)
|
||||
//If the openAPI json do not have response body, we return true as default
|
||||
val response = if (v.toJValue == JNothing) {
|
||||
JBool(true)
|
||||
} else{
|
||||
v.toJValue
|
||||
}
|
||||
Some(code -> response)
|
||||
|
||||
case (IsMockUrl(), v: JValue) =>
|
||||
Some(code -> v)
|
||||
//If the openAPI json do not have response body, we return true as default
|
||||
val response = if (v == JNothing) {
|
||||
JBool(true)
|
||||
} else{
|
||||
v
|
||||
}
|
||||
Some(code -> response)
|
||||
|
||||
case (IsMockUrl(), v) =>
|
||||
Some(code -> json.Extraction.decompose(v))
|
||||
@ -196,7 +263,14 @@ object DynamicEndpointHelper extends RestHelper {
|
||||
}
|
||||
|
||||
val paths: mutable.Map[String, PathItem] = openAPI.getPaths.asScala
|
||||
def entitlementSuffix(path: String) = Math.abs(path.hashCode).toString.substring(0, 3) // to avoid different swagger have same entitlement
|
||||
def entitlementSuffix(path: String) = {
|
||||
val pathHashCode = Math.abs(path.hashCode).toString
|
||||
//eg: path can be "/" --> "/".hashCode => 47, the length is only 2, we need to prepare the worst case:
|
||||
if(pathHashCode.length>3)
|
||||
pathHashCode.substring(0, 3)
|
||||
else
|
||||
pathHashCode.substring(0, 2)
|
||||
} // to avoid different swagger have same entitlement
|
||||
val dynamicEndpointItems: mutable.Iterable[DynamicEndpointItem] = for {
|
||||
(path, pathItem) <- paths
|
||||
(method: HttpMethod, op: Operation) <- pathItem.readOperationsMap.asScala
|
||||
@ -216,6 +290,9 @@ object DynamicEndpointHelper extends RestHelper {
|
||||
s"""
|
||||
|
|
||||
|MethodRouting settings example:
|
||||
|
|
||||
|<details>
|
||||
|
|
||||
|```
|
||||
|{
|
||||
| "is_bank_id_exact_match":false,
|
||||
@ -239,6 +316,7 @@ object DynamicEndpointHelper extends RestHelper {
|
||||
|}
|
||||
|```
|
||||
|
|
||||
|</details>
|
||||
|""".stripMargin
|
||||
val exampleRequestBody: Product = getRequestExample(openAPI, op.getRequestBody)
|
||||
val (successCode, successResponseBody: Product) = getResponseExample(openAPI, op.getResponses)
|
||||
@ -389,6 +467,12 @@ object DynamicEndpointHelper extends RestHelper {
|
||||
successResponse.flatMap(it => getMediaType(it.getContent))
|
||||
}
|
||||
maybeMediaType match {
|
||||
// https://github.com/OAI/OpenAPI-Specification/blob/3.0.1/versions/3.0.1.md#mediaTypeObject
|
||||
// following rule is also valid in Swagger UI using this json (object foo)
|
||||
//: https://github.com/OAI/OpenAPI-Specification/blob/main/examples/v3.0/api-with-examples.json
|
||||
// if schema is not null, then it has the 1st priority
|
||||
// if schema is null, 2rd priority is examples.
|
||||
// if schema is null and examples is null, 3rd priority is example field.
|
||||
case Some(mediaType) if mediaType.getSchema() != null =>
|
||||
val schema = mediaType.getSchema()
|
||||
if(schema.isInstanceOf[ArraySchema]) {
|
||||
@ -400,6 +484,15 @@ object DynamicEndpointHelper extends RestHelper {
|
||||
.map(getName)
|
||||
.orNull
|
||||
}
|
||||
case Some(mediaType) if mediaType.getExamples() != null =>{
|
||||
val examples: util.Map[String, Example] = mediaType.getExamples()
|
||||
val objectName: Option[String] = examples.keySet().asScala.headOption
|
||||
objectName.getOrElse(examples.values().toString)
|
||||
}
|
||||
case Some(mediaType) if mediaType.getExample() != null =>{
|
||||
val example: AnyRef = mediaType.getExample()
|
||||
example.toString //TODO, here better set a default value? or can get name from the object(but it depends on the input)
|
||||
}
|
||||
case None => null
|
||||
}
|
||||
}
|
||||
@ -416,10 +509,34 @@ object DynamicEndpointHelper extends RestHelper {
|
||||
|
||||
getExample(openAPI, schema)
|
||||
} else {
|
||||
//body.content is `REQUIRED` field
|
||||
val mediaType = getMediaType(body.getContent())
|
||||
assert(mediaType.isDefined, s"RequestBody $body have no MediaType of 'application/json', 'application/x-www-form-urlencoded', 'multipart/form-data' or '*/*'")
|
||||
val schema = mediaType.get.getSchema
|
||||
getExample(openAPI, schema)
|
||||
// https://github.com/OAI/OpenAPI-Specification/blob/3.0.1/versions/3.0.1.md#mediaTypeObject
|
||||
// following rule is also valid in Swagger UI using this json (object foo)
|
||||
//: https://github.com/OAI/OpenAPI-Specification/blob/main/examples/v3.0/api-with-examples.json
|
||||
// if schema is not null, then it has the 1st priority
|
||||
// if schema is null, 2rd priority is examples.
|
||||
// if schema is null and examples is null, 3rd priority is example field.
|
||||
if (mediaType.get.getSchema != null)
|
||||
getExample(openAPI, mediaType.get.getSchema)
|
||||
else if (body!=null
|
||||
&& body.getContent != null
|
||||
&& body.getContent.values().size()>0
|
||||
&& body.getContent.values().asScala.head.getExamples != null
|
||||
&& body.getContent.values().asScala.head.getExamples.values().size() > 0
|
||||
) {
|
||||
val examplesValue = body.getContent.values().asScala.map(_.getExamples.values().asScala.map(_.getValue.toString)).map(_.head)
|
||||
convertToProduct(json.parse(examplesValue.head))
|
||||
} else if(body!=null
|
||||
&& body.getContent != null
|
||||
&& body.getContent.values().size()>0
|
||||
&& body.getContent.values().asScala.head.getExample != null
|
||||
) {
|
||||
val exampleValue = body.getContent.values().asScala.map(_.getExample.toString)
|
||||
convertToProduct(json.parse(exampleValue.head))
|
||||
}else
|
||||
EmptyBody
|
||||
}
|
||||
}
|
||||
|
||||
@ -449,7 +566,32 @@ object DynamicEndpointHelper extends RestHelper {
|
||||
val result: Option[(Int, Product)] = for {
|
||||
(code, response) <- successResponse
|
||||
schema <- getResponseSchema(openAPI, response)
|
||||
example = getExample(openAPI, schema)
|
||||
// https://github.com/OAI/OpenAPI-Specification/blob/3.0.1/versions/3.0.1.md#mediaTypeObject
|
||||
// following rule is also valid in Swagger UI using this json (object foo)
|
||||
//: https://github.com/OAI/OpenAPI-Specification/blob/main/examples/v3.0/api-with-examples.json
|
||||
// if schema is not null, then it has the 1st priority
|
||||
// if schema is null, 2rd priority is examples.
|
||||
// if schema is null and examples is null, 3rd priority is example field.
|
||||
example = if (schema != null)
|
||||
getExample(openAPI, schema)
|
||||
else if (response!=null
|
||||
&& response.getContent != null
|
||||
&& response.getContent.values().size()>0
|
||||
&& response.getContent.values().asScala.head.getExamples != null
|
||||
&& response.getContent.values().asScala.head.getExamples.values().size() > 0
|
||||
) {
|
||||
val examplesValue = response.getContent.values().asScala.map(_.getExamples.values().asScala.map(_.getValue.toString)).map(_.head)
|
||||
convertToProduct(json.parse(examplesValue.head))
|
||||
} else if(response!=null
|
||||
&& response.getContent != null
|
||||
&& response.getContent.values().size()>0
|
||||
&& response.getContent.values().asScala.head.getExample != null
|
||||
) {
|
||||
val exampleValue = response.getContent.values().asScala.map(_.getExample.toString)
|
||||
convertToProduct(json.parse(exampleValue.head))
|
||||
}
|
||||
else
|
||||
EmptyBody
|
||||
} yield code -> example
|
||||
|
||||
result
|
||||
@ -595,7 +737,28 @@ object DynamicEndpointHelper extends RestHelper {
|
||||
case v: Schema[_] if StringUtils.isNotBlank(v.get$ref()) =>
|
||||
val refSchema = getRefSchema(openAPI, v.get$ref())
|
||||
convertToProduct(rec(refSchema))
|
||||
|
||||
//For OpenAPI30, have some default object, which do not have any ref.
|
||||
case v: Schema[_] if StringUtils.isNotBlank(v.getDescription) =>
|
||||
getDefaultValue(v, v.getDescription)
|
||||
|
||||
//https://github.com/OAI/OpenAPI-Specification/blob/main/examples/v3.0/petstore-expanded.json
|
||||
//added this case according to the up swagger, it has `allOf`
|
||||
case v: ComposedSchema =>{
|
||||
if (v.getAllOf != null && v.getAllOf.size() >0) {
|
||||
v.getAllOf.asScala.map(rec(_))
|
||||
.filter(_.!=(null))
|
||||
.filter(_.isInstanceOf[JObject])
|
||||
.map(_.asInstanceOf[JObject])
|
||||
.reduceLeft(_ merge _)
|
||||
} else if (v.getAnyOf != null && v.getAnyOf.size()>0){
|
||||
rec(v.getAllOf.asScala.head)
|
||||
}else if(v.getOneOf != null && v.getOneOf.size()>0){
|
||||
rec(v.getOneOf.asScala.head)
|
||||
}else{
|
||||
EmptyBody
|
||||
}
|
||||
}
|
||||
case v if v.getType() == "string" => "string"
|
||||
case _ => throw new RuntimeException(s"Not support type $schema, please support it if necessary.")
|
||||
}
|
||||
|
||||
@ -295,6 +295,9 @@ object DynamicEntityHelper {
|
||||
private def methodRoutingExample(entityName: String) =
|
||||
s"""
|
||||
|MethodRouting settings example:
|
||||
|
|
||||
|<details>
|
||||
|
|
||||
|```
|
||||
|{
|
||||
| "is_bank_id_exact_match":false,
|
||||
@ -313,6 +316,8 @@ object DynamicEntityHelper {
|
||||
| ]
|
||||
|}
|
||||
|```
|
||||
|
|
||||
|</details>
|
||||
|""".stripMargin
|
||||
|
||||
}
|
||||
|
||||
@ -3,6 +3,7 @@ package code.DynamicEndpoint
|
||||
import java.util.UUID.randomUUID
|
||||
import code.api.cache.Caching
|
||||
import code.api.util.{APIUtil, CustomJsonFormats}
|
||||
import code.api.v4_0_0.dynamic.DynamicEndpointHelper
|
||||
import code.util.MappedUUID
|
||||
import com.tesobe.CacheKeyFromArguments
|
||||
import net.liftweb.common.Box
|
||||
@ -18,7 +19,7 @@ object MappedDynamicEndpointProvider extends DynamicEndpointProvider with Custom
|
||||
val dynamicEndpointTTL : Int = {
|
||||
if(Props.testMode) 0
|
||||
else //Better set this to 0, we maybe create multiple endpoints, when we create new ones.
|
||||
APIUtil.getPropsValue(s"dynamicEndpoint.cache.ttl.seconds", "32").toInt
|
||||
APIUtil.getPropsValue(s"dynamicEndpoint.cache.ttl.seconds", "0").toInt
|
||||
}
|
||||
|
||||
override def create(bankId:Option[String], userId: String, swaggerString: String): Box[DynamicEndpointT] = {
|
||||
@ -50,7 +51,8 @@ object MappedDynamicEndpointProvider extends DynamicEndpointProvider with Custom
|
||||
By(DynamicEndpoint.BankId, bankId.getOrElse(""))
|
||||
)
|
||||
).map(dynamicEndpoint => {
|
||||
dynamicEndpoint.SwaggerString(json.compactRender(json.parse(dynamicEndpoint.swaggerString).replace("host" :: Nil, JString(hostString)))).saveMe()
|
||||
val updatedHost = DynamicEndpointHelper.changeOpenApiVersionHost(dynamicEndpoint.swaggerString, hostString )
|
||||
dynamicEndpoint.SwaggerString(updatedHost).saveMe()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@ -30,7 +30,7 @@ trait EntitlementProvider {
|
||||
def getEntitlementsByRole(roleName: String): Box[List[Entitlement]]
|
||||
def getEntitlementsFuture() : Future[Box[List[Entitlement]]]
|
||||
def getEntitlementsByRoleFuture(roleName: String) : Future[Box[List[Entitlement]]]
|
||||
def addEntitlement(bankId: String, userId: String, roleName: String, createdByProcess: String="manual") : Box[Entitlement]
|
||||
def addEntitlement(bankId: String, userId: String, roleName: String, createdByProcess: String="manual", grantorUserId: Option[String]=None) : Box[Entitlement]
|
||||
def deleteDynamicEntityEntitlement(entityName: String, bankId:Option[String]) : Box[Boolean]
|
||||
def deleteEntitlements(entityNames: List[String]) : Box[Boolean]
|
||||
}
|
||||
@ -54,7 +54,7 @@ class RemotedataEntitlementsCaseClasses {
|
||||
case class getEntitlementsByRole(roleName: String)
|
||||
case class getEntitlementsFuture()
|
||||
case class getEntitlementsByRoleFuture(roleName: String)
|
||||
case class addEntitlement(bankId: String, userId: String, roleName: String, createdByProcess: String="manual")
|
||||
case class addEntitlement(bankId: String, userId: String, roleName: String, createdByProcess: String="manual", grantorUserId: Option[String]=None)
|
||||
case class deleteDynamicEntityEntitlement(entityName: String, bankId:Option[String])
|
||||
case class deleteEntitlements(entityNames: List[String])
|
||||
}
|
||||
|
||||
@ -1,12 +1,15 @@
|
||||
package code.entitlement
|
||||
|
||||
import code.api.util.ApiRole.{CanCreateEntitlementAtAnyBank, CanCreateEntitlementAtOneBank}
|
||||
import code.api.util.ErrorMessages
|
||||
import code.api.v4_0_0.dynamic.DynamicEntityInfo
|
||||
import code.util.{MappedUUID, UUIDString}
|
||||
import net.liftweb.common.Box
|
||||
import net.liftweb.common.{Box, Failure, Full}
|
||||
import net.liftweb.mapper._
|
||||
|
||||
import scala.concurrent.Future
|
||||
import com.openbankproject.commons.ExecutionContext.Implicits.global
|
||||
import net.liftweb.common
|
||||
|
||||
object MappedEntitlementsProvider extends EntitlementProvider {
|
||||
override def getEntitlement(bankId: String, userId: String, roleName: String): Box[MappedEntitlement] = {
|
||||
@ -102,15 +105,26 @@ object MappedEntitlementsProvider extends EntitlementProvider {
|
||||
}
|
||||
}
|
||||
|
||||
override def addEntitlement(bankId: String, userId: String, roleName: String, createdByProcess: String ="manual"): Box[Entitlement] = {
|
||||
override def addEntitlement(bankId: String, userId: String, roleName: String, createdByProcess: String ="manual", grantorUserId: Option[String]=None): Box[Entitlement] = {
|
||||
def addEntitlementToUser(): Full[MappedEntitlement] = {
|
||||
val addEntitlement: MappedEntitlement =
|
||||
MappedEntitlement.create.mBankId(bankId).mUserId(userId).mRoleName(roleName).mCreatedByProcess(createdByProcess)
|
||||
.saveMe()
|
||||
Full(addEntitlement)
|
||||
}
|
||||
// Return a Box so we can handle errors later.
|
||||
val addEntitlement = MappedEntitlement.create
|
||||
.mBankId(bankId)
|
||||
.mUserId(userId)
|
||||
.mRoleName(roleName)
|
||||
.mCreatedByProcess(createdByProcess)
|
||||
.saveMe()
|
||||
Some(addEntitlement)
|
||||
grantorUserId match {
|
||||
case Some(userId) =>
|
||||
val canCreateEntitlementAtAnyBank = MappedEntitlement.findAll(By(MappedEntitlement.mUserId, userId)).exists(e => e.roleName == CanCreateEntitlementAtAnyBank)
|
||||
val canCreateEntitlementAtOneBank = MappedEntitlement.findAll(By(MappedEntitlement.mUserId, userId)).exists(e => e.roleName == CanCreateEntitlementAtOneBank && e.bankId == bankId)
|
||||
if(canCreateEntitlementAtAnyBank || canCreateEntitlementAtOneBank) {
|
||||
addEntitlementToUser()
|
||||
} else {
|
||||
Failure(ErrorMessages.EntitlementCannotBeGrantedGrantorIssue)
|
||||
}
|
||||
case None =>
|
||||
addEntitlementToUser()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -935,7 +935,7 @@ def restoreSomeSessions(): Unit = {
|
||||
// variable redirect is from loginRedirect, it is set-up in OAuthAuthorisation.scala as following code:
|
||||
// val currentUrl = S.uriAndQueryString.getOrElse("/")
|
||||
// AuthUser.loginRedirect.set(Full(Helpers.appendParams(currentUrl, List((LogUserOutParam, "false")))))
|
||||
def checkInternalRedirectAndLogUseIn(preLoginState: () => Unit, redirect: String, user: AuthUser) = {
|
||||
def checkInternalRedirectAndLogUserIn(preLoginState: () => Unit, redirect: String, user: AuthUser) = {
|
||||
if (Helper.isValidInternalRedirectUrl(redirect)) {
|
||||
logUserIn(user, () => {
|
||||
S.notice(S.?("logged.in"))
|
||||
@ -943,12 +943,12 @@ def restoreSomeSessions(): Unit = {
|
||||
if(emailDomainToSpaceMappings.nonEmpty){
|
||||
Future{
|
||||
tryo{AuthUser.grantEntitlementsToUseDynamicEndpointsInSpaces(user)}
|
||||
.openOr(logger.error(s"${user} checkInternalRedirectAndLogUseIn.grantEntitlementsToUseDynamicEndpointsInSpaces throw exception! "))
|
||||
.openOr(logger.error(s"${user} checkInternalRedirectAndLogUserIn.grantEntitlementsToUseDynamicEndpointsInSpaces throw exception! "))
|
||||
}}
|
||||
if(emailDomainToEntitlementMappings.nonEmpty){
|
||||
Future{
|
||||
tryo{AuthUser.grantEmailDomainEntitlementsToUser(user)}
|
||||
.openOr(logger.error(s"${user} checkInternalRedirectAndLogUseIn.grantEmailDomainEntitlementsToUser throw exception! "))
|
||||
.openOr(logger.error(s"${user} checkInternalRedirectAndLogUserIn.grantEmailDomainEntitlementsToUser throw exception! "))
|
||||
}}
|
||||
S.redirectTo(redirect)
|
||||
})
|
||||
@ -996,9 +996,11 @@ def restoreSomeSessions(): Unit = {
|
||||
// Reset any bad attempt
|
||||
LoginAttempt.resetBadLoginAttempts(usernameFromGui)
|
||||
val preLoginState = capturePreLoginState()
|
||||
// User init actions
|
||||
AfterApiAuth.innerLoginUserInitAction(Full(user))
|
||||
logger.info("login redirect: " + loginRedirect.get)
|
||||
val redirect = redirectUri()
|
||||
checkInternalRedirectAndLogUseIn(preLoginState, redirect, user)
|
||||
checkInternalRedirectAndLogUserIn(preLoginState, redirect, user)
|
||||
} else { // If user is NOT locked AND password is wrong => increment bad login attempt counter.
|
||||
LoginAttempt.incrementBadLoginAttempts(usernameFromGui)
|
||||
S.error(Helper.i18n("invalid.login.credentials"))
|
||||
@ -1021,7 +1023,9 @@ def restoreSomeSessions(): Unit = {
|
||||
//This method is used for connector = kafka* || obpjvm*
|
||||
//It will update the views and createAccountHolder ....
|
||||
registeredUserHelper(user.username.get)
|
||||
checkInternalRedirectAndLogUseIn(preLoginState, redirect, user)
|
||||
// User init actions
|
||||
AfterApiAuth.innerLoginUserInitAction(Full(user))
|
||||
checkInternalRedirectAndLogUserIn(preLoginState, redirect, user)
|
||||
|
||||
// If user cannot be found locally, try to authenticate user via connector
|
||||
case Empty if (APIUtil.getPropsAsBoolValue("connector.user.authentication", false) ||
|
||||
@ -1034,7 +1038,9 @@ def restoreSomeSessions(): Unit = {
|
||||
externalUserHelper(usernameFromGui, passwordFromGui) match {
|
||||
case Full(user: AuthUser) =>
|
||||
LoginAttempt.resetBadLoginAttempts(usernameFromGui)
|
||||
checkInternalRedirectAndLogUseIn(preLoginState, redirect, user)
|
||||
// User init actions
|
||||
AfterApiAuth.innerLoginUserInitAction(Full(user))
|
||||
checkInternalRedirectAndLogUserIn(preLoginState, redirect, user)
|
||||
case _ =>
|
||||
LoginAttempt.incrementBadLoginAttempts(username.get)
|
||||
Empty
|
||||
|
||||
@ -48,8 +48,8 @@ object RemotedataEntitlements extends ObpActorInit with EntitlementProvider {
|
||||
def getEntitlementsByRoleFuture(roleName: String) : Future[Box[List[Entitlement]]] =
|
||||
(actor ? cc.getEntitlementsByRoleFuture(roleName)).mapTo[Box[List[Entitlement]]]
|
||||
|
||||
def addEntitlement(bankId: String, userId: String, roleName: String, createdByProcess: String="manual") : Box[Entitlement] = getValueFromFuture(
|
||||
(actor ? cc.addEntitlement(bankId, userId, roleName, createdByProcess: String)).mapTo[Box[Entitlement]]
|
||||
def addEntitlement(bankId: String, userId: String, roleName: String, createdByProcess: String="manual", grantorUserId: Option[String]=None) : Box[Entitlement] = getValueFromFuture(
|
||||
(actor ? cc.addEntitlement(bankId, userId, roleName, createdByProcess, grantorUserId)).mapTo[Box[Entitlement]]
|
||||
)
|
||||
|
||||
override def deleteDynamicEntityEntitlement(entityName: String, bankId:Option[String]): Box[Boolean] = getValueFromFuture(
|
||||
|
||||
@ -55,9 +55,9 @@ class RemotedataEntitlementsActor extends Actor with ObpActorHelper with MdcLogg
|
||||
logger.debug(s"getEntitlementsByRole($role)")
|
||||
sender ! (mapper.getEntitlementsByRole(role))
|
||||
|
||||
case cc.addEntitlement(bankId: String, userId: String, roleName: String, createdByProcess: String) =>
|
||||
logger.debug(s"addEntitlement($bankId, $userId, $roleName, $createdByProcess)")
|
||||
sender ! (mapper.addEntitlement(bankId, userId, roleName, createdByProcess: String))
|
||||
case cc.addEntitlement(bankId: String, userId: String, roleName: String, createdByProcess: String, grantorUserId: Option[String]) =>
|
||||
logger.debug(s"addEntitlement($bankId, $userId, $roleName, $createdByProcess, $grantorUserId)")
|
||||
sender ! (mapper.addEntitlement(bankId, userId, roleName, createdByProcess, grantorUserId))
|
||||
|
||||
case cc.deleteDynamicEntityEntitlement(entityName: String, bankId:Option[String]) =>
|
||||
logger.debug(s"deleteDynamicEntityEntitlement($entityName) bankId($bankId)")
|
||||
|
||||
@ -29,6 +29,7 @@ package code.snippet
|
||||
import java.time.{Duration, ZoneId, ZoneOffset, ZonedDateTime}
|
||||
import java.util.Date
|
||||
|
||||
import code.api.Constant
|
||||
import code.api.util.{APIUtil, SecureRandomUtil}
|
||||
import code.model.dataAccess.{AuthUser, ResourceUser}
|
||||
import code.users
|
||||
@ -102,9 +103,10 @@ class UserInvitation extends MdcLoggable {
|
||||
else if(termsCheckboxVar.is == false) showErrorsForTermsAndConditions()
|
||||
else if(personalDataCollectionConsentCountryWaiverList.exists(_.toLowerCase == countryVar.is.toLowerCase) == false && consentForCollectingCheckboxVar.is == false) showErrorsForConsentForCollectingPersonalData()
|
||||
else {
|
||||
val localIdentityProviderUrl = APIUtil.getPropsValue("local_identity_provider_url", Constant.HostName)
|
||||
// Resource User table
|
||||
createResourceUser(
|
||||
provider = "OBP-User-Invitation",
|
||||
provider = localIdentityProviderUrl, // TODO Make provider an enum
|
||||
providerId = Some(usernameVar.is),
|
||||
name = Some(usernameVar.is),
|
||||
email = Some(email),
|
||||
|
||||
29
obp-api/src/main/scala/code/users/UserInitAction.scala
Normal file
29
obp-api/src/main/scala/code/users/UserInitAction.scala
Normal file
@ -0,0 +1,29 @@
|
||||
package code.users
|
||||
|
||||
import code.util.MappedUUID
|
||||
import net.liftweb.mapper._
|
||||
|
||||
class UserInitAction extends UserInitActionTrait with LongKeyedMapper[UserInitAction] with IdPK with CreatedUpdated {
|
||||
def getSingleton = UserInitAction
|
||||
|
||||
object UserId extends MappedUUID(this)
|
||||
object ActionName extends MappedString(this, 100)
|
||||
object ActionValue extends MappedString(this, 100)
|
||||
object Success extends MappedBoolean(this)
|
||||
|
||||
override def userId: String = UserId.get
|
||||
override def actionName: String = ActionName.get
|
||||
override def actionValue: String = ActionValue.get
|
||||
override def success: Boolean = Success.get
|
||||
}
|
||||
|
||||
object UserInitAction extends UserInitAction with LongKeyedMetaMapper[UserInitAction] {
|
||||
override def dbIndexes: List[BaseIndex[UserInitAction]] = UniqueIndex(UserId, ActionName, ActionValue) :: super.dbIndexes
|
||||
}
|
||||
|
||||
trait UserInitActionTrait {
|
||||
def userId: String
|
||||
def actionName: String
|
||||
def actionValue: String
|
||||
def success: Boolean
|
||||
}
|
||||
@ -0,0 +1,28 @@
|
||||
package code.users
|
||||
|
||||
import cats.Now
|
||||
import code.util.Helper.MdcLoggable
|
||||
import net.liftweb.common.{Box, Full}
|
||||
import net.liftweb.mapper.By
|
||||
import net.liftweb.util.Helpers
|
||||
|
||||
object UserInitActionProvider extends MdcLoggable {
|
||||
def createOrUpdateInitAction(userId: String, actionName: String, actionValue: String, success: Boolean): Box[UserInitAction] = {
|
||||
UserInitAction.find(
|
||||
By(UserInitAction.UserId, userId),
|
||||
By(UserInitAction.ActionName, actionName),
|
||||
By(UserInitAction.ActionValue, actionValue)
|
||||
) match {
|
||||
case Full(action) => Some(action.Success(success).updatedAt(Helpers.now).saveMe())
|
||||
case _ =>
|
||||
Some(
|
||||
UserInitAction.create
|
||||
.UserId(userId)
|
||||
.ActionName(actionName)
|
||||
.ActionValue(actionValue)
|
||||
.Success(success)
|
||||
.saveMe()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
38
obp-api/src/main/scala/deletion/DeleteBankCascade.scala
Normal file
38
obp-api/src/main/scala/deletion/DeleteBankCascade.scala
Normal file
@ -0,0 +1,38 @@
|
||||
package deletion
|
||||
|
||||
import code.api.APIFailureNewStyle
|
||||
import code.api.util.APIUtil.fullBoxOrException
|
||||
import code.api.util.ErrorMessages.CouldNotDeleteCascade
|
||||
import code.model.dataAccess.{MappedBank, MappedBankAccount}
|
||||
import com.openbankproject.commons.model.{AccountId, BankId}
|
||||
import deletion.DeletionUtil.databaseAtomicTask
|
||||
import net.liftweb.common.{Box, Empty, Full}
|
||||
import net.liftweb.db.DB
|
||||
import net.liftweb.mapper.By
|
||||
import net.liftweb.util.DefaultConnectionIdentifier
|
||||
|
||||
object DeleteBankCascade {
|
||||
|
||||
def delete(bankId: BankId): Boolean = {
|
||||
MappedBankAccount.findAll(By(MappedBankAccount.bank, bankId.value))
|
||||
.forall(i => DeleteAccountCascade.delete(i.bankId, i.accountId)) && deleteBank(bankId)
|
||||
}
|
||||
|
||||
def atomicDelete(bankId: BankId): Box[Boolean] = databaseAtomicTask {
|
||||
delete(bankId) match {
|
||||
case true =>
|
||||
Full(true)
|
||||
case false =>
|
||||
DB.rollback(DefaultConnectionIdentifier)
|
||||
fullBoxOrException(Empty ~> APIFailureNewStyle(CouldNotDeleteCascade, 400))
|
||||
}
|
||||
}
|
||||
|
||||
private def deleteBank(bankId: BankId): Boolean = {
|
||||
MappedBank.bulkDelete_!!(
|
||||
By(MappedBank.permalink, bankId.value)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
110
obp-api/src/main/scala/deletion/DeleteCustomerCascade.scala
Normal file
110
obp-api/src/main/scala/deletion/DeleteCustomerCascade.scala
Normal file
@ -0,0 +1,110 @@
|
||||
package deletion
|
||||
|
||||
import code.accountapplication.MappedAccountApplication
|
||||
import code.api.APIFailureNewStyle
|
||||
import code.api.util.APIUtil.fullBoxOrException
|
||||
import code.api.util.ErrorMessages.CouldNotDeleteCascade
|
||||
import code.customer.MappedCustomer
|
||||
import code.customer.internalMapping.MappedCustomerIdMapping
|
||||
import code.customeraddress.MappedCustomerAddress
|
||||
import code.customerattribute.MappedCustomerAttribute
|
||||
import code.kycchecks.MappedKycCheck
|
||||
import code.kycdocuments.MappedKycDocument
|
||||
import code.kycmedias.MappedKycMedia
|
||||
import code.kycstatuses.MappedKycStatus
|
||||
import code.taxresidence.MappedTaxResidence
|
||||
import code.usercustomerlinks.MappedUserCustomerLink
|
||||
import com.openbankproject.commons.model.CustomerId
|
||||
import deletion.DeletionUtil.databaseAtomicTask
|
||||
import net.liftweb.common.{Box, Empty, Full}
|
||||
import net.liftweb.db.DB
|
||||
import net.liftweb.mapper.By
|
||||
import net.liftweb.util.DefaultConnectionIdentifier
|
||||
|
||||
object DeleteCustomerCascade {
|
||||
|
||||
def delete(customerId: CustomerId): Boolean = {
|
||||
val doneTasks =
|
||||
deleteCustomerAttributes(customerId) ::
|
||||
deleteTaxResidence(customerId) ::
|
||||
deleteKycStatus(customerId) ::
|
||||
deleteKycMedia(customerId) ::
|
||||
deleteKycCheck(customerId) ::
|
||||
deleteKycDocument(customerId) ::
|
||||
deleteCustomerAddress(customerId) ::
|
||||
deleteCustomerIdMapping(customerId) ::
|
||||
deleteAccountApplication(customerId) ::
|
||||
deleteCustomerUserCustomerLinks(customerId) ::
|
||||
deleteCustomer(customerId) ::
|
||||
Nil
|
||||
doneTasks.forall(_ == true)
|
||||
}
|
||||
|
||||
def atomicDelete(customerId: CustomerId): Box[Boolean] = databaseAtomicTask {
|
||||
delete(customerId) match {
|
||||
case true =>
|
||||
Full(true)
|
||||
case false =>
|
||||
DB.rollback(DefaultConnectionIdentifier)
|
||||
fullBoxOrException(Empty ~> APIFailureNewStyle(CouldNotDeleteCascade, 400))
|
||||
}
|
||||
}
|
||||
|
||||
private def deleteCustomerAttributes(customerId: CustomerId): Boolean = {
|
||||
MappedCustomerAttribute.bulkDelete_!!(By(MappedCustomerAttribute.mCustomerId, customerId.value))
|
||||
}
|
||||
|
||||
private def deleteCustomer(customerId: CustomerId): Boolean = {
|
||||
MappedCustomer.bulkDelete_!!(
|
||||
By(MappedCustomer.mCustomerId, customerId.value)
|
||||
)
|
||||
}
|
||||
private def deleteCustomerUserCustomerLinks(customerId: CustomerId): Boolean = {
|
||||
MappedUserCustomerLink.bulkDelete_!!(
|
||||
By(MappedUserCustomerLink.mCustomerId, customerId.value)
|
||||
)
|
||||
}
|
||||
private def deleteTaxResidence(customerId: CustomerId): Boolean = {
|
||||
MappedCustomer.find(By(MappedCustomer.mCustomerId, customerId.value)).forall(c =>
|
||||
MappedTaxResidence.bulkDelete_!!(
|
||||
By(MappedTaxResidence.mCustomerId, c.id.get)
|
||||
))
|
||||
}
|
||||
private def deleteKycStatus(customerId: CustomerId): Boolean = {
|
||||
MappedKycStatus.bulkDelete_!!(
|
||||
By(MappedKycStatus.mCustomerId, customerId.value)
|
||||
)
|
||||
}
|
||||
private def deleteKycMedia(customerId: CustomerId): Boolean = {
|
||||
MappedKycMedia.bulkDelete_!!(
|
||||
By(MappedKycMedia.mCustomerId, customerId.value)
|
||||
)
|
||||
}
|
||||
private def deleteKycCheck(customerId: CustomerId): Boolean = {
|
||||
MappedKycCheck.bulkDelete_!!(
|
||||
By(MappedKycCheck.mCustomerId, customerId.value)
|
||||
)
|
||||
}
|
||||
private def deleteKycDocument(customerId: CustomerId): Boolean = {
|
||||
MappedKycDocument.bulkDelete_!!(
|
||||
By(MappedKycDocument.mCustomerId, customerId.value)
|
||||
)
|
||||
}
|
||||
private def deleteCustomerAddress(customerId: CustomerId): Boolean = {
|
||||
MappedCustomer.find(By(MappedCustomer.mCustomerId, customerId.value)).forall(c =>
|
||||
MappedCustomerAddress.bulkDelete_!!(
|
||||
By(MappedCustomerAddress.mCustomerId, c.id.get)
|
||||
))
|
||||
}
|
||||
private def deleteAccountApplication(customerId: CustomerId): Boolean = {
|
||||
MappedAccountApplication.bulkDelete_!!(
|
||||
By(MappedAccountApplication.mCustomerId, customerId.value)
|
||||
)
|
||||
}
|
||||
private def deleteCustomerIdMapping(customerId: CustomerId): Boolean = {
|
||||
MappedCustomerIdMapping.bulkDelete_!!(
|
||||
By(MappedCustomerIdMapping.mCustomerId, customerId.value)
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
@ -20,8 +20,7 @@ nav .navbar-collapse.collapse {
|
||||
margin-left: 0;
|
||||
}
|
||||
.navbar-default .navbar-nav > li #navitem-logo img{
|
||||
width:80px;
|
||||
height: 40px;
|
||||
height: 100%;
|
||||
}
|
||||
.navbar-default .navbar-nav > li > a {
|
||||
background-color: white;
|
||||
|
||||
@ -108,7 +108,7 @@ class DirectLoginTest extends ServerSetup with BeforeAndAfter {
|
||||
|
||||
val invalidConsumerKeyHeaders = List(accessControlOriginHeader, invalidConsumerKeyHeader)
|
||||
|
||||
val validHeaders = List(accessControlOriginHeader, validHeader)
|
||||
val validHeaders = List(accessControlOriginHeader, validHeader, ("Authorization", "Basic 123456"))
|
||||
val validDeprecatedHeaders = List(accessControlOriginHeader, validDeprecatedHeader)
|
||||
|
||||
val disabledConsumerKeyHeaders = List(accessControlOriginHeader, disabledConsumerValidHeader)
|
||||
|
||||
@ -0,0 +1,112 @@
|
||||
package code.api.v4_0_0
|
||||
|
||||
import code.api.ResourceDocs1_4_0.SwaggerDefinitionsJSON
|
||||
import code.api.ResourceDocs1_4_0.SwaggerDefinitionsJSON.createViewJson
|
||||
import code.api.util.APIUtil.OAuth._
|
||||
import code.api.util.{APIUtil, ApiRole}
|
||||
import code.api.util.ApiRole.{CanDeleteAccountCascade, CanDeleteBankCascade}
|
||||
import code.api.util.ErrorMessages.{UserHasMissingRoles, UserNotLoggedIn}
|
||||
import code.api.v3_1_0.CreateAccountResponseJsonV310
|
||||
import code.api.v4_0_0.OBPAPI4_0_0.Implementations4_0_0
|
||||
import code.entitlement.Entitlement
|
||||
import code.model.dataAccess.MappedBank
|
||||
import com.github.dwickern.macros.NameOf.nameOf
|
||||
import com.openbankproject.commons.model.{AmountOfMoneyJsonV121, ErrorMessage}
|
||||
import com.openbankproject.commons.util.ApiVersion
|
||||
import net.liftweb.json.Serialization.write
|
||||
import net.liftweb.mapper.By
|
||||
import org.scalatest.Tag
|
||||
|
||||
class DeleteBankCascadeTest extends V400ServerSetup {
|
||||
|
||||
/**
|
||||
* Test tags
|
||||
* Example: To run tests with tag "getPermissions":
|
||||
* mvn test -D tagsToInclude
|
||||
*
|
||||
* This is made possible by the scalatest maven plugin
|
||||
*/
|
||||
object VersionOfApi extends Tag(ApiVersion.v4_0_0.toString)
|
||||
object ApiEndpoint1 extends Tag(nameOf(Implementations4_0_0.deleteBankCascade))
|
||||
|
||||
lazy val addAccountJson = SwaggerDefinitionsJSON.createAccountRequestJsonV310.copy(user_id = resourceUser1.userId, balance = AmountOfMoneyJsonV121("EUR","0"))
|
||||
|
||||
|
||||
feature(s"test $ApiEndpoint1 version $VersionOfApi - Unauthorized access") {
|
||||
scenario("We will call the endpoint without user credentials", ApiEndpoint1, VersionOfApi) {
|
||||
val bankId = createBank(APIUtil.generateUUID()).bankId.value
|
||||
When("We make a request v4.0.0")
|
||||
val request400 = (v4_0_0_Request / "management" / "cascading" / "banks" / bankId ).DELETE
|
||||
val response400 = makeDeleteRequest(request400)
|
||||
Then("We should get a 401")
|
||||
response400.code should equal(401)
|
||||
response400.body.extract[ErrorMessage].message should equal(UserNotLoggedIn)
|
||||
}
|
||||
}
|
||||
feature(s"test $ApiEndpoint1 version $VersionOfApi - Authorized access") {
|
||||
scenario("We will call the endpoint without user credentials", ApiEndpoint1, VersionOfApi) {
|
||||
When("We make a request v4.0.0")
|
||||
val bankId = createBank(APIUtil.generateUUID()).bankId.value
|
||||
val request400 = (v4_0_0_Request / "management" / "cascading" / "banks" / bankId ).DELETE <@(user1)
|
||||
val response400 = makeDeleteRequest(request400)
|
||||
Then("We should get a 403")
|
||||
response400.code should equal(403)
|
||||
val errorMessage = response400.body.extract[ErrorMessage].message
|
||||
errorMessage contains (UserHasMissingRoles) should be (true)
|
||||
errorMessage contains (CanDeleteBankCascade.toString()) should be (true)
|
||||
}
|
||||
}
|
||||
feature(s"test $ApiEndpoint1 - Authorized access") {
|
||||
scenario("We will call the endpoint with user credentials", ApiEndpoint1, VersionOfApi) {
|
||||
When("We grant the role")
|
||||
val bankId = createBank(APIUtil.generateUUID()).bankId.value
|
||||
Entitlement.entitlement.vend.addEntitlement(bankId, resourceUser1.userId, ApiRole.canCreateAccount.toString)
|
||||
And("We make a request v4.0.0")
|
||||
val request400 = (v4_0_0_Request / "banks" / bankId / "accounts" ).POST <@(user1)
|
||||
val response400 = makePostRequest(request400, write(addAccountJson))
|
||||
Then("We should get a 201")
|
||||
response400.code should equal(201)
|
||||
val account = response400.body.extract[CreateAccountResponseJsonV310]
|
||||
account.account_id should not be empty
|
||||
|
||||
val postBodyView = createViewJson.copy(name = "_cascade_delete", metadata_view = "_cascade_delete", is_public = false)
|
||||
createViewViaEndpoint(bankId, account.account_id, postBodyView, user1)
|
||||
|
||||
createAccountAttributeViaEndpoint(
|
||||
bankId,
|
||||
account.account_id,
|
||||
"REQUIRED_CHALLENGE_ANSWERS",
|
||||
"2",
|
||||
"INTEGER"
|
||||
)
|
||||
|
||||
grantUserAccessToViewViaEndpoint(
|
||||
bankId,
|
||||
account.account_id,
|
||||
resourceUser2.userId,
|
||||
user1,
|
||||
PostViewJsonV400(view_id = "owner", is_system = true)
|
||||
)
|
||||
|
||||
createWebhookViaEndpoint(
|
||||
bankId,
|
||||
account.account_id,
|
||||
resourceUser1.userId,
|
||||
user1
|
||||
)
|
||||
|
||||
When("We grant the role")
|
||||
Entitlement.entitlement.vend.addEntitlement(bankId, resourceUser1.userId, ApiRole.canDeleteBankCascade.toString)
|
||||
And("We make a delete cascade request v4.0.0")
|
||||
val deleteRequest400 = (v4_0_0_Request / "management" / "cascading" / "banks" / bankId ).DELETE <@(user1)
|
||||
val deleteResponse400 = makeDeleteRequest(deleteRequest400)
|
||||
Then("We should get a 200")
|
||||
deleteResponse400.code should equal(200)
|
||||
|
||||
When("We try to delete one more time")
|
||||
makeDeleteRequest(request400).code should equal(404)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@ -0,0 +1,74 @@
|
||||
package code.api.v4_0_0
|
||||
|
||||
import code.api.util.APIUtil.OAuth._
|
||||
import code.api.util.ApiRole
|
||||
import code.api.util.ApiRole.{CanDeleteCustomerCascade, CanDeleteTransactionCascade}
|
||||
import code.api.util.ErrorMessages.{UserHasMissingRoles, UserNotLoggedIn}
|
||||
import code.api.v4_0_0.OBPAPI4_0_0.Implementations4_0_0
|
||||
import code.entitlement.Entitlement
|
||||
import com.github.dwickern.macros.NameOf.nameOf
|
||||
import com.openbankproject.commons.model.ErrorMessage
|
||||
import com.openbankproject.commons.util.ApiVersion
|
||||
import org.scalatest.Tag
|
||||
|
||||
class DeleteCustomerCascadeTest extends V400ServerSetup {
|
||||
|
||||
/**
|
||||
* Test tags
|
||||
* Example: To run tests with tag "getPermissions":
|
||||
* mvn test -D tagsToInclude
|
||||
*
|
||||
* This is made possible by the scalatest maven plugin
|
||||
*/
|
||||
object VersionOfApi extends Tag(ApiVersion.v4_0_0.toString)
|
||||
|
||||
object ApiEndpoint1 extends Tag(nameOf(Implementations4_0_0.deleteCustomerCascade))
|
||||
|
||||
lazy val bankId = randomBankId
|
||||
lazy val bankAccount = randomPrivateAccountViaEndpoint(bankId)
|
||||
|
||||
feature(s"test $ApiEndpoint1 version $VersionOfApi - Unauthorized access") {
|
||||
scenario("We will call the endpoint without user credentials", ApiEndpoint1, VersionOfApi) {
|
||||
When("We make a request v4.0.0")
|
||||
val request400 = (v4_0_0_Request / "management" / "cascading" / "banks" / bankId /
|
||||
"customers" / "CUSTOMER_ID" ).DELETE
|
||||
val response400 = makeDeleteRequest(request400)
|
||||
Then("We should get a 401")
|
||||
response400.code should equal(401)
|
||||
response400.body.extract[ErrorMessage].message should equal(UserNotLoggedIn)
|
||||
}
|
||||
}
|
||||
feature(s"test $ApiEndpoint1 version $VersionOfApi - Authorized access") {
|
||||
scenario("We will call the endpoint without user credentials", ApiEndpoint1, VersionOfApi) {
|
||||
When("We make a request v4.0.0")
|
||||
val request400 = (v4_0_0_Request / "management" / "cascading" / "banks" / bankId /
|
||||
"customers" / "CUSTOMER_ID" ).DELETE <@(user1)
|
||||
val response400 = makeDeleteRequest(request400)
|
||||
Then("We should get a 403")
|
||||
response400.code should equal(403)
|
||||
val errorMessage = response400.body.extract[ErrorMessage].message
|
||||
errorMessage contains (UserHasMissingRoles) should be (true)
|
||||
errorMessage contains (CanDeleteCustomerCascade.toString()) should be (true)
|
||||
}
|
||||
}
|
||||
feature(s"test $ApiEndpoint1 - Authorized access") {
|
||||
scenario("We will call the endpoint with user credentials", ApiEndpoint1, VersionOfApi) {
|
||||
val customerId = createAndGetCustomerIdViaEndpoint(bankId, user1)
|
||||
|
||||
Then("we create the Customer Attribute")
|
||||
createAndGetCustomerAttributeIdViaEndpoint(bankId:String, customerId:String, user1)
|
||||
|
||||
When("We make a request v4.0.0")
|
||||
Entitlement.entitlement.vend.addEntitlement(bankId, resourceUser1.userId, ApiRole.canDeleteCustomerCascade.toString)
|
||||
val request400 = (v4_0_0_Request / "management" / "cascading" / "banks" / bankId /
|
||||
"customers" / customerId ).DELETE <@(user1)
|
||||
val response400 = makeDeleteRequest(request400)
|
||||
Then("We should get a 200")
|
||||
response400.code should equal(200)
|
||||
|
||||
When("We try to delete one more time we should get 404")
|
||||
makeDeleteRequest(request400).code should equal(404)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@ -25,28 +25,16 @@ class DynamicIntegrationTest extends V400ServerSetup {
|
||||
object VersionOfApi extends Tag(ApiVersion.v4_0_0.toString)
|
||||
object DynamicIntegration extends Tag("Dynamic Entity/Dynamic/Mapping")
|
||||
object ApiEndpoint1 extends Tag(nameOf(Implementations4_0_0.createBankLevelEndpointMapping))
|
||||
object ApiEndpoint2 extends Tag(nameOf(Implementations4_0_0.getBankLevelEndpointMapping))
|
||||
object ApiEndpoint3 extends Tag(nameOf(Implementations4_0_0.getAllBankLevelEndpointMappings))
|
||||
object ApiEndpoint4 extends Tag(nameOf(Implementations4_0_0.updateBankLevelEndpointMapping))
|
||||
object ApiEndpoint5 extends Tag(nameOf(Implementations4_0_0.deleteBankLevelEndpointMapping))
|
||||
object ApiEndpoint2 extends Tag(nameOf(Implementations4_0_0.createBankLevelDynamicEndpoint))
|
||||
object ApiEndpoint3 extends Tag(nameOf(Implementations4_0_0.createBankLevelDynamicEntity))
|
||||
|
||||
object ApiEndpoint6 extends Tag(nameOf(Implementations4_0_0.createBankLevelDynamicEndpoint))
|
||||
object ApiEndpoint7 extends Tag(nameOf(Implementations4_0_0.getBankLevelDynamicEndpoints))
|
||||
object ApiEndpoint8 extends Tag(nameOf(Implementations4_0_0.getBankLevelDynamicEndpoint))
|
||||
object ApiEndpoint9 extends Tag(nameOf(Implementations4_0_0.deleteBankLevelDynamicEndpoint))
|
||||
|
||||
object ApiEndpoint10 extends Tag(nameOf(Implementations4_0_0.getBankLevelDynamicEntities))
|
||||
object ApiEndpoint11 extends Tag(nameOf(Implementations4_0_0.createBankLevelDynamicEntity))
|
||||
object ApiEndpoint12 extends Tag(nameOf(Implementations4_0_0.getBankLevelDynamicEntities))
|
||||
object ApiEndpoint13 extends Tag(nameOf(Implementations4_0_0.deleteBankLevelDynamicEntity))
|
||||
object ApiEndpoint14 extends Tag(nameOf(Implementations4_0_0.updateBankLevelDynamicEntity))
|
||||
|
||||
|
||||
val mapping = endpointMappingRequestBodyExample
|
||||
val dynamicEntity = dynamicEntityRequestBodyExample.copy(bankId = None)
|
||||
val dynamicEndpoint = dynamicEndpointRequestBodyExample
|
||||
|
||||
feature("test Dynamic Entity/Endpoint and endpoint mappings together") {
|
||||
feature(s"test Dynamic Entity/Endpoint and endpoint mappings together $ApiEndpoint1 $ApiEndpoint2 $ApiEndpoint3") {
|
||||
scenario("test Dynamic Entity/Endpoint and endpoint mappings together ", DynamicIntegration, VersionOfApi) {
|
||||
//First, we need to prepare the dynamic entity, it should have two fields: name, balance.
|
||||
Entitlement.entitlement.vend.addEntitlement(testBankId1.value, resourceUser1.userId, CanCreateBankLevelDynamicEntity.toString)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user