Merge pull request #2644 from hongwei1/develop

feature/added http4s
This commit is contained in:
Simon Redfern 2025-12-12 11:58:19 +01:00 committed by GitHub
commit 2adda4f552
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
23 changed files with 796 additions and 66 deletions

View File

@ -23,6 +23,52 @@
<webXmlPath>src/main/resources/web.xml</webXmlPath>
</properties>
</profile>
<profile>
<id>http4s-jar</id>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>3.6.0</version>
<configuration>
<appendAssemblyId>false</appendAssemblyId>
<finalName>${project.artifactId}-http4s</finalName>
<archive>
<manifest>
<mainClass>bootstrap.http4s.Http4sServer</mainClass>
</manifest>
</archive>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
<dependencySets>
<dependencySet>
<outputDirectory>/</outputDirectory>
<unpack>true</unpack>
<scope>runtime</scope>
</dependencySet>
</dependencySets>
<fileSets>
<fileSet>
<directory>${project.build.outputDirectory}</directory>
<outputDirectory>/</outputDirectory>
</fileSet>
</fileSets>
</configuration>
<executions>
<execution>
<id>http4s-fat-jar</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
<pluginRepositories>
<pluginRepository>
@ -84,6 +130,16 @@
<artifactId>bcpg-jdk15on</artifactId>
<version>1.70</version>
</dependency>
<dependency>
<groupId>org.http4s</groupId>
<artifactId>http4s-ember-server_${scala.version}</artifactId>
<version>${http4s.version}</version>
</dependency>
<dependency>
<groupId>org.http4s</groupId>
<artifactId>http4s-dsl_${scala.version}</artifactId>
<version>${http4s.version}</version>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk15on</artifactId>
@ -345,7 +401,7 @@
<dependency>
<groupId>org.clapper</groupId>
<artifactId>classutil_${scala.version}</artifactId>
<version>1.4.0</version>
<version>1.5.1</version>
</dependency>
<dependency>
<groupId>com.github.grumlimited</groupId>
@ -602,6 +658,20 @@
<plugin>
<groupId>net.alchim31.maven</groupId>
<artifactId>scala-maven-plugin</artifactId>
<version>4.8.1</version>
<configuration>
<fork>true</fork>
<jvmArgs>
<jvmArg>-Xms4G</jvmArg>
<jvmArg>-Xmx12G</jvmArg>
<jvmArg>-XX:MaxMetaspaceSize=4G</jvmArg>
<jvmArg>-XX:+UseG1GC</jvmArg>
</jvmArgs>
<args>
<arg>-deprecation</arg>
<arg>-feature</arg>
</args>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
@ -609,6 +679,8 @@
<version>3.4.0</version>
<configuration>
<webXml>${webXmlPath}</webXml>
<attachClasses>true</attachClasses>
<classesClassifier>classes</classesClassifier>
</configuration>
</plugin>
<plugin>

View File

@ -0,0 +1,346 @@
/**
Open Bank Project - API
Copyright (C) 2011-2019, 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
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Email: contact@tesobe.com
TESOBE GmbH.
Osloer Strasse 16/17
Berlin 13359, Germany
This product includes software developed at
TESOBE (http://www.tesobe.com/)
*/
package bootstrap.http4s
import bootstrap.liftweb.ToSchemify
import code.api.Constant._
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.migration.Migration.DbFunction
import code.entitlement.Entitlement
import code.model.dataAccess._
import code.scheduler._
import code.users._
import code.util.Helper.MdcLoggable
import code.views.Views
import com.openbankproject.commons.util.Functions.Implicits._
import net.liftweb.common.Box.tryo
import net.liftweb.common._
import net.liftweb.db.{DB, DBLogEntry}
import net.liftweb.mapper.{DefaultConnectionIdentifier => _, _}
import net.liftweb.util._
import java.io.{File, FileInputStream}
import java.util.TimeZone
/**
* Http4s Boot class for initializing OBP-API core components
* This class handles database initialization, migrations, and system setup
* without Lift Web framework dependencies
*/
class Http4sBoot extends MdcLoggable {
/**
* For the project scope, most early initiate logic should in this method.
*/
override protected def initiate(): Unit = {
val resourceDir = System.getProperty("props.resource.dir") ?: System.getenv("props.resource.dir")
val propsPath = tryo{Box.legacyNullTest(resourceDir)}.toList.flatten
val propsDir = for {
propsPath <- propsPath
} yield {
Props.toTry.map {
f => {
val name = propsPath + f() + "props"
name -> { () => tryo{new FileInputStream(new File(name))} }
}
}
}
Props.whereToLook = () => {
propsDir.flatten
}
if (Props.mode == Props.RunModes.Development) logger.info("OBP-API Props all fields : \n" + Props.props.mkString("\n"))
logger.info("external props folder: " + propsPath)
TimeZone.setDefault(TimeZone.getTimeZone("UTC"))
logger.info("Current Project TimeZone: " + TimeZone.getDefault)
// set dynamic_code_sandbox_enable to System.properties, so com.openbankproject.commons.ExecutionContext can read this value
APIUtil.getPropsValue("dynamic_code_sandbox_enable")
.foreach(it => System.setProperty("dynamic_code_sandbox_enable", it))
}
def boot: Unit = {
implicit val formats = CustomJsonFormats.formats
logger.info("Http4sBoot says: Hello from the Open Bank Project API. This is Http4sBoot.scala for Http4s runner. The gitCommit is : " + APIUtil.gitCommit)
logger.debug("Boot says:Using database driver: " + APIUtil.driver)
DB.defineConnectionManager(net.liftweb.util.DefaultConnectionIdentifier, APIUtil.vendor)
/**
* Function that determines if foreign key constraints are
* created by Schemifier for the specified connection.
*
* Note: The chosen driver must also support foreign keys for
* creation to happen
*
* In case of PostgreSQL it works
*/
MapperRules.createForeignKeys_? = (_) => APIUtil.getPropsAsBoolValue("mapper_rules.create_foreign_keys", false)
schemifyAll()
logger.info("Mapper database info: " + Migration.DbFunction.mapperDatabaseInfo)
DbFunction.tableExists(ResourceUser) match {
case true => // DB already exist
// Migration Scripts are used to update the model of OBP-API DB to a latest version.
// Please note that migration scripts are executed before Lift Mapper Schemifier
Migration.database.executeScripts(startedBeforeSchemifier = true)
logger.info("The Mapper database already exits. The scripts are executed BEFORE Lift Mapper Schemifier.")
case false => // DB is still not created. The scripts will be executed after Lift Mapper Schemifier
logger.info("The Mapper database is still not created. The scripts are going to be executed AFTER Lift Mapper Schemifier.")
}
// Migration Scripts are used to update the model of OBP-API DB to a latest version.
// Please note that migration scripts are executed after Lift Mapper Schemifier
Migration.database.executeScripts(startedBeforeSchemifier = false)
if (APIUtil.getPropsAsBoolValue("create_system_views_at_boot", true)) {
// Create system views
val owner = Views.views.vend.getOrCreateSystemView(SYSTEM_OWNER_VIEW_ID).isDefined
val auditor = Views.views.vend.getOrCreateSystemView(SYSTEM_AUDITOR_VIEW_ID).isDefined
val accountant = Views.views.vend.getOrCreateSystemView(SYSTEM_ACCOUNTANT_VIEW_ID).isDefined
val standard = Views.views.vend.getOrCreateSystemView(SYSTEM_STANDARD_VIEW_ID).isDefined
val stageOne = Views.views.vend.getOrCreateSystemView(SYSTEM_STAGE_ONE_VIEW_ID).isDefined
val manageCustomViews = Views.views.vend.getOrCreateSystemView(SYSTEM_MANAGE_CUSTOM_VIEWS_VIEW_ID).isDefined
// Only create Firehose view if they are enabled at instance.
val accountFirehose = if (ApiPropsWithAlias.allowAccountFirehose)
Views.views.vend.getOrCreateSystemView(SYSTEM_FIREHOSE_VIEW_ID).isDefined
else Empty.isDefined
APIUtil.getPropsValue("additional_system_views") match {
case Full(value) =>
val additionalSystemViewsFromProps = value.split(",").map(_.trim).toList
val additionalSystemViews = List(
SYSTEM_READ_ACCOUNTS_BASIC_VIEW_ID,
SYSTEM_READ_ACCOUNTS_DETAIL_VIEW_ID,
SYSTEM_READ_BALANCES_VIEW_ID,
SYSTEM_READ_TRANSACTIONS_BASIC_VIEW_ID,
SYSTEM_READ_TRANSACTIONS_DEBITS_VIEW_ID,
SYSTEM_READ_TRANSACTIONS_DETAIL_VIEW_ID,
SYSTEM_READ_ACCOUNTS_BERLIN_GROUP_VIEW_ID,
SYSTEM_READ_BALANCES_BERLIN_GROUP_VIEW_ID,
SYSTEM_READ_TRANSACTIONS_BERLIN_GROUP_VIEW_ID,
SYSTEM_INITIATE_PAYMENTS_BERLIN_GROUP_VIEW_ID
)
for {
systemView <- additionalSystemViewsFromProps
if additionalSystemViews.exists(_ == systemView)
} {
Views.views.vend.getOrCreateSystemView(systemView)
}
case _ => // Do nothing
}
}
ApiWarnings.logWarningsRegardingProperties()
ApiWarnings.customViewNamesCheck()
ApiWarnings.systemViewNamesCheck()
//see the notes for this method:
createDefaultBankAndDefaultAccountsIfNotExisting()
createBootstrapSuperUser()
if (APIUtil.getPropsAsBoolValue("logging.database.queries.enable", false)) {
DB.addLogFunc
{
case (log, duration) =>
{
logger.debug("Total query time : %d ms".format(duration))
log.allEntries.foreach
{
case DBLogEntry(stmt, duration) =>
logger.debug("The query : %s in %d ms".format(stmt, duration))
}
}
}
}
// start RabbitMq Adapter(using mapped connector as mockded CBS)
if (APIUtil.getPropsAsBoolValue("rabbitmq.adapter.enabled", false)) {
code.bankconnectors.rabbitmq.Adapter.startRabbitMqAdapter.main(Array(""))
}
// ensure our relational database's tables are created/fit the schema
val connector = code.api.Constant.CONNECTOR.openOrThrowException(s"$MandatoryPropertyIsNotSet. The missing prop is `connector` ")
logger.info(s"ApiPathZero (the bit before version) is $ApiPathZero")
logger.debug(s"If you can read this, logging level is debug")
// API Metrics (logs of API calls)
// If set to true we will write each URL with params to a datastore / log file
if (APIUtil.getPropsAsBoolValue("write_metrics", false)) {
logger.info("writeMetrics is true. We will write API metrics")
} else {
logger.info("writeMetrics is false. We will NOT write API metrics")
}
// API Metrics (logs of Connector calls)
// If set to true we will write each URL with params to a datastore / log file
if (APIUtil.getPropsAsBoolValue("write_connector_metrics", false)) {
logger.info("writeConnectorMetrics is true. We will write connector metrics")
} else {
logger.info("writeConnectorMetrics is false. We will NOT write connector metrics")
}
logger.info (s"props_identifier is : ${APIUtil.getPropsValue("props_identifier", "NONE-SET")}")
val locale = I18NUtil.getDefaultLocale()
logger.info("Default Project Locale is :" + locale)
}
def schemifyAll() = {
Schemifier.schemify(true, Schemifier.infoF _, ToSchemify.models: _*)
}
/**
* there will be a default bank and two default accounts in obp mapped mode.
* These bank and accounts will be used for the payments.
* when we create transaction request over counterparty and if the counterparty do not link to an existing obp account
* then we will use the default accounts (incoming and outgoing) to keep the money.
*/
private def createDefaultBankAndDefaultAccountsIfNotExisting() ={
val defaultBankId= APIUtil.defaultBankId
val incomingAccountId= INCOMING_SETTLEMENT_ACCOUNT_ID
val outgoingAccountId= OUTGOING_SETTLEMENT_ACCOUNT_ID
MappedBank.find(By(MappedBank.permalink, defaultBankId)) match {
case Full(b) =>
logger.debug(s"Bank(${defaultBankId}) is found.")
case _ =>
MappedBank.create
.permalink(defaultBankId)
.fullBankName("OBP_DEFAULT_BANK")
.shortBankName("OBP")
.national_identifier("OBP")
.mBankRoutingScheme("OBP")
.mBankRoutingAddress("obp1")
.logoURL("")
.websiteURL("")
.saveMe()
logger.debug(s"creating Bank(${defaultBankId})")
}
MappedBankAccount.find(By(MappedBankAccount.bank, defaultBankId), By(MappedBankAccount.theAccountId, incomingAccountId)) match {
case Full(b) =>
logger.debug(s"BankAccount(${defaultBankId}, $incomingAccountId) is found.")
case _ =>
MappedBankAccount.create
.bank(defaultBankId)
.theAccountId(incomingAccountId)
.accountCurrency("EUR")
.saveMe()
logger.debug(s"creating BankAccount(${defaultBankId}, $incomingAccountId).")
}
MappedBankAccount.find(By(MappedBankAccount.bank, defaultBankId), By(MappedBankAccount.theAccountId, outgoingAccountId)) match {
case Full(b) =>
logger.debug(s"BankAccount(${defaultBankId}, $outgoingAccountId) is found.")
case _ =>
MappedBankAccount.create
.bank(defaultBankId)
.theAccountId(outgoingAccountId)
.accountCurrency("EUR")
.saveMe()
logger.debug(s"creating BankAccount(${defaultBankId}, $outgoingAccountId).")
}
}
/**
* Bootstrap Super User
* Given the following credentials, OBP will create a user *if it does not exist already*.
* This user's password will be valid for a limited amount of time.
* This user will be granted ONLY CanCreateEntitlementAtAnyBank
* This feature can also be used in a "Break Glass scenario"
*/
private def createBootstrapSuperUser() ={
val superAdminUsername = APIUtil.getPropsValue("super_admin_username","")
val superAdminInitalPassword = APIUtil.getPropsValue("super_admin_inital_password","")
val superAdminEmail = APIUtil.getPropsValue("super_admin_email","")
val isPropsNotSetProperly = superAdminUsername==""||superAdminInitalPassword ==""||superAdminEmail==""
//This is the logic to check if an AuthUser exists for the `create sandbox` endpoint, AfterApiAuth, OpenIdConnect ,,,
val existingAuthUser = AuthUser.find(By(AuthUser.username, superAdminUsername))
if(isPropsNotSetProperly) {
//Nothing happens, props is not set
}else if(existingAuthUser.isDefined) {
logger.error(s"createBootstrapSuperUser- Errors: Existing AuthUser with username ${superAdminUsername} detected in data import where no ResourceUser was found")
} else {
val authUser = AuthUser.create
.email(superAdminEmail)
.firstName(superAdminUsername)
.lastName(superAdminUsername)
.username(superAdminUsername)
.password(superAdminInitalPassword)
.passwordShouldBeChanged(true)
.validated(true)
val validationErrors = authUser.validate
if(!validationErrors.isEmpty)
logger.error(s"createBootstrapSuperUser- Errors: ${validationErrors.map(_.msg)}")
else {
Full(authUser.save()) //this will create/update the resourceUser.
val userBox = Users.users.vend.getUserByProviderAndUsername(authUser.getProvider(), authUser.username.get)
val resultBox = userBox.map(user => Entitlement.entitlement.vend.addEntitlement("", user.userId, CanCreateEntitlementAtAnyBank.toString))
if(resultBox.isEmpty){
logger.error(s"createBootstrapSuperUser- Errors: ${resultBox}")
}
}
}
}
}

View File

@ -0,0 +1,35 @@
package bootstrap.http4s
import cats.data.{Kleisli, OptionT}
import cats.effect._
import code.api.util.APIUtil
import com.comcast.ip4s._
import org.http4s._
import org.http4s.ember.server._
import org.http4s.implicits._
import scala.language.higherKinds
object Http4sServer extends IOApp {
val services: Kleisli[({type λ[β$0$] = OptionT[IO, β$0$]})#λ, Request[IO], Response[IO]] =
code.api.v7_0_0.Http4s700.wrappedRoutesV700Services
val httpApp: Kleisli[IO, Request[IO], Response[IO]] = (services).orNotFound
//Start OBP relevant objects, and settings
new bootstrap.http4s.Http4sBoot().boot
val port = APIUtil.getPropsAsIntValue("http4s.port",8181)
val host = APIUtil.getPropsValue("http4s.host","127.0.0.1")
override def run(args: List[String]): IO[ExitCode] = EmberServerBuilder
.default[IO]
.withHost(Host.fromString(host).get)
.withPort(Port.fromInt(port).get)
.withHttpApp(httpApp)
.build
.use(_ => IO.never)
.as(ExitCode.Success)
}

View File

@ -50,7 +50,7 @@ import sh.ory.hydra.model.OAuth2TokenIntrospection
import java.net.URI
import scala.concurrent.Future
import scala.jdk.CollectionConverters.mapAsJavaMapConverter
import scala.collection.JavaConverters._
/**
* This object provides the API calls necessary to third party applications

View File

@ -2679,8 +2679,7 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
}
def getDisabledVersions() : List[String] = {
val defaultDisabledVersions = "OBPv1.2.1,OBPv1.3.0,OBPv1.4.0,OBPv2.0.0,OBPv2.1.0,OBPv2.2.0,OBPv3.0.0,OBPv3.1.0,OBPv4.0.0,OBPv5.0.0"
val disabledVersions = APIUtil.getPropsValue("api_disabled_versions").getOrElse(defaultDisabledVersions).replace("[", "").replace("]", "").split(",").toList.filter(_.nonEmpty)
val disabledVersions = APIUtil.getPropsValue("api_disabled_versions").getOrElse("").replace("[", "").replace("]", "").split(",").toList.filter(_.nonEmpty)
if (disabledVersions.nonEmpty) {
logger.info(s"Disabled API versions: ${disabledVersions.mkString(", ")}")
}

View File

@ -8,7 +8,7 @@ import java.security.cert._
import java.util.{Base64, Collections}
import javax.net.ssl.TrustManagerFactory
import scala.io.Source
import scala.jdk.CollectionConverters._
import scala.collection.JavaConverters._
import scala.util.{Failure, Success, Try}
object CertificateVerifier extends MdcLoggable {
@ -69,8 +69,8 @@ object CertificateVerifier extends MdcLoggable {
trustManagerFactory.init(trustStore)
// Get trusted CAs from the trust store
val trustAnchors = trustStore.aliases().asScala
.filter(trustStore.isCertificateEntry)
val trustAnchors = enumerationAsScalaIterator(trustStore.aliases())
.filter(trustStore.isCertificateEntry(_))
.map(alias => trustStore.getCertificate(alias).asInstanceOf[X509Certificate])
.map(cert => new TrustAnchor(cert, null))
.toSet

View File

@ -17,7 +17,7 @@ import java.time.format.DateTimeFormatter
import java.time.{Duration, ZoneOffset, ZonedDateTime}
import java.util
import scala.collection.immutable.{HashMap, List}
import scala.jdk.CollectionConverters.seqAsJavaListConverter
import scala.collection.JavaConverters._
object JwsUtil extends MdcLoggable {

View File

@ -5,16 +5,10 @@ import code.DynamicEndpoint.DynamicEndpointSwagger
import code.accountattribute.AccountAttributeX
import code.api.Constant._
import code.api.ResourceDocs1_4_0.SwaggerDefinitionsJSON
import code.api.ResourceDocs1_4_0.SwaggerDefinitionsJSON.{
jsonDynamicResourceDoc,
_
}
import code.api.dynamic.endpoint.helper.practise.{
DynamicEndpointCodeGenerator,
PractiseEndpoint
}
import code.api.ResourceDocs1_4_0.SwaggerDefinitionsJSON.{jsonDynamicResourceDoc, _}
import code.api.dynamic.endpoint.helper.practise.{DynamicEndpointCodeGenerator, PractiseEndpoint}
import code.api.dynamic.endpoint.helper.{CompiledObjects, DynamicEndpointHelper}
import code.api.dynamic.entity.helper.{DynamicEntityHelper, DynamicEntityInfo}
import code.api.dynamic.entity.helper.DynamicEntityInfo
import code.api.util.APIUtil.{fullBoxOrException, _}
import code.api.util.ApiRole._
import code.api.util.ApiTag._
@ -31,20 +25,11 @@ import code.api.util.migration.Migration
import code.api.util.newstyle.AttributeDefinition._
import code.api.util.newstyle.Consumer._
import code.api.util.newstyle.UserCustomerLinkNewStyle.getUserCustomerLinks
import code.api.util.newstyle.{
BalanceNewStyle,
UserCustomerLinkNewStyle,
ViewNewStyle
}
import code.api.util.newstyle.{BalanceNewStyle, UserCustomerLinkNewStyle, ViewNewStyle}
import code.api.v1_2_1.{JSONFactory, PostTransactionTagJSON}
import code.api.v1_4_0.JSONFactory1_4_0
import code.api.v2_0_0.OBPAPI2_0_0.Implementations2_0_0
import code.api.v2_0_0.{
CreateEntitlementJSON,
CreateUserCustomerLinkJson,
EntitlementJSONs,
JSONFactory200
}
import code.api.v2_0_0.{CreateEntitlementJSON, CreateUserCustomerLinkJson, EntitlementJSONs, JSONFactory200}
import code.api.v2_1_0._
import code.api.v3_0_0.{CreateScopeJson, JSONFactory300}
import code.api.v3_1_0._
@ -54,15 +39,10 @@ import code.apicollection.MappedApiCollectionsProvider
import code.apicollectionendpoint.MappedApiCollectionEndpointsProvider
import code.authtypevalidation.JsonAuthTypeValidation
import code.bankconnectors.LocalMappedConnectorInternal._
import code.bankconnectors.{
Connector,
DynamicConnector,
InternalConnector,
LocalMappedConnectorInternal
}
import code.bankconnectors.{Connector, DynamicConnector, InternalConnector, LocalMappedConnectorInternal}
import code.connectormethod.{JsonConnectorMethod, JsonConnectorMethodMethodBody}
import code.consent.{ConsentStatus, Consents}
import code.dynamicEntity.{DynamicEntityCommons, ReferenceType}
import code.dynamicEntity.DynamicEntityCommons
import code.dynamicMessageDoc.JsonDynamicMessageDoc
import code.dynamicResourceDoc.JsonDynamicResourceDoc
import code.endpointMapping.EndpointMappingCommons
@ -82,10 +62,7 @@ import code.util.Helper.{MdcLoggable, ObpS, SILENCE_IS_GOLDEN, booleanToFuture}
import code.util.{Helper, JsonSchemaUtil}
import code.validation.JsonValidation
import code.views.Views
import code.webhook.{
BankAccountNotificationWebhookTrait,
SystemAccountNotificationWebhookTrait
}
import code.webhook.{BankAccountNotificationWebhookTrait, SystemAccountNotificationWebhookTrait}
import code.webuiprops.MappedWebUiPropsProvider.getWebUiPropsValue
import com.github.dwickern.macros.NameOf.nameOf
import com.networknt.schema.ValidationMessage
@ -110,10 +87,10 @@ import java.net.URLEncoder
import java.text.SimpleDateFormat
import java.util
import java.util.{Calendar, Date}
import scala.collection.JavaConverters._
import scala.collection.immutable.{List, Nil}
import scala.collection.mutable.ArrayBuffer
import scala.concurrent.Future
import scala.jdk.CollectionConverters.collectionAsScalaIterableConverter
trait APIMethods400 extends MdcLoggable {
self: RestHelper =>
@ -11796,10 +11773,10 @@ trait APIMethods400 extends MdcLoggable {
Future {
val versions: List[ScannedApiVersion] =
ApiVersion.allScannedApiVersion.asScala.toList.filter { version =>
version.urlPrefix.trim.nonEmpty
version.urlPrefix.trim.nonEmpty && APIUtil.versionIsAllowed(version)
}
(
ListResult("scanned_api_versions", versions),
ListResult("scanned_api_versions", versions),
HttpCode.`200`(cc.callContext)
)
}

View File

@ -65,7 +65,7 @@ import scala.collection.immutable.{List, Nil}
import scala.collection.mutable.ArrayBuffer
import scala.concurrent.Future
import scala.concurrent.duration._
import scala.jdk.CollectionConverters._
import scala.collection.JavaConverters._
import scala.util.Random
@ -3486,7 +3486,7 @@ trait APIMethods600 {
|
|""",
EmptyBody,
WebUiPropsCommons("webui_api_explorer_url", "https://apiexplorer.openbankproject.com", Some("web-ui-props-id"), "database"),
WebUiPropsCommons("webui_api_explorer_url", "https://apiexplorer.openbankproject.com", Some("web-ui-props-id"), Some("config")),
List(
WebUiPropsNotFoundByName,
UnknownError
@ -3509,11 +3509,11 @@ trait APIMethods600 {
explicitProp match {
case Some(prop) =>
// Found in database
Future.successful(WebUiPropsCommons(prop.name, prop.value, prop.webUiPropsId, source = "database"))
Future.successful(WebUiPropsCommons(prop.name, prop.value, prop.webUiPropsId, source = Some("database")))
case None if isActived =>
// Not in database, check implicit props if active=true
val implicitWebUiProps = getWebUIPropsPairs.map(webUIPropsPairs =>
WebUiPropsCommons(webUIPropsPairs._1, webUIPropsPairs._2, webUiPropsId = None, source = "config")
WebUiPropsCommons(webUIPropsPairs._1, webUIPropsPairs._2, webUiPropsId = Some("default"), source = Some("config"))
)
val implicitProp = implicitWebUiProps.find(_.name == webUiPropName)
implicitProp match {
@ -3584,7 +3584,7 @@ trait APIMethods600 {
EmptyBody,
ListResult(
"webui_props",
(List(WebUiPropsCommons("webui_api_explorer_url", "https://apiexplorer.openbankproject.com", Some("web-ui-props-id"), "database")))
(List(WebUiPropsCommons("webui_api_explorer_url", "https://apiexplorer.openbankproject.com", Some("web-ui-props-id"), Some("database"))))
)
,
List(
@ -3608,8 +3608,8 @@ trait APIMethods600 {
}
}
explicitWebUiProps <- Future{ MappedWebUiPropsProvider.getAll() }
explicitWebUiPropsWithSource = explicitWebUiProps.map(prop => WebUiPropsCommons(prop.name, prop.value, prop.webUiPropsId, source = "database"))
implicitWebUiProps = getWebUIPropsPairs.map(webUIPropsPairs=>WebUiPropsCommons(webUIPropsPairs._1, webUIPropsPairs._2, webUiPropsId = None, source = "config"))
explicitWebUiPropsWithSource = explicitWebUiProps.map(prop => WebUiPropsCommons(prop.name, prop.value, prop.webUiPropsId, source = Some("database")))
implicitWebUiProps = getWebUIPropsPairs.map(webUIPropsPairs=>WebUiPropsCommons(webUIPropsPairs._1, webUIPropsPairs._2, webUiPropsId = Some("default"), source = Some("config")))
result = what match {
case "database" =>
// Return only database props

View File

@ -0,0 +1,72 @@
package code.api.v7_0_0
import cats.data.{Kleisli, OptionT}
import cats.effect._
import cats.implicits._
import code.api.util.{APIUtil, CustomJsonFormats}
import code.api.v4_0_0.JSONFactory400
import code.bankconnectors.Connector
import com.openbankproject.commons.util.{ApiVersion, ScannedApiVersion}
import net.liftweb.json.Formats
import net.liftweb.json.JsonAST.prettyRender
import net.liftweb.json.Extraction
import org.http4s._
import org.http4s.dsl.io._
import org.typelevel.vault.Key
import scala.concurrent.Future
import scala.language.{higherKinds, implicitConversions}
object Http4s700 {
type HttpF[A] = OptionT[IO, A]
implicit val formats: Formats = CustomJsonFormats.formats
implicit def convertAnyToJsonString(any: Any): String = prettyRender(Extraction.decompose(any))
val apiVersion: ScannedApiVersion = ApiVersion.v7_0_0
val apiVersionString: String = apiVersion.toString
case class CallContext(userId: String, requestId: String)
import cats.effect.unsafe.implicits.global
val callContextKey: Key[CallContext] = Key.newKey[IO, CallContext].unsafeRunSync()
object CallContextMiddleware {
def withCallContext(routes: HttpRoutes[IO]): HttpRoutes[IO] =
Kleisli[HttpF, Request[IO], Response[IO]] { req: Request[IO] =>
val callContext = CallContext(userId = "example-user", requestId = java.util.UUID.randomUUID().toString)
val updatedAttributes = req.attributes.insert(callContextKey, callContext)
val updatedReq = req.withAttributes(updatedAttributes)
routes(updatedReq)
}
}
val v700Services: HttpRoutes[IO] = HttpRoutes.of[IO] {
case req @ GET -> Root / "obp" / `apiVersionString` / "root" =>
import com.openbankproject.commons.ExecutionContext.Implicits.global
val callContext = req.attributes.lookup(callContextKey).get.asInstanceOf[CallContext]
Ok(IO.fromFuture(IO(
for {
_ <- Future() // Just start async call
} yield {
convertAnyToJsonString(
JSONFactory700.getApiInfoJSON(apiVersion, s"Hello, ${callContext.userId}! Your request ID is ${callContext.requestId}.")
)
}
)))
case req @ GET -> Root / "obp" / `apiVersionString` / "banks" =>
import com.openbankproject.commons.ExecutionContext.Implicits.global
Ok(IO.fromFuture(IO(
for {
(banks, callContext) <- code.api.util.NewStyle.function.getBanks(None)
} yield {
convertAnyToJsonString(JSONFactory400.createBanksJson(banks))
}
)))
}
val wrappedRoutesV700Services: HttpRoutes[IO] = CallContextMiddleware.withCallContext(v700Services)
}

View File

@ -0,0 +1,72 @@
package code.api.v7_0_0
import code.api.Constant
import code.api.util.APIUtil
import code.api.util.ErrorMessages.MandatoryPropertyIsNotSet
import code.api.v4_0_0.{EnergySource400, HostedAt400, HostedBy400}
import code.util.Helper.MdcLoggable
import com.openbankproject.commons.util.ApiVersion
import net.liftweb.util.Props
object JSONFactory700 extends MdcLoggable {
// Get git commit from build info
lazy val gitCommit: String = {
val commit = try {
Props.get("git.commit.id", "unknown")
} catch {
case _: Throwable => "unknown"
}
commit
}
case class APIInfoJsonV700(
version: String,
version_status: String,
git_commit: String,
stage: String,
connector: String,
hostname: String,
local_identity_provider: String,
hosted_by: HostedBy400,
hosted_at: HostedAt400,
energy_source: EnergySource400,
resource_docs_requires_role: Boolean,
message: String
)
def getApiInfoJSON(apiVersion: ApiVersion, message: String): APIInfoJsonV700 = {
val organisation = APIUtil.getPropsValue("hosted_by.organisation", "TESOBE")
val email = APIUtil.getPropsValue("hosted_by.email", "contact@tesobe.com")
val phone = APIUtil.getPropsValue("hosted_by.phone", "+49 (0)30 8145 3994")
val organisationWebsite = APIUtil.getPropsValue("organisation_website", "https://www.tesobe.com")
val hostedBy = new HostedBy400(organisation, email, phone, organisationWebsite)
val organisationHostedAt = APIUtil.getPropsValue("hosted_at.organisation", "")
val organisationWebsiteHostedAt = APIUtil.getPropsValue("hosted_at.organisation_website", "")
val hostedAt = HostedAt400(organisationHostedAt, organisationWebsiteHostedAt)
val organisationEnergySource = APIUtil.getPropsValue("energy_source.organisation", "")
val organisationWebsiteEnergySource = APIUtil.getPropsValue("energy_source.organisation_website", "")
val energySource = EnergySource400(organisationEnergySource, organisationWebsiteEnergySource)
val connector = code.api.Constant.CONNECTOR.openOrThrowException(s"$MandatoryPropertyIsNotSet. The missing prop is `connector` ")
val resourceDocsRequiresRole = APIUtil.getPropsAsBoolValue("resource_docs_requires_role", false)
APIInfoJsonV700(
version = apiVersion.vDottedApiVersion,
version_status = "BLEEDING_EDGE",
git_commit = gitCommit,
connector = connector,
hostname = Constant.HostName,
stage = System.getProperty("run.mode"),
local_identity_provider = Constant.localIdentityProvider,
hosted_by = hostedBy,
hosted_at = hostedAt,
energy_source = energySource,
resource_docs_requires_role = resourceDocsRequiresRole,
message = message
)
}
}

View File

@ -10,7 +10,7 @@ import org.apache.commons.lang3.StringUtils.uncapitalize
import java.io.File
import java.net.URL
import java.util.Date
import scala.jdk.CollectionConverters.enumerationAsScalaIteratorConverter
import scala.collection.JavaConverters._
import scala.language.postfixOps
import scala.reflect.runtime.universe._
import scala.reflect.runtime.{universe => ru}

View File

@ -45,7 +45,7 @@ import net.liftweb.util.Helpers._
import sh.ory.hydra.api.AdminApi
import sh.ory.hydra.model.{AcceptConsentRequest, RejectRequest}
import scala.jdk.CollectionConverters.seqAsJavaListConverter
import scala.collection.JavaConverters._
import scala.xml.NodeSeq

View File

@ -43,7 +43,7 @@ import org.apache.commons.lang3.StringUtils
import org.codehaus.jackson.map.ObjectMapper
import scala.collection.immutable.{List, ListMap}
import scala.jdk.CollectionConverters.seqAsJavaListConverter
import scala.collection.JavaConverters._
import scala.xml.{Text, Unparsed}
class ConsumerRegistration extends MdcLoggable {

View File

@ -35,7 +35,15 @@ object ClassScanUtils {
*/
def getSubTypeObjects[T:TypeTag]: List[T] = {
val clazz = ReflectUtils.typeTagToClass[T]
finder.getClasses().filter(_.implements(clazz.getName)).map(_.name).map(companion[T](_)).toList
val classes = try {
finder.getClasses().toList
} catch {
case _: UnsupportedOperationException =>
// ASM version is too old for some class files (e.g. requires ASM7). In that case,
// skip scanned APIs instead of failing the whole application.
Seq.empty
}
classes.filter(_.implements(clazz.getName)).map(_.name).map(companion[T](_)).toList
}
/**
@ -43,14 +51,22 @@ object ClassScanUtils {
* @param predict check whether include this type in the result
* @return all fit type names
*/
def findTypes(predict: ClassInfo => Boolean): List[String] = finder.getClasses()
.filter(predict)
.map(it => {
val name = it.name
if(name.endsWith("$")) name.substring(0, name.length - 1)
else name
}) //some companion type name ends with $, it added by scalac, should remove from class name
.toList
def findTypes(predict: ClassInfo => Boolean): List[String] = {
val classes = try {
finder.getClasses().toList
} catch {
case _: UnsupportedOperationException =>
Seq.empty
}
classes
.filter(predict)
.map(it => {
val name = it.name
if(name.endsWith("$")) name.substring(0, name.length - 1)
else name
}) //some companion type name ends with $, it added by scalac, should remove from class name
.toList
}
/**
* get given class exists jar Files
@ -71,7 +87,13 @@ object ClassScanUtils {
*/
def getMappers(packageName:String = ""): Seq[ClassInfo] = {
val mapperInterface = "net.liftweb.mapper.LongKeyedMapper"
val infos = finder.getClasses().filter(it => it.interfaces.contains(mapperInterface))
val classes = try {
finder.getClasses().toList
} catch {
case _: UnsupportedOperationException =>
Seq.empty
}
val infos = classes.filter(it => it.interfaces.contains(mapperInterface))
if(StringUtils.isNoneBlank()) {
infos.filter(classInfo => classInfo.name.startsWith(packageName))
} else {

View File

@ -15,7 +15,7 @@ import sh.ory.hydra.model.OAuth2Client
import sh.ory.hydra.{ApiClient, Configuration}
import scala.collection.immutable.List
import scala.jdk.CollectionConverters.{mapAsJavaMapConverter, seqAsJavaListConverter}
import scala.collection.JavaConverters._
object HydraUtil extends MdcLoggable{

View File

@ -77,6 +77,7 @@ class WebUiProps extends WebUiPropsT with LongKeyedMapper[WebUiProps] with IdPK
override def webUiPropsId: Option[String] = Option(WebUiPropsId.get)
override def name: String = Name.get
override def value: String = Value.get
override def source: Option[String] = Some("database")
}
object WebUiProps extends WebUiProps with LongKeyedMetaMapper[WebUiProps] {

View File

@ -9,12 +9,13 @@ trait WebUiPropsT {
def webUiPropsId: Option[String]
def name: String
def value: String
def source: Option[String]
}
case class WebUiPropsCommons(name: String,
value: String,
webUiPropsId: Option[String] = None,
source: String = "database") extends WebUiPropsT with JsonFieldReName
source: Option[String] = None) extends WebUiPropsT with JsonFieldReName
object WebUiPropsCommons extends Converter[WebUiPropsT, WebUiPropsCommons]

View File

@ -25,6 +25,7 @@ TESOBE (http://www.tesobe.com/)
*/
package code.api.v4_0_0
import code.api.util.APIUtil
import code.api.util.ApiRole._
import code.api.v4_0_0.APIMethods400.Implementations4_0_0
import code.entitlement.Entitlement
@ -33,7 +34,7 @@ import com.openbankproject.commons.model.ListResult
import com.openbankproject.commons.util.{ApiVersion, ScannedApiVersion}
import org.scalatest.Tag
import scala.jdk.CollectionConverters.collectionAsScalaIterableConverter
import scala.collection.JavaConverters._
class GetScannedApiVersionsTest extends V400ServerSetup {
/**
@ -46,8 +47,63 @@ class GetScannedApiVersionsTest extends V400ServerSetup {
object VersionOfApi extends Tag(ApiVersion.v4_0_0.toString)
object ApiEndpoint extends Tag(nameOf(Implementations4_0_0.getScannedApiVersions))
feature("test props-api_disabled_versions, Get all scanned API versions should works") {
scenario("We get all the scanned API versions with disabled versions filtered out", ApiEndpoint, VersionOfApi) {
// api_disabled_versions=[OBPv3.0.0,BGv1.3]
setPropsValues("api_disabled_versions"-> "[OBPv3.0.0,BGv1.3]")
Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, CanCreateSystemLevelDynamicEntity.toString)
When("We make a request v4.0.0")
val request = (v4_0_0_Request / "api" / "versions").GET
val response = makeGetRequest(request)
Then("We should get a 200")
response.code should equal(200)
val listResult = response.body.extract[ListResult[List[ScannedApiVersion]]]
val responseApiVersions = listResult.results
val scannedApiVersions = ApiVersion.allScannedApiVersion.asScala.toList.filter { version =>
version.urlPrefix.trim.nonEmpty && APIUtil.versionIsAllowed(version)
}
responseApiVersions should equal(scannedApiVersions)
// Verify that disabled versions are not included
responseApiVersions.exists(_.fullyQualifiedVersion == "OBPv3.0.0") should equal(false)
responseApiVersions.exists(_.fullyQualifiedVersion == "BGv1.3") should equal(false)
}
}
feature("test props-api_enabled_versions, Get all scanned API versions should works") {
scenario("We get all the scanned API versions with disabled versions filtered out", ApiEndpoint, VersionOfApi) {
// api_enabled_versions=[OBPv2.2.0,OBPv3.0.0,UKv2.0]
setPropsValues("api_enabled_versions"-> "[OBPv2.2.0,OBPv3.0.0,UKv2.0]")
Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, CanCreateSystemLevelDynamicEntity.toString)
When("We make a request v4.0.0")
val request = (v4_0_0_Request / "api" / "versions").GET
val response = makeGetRequest(request)
Then("We should get a 200")
response.code should equal(200)
val listResult = response.body.extract[ListResult[List[ScannedApiVersion]]]
val responseApiVersions = listResult.results
val scannedApiVersions = ApiVersion.allScannedApiVersion.asScala.toList.filter { version =>
version.urlPrefix.trim.nonEmpty && APIUtil.versionIsAllowed(version)
}
responseApiVersions should equal(scannedApiVersions)
// Verify that disabled versions are not included
responseApiVersions.exists(_.fullyQualifiedVersion == "OBPv2.2.0") should equal(true)
responseApiVersions.exists(_.fullyQualifiedVersion == "OBPv3.0.0") should equal(true)
responseApiVersions.exists(_.fullyQualifiedVersion == "UKv2.0") should equal(true)
}
}
feature("Get all scanned API versions should works") {
scenario("We get all the scanned API versions", ApiEndpoint, VersionOfApi) {
Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, CanCreateSystemLevelDynamicEntity.toString)
When("We make a request v4.0.0")

View File

@ -23,6 +23,7 @@ object ApiShortVersions extends Enumeration {
val `v5.0.0` = Value("v5.0.0")
val `v5.1.0` = Value("v5.1.0")
val `v6.0.0` = Value("v6.0.0")
val `v7.0.0` = Value("v7.0.0")
val `dynamic-endpoint` = Value("dynamic-endpoint")
val `dynamic-entity` = Value("dynamic-entity")
}
@ -114,6 +115,7 @@ object ApiVersion {
val v5_0_0 = ScannedApiVersion(urlPrefix,ApiStandards.obp.toString,ApiShortVersions.`v5.0.0`.toString)
val v5_1_0 = ScannedApiVersion(urlPrefix,ApiStandards.obp.toString,ApiShortVersions.`v5.1.0`.toString)
val v6_0_0 = ScannedApiVersion(urlPrefix,ApiStandards.obp.toString,ApiShortVersions.`v6.0.0`.toString)
val v7_0_0 = ScannedApiVersion(urlPrefix,ApiStandards.obp.toString,ApiShortVersions.`v7.0.0`.toString)
val `dynamic-endpoint` = ScannedApiVersion(urlPrefix,ApiStandards.obp.toString,ApiShortVersions.`dynamic-endpoint`.toString)
val `dynamic-entity` = ScannedApiVersion(urlPrefix,ApiStandards.obp.toString,ApiShortVersions.`dynamic-entity`.toString)
@ -131,6 +133,7 @@ object ApiVersion {
v5_0_0 ::
v5_1_0 ::
v6_0_0 ::
v7_0_0 ::
`dynamic-endpoint` ::
`dynamic-entity`::
Nil

71
obp-http4s-runner/pom.xml Normal file
View File

@ -0,0 +1,71 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.tesobe</groupId>
<artifactId>obp-parent</artifactId>
<version>1.10.1</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>obp-http4s-runner</artifactId>
<packaging>jar</packaging>
<name>OBP Http4s Runner</name>
<dependencies>
<!-- Use obp-api compiled classes as a normal JAR -->
<dependency>
<groupId>com.tesobe</groupId>
<artifactId>obp-api</artifactId>
<version>${project.version}</version>
<classifier>classes</classifier>
<type>jar</type>
<exclusions>
<!-- Exclude old classutil version if present -->
<exclusion>
<groupId>org.clapper</groupId>
<artifactId>classutil_${scala.version}</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- Explicitly include classutil 1.5.1 to override any transitive dependencies -->
<dependency>
<groupId>org.clapper</groupId>
<artifactId>classutil_${scala.version}</artifactId>
<version>1.5.1</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>3.6.0</version>
<configuration>
<archive>
<manifest>
<mainClass>bootstrap.http4s.Http4sServer</mainClass>
</manifest>
</archive>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
<appendAssemblyId>false</appendAssemblyId>
<finalName>obp-http4s-runner</finalName>
</configuration>
<executions>
<execution>
<id>make-fat-jar</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@ -15,6 +15,7 @@
<akka.version>2.5.32</akka.version>
<avro.version>1.8.2</avro.version>
<lift.version>3.5.0</lift.version>
<http4s.version>0.23.30</http4s.version>
<jetty.version>9.4.50.v20221201</jetty.version>
<obp-ri.version>2016.11-RC6-SNAPSHOT</obp-ri.version>
<!-- Common plugin settings -->
@ -34,6 +35,7 @@
<modules>
<module>obp-commons</module>
<module>obp-api</module>
<module>obp-http4s-runner</module>
</modules>
<repositories>
@ -126,7 +128,7 @@
<plugin>
<groupId>net.alchim31.maven</groupId>
<artifactId>scala-maven-plugin</artifactId>
<version>4.3.1</version>
<version>4.8.1</version>
<configuration>
<scalaVersion>${scala.compiler}</scalaVersion>
<charset>${project.build.sourceEncoding}</charset>
@ -143,6 +145,7 @@
<arg>-verbose</arg>
<arg>-deprecation</arg>
-->
<arg>-Ypartial-unification</arg>
</args>
</configuration>
<executions>