mirror of
https://github.com/OpenBankProject/OBP-API.git
synced 2026-02-06 11:06:49 +00:00
Merge remote-tracking branch 'Simon/develop' into develop
This commit is contained in:
commit
4fb6ce25a0
@ -178,6 +178,11 @@
|
||||
<artifactId>httpclient</artifactId>
|
||||
<version>4.5.13</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-pool2</artifactId>
|
||||
<version>2.11.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-util</artifactId>
|
||||
@ -397,6 +402,12 @@
|
||||
<groupId>org.asynchttpclient</groupId>
|
||||
<artifactId>async-http-client</artifactId>
|
||||
<version>2.10.4</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<artifactId>javax.activation</artifactId>
|
||||
<groupId>com.sun.activation</groupId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<!-- grpc related end-->
|
||||
|
||||
@ -405,6 +416,16 @@
|
||||
<groupId>org.scalikejdbc</groupId>
|
||||
<artifactId>scalikejdbc_${scala.version}</artifactId>
|
||||
<version>3.4.0</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>com.sun.activation</groupId>
|
||||
<artifactId>javax.activation</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>javax.activation</groupId>
|
||||
<artifactId>activation</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.microsoft.sqlserver</groupId>
|
||||
@ -498,6 +519,16 @@
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.sun.mail</groupId>
|
||||
<artifactId>jakarta.mail</artifactId>
|
||||
<version>2.0.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>jakarta.activation</groupId>
|
||||
<artifactId>jakarta.activation-api</artifactId>
|
||||
<version>2.0.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.sun.activation</groupId>
|
||||
<artifactId>javax.activation</artifactId>
|
||||
|
||||
@ -165,6 +165,10 @@ jwt.use.ssl=false
|
||||
|
||||
# Bypass TPP signature validation
|
||||
# bypass_tpp_signature_validation = false
|
||||
## Use TPP signature revocation list
|
||||
## - CRLs (Certificate Revocation Lists), or
|
||||
## - OCSP (Online Certificate Status Protocol).
|
||||
# use_tpp_signature_revocation_list = true
|
||||
|
||||
## Reject Berlin Group TRANSACTIONS with status "received" after a defined time (in seconds)
|
||||
# berlin_group_outdated_transactions_time_in_seconds = 300
|
||||
@ -266,20 +270,59 @@ dev.port=8080
|
||||
#Default value is obp (very highly recomended)
|
||||
apiPathZero=obp
|
||||
|
||||
## Sending mail out
|
||||
## Not need in dev mode, but important for production
|
||||
mail.api.consumer.registered.sender.address=no-reply@example.com
|
||||
mail.api.consumer.registered.notification.addresses=you@example.com
|
||||
## Not need in dev mode, but important for production
|
||||
## We send an email after any exception
|
||||
# mail.exception.sender.address=no-reply@example.com
|
||||
# mail.exception.registered.notification.addresses=notify@example.com,notify2@example.com,notify3@example.com
|
||||
# This property allows sending API registration data to developer's email.
|
||||
#mail.api.consumer.registered.notification.send=false
|
||||
We only send consumer keys and secret if this is true
|
||||
#mail.api.consumer.registered.notification.send.sensistive=false
|
||||
## Email Configuration (CommonsEmailWrapper)
|
||||
## ===========================================
|
||||
##
|
||||
## This section configures email sending using CommonsEmailWrapper instead of Lift Mailer.
|
||||
## All email functionality (password reset, validation, notifications) now uses these settings.
|
||||
##
|
||||
## SMTP Server Configuration
|
||||
## -------------------------
|
||||
## Basic SMTP settings
|
||||
mail.smtp.host=127.0.0.1
|
||||
mail.smtp.port=25
|
||||
mail.smtp.auth=false
|
||||
mail.smtp.user=
|
||||
mail.smtp.password=
|
||||
|
||||
## TLS/SSL Configuration
|
||||
## ---------------------
|
||||
## Enable STARTTLS (recommended for most SMTP servers)
|
||||
mail.smtp.starttls.enable=false
|
||||
## Enable SSL (use with port 465)
|
||||
mail.smtp.ssl.enable=false
|
||||
## TLS protocols to use (recommended: TLSv1.2)
|
||||
mail.smtp.ssl.protocols=TLSv1.2
|
||||
## Trust all certificates (for development only)
|
||||
#mail.smtp.ssl.trust=*
|
||||
|
||||
## Debug Configuration
|
||||
## ------------------
|
||||
## Enable email debugging (shows SMTP communication)
|
||||
mail.debug=false
|
||||
|
||||
## Email Sender Configuration
|
||||
## -------------------------
|
||||
## Default sender address for all emails
|
||||
mail.users.userinfo.sender.address=no-reply@example.com
|
||||
|
||||
## Consumer Registration Email
|
||||
## --------------------------
|
||||
## Enable/disable consumer registration notifications
|
||||
mail.api.consumer.registered.notification.send=false
|
||||
## Sender address for consumer registration emails
|
||||
mail.api.consumer.registered.sender.address=no-reply@example.com
|
||||
## Recipient addresses for consumer registration notifications (comma-separated)
|
||||
mail.api.consumer.registered.notification.addresses=you@example.com
|
||||
## Send sensitive information (consumer keys/secrets) via email
|
||||
mail.api.consumer.registered.notification.send.sensistive=false
|
||||
|
||||
## Exception Notification Email
|
||||
## ---------------------------
|
||||
## Sender address for exception notifications
|
||||
mail.exception.sender.address=no-reply@example.com
|
||||
## Recipient addresses for exception notifications (comma-separated)
|
||||
mail.exception.registered.notification.addresses=notify@example.com,notify2@example.com,notify3@example.com
|
||||
|
||||
## Oauth token timeout
|
||||
token_expiration_weeks=4
|
||||
|
||||
@ -46,6 +46,7 @@ import code.api.util.ApiRole.CanCreateEntitlementAtAnyBank
|
||||
import code.api.util.ErrorMessages.MandatoryPropertyIsNotSet
|
||||
import code.api.util._
|
||||
import code.api.util.migration.Migration
|
||||
import code.api.util.CommonsEmailWrapper
|
||||
import code.api.util.migration.Migration.DbFunction
|
||||
import code.apicollection.ApiCollection
|
||||
import code.apicollectionendpoint.ApiCollectionEndpoint
|
||||
@ -151,7 +152,6 @@ import org.apache.commons.io.FileUtils
|
||||
import java.io.{File, FileInputStream}
|
||||
import java.util.stream.Collectors
|
||||
import java.util.{Locale, TimeZone}
|
||||
import javax.mail.internet.MimeMessage
|
||||
import scala.concurrent.ExecutionContext
|
||||
|
||||
/**
|
||||
@ -693,10 +693,6 @@ class Boot extends MdcLoggable {
|
||||
case e: ExceptionInInitializerError => logger.warn(s"BankAccountCreationListener Exception: $e")
|
||||
}
|
||||
|
||||
Mailer.devModeSend.default.set( (m : MimeMessage) => {
|
||||
logger.info("Would have sent email if not in dev mode: " + m.getContent)
|
||||
})
|
||||
|
||||
LiftRules.exceptionHandler.prepend{
|
||||
case(_, r, e) if e.isInstanceOf[NullPointerException] && e.getMessage.contains("Looking for Connection Identifier") => {
|
||||
logger.error(s"Exception being returned to browser when processing url is ${r.request.uri}, method is ${r.request.method}, exception detail is $e", e)
|
||||
@ -880,7 +876,7 @@ class Boot extends MdcLoggable {
|
||||
}
|
||||
|
||||
private def sendExceptionEmail(exception: Throwable): Unit = {
|
||||
import Mailer.{From, PlainMailBodyType, Subject, To}
|
||||
|
||||
import net.liftweb.util.Helpers.now
|
||||
|
||||
val outputStream = new java.io.ByteArrayOutputStream
|
||||
@ -899,18 +895,18 @@ class Boot extends MdcLoggable {
|
||||
|
||||
//technically doesn't work for all valid email addresses so this will mess up if someone tries to send emails to "foo,bar"@example.com
|
||||
val to = toAddressesString.split(",").toList
|
||||
val toParams = to.map(To(_))
|
||||
val params = PlainMailBodyType(error) :: toParams
|
||||
|
||||
//this is an async call
|
||||
Mailer.sendMail(
|
||||
From(from),
|
||||
Subject(s"you got an exception on $host"),
|
||||
params :_*
|
||||
val emailContent = CommonsEmailWrapper.EmailContent(
|
||||
from = from,
|
||||
to = to,
|
||||
subject = s"you got an exception on $host",
|
||||
textContent = Some(error)
|
||||
)
|
||||
|
||||
//this is an async call∆∆
|
||||
CommonsEmailWrapper.sendTextEmail(emailContent)
|
||||
}
|
||||
|
||||
//if Mailer.sendMail wasn't called (note: this actually isn't checking if the mail failed to send as that is being done asynchronously)
|
||||
if(mailSent.isEmpty)
|
||||
logger.warn(s"Exception notification failed: $mailSent")
|
||||
}
|
||||
|
||||
@ -410,10 +410,21 @@ object OAuth2Login extends RestHelper with MdcLoggable {
|
||||
validateAccessToken(token) match {
|
||||
case Full(_) =>
|
||||
val user = getOrCreateResourceUser(token)
|
||||
val consumer = getOrCreateConsumer(token, user.map(_.userId), Some("OAuth 2.0"))
|
||||
LoginAttempt.userIsLocked(user.map(_.provider).getOrElse(""), user.map(_.name).getOrElse("")) match {
|
||||
case true => ((Failure(UsernameHasBeenLocked), Some(cc.copy(consumer = consumer))))
|
||||
case false => (user, Some(cc.copy(consumer = consumer)))
|
||||
val consumer: Box[Consumer] = getOrCreateConsumer(token, user.map(_.userId), Some("OAuth 2.0"))
|
||||
consumer match {
|
||||
case Full(_) =>
|
||||
LoginAttempt.userIsLocked(user.map(_.provider).getOrElse(""), user.map(_.name).getOrElse("")) match {
|
||||
case true => ((Failure(UsernameHasBeenLocked), Some(cc.copy(consumer = consumer))))
|
||||
case false => (user, Some(cc.copy(consumer = consumer)))
|
||||
}
|
||||
case ParamFailure(msg, exception, chain, apiFailure: APIFailure) =>
|
||||
logger.debug(s"ParamFailure - message: $msg, param: $apiFailure, exception: ${exception.map(_.getMessage).openOr("none")}, chain: ${chain.map(_.msg).openOr("none")}")
|
||||
(ParamFailure(msg, exception, chain, apiFailure: APIFailure), Some(cc))
|
||||
case Failure(msg, exception, c) =>
|
||||
logger.error(s"Failure - message: $msg, exception: ${exception.map(_.getMessage).openOr("none")}")
|
||||
(Failure(msg, exception, c), Some(cc))
|
||||
case _ =>
|
||||
(Failure(CreateConsumerError), Some(cc))
|
||||
}
|
||||
case ParamFailure(a, b, c, apiFailure: APIFailure) =>
|
||||
(ParamFailure(a, b, c, apiFailure: APIFailure), Some(cc))
|
||||
|
||||
@ -232,7 +232,7 @@ object MessageDocsSwaggerDefinitions
|
||||
startDate = DateWithDayExampleObject,
|
||||
finishDate = Some(DateWithDayExampleObject),
|
||||
balance = BigDecimal(balanceAmountExample.value),
|
||||
status = transactionStatusExample.value,
|
||||
status = Some(transactionStatusExample.value),
|
||||
)
|
||||
|
||||
val accountRouting = AccountRouting("","")
|
||||
|
||||
@ -1036,17 +1036,27 @@ Give detailed information about the addressed account together with balance info
|
||||
cc =>
|
||||
for {
|
||||
(Full(u), callContext) <- authenticatedAccess(cc)
|
||||
withBalanceParam <- NewStyle.function.tryons(s"$InvalidUrlParameters withBalance parameter can only take two values: TRUE or FALSE!", 400, callContext) {
|
||||
val withBalance = APIUtil.getHttpRequestUrlParam(cc.url, "withBalance")
|
||||
if (withBalance.isEmpty) Some(false) else Some(withBalance.toBoolean)
|
||||
}
|
||||
_ <- passesPsd2Aisp(callContext)
|
||||
(account: BankAccount, callContext) <- NewStyle.function.getBankAccountByAccountId(AccountId(accountId), callContext)
|
||||
(canReadBalancesAccounts, callContext) <- NewStyle.function.getAccountCanReadBalancesOfBerlinGroup(u, callContext)
|
||||
(canReadTransactionsAccounts, callContext) <- NewStyle.function.getAccountCanReadTransactionsOfBerlinGroup(u, callContext)
|
||||
_ <- checkAccountAccess(ViewId(SYSTEM_READ_ACCOUNTS_BERLIN_GROUP_VIEW_ID), u, account, callContext)
|
||||
(accountBalances, callContext) <- code.api.util.newstyle.BankAccountBalanceNewStyle.getBankAccountBalances(
|
||||
AccountId(accountId),
|
||||
callContext
|
||||
)
|
||||
} yield {
|
||||
(
|
||||
JSONFactory_BERLIN_GROUP_1_3.createAccountDetailsJson(
|
||||
account,
|
||||
canReadBalancesAccounts,
|
||||
canReadTransactionsAccounts,
|
||||
withBalanceParam,
|
||||
accountBalances,
|
||||
u
|
||||
),
|
||||
callContext
|
||||
@ -1105,8 +1115,23 @@ respectively the OAuth2 access token.
|
||||
(canReadBalancesAccounts, callContext) <- NewStyle.function.getAccountCanReadBalancesOfBerlinGroup(u, callContext)
|
||||
(canReadTransactionsAccounts, callContext) <- NewStyle.function.getAccountCanReadTransactionsOfBerlinGroup(u, callContext)
|
||||
_ <- checkAccountAccess(ViewId(SYSTEM_READ_ACCOUNTS_BERLIN_GROUP_VIEW_ID), u, account, callContext)
|
||||
withBalanceParam <- NewStyle.function.tryons(s"$InvalidUrlParameters withBalance parameter can only take two values: TRUE or FALSE!", 400, callContext) {
|
||||
val withBalance = APIUtil.getHttpRequestUrlParam(cc.url, "withBalance")
|
||||
if (withBalance.isEmpty) Some(false) else Some(withBalance.toBoolean)
|
||||
}
|
||||
(accountBalances, callContext) <- code.api.util.newstyle.BankAccountBalanceNewStyle.getBankAccountBalances(
|
||||
AccountId(accountId),
|
||||
callContext
|
||||
)
|
||||
} yield {
|
||||
(JSONFactory_BERLIN_GROUP_1_3.createCardAccountDetailsJson(account, canReadBalancesAccounts, canReadTransactionsAccounts, u), callContext)
|
||||
(JSONFactory_BERLIN_GROUP_1_3.createCardAccountDetailsJson(
|
||||
account,
|
||||
canReadBalancesAccounts,
|
||||
canReadTransactionsAccounts,
|
||||
withBalanceParam,
|
||||
accountBalances,
|
||||
u
|
||||
), callContext)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -93,6 +93,7 @@ object JSONFactory_BERLIN_GROUP_1_3 extends CustomJsonFormats with MdcLoggable{
|
||||
product: String,
|
||||
cashAccountType: String,
|
||||
name: Option[String],
|
||||
balances: Option[List[CoreAccountBalanceJson]] = None,
|
||||
_links: AccountDetailsLinksJsonV13,
|
||||
)
|
||||
|
||||
@ -407,14 +408,18 @@ object JSONFactory_BERLIN_GROUP_1_3 extends CustomJsonFormats with MdcLoggable{
|
||||
def createCardAccountDetailsJson(bankAccount: BankAccount,
|
||||
canReadBalancesAccounts: List[BankIdAccountId],
|
||||
canReadTransactionsAccounts: List[BankIdAccountId],
|
||||
withBalanceParam: Option[Boolean],
|
||||
balances: List[BankAccountBalanceTrait],
|
||||
user: User): CardAccountDetailsJsonV13 = {
|
||||
val accountDetailsJsonV13 = createAccountDetailsJson(bankAccount, canReadBalancesAccounts, canReadTransactionsAccounts, user)
|
||||
val accountDetailsJsonV13 = createAccountDetailsJson(bankAccount, canReadBalancesAccounts, canReadTransactionsAccounts, withBalanceParam, balances, user)
|
||||
CardAccountDetailsJsonV13(accountDetailsJsonV13.account)
|
||||
}
|
||||
|
||||
def createAccountDetailsJson(bankAccount: BankAccount,
|
||||
canReadBalancesAccounts: List[BankIdAccountId],
|
||||
canReadTransactionsAccounts: List[BankIdAccountId],
|
||||
withBalanceParam: Option[Boolean],
|
||||
balances: List[BankAccountBalanceTrait],
|
||||
user: User): AccountDetailsJsonV13 = {
|
||||
val (iBan: String, bBan: String) = getIbanAndBban(bankAccount)
|
||||
val commonPath = s"${OBP_BERLIN_GROUP_1_3.apiVersion.urlPrefix}/${OBP_BERLIN_GROUP_1_3.version}/accounts/${bankAccount.accountId.value}"
|
||||
@ -423,7 +428,15 @@ object JSONFactory_BERLIN_GROUP_1_3 extends CustomJsonFormats with MdcLoggable{
|
||||
val transactionRef = LinkHrefJson(s"/$commonPath/transactions")
|
||||
val canReadTransactions = canReadTransactionsAccounts.map(_.accountId.value).contains(bankAccount.accountId.value)
|
||||
val cashAccountType = bankAccount.attributes.getOrElse(Nil).filter(_.name== "cashAccountType").map(_.value).headOption.getOrElse("")
|
||||
|
||||
val accountBalances = if (withBalanceParam.contains(true)) {
|
||||
Some(balances.filter(_.accountId.equals(bankAccount.accountId)).flatMap(balance => (List(CoreAccountBalanceJson(
|
||||
balanceAmount = AmountOfMoneyV13(bankAccount.currency, balance.balanceAmount.toString()),
|
||||
balanceType = balance.balanceType,
|
||||
lastChangeDateTime = balance.lastChangeDateTime.map(APIUtil.DateWithMsAndTimeZoneOffset.format(_))
|
||||
)))))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
|
||||
val account = AccountJsonV13(
|
||||
resourceId = bankAccount.accountId.value,
|
||||
@ -432,6 +445,7 @@ object JSONFactory_BERLIN_GROUP_1_3 extends CustomJsonFormats with MdcLoggable{
|
||||
name = if(APIUtil.getPropsAsBoolValue("BG_v1312_show_account_name", defaultValue = true)) Some(bankAccount.name) else None,
|
||||
cashAccountType = cashAccountType,
|
||||
product = bankAccount.accountType,
|
||||
balances = if(canReadBalances) accountBalances else None,
|
||||
_links = AccountDetailsLinksJsonV13(
|
||||
balances = if (canReadBalances) Some(balanceRef) else None,
|
||||
transactions = if (canReadTransactions) Some(transactionRef) else None,
|
||||
|
||||
@ -71,7 +71,7 @@ import code.util.{Helper, JsonSchemaUtil}
|
||||
import code.views.system.AccountAccess
|
||||
import code.views.{MapperViews, Views}
|
||||
import code.webuiprops.MappedWebUiPropsProvider.getWebUiPropsValue
|
||||
import com.alibaba.ttl.internal.javassist.CannotCompileException
|
||||
import javassist.CannotCompileException
|
||||
import com.github.dwickern.macros.NameOf.{nameOf, nameOfType}
|
||||
import com.openbankproject.commons.ExecutionContext.Implicits.global
|
||||
import com.openbankproject.commons.model._
|
||||
@ -1147,6 +1147,7 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
|
||||
case "iss" => Full(OBPIss(values.head))
|
||||
case "consent_id" => Full(OBPConsentId(values.head))
|
||||
case "user_id" => Full(OBPUserId(values.head))
|
||||
case "provider_provider_id" => Full(ProviderProviderId(values.head))
|
||||
case "bank_id" => Full(OBPBankId(values.head))
|
||||
case "account_id" => Full(OBPAccountId(values.head))
|
||||
case "url" => Full(OBPUrl(values.head))
|
||||
@ -1198,6 +1199,7 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
|
||||
iss <- getHttpParamValuesByName(httpParams,"iss")
|
||||
consentId <- getHttpParamValuesByName(httpParams,"consent_id")
|
||||
userId <- getHttpParamValuesByName(httpParams, "user_id")
|
||||
providerProviderId <- getHttpParamValuesByName(httpParams, "provider_provider_id")
|
||||
bankId <- getHttpParamValuesByName(httpParams, "bank_id")
|
||||
accountId <- getHttpParamValuesByName(httpParams, "account_id")
|
||||
url <- getHttpParamValuesByName(httpParams, "url")
|
||||
@ -1231,9 +1233,9 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
|
||||
*/
|
||||
//val sortBy = json.header("obp_sort_by")
|
||||
val ordering = OBPOrdering(None, sortDirection)
|
||||
//This guarantee the order
|
||||
//This guarantee the order
|
||||
List(limit, offset, ordering, sortBy, fromDate, toDate,
|
||||
anon, status, consumerId, azp, iss, consentId, userId, url, appName, implementedByPartialFunction, implementedInVersion,
|
||||
anon, status, consumerId, azp, iss, consentId, userId, providerProviderId, url, appName, implementedByPartialFunction, implementedInVersion,
|
||||
verb, correlationId, duration, excludeAppNames, excludeUrlPattern, excludeImplementedByPartialfunctions,
|
||||
includeAppNames, includeUrlPattern, includeImplementedByPartialfunctions,
|
||||
connectorName,functionName, bankId, accountId, customerId, lockedStatus, deletedStatus
|
||||
@ -1276,6 +1278,7 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
|
||||
val azp = getHttpRequestUrlParam(httpRequestUrl,"azp")
|
||||
val consentId = getHttpRequestUrlParam(httpRequestUrl,"consent_id")
|
||||
val userId = getHttpRequestUrlParam(httpRequestUrl, "user_id")
|
||||
val providerProviderId = getHttpRequestUrlParam(httpRequestUrl, "provider_provider_id")
|
||||
val bankId = getHttpRequestUrlParam(httpRequestUrl, "bank_id")
|
||||
val accountId = getHttpRequestUrlParam(httpRequestUrl, "account_id")
|
||||
val url = getHttpRequestUrlParam(httpRequestUrl, "url")
|
||||
@ -1305,7 +1308,7 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
|
||||
|
||||
Full(List(
|
||||
HTTPParam("sort_by",sortBy), HTTPParam("sort_direction",sortDirection), HTTPParam("from_date",fromDate), HTTPParam("to_date", toDate), HTTPParam("limit",limit), HTTPParam("offset",offset),
|
||||
HTTPParam("anon", anon), HTTPParam("status", status), HTTPParam("consumer_id", consumerId), HTTPParam("azp", azp), HTTPParam("iss", iss), HTTPParam("consent_id", consentId), HTTPParam("user_id", userId), HTTPParam("url", url), HTTPParam("app_name", appName),
|
||||
HTTPParam("anon", anon), HTTPParam("status", status), HTTPParam("consumer_id", consumerId), HTTPParam("azp", azp), HTTPParam("iss", iss), HTTPParam("consent_id", consentId), HTTPParam("user_id", userId), HTTPParam("provider_provider_id", providerProviderId), HTTPParam("url", url), HTTPParam("app_name", appName),
|
||||
HTTPParam("implemented_by_partial_function",implementedByPartialFunction), HTTPParam("implemented_in_version",implementedInVersion), HTTPParam("verb", verb),
|
||||
HTTPParam("correlation_id", correlationId), HTTPParam("duration", duration), HTTPParam("exclude_app_names", excludeAppNames),
|
||||
HTTPParam("exclude_url_patterns", excludeUrlPattern),HTTPParam("exclude_implemented_by_partial_functions", excludeImplementedByPartialfunctions),
|
||||
@ -3020,6 +3023,7 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
|
||||
|
||||
// Identify consumer via certificate
|
||||
val consumerByCertificate = Consent.getCurrentConsumerViaTppSignatureCertOrMtls(callContext = cc)
|
||||
logger.debug(s"consumerByCertificate: $consumerByCertificate")
|
||||
val method = APIUtil.getPropsValue(nameOfProperty = "consumer_validation_method_for_consent", defaultValue = "CONSUMER_CERTIFICATE")
|
||||
val consumerByConsumerKey = getConsumerKey(reqHeaders) match {
|
||||
case Some(consumerKey) if method == "CONSUMER_KEY_VALUE" =>
|
||||
@ -3027,6 +3031,7 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
|
||||
case None =>
|
||||
Empty
|
||||
}
|
||||
logger.debug(s"consumerByConsumerKey: $consumerByConsumerKey")
|
||||
|
||||
val res =
|
||||
if (authHeadersWithEmptyValues.nonEmpty) { // Check Authorization Headers Empty Values
|
||||
|
||||
@ -80,7 +80,11 @@ object CertificateVerifier extends MdcLoggable {
|
||||
|
||||
// Set up PKIX parameters for validation
|
||||
val pkixParams = new PKIXParameters(trustAnchors)
|
||||
pkixParams.setRevocationEnabled(false) // Disable CRL checks
|
||||
if(APIUtil.getPropsAsBoolValue("use_tpp_signature_revocation_list", defaultValue = true)) {
|
||||
pkixParams.setRevocationEnabled(true) // Enable CRL checks
|
||||
} else {
|
||||
pkixParams.setRevocationEnabled(false) // Disable CRL checks
|
||||
}
|
||||
|
||||
// Validate certificate chain
|
||||
val certPath = CertificateFactory.getInstance("X.509").generateCertPath(Collections.singletonList(certificate))
|
||||
|
||||
193
obp-api/src/main/scala/code/api/util/CommonsEmailWrapper.scala
Normal file
193
obp-api/src/main/scala/code/api/util/CommonsEmailWrapper.scala
Normal file
@ -0,0 +1,193 @@
|
||||
package code.api.util
|
||||
|
||||
import code.util.Helper.MdcLoggable
|
||||
import jakarta.activation.{DataHandler, FileDataSource, URLDataSource}
|
||||
import jakarta.mail._
|
||||
import jakarta.mail.internet._
|
||||
import net.liftweb.common.{Box, Empty, Full}
|
||||
|
||||
import java.io.File
|
||||
import java.net.URL
|
||||
import java.util.Properties
|
||||
|
||||
/**
|
||||
* Jakarta Mail Wrapper for OBP-API
|
||||
* This wrapper provides a simple interface to send emails using Jakarta Mail
|
||||
*/
|
||||
object CommonsEmailWrapper extends MdcLoggable {
|
||||
|
||||
case class EmailConfig(
|
||||
smtpHost: String,
|
||||
smtpPort: Int,
|
||||
username: String,
|
||||
password: String,
|
||||
useTLS: Boolean = true,
|
||||
useSSL: Boolean = false,
|
||||
debug: Boolean = false,
|
||||
tlsProtocols: String = "TLSv1.2"
|
||||
)
|
||||
|
||||
case class EmailContent(
|
||||
from: String,
|
||||
to: List[String],
|
||||
cc: List[String] = List.empty,
|
||||
bcc: List[String] = List.empty,
|
||||
subject: String,
|
||||
textContent: Option[String] = None,
|
||||
htmlContent: Option[String] = None,
|
||||
attachments: List[EmailAttachment] = List.empty
|
||||
)
|
||||
|
||||
case class EmailAttachment(
|
||||
filePath: Option[String] = None,
|
||||
url: Option[String] = None,
|
||||
name: Option[String] = None
|
||||
)
|
||||
|
||||
def getDefaultEmailConfig(): EmailConfig = {
|
||||
EmailConfig(
|
||||
smtpHost = APIUtil.getPropsValue("mail.smtp.host", "localhost"),
|
||||
smtpPort = APIUtil.getPropsValue("mail.smtp.port", "1025").toInt,
|
||||
username = APIUtil.getPropsValue("mail.smtp.user", ""),
|
||||
password = APIUtil.getPropsValue("mail.smtp.password", ""),
|
||||
useTLS = APIUtil.getPropsValue("mail.smtp.starttls.enable", "false").toBoolean,
|
||||
useSSL = APIUtil.getPropsValue("mail.smtp.ssl.enable", "false").toBoolean,
|
||||
debug = APIUtil.getPropsValue("mail.debug", "false").toBoolean,
|
||||
tlsProtocols = APIUtil.getPropsValue("mail.smtp.ssl.protocols", "TLSv1.2")
|
||||
)
|
||||
}
|
||||
|
||||
def sendTextEmail(content: EmailContent): Box[String] = {
|
||||
sendTextEmail(getDefaultEmailConfig(), content)
|
||||
}
|
||||
|
||||
def sendHtmlEmail(content: EmailContent): Box[String] = {
|
||||
sendHtmlEmail(getDefaultEmailConfig(), content)
|
||||
}
|
||||
|
||||
def sendEmailWithAttachments(content: EmailContent): Box[String] = {
|
||||
sendEmailWithAttachments(getDefaultEmailConfig(), content)
|
||||
}
|
||||
|
||||
def sendTextEmail(config: EmailConfig, content: EmailContent): Box[String] = {
|
||||
try {
|
||||
logger.debug(s"Sending text email from ${content.from} to ${content.to.mkString(", ")}")
|
||||
val session = createSession(config)
|
||||
val message = new MimeMessage(session)
|
||||
setCommonHeaders(message, content)
|
||||
message.setText(content.textContent.getOrElse(""), "UTF-8")
|
||||
Transport.send(message)
|
||||
Full(message.getMessageID)
|
||||
} catch {
|
||||
case e: Exception =>
|
||||
logger.error(s"Failed to send text email: ${e.getMessage}", e)
|
||||
Empty
|
||||
}
|
||||
}
|
||||
|
||||
def sendHtmlEmail(config: EmailConfig, content: EmailContent): Box[String] = {
|
||||
try {
|
||||
logger.debug(s"Sending HTML email from ${content.from} to ${content.to.mkString(", ")}")
|
||||
val session = createSession(config)
|
||||
val message = new MimeMessage(session)
|
||||
setCommonHeaders(message, content)
|
||||
val multipart = {
|
||||
new MimeMultipart("alternative")
|
||||
}
|
||||
content.textContent.foreach { text =>
|
||||
val textPart = new MimeBodyPart()
|
||||
textPart.setText(text, "UTF-8")
|
||||
multipart.addBodyPart(textPart)
|
||||
}
|
||||
content.htmlContent.foreach { html =>
|
||||
val htmlPart = new MimeBodyPart()
|
||||
htmlPart.setContent(html, "text/html; charset=UTF-8")
|
||||
multipart.addBodyPart(htmlPart)
|
||||
}
|
||||
message.setContent(multipart)
|
||||
Transport.send(message)
|
||||
Full(message.getMessageID)
|
||||
} catch {
|
||||
case e: Exception =>
|
||||
logger.error(s"Failed to send HTML email: ${e.getMessage}", e)
|
||||
Empty
|
||||
}
|
||||
}
|
||||
|
||||
def sendEmailWithAttachments(config: EmailConfig, content: EmailContent): Box[String] = {
|
||||
try {
|
||||
logger.debug(s"Sending email with attachments from ${content.from} to ${content.to.mkString(", ")}")
|
||||
val session = createSession(config)
|
||||
val message = new MimeMessage(session)
|
||||
setCommonHeaders(message, content)
|
||||
val multipart = new MimeMultipart()
|
||||
// Add text or HTML part
|
||||
(content.htmlContent, content.textContent) match {
|
||||
case (Some(html), _) =>
|
||||
val htmlPart = new MimeBodyPart()
|
||||
htmlPart.setContent(html, "text/html; charset=UTF-8")
|
||||
multipart.addBodyPart(htmlPart)
|
||||
case (None, Some(text)) =>
|
||||
val textPart = new MimeBodyPart()
|
||||
textPart.setText(text, "UTF-8")
|
||||
multipart.addBodyPart(textPart)
|
||||
case _ =>
|
||||
val textPart = new MimeBodyPart()
|
||||
textPart.setText("", "UTF-8")
|
||||
multipart.addBodyPart(textPart)
|
||||
}
|
||||
// Add attachments
|
||||
content.attachments.foreach { att =>
|
||||
val attachPart = new MimeBodyPart()
|
||||
if (att.filePath.isDefined) {
|
||||
val fds = new FileDataSource(new File(att.filePath.get))
|
||||
attachPart.setDataHandler(new DataHandler(fds))
|
||||
attachPart.setFileName(att.name.getOrElse(new File(att.filePath.get).getName))
|
||||
} else if (att.url.isDefined) {
|
||||
val uds = new URLDataSource(new URL(att.url.get))
|
||||
attachPart.setDataHandler(new DataHandler(uds))
|
||||
attachPart.setFileName(att.name.getOrElse(att.url.get.split('/').last))
|
||||
}
|
||||
multipart.addBodyPart(attachPart)
|
||||
}
|
||||
message.setContent(multipart)
|
||||
Transport.send(message)
|
||||
Full(message.getMessageID)
|
||||
} catch {
|
||||
case e: Exception =>
|
||||
logger.error(s"Failed to send email with attachments: ${e.getMessage}", e)
|
||||
Empty
|
||||
}
|
||||
}
|
||||
|
||||
private def createSession(config: EmailConfig): Session = {
|
||||
val props = new Properties()
|
||||
props.put("mail.smtp.host", config.smtpHost)
|
||||
props.put("mail.smtp.port", config.smtpPort.toString)
|
||||
props.put("mail.smtp.auth", "true")
|
||||
props.put("mail.smtp.starttls.enable", config.useTLS.toString)
|
||||
props.put("mail.smtp.ssl.enable", config.useSSL.toString)
|
||||
props.put("mail.debug", config.debug.toString)
|
||||
props.put("mail.smtp.ssl.protocols", config.tlsProtocols)
|
||||
val authenticator = new Authenticator() {
|
||||
override def getPasswordAuthentication: PasswordAuthentication =
|
||||
new PasswordAuthentication(config.username, config.password)
|
||||
}
|
||||
Session.getInstance(props, authenticator)
|
||||
}
|
||||
|
||||
private def setCommonHeaders(message: MimeMessage, content: EmailContent): Unit = {
|
||||
message.setFrom(new InternetAddress(content.from))
|
||||
content.to.foreach(addr => message.addRecipient(Message.RecipientType.TO, new InternetAddress(addr)))
|
||||
content.cc.foreach(addr => message.addRecipient(Message.RecipientType.CC, new InternetAddress(addr)))
|
||||
content.bcc.foreach(addr => message.addRecipient(Message.RecipientType.BCC, new InternetAddress(addr)))
|
||||
message.setSubject(content.subject, "UTF-8")
|
||||
}
|
||||
|
||||
def createFileAttachment(filePath: String, name: Option[String] = None): EmailAttachment =
|
||||
EmailAttachment(filePath = Some(filePath), url = None, name = name)
|
||||
|
||||
def createUrlAttachment(url: String, name: String): EmailAttachment =
|
||||
EmailAttachment(filePath = None, url = Some(url), name = Some(name))
|
||||
|
||||
}
|
||||
@ -238,6 +238,7 @@ object Consent extends MdcLoggable {
|
||||
|
||||
private def tppIsConsentHolder(consumerIdFromConsent: String, callContext: CallContext): Boolean = {
|
||||
val consumerIdFromCurrentCall = callContext.consumer.map(_.consumerId.get).orNull
|
||||
logger.debug(s"consumerIdFromConsent == consumerIdFromCurrentCall ($consumerIdFromConsent == $consumerIdFromCurrentCall)")
|
||||
consumerIdFromConsent == consumerIdFromCurrentCall
|
||||
}
|
||||
|
||||
|
||||
@ -6,8 +6,7 @@ import code.users.Users
|
||||
import code.util.Helper.MdcLoggable
|
||||
import com.openbankproject.commons.model.User
|
||||
import net.liftweb.common.Box
|
||||
import net.liftweb.util.Mailer
|
||||
import net.liftweb.util.Mailer._
|
||||
|
||||
|
||||
import scala.collection.immutable.List
|
||||
|
||||
@ -27,14 +26,14 @@ object NotificationUtil extends MdcLoggable {
|
||||
|
|
||||
|Cheers
|
||||
|""".stripMargin
|
||||
val params = PlainMailBodyType(bodyOfMessage) :: List(To(user.emailAddress))
|
||||
val subjectOfMessage = "You have been granted the role"
|
||||
//this is an async call
|
||||
Mailer.sendMail(
|
||||
From(from),
|
||||
Subject(subjectOfMessage),
|
||||
params :_*
|
||||
val emailContent = CommonsEmailWrapper.EmailContent(
|
||||
from = from,
|
||||
to = List(user.emailAddress),
|
||||
subject = s"You have been granted the role: ${entitlement.roleName}",
|
||||
textContent = Some(bodyOfMessage)
|
||||
)
|
||||
//this is an async call
|
||||
CommonsEmailWrapper.sendTextEmail(emailContent)
|
||||
}
|
||||
if(mailSent.isEmpty) {
|
||||
val info =
|
||||
|
||||
@ -31,6 +31,7 @@ case class OBPAzp(value: String) extends OBPQueryParam
|
||||
case class OBPIss(value: String) extends OBPQueryParam
|
||||
case class OBPConsentId(value: String) extends OBPQueryParam
|
||||
case class OBPUserId(value: String) extends OBPQueryParam
|
||||
case class ProviderProviderId(value: String) extends OBPQueryParam
|
||||
case class OBPStatus(value: String) extends OBPQueryParam
|
||||
case class OBPBankId(value: String) extends OBPQueryParam
|
||||
case class OBPAccountId(value: String) extends OBPQueryParam
|
||||
|
||||
@ -100,6 +100,7 @@ object Migration extends MdcLoggable {
|
||||
// populateViewDefinitionCanSeeTransactionStatus()
|
||||
alterCounterpartyLimitFieldType()
|
||||
populateMigrationOfViewPermissions(startedBeforeSchemifier)
|
||||
changeTypeOfAudFieldAtConsumerTable()
|
||||
}
|
||||
|
||||
private def dummyScript(): Boolean = {
|
||||
@ -254,6 +255,12 @@ object Migration extends MdcLoggable {
|
||||
MigrationOfConsumer.populateAzpAndSub(name)
|
||||
}
|
||||
}
|
||||
private def changeTypeOfAudFieldAtConsumerTable(): Boolean = {
|
||||
val name = nameOf(changeTypeOfAudFieldAtConsumerTable)
|
||||
runOnce(name) {
|
||||
MigrationOfConsumer.alterTypeofAud(name)
|
||||
}
|
||||
}
|
||||
private def alterTableMappedUserAuthContext(startedBeforeSchemifier: Boolean): Boolean = {
|
||||
if(startedBeforeSchemifier == true) {
|
||||
logger.warn(s"Migration.database.alterTableMappedUserAuthContext(true) cannot be run before Schemifier.")
|
||||
|
||||
@ -2,11 +2,11 @@ package code.api.util.migration
|
||||
|
||||
import java.time.format.DateTimeFormatter
|
||||
import java.time.{ZoneId, ZonedDateTime}
|
||||
|
||||
import code.api.util.APIUtil
|
||||
import code.api.util.migration.Migration.{DbFunction, saveLog}
|
||||
import code.model.{AppType, Consumer}
|
||||
import net.liftweb.mapper.DB
|
||||
import net.liftweb.common.Full
|
||||
import net.liftweb.mapper.{DB, Schemifier}
|
||||
import net.liftweb.util.{DefaultConnectionIdentifier, Helpers}
|
||||
|
||||
object MigrationOfConsumer {
|
||||
@ -107,4 +107,52 @@ object MigrationOfConsumer {
|
||||
isSuccessful
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def alterTypeofAud(name: String): Boolean = {
|
||||
DbFunction.tableExists(Consumer) match {
|
||||
case true =>
|
||||
val startDate = System.currentTimeMillis()
|
||||
val commitId: String = APIUtil.gitCommit
|
||||
var isSuccessful = false
|
||||
|
||||
val executedSql =
|
||||
DbFunction.maybeWrite(true, Schemifier.infoF _) {
|
||||
APIUtil.getPropsValue("db.driver") match {
|
||||
case Full(dbDriver) if dbDriver.contains("com.microsoft.sqlserver.jdbc.SQLServerDriver") =>
|
||||
() =>
|
||||
"""
|
||||
|ALTER TABLE consumer ALTER COLUMN aud VARCHAR(MAX) NULL;
|
||||
|""".stripMargin
|
||||
case _ =>
|
||||
() =>
|
||||
"""
|
||||
|ALTER TABLE consumer ALTER COLUMN aud TYPE text;
|
||||
|""".stripMargin
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
val endDate = System.currentTimeMillis()
|
||||
val comment: String =
|
||||
s"""Executed SQL:
|
||||
|$executedSql
|
||||
|""".stripMargin
|
||||
isSuccessful = true
|
||||
saveLog(name, commitId, isSuccessful, startDate, endDate, comment)
|
||||
isSuccessful
|
||||
|
||||
case false =>
|
||||
val startDate = System.currentTimeMillis()
|
||||
val commitId: String = APIUtil.gitCommit
|
||||
val isSuccessful = false
|
||||
val endDate = System.currentTimeMillis()
|
||||
val comment: String =
|
||||
s"""${Consumer._dbTableNameLC} table does not exist""".stripMargin
|
||||
saveLog(name, commitId, isSuccessful, startDate, endDate, comment)
|
||||
isSuccessful
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@ -12,6 +12,7 @@ import code.api.dynamic.entity.helper.DynamicEntityInfo
|
||||
import code.api.util.APIUtil.{fullBoxOrException, _}
|
||||
import code.api.util.ApiRole._
|
||||
import code.api.util.ApiTag._
|
||||
import code.api.util.CommonsEmailWrapper._
|
||||
import code.api.util.DynamicUtil.Validation
|
||||
import code.api.util.ErrorMessages.{BankNotFound, _}
|
||||
import code.api.util.ExampleValue._
|
||||
@ -79,8 +80,7 @@ import net.liftweb.json.JsonAST.JValue
|
||||
import net.liftweb.json.JsonDSL._
|
||||
import net.liftweb.json._
|
||||
import net.liftweb.util.Helpers.{now, tryo}
|
||||
import net.liftweb.util.Mailer.{From, PlainMailBodyType, Subject, To, XHTMLMailBodyType}
|
||||
import net.liftweb.util.{Helpers, Mailer, StringHelpers}
|
||||
import net.liftweb.util.{Helpers, StringHelpers}
|
||||
import org.apache.commons.lang3.StringUtils
|
||||
|
||||
import java.net.URLEncoder
|
||||
@ -91,7 +91,6 @@ import scala.collection.immutable.{List, Nil}
|
||||
import scala.collection.mutable.ArrayBuffer
|
||||
import scala.concurrent.Future
|
||||
import scala.jdk.CollectionConverters.collectionAsScalaIterableConverter
|
||||
import scala.xml.XML
|
||||
|
||||
trait APIMethods400 extends MdcLoggable {
|
||||
self: RestHelper =>
|
||||
@ -3363,7 +3362,21 @@ trait APIMethods400 extends MdcLoggable {
|
||||
.replace(WebUIPlaceholder.activateYourAccount, link)
|
||||
logger.debug(s"customHtmlText: ${customHtmlText}")
|
||||
logger.debug(s"Before send user invitation by email. Purpose: ${UserInvitationPurpose.DEVELOPER}")
|
||||
Mailer.sendMail(From(from), Subject(subject), To(invitation.email), PlainMailBodyType(customText), XHTMLMailBodyType(XML.loadString(customHtmlText)))
|
||||
|
||||
// Use Apache Commons Email wrapper instead of Lift Mailer
|
||||
val emailContent = EmailContent(
|
||||
from = from,
|
||||
to = List(invitation.email),
|
||||
subject = subject,
|
||||
textContent = Some(customText),
|
||||
htmlContent = Some(customHtmlText)
|
||||
)
|
||||
|
||||
sendHtmlEmail(emailContent) match {
|
||||
case Full(messageId) => logger.debug(s"Email sent successfully with Message-ID: $messageId")
|
||||
case Empty => logger.error("Failed to send user invitation email")
|
||||
}
|
||||
|
||||
logger.debug(s"After send user invitation by email. Purpose: ${UserInvitationPurpose.DEVELOPER}")
|
||||
} else {
|
||||
val subject = getWebUiPropsValue("webui_customer_user_invitation_email_subject", "Welcome to the API Playground")
|
||||
@ -3375,7 +3388,21 @@ trait APIMethods400 extends MdcLoggable {
|
||||
.replace(WebUIPlaceholder.activateYourAccount, link)
|
||||
logger.debug(s"customHtmlText: ${customHtmlText}")
|
||||
logger.debug(s"Before send user invitation by email.")
|
||||
Mailer.sendMail(From(from), Subject(subject), To(invitation.email), PlainMailBodyType(customText), XHTMLMailBodyType(XML.loadString(customHtmlText)))
|
||||
|
||||
// Use Apache Commons Email wrapper instead of Lift Mailer
|
||||
val emailContent = EmailContent(
|
||||
from = from,
|
||||
to = List(invitation.email),
|
||||
subject = subject,
|
||||
textContent = Some(customText),
|
||||
htmlContent = Some(customHtmlText)
|
||||
)
|
||||
|
||||
sendHtmlEmail(emailContent) match {
|
||||
case Full(messageId) => logger.debug(s"Email sent successfully with Message-ID: $messageId")
|
||||
case Empty => logger.error("Failed to send user invitation email")
|
||||
}
|
||||
|
||||
logger.debug(s"After send user invitation by email.")
|
||||
}
|
||||
(JSONFactory400.createUserInvitationJson(invitation), HttpCode.`201`(callContext))
|
||||
|
||||
@ -1708,6 +1708,10 @@ trait APIMethods510 {
|
||||
|
|
||||
|7 bank_id (ignore if omitted)
|
||||
|
|
||||
|8 provider_provider_id (ignore if omitted)
|
||||
|provider and provider_id values are separated by pipe char
|
||||
|eg: provider_provider_id=http%3A%2F%2Flocalhost%3A7070%2Frealms%2Fmaster|7837ee9c-3446-4d8c-9b90-301a52b4851d
|
||||
|
|
||||
|eg:/management/consents?consumer_id=78&limit=10&offset=10
|
||||
|
|
||||
""".stripMargin,
|
||||
|
||||
@ -46,12 +46,13 @@ import code.consent.MappedConsent
|
||||
import code.metrics.APIMetric
|
||||
import code.model.Consumer
|
||||
import code.users.{UserAttribute, Users}
|
||||
import code.util.Helper.MdcLoggable
|
||||
import code.views.system.{AccountAccess, ViewDefinition, ViewPermission}
|
||||
import com.openbankproject.commons.model._
|
||||
import com.openbankproject.commons.util.ApiVersion
|
||||
import net.liftweb.common.{Box, Full}
|
||||
import net.liftweb.json
|
||||
import net.liftweb.json.{JString, JValue, parse, parseOpt}
|
||||
import net.liftweb.json.{JString, JValue, MappingException, parse, parseOpt}
|
||||
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
@ -705,7 +706,7 @@ case class ViewPermissionJson(
|
||||
extra_data: Option[List[String]]
|
||||
)
|
||||
|
||||
object JSONFactory510 extends CustomJsonFormats {
|
||||
object JSONFactory510 extends CustomJsonFormats with MdcLoggable {
|
||||
|
||||
def createTransactionRequestJson(tr : TransactionRequest, transactionRequestAttributes: List[TransactionRequestAttributeTrait] ) : TransactionRequestJsonV510 = {
|
||||
TransactionRequestJsonV510(
|
||||
@ -1009,7 +1010,16 @@ object JSONFactory510 extends CustomJsonFormats {
|
||||
def createConsentsJsonV510(consents: List[MappedConsent]): ConsentsJsonV510 = {
|
||||
ConsentsJsonV510(
|
||||
consents.map { c =>
|
||||
val jwtPayload = JwtUtil.getSignedPayloadAsJson(c.jsonWebToken).map(parse(_).extract[ConsentJWT]).toOption
|
||||
val jwtPayload = JwtUtil
|
||||
.getSignedPayloadAsJson(c.jsonWebToken)
|
||||
.flatMap { payload =>
|
||||
Try(parse(payload).extract[ConsentJWT]).recover {
|
||||
case e: MappingException =>
|
||||
logger.warn(s"Invalid JWT payload: ${e.getMessage}")
|
||||
null
|
||||
}.toOption
|
||||
}.toOption
|
||||
|
||||
AllConsentJsonV510(
|
||||
consent_reference_id = c.consentReferenceId,
|
||||
consumer_id = c.consumerId,
|
||||
|
||||
@ -79,8 +79,7 @@ import net.liftweb.json
|
||||
import net.liftweb.json.{JArray, JBool, JObject, JValue}
|
||||
import net.liftweb.mapper._
|
||||
import net.liftweb.util.Helpers.{hours, now, time, tryo}
|
||||
import net.liftweb.util.Mailer.{From, PlainMailBodyType, Subject, To}
|
||||
import net.liftweb.util.{Helpers, Mailer}
|
||||
import net.liftweb.util.Helpers
|
||||
import org.mindrot.jbcrypt.BCrypt
|
||||
import scalikejdbc.DB.CPContext
|
||||
import scalikejdbc.{ConnectionPool, ConnectionPoolSettings, MultipleConnectionPoolContext, DB => scalikeDB, _}
|
||||
@ -383,8 +382,13 @@ object LocalMappedConnector extends Connector with MdcLoggable {
|
||||
val hashedPassword = createHashedPassword(challengeAnswer)
|
||||
APIUtil.getEmailsByUserId(userId) map {
|
||||
pair =>
|
||||
val params = PlainMailBodyType(s"Your OTP challenge : ${challengeAnswer}") :: List(To(pair._2))
|
||||
Mailer.sendMail(From("challenge@tesobe.com"), Subject("Challenge"), params: _*)
|
||||
val emailContent = CommonsEmailWrapper.EmailContent(
|
||||
from = mailUsersUserinfoSenderAddress,
|
||||
to = List(pair._2),
|
||||
subject = "Challenge",
|
||||
textContent = Some(s"Your OTP challenge : ${challengeAnswer}")
|
||||
)
|
||||
CommonsEmailWrapper.sendTextEmail(emailContent)
|
||||
}
|
||||
hashedPassword
|
||||
case Some(StrongCustomerAuthentication.SMS) | Some(StrongCustomerAuthentication.SMS_OTP) =>
|
||||
@ -5185,12 +5189,13 @@ object LocalMappedConnector extends Connector with MdcLoggable {
|
||||
_ <- Future{
|
||||
scaMethod match {
|
||||
case v if v == StrongCustomerAuthentication.EMAIL.toString => // Send the email
|
||||
val params = PlainMailBodyType(userAuthContextUpdate.challenge) :: List(To(customer.email))
|
||||
Mailer.sendMail(
|
||||
From("challenge@tesobe.com"),
|
||||
Subject("Challenge request"),
|
||||
params :_*
|
||||
val emailContent = CommonsEmailWrapper.EmailContent(
|
||||
from = mailUsersUserinfoSenderAddress,
|
||||
to = List(customer.email),
|
||||
subject = "Challenge request",
|
||||
textContent = Some(userAuthContextUpdate.challenge)
|
||||
)
|
||||
CommonsEmailWrapper.sendTextEmail(emailContent)
|
||||
case v if v == StrongCustomerAuthentication.SMS.toString => // Not implemented
|
||||
case _ => // Not handled
|
||||
}
|
||||
@ -5211,8 +5216,13 @@ object LocalMappedConnector extends Connector with MdcLoggable {
|
||||
callContext: Option[CallContext]
|
||||
): OBPReturnType[Box[String]] = {
|
||||
if (scaMethod == StrongCustomerAuthentication.EMAIL){ // Send the email
|
||||
val params = PlainMailBodyType(message) :: List(To(recipient))
|
||||
Mailer.sendMail(From("challenge@tesobe.com"), Subject("OBP Consent Challenge"), params :_*)
|
||||
val emailContent = CommonsEmailWrapper.EmailContent(
|
||||
from = mailUsersUserinfoSenderAddress,
|
||||
to = List(recipient),
|
||||
subject = "OBP Consent Challenge",
|
||||
textContent = Some(message)
|
||||
)
|
||||
CommonsEmailWrapper.sendTextEmail(emailContent)
|
||||
Future{(Full("Success"), callContext)}
|
||||
} else if (scaMethod == StrongCustomerAuthentication.SMS){ // Send the SMS
|
||||
for {
|
||||
|
||||
@ -2,14 +2,11 @@ package code.bankconnectors.rabbitmq
|
||||
|
||||
|
||||
|
||||
import code.api.util.APIUtil
|
||||
import com.rabbitmq.client.{Connection, ConnectionFactory}
|
||||
import org.apache.commons.pool2.impl.{GenericObjectPool, GenericObjectPoolConfig}
|
||||
import org.apache.commons.pool2.BasePooledObjectFactory
|
||||
import org.apache.commons.pool2.PooledObject
|
||||
import org.apache.commons.pool2.impl.DefaultPooledObject
|
||||
import code.api.util.{APIUtil, ErrorMessages}
|
||||
import code.bankconnectors.rabbitmq.RabbitMQUtils._
|
||||
import code.api.util.ErrorMessages
|
||||
import com.rabbitmq.client.{Connection, ConnectionFactory}
|
||||
import org.apache.commons.pool2.{BasePooledObjectFactory, PooledObject}
|
||||
import org.apache.commons.pool2.impl.{DefaultPooledObject, GenericObjectPool, GenericObjectPoolConfig}
|
||||
|
||||
class RabbitMQConnectionFactory extends BasePooledObjectFactory[Connection] {
|
||||
|
||||
@ -59,7 +56,7 @@ class RabbitMQConnectionFactory extends BasePooledObjectFactory[Connection] {
|
||||
|
||||
// Pool to manage RabbitMQ connections
|
||||
object RabbitMQConnectionPool {
|
||||
private val poolConfig = new GenericObjectPoolConfig()
|
||||
private val poolConfig = new GenericObjectPoolConfig[Connection]()
|
||||
poolConfig.setMaxTotal(5) // Maximum number of connections
|
||||
poolConfig.setMinIdle(2) // Minimum number of idle connections
|
||||
poolConfig.setMaxIdle(5) // Maximum number of idle connections
|
||||
|
||||
@ -1552,7 +1552,7 @@ trait RabbitMQConnector_vOct2024 extends Connector with MdcLoggable {
|
||||
startDate=toDate(transactionStartDateExample),
|
||||
finishDate=Some(toDate(transactionFinishDateExample)),
|
||||
balance=BigDecimal(balanceExample.value),
|
||||
status=transactionStatusExample.value
|
||||
status=Some(transactionStatusExample.value)
|
||||
)))
|
||||
),
|
||||
adapterImplementation = Some(AdapterImplementation("- Core", 1))
|
||||
@ -1687,7 +1687,7 @@ trait RabbitMQConnector_vOct2024 extends Connector with MdcLoggable {
|
||||
startDate=toDate(transactionStartDateExample),
|
||||
finishDate=Some(toDate(transactionFinishDateExample)),
|
||||
balance=BigDecimal(balanceExample.value),
|
||||
status=transactionStatusExample.value))
|
||||
status=Some(transactionStatusExample.value)))
|
||||
),
|
||||
adapterImplementation = Some(AdapterImplementation("- Core", 1))
|
||||
)
|
||||
|
||||
@ -1500,7 +1500,7 @@ trait RestConnector_vMar2019 extends Connector with MdcLoggable {
|
||||
startDate=toDate(transactionStartDateExample),
|
||||
finishDate=Some(toDate(transactionFinishDateExample)),
|
||||
balance=BigDecimal(balanceExample.value),
|
||||
status=transactionStatusExample.value)))
|
||||
status=Some(transactionStatusExample.value))))
|
||||
),
|
||||
adapterImplementation = Some(AdapterImplementation("- Core", 1))
|
||||
)
|
||||
@ -1634,7 +1634,7 @@ trait RestConnector_vMar2019 extends Connector with MdcLoggable {
|
||||
startDate=toDate(transactionStartDateExample),
|
||||
finishDate=Some(toDate(transactionFinishDateExample)),
|
||||
balance=BigDecimal(balanceExample.value),
|
||||
status=transactionStatusExample.value))
|
||||
status=Some(transactionStatusExample.value)))
|
||||
),
|
||||
adapterImplementation = Some(AdapterImplementation("- Core", 1))
|
||||
)
|
||||
|
||||
@ -1481,7 +1481,7 @@ trait StoredProcedureConnector_vDec2019 extends Connector with MdcLoggable {
|
||||
startDate=toDate(transactionStartDateExample),
|
||||
finishDate=Some(toDate(transactionFinishDateExample)),
|
||||
balance=BigDecimal(balanceExample.value),
|
||||
status=transactionStatusExample.value)))
|
||||
status=Some(transactionStatusExample.value))))
|
||||
),
|
||||
adapterImplementation = Some(AdapterImplementation("- Core", 1))
|
||||
)
|
||||
@ -1615,7 +1615,7 @@ trait StoredProcedureConnector_vDec2019 extends Connector with MdcLoggable {
|
||||
startDate=toDate(transactionStartDateExample),
|
||||
finishDate=Some(toDate(transactionFinishDateExample)),
|
||||
balance=BigDecimal(balanceExample.value),
|
||||
status=transactionStatusExample.value))
|
||||
status=Some(transactionStatusExample.value)))
|
||||
),
|
||||
adapterImplementation = Some(AdapterImplementation("- Core", 1))
|
||||
)
|
||||
|
||||
@ -1,17 +1,20 @@
|
||||
package code.consent
|
||||
|
||||
import java.util.Date
|
||||
import code.api.util.{APIUtil, Consent, ErrorMessages, OBPBankId, OBPConsentId, OBPConsumerId, OBPLimit, OBPOffset, OBPQueryParam, OBPSortBy, OBPStatus, OBPUserId, SecureRandomUtil}
|
||||
import code.api.util.{APIUtil, Consent, ErrorMessages, OBPBankId, OBPConsentId, OBPConsumerId, OBPLimit, OBPOffset, OBPQueryParam, OBPSortBy, OBPStatus, OBPUserId, ProviderProviderId, SecureRandomUtil}
|
||||
import code.consent.ConsentStatus.ConsentStatus
|
||||
import code.model.Consumer
|
||||
import code.model.dataAccess.ResourceUser
|
||||
import code.util.MappedUUID
|
||||
import com.openbankproject.commons.model.User
|
||||
import com.openbankproject.commons.util.ApiStandards
|
||||
import net.liftweb.common.{Box, Empty, Failure, Full}
|
||||
import net.liftweb.mapper.{MappedString, _}
|
||||
import net.liftweb.mapper._
|
||||
import net.liftweb.util.Helpers.{now, tryo}
|
||||
import org.mindrot.jbcrypt.BCrypt
|
||||
|
||||
import java.net.URLDecoder
|
||||
import java.nio.charset.StandardCharsets
|
||||
import scala.collection.immutable.List
|
||||
|
||||
object MappedConsentProvider extends ConsentProvider {
|
||||
@ -71,6 +74,20 @@ object MappedConsentProvider extends ConsentProvider {
|
||||
// The optional variables:
|
||||
val consumerId = queryParams.collectFirst { case OBPConsumerId(value) => By(MappedConsent.mConsumerId, value) }
|
||||
val consentId = queryParams.collectFirst { case OBPConsentId(value) => By(MappedConsent.mConsentId, value) }
|
||||
val providerProviderId: Option[Cmp[MappedConsent, String]] = queryParams.collectFirst {
|
||||
case ProviderProviderId(value) =>
|
||||
val (provider, providerId) = value.split("\\|") match { // split by literal '|'
|
||||
case Array(a, b) => (a, b)
|
||||
case _ => ("", "") // fallback if format is unexpected
|
||||
}
|
||||
ResourceUser.findAll(By(ResourceUser.provider_, provider), By(ResourceUser.providerId, providerId)) match {
|
||||
case x :: Nil => // exactly one
|
||||
Some(By(MappedConsent.mUserId, x.userId))
|
||||
case _ =>
|
||||
None
|
||||
}
|
||||
}.flatten
|
||||
|
||||
val userId = queryParams.collectFirst { case OBPUserId(value) => By(MappedConsent.mUserId, value) }
|
||||
val status = queryParams.collectFirst {
|
||||
case OBPStatus(value) =>
|
||||
@ -96,7 +113,7 @@ object MappedConsentProvider extends ConsentProvider {
|
||||
offset.toSeq,
|
||||
limit.toSeq,
|
||||
status.toSeq,
|
||||
userId.toSeq,
|
||||
userId.orElse(providerProviderId).toSeq,
|
||||
consentId.toSeq,
|
||||
consumerId.toSeq
|
||||
).flatten
|
||||
|
||||
@ -57,7 +57,7 @@ class ModeratedTransaction(
|
||||
//the filteredBlance type in this class is a string rather than Big decimal like in Transaction trait for snippet (display) reasons.
|
||||
//the view should be able to return a sign (- or +) or the real value. casting signs into big decimal is not possible
|
||||
val balance : String,
|
||||
val status : String
|
||||
val status : Moderated[String]
|
||||
) {
|
||||
|
||||
def dateOption2JString(date: Option[Date]) : JString = {
|
||||
|
||||
@ -541,7 +541,7 @@ class Consumer extends LongKeyedMapper[Consumer] with CreatedUpdated{
|
||||
// because different databases treat unique indexes on NULL values differently.
|
||||
override def defaultValue = APIUtil.generateUUID()
|
||||
}
|
||||
object aud extends MappedString(this, 250) {
|
||||
object aud extends MappedText(this) {
|
||||
override def defaultValue = null
|
||||
}
|
||||
object iss extends MappedString(this, 250) {
|
||||
|
||||
@ -178,7 +178,7 @@ case class ViewExtended(val view: View) {
|
||||
|
||||
val transactionStatus =
|
||||
if (viewPermissions.exists(_ == CAN_SEE_TRANSACTION_STATUS)) transaction.status
|
||||
else ""
|
||||
else None
|
||||
|
||||
new ModeratedTransaction(
|
||||
UUID = transactionUUID,
|
||||
|
||||
@ -33,6 +33,7 @@ import code.api.cache.Caching
|
||||
import code.api.dynamic.endpoint.helper.DynamicEndpointHelper
|
||||
import code.api.util.APIUtil._
|
||||
import code.api.util.CommonFunctions.validUri
|
||||
import code.api.util.CommonsEmailWrapper._
|
||||
import code.api.util.ErrorMessages._
|
||||
import code.api.util._
|
||||
import code.bankconnectors.Connector
|
||||
@ -55,7 +56,6 @@ import net.liftweb.http.S.fmapFunc
|
||||
import net.liftweb.http._
|
||||
import net.liftweb.mapper._
|
||||
import net.liftweb.sitemap.Loc.{If, LocParam, Template}
|
||||
import net.liftweb.util.Mailer.{BCC, From, Subject, To}
|
||||
import net.liftweb.util._
|
||||
import org.apache.commons.lang3.StringUtils
|
||||
import sh.ory.hydra.api.AdminApi
|
||||
@ -589,24 +589,35 @@ import net.liftweb.util.Helpers._
|
||||
*/
|
||||
override def sendPasswordReset(name: String) {
|
||||
findAuthUserByUsernameLocallyLegacy(name).toList ::: findUsersByEmailLocally(name) map {
|
||||
// reason of case parameter name is "u" instead of "user": trait AuthUser have constant mumber name is "user"
|
||||
// So if the follow case paramter name is "user" will cause compile warnings
|
||||
case u if u.validated_? =>
|
||||
u.resetUniqueId().save
|
||||
//NOTE: here, if server_mode = portal, so we need modify the resetLink to portal_hostname, then developer can get proper response..
|
||||
val resetPasswordLinkProps = Constant.HostName
|
||||
val resetPasswordLink = APIUtil.getPropsValue("portal_hostname", resetPasswordLinkProps)+
|
||||
passwordResetPath.mkString("/", "/", "/")+urlEncode(u.getUniqueId())
|
||||
Mailer.sendMail(From(emailFrom),Subject(passwordResetEmailSubject + " - " + u.username),
|
||||
To(u.getEmail) ::
|
||||
generateResetEmailBodies(u, resetPasswordLink) :::
|
||||
(bccEmail.toList.map(BCC(_))) :_*)
|
||||
// Directly generate content using JakartaMail/CommonsEmailWrapper
|
||||
val textContent = Some(s"Please use the following link to reset your password: $resetPasswordLink")
|
||||
val htmlContent = Some(s"<p>Please use the following link to reset your password:</p><p><a href='$resetPasswordLink'>$resetPasswordLink</a></p>")
|
||||
val emailContent = EmailContent(
|
||||
from = emailFrom,
|
||||
to = List(u.getEmail),
|
||||
bcc = bccEmail.toList,
|
||||
subject = passwordResetEmailSubject + " - " + u.username,
|
||||
textContent = textContent,
|
||||
htmlContent = htmlContent
|
||||
)
|
||||
sendHtmlEmail(emailContent) match {
|
||||
case Full(messageId) =>
|
||||
logger.debug(s"Password reset email sent successfully with Message-ID: $messageId")
|
||||
S.notice("Password reset email sent successfully. Please check your email.")
|
||||
S.redirectTo(homePage)
|
||||
case Empty =>
|
||||
logger.error("Failed to send password reset email")
|
||||
S.error("Failed to send password reset email. Please try again.")
|
||||
S.redirectTo(homePage)
|
||||
}
|
||||
case u =>
|
||||
sendValidationEmail(u)
|
||||
}
|
||||
// In order to prevent any leakage of information we use the same message for all cases
|
||||
S.notice(userNameNotFoundString)
|
||||
S.redirectTo(homePage)
|
||||
}
|
||||
|
||||
override def lostPasswordXhtml = {
|
||||
@ -638,17 +649,26 @@ import net.liftweb.util.Helpers._
|
||||
* Overridden to use the hostname set in the props file
|
||||
*/
|
||||
override def sendValidationEmail(user: TheUserType) {
|
||||
val resetLink = Constant.HostName+"/"+validateUserPath.mkString("/")+
|
||||
"/"+urlEncode(user.getUniqueId())
|
||||
|
||||
val resetLink = Constant.HostName+"/"+validateUserPath.mkString("/")+"/"+urlEncode(user.getUniqueId())
|
||||
val email: String = user.getEmail
|
||||
|
||||
val msgXml = signupMailBody(user, resetLink)
|
||||
|
||||
Mailer.sendMail(From(emailFrom),Subject(signupMailSubject),
|
||||
To(user.getEmail) ::
|
||||
generateValidationEmailBodies(user, resetLink) :::
|
||||
(bccEmail.toList.map(BCC(_))) :_* )
|
||||
val textContent = Some(s"Welcome! Please validate your account by clicking the following link: $resetLink")
|
||||
val htmlContent = Some(s"<p>Welcome! Please validate your account by clicking the following link:</p><p><a href='$resetLink'>$resetLink</a></p>")
|
||||
val emailContent = EmailContent(
|
||||
from = emailFrom,
|
||||
to = List(user.getEmail),
|
||||
bcc = bccEmail.toList,
|
||||
subject = signupMailSubject,
|
||||
textContent = textContent,
|
||||
htmlContent = htmlContent
|
||||
)
|
||||
sendHtmlEmail(emailContent) match {
|
||||
case Full(messageId) =>
|
||||
logger.debug(s"Validation email sent successfully with Message-ID: $messageId")
|
||||
S.notice("Validation email sent successfully. Please check your email.")
|
||||
case Empty =>
|
||||
logger.error("Failed to send validation email")
|
||||
S.error("Failed to send validation email. Please try again.")
|
||||
}
|
||||
}
|
||||
|
||||
def grantDefaultEntitlementsToAuthUser(user: TheUserType) = {
|
||||
|
||||
@ -28,7 +28,7 @@ package code.snippet
|
||||
|
||||
import java.util
|
||||
import code.api.{Constant, DirectLogin}
|
||||
import code.api.util.{APIUtil, ErrorMessages, KeycloakAdmin, X509}
|
||||
import code.api.util.{APIUtil, ErrorMessages, KeycloakAdmin, X509, CommonsEmailWrapper}
|
||||
import code.consumer.Consumers
|
||||
import code.model.dataAccess.AuthUser
|
||||
import code.model.{Consumer, _}
|
||||
@ -424,18 +424,19 @@ class ConsumerRegistration extends MdcLoggable {
|
||||
s"Direct Login Documentation: ${oauthDocumentationUrl} \n" +
|
||||
s"$registrationMoreInfoText: $registrationMoreInfoUrl"
|
||||
|
||||
val params = PlainMailBodyType(registrationMessage) :: List(To(registered.developerEmail.get))
|
||||
|
||||
val webuiRegisterConsumerSuccessMssageEmail : String = getWebUiPropsValue(
|
||||
"webui_register_consumer_success_message_email",
|
||||
"Thank you for registering to use the Open Bank Project API.")
|
||||
|
||||
//this is an async call
|
||||
Mailer.sendMail(
|
||||
From(from),
|
||||
Subject(webuiRegisterConsumerSuccessMssageEmail),
|
||||
params :_*
|
||||
val emailContent = CommonsEmailWrapper.EmailContent(
|
||||
from = from,
|
||||
to = List(registered.developerEmail.get),
|
||||
subject = webuiRegisterConsumerSuccessMssageEmail,
|
||||
textContent = Some(registrationMessage)
|
||||
)
|
||||
|
||||
//this is an async call
|
||||
CommonsEmailWrapper.sendTextEmail(emailContent)
|
||||
}
|
||||
|
||||
if(mailSent.isEmpty)
|
||||
@ -445,8 +446,6 @@ class ConsumerRegistration extends MdcLoggable {
|
||||
|
||||
// This is to let the system administrators / API managers know that someone has registered a consumer key.
|
||||
def notifyRegistrationOccurred(registered : Consumer) = {
|
||||
import net.liftweb.util.Mailer
|
||||
import net.liftweb.util.Mailer._
|
||||
|
||||
val mailSent = for {
|
||||
// e.g mail.api.consumer.registered.sender.address=no-reply@example.com
|
||||
@ -465,18 +464,18 @@ class ConsumerRegistration extends MdcLoggable {
|
||||
|
||||
//technically doesn't work for all valid email addresses so this will mess up if someone tries to send emails to "foo,bar"@example.com
|
||||
val to = toAddressesString.split(",").toList
|
||||
val toParams = to.map(To(_))
|
||||
val params = PlainMailBodyType(registrationMessage) :: toParams
|
||||
|
||||
val emailContent = CommonsEmailWrapper.EmailContent(
|
||||
from = from,
|
||||
to = to,
|
||||
subject = s"New API user registered on $thisApiInstance",
|
||||
textContent = Some(registrationMessage)
|
||||
)
|
||||
|
||||
//this is an async call
|
||||
Mailer.sendMail(
|
||||
From(from),
|
||||
Subject(s"New API user registered on $thisApiInstance"),
|
||||
params :_*
|
||||
)
|
||||
CommonsEmailWrapper.sendTextEmail(emailContent)
|
||||
}
|
||||
|
||||
//if Mailer.sendMail wasn't called (note: this actually isn't checking if the mail failed to send as that is being done asynchronously)
|
||||
if(mailSent.isEmpty)
|
||||
this.logger.warn(s"API consumer registration failed: $mailSent")
|
||||
|
||||
|
||||
@ -156,7 +156,7 @@ class MappedTransaction extends LongKeyedMapper[MappedTransaction] with IdPK wit
|
||||
tStartDate.get,
|
||||
Some(tFinishDate.get),
|
||||
newBalance,
|
||||
status.get))
|
||||
Some(status.get)))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -91,7 +91,7 @@ class JSONFactory_BERLIN_GROUP_1_3Test extends FeatureSpec with Matchers with Gi
|
||||
startDate = Some(new java.util.Date()),
|
||||
finishDate = Some(new java.util.Date()),
|
||||
balance = "900.00",
|
||||
status = "booked"
|
||||
status = Some("booked")
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
Binary file not shown.
@ -29,6 +29,16 @@
|
||||
<dependency>
|
||||
<groupId>net.liftweb</groupId>
|
||||
<artifactId>lift-util_${scala.version}</artifactId>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>javax.activation</groupId>
|
||||
<artifactId>activation</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>javax.mail</groupId>
|
||||
<artifactId>mail</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.liftweb</groupId>
|
||||
|
||||
@ -1141,7 +1141,7 @@ case class Transaction(
|
||||
finishDate : Option[Date],
|
||||
//the new balance for the bank account
|
||||
balance : BigDecimal,
|
||||
status: String
|
||||
status : Option[String]
|
||||
) {
|
||||
|
||||
val bankId = thisAccount.bankId
|
||||
|
||||
@ -3,6 +3,20 @@
|
||||
### Most recent changes at top of file
|
||||
```
|
||||
Date Commit Action
|
||||
04/08/2025 d282d266 Enhanced Email Configuration with CommonsEmailWrapper
|
||||
Replaced Lift Mailer with Apache Commons Email for improved email functionality.
|
||||
Added comprehensive SMTP configuration options:
|
||||
- mail.smtp.auth (authentication support)
|
||||
- mail.smtp.user (SMTP username)
|
||||
- mail.smtp.password (SMTP password)
|
||||
- mail.smtp.starttls.enable (STARTTLS support)
|
||||
- mail.smtp.ssl.enable (SSL support)
|
||||
- mail.smtp.ssl.protocols (TLS protocol selection)
|
||||
- mail.smtp.ssl.trust (certificate trust options)
|
||||
- mail.debug (SMTP debugging)
|
||||
- mail.users.userinfo.sender.address (default sender)
|
||||
Added configuration examples forTesobe mail servers.
|
||||
All email functionality (password reset, validation, notifications) now uses CommonsEmailWrapper.
|
||||
25/06/2025 e49ebb4f Added props BG_remove_sign_of_amounts, default is false.
|
||||
If set to true, the sign of amounts will be removed in the BGv1.3 getTransaction endpoints.
|
||||
17/03/2025 166e4f2a Removed Kafka commits: 166e4f2a,7f24802e,6f0a3b53,f22763c3,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user