Merge pull request #1991 from OpenBankProject/deploy

regular code cycle
This commit is contained in:
tesobe-daniel 2021-12-13 13:23:23 +01:00 committed by GitHub
commit 59da6287d7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
177 changed files with 15860 additions and 15106 deletions

View File

@ -8,7 +8,7 @@
<groupId>com.tesobe</groupId>
<artifactId>obp-parent</artifactId>
<relativePath>../pom.xml</relativePath>
<version>1.9.0</version>
<version>1.10.0</version>
</parent>
<artifactId>obp-api</artifactId>
<packaging>war</packaging>
@ -210,7 +210,7 @@
<dependency>
<groupId>org.elasticsearch</groupId>
<artifactId>elasticsearch</artifactId>
<version>6.8.13</version>
<version>6.8.17</version>
</dependency>
<dependency>
<groupId>com.sksamuel.elastic4s</groupId>
@ -476,6 +476,13 @@
<artifactId>iban4j</artifactId>
<version>3.2.1</version>
</dependency>
<!--macro cache-->
<dependency>
<groupId>com.github.oldbig</groupId>
<artifactId>macmemo</artifactId>
<version>0.6-OBP-SNAPSHOT</version>
</dependency>
</dependencies>
<build>

View File

@ -8446,7 +8446,7 @@
"request_url": "/obp/v4.0.0/management/dynamic_entities/DYNAMIC_ENTITY_ID",
"summary": "更新DynamicEntity",
"description": "<p>更新DynamicEntity。 </p><p>身份验证是强制性的</p><p>更新一个DynamicEntity更新完成后将更改相应的CRUD端点。 </p><p>当前的支持文件类型如下: <br /> [字符串,数字,整数,布尔值] </p>",
"description_markdown": "Update a DynamicEntity.\n\n\nAuthentication is Mandatory\n\nUpdate one DynamicEntity, after update finished, the corresponding CRUD endpoints will be changed.\n\nCurrent support field types as follow:\n[string, number, integer, boolean]\n\n",
"description_markdown": "Update a DynamicEntity.\n\n\nAuthentication is Mandatory\n\nUpdate one DynamicEntity, after update finished, the corresponding CRUD endpoints will be changed.\n\nThe following field types are as supported:\n[string, number, integer, boolean]\n\n",
"example_request_body": {
"FooBar": {
"required": [
@ -11963,7 +11963,7 @@
"request_url": "/obp/v4.0.0/management/dynamic_entities",
"summary": "创建动态实体",
"description": "<p>创建一个DynamicEntity。 </p><p>身份验证是强制性的</p><p>创建一个DynamicEntity创建成功后将自动生成相应的CRUD端点</p><p>当前的支持文件类型如下: <br /> [字符串,数字,整数,布尔值] </p>",
"description_markdown": "Create a DynamicEntity.\n\n\nAuthentication is Mandatory\n\nCreate one DynamicEntity, after created success, the corresponding CRUD endpoints will be generated automatically\n\nCurrent support field types as follow:\n[string, number, integer, boolean]\n\n",
"description_markdown": "Create a DynamicEntity.\n\n\nAuthentication is Mandatory\n\nCreate a DynamicEntity. If creation is successful, the corresponding POST, GET, PUT and DELETE (Create, Read, Update, Delete or CRUD for short) endpoints will be generated automatically\n\nThe following field types are as supported:\n[string, number, integer, boolean]\n\n",
"example_request_body": {
"FooBar": {
"required": [

View File

@ -372,4 +372,10 @@ invalid.username=Invalid Username: \
2) Usernames MUST be between 8 and 100 characters long \
3) Usernames MUST NOT start with _ or . \
4) Usernames MUST NOT contain __ or ._ or ._ or .. \
5) Usernames MUST NOT end with _ or .
5) Usernames MUST NOT end with _ or . \
6) Any valid email address is allowed as the Username
your.username.is.not.unique = Your username is not unique. Please enter a different one.
# Those 2 messages must have the same output in order to prevent leakage of information
user.invitation.is.already.finished = Looks like the invitation link is invalid. Still need help? Please send us a message using API Playground Support.
your.secret.link.is.not.valid = Looks like the invitation link is invalid. Still need help? Please send us a message using API Playground Support.

View File

@ -387,6 +387,9 @@ webui_obp_cli_url = https://github.com/OpenBankProject/OBP-CLI
# API Tester URL, change to your instance
webui_api_tester_url = https://apitester.openbankproject.com
# API Hola app URL, change to your instance
webui_api_hola_url = #
@ -409,6 +412,8 @@ webui_sandbox_introduction=
# NOTE: if this is *not set*, the Introduction Button on /index will link the user to /introduction
# If this is set, the Introduction Button will link the user to the URL defined above. (but the page /introduction will still exist so you might want to populate webui_sandbox_introduction anyway.)
webui_api_documentation_url = https://github.com/OpenBankProject/OBP-API/wiki
# now, we hava a new props for the bottom ` API Documentation` menu.
#webui_api_documentation_bottom_url =https://github.com/OpenBankProject/OBP-API/wiki
###################################################################################
# Link for SDKs
@ -418,6 +423,11 @@ webui_sdks_url = https://github.com/OpenBankProject/OBP-API/wiki/OAuth-Client-SD
# then OBP-API can show the content to the HomePage `SDK Showcases`. Please check it over the sandbox homepage first.
#webui_featured_sdks_external_link = https://static.openbankproject.com/obp/sdks.html
# the external html page for the FAQ section. the default link is the obp one. Please following the div to modify it. This link should be anonymous access.
# then OBP-API can show the content to the HomePage `FAQs`. Please check it over the sandbox homepage first.
#webui_main_faq_external_link = /main-faq.html
# Text about data in FAQ
webui_faq_data_text = We use real data and customer profiles which have been anonymized.
@ -428,7 +438,7 @@ webui_faq_url = https://openbankproject.com/faq/
webui_faq_email = contact@openbankproject.com
# Link to support platform
webui_support_platform_url = https://slack.openbankproject.com/
webui_support_platform_url = https://chat.openbankproject.com
# Link to Direct Login glossary on api explorer
webui_direct_login_documentation_url =
@ -460,6 +470,9 @@ webui_main_partners=[\
# Prefix for all page titles (note the trailing space!)
webui_page_title_prefix = Open Bank Project:
# set the favicon icon
#webui_favicon_link_url =/favicon.ico
# Main style sheet. Add your own if need be.
webui_main_style_sheet = /media/css/website.css
@ -469,6 +482,12 @@ webui_override_style_sheet =
## Link to agree to Terms & Conditions, shown on signup page
webui_agree_terms_url =
## The Support Email, shown in the bottom page
#webui_support_email=contact@openbankproject.com
## Link to Privacy Policy, shown in the bottom page
#webui_privacy_policy_url =
# URL to load (additional) vendor support content
#webui_vendor_support_content_url = http://127.0.0.1:8080/plain.html
@ -608,6 +627,16 @@ super_admin_user_ids=USER_ID1,USER_ID2,
## Note: The email address used for login must match one
## registered on OBP localy.
# openid_connect.enabled=true
# openid_connect.show_tokens=false
# Response mode
# possible values: query, fragment, form_post, query.jwt, fragment.jwt, form_post.jwt, jwt
# openid_connect.response_mode=form_post
# Response type
# possible values: "code", "id_token", "code id_token"
# openid_connect.response_type=code
# Scope
# possible values: "openid email profile", "openid email", "openid"
# openid_connect.scope=openid email profile
# First identity provider
# openid_connect_1.button_text = Google
# openid_connect_1.client_secret=OYdWujJlU7fFOW_NXzPlDI4T
@ -622,7 +651,7 @@ super_admin_user_ids=USER_ID1,USER_ID2,
# openid_connect_2.button_text = name of 2nd provider
# openid_connect_2.client_secret=OYdWujJlU7fFOW_NXzPlDI4T
# openid_connect_2.client_id=883773244832-s4hi72j0rble0iiivq1gn09k7vvptdci.apps.googleusercontent.com
# openid_connect_2.callback_url=http://127.0.0.1:8080/auth/openid-connect/callback
# openid_connect_2.callback_url=http://127.0.0.1:8080/auth/openid-connect/callback-2
# openid_connect_2.endpoint.authorization=https://accounts.google.com/o/oauth2/v2/auth
# openid_connect_2.endpoint.userinfo=https://openidconnect.googleapis.com/v1/userinfo
# openid_connect_2.endpoint.token=https://oauth2.googleapis.com/token
@ -635,8 +664,8 @@ consumers_enabled_by_default=true
# Autocomplete for login form has to be explicitly set
autocomplete_at_login_form_enabled=false
# Skip Auth User Email validation (defaults to true)
#authUser.skipEmailValidation=true
# Skip Auth User Email validation (defaults to false as of 29 June 2021)
#authUser.skipEmailValidation=false
# If using Kafka but want to get counterparties from OBP, set this to true
#get_counterparties_from_OBP_DB=true
@ -653,11 +682,21 @@ autocomplete_at_login_form_enabled=false
# Enable/Disable Gateway communication at all
# In case isn't defined default value is false
# allow_gateway_login=false
# Define secret used to validate JWT token
# jwt.token_secret=your-at-least-256-bit-secret-token
# Define comma separated list of allowed IP addresses
# gateway.host=127.0.0.1
# Define secret used to validate JWT token
# gateway.token_secret=secret
# -------------------------------------- Gateway login --
# -- DAuth --------------------------------------
# Enable/Disable DAuth communication at all
# In case isn't defined default value is false
allow_dauth=false
# Define public key used to validate JWT token
jwt.public_key_rsa=path-to-the-pem-file
# Define comma separated list of allowed IP addresses
dauth.host=127.0.0.1
# -------------------------------------- DAuth--
# Disable akka (Remote storage not possible)
@ -776,7 +815,9 @@ featured_apis=elasticSearchWarehouseV300
# If Rest Connector do not get the response in the following seconds, it will throw the error message back.
# This props can be omitted, the default value is 59. It should be less than Nginx timeout.
# rest2019_connector_timeout = 59
# If set it to `true`, it will add the x-sign (SHA256WithRSA) into each the rest connector http calls,
# please add the name of the field for the UserAuthContext and/or link to other documentation..
#rest_connector_sends_x-sign_header=false
# -- Scopes -----------------------------------------------------
@ -1019,11 +1060,13 @@ default_auth_context_update_request_key=CUSTOMER_NUMBER
# User Invitation Time To Live
# user_invitation.ttl.seconds=86400
# User Invitation is mandatory in case of onboarding a user
# user_invitation.mandatory=false
# User (Developer) Invitation
webui_post_user_invitation_submit_button_value=Register as a Developer
webui_post_user_invitation_privacy_conditions_value=Privacy conditions..
webui_post_user_invitation_terms_and_conditions_value=Terms and Conditions..
webui_privacy_policy=
webui_terms_and_conditions=
webui_post_user_invitation_terms_and_conditions_checkbox_value=I agree to the above Developer Terms and Conditions
webui_developer_user_invitation_email_html_text=<!DOCTYPE html>\
@ -1061,3 +1104,5 @@ webui_developer_user_invitation_email_html_text=<!DOCTYPE html>\
</body>\
</html>
# List of countries where consent is not required for the collection of personal data
personal_data_collection_consent_country_waiver_list = Austria, Belgium, Bulgaria, Croatia, Republic of Cyprus, Czech Republic, Denmark, Estonia, Finland, France, Germany, Greece, Hungary, Ireland, Italy, Latvia, Lithuania, Luxembourg, Malta, Netherlands, Poland, Portugal, Romania, Slovakia, Slovenia, Spain, Sweden, England, Scotland, Wales, Northern Ireland

View File

@ -29,7 +29,6 @@ package bootstrap.liftweb
import java.io.{File, FileInputStream}
import java.util.stream.Collectors
import java.util.{Locale, TimeZone}
import code.CustomerDependants.MappedCustomerDependant
import code.DynamicData.DynamicData
import code.DynamicEndpoint.DynamicEndpoint
@ -100,10 +99,13 @@ import code.scheduler.DatabaseDriverScheduler
import code.scope.{MappedScope, MappedUserScope}
import code.apicollectionendpoint.ApiCollectionEndpoint
import code.apicollection.ApiCollection
import code.bankattribute.BankAttribute
import code.connectormethod.ConnectorMethod
import code.dynamicMessageDoc.DynamicMessageDoc
import code.dynamicResourceDoc.DynamicResourceDoc
import code.endpointMapping.EndpointMapping
import code.endpointTag.EndpointTag
import code.productfee.ProductFee
import code.snippet.{OAuthAuthorisation, OAuthWorkedThanks}
import code.socialmedia.MappedSocialMedia
import code.standingorders.StandingOrder
@ -129,6 +131,7 @@ import code.webuiprops.WebUiProps
import com.openbankproject.commons.model.ErrorMessage
import com.openbankproject.commons.util.Functions.Implicits._
import com.openbankproject.commons.util.{ApiVersion, Functions}
import javax.mail.{Authenticator, PasswordAuthentication}
import javax.mail.internet.MimeMessage
import net.liftweb.common._
@ -267,6 +270,20 @@ class Boot extends MdcLoggable {
}
}
}
implicit val formats = CustomJsonFormats.formats
LiftRules.statelessDispatch.prepend {
case _ if tryo(DB.use(DefaultConnectionIdentifier){ conn => conn}.isClosed).isEmpty =>
Props.mode match {
case Props.RunModes.Development =>
() =>
Full(
JsonResponse(
Extraction.decompose(ErrorMessage(code = 500, message = s"${ErrorMessages.DatabaseConnectionClosedError}")),
500
)
)
}
}
logger.info("Mapper database info: " + Migration.DbFunction.mapperDatabaseInfo())
@ -386,13 +403,14 @@ class Boot extends MdcLoggable {
enableVersionIfAllowed(ApiVersion.v4_0_0)
enableVersionIfAllowed(ApiVersion.b1)
def enableAPIs: LiftRules#RulesSeq[DispatchPF] = {
def enableOpenIdConnectApis = {
// OpenIdConnect endpoint and validator
if(APIUtil.getPropsAsBoolValue("openid_connect.enabled", false)) {
if (APIUtil.getPropsAsBoolValue("openid_connect.enabled", false)) {
LiftRules.dispatch.append(OpenIdConnect)
}
}
def enableAPIs: LiftRules#RulesSeq[DispatchPF] = {
//OAuth API call
LiftRules.statelessDispatch.append(OAuthHandshake)
@ -407,10 +425,20 @@ class Boot extends MdcLoggable {
}
APIUtil.getPropsValue("server_mode", "apis,portal") match {
case mode if mode == "portal" =>
case mode if mode == "apis" => enableAPIs
case mode if mode.contains("apis") && mode.contains("portal") => enableAPIs
case _ => enableAPIs
// Instance runs as the portal only
case mode if mode == "portal" => // Callback url in case of OpenID Connect MUST be enabled at portal side
enableOpenIdConnectApis
// Instance runs as the APIs only
case mode if mode == "apis" =>
enableAPIs
// Instance runs as the portal and APIs as well
// This is default mode
case mode if mode.contains("apis") && mode.contains("portal") =>
enableAPIs
enableOpenIdConnectApis
// Failure
case _ =>
throw new RuntimeException("The props server_mode`is not properly set. Allowed cases: { server_mode=portal, server_mode=apis, server_mode=apis,portal }")
}
@ -493,7 +521,12 @@ class Boot extends MdcLoggable {
Menu("Dummy user tokens", "Get Dummy user tokens") / "dummy-user-tokens" >> AuthUser.loginFirst,
Menu("Validate OTP", "Validate OTP") / "otp" >> AuthUser.loginFirst,
Menu("User Information", "User Information") / "user-information",
Menu("User Invitation", "User Invitation") / "user-invitation",
Menu("User Invitation Info", "User Invitation Info") / "user-invitation-info",
Menu("User Invitation Invalid", "User Invitation Invalid") / "user-invitation-invalid",
Menu("Terms and Conditions", "Terms and Conditions") / "terms-and-conditions",
Menu("Privacy Policy", "Privacy Policy") / "privacy-policy",
// Menu.i("Metrics") / "metrics", //TODO: allow this page once we can make the account number anonymous in the URL
Menu.i("OAuth") / "oauth" / "authorize", //OAuth authorization page
Menu.i("Consent") / "consent" >> AuthUser.loginFirst,//OAuth consent page
@ -569,8 +602,7 @@ class Boot extends MdcLoggable {
Mailer.devModeSend.default.set( (m : MimeMessage) => {
logger.info("Would have sent email if not in dev mode: " + m.getContent)
})
implicit val formats = CustomJsonFormats.formats
LiftRules.exceptionHandler.prepend{
case(_, r, e) if DB.use(DefaultConnectionIdentifier){ conn => conn}.isClosed => {
logger.error("Exception being returned to browser when processing " + r.uri.toString, e)
@ -870,6 +902,7 @@ object ToSchemify {
MappedCustomerAttribute,
MappedTransactionAttribute,
MappedCardAttribute,
BankAttribute,
RateLimiting,
MappedCustomerDependant,
AttributeDefinition
@ -928,7 +961,9 @@ object ToSchemify {
AuthenticationTypeValidation,
ConnectorMethod,
DynamicResourceDoc,
DynamicMessageDoc
DynamicMessageDoc,
EndpointTag,
ProductFee
)++ APIBuilder_Connector.allAPIBuilderModels
// start grpc server

View File

@ -120,10 +120,11 @@ object GatewayLogin extends RestHelper with MdcLoggable {
def validateJwtToken(token: String): Box[PayloadOfJwtJSON] = {
APIUtil.getPropsAsBoolValue("jwt.use.ssl", false) match {
case true =>
logger.debug("validateJwtToken says: verifying jwt token with RSA: " + token)
val claim = CertificateUtil.decryptJwtWithRsa(token)
Box(parse(claim.toString).extractOpt[PayloadOfJwtJSON])
case false =>
logger.debug("validateJwtToken says: verifying jwt token: " + token)
logger.debug("validateJwtToken says: verifying jwt token with HmacProtection: " + token)
logger.debug(CertificateUtil.verifywtWithHmacProtection(token).toString)
CertificateUtil.verifywtWithHmacProtection(token) match {
case true =>
@ -262,7 +263,8 @@ object GatewayLogin extends RestHelper with MdcLoggable {
email = None,
userId = None,
createdByUserInvitationId = None,
company = None
company = None,
lastMarketingAgreementSignedDate = None
)
} match {
case Full(u) =>
@ -480,7 +482,7 @@ object GatewayLogin extends RestHelper with MdcLoggable {
val payload = GatewayLogin.parseJwt(parameters)
payload match {
case Full(payload) =>
val username = getFieldFromPayloadJson(payload, "username")
val username = getFieldFromPayloadJson(payload, "login_user_name")
logger.debug("username: " + username)
Users.users.vend.getUserByProviderId(provider = gateway, idGivenByProvider = username)
case _ =>

View File

@ -76,6 +76,8 @@ object OAuth2Login extends RestHelper with MdcLoggable {
Google.applyRules(value, cc)
} else if (Yahoo.isIssuer(value)) {
Yahoo.applyRules(value, cc)
} else if (Azure.isIssuer(value)) {
Azure.applyRules(value, cc)
} else {
Hydra.applyRules(value, cc)
}
@ -94,6 +96,8 @@ object OAuth2Login extends RestHelper with MdcLoggable {
Google.applyRulesFuture(value, cc)
} else if (Yahoo.isIssuer(value)) {
Yahoo.applyRulesFuture(value, cc)
} else if (Azure.isIssuer(value)) {
Azure.applyRulesFuture(value, cc)
} else {
Hydra.applyRulesFuture(value, cc)
}
@ -283,7 +287,8 @@ object OAuth2Login extends RestHelper with MdcLoggable {
email = getClaim(name = "email", idToken = idToken),
userId = None,
createdByUserInvitationId = None,
company = None
company = None,
lastMarketingAgreementSignedDate = None
)
}
}
@ -395,6 +400,18 @@ object OAuth2Login extends RestHelper with MdcLoggable {
override def wellKnownOpenidConfiguration: URI = new URI("https://login.yahoo.com/.well-known/openid-configuration")
override def urlOfJwkSets: Box[String] = checkUrlOfJwkSets(identityProvider = yahoo)
def isIssuer(jwt: String): Boolean = isIssuer(jwtToken=jwt, identityProvider = yahoo)
}
object Azure extends OAuth2Util {
val microsoft = "microsoft"
/**
* OpenID Connect Discovery.
* Yahoo exposes OpenID Connect discovery documents ( https://YOUR_DOMAIN/.well-known/openid-configuration ).
* These can be used to automatically configure applications.
*/
override def wellKnownOpenidConfiguration: URI = new URI("https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration")
override def urlOfJwkSets: Box[String] = checkUrlOfJwkSets(identityProvider = microsoft)
def isIssuer(jwt: String): Boolean = isIssuer(jwtToken=jwt, identityProvider = microsoft)
}
}

View File

@ -32,11 +32,12 @@ import code.api.Constant._
import code.api.OAuthHandshake._
import code.api.builder.AccountInformationServiceAISApi.APIMethods_AccountInformationServiceAISApi
import code.api.util.APIUtil._
import code.api.util.ErrorMessages.attemptedToOpenAnEmptyBox
import code.api.util.ErrorMessages.{InvalidDAuthHeaderToken, UserIsDeleted, UsernameHasBeenLocked, attemptedToOpenAnEmptyBox}
import code.api.util._
import code.api.v3_0_0.APIMethods300
import code.api.v3_1_0.APIMethods310
import code.api.v4_0_0.APIMethods400
import code.loginattempts.LoginAttempt
import code.model.dataAccess.AuthUser
import code.util.Helper.MdcLoggable
import com.alibaba.ttl.TransmittableThreadLocal
@ -260,7 +261,24 @@ trait OBPRestHelper extends RestHelper with MdcLoggable {
}
}
def failIfBadAuthorizationHeader(rd: Option[ResourceDoc])(fn: CallContext => Box[JsonResponse]) : JsonResponse = {
def failIfBadAuthorizationHeader(rd: Option[ResourceDoc])(function: CallContext => Box[JsonResponse]) : JsonResponse = {
// Check is it a user deleted or locked
def fn(callContext: CallContext): Box[JsonResponse] = {
callContext.user match {
case Full(u) => // There is a user. Check it.
if(u.isDeleted.getOrElse(false)) {
Failure(UserIsDeleted) // The user is DELETED.
} else {
LoginAttempt.userIsLocked(u.name) match {
case true => Failure(UsernameHasBeenLocked) // The user is LOCKED.
case false => function(callContext) // All good
}
}
case _ => // There is no user. Just forward the result.
function(callContext)
}
}
val authorization = S.request.map(_.header("Authorization")).flatten
val directLogin: Box[String] = S.request.map(_.header("DirectLogin")).flatten
val body: Box[String] = getRequestBody(S.request)
@ -382,7 +400,45 @@ trait OBPRestHelper extends RestHelper with MdcLoggable {
case _ =>
Failure(ErrorMessages.GatewayLoginUnknownError)
}
} else {
}
else if (APIUtil.getPropsAsBoolValue("allow_dauth", false) && hasDAuthHeader(cc.requestHeaders)) {
logger.info("allow_dauth-getRemoteIpAddress: " + remoteIpAddress )
APIUtil.getPropsValue("dauth.host") match {
case Full(h) if h.split(",").toList.exists(_.equalsIgnoreCase(remoteIpAddress) == true) => // Only addresses from white list can use this feature
val dauthToken = DAuth.getDAuthToken(cc.requestHeaders)
dauthToken match {
case Some(token :: _) =>
val payload = DAuth.parseJwt(token)
payload match {
case Full(payload) =>
DAuth.getOrCreateResourceUser(payload: String, Some(cc)) match {
case Full((u, callContext)) => // Authentication is successful
val consumer = DAuth.getConsumerByConsumerKey(payload)//TODO, need to verify the key later.
val jwt = DAuth.createJwt(payload)
val callContextUpdated = ApiSession.updateCallContext(DAuthResponseHeader(Some(jwt)), callContext)
fn(callContextUpdated.map( callContext =>callContext.copy(user = Full(u), consumer = consumer)).getOrElse(callContext.getOrElse(cc).copy(user = Full(u), consumer = consumer)))
case Failure(msg, t, c) => Failure(msg, t, c)
case _ => Full(errorJsonResponse(payload))
}
case Failure(msg, t, c) =>
Failure(msg, t, c)
case _ =>
Failure(ErrorMessages.DAuthUnknownError)
}
case _ =>
Failure(InvalidDAuthHeaderToken)
}
case Full(h) if h.split(",").toList.exists(_.equalsIgnoreCase(remoteIpAddress) == false) => // All other addresses will be rejected
Failure(ErrorMessages.DAuthWhiteListAddresses)
case Empty =>
Failure(ErrorMessages.DAuthHostPropertyMissing) // There is no dauth.host in props file
case Failure(msg, t, c) =>
Failure(msg, t, c)
case _ =>
Failure(ErrorMessages.DAuthUnknownError)
}
}
else {
fn(cc)
}
}

View File

@ -10,7 +10,9 @@ object ResourceDocs140 extends OBPRestHelper with ResourceDocsAPIMethods with Md
val versionStatus = "STABLE"
val routes = List(
ImplementationsResourceDocs.getResourceDocsObp,
ImplementationsResourceDocs.getResourceDocsSwagger
ImplementationsResourceDocs.getBankLevelDynamicResourceDocsObp,
ImplementationsResourceDocs.getResourceDocsSwagger,
// ImplementationsResourceDocs.getStaticResourceDocsObp
)
routes.foreach(route => {
oauthServe(apiPrefix{route})
@ -24,7 +26,9 @@ object ResourceDocs200 extends OBPRestHelper with ResourceDocsAPIMethods with Md
val versionStatus = "DRAFT"
val routes = List(
ImplementationsResourceDocs.getResourceDocsObp,
ImplementationsResourceDocs.getResourceDocsSwagger
ImplementationsResourceDocs.getResourceDocsSwagger,
ImplementationsResourceDocs.getBankLevelDynamicResourceDocsObp,
// ImplementationsResourceDocs.getStaticResourceDocsObp
)
routes.foreach(route => {
oauthServe(apiPrefix{route})
@ -38,7 +42,9 @@ object ResourceDocs210 extends OBPRestHelper with ResourceDocsAPIMethods with Md
val versionStatus = "DRAFT"
val routes = List(
ImplementationsResourceDocs.getResourceDocsObp,
ImplementationsResourceDocs.getResourceDocsSwagger
ImplementationsResourceDocs.getResourceDocsSwagger,
ImplementationsResourceDocs.getBankLevelDynamicResourceDocsObp,
// ImplementationsResourceDocs.getStaticResourceDocsObp
)
routes.foreach(route => {
oauthServe(apiPrefix{route})
@ -51,7 +57,9 @@ object ResourceDocs220 extends OBPRestHelper with ResourceDocsAPIMethods with Md
val versionStatus = "DRAFT"
val routes = List(
ImplementationsResourceDocs.getResourceDocsObp,
ImplementationsResourceDocs.getResourceDocsSwagger
ImplementationsResourceDocs.getResourceDocsSwagger,
ImplementationsResourceDocs.getBankLevelDynamicResourceDocsObp,
// ImplementationsResourceDocs.getStaticResourceDocsObp
)
routes.foreach(route => {
oauthServe(apiPrefix{route})
@ -64,7 +72,9 @@ object ResourceDocs300 extends OBPRestHelper with ResourceDocsAPIMethods with Md
val versionStatus = "DRAFT"
val routes = List(
ImplementationsResourceDocs.getResourceDocsObp,
ImplementationsResourceDocs.getResourceDocsSwagger
ImplementationsResourceDocs.getResourceDocsSwagger,
ImplementationsResourceDocs.getBankLevelDynamicResourceDocsObp,
// ImplementationsResourceDocs.getStaticResourceDocsObp
)
routes.foreach(route => {
oauthServe(apiPrefix{route})
@ -76,7 +86,9 @@ object ResourceDocs300 extends OBPRestHelper with ResourceDocsAPIMethods with Md
val versionStatus = "DRAFT"
val routes = List(
ImplementationsResourceDocs.getResourceDocsObp,
ImplementationsResourceDocs.getResourceDocsSwagger
ImplementationsResourceDocs.getResourceDocsSwagger,
ImplementationsResourceDocs.getBankLevelDynamicResourceDocsObp,
// ImplementationsResourceDocs.getStaticResourceDocsObp
)
routes.foreach(route => {
oauthServe(apiPrefix {
@ -89,8 +101,10 @@ object ResourceDocs300 extends OBPRestHelper with ResourceDocsAPIMethods with Md
val version: ApiVersion = ApiVersion.v4_0_0 // = "4.0.0" // We match other api versions so API explorer can easily use the path.
val versionStatus = "BLEEDING-EDGE"
val routes = List(
ImplementationsResourceDocs.getResourceDocsObp,
ImplementationsResourceDocs.getResourceDocsSwagger
ImplementationsResourceDocs.getResourceDocsObpV400,
ImplementationsResourceDocs.getResourceDocsSwagger,
ImplementationsResourceDocs.getBankLevelDynamicResourceDocsObp,
// ImplementationsResourceDocs.getStaticResourceDocsObp
)
routes.foreach(route => {
oauthServe(apiPrefix {

View File

@ -1,11 +1,11 @@
package code.api.ResourceDocs1_4_0
import java.util.UUID.randomUUID
import code.api.OBPRestHelper
import code.api.builder.OBP_APIBuilder
import code.api.cache.Caching
import code.api.util.APIUtil._
import code.api.util.ApiRole.{canReadDynamicResourceDocsAtOneBank, canReadResourceDoc, canReadStaticResourceDoc}
import code.api.util.ApiTag._
import code.api.util.ExampleValue.endpointMappingRequestBodyExample
import code.api.util.{APIUtil, _}
@ -19,7 +19,7 @@ import code.api.v4_0_0.{APIMethods400, OBPAPI4_0_0}
import code.apicollectionendpoint.MappedApiCollectionEndpointsProvider
import code.util.Helper.MdcLoggable
import com.github.dwickern.macros.NameOf.nameOf
import com.openbankproject.commons.model.{ListResult, User}
import com.openbankproject.commons.model.{BankId, ListResult, User}
import com.openbankproject.commons.model.enums.ContentParam.{ALL, DYNAMIC, STATIC}
import com.openbankproject.commons.model.enums.LanguageParam._
import com.openbankproject.commons.model.enums.{ContentParam, LanguageParam}
@ -34,7 +34,6 @@ import net.liftweb.json._
import net.liftweb.util.Helpers.tryo
import net.liftweb.util.Props
import java.util.concurrent.ConcurrentHashMap
import code.api.util.NewStyle.HttpCode
import code.util.Helper
@ -167,9 +166,13 @@ trait ResourceDocsAPIMethods extends MdcLoggable with APIMethods220 with APIMeth
requestedApiVersion match
{
// only `obp` standard show the `localResouceDocs`
case version: ScannedApiVersion if(version.apiStandard == obp.toString) => activePlusLocalResourceDocs ++= localResourceDocs
// all other standards only show their own apis.
case _ => ;
case version: ScannedApiVersion
if(version.apiStandard == obp.toString && version==ApiVersion.v4_0_0) =>
activePlusLocalResourceDocs ++= localResourceDocs.filterNot(_.partialFunctionName == nameOf(getResourceDocsObp))
case version: ScannedApiVersion
if(version.apiStandard == obp.toString && version!=ApiVersion.v4_0_0) =>
activePlusLocalResourceDocs ++= localResourceDocs.filterNot(_.partialFunctionName == nameOf(getResourceDocsObpV400))
case _ => ; // all other standards only show their own apis.
}
@ -211,8 +214,8 @@ trait ResourceDocsAPIMethods extends MdcLoggable with APIMethods220 with APIMeth
//implicit val scalaCache = ScalaCache(GuavaCache(underlyingGuavaCache))
// if upload DynamicEntity, will generate corresponding endpoints, when current cache timeout, the new endpoints will be shown.
// so if you want the new generated endpoints shown timely, set this value to a small number, or set to a big number
val getDynamicResourceDocsTTL : Int = APIUtil.getPropsValue(s"dynamicResourceDocsObp.cache.ttl.seconds", "0").toInt
val getStaticResourceDocsTTL : Int = APIUtil.getPropsValue(s"staticResourceDocsObp.cache.ttl.seconds", "0").toInt
val getDynamicResourceDocsTTL : Int = APIUtil.getPropsValue(s"dynamicResourceDocsObp.cache.ttl.seconds", "3600").toInt
val getStaticResourceDocsTTL : Int = APIUtil.getPropsValue(s"staticResourceDocsObp.cache.ttl.seconds", "86400").toInt
/**
*
@ -224,7 +227,11 @@ trait ResourceDocsAPIMethods extends MdcLoggable with APIMethods220 with APIMeth
*/
private def getStaticResourceDocsObpCached(requestedApiVersion : ApiVersion,
resourceDocTags: Option[List[ResourceDocTag]],
partialFunctionNames: Option[List[String]]
partialFunctionNames: Option[List[String]],
languageParam: Option[LanguageParam],
contentParam: Option[ContentParam],
cacheModifierParam: Option[String],
isVersion4OrHigher:Boolean
) : Box[JValue] = {
/**
* Please note that "var cacheKey = (randomUUID().toString, randomUUID().toString, randomUUID().toString)"
@ -237,7 +244,7 @@ trait ResourceDocsAPIMethods extends MdcLoggable with APIMethods220 with APIMeth
Caching.memoizeSyncWithProvider (Some(cacheKey.toString())) (getStaticResourceDocsTTL second) {
logger.debug(s"Generating OBP Resource Docs requestedApiVersion is $requestedApiVersion")
val resourceDocJson = resourceDocsToResourceDocJson(getResourceDocsList(requestedApiVersion), resourceDocTags, partialFunctionNames)
val resourceDocJson = resourceDocsToResourceDocJson(getResourceDocsList(requestedApiVersion), resourceDocTags, partialFunctionNames, isVersion4OrHigher)
resourceDocJson.map(resourceDocsJsonToJsonResponse)
}
}
@ -253,7 +260,11 @@ trait ResourceDocsAPIMethods extends MdcLoggable with APIMethods220 with APIMeth
*/
private def getAllResourceDocsObpCached(requestedApiVersion : ApiVersion,
resourceDocTags: Option[List[ResourceDocTag]],
partialFunctionNames: Option[List[String]]
partialFunctionNames: Option[List[String]],
languageParam: Option[LanguageParam],
contentParam: Option[ContentParam],
cacheModifierParam: Option[String],
isVersion4OrHigher:Boolean
) : Box[JValue] = {
/**
* Please note that "var cacheKey = (randomUUID().toString, randomUUID().toString, randomUUID().toString)"
@ -294,7 +305,7 @@ trait ResourceDocsAPIMethods extends MdcLoggable with APIMethods220 with APIMeth
val allDocs = staticDocs.map( _ ++ filteredDocs)
val resourceDocJson = resourceDocsToResourceDocJson(allDocs, resourceDocTags, partialFunctionNames)
val resourceDocJson = resourceDocsToResourceDocJson(allDocs, resourceDocTags, partialFunctionNames, isVersion4OrHigher)
resourceDocJson.map(resourceDocsJsonToJsonResponse)
}
}
@ -302,12 +313,18 @@ trait ResourceDocsAPIMethods extends MdcLoggable with APIMethods220 with APIMeth
private def getResourceDocsObpDynamicCached(requestedApiVersion : ApiVersion,
resourceDocTags: Option[List[ResourceDocTag]],
partialFunctionNames: Option[List[String]]
partialFunctionNames: Option[List[String]],
languageParam: Option[LanguageParam],
contentParam: Option[ContentParam],
cacheModifierParam: Option[String],
bankId:Option[String],
isVersion4OrHigher:Boolean
): Option[JValue] = {
var cacheKey = (randomUUID().toString, randomUUID().toString, randomUUID().toString)
CacheKeyFromArguments.buildCacheKey {
Caching.memoizeSyncWithProvider (Some(cacheKey.toString())) (getDynamicResourceDocsTTL second) {
val dynamicDocs = (DynamicEntityHelper.doc ++ DynamicEndpointHelper.doc ++ DynamicEndpoints.dynamicResourceDocs)
.filter(rd => if (bankId.isDefined) rd.createdByBankId == bankId else true)
.filter(rd => rd.implementedInApiVersion == requestedApiVersion)
.map(it => it.specifiedUrl match {
case Some(_) => it
@ -333,7 +350,7 @@ trait ResourceDocsAPIMethods extends MdcLoggable with APIMethods220 with APIMeth
case None => dynamicDocs
}
val resourceDocJson = resourceDocsToResourceDocJson(Some(filteredDocs), resourceDocTags, partialFunctionNames)
val resourceDocJson = resourceDocsToResourceDocJson(Some(filteredDocs), resourceDocTags, partialFunctionNames, isVersion4OrHigher)
resourceDocJson.map(resourceDocsJsonToJsonResponse)
}}}
@ -341,14 +358,15 @@ trait ResourceDocsAPIMethods extends MdcLoggable with APIMethods220 with APIMeth
private def resourceDocsToResourceDocJson(rd: Option[List[ResourceDoc]],
resourceDocTags: Option[List[ResourceDocTag]],
partialFunctionNames: Option[List[String]]): Option[ResourceDocsJson] =
partialFunctionNames: Option[List[String]],
isVersion4OrHigher:Boolean): Option[ResourceDocsJson] =
for {
resourceDocs <- rd
} yield {
// Filter
val rdFiltered = ResourceDocsAPIMethodsUtil.filterResourceDocs(resourceDocs, resourceDocTags, partialFunctionNames)
// Format the data as json
JSONFactory1_4_0.createResourceDocsJson(rdFiltered)
JSONFactory1_4_0.createResourceDocsJson(rdFiltered, isVersion4OrHigher)
}
private val getChineseVersionResourceDocs : Box[JsonResponse] = {
@ -374,53 +392,24 @@ trait ResourceDocsAPIMethods extends MdcLoggable with APIMethods220 with APIMeth
"GET",
"/dummy",
"Test Resource Doc.",
"""
|I am only a test Resource Doc
|
|It's turtles all the way down.
|
|#This should be H1
|
|##This should be H2
|
|###This should be H3
|
|####This should be H4
|
|Here is a list with two items:
|
|* One
|* Two
|
|There are underscores by them selves _
|
|There are _underscores_ around a word
|
|There are underscores_in_words
|
|There are 'underscores_in_words_inside_quotes'
|
|There are (underscores_in_words_in_brackets)
|
|_etc_...""",
"""I am only a test Resource Doc""",
emptyObjectJson,
emptyObjectJson,
UnknownError :: Nil,
List(apiTagDocumentation))
val exampleResourceDocsJson = JSONFactory1_4_0.createResourceDocsJson(List(exampleResourceDoc))
val exampleResourceDocsJson = JSONFactory1_4_0.createResourceDocsJson(List(exampleResourceDoc), false)
val exampleResourceDocsJsonV400 = JSONFactory1_4_0.createResourceDocsJson(List(exampleResourceDoc), true)
localResourceDocs += ResourceDoc(
getResourceDocsObp,
implementedInApiVersion,
"getResourceDocsObp",
"GET",
"/resource-docs/API_VERSION/obp",
"Get Resource Docs.",
def getResourceDocsDescription(isBankLevelResourceDoc: Boolean) = {
val endpointBankIdPath = if (isBankLevelResourceDoc) "/banks/BANK_ID" else ""
s"""Get documentation about the RESTful resources on this server including example bodies for POST and PUT requests.
|
|This is the native data format used to document OBP endpoints. Each endpoint has a Resource Doc (a Scala case class) defined in the source code.
@ -444,15 +433,18 @@ trait ResourceDocsAPIMethods extends MdcLoggable with APIMethods220 with APIMeth
|
| You can filter with api-collection-id, but api-collection-id can not be used with others together. If api-collection-id is used in URL, it will ignore all other parameters.
|
| You can easily pass the cache, use different value for cache-modifier, eg: ?cache-modifier= 123
|
|See the Resource Doc endpoint for more information.
|
|Following are more examples:
|${getObpApiRoot}/v3.1.0/resource-docs/v3.1.0/obp
|${getObpApiRoot}/v3.1.0/resource-docs/v3.1.0/obp?tags=Account,Bank
|${getObpApiRoot}/v3.1.0/resource-docs/v3.1.0/obp?functions=getBanks,bankById
|${getObpApiRoot}/v3.1.0/resource-docs/v4.0.0/obp?language=zh
|${getObpApiRoot}/v3.1.0/resource-docs/v4.0.0/obp?content=static,dynamic,all
|${getObpApiRoot}/v3.1.0/resource-docs/v4.0.0/obp?api-collection-id=4e866c86-60c3-4268-a221-cb0bbf1ad221
|${getObpApiRoot}/v4.0.0$endpointBankIdPath/resource-docs/v4.0.0/obp
|${getObpApiRoot}/v4.0.0$endpointBankIdPath/resource-docs/v4.0.0/obp?tags=Account,Bank
|${getObpApiRoot}/v4.0.0$endpointBankIdPath/resource-docs/v4.0.0/obp?functions=getBanks,bankById
|${getObpApiRoot}/v4.0.0$endpointBankIdPath/resource-docs/v4.0.0/obp?language=zh
|${getObpApiRoot}/v4.0.0$endpointBankIdPath/resource-docs/v4.0.0/obp?content=static,dynamic,all
|${getObpApiRoot}/v4.0.0$endpointBankIdPath/resource-docs/v4.0.0/obp?api-collection-id=4e866c86-60c3-4268-a221-cb0bbf1ad221
|${getObpApiRoot}/v4.0.0$endpointBankIdPath/resource-docs/v4.0.0/obp?cache-modifier=3141592653
|
|<ul>
|<li> operation_id is concatenation of "v", version and function and should be unique (used for DOM element IDs etc. maybe used to link to source code) </li>
@ -463,57 +455,188 @@ trait ResourceDocsAPIMethods extends MdcLoggable with APIMethods220 with APIMeth
|<li> summary is a short description inline with the swagger terminology. </li>
|<li> description may contain html markup (generated from markdown on the server).</li>
|</ul>
""",
"""
}
localResourceDocs += ResourceDoc(
getResourceDocsObp,
implementedInApiVersion,
"getResourceDocsObp",
"GET",
"/resource-docs/API_VERSION/obp",
"Get Resource Docs.",
getResourceDocsDescription(false),
emptyObjectJson,
emptyObjectJson, //exampleResourceDocsJson
exampleResourceDocsJson,
UnknownError :: Nil,
List(apiTagDocumentation, apiTagApi)
List(apiTagDocumentation, apiTagApi),
Some(List(canReadResourceDoc))
)
val resourceDocsRequireRole = APIUtil.getPropsAsBoolValue("resource_docs_requires_role", false)
def resourceDocsRequireRole = APIUtil.getPropsAsBoolValue("resource_docs_requires_role", false)
// Provides resource documents so that API Explorer (or other apps) can display API documentation
// Note: description uses html markup because original markdown doesn't easily support "_" and there are multiple versions of markdown.
def getResourceDocsObp : OBPEndpoint = {
case "resource-docs" :: requestedApiVersionString :: "obp" :: Nil JsonGet _ => {
val (tags, partialFunctions, languageParam, contentParam, apiCollectionIdParam) = ResourceDocsAPIMethodsUtil.getParams()
cc =>
val (tags, partialFunctions, languageParam, contentParam, apiCollectionIdParam, cacheModifierParam) = ResourceDocsAPIMethodsUtil.getParams()
cc =>
getApiLevelResourceDocs(cc,requestedApiVersionString, tags, partialFunctions, languageParam, contentParam, apiCollectionIdParam, cacheModifierParam, false, false)
}
}
localResourceDocs += ResourceDoc(
getResourceDocsObpV400,
implementedInApiVersion,
nameOf(getResourceDocsObpV400),
"GET",
"/resource-docs/API_VERSION/obp",
"Get Resource Docs",
getResourceDocsDescription(false),
emptyObjectJson,
exampleResourceDocsJsonV400,
UnknownError :: Nil,
List(apiTagDocumentation, apiTagApi),
Some(List(canReadResourceDoc))
)
def getResourceDocsObpV400 : OBPEndpoint = {
case "resource-docs" :: requestedApiVersionString :: "obp" :: Nil JsonGet _ => {
val (tags, partialFunctions, languageParam, contentParam, apiCollectionIdParam, cacheModifierParam) = ResourceDocsAPIMethodsUtil.getParams()
cc =>
getApiLevelResourceDocs(cc,requestedApiVersionString, tags, partialFunctions, languageParam, contentParam, apiCollectionIdParam, cacheModifierParam, true, false)
}
}
// localResourceDocs += ResourceDoc(
// getStaticResourceDocsObp,
// implementedInApiVersion,
// nameOf(getStaticResourceDocsObp),
// "GET",
// "/static-resource-docs/API_VERSION/obp",
// "Get Static Resource Docs",
// getResourceDocsDescription(false),
// emptyObjectJson,
// exampleResourceDocsJsonV400,
// UnknownError :: Nil,
// List(apiTagDocumentation, apiTagApi),
// Some(List(canReadStaticResourceDoc))
// )
//
// def getStaticResourceDocsObp : OBPEndpoint = {
// case "static-resource-docs" :: requestedApiVersionString :: "obp" :: Nil JsonGet _ => {
// val (tags, partialFunctions, languageParam, contentParam, apiCollectionIdParam, cacheModifierParam) = ResourceDocsAPIMethodsUtil.getParams()
// cc =>
// getApiLevelResourceDocs(
// cc,requestedApiVersionString,
// tags,
// partialFunctions,
// languageParam,
// Some(ContentParam.STATIC) ,//Note: here it set to default STATIC value.
// apiCollectionIdParam,
// cacheModifierParam,
// true,
// true
// )
// }
// }
//API level just mean, this response will be forward to liftweb directly.
private def getApiLevelResourceDocs(
cc: CallContext,
requestedApiVersionString: String,
tags: Option[List[ResourceDocTag]],
partialFunctions: Option[List[String]],
languageParam: Option[LanguageParam],
contentParam: Option[ContentParam],
apiCollectionIdParam: Option[String],
cacheModifierParam: Option[String],
isVersion4OrHigher: Boolean,
isStaticResource: Boolean,
) = {
for {
(u: Box[User], callContext: Option[CallContext]) <- resourceDocsRequireRole match {
case false => anonymousAccess(cc)
case true => authenticatedAccess(cc) // If set resource_docs_requires_role=true, we need check the authentication
}
_ <- resourceDocsRequireRole match {
case false => Future()
case true => // If set resource_docs_requires_role=true, we need check the the roles as well
if(isStaticResource)
NewStyle.function.hasAtLeastOneEntitlement(failMsg = UserHasMissingRoles + canReadStaticResourceDoc.toString)("", u.map(_.userId).getOrElse(""), ApiRole.canReadStaticResourceDoc :: Nil, cc.callContext)
else
NewStyle.function.hasAtLeastOneEntitlement(failMsg = UserHasMissingRoles + canReadResourceDoc.toString)("", u.map(_.userId).getOrElse(""), ApiRole.canReadResourceDoc :: Nil, cc.callContext)
}
requestedApiVersion <- NewStyle.function.tryons(s"$InvalidApiVersionString $requestedApiVersionString", 400, callContext) {ApiVersionUtils.valueOf(requestedApiVersionString)}
_ <- Helper.booleanToFuture(s"$ApiVersionNotSupported $requestedApiVersionString", 400, callContext)(versionIsAllowed(requestedApiVersion))
json <- languageParam match {
case Some(ZH) => Future(getChineseVersionResourceDocs)
case _ if (apiCollectionIdParam.isDefined) =>
val operationIds = MappedApiCollectionEndpointsProvider.getApiCollectionEndpoints(apiCollectionIdParam.getOrElse("")).map(_.operationId).map(getObpFormatOperationId)
val resourceDocs = ResourceDoc.getResourceDocs(operationIds)
val resourceDocsJson = JSONFactory1_4_0.createResourceDocsJson(resourceDocs, isVersion4OrHigher)
val resourceDocsJsonJValue = Full(resourceDocsJsonToJsonResponse(resourceDocsJson))
Future(resourceDocsJsonJValue.map(successJsonResponse(_)))
case _ =>
contentParam match {
case Some(DYNAMIC) =>
val dynamicDocs: Box[JValue] = getResourceDocsObpDynamicCached(requestedApiVersion, tags, partialFunctions, languageParam, contentParam, cacheModifierParam, None, isVersion4OrHigher)
Future(dynamicDocs.map(successJsonResponse(_)))
case Some(STATIC) =>
val staticDocs: Box[JValue] = getStaticResourceDocsObpCached(requestedApiVersion, tags, partialFunctions, languageParam, contentParam, cacheModifierParam, isVersion4OrHigher)
Future(staticDocs.map(successJsonResponse(_)))
case _ =>
val docs: Box[JValue] = getAllResourceDocsObpCached(requestedApiVersion, tags, partialFunctions, languageParam, contentParam, cacheModifierParam, isVersion4OrHigher)
Future(docs.map(successJsonResponse(_)))
}
}
} yield {
(json, HttpCode.`200`(callContext))
}
}
localResourceDocs += ResourceDoc(
getBankLevelDynamicResourceDocsObp,
implementedInApiVersion,
nameOf(getBankLevelDynamicResourceDocsObp),
"GET",
"/banks/BANK_ID/resource-docs/API_VERSION/obp",
"Get Bank Level Dynamic Resource Docs.",
getResourceDocsDescription(true),
emptyObjectJson,
exampleResourceDocsJson,
UnknownError :: Nil,
List(apiTagDocumentation, apiTagApi),
Some(List(canReadDynamicResourceDocsAtOneBank))
)
// Provides resource documents so that API Explorer (or other apps) can display API documentation
// Note: description uses html markup because original markdown doesn't easily support "_" and there are multiple versions of markdown.
def getBankLevelDynamicResourceDocsObp : OBPEndpoint = {
case "banks" :: bankId :: "resource-docs" :: requestedApiVersionString :: "obp" :: Nil JsonGet _ => {
val (tags, partialFunctions, languageParam, contentParam, apiCollectionIdParam, cacheModifierParam) = ResourceDocsAPIMethodsUtil.getParams()
cc =>
for {
(u: Box[User], callContext: Option[CallContext]) <- resourceDocsRequireRole match {
(u: Box[User], callContext: Option[CallContext]) <- resourceDocsRequireRole match {
case false => anonymousAccess(cc)
case true => authenticatedAccess(cc) // If set resource_docs_requires_role=true, we need check the authentication
}
_ <- resourceDocsRequireRole match {
(_, callContext) <- NewStyle.function.getBank(BankId(bankId), Option(cc))
_ <- resourceDocsRequireRole match {
case false => Future()
case true => // If set resource_docs_requires_role=true, we need check the the roles as well
Future(NewStyle.function.ownEntitlement("", u.map(_.userId).getOrElse(""), ApiRole.canReadResourceDoc, cc.callContext))
NewStyle.function.hasAtLeastOneEntitlement(failMsg = UserHasMissingRoles + ApiRole.canReadDynamicResourceDocsAtOneBank.toString)(
bankId, u.map(_.userId).getOrElse(""), ApiRole.canReadDynamicResourceDocsAtOneBank::Nil, cc.callContext
)
}
requestedApiVersion <- NewStyle.function.tryons(s"$InvalidApiVersionString $requestedApiVersionString", 400, callContext) {ApiVersionUtils.valueOf(requestedApiVersionString)}
_ <- Helper.booleanToFuture(s"$ApiVersionNotSupported $requestedApiVersionString", 400, callContext)(versionIsAllowed(requestedApiVersion))
json <- languageParam match {
case Some(ZH) => Future(getChineseVersionResourceDocs)
case _ if(apiCollectionIdParam.isDefined) =>
val operationIds = MappedApiCollectionEndpointsProvider.getApiCollectionEndpoints(apiCollectionIdParam.getOrElse("")).map(_.operationId).map(getObpFormatOperationId)
val resourceDocs = ResourceDoc.getResourceDocs(operationIds)
val resourceDocsJson = JSONFactory1_4_0.createResourceDocsJson(resourceDocs)
val resourceDocsJsonJValue = Full(resourceDocsJsonToJsonResponse(resourceDocsJson))
Future(resourceDocsJsonJValue.map(successJsonResponse(_)))
case _ =>
contentParam match {
case Some(DYNAMIC) =>
val dynamicDocs: Box[JValue] = getResourceDocsObpDynamicCached(requestedApiVersion, tags, partialFunctions)
Future(dynamicDocs.map(successJsonResponse(_)))
case Some(STATIC) =>
val staticDocs: Box[JValue] = getStaticResourceDocsObpCached(requestedApiVersion, tags, partialFunctions)
Future(staticDocs.map(successJsonResponse(_)))
case _ =>
val docs: Box[JValue] = getAllResourceDocsObpCached(requestedApiVersion, tags, partialFunctions)
Future(docs.map(successJsonResponse(_)))
}
json <- NewStyle.function.tryons(s"$UnknownError Can not create dynamic resource docs.", 400, callContext) {
getResourceDocsObpDynamicCached(requestedApiVersion, tags, partialFunctions, languageParam, contentParam, cacheModifierParam, Some(bankId), false).map(successJsonResponse(_)).get
}
} yield {
(json, HttpCode.`200`(callContext))
(Full(json), HttpCode.`200`(callContext))
}
}
}
@ -557,7 +680,7 @@ trait ResourceDocsAPIMethods extends MdcLoggable with APIMethods220 with APIMeth
case "resource-docs" :: requestedApiVersionString :: "swagger" :: Nil JsonGet _ => {
cc =>{
for {
(resourceDocTags, partialFunctions, languageParam, contentParam, apiCollectionIdParam) <- tryo(ResourceDocsAPIMethodsUtil.getParams())
(resourceDocTags, partialFunctions, languageParam, contentParam, apiCollectionIdParam, cacheModifierParam) <- tryo(ResourceDocsAPIMethodsUtil.getParams())
requestedApiVersion <- tryo(ApiVersionUtils.valueOf(requestedApiVersionString)) ?~! s"$InvalidApiVersionString Current Version is $requestedApiVersionString"
_ <- booleanToBox(versionIsAllowed(requestedApiVersion), s"$ApiVersionNotSupported Current Version is $requestedApiVersionString")
staticJson <- getResourceDocsSwaggerCached(requestedApiVersionString, resourceDocTags, partialFunctions)
@ -802,7 +925,7 @@ object ResourceDocsAPIMethodsUtil extends MdcLoggable{
case _ => None
}
def getParams() : (Option[List[ResourceDocTag]], Option[List[String]], Option[LanguageParam], Option[ContentParam], Option[String]) = {
def getParams() : (Option[List[ResourceDocTag]], Option[List[String]], Option[LanguageParam], Option[ContentParam], Option[String], Option[String]) = {
val rawTagsParam = S.param("tags")
@ -868,8 +991,13 @@ object ResourceDocsAPIMethodsUtil extends MdcLoggable{
x <- S.param("api-collection-id")
} yield x
logger.info(s"apiCollectionIdParam is $apiCollectionIdParam")
val cacheModifierParam = for {
x <- S.param("cache-modifier")
} yield x
logger.info(s"cacheModifierParam is $cacheModifierParam")
(tags, partialFunctionNames, languageParam, contentParam, apiCollectionIdParam)
(tags, partialFunctionNames, languageParam, contentParam, apiCollectionIdParam, cacheModifierParam)
}
@ -894,7 +1022,7 @@ so the caller must specify any required filtering by catalog explicitly.
for {
rd <- filteredResources1
partialFunctionName <- pfNames
if rd.partialFunctionName.contains(partialFunctionName)
if rd.partialFunctionName.equals(partialFunctionName)
} yield {
rd
}

View File

@ -1,6 +1,7 @@
package code.api.ResourceDocs1_4_0
import java.util.Date
import code.api.Constant._
import code.api.Constant
import code.api.UKOpenBanking.v2_0_0.JSONFactory_UKOpenBanking_200
@ -15,7 +16,7 @@ import code.api.v3_0_0.JSONFactory300.createBranchJsonV300
import code.api.v3_0_0.custom.JSONFactoryCustom300
import code.api.v3_0_0.{LobbyJsonV330, _}
import code.api.v3_1_0.{AccountBalanceV310, AccountsBalancesV310Json, BadLoginStatusJson, ContactDetailsJson, CustomerWithAttributesJsonV310, InviteeJson, ObpApiLoopbackJson, PhysicalCardWithAttributesJsonV310, PutUpdateCustomerEmailJsonV310, _}
import code.api.v4_0_0._
import code.api.v4_0_0.{BankAttributeBankResponseJsonV400, FastFirehoseAccountsJsonV400, PostHistoricalTransactionAtBankJson, _}
import code.api.v3_1_0.{AccountBalanceV310, AccountsBalancesV310Json, BadLoginStatusJson, ContactDetailsJson, InviteeJson, ObpApiLoopbackJson, PhysicalCardWithAttributesJsonV310, PutUpdateCustomerEmailJsonV310, _}
import code.branches.Branches.{Branch, DriveUpString, LobbyString}
import code.consent.ConsentStatus
@ -33,6 +34,7 @@ import com.openbankproject.commons.model.{UserAuthContextUpdateStatus, ViewBasic
import com.openbankproject.commons.util.{ApiVersion, FieldNameApiVersions, ReflectUtils, RequiredArgs, RequiredInfo}
import net.liftweb.json
import java.net.URLEncoder
import code.endpointMapping.EndpointMappingCommons
import scala.collection.immutable.List
@ -50,8 +52,8 @@ object SwaggerDefinitionsJSON {
val license = License(
id = "id",
name ="String"
id = licenseIdExample.value,
name = licenseNameExample.value
)
val routing = Routing(
@ -504,7 +506,8 @@ object SwaggerDefinitionsJSON {
connector = "String",
hosted_by = hostedBy400,
hosted_at = hostedAt400,
energy_source = energySource400
energy_source = energySource400,
resource_docs_requires_role = false
)
val apiInfoJSON = APIInfoJSON(
version = "String",
@ -831,14 +834,23 @@ object SwaggerDefinitionsJSON {
val banksJSON = BanksJSON(
banks = List(bankJSON)
)
val bankAttributeBankResponseJsonV400 = BankAttributeBankResponseJsonV400(
name = nameExample.value,
value = valueExample.value
)
val bankAttributesResponseJson = BankAttributesResponseJson(
list = List(bankAttributeBankResponseJsonV400)
)
val bankJson400 = BankJson400(
id = "gh.29.uk",
short_name = "short_name ",
full_name = "full_name",
logo = "logo",
website = "www.openbankproject.com",
bank_routings = List(bankRoutingJsonV121)
bank_routings = List(bankRoutingJsonV121),
attributes = Some(List(bankAttributeBankResponseJsonV400))
)
val banksJSON400 = BanksJson400(
@ -1196,8 +1208,8 @@ object SwaggerDefinitionsJSON {
hours = "5"
)
val licenseJson = LicenseJsonV140(
id = "5",
name = "TESOBE"
id = licenseIdExample.value,
name = licenseNameExample.value
)
val metaJson = MetaJsonV140(
license = licenseJson
@ -1310,7 +1322,7 @@ object SwaggerDefinitionsJSON {
// Internal data examples (none JSON format).
// Use transform... to convert these to our various json formats for different API versions
val meta: Meta = Meta(license = License (id = "PDDL", name = "Open Data Commons Public Domain Dedication and License ")) // Note the meta is V140
val meta: Meta = Meta(license = License (id = licenseIdExample.value, name = licenseNameExample.value)) // Note the meta is V140
val openingTimesV300 =OpeningTimesV300(
opening_time = "10:00",
closing_time = "18:00")
@ -1691,6 +1703,31 @@ object SwaggerDefinitionsJSON {
username = usernameExample.value,
entitlements = entitlementJSONs
)
val userJsonV400 = UserJsonV400(
user_id = ExampleValue.userIdExample.value,
email = ExampleValue.emailExample.value,
provider_id = providerIdValueExample.value,
provider = providerValueExample.value,
username = usernameExample.value,
entitlements = entitlementJSONs,
views = None,
agreements = None,
is_deleted = false,
last_marketing_agreement_signed_date = Some(DateWithDayExampleObject)
)
val userJsonWithAgreementsV400 = UserJsonV400(
user_id = ExampleValue.userIdExample.value,
email = ExampleValue.emailExample.value,
provider_id = providerIdValueExample.value,
provider = providerValueExample.value,
username = usernameExample.value,
entitlements = entitlementJSONs,
views = None,
agreements = Some(Nil),
is_deleted = false,
last_marketing_agreement_signed_date = Some(DateWithDayExampleObject)
)
val userIdJsonV400 = UserIdJsonV400(
user_id = ExampleValue.userIdExample.value
)
@ -1955,6 +1992,9 @@ object SwaggerDefinitionsJSON {
val usersJsonV200 = UsersJsonV200(
users = List(userJsonV200)
)
val usersJsonV400 = UsersJsonV400(
users = List(userJsonV400)
)
val counterpartiesJSON = CounterpartiesJSON(
counterparties = List(coreCounterpartyJSON)
@ -2436,6 +2476,27 @@ object SwaggerDefinitionsJSON {
host = "dynamic_entity"
)
val endpointTagJson400 = EndpointTagJson400(
tag_name = tagNameExample.value
)
val systemLevelEndpointTagResponseJson400 = SystemLevelEndpointTagResponseJson400(
endpoint_tag_id = endpointTagIdExample.value,
operation_id = operationIdExample.value,
tag_name = tagNameExample.value
)
val bankLevelEndpointTagResponseJson400 = BankLevelEndpointTagResponseJson400(
bank_id = bankIdExample.value,
endpoint_tag_id = endpointTagIdExample.value,
operation_id = operationIdExample.value,
tag_name = tagNameExample.value
)
val mySpaces = MySpaces(
bank_ids = List(bankIdExample.value),
)
val metricsJson = MetricsJson(
metrics = List(metricJson)
)
@ -3105,12 +3166,12 @@ object SwaggerDefinitionsJSON {
)
val moderatedCoreAccountJsonV300 = ModeratedCoreAccountJsonV300(
id = "5995d6a2-01b3-423c-a173-5481df49bdaf",
bank_id= "String",
label= "String",
number= "String",
id = accountIdExample.value,
bank_id = bankIdExample.value,
label= labelExample.value,
number= numberExample.value,
owners = List(userJSONV121),
`type`= "String",
`type`= typeExample.value,
balance = amountOfMoneyJsonV121,
account_routings = List(accountRoutingJsonV121),
account_rules = List(accountRuleJsonV300)
@ -3119,12 +3180,12 @@ object SwaggerDefinitionsJSON {
val moderatedCoreAccountsJsonV300 = ModeratedCoreAccountsJsonV300(List(moderatedCoreAccountJsonV300))
val moderatedFirehoseAccountJsonV400 = ModeratedFirehoseAccountJsonV400(
id = "5995d6a2-01b3-423c-a173-5481df49bdaf",
bank_id= "String",
label= "String",
number= "String",
id = accountIdExample.value,
bank_id = bankIdExample.value,
label= labelExample.value,
number= numberExample.value,
owners = List(userJSONV121),
product_code = "String",
product_code = productCodeExample.value,
balance = amountOfMoneyJsonV121,
account_routings = List(accountRoutingJsonV121),
account_rules = List(accountRuleJsonV300)
@ -3132,6 +3193,23 @@ object SwaggerDefinitionsJSON {
val moderatedFirehoseAccountsJsonV400 = ModeratedFirehoseAccountsJsonV400(List(moderatedFirehoseAccountJsonV400))
val fastFirehoseAccountJsonV400 = FastFirehoseAccountJsonV400(
id = accountIdExample.value,
bank_id = bankIdExample.value,
label = labelExample.value,
number = numberExample.value,
owners = "user_id:b27327a2-a822-41e5-a909-0150da688939,provider:https://finx22openplatform.fintech-galaxy.com,user_name:synth_user_1_54891",
product_code = productCodeExample.value,
balance = amountOfMoneyJsonV121,
account_routings = "bank_id:bisb.com,account_id:c590e38e-847c-466f-9a62-f2ad67daf106",
account_attributes= "type:INTEGER,code:Loan1,value:0," +
"type:STRING,code:Loan1,value:4421.783"
)
val fastFirehoseAccountsJsonV400 = FastFirehoseAccountsJsonV400(
List(fastFirehoseAccountJsonV400)
)
val aggregateMetricsJSONV300 = AggregateMetricJSON(
count = 7076,
average_response_time = 65.21,
@ -3435,14 +3513,53 @@ object SwaggerDefinitionsJSON {
name = "OVERDRAFT_START_DATE",
`type` = "DATE_WITH_DAY",
value = "2012-04-23"
)
val productAttributeJsonV400 = ProductAttributeJsonV400(
name = "OVERDRAFT_START_DATE",
`type` = "DATE_WITH_DAY",
value = "2012-04-23",
is_active = Some(true)
)
val productAttributeResponseJson = ProductAttributeResponseWithoutBankIdJson(
product_code = "saving1",
product_code = productCodeExample.value,
product_attribute_id = "613c83ea-80f9-4560-8404-b9cd4ec42a7f",
name = "OVERDRAFT_START_DATE",
`type` = "DATE_WITH_DAY",
value = "2012-04-23"
)
val productAttributeResponseJsonV400 = ProductAttributeResponseJsonV400(
bank_id = bankIdExample.value,
product_code = productCodeExample.value,
product_attribute_id = "613c83ea-80f9-4560-8404-b9cd4ec42a7f",
name = "OVERDRAFT_START_DATE",
`type` = "DATE_WITH_DAY",
value = "2012-04-23",
is_active = Some(true)
)
val productAttributeResponseWithoutBankIdJsonV400 = ProductAttributeResponseWithoutBankIdJsonV400(
product_code = productCodeExample.value,
product_attribute_id = "613c83ea-80f9-4560-8404-b9cd4ec42a7f",
name = "OVERDRAFT_START_DATE",
`type` = "DATE_WITH_DAY",
value = "2012-04-23",
is_active = Some(true)
)
val bankAttributeJsonV400 = BankAttributeJsonV400(
name = "TAX_ID",
`type` = "INTEGER",
value = "12345678",
is_active = Some(true)
)
val bankAttributeResponseJsonV400 = BankAttributeResponseJsonV400(
bank_id = bankIdExample.value,
bank_attribute_id = "613c83ea-80f9-4560-8404-b9cd4ec42a7f",
name = "OVERDRAFT_START_DATE",
`type` = "DATE_WITH_DAY",
value = "2012-04-23",
is_active = Some(true)
)
val accountAttributeJson = AccountAttributeJson(
name = "OVERDRAFT_START_DATE",
@ -3450,7 +3567,7 @@ object SwaggerDefinitionsJSON {
value = "2012-04-23"
)
val accountAttributeResponseJson = AccountAttributeResponseJson(
product_code = "saving1",
product_code = productCodeExample.value,
account_attribute_id = "613c83ea-80f9-4560-8404-b9cd4ec42a7f",
name = "OVERDRAFT_START_DATE",
`type` = "DATE_WITH_DAY",
@ -3471,14 +3588,14 @@ object SwaggerDefinitionsJSON {
)
val accountApplicationJson = AccountApplicationJson(
product_code = "saveing1",
product_code = productCodeExample.value,
user_id = Some(ExampleValue.userIdExample.value),
customer_id = Some(customerIdExample.value)
)
val accountApplicationResponseJson = AccountApplicationResponseJson (
account_application_id = "gc23a7e2-7dd2-4bdf-a0b4-ae31232a4763",
product_code = "saveing1",
product_code = productCodeExample.value,
user = resourceUserJSON,
customer = customerJsonV310,
date_of_application = DateWithDayExampleObject,
@ -3492,7 +3609,7 @@ object SwaggerDefinitionsJSON {
val productJsonV310 = ProductJsonV310(
bank_id = bankIdExample.value,
code = "product_code",
code = productCodeExample.value,
parent_product_code = "parent",
name = "product name",
category = "category",
@ -3505,12 +3622,11 @@ object SwaggerDefinitionsJSON {
Some(List(productAttributeResponseJson))
)
val productsJsonV310 = ProductsJsonV310(products = List(productJsonV310))
val productCollectionItemJsonV310 = ProductCollectionItemJsonV310(member_product_code = "A")
val productCollectionJsonV310 = ProductCollectionJsonV310(
collection_code = "C",
product_code = "D", items = List(productCollectionItemJsonV310, productCollectionItemJsonV310.copy(member_product_code = "B"))
product_code = productCodeExample.value, items = List(productCollectionItemJsonV310, productCollectionItemJsonV310.copy(member_product_code = "B"))
)
val productCollectionsJsonV310 = ProductCollectionsJsonV310(product_collection = List(productCollectionJsonV310))
@ -3696,7 +3812,7 @@ object SwaggerDefinitionsJSON {
account_id = accountIdExample.value,
user_id = userIdExample.value,
label = labelExample.value,
product_code = accountTypeExample.value,
product_code = productCodeExample.value,
balance = amountOfMoneyJsonV121,
branch_id = branchIdExample.value,
account_routings = List(accountRoutingJsonV121),
@ -3732,7 +3848,7 @@ object SwaggerDefinitionsJSON {
label = "NoneLabel",
number = "123",
owners = List(userJSONV121),
product_code = "OBP",
product_code = productCodeExample.value,
balance = amountOfMoneyJsonV121,
views_available = List(viewJSONV121),
bank_id = bankIdExample.value,
@ -3760,6 +3876,16 @@ object SwaggerDefinitionsJSON {
completed= DateWithSecondsExampleString,
`type`= SANDBOX_TAN.toString,
charge_policy= "SHARED"
)
val postHistoricalTransactionAtBankJson = PostHistoricalTransactionAtBankJson(
from_account_id = "",
to_account_id = "",
value = amountOfMoneyJsonV121,
description = "this is for work",
posted = DateWithSecondsExampleString,
completed= DateWithSecondsExampleString,
`type`= SANDBOX_TAN.toString,
charge_policy= "SHARED"
)
val postHistoricalTransactionResponseJson = PostHistoricalTransactionResponseJson(
@ -3903,7 +4029,7 @@ object SwaggerDefinitionsJSON {
val createAccountRequestJsonV310 = CreateAccountRequestJsonV310(
user_id = userIdExample.value,
label = labelExample.value,
product_code = accountTypeExample.value,
product_code = productCodeExample.value,
balance = amountOfMoneyJsonV121,
branch_id = branchIdExample.value,
account_routings = List(accountRoutingJsonV121)
@ -3962,6 +4088,16 @@ object SwaggerDefinitionsJSON {
)
val postAccountAccessJsonV400 = PostAccountAccessJsonV400(userIdExample.value, PostViewJsonV400(ExampleValue.viewIdExample.value, true))
val postCreateUserAccountAccessJsonV400 = PostCreateUserAccountAccessJsonV400(
usernameExample.value,
s"dauth.${providerExample.value}",
List(PostViewJsonV400(viewIdExample.value, isSystemExample.value.toBoolean))
)
val postCreateUserWithRolesJsonV400 = PostCreateUserWithRolesJsonV400(
usernameExample.value,
s"dauth.${providerExample.value}",
List(createEntitlementJSON)
)
val revokedJsonV400 = RevokedJsonV400(true)
val postRevokeGrantAccountAccessJsonV400 = PostRevokeGrantAccountAccessJsonV400(List("ReadAccountsBasic"))
@ -4062,9 +4198,15 @@ object SwaggerDefinitionsJSON {
val productAttributeDefinitionJsonV400 =
templateAttributeDefinitionJsonV400.copy(category = AttributeCategory.Product.toString)
val bankAttributeDefinitionJsonV400 =
templateAttributeDefinitionJsonV400.copy(category = AttributeCategory.Bank.toString)
val productAttributeDefinitionResponseJsonV400 =
templateAttributeDefinitionResponseJsonV400.copy(category = AttributeCategory.Product.toString)
val bankAttributeDefinitionResponseJsonV400 =
templateAttributeDefinitionResponseJsonV400.copy(category = AttributeCategory.Bank.toString)
val transactionAttributeDefinitionJsonV400 =
templateAttributeDefinitionJsonV400.copy(category = AttributeCategory.Transaction.toString)
@ -4123,9 +4265,9 @@ object SwaggerDefinitionsJSON {
charge = transactionRequestChargeJsonV200
)
val postApiCollectionJson400 = PostApiCollectionJson400(apiCollectionNameExample.value, true)
val postApiCollectionJson400 = PostApiCollectionJson400(apiCollectionNameExample.value, true, Some(descriptionExample.value))
val apiCollectionJson400 = ApiCollectionJson400(apiCollectionIdExample.value, userIdExample.value, apiCollectionNameExample.value, true)
val apiCollectionJson400 = ApiCollectionJson400(apiCollectionIdExample.value, userIdExample.value, apiCollectionNameExample.value, true, descriptionExample.value)
val apiCollectionsJson400 = ApiCollectionsJson400(List(apiCollectionJson400))
val postApiCollectionEndpointJson400 = PostApiCollectionEndpointJson400(operationIdExample.value)
@ -4143,7 +4285,7 @@ object SwaggerDefinitionsJSON {
requestVerb = requestVerbExample.value,
requestUrl = requestUrlExample.value,
summary = dynamicResourceDocSummaryExample.value,
description = dynamicResourceDocdescriptionExample.value,
description = dynamicResourceDocDescriptionExample.value,
exampleRequestBody = Option(json.parse(exampleRequestBodyExample.value)),
successResponseBody = Option(json.parse(successResponseBodyExample.value)),
errorResponseBodies = errorResponseBodiesExample.value,
@ -4298,6 +4440,70 @@ object SwaggerDefinitionsJSON {
)
val atmsJsonV400 = AtmsJsonV400(List(atmJsonV400))
val productFeeValueJsonV400 = ProductFeeValueJsonV400(
currency = currencyExample.value,
amount = 10.12,
frequency = frequencyExample.value,
`type` = typeExample.value
)
val productFeeJsonV400 = ProductFeeJsonV400(
product_fee_id = Some(productFeeIdExample.value),
name = nameExample.value,
is_active = true,
more_info = moreInfoExample.value,
value = productFeeValueJsonV400
)
val productFeeResponseJsonV400 = ProductFeeResponseJsonV400(
bank_id = bankIdExample.value,
product_code = productCodeExample.value,
product_fee_id = productFeeIdExample.value,
name = nameExample.value,
is_active = true,
more_info = moreInfoExample.value,
value = productFeeValueJsonV400
)
val productFeesResponseJsonV400 = ProductFeesResponseJsonV400(List(productFeeResponseJsonV400))
val productJsonV400 = ProductJsonV400(
bank_id = bankIdExample.value,
product_code = productCodeExample.value,
parent_product_code = parentProductCodeExample.value,
name = productNameExample.value,
more_info_url = moreInfoUrlExample.value,
terms_and_conditions_url = termsAndConditionsUrlExample.value,
description = descriptionExample.value,
meta = metaJson,
attributes = Some(List(productAttributeResponseJson)),
fees = Some(List(productFeeJsonV400))
)
val productsJsonV400 = ProductsJsonV400(products = List(productJsonV400))
val putProductJsonV400 = PutProductJsonV400(
parent_product_code = parentProductCodeExample.value,
name = productNameExample.value,
more_info_url = moreInfoUrlExample.value,
terms_and_conditions_url = termsAndConditionsUrlExample.value,
description = descriptionExample.value,
meta = metaJson,
)
val entitlementJsonV400 = EntitlementJsonV400(
entitlement_id = entitlementIdExample.value,
role_name = roleNameExample.value,
bank_id = bankIdExample.value,
user_id = userIdExample.value,
)
val entitlementsJsonV400 = EntitlementsJsonV400(
list = List(entitlementJsonV400)
)
//The common error or success format.
//Just some helper format to use in Json

View File

@ -6,7 +6,9 @@ import net.liftweb.common.Full
import scala.concurrent.Future
import scala.concurrent.duration.Duration
import scala.language.postfixOps
import com.softwaremill.macmemo.{Cache, MemoCacheBuilder, MemoizeParams}
import scala.reflect.runtime.universe._
object Caching {
def memoizeSyncWithProvider[A](cacheKey: Option[String])(ttl: Duration)(f: => A)(implicit m: Manifest[A]): A = {
@ -46,4 +48,43 @@ object Caching {
}
}
/**
* the default MemoCacheBuilder for annotation OBPMemoize
*
* e.g:
*{{{
* import Caching._
*
* @OBPMemoize(ttl = 2 hours, maxSize = 111)
* def hello(name: String, age: Int): Future[String] = ???
*}}}
*/
implicit object OBPCacheBuilder extends MemoCacheBuilder {
override def build[V : TypeTag : Manifest](bucketId: String, params: MemoizeParams): Cache[V] = new Cache[V] {
val ttl = params.expiresAfterMillis
var isFuture = implicitly[TypeTag[V]].tpe <:< typeOf[Future[_]]
var fixedReturnType = false
override def get(key: List[Any], compute: => V): V = {
val cacheKey = bucketId + "_" + key.mkString("_")
if(isFuture) {
val result = memoizeWithProvider(Some(cacheKey))(ttl)(compute.asInstanceOf[Future[Any]])
result.asInstanceOf[V]
} else if(implicitly[TypeTag[V]].tpe =:= typeOf[Any] && !fixedReturnType) {
val result = compute
isFuture = result.isInstanceOf[Future[_]]
fixedReturnType = true
this.get(key, result)
} else {
val result = memoizeSyncWithProvider(Some(cacheKey))(ttl)(compute)
if(result.isInstanceOf[Future[_]]) {
isFuture = true
}
result
}
}
}
}
}

View File

@ -0,0 +1,221 @@
/**
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 code.api
import code.api.JSONFactoryDAuth.PayloadOfJwtJSON
import code.api.util._
import code.consumer.Consumers
import code.model.{Consumer, UserX}
import code.users.Users
import code.util.Helper.MdcLoggable
import com.nimbusds.jwt.JWTClaimsSet
import com.openbankproject.commons.model.User
import net.liftweb.common._
import net.liftweb.http._
import net.liftweb.http.rest.RestHelper
import net.liftweb.json._
import com.openbankproject.commons.ExecutionContext.Implicits.global
import net.liftweb.http.provider.HTTPParam
import scala.collection.immutable.List
import scala.concurrent.Future
/**
* This object provides the API calls necessary to
* authenticate users using JSON Web Tokens (http://jwt.io).
*/
object JSONFactoryDAuth {
//Never update these values inside the case class
case class PayloadOfJwtJSON(
smart_contract_address: String,
network_name: String,
consumer_key: String,
timestamp: Option[String],
msg_sender: Option[String],
request_id: Option[String]
)
}
object DAuth extends RestHelper with MdcLoggable {
def createJwt(payloadAsJsonString: String) : String = {
val smartContractAddress = getFieldFromPayloadJson(payloadAsJsonString, "smart_contract_address")
val networkName = getFieldFromPayloadJson(payloadAsJsonString, "network_name")
val msgSender = getFieldFromPayloadJson(payloadAsJsonString, "msg_sender")
val consumerKey = getFieldFromPayloadJson(payloadAsJsonString, "consumer_key")
val timeStamp = getFieldFromPayloadJson(payloadAsJsonString, "timestamp")
val requestId = getFieldFromPayloadJson(payloadAsJsonString, "request_id")
val json = JSONFactoryDAuth.PayloadOfJwtJSON(
smart_contract_address = smartContractAddress,
network_name = networkName,
consumer_key = consumerKey,
msg_sender = Some(msgSender),
timestamp = Some(timeStamp),
request_id = Some(requestId)
)
val jwtPayloadAsJson = compactRender(Extraction.decompose(json))
val jwtClaims: JWTClaimsSet = JWTClaimsSet.parse(jwtPayloadAsJson)
APIUtil.getPropsAsBoolValue("jwt.use.ssl", false) match {
case true =>
CertificateUtil.encryptJwtWithRsa(jwtClaims)
case false =>
CertificateUtil.jwtWithHmacProtection(jwtClaims)
}
}
def parseJwt(jwt:String): Box[String] = {
logger.debug("parseJwt says jwt.toString is: " + jwt)
logger.debug("parseJwt says: validateJwtToken(jwt) is:" + validateJwtToken(jwt))
validateJwtToken(jwt) match {
case Full(jwtPayload) =>
logger.debug("parseJwt says: Full: " + jwtPayload.toString)
Full(compactRender(Extraction.decompose(jwtPayload)))
case _ =>
logger.debug("parseJwt says: Not Full(jwtPayload)")
Failure(ErrorMessages.DAuthJwtTokenIsNotValid)
}
}
def validateJwtToken(token: String): Box[PayloadOfJwtJSON] = {
APIUtil.getPropsAsBoolValue("jwt.use.ssl", false) match {
case true =>
logger.debug("validateJwtToken says: verifying jwt token with RSA: " + token)
val claim = CertificateUtil.decryptJwtWithRsa(token)
Box(parse(claim.toString).extractOpt[PayloadOfJwtJSON])
case false =>
logger.debug("validateJwtToken says: verifying jwt token with HmacProtection: " + token)
logger.debug(JwtUtil.validateJwtWithRsaKey(token).toString)
JwtUtil.validateJwtWithRsaKey(token) match {
case true =>
logger.debug("validateJwtToken says: jwt is verified: " + token)
val claim = CertificateUtil.parseJwtWithHmacProtection(token)
logger.debug("validateJwtToken says: this is claim of verified jwt: " + claim.toString())
Box(parse(claim.toString).extractOpt[PayloadOfJwtJSON])
case _ =>
logger.debug("validateJwtToken says: could not verify jwt")
Failure(ErrorMessages.DAuthJwtTokenIsNotValid)
}
}
}
// Check if the request (access token or request token) is valid and return a tuple
def getDAuthToken(requestHeaders: List[HTTPParam]) : Option[List[String]] = {
requestHeaders.find(_.name==APIUtil.DAuthHeaderKey).map(_.values)
}
def getOrCreateResourceUser(jwtPayload: String, callContext: Option[CallContext]) : Box[(User, Option[CallContext])] = {
val userName = getFieldFromPayloadJson(jwtPayload, "smart_contract_address")
val provider = "dauth."+getFieldFromPayloadJson(jwtPayload, "network_name")
logger.debug("login_user_name: " + userName)
for {
tuple <-
UserX.getOrCreateDauthResourceUser(userName, provider) match {
case Full(u) =>
Full((u,callContext)) // Return user
case Empty =>
Failure(ErrorMessages.DAuthCannotGetOrCreateUser)
case Failure(msg, t, c) =>
Failure(msg, t, c)
case _ =>
Failure(ErrorMessages.DAuthUnknownError)
}
} yield {
tuple
}
}
def getOrCreateResourceUserFuture(jwtPayload: String, callContext: Option[CallContext]) : Future[Box[(User, Option[CallContext])]] = {
val username = getFieldFromPayloadJson(jwtPayload, "smart_contract_address")
val provider = "dauth."+ getFieldFromPayloadJson(jwtPayload, "network_name")
logger.debug("login_user_name: " + username)
for {
tuple <- Future { UserX.getOrCreateDauthResourceUser(username, provider)} map {
case (Full(u)) =>
Full(u, callContext) // Return user
case (Empty) =>
Failure(ErrorMessages.DAuthCannotGetOrCreateUser)
case (Failure(msg, t, c)) =>
Failure(msg, t, c)
case _ =>
Failure(ErrorMessages.DAuthUnknownError)
}
} yield {
tuple
}
}
def getConsumerByConsumerKey(jwtPayload: String) : Box[Consumer] = {
val consumeyKey = getFieldFromPayloadJson(jwtPayload, "consumer_key")
Consumers.consumers.vend.getConsumerByConsumerKey(consumeyKey)
}
private def getFieldFromPayloadJson(payloadAsJsonString: String, fieldName: String) = {
val jwtJson = parse(payloadAsJsonString) // Transform Json string to JsonAST
val v = jwtJson.\(fieldName)
v match {
case JNothing =>
""
case _ =>
compactRender(v).replace("\"", "")
}
}
// Try to find errorCode in Json string received from South side and extract to list
// Return list of error codes values
def getErrors(message: String) : List[String] = {
val json = parse(message) removeField {
case JField("backendMessages", _) => true
case _ => false
}
val listOfValues = for {
JArray(objects) <- json
JObject(obj) <- objects
JField("errorCode", JString(fieldName)) <- obj
} yield fieldName
listOfValues
}
def getUser : Box[User] = {
val token = S.getRequestHeader(APIUtil.DAuthHeaderKey)
val payload = token.map(DAuth.parseJwt).flatten
payload match {
case Full(payload) =>
val username = getFieldFromPayloadJson(payload, "smart_contract_address")
val provider = getFieldFromPayloadJson(payload, "network_name")
val providerHardCodePrefixDauth = "dauth."+provider
logger.debug("username: " + username)
Users.users.vend.getUserByProviderId(provider = providerHardCodePrefixDauth, idGivenByProvider = username)
case _ =>
None
}
}
}

View File

@ -113,12 +113,8 @@ object DirectLogin extends RestHelper with MdcLoggable {
try {
val resourceUser = UserX.findByResourceUserId(userId).openOrThrowException(s"$InvalidDirectLoginParameters can not find the resourceUser!")
val authUser = AuthUser.findUserByUsernameLocally(resourceUser.name).openOrThrowException(s"$InvalidDirectLoginParameters can not find the auth user!")
if(!emailDomainToSpaceMappings.isEmpty){
AuthUser.grantEntitlementsToUseDynamicEndpointsInSpaces(authUser)
}
if(!emailDomainToEntitlementMappings.isEmpty){
AuthUser.grantEmailDomainEntitlementsToUser(authUser)
}
AuthUser.grantEntitlementsToUseDynamicEndpointsInSpaces(authUser)
AuthUser.grantEmailDomainEntitlementsToUser(authUser)
} catch {
case e: Throwable => // error handling, found wrong props value as early as possible.
this.logger.error(s"directLogin.grantEntitlementsToUseDynamicEndpointsInSpacesInDirectLogin throw exception, details: $e" );
@ -202,7 +198,7 @@ object DirectLogin extends RestHelper with MdcLoggable {
case "username" =>
checkUsernameString(parameterValue)
case "password" =>
checkMediumPassword(parameterValue)
validatePasswordOnUsage(parameterValue)
case "consumer_key" =>
checkMediumAlphaNumeric(parameterValue)
case "token" =>

View File

@ -101,6 +101,15 @@ object OpenIdConnect extends OBPRestHelper with MdcLoggable {
private def callbackUrlCommonCode(identityProvider: Int): JsonResponse = {
val (code, state, sessionState) = extractParams(S)
logger.debug("(code, state, sessionState) = " + (code, state, sessionState))
logger.debug("S.receivedCookies = " + S.receivedCookies)
logger.debug("S.responseCookies = " + S.responseCookies)
logger.debug("server_mode = " + APIUtil.getPropsValue("server_mode"))
def chainErrorMessage(badObj: Failure, errorMessage: String) = {
val chainedFailure: Failure = badObj ?~! errorMessage
(401, filterMessage(chainedFailure), None)
}
val (httpCode, message, authorizationUser) = if (state == sessionState) {
exchangeAuthorizationCodeForTokens(code, identityProvider) match {
@ -113,20 +122,31 @@ object OpenIdConnect extends OBPRestHelper with MdcLoggable {
case Full(user) => // All good
getOrCreateAuthUser(user) match {
case Full(authUser) =>
// Grant roles according to the props email_domain_to_space_mappings
AuthUser.grantEmailDomainEntitlementsToUser(authUser)
// Grant roles according to the props email_domain_to_space_mappings
AuthUser.grantEntitlementsToUseDynamicEndpointsInSpaces(authUser)
// Consumer
getOrCreateConsumer(idToken, user.userId) match {
case Full(consumer) =>
saveAuthorizationToken(tokenType, accessToken, idToken, refreshToken, scope, expiresIn) match {
saveAuthorizationToken(tokenType, accessToken, idToken, refreshToken, scope, expiresIn, authUser.id.get) match {
case Full(token) => (200, "OK", Some(authUser))
case _ => (401, ErrorMessages.CouldNotHandleOpenIDConnectData + "1", Some(authUser))
case badObj@Failure(_, _, _) => chainErrorMessage(badObj, ErrorMessages.CouldNotHandleOpenIDConnectData)
case _ => (401, ErrorMessages.CouldNotHandleOpenIDConnectData + "saveAuthorizationToken", Some(authUser))
}
case _ => (401, ErrorMessages.CouldNotHandleOpenIDConnectData + "2", Some(authUser))
case badObj@Failure(_, _, _) => chainErrorMessage(badObj, ErrorMessages.CouldNotHandleOpenIDConnectData)
case _ => (401, ErrorMessages.CouldNotHandleOpenIDConnectData + "getOrCreateConsumer", Some(authUser))
}
case _ => (401, ErrorMessages.CouldNotHandleOpenIDConnectData + "3", None)
case badObj@Failure(_, _, _) => chainErrorMessage(badObj, ErrorMessages.CouldNotHandleOpenIDConnectData)
case _ => (401, ErrorMessages.CouldNotHandleOpenIDConnectData + "getOrCreateAuthUser", None)
}
case badObj@Failure(_, _, _) => chainErrorMessage(badObj, ErrorMessages.CouldNotSaveOpenIDConnectUser)
case _ => (401, ErrorMessages.CouldNotSaveOpenIDConnectUser, None)
}
case badObj@Failure(_, _, _) => chainErrorMessage(badObj, ErrorMessages.CouldNotValidateIDToken)
case _ => (401, ErrorMessages.CouldNotValidateIDToken, None)
}
case badObj@Failure(_, _, _) => chainErrorMessage(badObj, ErrorMessages.CouldNotExchangeAuthorizationCodeForTokens)
case _ => (401, ErrorMessages.CouldNotExchangeAuthorizationCodeForTokens, None)
}
} else {
@ -182,11 +202,12 @@ object OpenIdConnect extends OBPRestHelper with MdcLoggable {
provider = issuer,
providerId = subject,
createdByConsentId = None,
name = getClaim(name = "given_name", idToken = idToken).orElse(subject),
name = subject,
email = getClaim(name = "email", idToken = idToken),
userId = None,
createdByUserInvitationId = None,
company = None
company = None,
lastMarketingAgreementSignedDate = None
)
}
}
@ -219,17 +240,26 @@ object OpenIdConnect extends OBPRestHelper with MdcLoggable {
"redirect_uri=" + config.callback_url + "&" +
"code=" + authorizationCode + "&" +
"grant_type=authorization_code"
val response = fromUrl(String.format("%s", config.token_endpoint), data, "POST")
val tokenResponse = json.parse(response)
for {
idToken <- tryo{(tokenResponse \ "id_token").extractOrElse[String]("")}
accessToken <- tryo{(tokenResponse \ "access_token").extractOrElse[String]("")}
tokenType <- tryo{(tokenResponse \ "token_type").extractOrElse[String]("")}
expiresIn <- tryo{(tokenResponse \ "expires_in").extractOrElse[String]("")}
refreshToken <- tryo{(tokenResponse \ "refresh_token").extractOrElse[String]("")}
scope <- tryo{(tokenResponse \ "scope").extractOrElse[String]("")}
} yield {
(idToken, accessToken, tokenType, expiresIn.toLong, refreshToken, scope)
logger.debug("Request parameters: " + data)
logger.debug("Token endpoint: " + config.token_endpoint)
val response: Box[String] = fromUrl(String.format("%s", config.token_endpoint), data, "POST")
logger.debug("Response: " + response)
response match {
case Full(value) =>
val tokenResponse = json.parse(value)
logger.debug("Token response: " + tokenResponse)
for {
idToken <- tryo{(tokenResponse \ "id_token").extractOrElse[String]("")}
accessToken <- tryo{(tokenResponse \ "access_token").extractOrElse[String]("")}
tokenType <- tryo{(tokenResponse \ "token_type").extractOrElse[String]("")}
expiresIn <- tryo{(tokenResponse \ "expires_in").extractOrElse[String]("")}
refreshToken <- tryo{(tokenResponse \ "refresh_token").extractOrElse[String]("")}
scope <- tryo{(tokenResponse \ "scope").extractOrElse[String]("")}
} yield {
(idToken, accessToken, tokenType, expiresIn.toLong, refreshToken, scope)
}
case badObject@Failure(_, _, _) => badObject
case _ => Failure(ErrorMessages.InternalServerError + " - exchangeAuthorizationCodeForTokens")
}
}
@ -240,7 +270,7 @@ object OpenIdConnect extends OBPRestHelper with MdcLoggable {
String.format("%s", config.userinfo_endpoint),
"?access_token="+accessToken,
"GET"
)
).openOrThrowException(ErrorMessages.InternalServerError + " - getUserInfo")
)
userResponse match {
case response: JValue => Full(response)
@ -272,14 +302,16 @@ object OpenIdConnect extends OBPRestHelper with MdcLoggable {
idToken: String,
refreshToken: String,
scope: String,
expiresIn: Long): Box[OpenIDConnectToken] = {
expiresIn: Long,
authUserPrimaryKey: Long): Box[OpenIDConnectToken] = {
val token = TokensOpenIDConnect.tokens.vend.createToken(
tokenType = tokenType,
accessToken = accessToken,
idToken = idToken,
refreshToken = refreshToken,
scope = scope,
expiresIn = expiresIn
expiresIn = expiresIn,
authUserPrimaryKey = authUserPrimaryKey
)
token match {
case Full(_) => // All good
@ -293,7 +325,7 @@ object OpenIdConnect extends OBPRestHelper with MdcLoggable {
method: String,
connectTimeout: Int = 2000,
readTimeout: Int = 10000
): String = {
): Box[String] = {
var content:String = ""
import java.net.URL
try {
@ -328,10 +360,13 @@ object OpenIdConnect extends OBPRestHelper with MdcLoggable {
val inputStream = connection.getInputStream
content = scala.io.Source.fromInputStream(inputStream).mkString
if (inputStream != null) inputStream.close()
Full(content)
} catch {
case e:Throwable => logger.error(e)
case e:Throwable =>
e.printStackTrace()
logger.error(e)
Failure(e.getMessage)
}
content
}

View File

@ -42,14 +42,13 @@ import code.api.builder.OBP_APIBuilder
import code.api.oauth1a.Arithmetics
import code.api.oauth1a.OauthParams._
import code.api.util.APIUtil.ResourceDoc.{findPathVariableNames, isPathVariable}
import code.api.util.ApiRole.{canCreateProduct, canCreateProductAtAnyBank}
import code.api.util.ApiTag.{ResourceDocTag, apiTagBank, apiTagNewStyle}
import code.api.util.Glossary.GlossaryItem
import code.api.util.JwsUtil.getJwsHeaderValue
import code.api.util.RateLimitingJson.CallLimit
import code.api.v1_2.ErrorMessage
import code.api.v2_0_0.CreateEntitlementJSON
import code.api.{DirectLogin, _}
import code.api.v4_0_0.dynamic.{DynamicEndpointHelper, DynamicEndpoints, DynamicEntityHelper}
import code.api.{DirectLogin, _}
import code.authtypevalidation.AuthenticationTypeValidationProvider
import code.bankconnectors.Connector
import code.consumer.Consumers
@ -58,20 +57,25 @@ import code.entitlement.Entitlement
import code.metrics._
import code.model._
import code.model.dataAccess.AuthUser
import code.ratelimiting.{RateLimiting, RateLimitingDI}
import code.sanitycheck.SanityCheck
import code.scope.Scope
import code.usercustomerlinks.UserCustomerLink
import code.util.{Helper, JsonSchemaUtil}
import code.util.Helper.{MdcLoggable, SILENCE_IS_GOLDEN}
import code.util.{Helper, JsonSchemaUtil}
import code.views.Views
import code.webuiprops.MappedWebUiPropsProvider.getWebUiPropsValue
import com.alibaba.ttl.internal.javassist.CannotCompileException
import com.github.dwickern.macros.NameOf.{nameOf, nameOfType}
import com.openbankproject.commons.ExecutionContext.Implicits.global
import com.openbankproject.commons.model.enums.StrongCustomerAuthentication.SCA
import com.openbankproject.commons.model.enums.{PemCertificateRole, StrongCustomerAuthentication}
import com.openbankproject.commons.model.{Customer, _}
import com.openbankproject.commons.util.Functions.Implicits._
import com.openbankproject.commons.util.Functions.Memo
import com.openbankproject.commons.util._
import dispatch.url
import javassist.expr.{ExprEditor, MethodCall}
import javassist.{ClassPool, LoaderClassPath}
import net.liftweb.actor.LAFuture
import net.liftweb.common.{Empty, _}
import net.liftweb.http._
@ -83,21 +87,14 @@ import net.liftweb.json.JsonAST.{JField, JNothing, JObject, JString, JValue}
import net.liftweb.json.JsonParser.ParseException
import net.liftweb.json._
import net.liftweb.util.Helpers._
import net.liftweb.util.{Helpers, LiftFlowOfControlException, Props, StringHelpers, ThreadGlobal}
import scala.collection.JavaConverters._
import scala.collection.immutable.{List, Nil}
import scala.collection.mutable.{ArrayBuffer, ListBuffer}
import com.openbankproject.commons.ExecutionContext.Implicits.global
import com.openbankproject.commons.util.{ApiVersion, Functions, JsonAble, ReflectUtils, ScannedApiVersion}
import com.openbankproject.commons.util.Functions.Implicits._
import com.openbankproject.commons.util.Functions.Memo
import javassist.{ClassPool, LoaderClassPath}
import javassist.expr.{ExprEditor, MethodCall}
import net.liftweb.util._
import org.apache.commons.io.IOUtils
import org.apache.commons.lang3.StringUtils
import scala.collection.JavaConverters._
import scala.collection.immutable.{List, Nil}
import scala.collection.mutable
import scala.collection.mutable.{ArrayBuffer, ListBuffer}
import scala.concurrent.Future
import scala.io.BufferedSource
import scala.util.Either
@ -178,6 +175,15 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
def hasAnOAuth2Header(authorization: Box[String]): Boolean = hasHeader("Bearer", authorization)
def hasGatewayHeader(authorization: Box[String]) = hasHeader("GatewayLogin", authorization)
/**
* The value `DAuth` is in the KEY
* DAuth:xxxxx
*
* Other types: the `GatewayLogin` is in the VALUE
* Authorization:GatewayLogin token=xxxx
*/
def hasDAuthHeader(requestHeaders: List[HTTPParam]) = requestHeaders.map(_.name).exists(_ ==DAuthHeaderKey)
/**
* Helper function which tells us does an "Authorization" request header field has the Type of an authentication scheme
@ -624,7 +630,8 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
/** check the currency ISO code from the ISOCurrencyCodes.xml file */
def isValidCurrencyISOCode(currencyCode: String): Boolean = {
val currencyIsoCodeArray = (CurrencyIsoCodeFromXmlFile \"CcyTbl" \ "CcyNtry" \ "Ccy").map(_.text).mkString(" ").split("\\s+")
// Note: We add BTC bitcoin as XBT (the ISO compliant varient)
val currencyIsoCodeArray = (CurrencyIsoCodeFromXmlFile \"CcyTbl" \ "CcyNtry" \ "Ccy").map(_.text).mkString(" ").split("\\s+") :+ "XBT"
currencyIsoCodeArray.contains(currencyCode)
}
@ -643,10 +650,10 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
/** enforce the password.
* The rules :
* 1) length is >16 characters without validations
* 1) length is >16 characters without validations but max length <= 512
* 2) or Min 10 characters with mixed numbers + letters + upper+lower case + at least one special character.
* */
def isValidStrongPassword(password: String): Boolean = {
def validatePasswordOnCreation(password: String): Boolean = {
/**
* (?=.*\d) //should contain at least one digit
* (?=.*[a-z]) //should contain at least one lower case
@ -657,7 +664,8 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
val regex =
"""^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[!"#$%&'\(\)*+,-./:;<=>?@\\[\\\\]^_\\`{|}~])([A-Za-z0-9!"#$%&'\(\)*+,-./:;<=>?@\\[\\\\]^_\\`{|}~]{10,16})$""".r
password match {
case password if (password.length > 16) => true
case password if(validatePasswordOnUsage(password) ==SILENCE_IS_GOLDEN) => true
case password if(password.length > 16 && password.length <= 512) => true
case regex(password) => true
case _ => false
}
@ -692,9 +700,10 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
}
/** only A-Z, a-z, 0-9, all allowed characters for password and max length <= 512 */
def checkMediumPassword(value:String): String ={
/** also support space now */
def validatePasswordOnUsage(value:String): String ={
val valueLength = value.length
val regex = """^([A-Za-z0-9!"#$%&'\(\)*+,-./:;<=>?@\\[\\\\]^_\\`{|}~]+)$""".r
val regex = """^([A-Za-z0-9!"#$%&'\(\)*+,-./:;<=>?@\\[\\\\]^_\\`{|}~ ]+)$""".r
value match {
case regex(e) if(valueLength <= 512) => SILENCE_IS_GOLDEN
case regex(e) if(valueLength > 512) => ErrorMessages.InvalidValueLength
@ -1347,7 +1356,8 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
var roles: Option[List[ApiRole]] = None,
isFeatured: Boolean = false,
specialInstructions: Option[String] = None,
var specifiedUrl: Option[String] = None // A derived value: Contains the called version (added at run time). See the resource doc for resource doc!
var specifiedUrl: Option[String] = None, // A derived value: Contains the called version (added at run time). See the resource doc for resource doc!
createdByBankId: Option[String] = None //we need to filter the resource Doc by BankId
) {
// this code block will be merged to constructor.
{
@ -1656,7 +1666,7 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
}
def buildOperationId(apiVersion: ScannedApiVersion, partialFunctionName: String) =
s"${apiVersion.fullyQualifiedVersion}-$partialFunctionName"
s"${apiVersion.fullyQualifiedVersion}-$partialFunctionName".trim
//This is correct: OBPv3.0.0-getCoreAccountById
//This is OBPv4_0_0-dynamicEntity_deleteFooBar33
@ -2239,7 +2249,10 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
None
}
}
/**
* Defines DAuth Custom Response Header.
*/
val DAuthHeaderKey = "DAuth"
/**
* Turn a string of format "FooBar" into snake case "foo_bar"
*
@ -2657,8 +2670,6 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
for {
(user, callContext) <- OAuth2Login.getUserFuture(cc)
} yield {
if (!APIUtil.isSandboxMode && user.isDefined)
AuthUser.updateUserAccountViewsFuture(user.openOrThrowException("Can not be empty here"), callContext)
(user, callContext)
}
} // Direct Login i.e DirectLogin: token=eyJhbGciOiJIUzI1NiJ9.eyIiOiIifQ.Y0jk1EQGB4XgdqmYZUHT6potmH3mKj5mEaA9qrIXXWQ
@ -2705,69 +2716,62 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
case _ =>
Future { (Failure(ErrorMessages.GatewayLoginUnknownError), None) }
}
} else if(Option(cc).flatMap(_.user).isDefined) {
} // DAuth Login
else if (getPropsAsBoolValue("allow_dauth", false) && hasDAuthHeader(cc.requestHeaders)) {
logger.info("allow_dauth-getRemoteIpAddress: " + remoteIpAddress )
APIUtil.getPropsValue("dauth.host") match {
case Full(h) if h.split(",").toList.exists(_.equalsIgnoreCase(remoteIpAddress) == true) => // Only addresses from white list can use this feature
val dauthToken = DAuth.getDAuthToken(cc.requestHeaders)
dauthToken match {
case Some(token :: _) =>
val payload = DAuth.parseJwt(token)
payload match {
case Full(payload) =>
DAuth.getOrCreateResourceUserFuture(payload: String, Some(cc)) map {
case Full((u,callContext)) => // Authentication is successful
val consumer = DAuth.getConsumerByConsumerKey(payload)//TODO, need to verify the key later.
val jwt = DAuth.createJwt(payload)
val callContextUpdated = ApiSession.updateCallContext(DAuthResponseHeader(Some(jwt)), callContext)
(Full(u), callContextUpdated.map(_.copy(consumer=consumer, user = Full(u))))
case Failure(msg, t, c) =>
(Failure(msg, t, c), None)
case _ =>
(Failure(payload), None)
}
case Failure(msg, t, c) =>
Future { (Failure(msg, t, c), None) }
case _ =>
Future { (Failure(ErrorMessages.DAuthUnknownError), None) }
}
case _ =>
Future { (Failure(InvalidDAuthHeaderToken), None) }
}
case Full(h) if h.split(",").toList.exists(_.equalsIgnoreCase(remoteIpAddress) == false) => // All other addresses will be rejected
Future { (Failure(ErrorMessages.DAuthWhiteListAddresses), None) }
case Empty =>
Future { (Failure(ErrorMessages.DAuthHostPropertyMissing), None) } // There is no dauth.host in props file
case Failure(msg, t, c) =>
Future { (Failure(msg, t, c), None) }
case _ =>
Future { (Failure(ErrorMessages.DAuthUnknownError), None) }
}
}
else if(Option(cc).flatMap(_.user).isDefined) {
Future{(cc.user, Some(cc))}
}
else {
Future { (Empty, Some(cc)) }
}
// COMMON POST AUTHENTICATION CODE GOES BELOW
// Check is it a user deleted or locked
val userIsLockedOrDeleted: Future[(Box[User], Option[CallContext])] = AfterApiAuth.checkUserIsDeletedOrLocked(res)
// Check Rate Limiting
val resultWithRateLimiting: Future[(Box[User], Option[CallContext])] = AfterApiAuth.checkRateLimiting(userIsLockedOrDeleted)
/******************************************************************************************************************
* This block of code needs to update Call Context with Rate Limiting
* Please note that first source is the table RateLimiting and second is the table Consumer
*/
def getRateLimiting(consumerId: String, version: String, name: String): Future[Box[RateLimiting]] = {
RateLimitingUtil.useConsumerLimits match {
case true => RateLimitingDI.rateLimiting.vend.getByConsumerId(consumerId, version, name, Some(new Date()))
case false => Future(Empty)
}
}
val resultWithRateLimiting: Future[(Box[User], Option[CallContext])] = for {
(user, cc) <- res
consumer = cc.flatMap(_.consumer)
version = cc.map(_.implementedInVersion).getOrElse("None") // Calculate apiVersion in case of Rate Limiting
operationId = cc.flatMap(_.operationId) // Unique Identifier of Dynamic Endpoints
// Calculate apiName in case of Rate Limiting
name = cc.flatMap(_.resourceDocument.map(_.partialFunctionName)) // 1st try: function name at resource doc
.orElse(operationId) // 2nd try: In case of Dynamic Endpoint we can only use operationId
.getOrElse("None") // Not found any unique identifier
rateLimiting <- getRateLimiting(consumer.map(_.consumerId.get).getOrElse(""), version, name)
} yield {
val limit: Option[CallLimit] = rateLimiting match {
case Full(rl) => Some(CallLimit(
rl.consumerId,
rl.apiName,
rl.apiVersion,
rl.bankId,
rl.perSecondCallLimit,
rl.perMinuteCallLimit,
rl.perHourCallLimit,
rl.perDayCallLimit,
rl.perWeekCallLimit,
rl.perMonthCallLimit))
case Empty =>
Some(CallLimit(
consumer.map(_.consumerId.get).getOrElse(""),
None,
None,
None,
consumer.map(_.perSecondCallLimit.get).getOrElse(-1),
consumer.map(_.perMinuteCallLimit.get).getOrElse(-1),
consumer.map(_.perHourCallLimit.get).getOrElse(-1),
consumer.map(_.perDayCallLimit.get).getOrElse(-1),
consumer.map(_.perWeekCallLimit.get).getOrElse(-1),
consumer.map(_.perMonthCallLimit.get).getOrElse(-1)
))
case _ => None
}
(user, cc.map(_.copy(rateLimiting = limit)))
}
/*************************************************************************************************************** */
resultWithRateLimiting map { // Update Call Context
// Update Call Context
resultWithRateLimiting map {
x => (x._1, ApiSession.updateCallContext(Spelling(spelling), x._2))
} map {
x => (x._1, x._2.map(_.copy(implementedInVersion = implementedInVersion)))
@ -2788,7 +2792,9 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
}
}
/**
* This Function is used to terminate a Future used in for-comprehension with specific message and code in case that value of Box is not Full.
@ -2836,7 +2842,7 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
* Better also check the logic for needToRefreshUser method.
*/
def refreshUserIfRequired(user: Box[User], callContext: Option[CallContext]) = {
if(!APIUtil.isSandboxMode && user.isDefined && UserRefreshes.UserRefreshes.vend.needToRefreshUser(user.head.userId))
if(user.isDefined && UserRefreshes.UserRefreshes.vend.needToRefreshUser(user.head.userId))
user.map(AuthUser.updateUserAccountViewsFuture(_, callContext))
else
None
@ -2988,7 +2994,7 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
throw new Exception(UnknownError)
}
}
def unboxFullAndWrapIntoFuture[T](box: Box[T])(implicit m: Manifest[T]) : Future[T] = {
Future {
unboxFull(fullBoxOrException(box))
@ -3051,9 +3057,6 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
otherAccountRoutingAddress: String
)= createOBPId(s"$thisBankId$thisAccountId$counterpartyName$otherAccountRoutingScheme$otherAccountRoutingAddress")
//TODO, now we have the star connector, it will break the isSandboxMode method logic. Need to double check how to use this method now.
val isSandboxMode: Boolean = (APIUtil.getPropsValue("connector").openOrThrowException(attemptedToOpenAnEmptyBox).toString).equalsIgnoreCase("mapped")
def isDataFromOBPSide (methodName: String, argNameToValue: Array[(String, AnyRef)] = Array.empty): Boolean = {
val connectorNameInProps = APIUtil.getPropsValue("connector").openOrThrowException(attemptedToOpenAnEmptyBox)
//if the connector == mapped, then the data is always over obp database
@ -3850,6 +3853,40 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
}
}
}
/**
* validate whether current request's query parameters
* @param operationId
* @param callContext
* @return Full(errorResponse) if validate fail
*/
def validateQueryParams(operationId: String, callContext: CallContext): Box[JsonResponse] = {
val queryString: String = if (callContext.url.contains("?")) callContext.url.split("\\?",2)(1) else ""
val queryParams: Array[String] = queryString.split("&").map(_.split("=")(0))
val queryParamsGrouped: Map[String, Array[String]] = queryParams.groupBy(x => x)
queryParamsGrouped.toList.forall(_._2.size == 1) match {
case true => Empty
case false =>
Box.tryo(
createErrorJsonResponse(s"${ErrorMessages.DuplicateQueryParameters}", 400, callContext.correlationId)
)
}
}
/**
* validate whether current request's header keys
* @param operationId
* @param callContext
* @return Full(errorResponse) if validate fail
*/
def validateRequestHeadersKeys(operationId: String, callContext: CallContext): Box[JsonResponse] = {
val headerKeysGrouped: Map[String, List[HTTPParam]] = callContext.requestHeaders.groupBy(x => x.name)
headerKeysGrouped.toList.forall(_._2.size == 1) match {
case true => Empty
case false =>
Box.tryo(
createErrorJsonResponse(s"${ErrorMessages.DuplicateHeaderKeys}", 400, callContext.correlationId)
)
}
}
def createErrorJsonResponse(errorMsg: String, errorCode: Int, correlationId: String): JsonResponse = {
import net.liftweb.json.JsonDSL._
@ -3913,6 +3950,14 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
// validate auth type
{
case (Some(callContext), operationId) => validateAuthType(operationId, callContext)
},
// validate query params
{
case (Some(callContext), operationId) => validateQueryParams(operationId, callContext)
},
// validate request header keys
{
case (Some(callContext), operationId) => validateRequestHeadersKeys(operationId, callContext)
}
)
@ -4052,4 +4097,21 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
APIUtil.getPropsValue("email_domain_to_entitlement_mappings").map(extractor).getOrElse(Nil)
}
val getProductsIsPublic = APIUtil.getPropsAsBoolValue("apiOptions.getProductsIsPublic", true)
val createProductEntitlements = canCreateProduct :: canCreateProductAtAnyBank :: Nil
val createProductEntitlementsRequiredText = UserHasMissingRoles + createProductEntitlements.mkString(" or ")
val productHiearchyAndCollectionNote =
"""
|
|Product hiearchy vs Product Collections:
|
|* You can define a hierarchy of products - so that a child Product inherits attributes of its parent Product - using the parent_product_code in Product.
|
|* You can define a collection (also known as baskets or buckets) of products using Product Collections.
|
""".stripMargin
}

View File

@ -0,0 +1,90 @@
package code.api.util
import java.util.Date
import code.api.util.ErrorMessages.{UserIsDeleted, UsernameHasBeenLocked}
import code.api.util.RateLimitingJson.CallLimit
import code.loginattempts.LoginAttempt
import code.ratelimiting.{RateLimiting, RateLimitingDI}
import com.openbankproject.commons.model.User
import net.liftweb.common.{Box, Empty, Failure, Full}
import com.openbankproject.commons.ExecutionContext.Implicits.global
import scala.concurrent.Future
object AfterApiAuth {
def checkUserIsDeletedOrLocked(res: Future[(Box[User], Option[CallContext])]): Future[(Box[User], Option[CallContext])] = {
for {
(user: Box[User], cc) <- res
} yield {
user match {
case Full(u) => // There is a user. Check it.
if (u.isDeleted.getOrElse(false)) {
(Failure(UserIsDeleted), cc) // The user is DELETED.
} else {
LoginAttempt.userIsLocked(u.name) match {
case true => (Failure(UsernameHasBeenLocked), cc) // The user is LOCKED.
case false => (user, cc) // All good
}
}
case _ => // There is no user. Just forward the result.
(user, cc)
}
}
}
/**
* This block of code needs to update Call Context with Rate Limiting
* Please note that first source is the table RateLimiting and second is the table Consumer
*/
def checkRateLimiting(userIsLockedOrDeleted: Future[(Box[User], Option[CallContext])]): Future[(Box[User], Option[CallContext])] = {
def getRateLimiting(consumerId: String, version: String, name: String): Future[Box[RateLimiting]] = {
RateLimitingUtil.useConsumerLimits match {
case true => RateLimitingDI.rateLimiting.vend.getByConsumerId(consumerId, version, name, Some(new Date()))
case false => Future(Empty)
}
}
for {
(user, cc) <- userIsLockedOrDeleted
consumer = cc.flatMap(_.consumer)
version = cc.map(_.implementedInVersion).getOrElse("None") // Calculate apiVersion in case of Rate Limiting
operationId = cc.flatMap(_.operationId) // Unique Identifier of Dynamic Endpoints
// Calculate apiName in case of Rate Limiting
name = cc.flatMap(_.resourceDocument.map(_.partialFunctionName)) // 1st try: function name at resource doc
.orElse(operationId) // 2nd try: In case of Dynamic Endpoint we can only use operationId
.getOrElse("None") // Not found any unique identifier
rateLimiting <- getRateLimiting(consumer.map(_.consumerId.get).getOrElse(""), version, name)
} yield {
val limit: Option[CallLimit] = rateLimiting match {
case Full(rl) => Some(CallLimit(
rl.consumerId,
rl.apiName,
rl.apiVersion,
rl.bankId,
rl.perSecondCallLimit,
rl.perMinuteCallLimit,
rl.perHourCallLimit,
rl.perDayCallLimit,
rl.perWeekCallLimit,
rl.perMonthCallLimit))
case Empty =>
Some(CallLimit(
consumer.map(_.consumerId.get).getOrElse(""),
None,
None,
None,
consumer.map(_.perSecondCallLimit.get).getOrElse(-1),
consumer.map(_.perMinuteCallLimit.get).getOrElse(-1),
consumer.map(_.perHourCallLimit.get).getOrElse(-1),
consumer.map(_.perDayCallLimit.get).getOrElse(-1),
consumer.map(_.perWeekCallLimit.get).getOrElse(-1),
consumer.map(_.perMonthCallLimit.get).getOrElse(-1)
))
case _ => None
}
(user, cc.map(_.copy(rateLimiting = limit)))
}
}
}

View File

@ -29,6 +29,10 @@ object ApiPropsWithAlias {
name="allow_customer_firehose",
alias="allow_firehose_views",
defaultValue="false")
def jwtTokenSecret = getValueByNameOrAlias(
name="jwt.token_secret",
alias="gateway.token_secret",
defaultValue="Cannot get your at least 256 bit secret")
}
object HelperFunctions extends MdcLoggable {

View File

@ -409,16 +409,40 @@ object ApiRole {
case class CanUpdateProductAttribute(requiresBankId: Boolean = true) extends ApiRole
lazy val canUpdateProductAttribute = CanUpdateProductAttribute()
case class CanUpdateBankAttribute(requiresBankId: Boolean = true) extends ApiRole
lazy val canUpdateBankAttribute = CanUpdateBankAttribute()
case class CanGetBankAttribute(requiresBankId: Boolean = true) extends ApiRole
lazy val canGetBankAttribute = CanGetBankAttribute()
case class CanGetProductAttribute(requiresBankId: Boolean = true) extends ApiRole
lazy val canGetProductAttribute = CanGetProductAttribute()
case class CanDeleteProductAttribute(requiresBankId: Boolean = true) extends ApiRole
lazy val canDeleteProductAttribute = CanDeleteProductAttribute()
case class CanDeleteBankAttribute(requiresBankId: Boolean = true) extends ApiRole
lazy val canDeleteBankAttribute = CanDeleteBankAttribute()
case class CanCreateProductAttribute(requiresBankId: Boolean = true) extends ApiRole
lazy val canCreateProductAttribute = CanCreateProductAttribute()
case class CanCreateBankAttribute(requiresBankId: Boolean = true) extends ApiRole
lazy val canCreateBankAttribute = CanCreateBankAttribute()
case class CanUpdateProductFee(requiresBankId: Boolean = true) extends ApiRole
lazy val canUpdateProductFee = CanUpdateProductFee()
case class CanGetProductFee(requiresBankId: Boolean = true) extends ApiRole
lazy val canGetProductFee = CanGetProductFee()
case class CanDeleteProductFee(requiresBankId: Boolean = true) extends ApiRole
lazy val canDeleteProductFee = CanDeleteProductFee()
case class CanCreateProductFee(requiresBankId: Boolean = true) extends ApiRole
lazy val canCreateProductFee = CanCreateProductFee()
case class CanMaintainProductCollection(requiresBankId: Boolean = true) extends ApiRole
lazy val canMaintainProductCollection = CanMaintainProductCollection()
@ -594,6 +618,12 @@ object ApiRole {
case class CanReadResourceDoc(requiresBankId: Boolean = false) extends ApiRole
lazy val canReadResourceDoc = CanReadResourceDoc()
case class CanReadStaticResourceDoc(requiresBankId: Boolean = false) extends ApiRole
lazy val canReadStaticResourceDoc = CanReadStaticResourceDoc()
case class CanReadDynamicResourceDocsAtOneBank(requiresBankId: Boolean = true) extends ApiRole
lazy val canReadDynamicResourceDocsAtOneBank = CanReadDynamicResourceDocsAtOneBank()
case class CanReadGlossary(requiresBankId: Boolean = false) extends ApiRole
lazy val canReadGlossary = CanReadGlossary()
@ -624,6 +654,9 @@ object ApiRole {
case class CanCreateProductAttributeDefinitionAtOneBank(requiresBankId: Boolean = true) extends ApiRole
lazy val canCreateProductAttributeDefinitionAtOneBank = CanCreateProductAttributeDefinitionAtOneBank()
case class CanCreateBankAttributeDefinitionAtOneBank(requiresBankId: Boolean = true) extends ApiRole
lazy val canCreateBankAttributeDefinitionAtOneBank = CanCreateBankAttributeDefinitionAtOneBank()
case class CanCreateTransactionAttributeDefinitionAtOneBank(requiresBankId: Boolean = true) extends ApiRole
lazy val canCreateTransactionAttributeDefinitionAtOneBank = CanCreateTransactionAttributeDefinitionAtOneBank()
@ -764,6 +797,33 @@ object ApiRole {
case class CanGetUserInvitation(requiresBankId: Boolean = true) extends ApiRole
lazy val canGetUserInvitation = CanGetUserInvitation()
case class CanCreateSystemLevelEndpointTag(requiresBankId: Boolean = false) extends ApiRole
lazy val canCreateSystemLevelEndpointTag = CanCreateSystemLevelEndpointTag()
case class CanUpdateSystemLevelEndpointTag(requiresBankId: Boolean = false) extends ApiRole
lazy val canUpdateSystemLevelEndpointTag = CanUpdateSystemLevelEndpointTag()
case class CanDeleteSystemLevelEndpointTag(requiresBankId: Boolean = false) extends ApiRole
lazy val canDeleteSystemLevelEndpointTag = CanDeleteSystemLevelEndpointTag()
case class CanGetSystemLevelEndpointTag(requiresBankId: Boolean = false) extends ApiRole
lazy val canGetSystemLevelEndpointTag = CanGetSystemLevelEndpointTag()
case class CanCreateBankLevelEndpointTag(requiresBankId: Boolean = true) extends ApiRole
lazy val canCreateBankLevelEndpointTag = CanCreateBankLevelEndpointTag()
case class CanUpdateBankLevelEndpointTag(requiresBankId: Boolean = true) extends ApiRole
lazy val canUpdateBankLevelEndpointTag = CanUpdateBankLevelEndpointTag()
case class CanDeleteBankLevelEndpointTag(requiresBankId: Boolean = true) extends ApiRole
lazy val canDeleteBankLevelEndpointTag = CanDeleteBankLevelEndpointTag()
case class CanGetBankLevelEndpointTag(requiresBankId: Boolean = true) extends ApiRole
lazy val canGetBankLevelEndpointTag = CanGetBankLevelEndpointTag()
case class CanCreateHistoricalTransactionAtBank(requiresBankId: Boolean = true) extends ApiRole
lazy val canCreateHistoricalTransactionAtBank = CanCreateHistoricalTransactionAtBank()
private val dynamicApiRoles = new ConcurrentHashMap[String, ApiRole]
private case class DynamicApiRole(role: String, requiresBankId: Boolean = false) extends ApiRole{

View File

@ -1,10 +1,11 @@
package code.api.util
import code.api.JSONFactoryDAuth
import java.util.{Date, UUID}
import code.api.JSONFactoryGateway.PayloadOfJwtJSON
import code.api.oauth1a.OauthParams._
import code.api.util.APIUtil._
import code.api.util.AuthenticationType.{Anonymous, DirectLogin, GatewayLogin, OAuth2_OIDC, OAuth2_OIDC_FAPI}
import code.api.util.AuthenticationType.{Anonymous, DirectLogin, GatewayLogin, DAuth, OAuth2_OIDC, OAuth2_OIDC_FAPI}
import code.api.util.ErrorMessages.{BankAccountNotFound, UserNotLoggedIn}
import code.api.util.RateLimitingJson.CallLimit
import code.context.UserAuthContextProvider
@ -25,6 +26,8 @@ import scala.collection.immutable.List
case class CallContext(
gatewayLoginRequestPayload: Option[PayloadOfJwtJSON] = None, //Never update these values inside the case class !!!
gatewayLoginResponseHeader: Option[String] = None,
dauthRequestPayload: Option[JSONFactoryDAuth.PayloadOfJwtJSON] = None, //Never update these values inside the case class !!!
dauthResponseHeader: Option[String] = None,
spelling: Option[String] = None,
user: Box[User] = Empty,
consumer: Box[Consumer] = Empty,
@ -137,6 +140,8 @@ case class CallContext(
def authType: AuthenticationType = {
if(hasGatewayHeader(authReqHeaderField)) {
GatewayLogin
} else if(requestHeaders.exists(_.name==DAuthHeaderKey)) { // DAuth Login
DAuth
} else if(has2021DirectLoginHeader(requestHeaders)) { // Direct Login
DirectLogin
} else if(hasDirectLoginHeader(authReqHeaderField)) { // Direct Login Deprecated
@ -161,6 +166,7 @@ object AuthenticationType extends OBPEnumeration[AuthenticationType]{
override def toString: String = "OAuth1.0a"
}
object GatewayLogin extends AuthenticationType
object DAuth extends AuthenticationType
object OAuth2_OIDC extends AuthenticationType
object OAuth2_OIDC_FAPI extends AuthenticationType
object Anonymous extends AuthenticationType
@ -193,9 +199,11 @@ case class CallContextLight(gatewayLoginRequestPayload: Option[PayloadOfJwtJSON]
`X-Rate-Limit-Reset` : Long = -1
)
trait GatewayLoginParam
case class GatewayLoginRequestPayload(jwtPayload: Option[PayloadOfJwtJSON]) extends GatewayLoginParam
case class GatewayLoginResponseHeader(jwt: Option[String]) extends GatewayLoginParam
trait LoginParam
case class GatewayLoginRequestPayload(jwtPayload: Option[PayloadOfJwtJSON]) extends LoginParam
case class GatewayLoginResponseHeader(jwt: Option[String]) extends LoginParam
case class DAuthRequestPayload(jwtPayload: Option[JSONFactoryDAuth.PayloadOfJwtJSON]) extends LoginParam
case class DAuthResponseHeader(jwt: Option[String]) extends LoginParam
case class Spelling(spelling: Box[String])
@ -230,17 +238,17 @@ object ApiSession {
def updateCallContext(s: Spelling, cnt: Option[CallContext]): Option[CallContext] = {
cnt match {
case None =>
Some(CallContext(gatewayLoginRequestPayload = None, gatewayLoginResponseHeader = None, spelling = s.spelling))
Some(CallContext(spelling = s.spelling)) //Some fields default value is NONE.
case Some(v) =>
Some(v.copy(spelling = s.spelling))
}
}
def updateCallContext(jwt: GatewayLoginParam, cnt: Option[CallContext]): Option[CallContext] = {
def updateCallContext(jwt: LoginParam, cnt: Option[CallContext]): Option[CallContext] = {
jwt match {
case GatewayLoginRequestPayload(None) =>
case GatewayLoginRequestPayload(None) | DAuthRequestPayload(None) =>
cnt
case GatewayLoginResponseHeader(None) =>
case GatewayLoginResponseHeader(None) | DAuthResponseHeader(None) =>
cnt
case GatewayLoginRequestPayload(Some(jwtPayload)) =>
cnt match {
@ -256,6 +264,20 @@ object ApiSession {
case None =>
Some(CallContext(gatewayLoginRequestPayload = None, gatewayLoginResponseHeader = Some(j), spelling = None))
}
case DAuthRequestPayload(Some(jwtPayload)) =>
cnt match {
case Some(v) =>
Some(v.copy(dauthRequestPayload = Some(jwtPayload)))
case None =>
Some(CallContext(dauthRequestPayload = Some(jwtPayload), dauthResponseHeader = None, spelling = None))
}
case DAuthResponseHeader(Some(j)) =>
cnt match {
case Some(v) =>
Some(v.copy(dauthResponseHeader = Some(j)))
case None =>
Some(CallContext(dauthRequestPayload = None, dauthResponseHeader = Some(j), spelling = None))
}
}
}

View File

@ -70,16 +70,20 @@ object ApiTag {
val apiTagConsent = ResourceDocTag("Consent")
val apiTagMethodRouting = ResourceDocTag("Method-Routing")
val apiTagWebUiProps = ResourceDocTag("WebUi-Props")
val apiTagEndpointMapping = ResourceDocTag("Endpoint-Mapping-Manage")
val apiTagManageDynamicEndpoint = ResourceDocTag("Dynamic-Endpoint-Manage")
val apiTagManageDynamicEntity = ResourceDocTag("Dynamic-Entity-Manage")
val apiTagDynamicResourceDoc = ResourceDocTag("Dynamic-Resource-Doc-Manage")
val apiTagDynamicMessageDoc = ResourceDocTag("Dynamic-Message-Doc-Manage")
val apiTagEndpointMapping = ResourceDocTag("Endpoint-Mapping")
val apiTagApiCollection = ResourceDocTag("Api-Collection")
val apiTagDynamicResourceDoc = ResourceDocTag("Dynamic-Resource-Doc")
val apiTagDynamicMessageDoc = ResourceDocTag("Dynamic-Message-Doc")
val apiTagDAuth = ResourceDocTag("DAuth")
val apiTagDynamic = ResourceDocTag("Dynamic")
val apiTagDynamicEntity = ResourceDocTag("Dynamic-Entity")
val apiTagManageDynamicEntity = ResourceDocTag("Dynamic-Entity-Manage")
val apiTagDynamicEndpoint = ResourceDocTag("Dynamic-Endpoint")
val apiTagManageDynamicEndpoint = ResourceDocTag("Dynamic-Endpoint-Manage")
val apiTagJsonSchemaValidation = ResourceDocTag("JSON-Schema-Validation")
val apiTagAuthenticationTypeValidation = ResourceDocTag("Authentication-Type-Validation")

View File

@ -25,7 +25,7 @@ object CryptoSystem extends Enumeration {
object CertificateUtil extends MdcLoggable {
// your-at-least-256-bit-secret
val sharedSecret = APIUtil.getPropsValue("gateway.token_secret", "Cannot get your at least 256 bit secret")
val sharedSecret: String = ApiPropsWithAlias.jwtTokenSecret
lazy val (publicKey: RSAPublicKey, privateKey: RSAPrivateKey) = APIUtil.getPropsAsBoolValue("jwt.use.ssl", false) match {
case true =>

View File

@ -198,7 +198,8 @@ object Consent {
email = email,
userId = None,
createdByUserInvitationId = None,
company = None
company = None,
lastMarketingAgreementSignedDate = None
)
}
}

View File

@ -58,6 +58,8 @@ object ErrorMessages {
val InvalidRequestPayload = "OBP-09014: Incorrect request body Format, it should be a valid json that matches Validation rule."
val DynamicDataNotFound = "OBP-09015: Dynamic Data not found. Please specify a valid value."
val DuplicateQueryParameters = "OBP-09016: Duplicate Query Parameters are not allowed."
val DuplicateHeaderKeys = "OBP-09017: Duplicate Header Keys are not allowed."
// General messages (OBP-10XXX)
@ -87,8 +89,8 @@ object ErrorMessages {
val InvalidInBoundMapping = "OBP-10032: Incorrect inBoundMapping Format, it should be a json structure."
val invalidIban = "OBP-10033: Invalid IBAN."
val InvalidUrlParameters = "OBP-10034: Invalid URL parameters."
val InvalidUri = "OBP-10404: Request Not Found. The server has not found anything matching the Request-URI.Check your URL and the headers. " +
"NOTE: when it is POST or PUT api, the Content-Type must be `application/json`. OBP only support the json format body."
val InvalidUri = "OBP-10404: 404 Not Found. The server could not find the requested URI. Please double check your URL, headers and body. " +
"Note: When you are making a POST or PUT request, the Content-Type header MUST be `application/json`. Note: OBP only supports JSON formatted bodies."
val ResourceDoesNotExist = "OBP-10405: Resource does not exist."
val InvalidJsonValue = "OBP-10035: Incorrect json value."
@ -152,7 +154,7 @@ object ErrorMessages {
val GatewayLoginUnknownError = "OBP-20029: Unknown Gateway login error."
val GatewayLoginHostPropertyMissing = "OBP-20030: Property gateway.host is not defined."
val GatewayLoginWhiteListAddresses = "OBP-20031: Gateway login can be done only from allowed addresses."
val GatewayLoginJwtTokenIsNotValid = "OBP-20040: The JWT is corrupted/changed during a transport."
val GatewayLoginJwtTokenIsNotValid = "OBP-20040: The Gateway login JWT is corrupted/changed during a transport."
val GatewayLoginCannotExtractJwtToken = "OBP-20041: Header, Payload and Signature cannot be extracted from the JWT."
val GatewayLoginNoNeedToCallCbs = "OBP-20042: There is no need to call CBS"
val GatewayLoginCannotFindUser = "OBP-20043: User cannot be found. Please initiate CBS communication in order to create it."
@ -178,8 +180,20 @@ object ErrorMessages {
val FrequencyPerDayError = "OBP-20062: Frequency per day must be greater than 0."
val FrequencyPerDayMustBeOneError = "OBP-20063: Frequency per day must be equal to 1 in case of one-off access."
val UserIsDeleted = "OBP-20064: The user is deleted!"
val DAuthCannotGetOrCreateUser = "OBP-20065: Cannot get or create user during DAuth process."
val DAuthMissingParameters = "OBP-20066: These DAuth parameters are missing: "
val DAuthUnknownError = "OBP-20067: Unknown DAuth login error."
val DAuthHostPropertyMissing = "OBP-20068: Property dauth.host is not defined."
val DAuthWhiteListAddresses = "OBP-20069: DAuth login can be done only from allowed addresses."
val DAuthNoJwtForResponse = "OBP-20070: There is no useful value for JWT."
val DAuthJwtTokenIsNotValid = "OBP-20071: The DAuth JWT is corrupted/changed during a transport."
val InvalidDAuthHeaderToken = "OBP-20072: DAuth Header value should be one single string."
val UserNotSuperAdminOrMissRole = "OBP-20101: Current User is not super admin or is missing entitlements: "
val CannotGetOrCreateUser = "OBP-20102: Cannot get or create user."
val InvalidUserProvider = "OBP-20103: Invalid DAuth User Provider."
// OAuth 2
val ApplicationNotIdentified = "OBP-20200: The application cannot be identified. "
@ -344,6 +358,12 @@ object ErrorMessages {
val UpdateAtmLocationCategoriesException = "OBP-30095: Could not update the Atm Location Categories."
val CreateEndpointTagError = "OBP-30096: Could not insert the Endpoint Tag."
val UpdateEndpointTagError = "OBP-30097: Could not update the Endpoint Tag."
val UnknownEndpointTagError = "OBP-30098: Unknown Endpoint Tag error. "
val EndpointTagNotFoundByEndpointTagId = "OBP-30099: Invalid ENDPOINT_TAG_ID. Please specify a valid value for ENDPOINT_TAG_ID."
val EndpointTagAlreadyExists = "OBP-30100: EndpointTag already exists."
// Meetings
val MeetingsNotSupported = "OBP-30101: Meetings are not supported on this server."
val MeetingApiKeyNotConfigured = "OBP-30102: Meeting provider API Key is not configured."
@ -364,13 +384,17 @@ object ErrorMessages {
val InvalidAccountRoutings = "OBP-30114: Invalid Account Routings."
val AccountRoutingAlreadyExist = "OBP-30115: Account Routing already exist."
val InvalidPaymentSystemName = "OBP-30116: Invalid payment system name. The payment system name should only contain 0-9/a-z/A-Z/'-'/'.'/'_', the length should be smaller than 200."
val ProductFeeNotFoundById = "OBP-30117: Product Fee not found. Please specify a valid value for PRODUCT_FEE_ID."
val CreateProductFeeError = "OBP-30118: Could not insert the Product Fee."
val UpdateProductFeeError = "OBP-30119: Could not update the Product Fee."
val EntitlementIsBankRole = "OBP-30205: This entitlement is a Bank Role. Please set bank_id to a valid bank id."
val EntitlementIsSystemRole = "OBP-30206: This entitlement is a System Role. Please set bank_id to empty string."
val InvalidStrongPasswordFormat = "OBP-30207: Invalid Password Format. Your password should EITHER be at least 10 characters long and contain mixed numbers and both upper and lower case letters and at least one special character, OR be longer than 16 characters."
val InvalidStrongPasswordFormat = "OBP-30207: Invalid Password Format. Your password should EITHER be at least 10 characters long and contain mixed numbers and both upper and lower case letters and at least one special character, OR the length should be > 16 and <= 512."
val AccountIdAlreadyExists = "OBP-30208: Account_ID already exists at the Bank."
@ -388,6 +412,7 @@ object ErrorMessages {
val EntitlementRequestNotFound = "OBP-30215: EntitlementRequestId not found"
val EntitlementAlreadyExists = "OBP-30216: Entitlement already exists for the user."
val EntitlementCannotBeDeleted = "OBP-30219: EntitlementId cannot be deleted."
val EntitlementCannotBeGranted = "OBP-30220: Entitlement cannot be granted."
val CreateSystemViewError = "OBP-30250: Could not create the system view"
val DeleteSystemViewError = "OBP-30251: Could not delete the system view"
@ -478,7 +503,9 @@ object ErrorMessages {
val InvalidChargePolicy = "OBP-40013: Invalid Charge Policy. Please specify a valid value for Charge_Policy: SHARED, SENDER or RECEIVER. "
val AllowedAttemptsUsedUp = "OBP-40014: Sorry, you've used up your allowed attempts. "
val InvalidChallengeType = "OBP-40015: Invalid Challenge Type. Please specify a valid value for CHALLENGE_TYPE, when you create the transaction request."
val InvalidChallengeAnswer = "OBP-40016: Invalid Challenge Answer. Please specify a valid value for answer in Json body. If it is sandbox mode, the answer must be `123`. If it kafka mode, the answer can be got by phone message or other security ways."
val InvalidChallengeAnswer = "OBP-40016: Invalid Challenge Answer. Please specify a valid value for answer in Json body. " +
"If connector = mapped and transactionRequestType_OTP_INSTRUCTION_TRANSPORT = DUMMY and suggested_default_sca_method=DUMMY, the answer must be `123`. " +
"If connector = others, the challenge answer can be got by phone message or other security ways."
val InvalidPhoneNumber = "OBP-40017: Invalid Phone Number. Please specify a valid value for PHONE_NUMBER. Eg:+9722398746 "
val TransactionRequestsNotEnabled = "OBP-40018: Sorry, Transaction Requests are not enabled in this API instance."
val NextChallengePending = s"OBP-40019: Cannot create transaction due to transaction request is in status: ${NEXT_CHALLENGE_PENDING}."
@ -513,6 +540,7 @@ object ErrorMessages {
val DynamicMessageDocNotFound = "OBP-40043: DynamicMessageDoc not found, please specify valid DYNAMIC_MESSAGE_DOC_ID. "
val DynamicMessageDocDeleteError = "OBP-40044: DynamicMessageDoc can not be deleted. "
val DynamicCodeCompileFail = "OBP-40045: The code to do compile is illegal scala code, compilation failed. "
val InvalidOperationId = "OBP-40046: Invalid operation_id, please specify valid operation_id."
// Exceptions (OBP-50XXX)
val UnknownError = "OBP-50000: Unknown Error."
val FutureTimeoutException = "OBP-50001: Future Timeout Exception."
@ -555,7 +583,8 @@ object ErrorMessages {
val InvalidConnectorResponseForCreateChallenge = "OBP-50215: Connector did not return the set of challenge we requested."
val InvalidConnectorResponseForSaveDoubleEntryBookTransaction = "OBP-50216: The Connector did not return a valid response for saving double-entry transaction."
val InvalidConnectorResponseForCancelPayment = "OBP-50217: Connector did not return the transaction we requested."
val InvalidConnectorResponseForGetEndpointTags = "OBP-50218: Connector did not return the set of endpoint tags we requested."
val InvalidConnectorResponseForGetBankAccountsWithAttributes = "OBP-50219: Connector did not return the bank accounts we requested."
// Adapter Exceptions (OBP-6XXXX)
// Reserved for adapter (south of Kafka) messages

View File

@ -2,9 +2,10 @@ package code.api.util
import code.api.util.APIUtil.parseDate
import code.api.util.ErrorMessages.{InvalidJsonFormat, UserHasMissingRoles, UserNotLoggedIn, UnknownError}
import code.api.util.ErrorMessages.{InvalidJsonFormat, UnknownError, UserHasMissingRoles, UserNotLoggedIn}
import net.liftweb.json.JsonDSL._
import code.api.util.Glossary.{glossaryItems, makeGlossaryItem}
import code.apicollection.ApiCollection
import code.dynamicEntity.{DynamicEntityDefinition, DynamicEntityFooBar, DynamicEntityFullBarFields, DynamicEntityIntTypeExample, DynamicEntityStringTypeExample}
import com.openbankproject.commons.model.enums.{CustomerAttributeType, DynamicEntityFieldType}
import com.openbankproject.commons.util.ReflectUtils
@ -23,6 +24,7 @@ object ExampleValue {
val NoDescriptionProvided = "no-description-provided"
val NoExampleProvided = "no-example-provided"
val booleanTrue = "true"
lazy val bankIdGlossary = glossaryItems.find(_.title == "Bank.bank_id").map(_.textDescription)
@ -91,7 +93,7 @@ object ExampleValue {
lazy val dependentsExample = ConnectorField("1", s"the number of dependents")
glossaryItems += makeGlossaryItem("Customer.dependents", dependentsExample)
lazy val kycStatusExample = ConnectorField("true", s"This is boolean to indicate if the cusomter's KYC has been checked.")
lazy val kycStatusExample = ConnectorField(booleanTrue, s"This is boolean to indicate if the cusomter's KYC has been checked.")
glossaryItems += makeGlossaryItem("Customer.kycStatus", kycStatusExample)
lazy val urlExample = ConnectorField("http://www.example.com/id-docs/123/image.png", s"The URL ")
@ -99,7 +101,13 @@ object ExampleValue {
lazy val customerNumberExample = ConnectorField("5987953", s"The human friendly customer identifier that MUST uniquely identify the Customer at the Bank ID. Customer Number is NOT used in URLs.")
glossaryItems += makeGlossaryItem("Customer.customerNumber", customerNumberExample)
lazy val licenseIdExample = ConnectorField("ODbL-1.0", s"")
glossaryItems += makeGlossaryItem("License.id", licenseIdExample)
lazy val licenseNameExample = ConnectorField("Open Database License", s"")
glossaryItems += makeGlossaryItem("License.name", licenseNameExample)
lazy val customerAttributeIdExample = ConnectorField("7uy8a7e4-6d02-40e3-a129-0b2bf89de8uh", s"Customer attribute id")
glossaryItems += makeGlossaryItem("Customer.attributeId", customerAttributeIdExample)
@ -144,7 +152,7 @@ object ExampleValue {
lazy val otherAccountProviderExample = ConnectorField("", s"")//TODO, not sure what is this field for?
glossaryItems += makeGlossaryItem("Transaction.otherAccountProvider", otherAccountProviderExample)
lazy val isBeneficiaryExample = ConnectorField("true", s"This is a boolean. True if the originAccount can send money to the Counterparty")
lazy val isBeneficiaryExample = ConnectorField(booleanTrue, s"This is a boolean. True if the originAccount can send money to the Counterparty")
glossaryItems += makeGlossaryItem("Counterparty.isBeneficiary", isBeneficiaryExample)
lazy val counterpartyNameExample = ConnectorField("John Smith Ltd.", s"The name of a Counterparty. Ideally unique for an Account")
@ -232,6 +240,13 @@ object ExampleValue {
lazy val operationIdExample = ConnectorField("OBPv4.0.0-getBanks", "A uniquely identify the obp endpoint on OBP instance, you can get it from Get Resource endpoints.")
glossaryItems += makeGlossaryItem("ApiCollectionEndpoint.operationId", operationIdExample)
lazy val tagNameExample = ConnectorField("BankAccountTag1", "The endpoint tag name")
glossaryItems += makeGlossaryItem("EndpointTag.tagName", tagNameExample)
lazy val endpointTagIdExample = ConnectorField("7uy8a7e4-6d02-40e3-a129-0b2bf89de8uh", "A string that MUST uniquely identify the endpointTag on this OBP instance, can be used in all cache.")
glossaryItems += makeGlossaryItem("EndpointTag.endpointTagId", endpointTagIdExample)
lazy val accountTypeExample = ConnectorField("AC","A short code that represents the type of the account as provided by the bank.")
@ -457,7 +472,7 @@ object ExampleValue {
lazy val inboundAvroSchemaExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
glossaryItems += makeGlossaryItem("DynamicMessageDoc.inboundAvroSchema", inboundAvroSchemaExample)
lazy val canSeeImagesExample = ConnectorField("true",NoDescriptionProvided)
lazy val canSeeImagesExample = ConnectorField(booleanTrue,NoDescriptionProvided)
glossaryItems += makeGlossaryItem("can_see_images", canSeeImagesExample)
lazy val topConsumersExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
@ -466,10 +481,10 @@ object ExampleValue {
lazy val smsExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
glossaryItems += makeGlossaryItem("sms", smsExample)
lazy val maximumResponseTimeExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
lazy val maximumResponseTimeExample = ConnectorField("60",NoDescriptionProvided)
glossaryItems += makeGlossaryItem("maximum_response_time", maximumResponseTimeExample)
lazy val cancelledExample = ConnectorField("true",NoDescriptionProvided)
lazy val cancelledExample = ConnectorField(booleanTrue,NoDescriptionProvided)
glossaryItems += makeGlossaryItem("cancelled", cancelledExample)
lazy val entitlementRequestsExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
@ -502,7 +517,7 @@ object ExampleValue {
lazy val canAddCommentExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
glossaryItems += makeGlossaryItem("can_add_comment", canAddCommentExample)
lazy val frequencyExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
lazy val frequencyExample = ConnectorField("5",NoDescriptionProvided)
glossaryItems += makeGlossaryItem("frequency", frequencyExample)
lazy val ordersExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
@ -517,13 +532,13 @@ object ExampleValue {
lazy val canSeeOtherAccountRoutingSchemeExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
glossaryItems += makeGlossaryItem("can_see_other_account_routing_scheme", canSeeOtherAccountRoutingSchemeExample)
lazy val canDeleteCorporateLocationExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
lazy val canDeleteCorporateLocationExample = ConnectorField(booleanTrue,NoDescriptionProvided)
glossaryItems += makeGlossaryItem("can_delete_corporate_location", canDeleteCorporateLocationExample)
lazy val fromExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
glossaryItems += makeGlossaryItem("from", fromExample)
lazy val httpMethodExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
lazy val httpMethodExample = ConnectorField("GET",NoDescriptionProvided)
glossaryItems += makeGlossaryItem("http_method", httpMethodExample)
lazy val developerEmailExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
@ -541,7 +556,7 @@ object ExampleValue {
lazy val portsExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
glossaryItems += makeGlossaryItem("ports", portsExample)
lazy val perSecondExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
lazy val perSecondExample = ConnectorField("1000",NoDescriptionProvided)
glossaryItems += makeGlossaryItem("per_second", perSecondExample)
lazy val challengeExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
@ -550,7 +565,7 @@ object ExampleValue {
lazy val appNameExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
glossaryItems += makeGlossaryItem("app_name", appNameExample)
lazy val executionDateExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
lazy val executionDateExample = ConnectorField("2020-01-27",NoDescriptionProvided)
glossaryItems += makeGlossaryItem("execution_date", executionDateExample)
lazy val technologyExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
@ -586,13 +601,13 @@ object ExampleValue {
lazy val sandboxTanExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
glossaryItems += makeGlossaryItem("sandbox_tan", sandboxTanExample)
lazy val corporateLocationExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
lazy val corporateLocationExample = ConnectorField("10",NoDescriptionProvided)
glossaryItems += makeGlossaryItem("corporate_location", corporateLocationExample)
lazy val enabledExample = ConnectorField("true",NoDescriptionProvided)
lazy val enabledExample = ConnectorField(booleanTrue,NoDescriptionProvided)
glossaryItems += makeGlossaryItem("enabled", enabledExample)
lazy val durationExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
lazy val durationExample = ConnectorField("10",NoDescriptionProvided)
glossaryItems += makeGlossaryItem("duration", durationExample)
lazy val canSeeBankAccountTypeExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
@ -610,10 +625,10 @@ object ExampleValue {
lazy val accountAttributeIdExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
glossaryItems += makeGlossaryItem("account_attribute_id", accountAttributeIdExample)
lazy val closingTimeExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
lazy val closingTimeExample = ConnectorField("2020-01-27",NoDescriptionProvided)
glossaryItems += makeGlossaryItem("closing_time", closingTimeExample)
lazy val lastFailureDateExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
lazy val lastFailureDateExample = ConnectorField("2020-01-27",NoDescriptionProvided)
glossaryItems += makeGlossaryItem("last_failure_date", lastFailureDateExample)
lazy val whereExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
@ -664,7 +679,7 @@ object ExampleValue {
lazy val creatorExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
glossaryItems += makeGlossaryItem("creator", creatorExample)
lazy val activeExample = ConnectorField("true",NoDescriptionProvided)
lazy val activeExample = ConnectorField(booleanTrue,NoDescriptionProvided)
glossaryItems += makeGlossaryItem("active", activeExample)
lazy val canSeeOtherAccountMetadataExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
@ -811,7 +826,7 @@ object ExampleValue {
lazy val relatesToKycCheckIdExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
glossaryItems += makeGlossaryItem("relates_to_kyc_check_id", relatesToKycCheckIdExample)
lazy val productCodeExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
lazy val productCodeExample = ConnectorField("1234", NoDescriptionProvided)
glossaryItems += makeGlossaryItem("product_code", productCodeExample)
lazy val imageUrlExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
@ -850,7 +865,7 @@ object ExampleValue {
lazy val canSeeTransactionFinishDateExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
glossaryItems += makeGlossaryItem("can_see_transaction_finish_date", canSeeTransactionFinishDateExample)
lazy val satisfiedExample = ConnectorField("true",NoDescriptionProvided)
lazy val satisfiedExample = ConnectorField(booleanTrue,NoDescriptionProvided)
glossaryItems += makeGlossaryItem("satisfied", satisfiedExample)
lazy val canSeeOtherAccountIbanExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
@ -1186,8 +1201,8 @@ object ExampleValue {
lazy val productAttributeIdExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
glossaryItems += makeGlossaryItem("product_attribute_id", productAttributeIdExample)
lazy val isSystemExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
glossaryItems += makeGlossaryItem("is_system", isSystemExample)
lazy val isSystemExample = ConnectorField("true", "If the view is the system level, then it is true")
glossaryItems += makeGlossaryItem("view.is_system", isSystemExample)
lazy val detailsExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
glossaryItems += makeGlossaryItem("details", detailsExample)
@ -1213,8 +1228,8 @@ object ExampleValue {
lazy val roleNameExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
glossaryItems += makeGlossaryItem("role_name", roleNameExample)
lazy val refundExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
glossaryItems += makeGlossaryItem("refund", refundExample)
lazy val termsAndConditionsUrlExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
glossaryItems += makeGlossaryItem("terms_and_conditions_url_example", termsAndConditionsUrlExample)
lazy val canAddUrlExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
glossaryItems += makeGlossaryItem("can_add_url", canAddUrlExample)
@ -1285,9 +1300,6 @@ object ExampleValue {
lazy val chargeExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
glossaryItems += makeGlossaryItem("charge", chargeExample)
lazy val connectorExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
glossaryItems += makeGlossaryItem("connector", connectorExample)
lazy val kycDocumentIdExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
glossaryItems += makeGlossaryItem("kyc_document_id", kycDocumentIdExample)
@ -1297,7 +1309,7 @@ object ExampleValue {
lazy val webUiPropsIdExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
glossaryItems += makeGlossaryItem("web_ui_props_id", webUiPropsIdExample)
lazy val providerExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
lazy val providerExample = ConnectorField("ETHEREUM","the provider name ")
glossaryItems += makeGlossaryItem("provider", providerExample)
lazy val canSeePhysicalLocationExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
@ -1360,6 +1372,9 @@ object ExampleValue {
lazy val parentProductCodeExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
glossaryItems += makeGlossaryItem("parent_product_code", parentProductCodeExample)
lazy val productNameExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
glossaryItems += makeGlossaryItem("product_name", productNameExample)
lazy val numberOfCheckbooksExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
glossaryItems += makeGlossaryItem("number_of_checkbooks", numberOfCheckbooksExample)
@ -1417,7 +1432,7 @@ object ExampleValue {
lazy val isFirehoseExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
glossaryItems += makeGlossaryItem("is_firehose", isFirehoseExample)
lazy val okExample = ConnectorField("true",NoDescriptionProvided)
lazy val okExample = ConnectorField(booleanTrue,NoDescriptionProvided)
glossaryItems += makeGlossaryItem("ok", okExample)
lazy val bankRoutingExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
@ -1441,7 +1456,7 @@ object ExampleValue {
lazy val dependentEndpointsExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
glossaryItems += makeGlossaryItem("dependent_endpoints", dependentEndpointsExample)
lazy val hasDepositCapabilityExample = ConnectorField("true",NoDescriptionProvided)
lazy val hasDepositCapabilityExample = ConnectorField(booleanTrue,NoDescriptionProvided)
glossaryItems += makeGlossaryItem("ATM.has_deposit_capability", hasDepositCapabilityExample)
lazy val toCounterpartyExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
@ -1465,10 +1480,10 @@ object ExampleValue {
lazy val canSeeCommentsExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
glossaryItems += makeGlossaryItem("can_see_comments", canSeeCommentsExample)
lazy val canEditOwnerCommentExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
lazy val canEditOwnerCommentExample = ConnectorField(booleanTrue,NoDescriptionProvided)
glossaryItems += makeGlossaryItem("can_edit_owner_comment", canEditOwnerCommentExample)
lazy val canAddCounterpartyExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
lazy val canAddCounterpartyExample = ConnectorField(booleanTrue,NoDescriptionProvided)
glossaryItems += makeGlossaryItem("can_add_counterparty", canAddCounterpartyExample)
lazy val markdownExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
@ -1495,19 +1510,19 @@ object ExampleValue {
lazy val accountRoutingExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
glossaryItems += makeGlossaryItem("account_routing", accountRoutingExample)
lazy val requestedCurrentRateAmount2Example = ConnectorField(NoExampleProvided,NoDescriptionProvided)
lazy val requestedCurrentRateAmount2Example = ConnectorField("20",NoDescriptionProvided)
glossaryItems += makeGlossaryItem("requested_current_rate_amount2", requestedCurrentRateAmount2Example)
lazy val narrativeExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
glossaryItems += makeGlossaryItem("narrative", narrativeExample)
lazy val canSeeOtherAccountRoutingAddressExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
lazy val canSeeOtherAccountRoutingAddressExample = ConnectorField(booleanTrue,NoDescriptionProvided)
glossaryItems += makeGlossaryItem("can_see_other_account_routing_address", canSeeOtherAccountRoutingAddressExample)
lazy val statusesExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
glossaryItems += makeGlossaryItem("statuses", statusesExample)
lazy val callsMadeExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
lazy val callsMadeExample = ConnectorField("50",NoDescriptionProvided)
glossaryItems += makeGlossaryItem("calls_made", callsMadeExample)
lazy val currentStateExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
@ -1519,10 +1534,10 @@ object ExampleValue {
lazy val customersExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
glossaryItems += makeGlossaryItem("customers", customersExample)
lazy val scheduledDateExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
lazy val scheduledDateExample = ConnectorField("2020-01-27",NoDescriptionProvided)
glossaryItems += makeGlossaryItem("scheduled_date", scheduledDateExample)
lazy val allowedAttemptsExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
lazy val allowedAttemptsExample = ConnectorField("5",NoDescriptionProvided)
glossaryItems += makeGlossaryItem("allowed_attempts", allowedAttemptsExample)
lazy val hostedByExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
@ -1540,7 +1555,7 @@ object ExampleValue {
lazy val tuesdayExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
glossaryItems += makeGlossaryItem("tuesday", tuesdayExample)
lazy val canQueryAvailableFundsExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
lazy val canQueryAvailableFundsExample = ConnectorField(booleanTrue,NoDescriptionProvided)
glossaryItems += makeGlossaryItem("can_query_available_funds", canQueryAvailableFundsExample)
lazy val otherAccountSecondaryRoutingSchemeExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
@ -1579,7 +1594,7 @@ object ExampleValue {
lazy val cardNumberExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
glossaryItems += makeGlossaryItem("card_number", cardNumberExample)
lazy val instructedamountExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
lazy val instructedamountExample = ConnectorField("100",NoDescriptionProvided)
glossaryItems += makeGlossaryItem("instructedamount", instructedamountExample)
lazy val userCustomerLinkIdExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
@ -1588,15 +1603,18 @@ object ExampleValue {
lazy val outboundTopicExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
glossaryItems += makeGlossaryItem("outbound_topic", outboundTopicExample)
lazy val postCodeExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
lazy val postCodeExample = ConnectorField("789",NoDescriptionProvided)
glossaryItems += makeGlossaryItem("post_code", postCodeExample)
lazy val superFamilyExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
glossaryItems += makeGlossaryItem("super_family", superFamilyExample)
lazy val nameExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
lazy val nameExample = ConnectorField("ACCOUNT_MANAGEMENT_FEE",NoDescriptionProvided)
glossaryItems += makeGlossaryItem("name", nameExample)
lazy val productFeeIdExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
glossaryItems += makeGlossaryItem("product_fee_id", nameExample)
lazy val emailAddressExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
glossaryItems += makeGlossaryItem("email_address", emailAddressExample)
@ -1612,7 +1630,7 @@ object ExampleValue {
lazy val roleExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
glossaryItems += makeGlossaryItem("role", roleExample)
lazy val requireScopesForListedRolesExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
lazy val requireScopesForListedRolesExample = ConnectorField(booleanTrue,NoDescriptionProvided)
glossaryItems += makeGlossaryItem("require_scopes_for_listed_roles", requireScopesForListedRolesExample)
lazy val branchTypeExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
@ -1621,10 +1639,10 @@ object ExampleValue {
lazy val fullNameExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
glossaryItems += makeGlossaryItem("full_name", fullNameExample)
lazy val canCreateDirectDebitExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
lazy val canCreateDirectDebitExample = ConnectorField(booleanTrue,NoDescriptionProvided)
glossaryItems += makeGlossaryItem("can_create_direct_debit", canCreateDirectDebitExample)
lazy val futureDateExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
lazy val futureDateExample = ConnectorField("2020-01-27",NoDescriptionProvided)
glossaryItems += makeGlossaryItem("future_date", futureDateExample)
lazy val toTransferToAccountExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
@ -1639,7 +1657,7 @@ object ExampleValue {
lazy val documentNumberExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
glossaryItems += makeGlossaryItem("document_number", documentNumberExample)
lazy val canSeeOtherAccountNationalIdentifierExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
lazy val canSeeOtherAccountNationalIdentifierExample = ConnectorField(booleanTrue,NoDescriptionProvided)
glossaryItems += makeGlossaryItem("can_see_other_account_national_identifier", canSeeOtherAccountNationalIdentifierExample)
lazy val canSeeTransactionStartDateExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
@ -1651,7 +1669,7 @@ object ExampleValue {
lazy val cacheExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
glossaryItems += makeGlossaryItem("cache", cacheExample)
lazy val canSeeBankRoutingAddressExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
lazy val canSeeBankRoutingAddressExample = ConnectorField(booleanTrue,NoDescriptionProvided)
glossaryItems += makeGlossaryItem("can_see_bank_routing_address", canSeeBankRoutingAddressExample)
lazy val usersExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
@ -1663,7 +1681,7 @@ object ExampleValue {
lazy val ktyExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
glossaryItems += makeGlossaryItem("kty", ktyExample)
lazy val canBeSeenOnViewsExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
lazy val canBeSeenOnViewsExample = ConnectorField(booleanTrue,NoDescriptionProvided)
glossaryItems += makeGlossaryItem("can_be_seen_on_views", canBeSeenOnViewsExample)
lazy val fromPersonExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
@ -1675,7 +1693,7 @@ object ExampleValue {
lazy val createdByUserExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
glossaryItems += makeGlossaryItem("created_by_user", createdByUserExample)
lazy val taxNumberExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
lazy val taxNumberExample = ConnectorField("456",NoDescriptionProvided)
glossaryItems += makeGlossaryItem("tax_number", taxNumberExample)
lazy val presentExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
@ -1684,7 +1702,7 @@ object ExampleValue {
lazy val metadataExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
glossaryItems += makeGlossaryItem("metadata", metadataExample)
lazy val canSeeTransactionAmountExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
lazy val canSeeTransactionAmountExample = ConnectorField(booleanTrue,NoDescriptionProvided)
glossaryItems += makeGlossaryItem("can_see_transaction_amount", canSeeTransactionAmountExample)
lazy val methodRoutingIdExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
@ -1702,16 +1720,16 @@ object ExampleValue {
lazy val bespokeExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
glossaryItems += makeGlossaryItem("bespoke", bespokeExample)
lazy val codeExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
lazy val codeExample = ConnectorField("125",NoDescriptionProvided)
glossaryItems += makeGlossaryItem("code", codeExample)
lazy val countryCodeExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
lazy val countryCodeExample = ConnectorField("1254",NoDescriptionProvided)
glossaryItems += makeGlossaryItem("country_code", countryCodeExample)
lazy val canSeeBankAccountCreditLimitExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
lazy val canSeeBankAccountCreditLimitExample = ConnectorField(booleanTrue,NoDescriptionProvided)
glossaryItems += makeGlossaryItem("can_see_bank_account_credit_limit", canSeeBankAccountCreditLimitExample)
lazy val canSeeOtherAccountNumberExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
lazy val canSeeOtherAccountNumberExample = ConnectorField(booleanTrue,NoDescriptionProvided)
glossaryItems += makeGlossaryItem("can_see_other_account_number", canSeeOtherAccountNumberExample)
lazy val orderExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
@ -1729,10 +1747,10 @@ object ExampleValue {
lazy val taxResidenceExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
glossaryItems += makeGlossaryItem("tax_residence", taxResidenceExample)
lazy val isActiveExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
lazy val isActiveExample = ConnectorField(booleanTrue,NoDescriptionProvided)
glossaryItems += makeGlossaryItem("is_active", isActiveExample)
lazy val canSeeBankAccountBankNameExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
lazy val canSeeBankAccountBankNameExample = ConnectorField(booleanTrue,NoDescriptionProvided)
glossaryItems += makeGlossaryItem("can_see_bank_account_bank_name", canSeeBankAccountBankNameExample)
lazy val firstNameExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
@ -1747,19 +1765,19 @@ object ExampleValue {
lazy val transactionIdsExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
glossaryItems += makeGlossaryItem("transaction_ids", transactionIdsExample)
lazy val canSeeBankAccountOwnersExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
lazy val canSeeBankAccountOwnersExample = ConnectorField(booleanTrue,NoDescriptionProvided)
glossaryItems += makeGlossaryItem("can_see_bank_account_owners", canSeeBankAccountOwnersExample)
lazy val actualDateExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
lazy val actualDateExample = ConnectorField("2020-01-27",NoDescriptionProvided)
glossaryItems += makeGlossaryItem("actual_date", actualDateExample)
lazy val exampleOutboundMessageExample = ConnectorField("{}","this will the json object")
glossaryItems += makeGlossaryItem("example_outbound_message", exampleOutboundMessageExample)
lazy val canDeleteWhereTagExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
lazy val canDeleteWhereTagExample = ConnectorField(booleanTrue,NoDescriptionProvided)
glossaryItems += makeGlossaryItem("can_delete_where_tag", canDeleteWhereTagExample)
lazy val canSeeUrlExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
lazy val canSeeUrlExample = ConnectorField(booleanTrue,NoDescriptionProvided)
glossaryItems += makeGlossaryItem("can_see_url", canSeeUrlExample)
lazy val versionExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
@ -1774,10 +1792,10 @@ object ExampleValue {
lazy val allowedActionsExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
glossaryItems += makeGlossaryItem("allowed_actions", allowedActionsExample)
lazy val rankAmount1Example = ConnectorField(NoExampleProvided,NoDescriptionProvided)
lazy val rankAmount1Example = ConnectorField("100",NoDescriptionProvided)
glossaryItems += makeGlossaryItem("rank_amount1", rankAmount1Example)
lazy val durationTimeExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
lazy val durationTimeExample = ConnectorField("60",NoDescriptionProvided)
glossaryItems += makeGlossaryItem("duration_time", durationTimeExample)
lazy val noneExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
@ -1786,13 +1804,13 @@ object ExampleValue {
lazy val implementedInVersionExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
glossaryItems += makeGlossaryItem("implemented_in_version", implementedInVersionExample)
lazy val canSeeImageUrlExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
lazy val canSeeImageUrlExample = ConnectorField(booleanTrue,NoDescriptionProvided)
glossaryItems += makeGlossaryItem("can_see_image_url", canSeeImageUrlExample)
lazy val toTransferToPhoneExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
glossaryItems += makeGlossaryItem("to_transfer_to_phone", toTransferToPhoneExample)
lazy val perDayExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
lazy val perDayExample = ConnectorField("4000",NoDescriptionProvided)
glossaryItems += makeGlossaryItem("per_day", perDayExample)
lazy val elasticSearchExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
@ -1801,7 +1819,7 @@ object ExampleValue {
lazy val reasonRequestedExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
glossaryItems += makeGlossaryItem("reason_requested", reasonRequestedExample)
lazy val perWeekExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
lazy val perWeekExample = ConnectorField("50000",NoDescriptionProvided)
glossaryItems += makeGlossaryItem("per_week", perWeekExample)
lazy val productsExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
@ -1819,7 +1837,7 @@ object ExampleValue {
lazy val apiVersionExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
glossaryItems += makeGlossaryItem("api_version", apiVersionExample)
lazy val perSecondCallLimitExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
lazy val perSecondCallLimitExample = ConnectorField("10",NoDescriptionProvided)
glossaryItems += makeGlossaryItem("per_second_call_limit", perSecondCallLimitExample)
lazy val messagesExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
@ -1831,19 +1849,19 @@ object ExampleValue {
lazy val eExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
glossaryItems += makeGlossaryItem("e", eExample)
lazy val canSeeCorporateLocationExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
lazy val canSeeCorporateLocationExample = ConnectorField(booleanTrue,NoDescriptionProvided)
glossaryItems += makeGlossaryItem("can_see_corporate_location", canSeeCorporateLocationExample)
lazy val userExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
glossaryItems += makeGlossaryItem("user", userExample)
lazy val lastLockDateExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
lazy val lastLockDateExample = ConnectorField("2020-01-27",NoDescriptionProvided)
glossaryItems += makeGlossaryItem("last_lock_date", lastLockDateExample)
lazy val requestedCurrentRateAmount1Example = ConnectorField(NoExampleProvided,NoDescriptionProvided)
glossaryItems += makeGlossaryItem("requested_current_rate_amount1", requestedCurrentRateAmount1Example)
lazy val toCurrencyCodeExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
lazy val toCurrencyCodeExample = ConnectorField("EUR",NoDescriptionProvided)
glossaryItems += makeGlossaryItem("to_currency_code", toCurrencyCodeExample)
lazy val dobOfDependantsExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
@ -1876,10 +1894,10 @@ object ExampleValue {
lazy val mondayExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
glossaryItems += makeGlossaryItem("monday", mondayExample)
lazy val requiredfieldinfoExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
lazy val requiredfieldinfoExample = ConnectorField("false",NoDescriptionProvided)
glossaryItems += makeGlossaryItem("requiredfieldinfo", requiredfieldinfoExample)
lazy val canSeeWhereTagExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
lazy val canSeeWhereTagExample = ConnectorField(booleanTrue,NoDescriptionProvided)
glossaryItems += makeGlossaryItem("can_see_where_tag", canSeeWhereTagExample)
lazy val fromDepartmentExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
@ -1891,7 +1909,7 @@ object ExampleValue {
lazy val otherAccountSecondaryRoutingAddressExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
glossaryItems += makeGlossaryItem("other_account_secondary_routing_address", otherAccountSecondaryRoutingAddressExample)
lazy val perMonthExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
lazy val perMonthExample = ConnectorField("500",NoDescriptionProvided)
glossaryItems += makeGlossaryItem("per_month", perMonthExample)
lazy val inboundTopicExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
@ -1948,10 +1966,10 @@ object ExampleValue {
lazy val toSandboxTanExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
glossaryItems += makeGlossaryItem("to_sandbox_tan", toSandboxTanExample)
lazy val canAddTagExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
lazy val canAddTagExample = ConnectorField(booleanTrue,NoDescriptionProvided)
glossaryItems += makeGlossaryItem("can_add_tag", canAddTagExample)
lazy val canSeeBankAccountLabelExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
lazy val canSeeBankAccountLabelExample = ConnectorField(booleanTrue,NoDescriptionProvided)
glossaryItems += makeGlossaryItem("can_see_bank_account_label", canSeeBankAccountLabelExample)
lazy val serviceAvailableExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
@ -1975,7 +1993,7 @@ object ExampleValue {
lazy val driveUpExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
glossaryItems += makeGlossaryItem("drive_up", driveUpExample)
lazy val canAddMoreInfoExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
lazy val canAddMoreInfoExample = ConnectorField(booleanTrue,NoDescriptionProvided)
glossaryItems += makeGlossaryItem("can_add_more_info", canAddMoreInfoExample)
lazy val detailExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
@ -1987,13 +2005,13 @@ object ExampleValue {
lazy val transactionRequestTypesExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
glossaryItems += makeGlossaryItem("transaction_request_types", transactionRequestTypesExample)
lazy val canAddImageUrlExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
lazy val canAddImageUrlExample = ConnectorField(booleanTrue,NoDescriptionProvided)
glossaryItems += makeGlossaryItem("can_add_image_url", canAddImageUrlExample)
lazy val jwksUrisExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
glossaryItems += makeGlossaryItem("jwks_uris", jwksUrisExample)
lazy val canSeeOtherAccountSwiftBicExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
lazy val canSeeOtherAccountSwiftBicExample = ConnectorField(booleanTrue,NoDescriptionProvided)
glossaryItems += makeGlossaryItem("can_see_other_account_swift_bic", canSeeOtherAccountSwiftBicExample)
lazy val staffUserIdExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
@ -2005,7 +2023,7 @@ object ExampleValue {
lazy val validFromExample = ConnectorField("2020-01-27",NoDescriptionProvided)
glossaryItems += makeGlossaryItem("valid_from", validFromExample)
lazy val canDeleteImageExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
lazy val canDeleteImageExample = ConnectorField(booleanTrue,NoDescriptionProvided)
glossaryItems += makeGlossaryItem("can_delete_image", canDeleteImageExample)
lazy val toExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
@ -2017,25 +2035,25 @@ object ExampleValue {
lazy val productAttributesExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
glossaryItems += makeGlossaryItem("product_attributes", productAttributesExample)
lazy val canSeeTransactionDescriptionExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
lazy val canSeeTransactionDescriptionExample = ConnectorField(booleanTrue,NoDescriptionProvided)
glossaryItems += makeGlossaryItem("can_see_transaction_description", canSeeTransactionDescriptionExample)
lazy val faceImageExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
glossaryItems += makeGlossaryItem("face_image", faceImageExample)
lazy val canSeeBankAccountNumberExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
lazy val canSeeBankAccountNumberExample = ConnectorField(booleanTrue,NoDescriptionProvided)
glossaryItems += makeGlossaryItem("can_see_bank_account_number", canSeeBankAccountNumberExample)
lazy val glossaryItemsExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
glossaryItems += makeGlossaryItem("glossary_items", glossaryItemsExample)
lazy val isBankIdExactMatchExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
lazy val isBankIdExactMatchExample = ConnectorField(booleanTrue,NoDescriptionProvided)
glossaryItems += makeGlossaryItem("is_bank_id_exact_match", isBankIdExactMatchExample)
lazy val isPublicExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
lazy val isPublicExample = ConnectorField(booleanTrue,NoDescriptionProvided)
glossaryItems += makeGlossaryItem("is_public", isPublicExample)
lazy val isAccessibleExample = ConnectorField("true",NoDescriptionProvided)
lazy val isAccessibleExample = ConnectorField(booleanTrue,NoDescriptionProvided)
glossaryItems += makeGlossaryItem("ATM.is_accessible", isAccessibleExample)
lazy val entitlementIdExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
@ -2044,11 +2062,11 @@ object ExampleValue {
lazy val indexExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
glossaryItems += makeGlossaryItem("index", indexExample)
lazy val descriptionExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
lazy val descriptionExample = ConnectorField(s"This an optional field. Maximum length is ${ApiCollection.Description.maxLen}. It can be any characters here.","The human readable description here.")
glossaryItems += makeGlossaryItem("description", descriptionExample)
lazy val dynamicResourceDocdescriptionExample = ConnectorField("Create one User", "the description for this endpoint")
glossaryItems += makeGlossaryItem("DynamicResourceDoc.description", dynamicResourceDocdescriptionExample)
lazy val dynamicResourceDocDescriptionExample = ConnectorField("Create one User", "the description for this endpoint")
glossaryItems += makeGlossaryItem("DynamicResourceDoc.description", dynamicResourceDocDescriptionExample)
lazy val canDeleteCommentExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
glossaryItems += makeGlossaryItem("can_delete_comment", canDeleteCommentExample)

View File

@ -2,13 +2,38 @@ package code.api.util
import code.api.util.APIUtil.{getOAuth2ServerUrl, getObpApiRoot, getServerUrl}
import code.api.util.ExampleValue.{accountIdExample, bankIdExample, customerIdExample, userIdExample}
import code.util.Helper.MdcLoggable
import scala.collection.mutable.ArrayBuffer
import code.webuiprops.MappedWebUiPropsProvider.getWebUiPropsValue
object Glossary {
object Glossary extends MdcLoggable {
def getGlossaryItem(title: String): String = {
logger.debug(s"getGlossaryItem says Hello. title to find is: $title")
val something = glossaryItems.find(_.title.toLowerCase == title.toLowerCase) match {
case Some(foundItem) =>
/**
* Two important rules:
* 1. Make sure you have an **empty line** after the closing `</summary>` tag, otherwise the markdown/code blocks won't show correctly.
* 2. Make sure you have an **empty line** after the closing `</details>` tag if you have multiple collapsible sections.
*/
s"""
|<details>
| <summary style="display:list-item;cursor:s-resize;">${foundItem.title}</summary>
|
| ${foundItem.htmlDescription}
|</details>
|<br></br>
|""".stripMargin
case None => ""
}
logger.debug(s"getGlossaryItem says the text to return is $something")
something
}
// reason of description is function: because we want make description is dynamic, so description can read
// webui_ props dynamic instead of a constant string.
@ -96,7 +121,7 @@ object Glossary {
title = "Cheat Sheet",
description =
s"""
|## A selection of links to get you started using the Open Bank Project API platform, applications and tools.
|### A selection of links to get you started using the Open Bank Project API platform, applications and tools.
|
|[OBP API Installation](https://github.com/OpenBankProject/OBP-API/blob/develop/README.md)
|
@ -409,42 +434,75 @@ object Glossary {
// ***Note***! Don't use "--" (double hyphen) in the description because API Explorer scala.xml.XML.loadString cannot parse.
glossaryItems += GlossaryItem(
title = "Connector",
description =
s"""In OBP, we use the term "Connector" to mean the Scala/Java/Other JVM code in OBP that connects directly or indirectly to the systems of record i.e. the Core Banking Systems, Payment Systems and Databases.
s"""In OBP, most internal functions / methods can have different implementations which follow the same interface.
|
|Several Connectors are present in the OBP source code and all must implement the Connector interface
|- but, except when using the Star Connector, only one of them is active at any one time.
|
| The active connector is defined in the OBP Props file.
|
| A "Direct Connector" is considered to be one that talks directly to the system of record or existing service layer.
|
| i.e. API -> Connector -> CBS
|
| An "Indirect Connector" is considered one which pairs with an Adapter which in turn talks to the system of record or service layer.
|
| i.e. API -> Connector -> Adapter -> CBS
|
| The advantage of a Direct connector is that its perhaps simpler. The disadvantage is that you have to code in a JVM language, understand a bit about OBP internals and a bit of Scala.
|
| The advantage of the Indirect Connector is that you can write the Adapter in any language and the Connector and Adapter are decoupled (you just have to respect the Outbound / Inbound message format).
|
| The default Connector in OBP is a Direct Connector called "mapped". It is called the "mapped" connector because it talks directly to the OBP database (Postgres, MySQL, Oracle, MSSQL etc.) via the Liftweb ORM which is called Mapper.
|
|If you want to create your own (Direct) Connector you can fork any of the connectors within OBP.
|
|
| There is a special Connector called the Star Connector which can use functions from all the normal connectors.
|
| Using the Star Connector we can dynamically reroute function calls to different Connectors per function per bank_id.
|
| The OBP API Manager has a GUI to manage this or you can use the OBP Method Routing APIs to set destinations for each function call.
|
| Note: We generate the source code for individual connectors automatically.
|
|"""
|These functions are called connector methods and their implementations.
|
|The default implementation of the connector is the "mapped" connector.
|
|It's called "mapped" because the default datasource on OBP is a relational database, and access to that database is always done through an Object-Relational Mapper (ORM) called Mapper (from a framework we use called Liftweb).
|
|
|<pre>
|[=============] [============] [============]
|[.............] [ ] [ ]
|[...OBP API...] ===> OBP Endpoints call connector functions (aka methods) ===> [ Connector ] ===> [ Database ]
|[.............] The default implementation is called "Mapped" [ (Mapped) ] [ (Adapter) ]
|[=============] The Mapped Connector talks to a Database [============] [============]
|
|</pre>
|
|However, there are multiple available connector implementations - and you can also mix and create your own.|
|
|E.g. Kafka
|
|<pre>
|[=============] [============] [============] [============] [============]
|[ ] [ ] [ ] [ ] [ ]
|[ OBP API ] ===> Kafka Connector ===> [ Kafka ] ===> [ Kafka ] [ OBP Kafka ] ===> [ CBS ]
|[ ] Puts OBP Messages [ Connector ] [ Cluster ] [ Adapter ] [ ]
|[=============] onto a Kafka [============] [============] [============] [============]
|
|</pre>
|
|
|
|You can mix and match them using the Star connector and you can write your own in Scala. You can also write Adapters in any language which respond to messages sent by the connector.
|
|we use the term "Connector" to mean the Scala/Java/Other JVM code in OBP that connects directly or indirectly to the systems of record i.e. the Core Banking Systems, Payment Systems and Databases.
|
|
| A "Direct Connector" is considered to be one that talks directly to the system of record or existing service layer.
|
| i.e. API -> Connector -> CBS
|
| An "Indirect Connector" is considered one which pairs with an Adapter which in turn talks to the system of record or service layer.
|
| i.e. API -> Connector -> Adapter -> CBS
|
| The advantage of a Direct connector is that its perhaps simpler. The disadvantage is that you have to code in a JVM language, understand a bit about OBP internals and a bit of Scala.
|
| The advantage of the Indirect Connector is that you can write the Adapter in any language and the Connector and Adapter are decoupled (you just have to respect the Outbound / Inbound message format).
|
| The default Connector in OBP is a Direct Connector called "mapped". It is called the "mapped" connector because it talks directly to the OBP database (Postgres, MySQL, Oracle, MSSQL etc.) via the Liftweb ORM which is called Mapper.
|
|If you want to create your own (Direct) Connector you can fork any of the connectors within OBP.
|
|
| There is a special Connector called the Star Connector which can use functions from all the normal connectors.
|
| Using the Star Connector we can dynamically reroute function calls to different Connectors per function per bank_id.
|
| The OBP API Manager has a GUI to manage this or you can use the OBP Method Routing APIs to set destinations for each function call.
|
| Note: We generate the source code for individual connectors automatically.
|
|"""
)
@ -577,8 +635,21 @@ object Glossary {
title = "Bank",
description =
"""
|The entity that represents the financial institution or bank within a financial group.
|Open Bank Project is a multi-bank API. Each bank resource contains basic identifying information such as name, logo and website.
|A Bank (aka Space) represents a financial institution, brand or organizational unit under which resources such as endpoints and entities exist.
|
|Both standard entities (e.g. financial products and bank accounts in the OBP standard) and dynamic entities and endpoints (created by you or your organisation) can exist at the Bank level.
|
|For example see [Bank/Space level Dynamic Entities](/?version=OBPv4.0.0&operation_id=OBPv4_0_0-createBankLevelDynamicEntity) and [Bank/Space level Dynamic Endpoints](http://localhost:8082/?version=OBPv4.0.0&operation_id=OBPv4_0_0-createBankLevelDynamicEndpoint)
|
|The Bank is important because many Roles can be granted at the Bank level. In this way, it's possible to create segregated or partitioned sets of endpoints and data structures in a single OBP instance.
|
|A User creating a Bank (if they have the right so to do), automatically gets the Entitlement to grant any Role for that Bank. Thus the creator of a Bank / Space becomes the "god" of that Bank / Space.
|
|Basic attributes for the bank resource include identifying information such as name, logo and website.
|
|Using the OBP endpoints for bank accounts it's possible to view accounts at one Bank or aggregate accounts from all Banks connected to the OBP instance.
|
|See also Props settings named "brand".
""")
@ -1773,10 +1844,10 @@ object Glossary {
|# Define comma separated list of allowed IP addresses
|# gateway.host=127.0.0.1
|# Define secret used to validate JWT token
|# gateway.token_secret=secret
|# jwt.token_secret=your-at-least-256-bit-secret-token
|# -------------------------------------- Gateway login --
|```
|Please keep in mind that property gateway.token_secret is used to validate JWT token to check it is not changed or corrupted during transport.
|Please keep in mind that property jwt.token_secret is used to validate JWT token to check it is not changed or corrupted during transport.
|
|### 2) Create / have access to a JWT
|
@ -1810,15 +1881,15 @@ object Glossary {
| base64UrlEncode(header) + "." +
| base64UrlEncode(payload),
|
|) secret base64 encoded
|) your-at-least-256-bit-secret-token
|```
|
|Here is the above example token:
|
|```
|eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
|AS8D76F7A89S87D6F7A9SD876FA789SD78F6A7S9D78F6AS79DF87A6S7D9F7A6S7D9F78A6SD798F78679D786S789D78F6A7S9D78F6AS79DF876A7S89DF786AS9D87F69AS7D6FN1bWVyIn0.
|KEuvjv3dmwkOhQ3JJ6dIShK8CG_fd2REApOGn1TRmgU
|eyJsb2dpbl91c2VyX25hbWUiOiJ1c2VybmFtZSIsImlzX2ZpcnN0IjpmYWxzZSwiYXBwX2lkIjoiODVhOTY1ZjAtMGQ1NS00ZTBhLThiMWMtNjQ5YzRiMDFjNGZiIiwiYXBwX25hbWUiOiJHV0wiLCJ0aW1lX3N0YW1wIjoiMjAxOC0wOC0yMFQxNDoxMzo0MFoiLCJjYnNfdG9rZW4iOiJ5b3VyX3Rva2VuIiwiY2JzX2lkIjoieW91cl9jYnNfaWQiLCJzZXNzaW9uX2lkIjoiMTIzNDU2Nzg5In0.
|bfWGWttEEcftiqrb71mE6Xy1tT_I-gmDPgjzvn6kC_k
|```
|
|
@ -1853,8 +1924,8 @@ object Glossary {
|
|```
|curl -v -H 'Authorization: GatewayLogin token="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
|AS8D76F7A89S87D6F7A9SD876FA789SD78F6A7S9D78F6AS79DF87A6S7D9F7A6S7D9F78A6SD798F78679D786S789D78F6A7S9D78F6AS79DF876A7S89DF786AS9D87F69AS7D6FN1bWVyIn0.
|KEuvjv3dmwkOhQ3JJ6dIShK8CG_fd2REApOGn1TRmgU" $getServerUrl/obp/v3.0.0/users/current
|eyJsb2dpbl91c2VyX25hbWUiOiJ1c2VybmFtZSIsImlzX2ZpcnN0IjpmYWxzZSwiYXBwX2lkIjoiODVhOTY1ZjAtMGQ1NS00ZTBhLThiMWMtNjQ5YzRiMDFjNGZiIiwiYXBwX25hbWUiOiJHV0wiLCJ0aW1lX3N0YW1wIjoiMjAxOC0wOC0yMFQxNDoxMzo0MFoiLCJjYnNfdG9rZW4iOiJ5b3VyX3Rva2VuIiwiY2JzX2lkIjoieW91cl9jYnNfaWQiLCJzZXNzaW9uX2lkIjoiMTIzNDU2Nzg5In0.
|bfWGWttEEcftiqrb71mE6Xy1tT_I-gmDPgjzvn6kC_k"' $getServerUrl/obp/v3.0.0/users/current
|```
|
|
@ -1891,12 +1962,12 @@ object Glossary {
|```
|import jwt
|from datetime import datetime, timezone
|from obp_python.config import obp_api_host
|import requests
|
|env = 'local'
|DATE_FORMAT = '%Y-%m-%dT%H:%M:%SZ'
|
|obp_api_host = 'https://yourhost.com'
|payload = {
| "login_user_name": "username",
| "is_first": False,
@ -1909,7 +1980,7 @@ object Glossary {
|}
|
|
|token = jwt.encode(payload, 'secretsecretsecretstsecretssssss', algorithm='HS256')
|token = jwt.encode(payload, 'your-at-least-256-bit-secret-token', algorithm='HS256').decode("utf-8")
|authorization = 'GatewayLogin token="{}"'.format(token)
|headers = {'Authorization': authorization}
|url = obp_api_host + '/obp/v4.0.0/users/current'
@ -1945,6 +2016,171 @@ object Glossary {
""")
val dauthEnabledMessage : String = if (APIUtil.getPropsAsBoolValue("allow_dauth", false))
{"Note: DAuth is enabled."} else {"Note: *DAuth is NOT enabled on this instance!*"}
glossaryItems += GlossaryItem(
title = APIUtil.DAuthHeaderKey,
description =
s"""
|### DAuth Introduction, Setup and Usage
|
|
|DAuth is an experimental authentication mechanism that aims to pin an ethereum or other blockchain Smart Contract to an OBP "User".
|
|In the future, it might be possible to be more specific and pin specific actors (wallets) that are acting within the smart contract, but so far, one smart contract acts on behalf of one User.
|
|Thus, if a smart contract "X" calls the OBP API using the DAuth header, OBP will get or create a user called X and the call will proceed in the context of that User "X".
|
|
|DAuth is invoked by the REST client (caller) including a specific header (see step 3 below) in any OBP REST call.
|
|When OBP receives the DAuth token, it creates or gets a User with a username based on the smart_contract_address and the provider based on the network_name. The combination of username and provider is unique in OBP.
|
|If you are calling OBP-API via an API3 Airnode, the Airnode will take care of constructing the required header.
|
|When OBP detects a DAuth header / token it first checks if the Consumer is allowed to make such a call. OBP will validate the Consumer ip address and signature etc.
|
|Note: The DAuth flow does *not* require an explicit POST like Direct Login to create the token.
|
|Permissions may be assigned to an OBP User at any time, via the UserAuthContext, Views, Entitlements to Roles or Consents.
|
|$dauthEnabledMessage
|
|Note: *The DAuth client is responsible for creating a token which will be trusted by OBP absolutely*!
|
|
|To use DAuth:
|
|### 1) Configure OBP API to accept DAuth.
|
|Set up properties in your props file
|
|```
|# -- DAuth --------------------------------------
|# Define secret used to validate JWT token
|# jwt.public_key_rsa=path-to-the-pem-file
|# Enable/Disable DAuth communication at all
|# In case isn't defined default value is false
|# allow_dauth=false
|# Define comma separated list of allowed IP addresses
|# dauth.host=127.0.0.1
|# -------------------------------------- DAuth--
|```
|Please keep in mind that property jwt.public_key_rsa is used to validate JWT token to check it is not changed or corrupted during transport.
|
|### 2) Create / have access to a JWT
|
|The following videos are available:
| * [DAuth in local environment](https://vimeo.com/644315074)
|
|HEADER:ALGORITHM & TOKEN TYPE
|
|```
|{
| "alg": "RS256",
| "typ": "JWT"
|}
|```
|PAYLOAD:DATA
|
|```
|{
| "smart_contract_address": "0xe123425E7734CE288F8367e1Bb143E90bb3F051224",
| "network_name": "AIRNODE.TESTNET.ETHEREUM",
| "msg_sender": "0xe12340927f1725E7734CE288F8367e1Bb143E90fhku767",
| "consumer_key": "0x1234a4ec31e89cea54d1f125db7536e874ab4a96b4d4f6438668b6bb10a6adb",
| "timestamp": "2021-11-04T14:13:40Z",
| "request_id": "0Xe876987694328763492876348928736497869273649"
|}
|```
|VERIFY SIGNATURE
|```
|RSASHA256(
| base64UrlEncode(header) + "." +
| base64UrlEncode(payload),
|
|) your-RSA-key-pair
|```
|
|Here is an example token:
|
|```
|eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzbWFydF9jb250cmFjdF9hZGRyZXNzIjoiMHhlMTIzNDI1RTc3MzRDRTI4OEY4MzY3ZTFCYjE0M0U5MGJiM0YwNTEyMjQiLCJuZXR3b3JrX25hbWUiOiJFVEhFUkVVTSIsIm1zZ19zZW5kZXIiOiIweGUxMjM0MDkyN2YxNzI1RTc3MzRDRTI4OEY4MzY3ZTFCYjE0M0U5MGZoa3U3NjciLCJjb25zdW1lcl9rZXkiOiIweDEyMzRhNGVjMzFlODljZWE1NGQxZjEyNWRiNzUzNmU4NzRhYjRhOTZiNGQ0ZjY0Mzg2NjhiNmJiMTBhNmFkYiIsInRpbWVzdGFtcCI6IjIwMjEtMTEtMDRUMTQ6MTM6NDBaIiwicmVxdWVzdF9pZCI6IjBYZTg3Njk4NzY5NDMyODc2MzQ5Mjg3NjM0ODkyODczNjQ5Nzg2OTI3MzY0OSJ9.XSiQxjEVyCouf7zT8MubEKsbOBZuReGVhnt9uck6z6k
|```
|
|
|
|### 3) Try a REST call using the header
|
|
|Using your favorite http client:
|
| GET $getServerUrl/obp/v3.0.0/users/current
|
|Body
|
| Leave Empty!
|
|
|Headers:
|
| DAuth: your-jwt-from-step-above
|
|Here is it all together:
|
| GET $getServerUrl/obp/v3.0.0/users/current HTTP/1.1
| Host: localhost:8080
| User-Agent: curl/7.47.0
| Accept: */*
| DAuth: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzbWFydF9jb250cmFjdF9hZGRyZXNzIjoiMHhlMTIzNDI1RTc3MzRDRTI4OEY4MzY3ZTFCYjE0M0U5MGJiM0YwNTEyMjQiLCJuZXR3b3JrX25hbWUiOiJFVEhFUkVVTSIsIm1zZ19zZW5kZXIiOiIweGUxMjM0MDkyN2YxNzI1RTc3MzRDRTI4OEY4MzY3ZTFCYjE0M0U5MGZoa3U3NjciLCJjb25zdW1lcl9rZXkiOiIweDEyMzRhNGVjMzFlODljZWE1NGQxZjEyNWRiNzUzNmU4NzRhYjRhOTZiNGQ0ZjY0Mzg2NjhiNmJiMTBhNmFkYiIsInRpbWVzdGFtcCI6IjIwMjEtMTEtMDRUMTQ6MTM6NDBaIiwicmVxdWVzdF9pZCI6IjBYZTg3Njk4NzY5NDMyODc2MzQ5Mjg3NjM0ODkyODczNjQ5Nzg2OTI3MzY0OSJ9.XSiQxjEVyCouf7zT8MubEKsbOBZuReGVhnt9uck6z6k
|
|CURL example
|
|```
|curl -v -H 'DAuth: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzbWFydF9jb250cmFjdF9hZGRyZXNzIjoiMHhlMTIzNDI1RTc3MzRDRTI4OEY4MzY3ZTFCYjE0M0U5MGJiM0YwNTEyMjQiLCJuZXR3b3JrX25hbWUiOiJFVEhFUkVVTSIsIm1zZ19zZW5kZXIiOiIweGUxMjM0MDkyN2YxNzI1RTc3MzRDRTI4OEY4MzY3ZTFCYjE0M0U5MGZoa3U3NjciLCJjb25zdW1lcl9rZXkiOiIweDEyMzRhNGVjMzFlODljZWE1NGQxZjEyNWRiNzUzNmU4NzRhYjRhOTZiNGQ0ZjY0Mzg2NjhiNmJiMTBhNmFkYiIsInRpbWVzdGFtcCI6IjIwMjEtMTEtMDRUMTQ6MTM6NDBaIiwicmVxdWVzdF9pZCI6IjBYZTg3Njk4NzY5NDMyODc2MzQ5Mjg3NjM0ODkyODczNjQ5Nzg2OTI3MzY0OSJ9.XSiQxjEVyCouf7zT8MubEKsbOBZuReGVhnt9uck6z6k' $getServerUrl/obp/v3.0.0/users/current
|```
|
|
|You should receive a response like:
|
|```
|{
| "user_id": "4c4d3175-1e5c-4cfd-9b08-dcdc209d8221",
| "email": "",
| "provider_id": "0xe123425E7734CE288F8367e1Bb143E90bb3F051224",
| "provider": "ETHEREUM",
| "username": "0xe123425E7734CE288F8367e1Bb143E90bb3F051224",
| "entitlements": {
| "list": []
| }
|}
|```
|
|### Under the hood
|
|The file, dauth.scala handles the DAuth,
|
|We:
|
|```
|-> Check if Props allow_dauth is true
| -> Check if DAuth header exists
| -> Check if getRemoteIpAddress is OK
| -> Look for "token"
| -> parse the JWT token and getOrCreate the user
| -> get the data of the user
|```
|
|### More information
|
| Parameter names and values are case sensitive.
| Each parameter MUST NOT appear more than once per request.
|
""")
glossaryItems += GlossaryItem(
title = "SCA (Strong Customer Authentication)",
@ -2082,8 +2318,7 @@ object Glossary {
glossaryItems += GlossaryItem(
title = "API Collection",
description =
s"""|An API Collection is a collection of endpoints grouped together for a certain purpose.
description = s"""An API Collection is a collection of endpoints grouped together for a certain purpose.
|
|Having read access to a Collection does not constitute execute access on the endpoints in the Collection.
|
@ -2097,10 +2332,385 @@ object Glossary {
|
|If you share a Collection it can't be modified by anyone else, but anyone can use it as a basis for their own Favourites or another collection.
|
|At the time of writing (July 2021), there are 13 endpoints for controlling Collections.
|There are over 13 endpoints for controlling Collections.
|Some of these endpoints require Entitlements to Roles and some operate on your own personal collections such as your favourites.
|
""")
glossaryItems += GlossaryItem(
title = "Space",
description =
s"""In OBP, if you have access to a "Space", you have access to a set of Dynamic Endpoints and Dynamic Entities that belong to that Space.
|Internally, Spaces are defined as a "Banks" thus Spaces are synonymous with OBP Banks.
|
|A user can have access to several spaces. The API Explorer shows these under the Spaces menu.
|
|In order to see the documentation for the Dynamic Endpoints and Dynamic Entities, a user may need to have access to the CanReadDynamicResourceDocsAtOneBank Role.
|
|You can create your own Space by creating an OBP Bank.
|
""".stripMargin)
glossaryItems += GlossaryItem(
title = "Dynamic Entity Manage",
description =
s"""
|
|Dynamic Entities can be used to store and retrieve custom data objects (think your own tables and fields) in the OBP instance.
|
|You can define your own Dynamic Entities or use Dynamic Entities created by others.
|
|You would use Dynamic Entities if you want to go beyond the OBP standard data model and store custom data structures. Note, if you want to extend the core OBP banking model of Customers, Products, Accounts, Transactions and so on you can also add Custom Attributes to these standard objects.
|
|You would use Dynamic Endpoints if you want to go beyond the standard OBP or other open banking standard APIs.
|
|Dynamic Entities have their own REST APIs so you can easily Create, Read, Update and Delete records. However, you can also connect Dynamic Endpoints with your own API definitions (via Swagger) and so create custom GET endpoints connecting to any combination of Dynamic Entities.
|
|Dynamic Endpoints can retrieve the data of Dynamic Entities so you can effectively create bespoke endpoint / data combinations - at least for GET endpoints - using Dynamic Endpoints, Entities and Endpoint Mapping.
|
|In order to use Dynamic Entities you will need to have the appropriate Entitlements to Create, Read, Update or Delete records in the Dynamic Entity.
|
|You define your Dynamic Entities in JSON.
|
|Fields are typed, have an example value and a (markdown) description. They can also be constrained in size.
|
|You can also create field "references" to other fields in other Entities. These are like foreign keys to other Dynamic or Static (built in) entities.
|In other words, if you create an Entity called X which has a field called A, you can force the values of X.A to match the values of Y.B where Y is another Dynamic Entity or Z.B where Z is a Static (OBP) Entity.
|If you want to add data to an existing Entity, you can create a Dynamic Entity which has a reference field to the existing entity.
|
|Dynamic Entities can be created at the System level (bank_id is null) - or Bank / Space level (bank_id is not null). You might want to create Bank level Dynamic Entities in order to grant automated roles based on user email domain.
|
|When creating a Dynamic Entity, OBP automatically:
|
|* Creates a data structure in the OBP database in which to store the records of the new Entity.
|* Creates a primary key for the Entity which can be used to update and delete the Entity.
|* Creates Create, Read, Update and Delete endpoints to operate on the Entity so you can insert, get, modify and delete records. These CRUD operations are all available over the generated REST endpoints.
|* Creates Roles to guard the above endpoints.
|
|Following the creation of a Dynamic Entity you will need to grant yourself or others the appropriate roles before you can insert or get records.
|
|The generated Roles required for CRUD operations on a Dynamic Entity are like any other OBP Role i.e. they can be requested, granted, revoked and auto-granted using the API Explorer / API Manager or via REST API. To see the Roles required for a Dynamic Entities endpoints, see the API Explorer for each endpoint concerned.
|
|Each Dynamic Entity gets a dynamicEntityId which uniquely identifies it and also the userId which identifies the user who created the Entity. The dynamicEntityId is used to update the definition of the Entity.
|
|To visualise any data contained in Dynamic Entities you could use external BI tools and use the GET endpoints and authenticate using OAuth or Direct Login.
|
|The following videos are available:
|
| * [Introduction to Dynamic Entities](https://vimeo.com/426524451)
| * [Features of Dynamic Entities](https://vimeo.com/446465797)
|
""".stripMargin)
glossaryItems += GlossaryItem(
title = "Dynamic Endpoint Manage",
description =
s"""
|
|If you want to create endpoints from Swagger / Open API specification files, use Dynamic Endpoints.
|
|We use the term "Dynamic" because these Endpoints persist in the OBP database and are served from real time generated Scala code.
|
|This contrasts to the "Static" endpoints (see the Static glossary item) which are served from static Scala code.
|
|Dynamic endpoints can be changed in real-time and do not require an OBP instance restart.
|
|When you POST a swagger file, all the endpoints defined in the swagger file, will be created in this OBP instance.
|
|You can create a set of endpoints in three different modes:
|
|1) If the *host* field in the Swagger file is set to "dynamic_entity", then you should link the swagger JSON fields to Dynamic Entity fields. To do this use the *Endpoint Mapping* endpoints.
|
|2) If the *host* field in the Swagger file is set to "obp_mock", the Dynamic Endpoints created will return *example responses defined in the swagger file*.
|
|3) If you need to link the responses to external resource, use the *Method Routing* endpoints.
|
|
|Dynamic Endpoints can be created at the System level (bank_id is null) or Bank / Space level (bank_id is NOT null).
|You might want to create Bank level Dynamic Entities in order to grant automated roles based on user email domain. See the OBP-API sample.props.template
|
|Upon the successful creation of each Dynamic Endpoint, OBP will automatically:
|
|*Create a Guard with a named Role on the Endpoint to protect it from unauthorised users.
|*Grant you an Entitlement to the required Role so you can call the endpoint and pass its Guard.
|
|The following videos are available:
|
| * [Introduction to Dynamic Endpoints](https://vimeo.com/426235612)
| * [Features of Dynamic Endpoints](https://vimeo.com/444133309)
|
""".stripMargin)
glossaryItems += GlossaryItem(
title = "Endpoint Mapping",
description =
s"""
|Endpoint Mapping can be used to map each JSON field in a Dynamic Endpoint to different Dynamic Entity fields.
|
|This document assumes you already have some knowledge of OBP Dynamic Endpoints and Dynamic Entities.
|
|To enable Endpoint Mapping for your Dynamic Endpoints, either set the `host` in the swagger file to "dynamic_entity" upon creation of the Dynamic Endpoints - or update the host using the Update Dynamic Endpoint Host endpoints.
|
|Once the `host` is thus set, you can use the Endpoint Mapping endpoints to map the Dynamic Endpoint fields to Dynamic Entity data.
|
|See the [Create Endpoint Mapping](/index#OBPv4.0.0-createEndpointMapping) JSON body. You will need to know the operation_id in advance and you can prepare the request_mapping and response_mapping objects. You can get the operation ID from the API Explorer or Get Dynamic Endpoints endpoints.
|
|For more details and a walk through, please see the following video:
|
| * [Endpoint Mapping](https://vimeo.com/553369108)
|""".stripMargin)
glossaryItems += GlossaryItem(
title = "Branch",
description =
s"""The bank branches, it contains the address, location, lobby, drive_up of the Branch.
""".stripMargin)
glossaryItems += GlossaryItem(
title = "API",
description =
s"""|The terms `API` (Application Programming Interface) and `Endpoint` are used somewhat interchangeably.
|
|However, an API normally refers to a group of Endpoints.
|
|An endpoint has a unique URL path and HTTP verb (GET, POST, PUT, DELETE etc).
|
|When we POST a Swagger file to the Create Endpoint endpoint, we are in fact creating a set of Endpoints that have a common Tag. Tags are used to group Endpoints in the API Explorer and filter the Endpoints in the Resource Doc endpoints.
|
|Endpoints can also be grouped together in Collections.
|
|See also [Endpoint](/glossary#Endpoint)
|
""".stripMargin)
glossaryItems += GlossaryItem(
title = "Endpoint",
description =
s"""
|The terms `Endpoint` and `API` (Application Programming Interface) are used somewhat interchangeably. However, an Endpoint is a specific URL defined by its path (eg. /obp/v4.0/root) and its http verb (e.g. GET, POST, PUT, DELETE etc).
|Endpoints are like arrows into a system. Like any good computer function, endpoints should expect much and offer little in return. They should fail early and be clear about any reason for failure. In other words each endpoint should have a tight and limited contract with any caller - and especially the outside world!
|
|In OBP, all system endpoints are RESTful - and most Open Banking Standards are RESTful. However, it is possible to create non-RESTful APIs in OBP using the Create Endpoint endpoints.
|
|You can immediately tell if an endpoint is not RESTful by seeing a verb in the URL. For example:
|
|POST /customers is RESTful = GOOD
|POST /create-customer is NOT RESTful (due to the word "create") = BAD
|
|RESTful APIs use resource names in URL paths. You can think of RESTful resources like database tables. You wouldn't name a database table "create-customer", so don't use that in a URL path.
|
|If we consider interacting with a Customers table, we read the data using GET /Customers and write to the table using POST /Customers. This model keeps the names clear and predictable.
|Note that we are only talking about the front end interface here - anything could be happening in the backend - and that is one of the beauties of APIs. For instance GET /Customers could call 5 different databases and 3 XML services in the background. Similarly POST /Customers could insert into various different tables and backend services. The important thing is that the user of the API (The Consumer or Client in OAuth parlance) has a simple and consistent experience.
|
|In OBP, all Endpoints are implemented by `Partial Functions`. A Partial Function is a function which only accepts (and responds) to calls with certain parameter values. In the case of API Endpoints the inputs to the Partial Functions are the URL path and http verb. Note that it would be possible to have different Partial Functions respond even to different query parameters, but for OBP static endpoints at least, we take the approach of URL path + http Verb is handled by one Partial Function.
|Each Partial Function is identified by an Operation ID which uniquely identifies the endpoint in the system. Having an Operation ID allows us to decorate the Endpoint with metadata (e.g. Tags) and surround the Endpoint with behaviour such as JSON Schema Validation.
|
|See also [API](/glossary#API)
|
""".stripMargin)
glossaryItems += GlossaryItem(
title = "API Tag",
description =
s"""All OBP API relevant docs, eg: API configuration, JSON Web Key, Adapter Info, Rate Limiting
""".stripMargin)
glossaryItems += GlossaryItem(
title = "Account Access",
description =
s"""
|Account Access is OBP View system. The Account owners can create the view themselves.
|And they can grant/revoke the view to other users to use their view.
|""".stripMargin)
// val allTagNames: Set[String] = ApiTag.allDisplayTagNames
// val existingItems: Set[String] = glossaryItems.map(_.title).toSet
// allTagNames.diff(existingItems).map(title => glossaryItems += GlossaryItem(title, title))
glossaryItems += GlossaryItem(
title = "Static Endpoint",
description =
s"""
|Static endpoints are served from static Scala source code which is contained in (public) Git repositories.
|
|Static endpoints cover all the OBP API and User management functionality as well as the Open Bank Project banking APIs and other Open Banking standards such as UK Open Banking, Berlin Group and STET etc..
|In short, Static (standard) endpoints are defined in Git as Scala source code, where as Dynamic (custom) endpoints are defined in the OBP database.
|
|Modifications to Static endpoint core properties such as URLs and response bodies require source code changes and an instance restart. However, JSON Schema Validation and Dynamic Connector changes can be applied in real-time.
""".stripMargin)
glossaryItems += GlossaryItem(
title = "Message Doc",
description =
s"""
|OBP can communicate with core banking systems (CBS) and other back end services using a "Connector -> Adapter" approach.
|
|The OBP Connector is a core part of the OBP-API and is written in Scala / Java and potentially other JVM languages.
|
|The OBP Connector implements multiple functions / methods in a style that satisfies a particular transport / protocol such as HTTP REST, Akka or Kafka.
|
|An OBP Adapter is a separate software component written in any programming language that responds to requests from the OBP Connector.
|
|Requests are sent by the Connector to the Adapter (or a message queue).
|
|The Adapter must satisfy the Connector method's request for data (or return an error).
|
|"Message Docs" are used to define and document the request / response structure.
|
|Message Docs are visible in the API Explorer.
|
|Message Docs are also available over the Message Doc endpoints.
|
|Each Message Doc relates to one OBP function / method.
|
|The Message Doc includes:
|
| 1) The Name of the internal OBP function / method e.g. getAccountsForUser
| 2) The Outbound Message structure.
| 3) The Inbound Message structure.
| 4) The Connector name which denotes the protocol / transport used (e.g. REST, Akka, Kafka etc)
| 5) Outbound / Inbound Topic
| 6) A list of required Inbound fields
| 7) A list of dependent endpoints.
|
|The perspective is that of the OBP-API Connector i.e. the OBP Connector sends the message Out, and it receives the answer In.
|
|The Outbound message contains several top level data structures:
|
| 1) The outboundAdapterCallContext
|
| This tells the Adapter about the specific REST call that triggered the request and contains the correlationId to uniquely identify the REST call, the consumerId to identify the API Consumer (App) and a generalContext which is a list of key / value pairs that give the Adapter additional custom information about the call.
|
| 2) outboundAdapterAuthInfo
|
|This tells the Adapter about the authenticated User that is making the call including: the userId, the userName, the userAuthContext (a list of key / value pairs that have been validated using SCA (see the UserAuthContext endpoints)) and other optional structures such as linked Customers and Views on Accounts to further identify the User.
|
|3) The body
|
|The body contains named fields that are specific to each Function / Message Doc.
|
|For instance, getTransaction might send the bankId, accountId and transactionId so the Adapter can route the request based on bankId and check User permissions on the AccountId before retrieving a Transaction.
|
|The Inbound message
|
|The Inbound message is the reply or response from the Adapter and has the following structure:
|
|1) The inboundAdapterCallContext
|
|This is generally an echo of the outboundAdapterCallContext so the Connector can double check the target destination of the response.
|
|2) The status
|
|This contains information about status of the response including any errorCode and a list of backendMessages.
|
|3) The data
|
|This contains the named fields and their values which are specific to each Function / Message Doc.
|
|
|The Outbound / Inbound Topics are used for routing in multi OBP instance / Kafka installations. (so OBP nodes only listen only to the correct Topics).
|
|The dependent endpoints are listed to facilitate navigation in the API Explorer so integrators can test endpoints during integration.
|
|Message Docs can be generated automatically using OBP code tools. Thus, it's possible to create custom connectors that follow specific protocol and structural patterns e.g. for message queue X over XML format Y.
|
|""".stripMargin)
glossaryItems += GlossaryItem(
title = "Method Routing",
description =
s"""
|
| Open Bank Project can have different connectors, to connect difference data sources.
| We support several sources at the moment, eg: databases, rest services, stored procedures and kafka.
|
| If OBP set connector=star, then you can use this method routing to switch the sources.
| And we also provide the fields mapping in side the endpoints. If the fields in the source are different from connector,
| then you can map the fields yourself.
|
| The following videos are available:
|
| *[Method Routing Endpoints](https://vimeo.com/398973130)
| *[Method Routing Endpoints Mapping](https://vimeo.com/404983764)
|
|""".stripMargin)
glossaryItems += GlossaryItem(
title = "JSON Schema Validation",
description =
s"""
|JSON Schema is "a vocabulary that allows you to annotate and validate JSON documents".
|
|By applying JSON Schema Validation to your endpoints you can constrain POST and PUT request bodies. For example, you can set minimum / maximum lengths of fields and constrain values to certain lists or regular expressions.
|
|See [JSONSchema.org](https://json-schema.org/) for more information about the standard.
|
|Note that Dynamic Entities also use JSON Schema Validation so you don't need to additionally wrap the resulting endpoints with extra JSON Schema Validation but you could do.
|
|
| We provide the schema validations over the endpoints.
| All the OBP endpoints request/response body fields can be validated by the schema.
|
|The following videos are available:
|* [JSON schema validation of request for Static and Dynamic Endpoints and Entities] (https://vimeo.com/485287014)
|""".stripMargin)
glossaryItems += GlossaryItem(
title = "Connector Method",
description =
s"""
| The developer can override all the existing Connector methods on their own.
| This function needs to be used together with the Method Routing.
| when set "connector = internal", then the developer can call their own method body at API level.
|
|eg: Get Banks endpoint, it calls the connector "getBanks" method, then the developers can use these endpoints to modify the business logic in the getBanks method body.
|
| The following videos are available:
|* [Introduction for Connector Method] (https://vimeo.com/507795470)
|
|""".stripMargin)
glossaryItems += GlossaryItem(
title = "Dynamic Resource Doc",
description =
s"""
| The developers can create their own endpoints by this endpoint.
| Need to prepare the obp resource doc format json.
| And all the business logic code can be written in the *method_body* field, it is the encoded scala code.
|
| It is still working in the processing ..
|The following videos are available:
|* [Introduction for dConnector Method] (https://vimeo.com/623381607)
|
|""".stripMargin)
glossaryItems += GlossaryItem(
title = "Dynamic Message Doc",
description =
s"""
| The developers can create their own scala methods in OBP code.
| These endpoints are designed for extending the current connector methods.
| when you call the dynamic resource doc endpoints, sometimes you need to call internal scala methods,
| which are not existing in OBP code, then you can use these endpoints to prepare them on your own.
|
| And you can use these endpoints to design your own helper methods in OBP code.
|
| It is still working in the processing ..
|The following videos are available:
|* [Introduction for Connector Method] (https://vimeo.com/623317747)
|
|""".stripMargin)
///////////////////////////////////////////////////////////////////
// NOTE! Some glossary items are generated in ExampleValue.scala

View File

@ -14,9 +14,10 @@ object HashUtil {
def main(args: Array[String]): Unit = {
// You can verify hash with command line tool in linux, unix:
// $ echo -n "123" | openssl dgst -sha256
val password = "123"
val hashedPassword = Sha256Hash(password)
println("Password: " + password)
println("Hashed password: " + hashedPassword)
val plainText = "123"
val hashedText = Sha256Hash(plainText)
println("Password: " + plainText)
println("Hashed password: " + hashedText)
}
}

View File

@ -1,11 +1,14 @@
package code.api.util
import java.net.URL
import java.net.{URI, URL}
import java.nio.file.{Files, Paths}
import java.text.ParseException
import code.api.util.RSAUtil.logger
import code.util.Helper.MdcLoggable
import com.nimbusds.jose.JWSAlgorithm
import com.nimbusds.jose.crypto.{MACVerifier, RSASSAVerifier}
import com.nimbusds.jose.jwk.{JWK, RSAKey}
import com.nimbusds.jose.jwk.source.{JWKSource, RemoteJWKSet}
import com.nimbusds.jose.proc.{JWSVerificationKeySelector, SecurityContext}
import com.nimbusds.jose.util.{DefaultResourceRetriever, JSONObjectUtils}
@ -235,7 +238,26 @@ object JwtUtil extends MdcLoggable {
}
}
def validateJwtWithRsaKey(jwtString: String): Boolean = {
val relativePath = APIUtil.getPropsValue("jwt.public_key_rsa", "")
val basePath = this.getClass.getResource("/").toString .replaceFirst("target[/\\\\].*$", "")
val filePath = new URI(s"${basePath}$relativePath").getPath
val publicKey = getPublicRsaKeyFromFile(filePath)
val signedJWT = SignedJWT.parse(jwtString)
val verifier = new RSASSAVerifier(publicKey)
signedJWT.verify(verifier)
}
def getPublicRsaKeyFromFile(path: String): RSAKey = {
val pathOfFile = Paths.get(path)
val pemEncodedRSAPubliceKey = Files.readAllLines(pathOfFile).toArray.toList.mkString("\n")
logger.debug(pemEncodedRSAPubliceKey)
// Parse PEM-encoded key to RSA public / private JWK
val jwk: JWK = JWK.parseFromPEMEncodedObjects(pemEncodedRSAPubliceKey);
logger.debug(s"Key $path is private: " + jwk.isPrivate)
jwk.toPublicJWK.toRSAKey
}
def main(args: Array[String]): Unit = {

View File

@ -36,7 +36,7 @@ import code.apicollection.{ApiCollectionTrait, MappedApiCollectionsProvider}
import code.model.dataAccess.{AuthUser, BankAccountRouting}
import code.standingorders.StandingOrderTrait
import code.usercustomerlinks.UserCustomerLink
import code.users.{UserInvitation, UserInvitationProvider, Users}
import code.users.{UserAgreement, UserAgreementProvider, UserInvitation, UserInvitationProvider, Users}
import code.util.Helper
import com.openbankproject.commons.util.{ApiVersion, JsonUtils}
import code.views.Views
@ -48,7 +48,7 @@ import com.openbankproject.commons.model.enums.StrongCustomerAuthenticationStatu
import com.openbankproject.commons.model.enums._
import com.openbankproject.commons.model.{AccountApplication, Bank, Customer, CustomerAddress, Product, ProductCollection, ProductCollectionItem, TaxResidence, UserAuthContext, UserAuthContextUpdate, _}
import com.tesobe.CacheKeyFromArguments
import net.liftweb.common.{Box, Empty, Full, ParamFailure}
import net.liftweb.common.{Box, Empty, Failure, Full, ParamFailure}
import net.liftweb.http.provider.HTTPParam
import net.liftweb.json.JsonDSL._
import net.liftweb.json.{JField, JInt, JNothing, JNull, JObject, JString, JValue, _}
@ -63,13 +63,18 @@ import code.validation.{JsonSchemaValidationProvider, JsonValidation}
import net.liftweb.http.JsonResponse
import net.liftweb.util.Props
import code.api.JsonResponseException
import code.api.v4_0_0.ProductFeeJsonV400
import code.api.v4_0_0.dynamic.{DynamicEndpointHelper, DynamicEntityInfo}
import code.bankattribute.BankAttribute
import code.connectormethod.{ConnectorMethodProvider, JsonConnectorMethod}
import code.dynamicMessageDoc.{DynamicMessageDocProvider, JsonDynamicMessageDoc}
import code.dynamicResourceDoc.{DynamicResourceDocProvider, JsonDynamicResourceDoc}
import code.endpointMapping.{EndpointMappingProvider, EndpointMappingT}
import code.endpointTag.EndpointTagT
import net.liftweb.json
import scala.util.Success
object NewStyle {
lazy val endpoints: List[(String, String)] = List(
(nameOf(ImplementationsResourceDocs.getResourceDocsObp), ApiVersion.v1_4_0.toString),
@ -273,6 +278,66 @@ object NewStyle {
i => (unboxFullOrFail(i._1, callContext, s"$CreateAtmError", 400), i._2)
}
}
def createSystemLevelEndpointTag(operationId:String, tagName:String, callContext: Option[CallContext]): OBPReturnType[EndpointTagT] = {
Connector.connector.vend.createSystemLevelEndpointTag(operationId, tagName, callContext) map {
i => (unboxFullOrFail(i._1, callContext, s"$CreateEndpointTagError", 400), i._2)
}
}
def updateSystemLevelEndpointTag(endpointTagId: String, operationId:String, tagName:String, callContext: Option[CallContext]): OBPReturnType[EndpointTagT] = {
Connector.connector.vend.updateSystemLevelEndpointTag(endpointTagId: String, operationId:String, tagName:String, callContext) map {
i => (unboxFullOrFail(i._1, callContext, s"$UpdateEndpointTagError", 400), i._2)
}
}
def createBankLevelEndpointTag(bankId:String, operationId:String, tagName:String, callContext: Option[CallContext]): OBPReturnType[EndpointTagT] = {
Connector.connector.vend.createBankLevelEndpointTag(bankId, operationId, tagName, callContext) map {
i => (unboxFullOrFail(i._1, callContext, s"$CreateEndpointTagError", 400), i._2)
}
}
def updateBankLevelEndpointTag(bankId:String, endpointTagId: String, operationId:String, tagName:String, callContext: Option[CallContext]): OBPReturnType[EndpointTagT] = {
Connector.connector.vend.updateBankLevelEndpointTag(bankId, endpointTagId, operationId, tagName, callContext) map {
i => (unboxFullOrFail(i._1, callContext, s"$UpdateEndpointTagError", 400), i._2)
}
}
def checkSystemLevelEndpointTagExists(operationId: String, tagName:String, callContext: Option[CallContext]): OBPReturnType[Boolean] = {
Connector.connector.vend.getSystemLevelEndpointTag(operationId: String, tagName: String, callContext) map {
i => (i._1.isDefined, i._2)
}
}
def checkBankLevelEndpointTagExists(bankId: String, operationId: String, tagName:String, callContext: Option[CallContext]): OBPReturnType[Boolean] = {
Connector.connector.vend.getBankLevelEndpointTag(bankId: String, operationId: String, tagName: String, callContext) map {
i => (i._1.isDefined, i._2)
}
}
def getEndpointTag(endpointTagId : String, callContext: Option[CallContext]) : OBPReturnType[EndpointTagT] = {
Connector.connector.vend.getEndpointTagById(endpointTagId, callContext) map {
i => (unboxFullOrFail(i._1, callContext, s"$EndpointTagNotFoundByEndpointTagId Current ENDPOINT_TAG_ID is $endpointTagId", 404), i._2)
}
}
def deleteEndpointTag(endpointTagId : String, callContext: Option[CallContext]) : OBPReturnType[Boolean] = {
Connector.connector.vend.deleteEndpointTag(endpointTagId, callContext) map {
i => (unboxFullOrFail(i._1, callContext, s"$UnknownEndpointTagError Current ENDPOINT_TAG_ID is $endpointTagId", 404), i._2)
}
}
def getSystemLevelEndpointTags(operationId : String, callContext: Option[CallContext]) : OBPReturnType[List[EndpointTagT]] = {
Connector.connector.vend.getSystemLevelEndpointTags(operationId, callContext) map {
i => (unboxFullOrFail(i._1, callContext, s"$InvalidConnectorResponseForGetEndpointTags Current OPERATION_ID is $operationId", 404), i._2)
}
}
def getBankLevelEndpointTags(bankId:String, operationId : String, callContext: Option[CallContext]) : OBPReturnType[List[EndpointTagT]] = {
Connector.connector.vend.getBankLevelEndpointTags(bankId, operationId, callContext) map {
i => (unboxFullOrFail(i._1, callContext, s"$InvalidConnectorResponseForGetEndpointTags Current OPERATION_ID is $operationId", 404), i._2)
}
}
def getBank(bankId : BankId, callContext: Option[CallContext]) : OBPReturnType[Bank] = {
Connector.connector.vend.getBank(bankId, callContext) map {
@ -343,6 +408,12 @@ object NewStyle {
}
}
def getBankAccountsWithAttributes(bankId: BankId, queryParams: List[OBPQueryParam], callContext: Option[CallContext]): OBPReturnType[List[FastFirehoseAccount]] = {
Connector.connector.vend.getBankAccountsWithAttributes(bankId, queryParams, callContext) map { i =>
(unboxFullOrFail(i._1, callContext,s"$InvalidConnectorResponseForGetBankAccountsWithAttributes", 400 ), i._2)
}
}
def getBankAccountBalances(bankIdAccountId: BankIdAccountId, callContext: Option[CallContext]): OBPReturnType[AccountBalances] = {
Connector.connector.vend.getBankAccountBalances(bankIdAccountId: BankIdAccountId, callContext: Option[CallContext]) map { i =>
(unboxFullOrFail(i._1, callContext,s"$InvalidConnectorResponseForGetBankAccounts", 400 ), i._2)
@ -759,6 +830,9 @@ object NewStyle {
connectorEmptyResponse(_, callContext)
}
}
def getAgreementByUserId(userId: String, agreementType: String, callContext: Option[CallContext]): Future[Box[UserAgreement]] = {
Future(UserAgreementProvider.userAgreementProvider.vend.getUserAgreement(userId, agreementType))
}
def getEntitlementsByBankId(bankId: String, callContext: Option[CallContext]): Future[List[Entitlement]] = {
Entitlement.entitlement.vend.getEntitlementsByBankId(bankId) map {
@ -830,7 +904,7 @@ object NewStyle {
def isValidCurrencyISOCode(currencyCode: String, callContext: Option[CallContext]) = {
tryons(failMsg = InvalidISOCurrencyCode,400, callContext) {
tryons(failMsg = InvalidISOCurrencyCode+s" Current currencyCode is $currencyCode",400, callContext) {
assert(APIUtil.isValidCurrencyISOCode(currencyCode))
}
}
@ -960,6 +1034,12 @@ object NewStyle {
}
}
def getOrCreateResourceUser(username: String, provider: String, callContext: Option[CallContext]): OBPReturnType[User] = {
Future { UserX.getOrCreateDauthResourceUser(username, provider).map(user =>(user, callContext))} map {
unboxFullOrFail(_, callContext, s"$CannotGetOrCreateUser Current USERName($username) PROVIDER ($provider)", 404)
}
}
def createTransactionRequestv210(
u: User,
viewId: ViewId,
@ -1260,8 +1340,9 @@ object NewStyle {
productCode: ProductCode,
productAttributeId: Option[String],
name: String,
attributType: ProductAttributeType.Value,
attributeType: ProductAttributeType.Value,
value: String,
isActive: Option[Boolean],
callContext: Option[CallContext]
): OBPReturnType[ProductAttribute] = {
Connector.connector.vend.createOrUpdateProductAttribute(
@ -1269,14 +1350,112 @@ object NewStyle {
productCode: ProductCode,
productAttributeId: Option[String],
name: String,
attributType: ProductAttributeType.Value,
attributeType: ProductAttributeType.Value,
value: String,
isActive: Option[Boolean],
callContext: Option[CallContext]
) map {
i => (connectorEmptyResponse(i._1, callContext), i._2)
}
}
def createOrUpdateProductFee(
bankId: BankId,
productCode: ProductCode,
productFeeId: Option[String],
name: String,
isActive: Boolean,
moreInfo: String,
currency: String,
amount: BigDecimal,
frequency: String,
`type`: String,
callContext: Option[CallContext]
): OBPReturnType[ProductFeeTrait] = {
Connector.connector.vend.createOrUpdateProductFee(
bankId: BankId,
productCode: ProductCode,
productFeeId: Option[String],
name: String,
isActive: Boolean,
moreInfo: String,
currency: String,
amount: BigDecimal,
frequency: String,
`type`: String,
callContext: Option[CallContext]
) map {
i => (connectorEmptyResponse(i._1, callContext), i._2)
}
}
def getProductFeeById(
productFeeId: String,
callContext: Option[CallContext]
) : OBPReturnType[ProductFeeTrait] =
Connector.connector.vend.getProductFeeById(
productFeeId: String,
callContext: Option[CallContext]
) map {
i => (unboxFullOrFail(i._1, callContext, ProductFeeNotFoundById + " {" + productFeeId + "}", 404), i._2)
}
def deleteProductFee(
productFeeId: String,
callContext: Option[CallContext]
) : OBPReturnType[Boolean] =
Connector.connector.vend.deleteProductFee(
productFeeId: String,
callContext: Option[CallContext]
) map {
i => (unboxFullOrFail(i._1, callContext, ProductFeeNotFoundById + " {" + productFeeId + "}", 404), i._2)
}
def getProductFeesFromProvider(
bankId: BankId,
productCode: ProductCode,
callContext: Option[CallContext]
): OBPReturnType[List[ProductFeeTrait]] = {
Connector.connector.vend.getProductFeesFromProvider(
bankId: BankId,
productCode: ProductCode,
callContext: Option[CallContext]
) map {
i => (connectorEmptyResponse(i._1, callContext), i._2)
}
}
def createOrUpdateBankAttribute(
bankId: BankId,
bankAttributeId: Option[String],
name: String,
attributeType: BankAttributeType.Value,
value: String,
isActive: Option[Boolean],
callContext: Option[CallContext]
): OBPReturnType[BankAttribute] = {
Connector.connector.vend.createOrUpdateBankAttribute(
bankId: BankId,
bankAttributeId: Option[String],
name: String,
attributeType: BankAttributeType.Value,
value: String,
isActive: Option[Boolean],
callContext: Option[CallContext]
) map {
i => (connectorEmptyResponse(i._1, callContext), i._2)
}
}
def getBankAttributesByBank(bank: BankId,callContext: Option[CallContext]): OBPReturnType[List[BankAttribute]] = {
Connector.connector.vend.getBankAttributesByBank(
bank: BankId,
callContext: Option[CallContext]
) map {
i => (connectorEmptyResponse(i._1, callContext), i._2)
}
}
def getProductAttributesByBankAndCode(
bank: BankId,
productCode: ProductCode,
@ -1303,6 +1482,30 @@ object NewStyle {
}
}
def getBankAttributeById(
bankAttributeId: String,
callContext: Option[CallContext]
): OBPReturnType[BankAttribute] = {
Connector.connector.vend.getBankAttributeById(
bankAttributeId: String,
callContext: Option[CallContext]
) map {
i => (connectorEmptyResponse(i._1, callContext), i._2)
}
}
def deleteBankAttribute(
bankAttributeId: String,
callContext: Option[CallContext]
): OBPReturnType[Boolean] = {
Connector.connector.vend.deleteBankAttribute(
bankAttributeId: String,
callContext: Option[CallContext]
) map {
i => (connectorEmptyResponse(i._1, callContext), i._2)
}
}
def deleteProductAttribute(
productAttributeId: String,
callContext: Option[CallContext]
@ -2876,12 +3079,14 @@ object NewStyle {
userId: String,
apiCollectionName: String,
isSharable: Boolean,
description: String,
callContext: Option[CallContext]
) : OBPReturnType[ApiCollectionTrait] = {
Future(MappedApiCollectionsProvider.createApiCollection(
userId: String,
apiCollectionName: String,
isSharable: Boolean)
isSharable: Boolean,
description: String)
) map {
i => (unboxFullOrFail(i, callContext, CreateApiCollectionError), callContext)
}

View File

@ -1,8 +1,16 @@
package code.api.util
import java.nio.file.{Files, Paths}
import java.security.Signature
import code.api.util.CertificateUtil.{privateKey, publicKey}
import code.util.Helper.MdcLoggable
import com.nimbusds.jose.crypto.RSASSASigner
import com.nimbusds.jose.jwk.JWK
import com.nimbusds.jose.{JWSAlgorithm, JWSHeader, JWSObject, Payload}
import javax.crypto.Cipher
import net.liftweb.util.SecurityHelpers
import net.liftweb.util.SecurityHelpers.base64EncodeURLSafe
import java.time.Instant
object RSAUtil extends MdcLoggable {
@ -16,20 +24,90 @@ object RSAUtil extends MdcLoggable {
Base64.encodeBase64String(res)
}
def decrypt(encrypted: String): String = {
import org.apache.commons.codec.binary.Base64
import javax.crypto.Cipher
import org.apache.commons.codec.binary.Base64
val bytes = Base64.decodeBase64(encrypted)
val cipher = Cipher.getInstance(cryptoSystem)
cipher.init(Cipher.DECRYPT_MODE, privateKey)
new String(cipher.doFinal(bytes), "utf-8")
}
def computeHash(input: String): String = SecurityHelpers.hash256(input)
def computeHexHash(input: String): String = {
SecurityHelpers.hexDigest256(input.getBytes("UTF-8"))
}
def signWithRsa256(payload: String, jwk: JWK): String = {
// Prepare JWS object with simple string as a payload
val jwsObject = new JWSObject(
new JWSHeader.Builder(JWSAlgorithm.RS256).build,
new Payload(payload)
)
val rsaSigner = new RSASSASigner(jwk.toRSAKey)
// Compute the RSA signature
jwsObject.sign(rsaSigner)
// To serialize to compact form, produces something like
// eyJhbGciOiJSUzI1NiJ9.SW4gUlNBIHdlIHRydXN0IQ.IRMQENi4nJyp4er2L
// mZq3ivwoAjqa1uUkSBKFIX7ATndFF5ivnt-m8uApHO4kfIFOrW7w2Ezmlg3Qd
// maXlS9DhN0nUk_hGI3amEjkKd0BWYCB8vfUbUv0XGjQip78AI4z1PrFRNidm7
// -jPDm5Iq0SZnjKjCNS5Q15fokXZc8u0A
val s = jwsObject.serialize
s
}
def computeXSign(input: String, jwk: JWK) = {
logger.debug("Input: " + input)
logger.debug("Hash: " + computeHash(input))
logger.debug("HEX hash: " + computeHexHash(input))
// Compute the signature
val data = input.getBytes("UTF8")
val sig = Signature.getInstance("SHA256WithRSA")
sig.initSign(jwk.toRSAKey.toPrivateKey)
sig.update(data)
val signatureBytes = sig.sign
val xSign = base64EncodeURLSafe(signatureBytes)
logger.debug("x-sign: " + xSign)
xSign
}
def getPrivateKeyFromFile(path: String): JWK = {
val pathOfFile = Paths.get(path)
val pemEncodedRSAPrivateKey = Files.readAllLines(pathOfFile).toArray.toList.mkString("\n")
logger.debug(pemEncodedRSAPrivateKey)
// Parse PEM-encoded key to RSA public / private JWK
val jwk: JWK = JWK.parseFromPEMEncodedObjects(pemEncodedRSAPrivateKey);
logger.debug("Key is private: " + jwk.isPrivate)
jwk
}
def getPrivateKeyFromString(privateKeyValue: String): JWK = {
val pemEncodedRSAPrivateKey = privateKeyValue
logger.debug(privateKeyValue)
// Parse PEM-encoded key to RSA public / private JWK
val jwk: JWK = JWK.parseFromPEMEncodedObjects(pemEncodedRSAPrivateKey);
logger.debug("Key is private: " + jwk.isPrivate)
jwk
}
def main(args: Array[String]): Unit = {
val db = "jdbc:postgresql://localhost:5432/obp_mapped?user=obp&password=f"
val randomString = """G!y"k9GHD$D"""
val db = "jdbc:postgresql://localhost:5432/obp_mapped?user=obp&password=%s".format(randomString)
val res = encrypt(db)
println("db.url: " + db)
println("encrypt: " + res)
println("decrypt: " + decrypt(res))
val timestamp = Instant.now.getEpochSecond
val uri = "https://api.qredo.network/api/v1/p/company"
val body = """{"name":"Tesobe GmbH","city":"Berlin","country":"DE","domain":"tesobe.com","ref":"9827feec-4eae-4e80-bda3-daa7c3b97ad1"}"""
val inputMessage = s"""${timestamp}${uri}${body}"""
val privateKey = getPrivateKeyFromFile("obp-api/src/test/resources/cert/private.pem")
computeXSign(inputMessage, privateKey)
logger.debug("timestamp: " + timestamp)
}
}

View File

@ -1,5 +1,6 @@
package code.api.util
import java.math.BigInteger
import java.security.SecureRandom
/**
@ -15,4 +16,8 @@ object SecureRandomUtil {
// Obtains random numbers from the underlying native OS.
// No assertions are made as to the blocking nature of generating these numbers.
val csprng = SecureRandom.getInstance("NativePRNG")
def alphanumeric(nrChars: Int = 24): String = {
new BigInteger(nrChars * 5, csprng).toString(32)
}
}

View File

@ -81,6 +81,13 @@ object Migration extends MdcLoggable {
alterColumnDetailsAtTableTransactionRequest()
deleteDuplicatedRowsInTheTableUserAuthContext()
populateTheFieldDeletedAtResourceUser(startedBeforeSchemifier)
populateTheFieldIsActiveAtProductAttribute(startedBeforeSchemifier)
alterColumnUsernameProviderFirstnameAndLastnameAtAuthUser(startedBeforeSchemifier)
alterColumnEmailAtResourceUser(startedBeforeSchemifier)
alterColumnNameAtProductFee(startedBeforeSchemifier)
addFastFirehoseAccountsView(startedBeforeSchemifier)
addFastFirehoseAccountsMaterializedView(startedBeforeSchemifier)
alterUserAuthContextColumnKeyAndValueLength(startedBeforeSchemifier)
}
private def dummyScript(): Boolean = {
@ -247,6 +254,85 @@ object Migration extends MdcLoggable {
}
}
}
private def populateTheFieldIsActiveAtProductAttribute(startedBeforeSchemifier: Boolean): Boolean = {
if(startedBeforeSchemifier == true) {
logger.warn(s"Migration.database.populateTheFieldIsActiveAtProductAttribute(true) cannot be run before Schemifier.")
true
} else {
val name = nameOf(populateTheFieldIsActiveAtProductAttribute(startedBeforeSchemifier))
runOnce(name) {
MigrationOfProductAttribute.populateTheFieldIsActive(name)
}
}
}
private def alterColumnUsernameProviderFirstnameAndLastnameAtAuthUser(startedBeforeSchemifier: Boolean): Boolean = {
if(startedBeforeSchemifier == true) {
logger.warn(s"Migration.database.alterColumnUsernameProviderFirstnameAndLastnameAtAuthUser(true) cannot be run before Schemifier.")
true
} else {
val name = nameOf(alterColumnUsernameProviderFirstnameAndLastnameAtAuthUser(startedBeforeSchemifier))
runOnce(name) {
MigrationOfAuthUser.alterColumnUsernameProviderEmailFirstnameAndLastname(name)
}
}
}
private def alterColumnEmailAtResourceUser(startedBeforeSchemifier: Boolean): Boolean = {
if(startedBeforeSchemifier == true) {
logger.warn(s"Migration.database.alterColumnEmailAtResourceUser(true) cannot be run before Schemifier.")
true
} else {
val name = nameOf(alterColumnEmailAtResourceUser(startedBeforeSchemifier))
runOnce(name) {
MigrationOfResourceUser.alterColumnEmail(name)
}
}
}
private def alterColumnNameAtProductFee(startedBeforeSchemifier: Boolean): Boolean = {
if(startedBeforeSchemifier == true) {
logger.warn(s"Migration.database.alterColumnNameAtProductFee(true) cannot be run before Schemifier.")
true
} else {
val name = nameOf(alterColumnNameAtProductFee(startedBeforeSchemifier))
runOnce(name) {
MigrationOfProductFee.alterColumnProductFeeName(name)
}
}
}
private def addFastFirehoseAccountsView(startedBeforeSchemifier: Boolean): Boolean = {
if(startedBeforeSchemifier == true) {
logger.warn(s"Migration.database.addfastFirehoseAccountsView(true) cannot be run before Schemifier.")
true
} else {
val name = nameOf(addFastFirehoseAccountsView(startedBeforeSchemifier))
runOnce(name) {
MigrationOfFastFireHoseView.addFastFireHoseView(name)
}
}
}
private def addFastFirehoseAccountsMaterializedView(startedBeforeSchemifier: Boolean): Boolean = {
if(startedBeforeSchemifier == true) {
logger.warn(s"Migration.database.addfastFirehoseAccountsMaterializedView(true) cannot be run before Schemifier.")
true
} else {
val name = nameOf(addFastFirehoseAccountsMaterializedView(startedBeforeSchemifier))
runOnce(name) {
MigrationOfFastFireHoseMaterializedView.addFastFireHoseMaterializedView(name)
}
}
}
private def alterUserAuthContextColumnKeyAndValueLength(startedBeforeSchemifier: Boolean): Boolean = {
if(startedBeforeSchemifier == true) {
logger.warn(s"Migration.database.alterUserAuthContextColumnKeyAndValueLength(true) cannot be run before Schemifier.")
true
} else {
val name = nameOf(alterUserAuthContextColumnKeyAndValueLength(startedBeforeSchemifier))
runOnce(name) {
MigrationOfUserAuthContextFieldLength.alterColumnKeyAndValueLength(name)
}
}
}
}

View File

@ -0,0 +1,72 @@
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.dataAccess.AuthUser
import net.liftweb.common.Full
import net.liftweb.mapper.{DB, Schemifier}
import net.liftweb.util.DefaultConnectionIdentifier
object MigrationOfAuthUser {
val oneDayAgo = ZonedDateTime.now(ZoneId.of("UTC")).minusDays(1)
val oneYearInFuture = ZonedDateTime.now(ZoneId.of("UTC")).plusYears(1)
val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm'Z'")
def alterColumnUsernameProviderEmailFirstnameAndLastname(name: String): Boolean = {
DbFunction.tableExists(AuthUser, (DB.use(DefaultConnectionIdentifier){ conn => conn})) match {
case true =>
val startDate = System.currentTimeMillis()
val commitId: String = APIUtil.gitCommit
var isSuccessful = false
val executedSql =
DbFunction.maybeWrite(true, Schemifier.infoF _, DB.use(DefaultConnectionIdentifier){ conn => conn}) {
APIUtil.getPropsValue("db.driver") match {
case Full(value) if value.contains("com.microsoft.sqlserver.jdbc.SQLServerDriver") =>
() =>
"""
|ALTER TABLE authuser ALTER COLUMN username varchar(100);
|ALTER TABLE authuser ALTER COLUMN provider varchar(100);
|ALTER TABLE authuser ALTER COLUMN firstname varchar(100);
|ALTER TABLE authuser ALTER COLUMN lastname varchar(100);
|ALTER TABLE authuser ALTER COLUMN email varchar(100);
|""".stripMargin
case _ =>
() =>
"""
|ALTER TABLE authuser ALTER COLUMN username type varchar(100);
|ALTER TABLE authuser ALTER COLUMN provider type varchar(100);
|ALTER TABLE authuser ALTER COLUMN firstname type varchar(100);
|ALTER TABLE authuser ALTER COLUMN lastname type varchar(100);
|ALTER TABLE authuser ALTER COLUMN email type varchar(100);
|""".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"""${AuthUser._dbTableNameLC} table does not exist""".stripMargin
saveLog(name, commitId, isSuccessful, startDate, endDate, comment)
isSuccessful
}
}
}

View File

@ -0,0 +1,108 @@
package code.api.util.migration
import code.api.util.APIUtil
import code.api.util.migration.Migration.{DbFunction, saveLog}
import code.productfee.ProductFee
import net.liftweb.common.Full
import net.liftweb.mapper.{DB, Schemifier}
import net.liftweb.util.DefaultConnectionIdentifier
import java.time.format.DateTimeFormatter
import java.time.{ZoneId, ZonedDateTime}
object MigrationOfFastFireHoseMaterializedView {
val oneDayAgo = ZonedDateTime.now(ZoneId.of("UTC")).minusDays(1)
val oneYearInFuture = ZonedDateTime.now(ZoneId.of("UTC")).plusYears(1)
val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm'Z'")
def addFastFireHoseMaterializedView(name: String): Boolean = {
DbFunction.tableExists(ProductFee, (DB.use(DefaultConnectionIdentifier){ conn => conn})) match {
case true =>
val startDate = System.currentTimeMillis()
val commitId: String = APIUtil.gitCommit
var isSuccessful = false
def migrationSql(isMaterializedView:Boolean) =s"""
|CREATE ${if(isMaterializedView) "MATERIALIZED" else ""} VIEW mv_fast_firehose_accounts AS select
| mappedbankaccount.theaccountid as account_id,
| mappedbankaccount.bank as bank_id,
| mappedbankaccount.accountlabel as account_label,
| mappedbankaccount.accountnumber as account_number,
| (select
| string_agg(
| 'user_id:'
| || resourceuser.userid_
| ||',provider:'
| ||resourceuser.provider_
| ||',user_name:'
| ||resourceuser.name_,
| ',') as owners
| from resourceuser
| where
| resourceuser.id = mapperaccountholders.user_c
| ),
| mappedbankaccount.kind as kind,
| mappedbankaccount.accountcurrency as account_currency ,
| mappedbankaccount.accountbalance as account_balance,
| (select
| string_agg(
| 'bank_id:'
| ||bankaccountrouting.bankid
| ||',account_id:'
| ||bankaccountrouting.accountid,
| ','
| ) as account_routings
| from bankaccountrouting
| where
| bankaccountrouting.accountid = mappedbankaccount.theaccountid
| ),
| (select
| string_agg(
| 'type:'
| || mappedaccountattribute.mtype
| ||',code:'
| ||mappedaccountattribute.mcode
| ||',value:'
| ||mappedaccountattribute.mvalue,
| ',') as account_attributes
| from mappedaccountattribute
| where
| mappedaccountattribute.maccountid = mappedbankaccount.theaccountid
| )
|from mappedbankaccount
| LEFT JOIN mapperaccountholders
| ON (mappedbankaccount.bank = mapperaccountholders.accountbankpermalink and mappedbankaccount.theaccountid = mapperaccountholders.accountpermalink);
|""".stripMargin
val executedSql =
DbFunction.maybeWrite(true, Schemifier.infoF _, DB.use(DefaultConnectionIdentifier){ conn => conn}) {
APIUtil.getPropsValue("db.driver") openOr("org.h2.Driver") match {
case value if value.contains("org.h2.Driver") =>
() => migrationSql(false)//Note: H2 database, do not support the MATERIALIZED view
case _ =>
() => migrationSql(true)
}
}
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"""${ProductFee._dbTableNameLC} table does not exist""".stripMargin
saveLog(name, commitId, isSuccessful, startDate, endDate, comment)
isSuccessful
}
}
}

View File

@ -0,0 +1,102 @@
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.productfee.ProductFee
import net.liftweb.common.Full
import net.liftweb.mapper.{DB, Schemifier}
import net.liftweb.util.DefaultConnectionIdentifier
object MigrationOfFastFireHoseView {
val oneDayAgo = ZonedDateTime.now(ZoneId.of("UTC")).minusDays(1)
val oneYearInFuture = ZonedDateTime.now(ZoneId.of("UTC")).plusYears(1)
val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm'Z'")
def addFastFireHoseView(name: String): Boolean = {
DbFunction.tableExists(ProductFee, (DB.use(DefaultConnectionIdentifier){ conn => conn})) match {
case true =>
val startDate = System.currentTimeMillis()
val commitId: String = APIUtil.gitCommit
var isSuccessful = false
val executedSql =
DbFunction.maybeWrite(true, Schemifier.infoF _, DB.use(DefaultConnectionIdentifier){ conn => conn}) {
() =>
"""
|CREATE VIEW v_fast_firehose_accounts AS select
| mappedbankaccount.theaccountid as account_id,
| mappedbankaccount.bank as bank_id,
| mappedbankaccount.accountlabel as account_label,
| mappedbankaccount.accountnumber as account_number,
| (select
| string_agg(
| 'user_id:'
| || resourceuser.userid_
| ||',provider:'
| ||resourceuser.provider_
| ||',user_name:'
| ||resourceuser.name_,
| ',') as owners
| from resourceuser
| where
| resourceuser.id = mapperaccountholders.user_c
| ),
| mappedbankaccount.kind as kind,
| mappedbankaccount.accountcurrency as account_currency ,
| mappedbankaccount.accountbalance as account_balance,
| (select
| string_agg(
| 'bank_id:'
| ||bankaccountrouting.bankid
| ||',account_id:'
| ||bankaccountrouting.accountid,
| ','
| ) as account_routings
| from bankaccountrouting
| where
| bankaccountrouting.accountid = mappedbankaccount.theaccountid
| ),
| (select
| string_agg(
| 'type:'
| || mappedaccountattribute.mtype
| ||',code:'
| ||mappedaccountattribute.mcode
| ||',value:'
| ||mappedaccountattribute.mvalue,
| ',') as account_attributes
| from mappedaccountattribute
| where
| mappedaccountattribute.maccountid = mappedbankaccount.theaccountid
| )
|from mappedbankaccount
| LEFT JOIN mapperaccountholders
| ON (mappedbankaccount.bank = mapperaccountholders.accountbankpermalink and mappedbankaccount.theaccountid = mapperaccountholders.accountpermalink);
|""".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"""${ProductFee._dbTableNameLC} table does not exist""".stripMargin
saveLog(name, commitId, isSuccessful, startDate, endDate, comment)
isSuccessful
}
}
}

View File

@ -0,0 +1,56 @@
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.Consumer
import code.productAttributeattribute.MappedProductAttribute
import net.liftweb.mapper.DB
import net.liftweb.util.DefaultConnectionIdentifier
object MigrationOfProductAttribute {
val oneDayAgo = ZonedDateTime.now(ZoneId.of("UTC")).minusDays(1)
val oneYearInFuture = ZonedDateTime.now(ZoneId.of("UTC")).plusYears(1)
val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm'Z'")
def populateTheFieldIsActive(name: String): Boolean = {
DbFunction.tableExists(MappedProductAttribute, (DB.use(DefaultConnectionIdentifier){ conn => conn})) match {
case true =>
val startDate = System.currentTimeMillis()
val commitId: String = APIUtil.gitCommit
var isSuccessful = false
// Make back up
DbFunction.makeBackUpOfTable(MappedProductAttribute)
val emptyDeletedField =
for {
attribute <- MappedProductAttribute.findAll() if attribute.isActive.isEmpty == true
} yield {
attribute.IsActive(true).saveMe()
}
val endDate = System.currentTimeMillis()
val comment: String =
s"""Updated number of rows:
|${emptyDeletedField.size}
|""".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
}
}
}

View File

@ -0,0 +1,63 @@
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.productfee.ProductFee
import net.liftweb.common.Full
import net.liftweb.mapper.{DB, Schemifier}
import net.liftweb.util.DefaultConnectionIdentifier
object MigrationOfProductFee {
val oneDayAgo = ZonedDateTime.now(ZoneId.of("UTC")).minusDays(1)
val oneYearInFuture = ZonedDateTime.now(ZoneId.of("UTC")).plusYears(1)
val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm'Z'")
def alterColumnProductFeeName(name: String): Boolean = {
DbFunction.tableExists(ProductFee, (DB.use(DefaultConnectionIdentifier){ conn => conn})) match {
case true =>
val startDate = System.currentTimeMillis()
val commitId: String = APIUtil.gitCommit
var isSuccessful = false
val executedSql =
DbFunction.maybeWrite(true, Schemifier.infoF _, DB.use(DefaultConnectionIdentifier){ conn => conn}) {
APIUtil.getPropsValue("db.driver") match {
case Full(value) if value.contains("com.microsoft.sqlserver.jdbc.SQLServerDriver") =>
() =>
"""
|ALTER TABLE ProductFee ALTER COLUMN name varchar(100);
|""".stripMargin
case _ =>
() =>
"""
|ALTER TABLE ProductFee ALTER COLUMN name type varchar(100);
|""".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"""${ProductFee._dbTableNameLC} table does not exist""".stripMargin
saveLog(name, commitId, isSuccessful, startDate, endDate, comment)
isSuccessful
}
}
}

View File

@ -5,10 +5,11 @@ import java.time.{ZoneId, ZonedDateTime}
import code.api.util.APIUtil
import code.api.util.migration.Migration.{DbFunction, saveLog}
import code.model.Consumer
import code.model.dataAccess.ResourceUser
import code.model.{AppType, Consumer}
import net.liftweb.mapper.DB
import net.liftweb.util.{DefaultConnectionIdentifier, Helpers}
import net.liftweb.common.Full
import net.liftweb.mapper.{DB, Schemifier}
import net.liftweb.util.DefaultConnectionIdentifier
object MigrationOfResourceUser {
@ -28,7 +29,7 @@ object MigrationOfResourceUser {
val emptyDeletedField =
for {
user <- ResourceUser.findAll() if user.isDeleted == false
user <- ResourceUser.findAll() if user.isDeleted.getOrElse(false) == false
} yield {
user.IsDeleted(false).saveMe()
}
@ -53,4 +54,48 @@ object MigrationOfResourceUser {
isSuccessful
}
}
def alterColumnEmail(name: String): Boolean = {
DbFunction.tableExists(ResourceUser, (DB.use(DefaultConnectionIdentifier){ conn => conn})) match {
case true =>
val startDate = System.currentTimeMillis()
val commitId: String = APIUtil.gitCommit
var isSuccessful = false
val executedSql =
DbFunction.maybeWrite(true, Schemifier.infoF _, DB.use(DefaultConnectionIdentifier){ conn => conn}) {
APIUtil.getPropsValue("db.driver") match {
case Full(value) if value.contains("com.microsoft.sqlserver.jdbc.SQLServerDriver") =>
() =>
"""ALTER TABLE resourceuser ALTER COLUMN email varchar(100);
|""".stripMargin
case _ =>
() =>
"""ALTER TABLE resourceuser ALTER COLUMN email type varchar(100);
|""".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"""${ResourceUser._dbTableNameLC} table does not exist""".stripMargin
saveLog(name, commitId, isSuccessful, startDate, endDate, comment)
isSuccessful
}
}
}

View File

@ -0,0 +1,63 @@
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.context.MappedUserAuthContext
import net.liftweb.common.Full
import net.liftweb.mapper.{DB, Schemifier}
import net.liftweb.util.DefaultConnectionIdentifier
object MigrationOfUserAuthContextFieldLength {
val oneDayAgo = ZonedDateTime.now(ZoneId.of("UTC")).minusDays(1)
val oneYearInFuture = ZonedDateTime.now(ZoneId.of("UTC")).plusYears(1)
val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm'Z'")
def alterColumnKeyAndValueLength(name: String): Boolean = {
DbFunction.tableExists(MappedUserAuthContext, (DB.use(DefaultConnectionIdentifier){ conn => conn})) match {
case true =>
val startDate = System.currentTimeMillis()
val commitId: String = APIUtil.gitCommit
var isSuccessful = false
val executedSql =
DbFunction.maybeWrite(true, Schemifier.infoF _, DB.use(DefaultConnectionIdentifier){ conn => conn}) {
APIUtil.getPropsValue("db.driver") match {
case Full(value) if value.contains("com.microsoft.sqlserver.jdbc.SQLServerDriver") =>
() =>
"""
|ALTER TABLE MappedUserAuthContext ALTER COLUMN mKey varchar(4000);
|ALTER TABLE MappedUserAuthContext ALTER COLUMN mValue varchar(4000);
|""".stripMargin
case _ =>
() =>
"""
|ALTER TABLE MappedUserAuthContext ALTER COLUMN mKey type varchar(4000);
|ALTER TABLE MappedUserAuthContext ALTER COLUMN mValue type varchar(4000);
|""".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"""${MappedUserAuthContext._dbTableNameLC} table does not exist""".stripMargin
saveLog(name, commitId, isSuccessful, startDate, endDate, comment)
isSuccessful
}
}
}

View File

@ -2401,7 +2401,7 @@ trait APIMethods121 {
"getTransactionNarrative",
"GET",
"/banks/BANK_ID/accounts/ACCOUNT_ID/VIEW_ID/transactions/TRANSACTION_ID/metadata/narrative",
"Get narrative",
"Get a Transaction Narrative",
"""Returns the account owner description of the transaction [moderated](#1_2_1-getViewsForBankAccount) by the view.
|
|Authentication via OAuth is required if the view is not public.""",
@ -2437,7 +2437,7 @@ trait APIMethods121 {
"addTransactionNarrative",
"POST",
"/banks/BANK_ID/accounts/ACCOUNT_ID/VIEW_ID/transactions/TRANSACTION_ID/metadata/narrative",
"Add narrative",
"Add a Transaction Narrative",
s"""Creates a description of the transaction TRANSACTION_ID.
|
|Note: Unlike other items of metadata, there is only one "narrative" per transaction accross all views.
@ -2481,7 +2481,7 @@ trait APIMethods121 {
"updateTransactionNarrative",
"PUT",
"/banks/BANK_ID/accounts/ACCOUNT_ID/VIEW_ID/transactions/TRANSACTION_ID/metadata/narrative",
"Update narrative",
"Update a Transaction Narrative",
"""Updates the description of the transaction TRANSACTION_ID.
|
|Authentication via OAuth is required if the view is not public.""",
@ -2519,7 +2519,7 @@ trait APIMethods121 {
"deleteTransactionNarrative",
"DELETE",
"/banks/BANK_ID/accounts/ACCOUNT_ID/VIEW_ID/transactions/TRANSACTION_ID/metadata/narrative",
"Delete narrative",
"Delete a Transaction Narrative",
"""Deletes the description of the transaction TRANSACTION_ID.
|
|Authentication via OAuth is required if the view is not public.""",
@ -2556,7 +2556,7 @@ trait APIMethods121 {
"getCommentsForViewOnTransaction",
"GET",
"/banks/BANK_ID/accounts/ACCOUNT_ID/VIEW_ID/transactions/TRANSACTION_ID/metadata/comments",
"Get comments",
"Get Transaction Comments",
"""Returns the transaction TRANSACTION_ID comments made on a [view](#1_2_1-getViewsForBankAccount) (VIEW_ID).
|
|Authentication via OAuth is required if the view is not public.""",
@ -2593,7 +2593,7 @@ trait APIMethods121 {
"addCommentForViewOnTransaction",
"POST",
"/banks/BANK_ID/accounts/ACCOUNT_ID/VIEW_ID/transactions/TRANSACTION_ID/metadata/comments",
"Add comment",
"Add a Transaction Comment",
"""Posts a comment about a transaction TRANSACTION_ID on a [view](#1_2_1-getViewsForBankAccount) VIEW_ID.
|
|${authenticationRequiredMessage(false)}
@ -2639,7 +2639,7 @@ trait APIMethods121 {
"deleteCommentForViewOnTransaction",
"DELETE",
"/banks/BANK_ID/accounts/ACCOUNT_ID/VIEW_ID/transactions/TRANSACTION_ID/metadata/comments/COMMENT_ID",
"Delete comment",
"Delete a Transaction Comment",
"""Delete the comment COMMENT_ID about the transaction TRANSACTION_ID made on [view](#1_2_1-getViewsForBankAccount).
|
|Authentication via OAuth is required. The user must either have owner privileges for this account, or must be the user that posted the comment.""",
@ -2677,7 +2677,7 @@ trait APIMethods121 {
"getTagsForViewOnTransaction",
"GET",
"/banks/BANK_ID/accounts/ACCOUNT_ID/VIEW_ID/transactions/TRANSACTION_ID/metadata/tags",
"Get tags",
"Get Transaction Tags",
"""Returns the transaction TRANSACTION_ID tags made on a [view](#1_2_1-getViewsForBankAccount) (VIEW_ID).
Authentication via OAuth is required if the view is not public.""",
emptyObjectJson,
@ -2713,7 +2713,7 @@ trait APIMethods121 {
"addTagForViewOnTransaction",
"POST",
"/banks/BANK_ID/accounts/ACCOUNT_ID/VIEW_ID/transactions/TRANSACTION_ID/metadata/tags",
"Add a tag",
"Add a Transaction Tag",
s"""Posts a tag about a transaction TRANSACTION_ID on a [view](#1_2_1-getViewsForBankAccount) VIEW_ID.
|
|${authenticationRequiredMessage(true)}
@ -2759,7 +2759,7 @@ trait APIMethods121 {
"deleteTagForViewOnTransaction",
"DELETE",
"/banks/BANK_ID/accounts/ACCOUNT_ID/VIEW_ID/transactions/TRANSACTION_ID/metadata/tags/TAG_ID",
"Delete a tag",
"Delete a Transaction Tag",
"""Deletes the tag TAG_ID about the transaction TRANSACTION_ID made on [view](#1_2_1-getViewsForBankAccount).
|Authentication via OAuth is required. The user must either have owner privileges for this account,
|or must be the user that posted the tag.
@ -2796,7 +2796,7 @@ trait APIMethods121 {
"getImagesForViewOnTransaction",
"GET",
"/banks/BANK_ID/accounts/ACCOUNT_ID/VIEW_ID/transactions/TRANSACTION_ID/metadata/images",
"Get images",
"Get Transaction Images",
"""Returns the transaction TRANSACTION_ID images made on a [view](#1_2_1-getViewsForBankAccount) (VIEW_ID).
Authentication via OAuth is required if the view is not public.""",
emptyObjectJson,
@ -2832,7 +2832,7 @@ trait APIMethods121 {
"addImageForViewOnTransaction",
"POST",
"/banks/BANK_ID/accounts/ACCOUNT_ID/VIEW_ID/transactions/TRANSACTION_ID/metadata/images",
"Add an image",
"Add a Transaction Image",
s"""Posts an image about a transaction TRANSACTION_ID on a [view](#1_2_1-getViewsForBankAccount) VIEW_ID.
|
|${authenticationRequiredMessage(true) }
@ -2878,7 +2878,7 @@ trait APIMethods121 {
"deleteImageForViewOnTransaction",
"DELETE",
"/banks/BANK_ID/accounts/ACCOUNT_ID/VIEW_ID/transactions/TRANSACTION_ID/metadata/images/IMAGE_ID",
"Delete an image",
"Delete a Transaction Image",
"""Deletes the image IMAGE_ID about the transaction TRANSACTION_ID made on [view](#1_2_1-getViewsForBankAccount).
|
|Authentication via OAuth is required. The user must either have owner privileges for this account, or must be the user that posted the image.""",
@ -2919,7 +2919,7 @@ trait APIMethods121 {
"getWhereTagForViewOnTransaction",
"GET",
"/banks/BANK_ID/accounts/ACCOUNT_ID/VIEW_ID/transactions/TRANSACTION_ID/metadata/where",
"Get where tag",
"Get a Transaction where Tag",
"""Returns the "where" Geo tag added to the transaction TRANSACTION_ID made on a [view](#1_2_1-getViewsForBankAccount) (VIEW_ID).
|It represents the location where the transaction has been initiated.
|
@ -2956,7 +2956,7 @@ trait APIMethods121 {
"addWhereTagForViewOnTransaction",
"POST",
"/banks/BANK_ID/accounts/ACCOUNT_ID/VIEW_ID/transactions/TRANSACTION_ID/metadata/where",
"Add where tag",
"Add a Transaction where Tag",
s"""Creates a "where" Geo tag on a transaction TRANSACTION_ID in a [view](#1_2_1-getViewsForBankAccount).
|
|${authenticationRequiredMessage(true)}
@ -3002,7 +3002,7 @@ trait APIMethods121 {
"updateWhereTagForViewOnTransaction",
"PUT",
"/banks/BANK_ID/accounts/ACCOUNT_ID/VIEW_ID/transactions/TRANSACTION_ID/metadata/where",
"Update where tag",
"Update a Transaction where Tag",
s"""Updates the "where" Geo tag on a transaction TRANSACTION_ID in a [view](#1_2_1-getViewsForBankAccount).
|
|${authenticationRequiredMessage(true)}
@ -3048,7 +3048,7 @@ trait APIMethods121 {
"deleteWhereTagForViewOnTransaction",
"DELETE",
"/banks/BANK_ID/accounts/ACCOUNT_ID/VIEW_ID/transactions/TRANSACTION_ID/metadata/where",
"Delete where tag",
"Delete a Transaction Tag",
s"""Deletes the where tag of the transaction TRANSACTION_ID made on [view](#1_2_1-getViewsForBankAccount).
|
|${authenticationRequiredMessage(true)}

View File

@ -4,8 +4,10 @@ import code.api.berlin.group.v1_3.JvalueCaseClass
import java.util.Date
import code.api.util.APIUtil.{EmptyBody, PrimaryDataBody, ResourceDoc}
import code.api.util.ApiTag.ResourceDocTag
import code.api.util.Glossary.glossaryItems
import code.api.util.{APIUtil, ApiRole, ConnectorField, CustomJsonFormats, ExampleValue, PegdownOptions}
import code.bankconnectors.LocalMappedConnector.getAllEndpointTagsBox
import com.openbankproject.commons.model.ListResult
import code.crm.CrmEvent.CrmEvent
import com.openbankproject.commons.model.TransactionRequestTypeCharge
@ -17,7 +19,6 @@ import net.liftweb.json.Extraction.decompose
import net.liftweb.json.{Formats, JDouble, JInt, JString}
import net.liftweb.json.JsonAST.{JArray, JBool, JNothing, JObject, JValue}
import net.liftweb.util.StringHelpers
import code.util.Helper.MdcLoggable
import org.apache.commons.lang3.StringUtils
@ -324,7 +325,10 @@ object JSONFactory1_4_0 extends MdcLoggable{
function : String // The val / partial function that implements the call e.g. "getBranches"
)
case class ResourceDocMeta(
response_date: Date,
count: Int
)
// Used to describe the OBP API calls for documentation and API discovery purposes
case class ResourceDocJson(operation_id: String,
implemented_by: ImplementedByJson,
@ -343,13 +347,17 @@ object JSONFactory1_4_0 extends MdcLoggable{
is_featured: Boolean,
special_instructions: String,
specified_url: String, // Derived value. The Url when called under a certain version.
connector_methods: List[String] // this is the connector methods which need to be connected by this endpoint.
connector_methods: List[String], // this is the connector methods which need to be connected by this endpoint.
created_by_bank_id: Option[String] = None
)
// Creates the json resource_docs
case class ResourceDocsJson (resource_docs : List[ResourceDocJson])
case class ResourceDocsJson (
resource_docs : List[ResourceDocJson],
meta: Option[ResourceDocMeta] = None
)
/**
* get the glossaryItem.title by the input string
@ -444,10 +452,12 @@ object JSONFactory1_4_0 extends MdcLoggable{
private val createResourceDocJsonMemo = new ConcurrentHashMap[ResourceDoc, ResourceDocJson]
def createResourceDocJson(rd: ResourceDoc) : ResourceDocJson = {
def createResourceDocJson(rd: ResourceDoc, isVersion4OrHigher:Boolean) : ResourceDocJson = {
// if this calculate conversion already happened before, just return that value
// if not calculated before, just do conversion
createResourceDocJsonMemo.computeIfAbsent(rd, _=>{
val endpointTags = getAllEndpointTagsBox(rd.operationId).map(endpointTag =>ResourceDocTag(endpointTag.tagName))
val resourceDocUpdatedTags = rd.copy(tags = endpointTags++ rd.tags)
createResourceDocJsonMemo.computeIfAbsent(resourceDocUpdatedTags, _=>{
// There are multiple flavours of markdown. For instance, original markdown emphasises underscores (surrounds _ with (<em>))
// But we don't want to have to escape underscores (\_) in our documentation
// Thus we use a flavour of markdown that ignores underscores in words. (Github markdown does this too)
@ -458,55 +468,63 @@ object JSONFactory1_4_0 extends MdcLoggable{
// 2rd: Dynamic endpoint endpoints,
// 3rd: all the user created endpoints,
val fieldsDescription =
if(rd.tags.toString.contains("Dynamic-Entity")
||rd.tags.toString.contains("Dynamic-Endpoint")
||rd.roles.toString.contains("DynamicEntity")
||rd.roles.toString.contains("DynamicEntities")
||rd.roles.toString.contains("DynamicEndpoint")) {
if(resourceDocUpdatedTags.tags.toString.contains("Dynamic-Entity")
||resourceDocUpdatedTags.tags.toString.contains("Dynamic-Endpoint")
||resourceDocUpdatedTags.roles.toString.contains("DynamicEntity")
||resourceDocUpdatedTags.roles.toString.contains("DynamicEntities")
||resourceDocUpdatedTags.roles.toString.contains("DynamicEndpoint")) {
""
} else{
//1st: prepare the description from URL
val urlParametersDescription: String = prepareUrlParameterDescription(rd.requestUrl)
val urlParametersDescription: String = prepareUrlParameterDescription(resourceDocUpdatedTags.requestUrl)
//2rd: get the fields description from the post json body:
val exampleRequestBodyFieldsDescription =
if (rd.requestVerb=="POST" ){
prepareJsonFieldDescription(rd.exampleRequestBody,"request")
if (resourceDocUpdatedTags.requestVerb=="POST" ){
prepareJsonFieldDescription(resourceDocUpdatedTags.exampleRequestBody,"request")
} else {
""
}
//3rd: get the fields description from the response body:
val responseFieldsDescription = prepareJsonFieldDescription(rd.successResponseBody,"response")
val responseFieldsDescription = prepareJsonFieldDescription(resourceDocUpdatedTags.successResponseBody,"response")
urlParametersDescription ++ exampleRequestBodyFieldsDescription ++ responseFieldsDescription
}
val description = rd.description.stripMargin.trim ++ fieldsDescription
val description = resourceDocUpdatedTags.description.stripMargin.trim ++ fieldsDescription
ResourceDocJson(
operation_id = rd.operationId,
request_verb = rd.requestVerb,
request_url = rd.requestUrl,
summary = rd.summary.replaceFirst("""\.(\s*)$""", "$1"), // remove the ending dot in summary
operation_id = resourceDocUpdatedTags.operationId,
request_verb = resourceDocUpdatedTags.requestVerb,
request_url = resourceDocUpdatedTags.requestUrl,
summary = resourceDocUpdatedTags.summary.replaceFirst("""\.(\s*)$""", "$1"), // remove the ending dot in summary
// Strip the margin character (|) and line breaks and convert from markdown to html
description = PegdownOptions.convertPegdownToHtmlTweaked(description), //.replaceAll("\n", ""),
description_markdown = description,
example_request_body = rd.exampleRequestBody,
success_response_body = rd.successResponseBody,
error_response_bodies = rd.errorResponseBodies,
implemented_by = ImplementedByJson(rd.implementedInApiVersion.fullyQualifiedVersion, rd.partialFunctionName), // was rd.implementedInApiVersion.noV
tags = rd.tags.map(i => i.tag),
typed_request_body = createTypedBody(rd.exampleRequestBody),
typed_success_response_body = createTypedBody(rd.successResponseBody),
roles = rd.roles,
is_featured = rd.isFeatured,
special_instructions = PegdownOptions.convertPegdownToHtmlTweaked(rd.specialInstructions.getOrElse("").stripMargin),
specified_url = rd.specifiedUrl.getOrElse(""),
connector_methods = rd.connectorMethods
example_request_body = resourceDocUpdatedTags.exampleRequestBody,
success_response_body = resourceDocUpdatedTags.successResponseBody,
error_response_bodies = resourceDocUpdatedTags.errorResponseBodies,
implemented_by = ImplementedByJson(resourceDocUpdatedTags.implementedInApiVersion.fullyQualifiedVersion, resourceDocUpdatedTags.partialFunctionName), // was resourceDocUpdatedTags.implementedInApiVersion.noV
tags = resourceDocUpdatedTags.tags.map(i => i.tag),
typed_request_body = createTypedBody(resourceDocUpdatedTags.exampleRequestBody),
typed_success_response_body = createTypedBody(resourceDocUpdatedTags.successResponseBody),
roles = resourceDocUpdatedTags.roles,
is_featured = resourceDocUpdatedTags.isFeatured,
special_instructions = PegdownOptions.convertPegdownToHtmlTweaked(resourceDocUpdatedTags.specialInstructions.getOrElse("").stripMargin),
specified_url = resourceDocUpdatedTags.specifiedUrl.getOrElse(""),
connector_methods = resourceDocUpdatedTags.connectorMethods,
created_by_bank_id= if (isVersion4OrHigher) rd.createdByBankId else None // only for V400 we show the bankId
)
})
})
}
def createResourceDocsJson(resourceDocList: List[ResourceDoc]) : ResourceDocsJson = {
ResourceDocsJson(resourceDocList.map(createResourceDocJson))
def createResourceDocsJson(resourceDocList: List[ResourceDoc], isVersion4OrHigher:Boolean) : ResourceDocsJson = {
if(isVersion4OrHigher){
ResourceDocsJson(
resourceDocList.map(createResourceDocJson(_,isVersion4OrHigher)),
meta=Some(ResourceDocMeta(new Date(), resourceDocList.length))
)
} else {
ResourceDocsJson(resourceDocList.map(createResourceDocJson(_,false)))
}
}
//please check issue first: https://github.com/OpenBankProject/OBP-API/issues/877

View File

@ -1477,7 +1477,7 @@ trait APIMethods200 {
cc =>
for {
postedData <- tryo {json.extract[CreateUserJson]} ?~! ErrorMessages.InvalidJsonFormat
_ <- tryo(assert(isValidStrongPassword(postedData.password))) ?~! ErrorMessages.InvalidStrongPasswordFormat
_ <- tryo(assert(validatePasswordOnCreation(postedData.password))) ?~! ErrorMessages.InvalidStrongPasswordFormat
} yield {
if (AuthUser.find(By(AuthUser.username, postedData.username)).isEmpty) {
val userCreated = AuthUser.create

View File

@ -1,7 +1,6 @@
package code.api.v2_2_0
import java.util.Date
import code.api.ResourceDocs1_4_0.SwaggerDefinitionsJSON._
import code.api.util.APIUtil._
import code.api.util.ApiRole.{canCreateBranch, _}
@ -14,6 +13,7 @@ import code.api.v2_1_0._
import code.api.v2_2_0.JSONFactory220.transformV220ToBranch
import code.bankconnectors._
import code.consumer.Consumers
import code.entitlement.Entitlement
import code.fx.{MappedFXRate, fx}
import code.metadata.counterparties.{Counterparties, MappedCounterparty}
import code.metrics.ConnectorMetricsProvider
@ -455,6 +455,23 @@ trait APIMethods220 {
bank.bank_routing.scheme,
bank.bank_routing.address
)
entitlements <- Entitlement.entitlement.vend.getEntitlementsByUserId(u.userId)
entitlementsByBank = entitlements.filter(_.bankId==bank.id)
_ <- entitlementsByBank.filter(_.roleName == CanCreateEntitlementAtOneBank.toString()).size > 0 match {
case true =>
// Already has entitlement
Full()
case false =>
Full(Entitlement.entitlement.vend.addEntitlement(bank.id, u.userId, CanCreateEntitlementAtOneBank.toString()))
}
_ <- entitlementsByBank.filter(_.roleName == CanReadDynamicResourceDocsAtOneBank.toString()).size > 0 match {
case true =>
// Already has entitlement
Full()
case false =>
Full(Entitlement.entitlement.vend.addEntitlement(bank.id, u.userId, CanReadDynamicResourceDocsAtOneBank.toString()))
}
} yield {
val json = JSONFactory220.createBankJSON(success)
createdJsonResponse(Extraction.decompose(json))
@ -607,6 +624,7 @@ trait APIMethods220 {
family = product.family,
superFamily = product.super_family,
moreInfoUrl = product.more_info_url,
termsAndConditionsUrl = null,
details = product.details,
description = product.description,
metaLicenceId = product.meta.license.id,
@ -670,6 +688,8 @@ trait APIMethods220 {
(bank, callContext) <- BankX(bankId, Some(cc)) ?~! BankNotFound
_ <- NewStyle.function.hasAllEntitlements(bank.bankId.value, u.userId, createFxEntitlementsRequiredForSpecificBank, createFxEntitlementsRequiredForAnyBank, callContext)
fx <- tryo {json.extract[FXRateJsonV220]} ?~! ErrorMessages.InvalidJsonFormat
_ <- booleanToBox(APIUtil.isValidCurrencyISOCode(fx.from_currency_code),InvalidISOCurrencyCode+s"Current from_currency_code is ${fx.from_currency_code}")
_ <- booleanToBox(APIUtil.isValidCurrencyISOCode(fx.to_currency_code),InvalidISOCurrencyCode+s"Current to_currency_code is ${fx.to_currency_code}")
success <- Connector.connector.vend.createOrUpdateFXRate(
bankId = fx.bank_id,
fromCurrencyCode = fx.from_currency_code,

View File

@ -2027,8 +2027,11 @@ trait APIMethods300 {
nameOf(getApiGlossary),
"GET",
"/api/glossary",
"Get API Glossary",
"""Returns the glossary of the API
"Get Glossary of the API",
"""Get API Glossary
|
|Returns the glossary of the API
|
|""",
emptyObjectJson,
glossaryItemsJsonV300,

View File

@ -1450,7 +1450,6 @@ trait APIMethods310 {
List(
UserNotLoggedIn,
UserHasMissingRoles,
CreateUserAuthContextError,
UnknownError
),
List(apiTagUser, apiTagNewStyle),
@ -1973,7 +1972,7 @@ trait APIMethods310 {
_ <- NewStyle.function.hasEntitlement("", userId, canRefreshUser, callContext)
startTime <- Future{Helpers.now}
_ <- NewStyle.function.findByUserId(userId, callContext)
_ <- if (APIUtil.isSandboxMode) Future{} else AuthUser.updateUserAccountViewsFuture(u, callContext)
_ <- Future{refreshUserIfRequired(Full(u), callContext)}
endTime <- Future{Helpers.now}
durationTime = endTime.getTime - startTime.getTime
} yield {
@ -2058,6 +2057,7 @@ trait APIMethods310 {
postedData.name,
productAttributeType,
postedData.value,
None,
callContext: Option[CallContext]
)
} yield {
@ -2154,6 +2154,7 @@ trait APIMethods310 {
postedData.name,
productAttributeType,
postedData.value,
None,
callContext: Option[CallContext]
)
} yield {
@ -2428,22 +2429,6 @@ trait APIMethods310 {
}
}
val productHiearchyAndCollectionNote =
"""
|
|Product hiearchy vs Product Collections:
|
|* You can define a hierarchy of products - so that a child Product inherits attributes of its parent Product - using the parent_product_code in Product.
|
|* You can define a collection (also known as baskets or buckets) of products using Product Collections.
|
""".stripMargin
val createProductEntitlements = canCreateProduct :: canCreateProductAtAnyBank :: Nil
val createProductEntitlementsRequiredText = UserHasMissingRoles + createProductEntitlements.mkString(" or ")
resourceDocs += ResourceDoc(
createProduct,
implementedInApiVersion,
@ -2509,6 +2494,7 @@ trait APIMethods310 {
family = product.family,
superFamily = product.super_family,
moreInfoUrl = product.more_info_url,
termsAndConditionsUrl = null,
details = product.details,
description = product.description,
metaLicenceId = product.meta.license.id,
@ -2524,8 +2510,6 @@ trait APIMethods310 {
}
val getProductsIsPublic = APIUtil.getPropsAsBoolValue("apiOptions.getProductsIsPublic", true)
resourceDocs += ResourceDoc(
getProduct,
implementedInApiVersion,
@ -3241,7 +3225,7 @@ trait APIMethods310 {
lazy val getMessageDocsSwagger: OBPEndpoint = {
case "message-docs" :: restConnectorVersion ::"swagger2.0" :: Nil JsonGet _ => {
val (resourceDocTags, partialFunctions, languageParam, contentParam, apiCollectionIdParam) = ResourceDocsAPIMethodsUtil.getParams()
val (resourceDocTags, partialFunctions, languageParam, contentParam, apiCollectionIdParam, cacheModifierParam) = ResourceDocsAPIMethodsUtil.getParams()
cc => {
for {
(_, callContext) <- anonymousAccess(cc)

File diff suppressed because it is too large Load Diff

View File

@ -28,36 +28,40 @@ package code.api.v4_0_0
import java.text.SimpleDateFormat
import java.util.Date
import code.api.attributedefinition.AttributeDefinition
import code.api.util.APIUtil
import code.api.util.APIUtil.{DateWithDay, DateWithSeconds, stringOptionOrNull, stringOrNull}
import code.api.v1_2_1.JSONFactory.{createAmountOfMoneyJSON, createOwnersJSON}
import code.api.v1_2_1.{BankRoutingJsonV121, JSONFactory, UserJSONV121, ViewJSONV121}
import code.api.v1_4_0.JSONFactory1_4_0.{LocationJsonV140, MetaJsonV140, TransactionRequestAccountJsonV140, transformToLocationFromV140, transformToMetaFromV140}
import code.api.v2_0_0.{EntitlementJSONs, JSONFactory200, TransactionRequestChargeJsonV200}
import code.api.v2_0_0.JSONFactory200.UserJsonV200
import code.api.v2_0_0.{CreateEntitlementJSON, EntitlementJSONs, JSONFactory200, TransactionRequestChargeJsonV200}
import code.api.v2_1_0.{IbanJson, JSONFactory210, PostCounterpartyBespokeJson, ResourceUserJSON}
import code.api.v2_2_0.CounterpartyMetadataJson
import code.api.v3_0_0.JSONFactory300.{createAccountRoutingsJSON, createAccountRulesJSON, createLocationJson, createMetaJson, transformToAddressFromV300}
import code.api.v3_0_0.{AccountRuleJsonV300, AddressJsonV300, CustomerAttributeResponseJsonV300, OpeningTimesV300, ViewJSON300, ViewsJSON300}
import code.api.v3_1_0.JSONFactory310.createAccountAttributeJson
import code.api.v3_1_0.{AccountAttributeResponseJson, RedisCallLimitJson}
import code.api.v3_0_0.JSONFactory300._
import code.api.v3_0_0._
import code.api.v3_1_0.JSONFactory310.{createAccountAttributeJson, createProductAttributesJson}
import code.api.v3_1_0.{AccountAttributeResponseJson, PostHistoricalTransactionResponseJson, ProductAttributeResponseWithoutBankIdJson, RedisCallLimitJson}
import code.apicollection.ApiCollectionTrait
import code.apicollectionendpoint.ApiCollectionEndpointTrait
import code.atms.Atms.Atm
import code.bankattribute.BankAttribute
import code.consent.MappedConsent
import code.entitlement.Entitlement
import code.model.dataAccess.ResourceUser
import code.model.{Consumer, ModeratedBankAccount, ModeratedBankAccountCore}
import code.ratelimiting.RateLimiting
import code.standingorders.StandingOrderTrait
import code.transactionrequests.TransactionRequests.TransactionChallengeTypes
import code.userlocks.UserLocks
import code.users.UserInvitation
import com.openbankproject.commons.model.{DirectDebitTrait, _}
import code.users.{UserAgreement, UserInvitation}
import com.openbankproject.commons.model.{DirectDebitTrait, ProductFeeTrait, _}
import net.liftweb.common.{Box, Full}
import net.liftweb.json.JValue
import net.liftweb.mapper.By
import scala.collection.immutable.List
import scala.math.BigDecimal
import scala.util.Try
@ -96,7 +100,8 @@ case class BankJson400(
full_name: String,
logo: String,
website: String,
bank_routings: List[BankRoutingJsonV121]
bank_routings: List[BankRoutingJsonV121],
attributes: Option[List[BankAttributeBankResponseJsonV400]]
)
case class BanksJson400(banks: List[BankJson400])
@ -122,6 +127,31 @@ case class TransactionRequestWithChargeJSON400(
challenges: List[ChallengeJsonV400],
charge : TransactionRequestChargeJsonV200
)
case class PostHistoricalTransactionAtBankJson(
from_account_id: String,
to_account_id: String,
value: AmountOfMoneyJsonV121,
description: String,
posted: String,
completed: String,
`type`: String,
charge_policy: String
)
case class HistoricalTransactionAccountJsonV400(
bank_id: String,
account_id : String
)
case class PostHistoricalTransactionResponseJsonV400(
transaction_id: String,
from: HistoricalTransactionAccountJsonV400,
to: HistoricalTransactionAccountJsonV400,
value: AmountOfMoneyJsonV121,
description: String,
posted: Date,
completed: Date,
transaction_request_type: String,
charge_policy: String
)
case class PostResetPasswordUrlJsonV400(username: String, email: String, user_id: String)
case class ResetPasswordUrlJsonV400(reset_password_url: String)
@ -150,7 +180,8 @@ case class APIInfoJson400(
connector : String,
hosted_by : HostedBy400,
hosted_at : HostedAt400,
energy_source : EnergySource400
energy_source : EnergySource400,
resource_docs_requires_role: Boolean
)
case class HostedBy400(
organisation : String,
@ -195,6 +226,22 @@ case class ModeratedFirehoseAccountsJsonV400(
accounts: List[ModeratedFirehoseAccountJsonV400]
)
case class FastFirehoseAccountJsonV400(
id: String,
bank_id: String,
label: String,
number: String,
owners: String,
product_code: String,
balance: AmountOfMoneyJsonV121,
account_routings: String ,
account_attributes: String
)
case class FastFirehoseAccountsJsonV400(
accounts: List[FastFirehoseAccountJsonV400]
)
case class ModeratedAccountJSON400(
id : String,
label : String,
@ -307,6 +354,8 @@ case class StandingOrderJsonV400(standing_order_id: String,
active: Boolean)
case class PostViewJsonV400(view_id: String, is_system: Boolean)
case class PostAccountAccessJsonV400(user_id: String, view: PostViewJsonV400)
case class PostCreateUserAccountAccessJsonV400(username: String, provider:String, views: List[PostViewJsonV400])
case class PostCreateUserWithRolesJsonV400(username: String, provider:String, roles: List[CreateEntitlementJSON])
case class PostRevokeGrantAccountAccessJsonV400(views: List[String])
case class RevokedJsonV400(revoked: Boolean)
@ -545,6 +594,50 @@ case class DynamicEndpointHostJson400(
host: String
)
case class EndpointTagJson400(
tag_name: String,
)
case class SystemLevelEndpointTagResponseJson400(
endpoint_tag_id: String,
operation_id: String,
tag_name: String
)
case class BankLevelEndpointTagResponseJson400(
bank_id: String,
endpoint_tag_id: String,
operation_id: String,
tag_name: String
)
case class MySpaces(
bank_ids: List[String],
)
case class ProductJsonV400(
bank_id: String,
product_code: String,
parent_product_code: String,
name: String,
more_info_url: String,
terms_and_conditions_url: String,
description: String,
meta: MetaJsonV140,
attributes: Option[List[ProductAttributeResponseWithoutBankIdJson]],
fees: Option[List[ProductFeeJsonV400]]
)
case class ProductsJsonV400(products: List[ProductJsonV400])
case class PutProductJsonV400(
parent_product_code: String,
name: String,
more_info_url: String,
terms_and_conditions_url: String,
description: String,
meta: MetaJsonV140,
)
case class CounterpartyJson400(
name: String,
description: String,
@ -588,7 +681,8 @@ case class ApiCollectionJson400 (
api_collection_id: String,
user_id: String,
api_collection_name: String,
is_sharable: Boolean
is_sharable: Boolean,
description: String
)
case class ApiCollectionsJson400 (
api_collections: List[ApiCollectionJson400]
@ -596,7 +690,8 @@ case class ApiCollectionsJson400 (
case class PostApiCollectionJson400(
api_collection_name: String,
is_sharable: Boolean
is_sharable: Boolean,
description: Option[String]
)
case class ApiCollectionEndpointJson400 (
@ -632,6 +727,77 @@ case class JsonSchemaV400(
case class JsonValidationV400(operation_id: String, json_schema: JsonSchemaV400)
// Validation related END
case class ProductAttributeJsonV400(
name: String,
`type`: String,
value: String,
is_active: Option[Boolean]
)
case class ProductAttributeResponseJsonV400(
bank_id: String,
product_code: String,
product_attribute_id: String,
name: String,
`type`: String,
value: String,
is_active: Option[Boolean]
)
case class ProductAttributeResponseWithoutBankIdJsonV400(
product_code: String,
product_attribute_id: String,
name: String,
`type`: String,
value: String,
is_active: Option[Boolean]
)
case class BankAttributeJsonV400(
name: String,
`type`: String,
value: String,
is_active: Option[Boolean])
case class BankAttributeResponseJsonV400(
bank_id: String,
bank_attribute_id: String,
name: String,
`type`: String,
value: String,
is_active: Option[Boolean]
)
case class BankAttributesResponseJsonV400(bank_attributes: List[BankAttributeResponseJsonV400])
case class BankAttributeBankResponseJsonV400(name: String,
value: String)
case class BankAttributesResponseJson(list: List[BankAttributeBankResponseJsonV400])
case class ProductFeeValueJsonV400(
currency: String,
amount: BigDecimal,
frequency: String,
`type`: String,
)
case class ProductFeeJsonV400(
product_fee_id:Option[String],
name: String,
is_active: Boolean,
more_info: String,
value:ProductFeeValueJsonV400,
)
case class ProductFeeResponseJsonV400(
bank_id: String,
product_code: String,
product_fee_id: String,
name: String,
is_active: Boolean,
more_info: String,
value:ProductFeeValueJsonV400,
)
case class ProductFeesResponseJsonV400(
product_fees: List[ProductFeeResponseJsonV400]
)
case class IbanCheckerJsonV400(
is_valid: Boolean,
@ -767,6 +933,7 @@ case class AtmJsonV400 (
case class AtmsJsonV400(atms : List[AtmJsonV400])
case class UserAgreementJson(`type`: String, text: String)
case class UserJsonV400(
user_id: String,
email : String,
@ -775,12 +942,15 @@ case class UserJsonV400(
username : String,
entitlements : EntitlementJSONs,
views: Option[ViewsJSON300],
is_deleted: Boolean
agreements: Option[List[UserAgreementJson]],
is_deleted: Boolean,
last_marketing_agreement_signed_date: Option[Date]
)
case class UsersJsonV400(users: List[UserJsonV400])
object JSONFactory400 {
def createUserInfoJSON(user : User, entitlements: List[Entitlement]) : UserJsonV400 = {
def createUserInfoJSON(user : User, entitlements: List[Entitlement], agreements: Option[List[UserAgreement]]) : UserJsonV400 = {
UserJsonV400(
user_id = user.userId,
email = user.emailAddress,
@ -789,7 +959,23 @@ object JSONFactory400 {
provider = stringOrNull(user.provider),
entitlements = JSONFactory200.createEntitlementJSONs(entitlements),
views = None,
is_deleted = user.isDeleted.getOrElse(false)
agreements = agreements.map(_.map( i =>
UserAgreementJson(`type` = i.agreementType, text = i.agreementText))
),
is_deleted = user.isDeleted.getOrElse(false),
last_marketing_agreement_signed_date = user.lastMarketingAgreementSignedDate
)
}
def createUsersJson(users : List[(ResourceUser, Box[List[Entitlement]], Option[List[UserAgreement]])]) : UsersJsonV400 = {
UsersJsonV400(
users.map(t =>
createUserInfoJSON(
t._1,
t._2.getOrElse(Nil),
t._3
)
)
)
}
@ -811,7 +997,7 @@ object JSONFactory400 {
}
def createBankJSON400(bank: Bank): BankJson400 = {
def createBankJSON400(bank: Bank, attributes: List[BankAttribute] = Nil): BankJson400 = {
val obp = BankRoutingJsonV121("OBP", bank.bankId.value)
val bic = BankRoutingJsonV121("BIC", bank.swiftBic)
val routings = bank.bankRoutingScheme match {
@ -825,12 +1011,18 @@ object JSONFactory400 {
stringOrNull(bank.fullName),
stringOrNull(bank.logoUrl),
stringOrNull(bank.websiteUrl),
routings.filter(a => stringOrNull(a.address) != null)
routings.filter(a => stringOrNull(a.address) != null),
Option(
attributes.filter(_.isActive == Some(true)).map(a => BankAttributeBankResponseJsonV400(
name = a.name,
value = a.value)
)
)
)
}
def createBanksJson(l: List[Bank]): BanksJson400 = {
BanksJson400(l.map(createBankJSON400))
BanksJson400(l.map(i => createBankJSON400(i, Nil)))
}
def createUserIdInfoJson(user : User) : UserIdJsonV400 = {
@ -1001,6 +1193,24 @@ object JSONFactory400 {
)
)
}
def createFirehoseBankAccountJSON(firehoseAccounts : List[FastFirehoseAccount]) : FastFirehoseAccountsJsonV400 = {
FastFirehoseAccountsJsonV400(
firehoseAccounts.map(
account =>
FastFirehoseAccountJsonV400(
account.id,
account.bankId,
account.label,
account.number,
account.owners,
account.productCode,
AmountOfMoneyJsonV121(account.balance.currency, account.balance.amount),
account.accountRoutings,
account.accountAttributes
)
)
)
}
def createEntitlementJSONs(entitlements: List[Entitlement]): EntitlementsJsonV400 = {
@ -1276,6 +1486,7 @@ object JSONFactory400 {
apiCollection.userId,
apiCollection.apiCollectionName,
apiCollection.isSharable,
apiCollection.description
)
}
def createIbanCheckerJson(iban: IbanChecker): IbanCheckerJsonV400 = {
@ -1341,6 +1552,50 @@ object JSONFactory400 {
)
}
def createProductAttributeJson(productAttribute: ProductAttribute): ProductAttributeResponseJsonV400 =
ProductAttributeResponseJsonV400(
bank_id = productAttribute.bankId.value,
product_code = productAttribute.productCode.value,
product_attribute_id = productAttribute.productAttributeId,
name = productAttribute.name,
`type` = productAttribute.attributeType.toString,
value = productAttribute.value,
is_active = productAttribute.isActive
)
def createBankAttributeJson(bankAttribute: BankAttribute): BankAttributeResponseJsonV400 =
BankAttributeResponseJsonV400(
bank_id = bankAttribute.bankId.value,
bank_attribute_id = bankAttribute.bankAttributeId,
name = bankAttribute.name,
`type` = bankAttribute.attributeType.toString,
value = bankAttribute.value,
is_active = bankAttribute.isActive
)
def createBankAttributesJson(bankAttributes: List[BankAttribute]): BankAttributesResponseJsonV400 =
BankAttributesResponseJsonV400(bankAttributes.map(createBankAttributeJson))
def createProductFeeJson(productFee: ProductFeeTrait): ProductFeeResponseJsonV400 =
ProductFeeResponseJsonV400(
bank_id = productFee.bankId.value,
product_code = productFee.productCode.value,
product_fee_id = productFee.productFeeId,
name = productFee.name,
is_active = productFee.isActive,
more_info = productFee.moreInfo,
value = ProductFeeValueJsonV400(
currency = productFee.currency,
amount = productFee.amount,
frequency= productFee.frequency,
`type`= productFee.`type`
)
)
def createProductFeesJson(productFees: List[ProductFeeTrait]): ProductFeesResponseJsonV400 =
ProductFeesResponseJsonV400(productFees.map(createProductFeeJson))
def createApiCollectionEndpointsJsonV400(apiCollectionEndpoints: List[ApiCollectionEndpointTrait]) = {
ApiCollectionEndpointsJson400(apiCollectionEndpoints.map(apiCollectionEndpoint => createApiCollectionEndpointJsonV400(apiCollectionEndpoint)))
}
@ -1459,6 +1714,79 @@ object JSONFactory400 {
balanceInquiryFee = Some(atmJsonV400.balance_inquiry_fee)
)
}
def createProductJson(product: Product) : ProductJsonV400 = {
ProductJsonV400(
bank_id = product.bankId.toString,
product_code = product.code.value,
parent_product_code = product.parentProductCode.value,
name = product.name,
more_info_url = product.moreInfoUrl,
terms_and_conditions_url = product.termsAndConditionsUrl,
description = product.description,
meta = createMetaJson(product.meta),
None,
None
)
}
def createProductsJson(productsList: List[Product]) : ProductsJsonV400 = {
ProductsJsonV400(productsList.map(createProductJson))}
def createProductJson(product: Product, productAttributes: List[ProductAttribute], productFees:List[ProductFeeTrait]) : ProductJsonV400 = {
ProductJsonV400(
bank_id = product.bankId.toString,
product_code = product.code.value,
parent_product_code = product.parentProductCode.value,
name = product.name,
more_info_url = product.moreInfoUrl,
terms_and_conditions_url = product.termsAndConditionsUrl,
description = product.description,
meta = createMetaJson(product.meta),
attributes = Some(createProductAttributesJson(productAttributes)),
fees = Some(productFees.map(productFee =>ProductFeeJsonV400(
product_fee_id= Some(productFee.productFeeId),
name = productFee.name,
is_active = productFee.isActive,
more_info = productFee.moreInfo,
value = ProductFeeValueJsonV400(
currency = productFee.currency,
amount = productFee.amount,
frequency = productFee.frequency,
`type` = productFee.`type`
))))
)
}
def createPostHistoricalTransactionResponseJson(
bankId: BankId,
transactionId: TransactionId,
fromAccountId: AccountId,
toAccountId: AccountId,
value: AmountOfMoneyJsonV121,
description: String,
posted: Date,
completed: Date,
transactionRequestType: String,
chargePolicy: String
) : PostHistoricalTransactionResponseJsonV400 = {
PostHistoricalTransactionResponseJsonV400(
transaction_id = transactionId.value,
from = HistoricalTransactionAccountJsonV400(bankId.value, fromAccountId.value),
to = HistoricalTransactionAccountJsonV400(bankId.value, toAccountId.value),
value: AmountOfMoneyJsonV121,
description: String,
posted: Date,
completed: Date,
transaction_request_type = transactionRequestType,
chargePolicy: String
)
}
}

View File

@ -294,7 +294,8 @@ object DynamicEndpointHelper extends RestHelper {
successResponseBody,
errorResponseBodies,
tags,
roles
roles,
createdByBankId= bankId
)
DynamicEndpointItem(path, successCode, doc)
}

View File

@ -1,6 +1,6 @@
package code.api.v4_0_0.dynamic
import code.api.util.APIUtil.{BooleanBody, DoubleBody, EmptyBody, LongBody, OBPEndpoint, PrimaryDataBody, ResourceDoc, StringBody}
import code.api.util.APIUtil.{BooleanBody, DoubleBody, EmptyBody, LongBody, OBPEndpoint, PrimaryDataBody, ResourceDoc, StringBody, getDisabledEndpointOperationIds}
import code.api.util.{CallContext, DynamicUtil}
import code.api.v4_0_0.dynamic.practise.{DynamicEndpointCodeGenerator, PractiseEndpointGroup}
import net.liftweb.common.{Box, Failure, Full}
@ -15,7 +15,14 @@ import scala.util.control.Breaks.{break, breakable}
object DynamicEndpoints {
//TODO, better put all other dynamic endpoints into this list. eg: dynamicEntityEndpoints, dynamicSwaggerDocsEndpoints ....
private val endpointGroups: List[EndpointGroup] = PractiseEndpointGroup :: DynamicResourceDocsEndpointGroup :: Nil
val disabledEndpointOperationIds = getDisabledEndpointOperationIds
private val endpointGroups: List[EndpointGroup] =
if(disabledEndpointOperationIds.contains("OBPv4.0.0-test-dynamic-resource-doc")) {
DynamicResourceDocsEndpointGroup :: Nil
}else{
PractiseEndpointGroup :: DynamicResourceDocsEndpointGroup :: Nil
}
/**
* this will find dynamic endpoint by request.

View File

@ -154,7 +154,8 @@ object DynamicEntityHelper {
UnknownError
),
List(apiTag, apiTagNewStyle, apiTagDynamicEntity, apiTagDynamic),
Some(List(dynamicEntityInfo.canGetRole))
Some(List(dynamicEntityInfo.canGetRole)),
createdByBankId= dynamicEntityInfo.bankId
)
resourceDocs += (DynamicEntityOperation.GET_ONE, entityName) -> ResourceDoc(
endPoint,
@ -180,7 +181,8 @@ object DynamicEntityHelper {
UnknownError
),
List(apiTag, apiTagNewStyle, apiTagDynamicEntity, apiTagDynamic),
Some(List(dynamicEntityInfo.canGetRole))
Some(List(dynamicEntityInfo.canGetRole)),
createdByBankId= dynamicEntityInfo.bankId
)
resourceDocs += (DynamicEntityOperation.CREATE, entityName) -> ResourceDoc(
@ -209,7 +211,8 @@ object DynamicEntityHelper {
UnknownError
),
List(apiTag, apiTagNewStyle, apiTagDynamicEntity, apiTagDynamic),
Some(List(dynamicEntityInfo.canCreateRole))
Some(List(dynamicEntityInfo.canCreateRole)),
createdByBankId= dynamicEntityInfo.bankId
)
resourceDocs += (DynamicEntityOperation.UPDATE, entityName) -> ResourceDoc(
@ -238,7 +241,8 @@ object DynamicEntityHelper {
UnknownError
),
List(apiTag, apiTagNewStyle, apiTagDynamicEntity, apiTagDynamic),
Some(List(dynamicEntityInfo.canUpdateRole))
Some(List(dynamicEntityInfo.canUpdateRole)),
createdByBankId= dynamicEntityInfo.bankId
)
resourceDocs += (DynamicEntityOperation.DELETE, entityName) -> ResourceDoc(
@ -264,7 +268,8 @@ object DynamicEntityHelper {
UnknownError
),
List(apiTag, apiTagNewStyle, apiTagDynamicEntity, apiTagDynamic),
Some(List(dynamicEntityInfo.canDeleteRole))
Some(List(dynamicEntityInfo.canDeleteRole)),
createdByBankId= dynamicEntityInfo.bankId
)
resourceDocs

View File

@ -10,11 +10,13 @@ class ApiCollection extends ApiCollectionTrait with LongKeyedMapper[ApiCollectio
object UserId extends MappedString(this, 100)
object ApiCollectionName extends MappedString(this, 100)
object IsSharable extends MappedBoolean(this)
object Description extends MappedString(this, 2000)
override def apiCollectionId: String = ApiCollectionId.get
override def userId: String = UserId.get
override def apiCollectionName: String = ApiCollectionName.get
override def isSharable: Boolean = IsSharable.get
override def description: String = Description.get
}
object ApiCollection extends ApiCollection with LongKeyedMetaMapper[ApiCollection] {
@ -26,4 +28,5 @@ trait ApiCollectionTrait {
def userId: String
def apiCollectionName: String
def isSharable: Boolean
def description: String
}

View File

@ -9,7 +9,8 @@ trait ApiCollectionsProvider {
def createApiCollection(
userId: String,
apiCollectionName: String,
isSharable: Boolean
isSharable: Boolean,
description: String
): Box[ApiCollectionTrait]
def getApiCollectionById(
@ -36,7 +37,8 @@ object MappedApiCollectionsProvider extends MdcLoggable with ApiCollectionsProvi
override def createApiCollection(
userId: String,
apiCollectionName: String,
isSharable: Boolean
isSharable: Boolean,
description: String
): Box[ApiCollectionTrait] =
tryo (
ApiCollection
@ -44,6 +46,7 @@ object MappedApiCollectionsProvider extends MdcLoggable with ApiCollectionsProvi
.UserId(userId)
.ApiCollectionName(apiCollectionName)
.IsSharable(isSharable)
.Description(description)
.saveMe()
)

View File

@ -92,7 +92,7 @@ trait AtmsProvider {
getAtmsFromProvider(bankId,queryParams) match {
case Some(atms) => {
val atmsWithLicense = for {
branch <- atms if branch.meta.license.name.size > 3 && branch.meta.license.name.size > 3
branch <- atms if branch.meta.license.name.size > 3
} yield branch
Option(atmsWithLicense)
}

View File

@ -0,0 +1,69 @@
package code.bankattribute
/* For ProductAttribute */
import code.api.util.APIUtil
import code.remotedata.RemotedataBankAttribute
import com.openbankproject.commons.model.BankId
import com.openbankproject.commons.model.enums.BankAttributeType
import net.liftweb.common.{Box, Logger}
import net.liftweb.util.SimpleInjector
import scala.concurrent.Future
object BankAttributeX extends SimpleInjector {
val bankAttributeProvider = new Inject(buildOne _) {}
def buildOne: BankAttributeProviderTrait =
APIUtil.getPropsAsBoolValue("use_akka", false) match {
case false => BankAttributeProvider
case true => RemotedataBankAttribute // We will use Akka as a middleware
}
// Helper to get the count out of an option
def countOfBankAttribute(listOpt: Option[List[BankAttribute]]): Int = {
val count = listOpt match {
case Some(list) => list.size
case None => 0
}
count
}
}
trait BankAttributeProviderTrait {
private val logger = Logger(classOf[BankAttributeProviderTrait])
def getBankAttributesFromProvider(bankId: BankId): Future[Box[List[BankAttribute]]]
def getBankAttributeById(bankAttributeId: String): Future[Box[BankAttribute]]
def createOrUpdateBankAttribute(bankId : BankId,
bankAttributeId: Option[String],
name: String,
attributType: BankAttributeType.Value,
value: String,
isActive: Option[Boolean]): Future[Box[BankAttribute]]
def deleteBankAttribute(bankAttributeId: String): Future[Box[Boolean]]
// End of Trait
}
class RemotedataBankAttributeCaseClasses {
case class getBankAttributesFromProvider(bank: BankId)
case class getBankAttributeById(bankAttributeId: String)
case class createOrUpdateBankAttribute(bankId : BankId,
bankAttributeId: Option[String],
name: String,
attributType: BankAttributeType.Value,
value: String,
isActive: Option[Boolean])
case class deleteBankAttribute(bankAttributeId: String)
}
object RemotedataBankAttributeCaseClasses extends RemotedataBankAttributeCaseClasses

View File

@ -0,0 +1,94 @@
package code.bankattribute
import code.util.{MappedUUID, UUIDString}
import com.openbankproject.commons.ExecutionContext.Implicits.global
import com.openbankproject.commons.model.enums.BankAttributeType
import com.openbankproject.commons.model.{BankAttributeTrait, BankId}
import net.liftweb.common.{Box, Empty, Full}
import net.liftweb.mapper.{MappedBoolean, _}
import net.liftweb.util.Helpers.tryo
import scala.concurrent.Future
object BankAttributeProvider extends BankAttributeProviderTrait {
override def getBankAttributesFromProvider(bankId: BankId): Future[Box[List[BankAttribute]]] =
Future {
Box !! BankAttribute.findAll(
By(BankAttribute.BankId_, bankId.value)
)
}
override def getBankAttributeById(bankAttributeId: String): Future[Box[BankAttribute]] = Future {
BankAttribute.find(By(BankAttribute.BankAttributeId, bankAttributeId))
}
override def createOrUpdateBankAttribute(bankId: BankId,
bankAttributeId: Option[String],
name: String,
attributType: BankAttributeType.Value,
value: String,
isActive: Option[Boolean]): Future[Box[BankAttribute]] = {
bankAttributeId match {
case Some(id) => Future {
BankAttribute.find(By(BankAttribute.BankAttributeId, id)) match {
case Full(attribute) => tryo {
attribute.BankId_(bankId.value)
.Name(name)
.Type(attributType.toString)
.Value(value)
.IsActive(isActive.getOrElse(true))
.saveMe()
}
case _ => Empty
}
}
case None => Future {
Full {
BankAttribute.create
.BankId_(bankId.value)
.Name(name)
.Type(attributType.toString())
.Value(value)
.IsActive(isActive.getOrElse(true))
.saveMe()
}
}
}
}
override def deleteBankAttribute(bankAttributeId: String): Future[Box[Boolean]] = Future {
Some(
BankAttribute.bulkDelete_!!(By(BankAttribute.BankAttributeId, bankAttributeId))
)
}
}
class BankAttribute extends BankAttributeTrait with LongKeyedMapper[BankAttribute] with IdPK {
override def getSingleton = BankAttribute
object BankId_ extends UUIDString(this) // combination of this
object BankAttributeId extends MappedUUID(this)
object Name extends MappedString(this, 50)
object Type extends MappedString(this, 50)
object Value extends MappedString(this, 255)
object IsActive extends MappedBoolean(this) {
override def defaultValue = true
}
override def bankId: BankId = BankId(BankId_.get)
override def bankAttributeId: String = BankAttributeId.get
override def name: String = Name.get
override def attributeType: BankAttributeType.Value = BankAttributeType.withName(Type.get)
override def value: String = Value.get
override def isActive: Option[Boolean] = if (IsActive.jdbcFriendly(IsActive.calcFieldName) == null) { None } else Some(IsActive.get)
}
object BankAttribute extends BankAttribute with LongKeyedMetaMapper[BankAttribute] {
override def dbIndexes = Index(BankId_) :: super.dbIndexes
}

View File

@ -2,7 +2,6 @@ package code.bankconnectors
import java.util.Date
import java.util.UUID.randomUUID
import _root_.akka.http.scaladsl.model.HttpMethod
import code.accountholders.{AccountHolders, MapperAccountHolders}
import code.api.attributedefinition.AttributeDefinition
@ -13,7 +12,10 @@ import code.api.util.ErrorMessages._
import code.api.util._
import code.api.v1_4_0.JSONFactory1_4_0.TransactionRequestAccountJsonV140
import code.api.v2_1_0._
import code.api.v4_0_0.ModeratedFirehoseAccountsJsonV400
import code.api.{APIFailure, APIFailureNewStyle}
import code.bankattribute.BankAttribute
import code.bankconnectors.LocalMappedConnector.setUnimplementedError
import code.bankconnectors.akka.AkkaConnector_vDec2018
import code.bankconnectors.rest.RestConnector_vMar2019
import code.bankconnectors.storedprocedure.StoredProcedureConnector_vDec2019
@ -21,10 +23,12 @@ import code.bankconnectors.vJune2017.KafkaMappedConnector_vJune2017
import code.bankconnectors.vMar2017.KafkaMappedConnector_vMar2017
import code.bankconnectors.vMay2019.KafkaMappedConnector_vMay2019
import code.bankconnectors.vSept2018.KafkaMappedConnector_vSept2018
import code.endpointTag.EndpointTagT
import code.fx.fx.TTL
import code.management.ImporterAPI.ImporterTransaction
import code.model.dataAccess.{BankAccountRouting, ResourceUser}
import code.model.toUserExtended
import code.productfee.ProductFeeX
import code.standingorders.StandingOrderTrait
import code.transactionrequests.TransactionRequests
import code.transactionrequests.TransactionRequests.TransactionRequestTypes._
@ -42,6 +46,7 @@ import com.openbankproject.commons.util.Functions.lazyValue
import com.openbankproject.commons.util.{JsonUtils, ReflectUtils}
import com.tesobe.CacheKeyFromArguments
import net.liftweb.common._
import net.liftweb.http.provider.HTTPParam
import net.liftweb.json
import net.liftweb.json.{Formats, JObject, JValue}
import net.liftweb.mapper.By
@ -500,6 +505,10 @@ trait Connector extends MdcLoggable {
def getCoreBankAccounts(bankIdAccountIds: List[BankIdAccountId], callContext: Option[CallContext]) : Future[Box[(List[CoreAccount], Option[CallContext])]]=
Future{Failure(setUnimplementedError)}
def getBankAccountsWithAttributes(bankId: BankId, queryParams: List[OBPQueryParam], callContext: Option[CallContext]): OBPReturnType[Box[List[FastFirehoseAccount]]] =
Future{(Failure(setUnimplementedError), callContext)}
def getBankSettlementAccounts(bankId: BankId, callContext: Option[CallContext]): OBPReturnType[Box[List[BankAccount]]] = Future{(Failure(setUnimplementedError), callContext)}
def getBankAccountsHeldLegacy(bankIdAccountIds: List[BankIdAccountId], callContext: Option[CallContext]) : Box[List[AccountHeld]]= Failure(setUnimplementedError)
@ -1558,7 +1567,27 @@ trait Connector extends MdcLoggable {
def createOrUpdateAtmLegacy(atm: AtmT): Box[AtmT] = Failure(setUnimplementedError)
def createOrUpdateAtm(atm: AtmT, callContext: Option[CallContext]): OBPReturnType[Box[AtmT]] = Future{Failure(setUnimplementedError)}
def createSystemLevelEndpointTag(operationId:String, tagName:String, callContext: Option[CallContext]): OBPReturnType[Box[EndpointTagT]] = Future{Failure(setUnimplementedError)}
def updateSystemLevelEndpointTag(endpointTagId:String, operationId:String, tagName:String, callContext: Option[CallContext]): OBPReturnType[Box[EndpointTagT]] = Future{Failure(setUnimplementedError)}
def createBankLevelEndpointTag(bankId:String, operationId:String, tagName:String, callContext: Option[CallContext]): OBPReturnType[Box[EndpointTagT]] = Future{Failure(setUnimplementedError)}
def updateBankLevelEndpointTag(bankId:String, endpointTagId:String, operationId:String, tagName:String, callContext: Option[CallContext]): OBPReturnType[Box[EndpointTagT]] = Future{Failure(setUnimplementedError)}
def getSystemLevelEndpointTag(operationId: String, tagName:String, callContext: Option[CallContext]): OBPReturnType[Box[EndpointTagT]] = Future{Failure(setUnimplementedError)}
def getBankLevelEndpointTag(bankId: String, operationId: String, tagName:String, callContext: Option[CallContext]): OBPReturnType[Box[EndpointTagT]] = Future{Failure(setUnimplementedError)}
def getEndpointTagById(endpointTagId : String, callContext: Option[CallContext]) : OBPReturnType[Box[EndpointTagT]] = Future(Failure(setUnimplementedError))
def deleteEndpointTag(endpointTagId : String, callContext: Option[CallContext]) : OBPReturnType[Box[Boolean]] = Future(Failure(setUnimplementedError))
def getSystemLevelEndpointTags(operationId : String, callContext: Option[CallContext]) : OBPReturnType[Box[List[EndpointTagT]]] = Future(Failure(setUnimplementedError))
def getBankLevelEndpointTags(bankId:String, operationId : String, callContext: Option[CallContext]) : OBPReturnType[Box[List[EndpointTagT]]] = Future(Failure(setUnimplementedError))
def createOrUpdateProduct(
bankId : String,
code : String,
@ -1568,13 +1597,44 @@ trait Connector extends MdcLoggable {
family : String,
superFamily : String,
moreInfoUrl : String,
termsAndConditionsUrl : String,
details : String,
description : String,
metaLicenceId : String,
metaLicenceName : String
): Box[Product] = Failure(setUnimplementedError)
def createOrUpdateProductFee(
bankId: BankId,
productCode: ProductCode,
productFeeId: Option[String],
name: String,
isActive: Boolean,
moreInfo: String,
currency: String,
amount: BigDecimal,
frequency: String,
`type`: String,
callContext: Option[CallContext]
): OBPReturnType[Box[ProductFeeTrait]]= Future(Failure(setUnimplementedError))
def getProductFeesFromProvider(
bankId: BankId,
productCode: ProductCode,
callContext: Option[CallContext]
): OBPReturnType[Box[List[ProductFeeTrait]]] = Future(Failure(setUnimplementedError))
def getProductFeeById(
productFeeId: String,
callContext: Option[CallContext]
): OBPReturnType[Box[ProductFeeTrait]] = Future(Failure(setUnimplementedError))
def deleteProductFee(
productFeeId: String,
callContext: Option[CallContext]
): OBPReturnType[Box[Boolean]] = Future(Failure(setUnimplementedError))
def createOrUpdateFXRate(
bankId: String,
fromCurrencyCode: String,
@ -1969,9 +2029,26 @@ trait Connector extends MdcLoggable {
name: String,
productAttributeType: ProductAttributeType.Value,
value: String,
isActive: Option[Boolean],
callContext: Option[CallContext]
): OBPReturnType[Box[ProductAttribute]] = Future{(Failure(setUnimplementedError), callContext)}
def createOrUpdateBankAttribute(bankId: BankId,
bankAttributeId: Option[String],
name: String,
bankAttributeType: BankAttributeType.Value,
value: String,
isActive: Option[Boolean],
callContext: Option[CallContext]
): OBPReturnType[Box[BankAttribute]] = Future{(Failure(setUnimplementedError), callContext)}
def getBankAttributesByBank(bank: BankId, callContext: Option[CallContext]): OBPReturnType[Box[List[BankAttribute]]] =
Future{(Failure(setUnimplementedError), callContext)}
def getBankAttributeById(bankAttributeId: String,
callContext: Option[CallContext]
): OBPReturnType[Box[BankAttribute]] = Future{(Failure(setUnimplementedError), callContext)}
def getProductAttributeById(
productAttributeId: String,
callContext: Option[CallContext]
@ -1984,6 +2061,10 @@ trait Connector extends MdcLoggable {
): OBPReturnType[Box[List[ProductAttribute]]] =
Future{(Failure(setUnimplementedError), callContext)}
def deleteBankAttribute(bankAttributeId: String,
callContext: Option[CallContext]
): OBPReturnType[Box[Boolean]] = Future{(Failure(setUnimplementedError), callContext)}
def deleteProductAttribute(
productAttributeId: String,
callContext: Option[CallContext]

View File

@ -1140,11 +1140,12 @@ object KafkaMappedConnector_JVMcompatible extends Connector with KafkaHelper wit
family : String,
superFamily : String,
moreInfoUrl : String,
termsAndConditionsUrl: String,
details : String,
description : String,
metaLicenceId : String,
metaLicenceName : String): Box[Product] = {
LocalMappedConnector.createOrUpdateProduct(bankId, code, parentProductCode, name, category, family, superFamily, moreInfoUrl, details, description, metaLicenceId, metaLicenceName)
LocalMappedConnector.createOrUpdateProduct(bankId, code, parentProductCode, name, category, family, superFamily, moreInfoUrl, termsAndConditionsUrl, details, description, metaLicenceId, metaLicenceName)
}
override def getProduct(bankId: BankId, productCode: ProductCode): Box[Product] = {

View File

@ -2,7 +2,6 @@ package code.bankconnectors
import java.util.Date
import java.util.UUID.randomUUID
import _root_.akka.http.scaladsl.model.HttpMethod
import code.DynamicData.DynamicDataProvider
import code.DynamicEndpoint.{DynamicEndpointProvider, DynamicEndpointT}
@ -22,6 +21,7 @@ import code.api.v1_4_0.JSONFactory1_4_0.TransactionRequestAccountJsonV140
import code.api.v2_1_0._
import code.atms.Atms.Atm
import code.atms.MappedAtm
import code.bankattribute.{BankAttribute, BankAttributeX}
import code.branches.Branches.Branch
import code.branches.MappedBranch
import code.cardattribute.CardAttributeX
@ -32,6 +32,7 @@ import code.customeraddress.CustomerAddressX
import code.customerattribute.CustomerAttributeX
import code.database.authorisation.Authorisations
import code.directdebit.DirectDebits
import code.endpointTag.{EndpointTag, EndpointTagT}
import code.fx.fx.TTL
import code.fx.{MappedFXRate, fx}
import code.kycchecks.KycChecks
@ -46,6 +47,7 @@ import code.metadata.narrative.Narrative
import code.metadata.tags.Tags
import code.metadata.transactionimages.TransactionImages
import code.metadata.wheretags.WhereTags
import code.metrics.MappedMetric
import code.model._
import code.model.dataAccess.AuthUser.findUserByUsernameLocally
import code.model.dataAccess._
@ -53,6 +55,7 @@ import code.productAttributeattribute.MappedProductAttribute
import code.productattribute.ProductAttributeX
import code.productcollection.ProductCollectionX
import code.productcollectionitem.ProductCollectionItems
import code.productfee.ProductFeeX
import code.products.MappedProduct
import code.standingorders.{StandingOrderTrait, StandingOrders}
import code.taxresidence.TaxResidenceX
@ -92,6 +95,9 @@ import org.iban4j.{CountryCode, IbanFormat}
import org.mindrot.jbcrypt.BCrypt
import scalacache.ScalaCache
import scalacache.guava.GuavaCache
import scalikejdbc.{ConnectionPool, ConnectionPoolSettings, MultipleConnectionPoolContext}
import scalikejdbc.DB.CPContext
import scalikejdbc.{DB => scalikeDB, _}
import scala.collection.immutable.{List, Nil}
import scala.concurrent._
@ -796,6 +802,76 @@ object LocalMappedConnector extends Connector with MdcLoggable {
}
}
private lazy val getDbConnectionParameters: (String, String, String) = {
val dbUrl = APIUtil.getPropsValue("db.url") openOr "jdbc:h2:mem:OBPTest;DB_CLOSE_DELAY=-1"
val username = dbUrl.split(";").filter(_.contains("user")).toList.headOption.map(_.split("=")(1))
val password = dbUrl.split(";").filter(_.contains("password")).toList.headOption.map(_.split("=")(1))
val dbUser = APIUtil.getPropsValue("db.user").orElse(username)
val dbPassword = APIUtil.getPropsValue("db.password").orElse(password)
(dbUrl, dbUser.getOrElse(""), dbPassword.getOrElse(""))
}
/**
* this connection pool context corresponding db.url in default.props
*/
implicit lazy val context: CPContext = {
val settings = ConnectionPoolSettings(
initialSize = 5,
maxSize = 20,
connectionTimeoutMillis = 3000L,
validationQuery = "select 1",
connectionPoolFactoryName = "commons-dbcp2"
)
val (dbUrl, user, password) = getDbConnectionParameters
val dbName = "DB_NAME" // corresponding props db.url DB
ConnectionPool.add(dbName, dbUrl, user, password, settings)
val connectionPool = ConnectionPool.get(dbName)
MultipleConnectionPoolContext(ConnectionPool.DEFAULT_NAME -> connectionPool)
}
override def getBankAccountsWithAttributes(bankId: BankId, queryParams: List[OBPQueryParam], callContext: Option[CallContext]): OBPReturnType[Box[List[FastFirehoseAccount]]] =
Future{
val limit = queryParams.collect { case OBPLimit(value) => value }.headOption.getOrElse(50)
val offset = queryParams.collect { case OBPOffset(value) => value }.headOption.getOrElse(0)
val orderBy = queryParams.collect {
case OBPOrdering(_, OBPDescending) => "DESC"
}.headOption.getOrElse("ASC")
val ordering = if (orderBy =="DESC" ) sqls"DESC" else sqls"ASC"
val firehoseAccounts = {
scalikeDB readOnly { implicit session =>
val sqlResult = sql"""
select * from mv_fast_firehose_accounts
WHERE mv_fast_firehose_accounts.bank_id = ${bankId.value}
ORDER BY mv_fast_firehose_accounts.account_id $ordering
LIMIT $limit
OFFSET $offset
""".stripMargin
.map(
rs => // Map result to case class
FastFirehoseAccount(
id = rs.stringOpt(1).map(_.toString).getOrElse(null),
bankId= rs.stringOpt(2).map(_.toString).getOrElse(null),
label= rs.stringOpt(3).map(_.toString).getOrElse(null),
number = rs.stringOpt(4).map(_.toString).getOrElse(null),
owners = rs.stringOpt(5).map(_.toString).getOrElse(null),
productCode = rs.stringOpt(6).map(_.toString).getOrElse(null),
balance = AmountOfMoney(
currency = rs.stringOpt(7).map(_.toString).getOrElse(null),
amount = rs.stringOpt(8).map(_.toString).getOrElse(null)
),
accountRoutings = rs.stringOpt(9).map(_.toString).getOrElse(null),
accountAttributes = rs.stringOpt(10).map(_.toString).getOrElse(null)
)
).list().apply()
sqlResult
}
}
(Full(firehoseAccounts), callContext)
}
override def getBankSettlementAccounts(bankId: BankId, callContext: Option[CallContext]): OBPReturnType[Box[List[BankAccount]]] = {
Future {
Full {
@ -2479,7 +2555,112 @@ object LocalMappedConnector extends Connector with MdcLoggable {
override def createOrUpdateAtm(atm: AtmT, callContext: Option[CallContext]): OBPReturnType[Box[AtmT]] = Future{
(createOrUpdateAtmLegacy(atm), callContext)
}
override def getEndpointTagById(endpointTagId : String, callContext: Option[CallContext]) : OBPReturnType[Box[EndpointTagT]] = Future(
(EndpointTag.find(By(EndpointTag.EndpointTagId, endpointTagId)), callContext)
)
override def deleteEndpointTag(endpointTagId : String, callContext: Option[CallContext]) : OBPReturnType[Box[Boolean]] = Future(
(EndpointTag.find(By(EndpointTag.EndpointTagId, endpointTagId)).map(_.delete_!), callContext)
)
override def getSystemLevelEndpointTags(operationId : String, callContext: Option[CallContext]) : OBPReturnType[Box[List[EndpointTagT]]] = Future(
(tryo{getSystemLevelEndpointTagsBox(operationId : String)}, callContext)
)
override def getBankLevelEndpointTags(bankId:String, operationId : String, callContext: Option[CallContext]) : OBPReturnType[Box[List[EndpointTagT]]] = Future(
(tryo{getBankLevelEndpointTagsBox(bankId:String, operationId : String)}, callContext)
)
def getAllEndpointTagsBox(operationId : String) : List[EndpointTagT] = EndpointTag.findAll(
By(EndpointTag.OperationId, operationId),
OrderBy(EndpointTag.TagName, Ascending)
)
def getSystemLevelEndpointTagsBox(operationId : String) : List[EndpointTagT] = EndpointTag.findAll(
By(EndpointTag.OperationId, operationId),
OrderBy(EndpointTag.TagName, Ascending)
).filter(_.bankId == None)
def getBankLevelEndpointTagsBox(bankId:String, operationId : String) : List[EndpointTagT] = EndpointTag.findAll(
By(EndpointTag.BankId, bankId),
By(EndpointTag.OperationId, operationId),
OrderBy(EndpointTag.TagName, Ascending)
)
override def createSystemLevelEndpointTag(operationId:String, tagName:String, callContext: Option[CallContext]): OBPReturnType[Box[EndpointTagT]] = Future{
(
tryo {
EndpointTag.create
.BankId(null)
.OperationId(operationId)
.TagName(tagName)
.saveMe()
} ?~! CreateEndpointTagError,
callContext
)
}
override def updateSystemLevelEndpointTag(endpointTagId:String, operationId:String, tagName:String, callContext: Option[CallContext]): OBPReturnType[Box[EndpointTagT]] = Future{
(
EndpointTag.find(
By(EndpointTag.EndpointTagId, endpointTagId)
).map(endpointTag =>
endpointTag
.BankId(null)
.OperationId(operationId)
.TagName(tagName)
.saveMe()
)
, callContext
)
}
override def createBankLevelEndpointTag(bankId:String, operationId:String, tagName:String, callContext: Option[CallContext]): OBPReturnType[Box[EndpointTagT]] = Future{
(
tryo {
EndpointTag.create
.BankId(bankId)
.OperationId(operationId)
.TagName(tagName)
.saveMe()
} ?~! CreateEndpointTagError,
callContext
)
}
override def updateBankLevelEndpointTag(bankId:String, endpointTagId:String, operationId:String, tagName:String, callContext: Option[CallContext]): OBPReturnType[Box[EndpointTagT]] = Future{
(
EndpointTag.find(
By(EndpointTag.EndpointTagId, endpointTagId)
).map(endpointTag =>
endpointTag
.BankId(bankId)
.OperationId(operationId)
.TagName(tagName)
.saveMe()
)
, callContext
)
}
override def getSystemLevelEndpointTag(operationId: String, tagName:String, callContext: Option[CallContext]): OBPReturnType[Box[EndpointTagT]] = Future{
(EndpointTag.find(
By(EndpointTag.OperationId, operationId),
By(EndpointTag.TagName, tagName),
).filter(_.bankId == None), callContext)
}
override def getBankLevelEndpointTag(bankId: String, operationId: String, tagName:String, callContext: Option[CallContext]): OBPReturnType[Box[EndpointTagT]] = Future{
(EndpointTag.find(
By(EndpointTag.OperationId, operationId),
By(EndpointTag.TagName, tagName),
By(EndpointTag.TagName, tagName),
), callContext)
}
override def createOrUpdateAtmLegacy(atm: AtmT): Box[AtmT] = {
val isAccessibleString = optionBooleanToString(atm.isAccessible)
@ -2607,6 +2788,62 @@ object LocalMappedConnector extends Connector with MdcLoggable {
}
}
override def createOrUpdateProductFee(
bankId: BankId,
productCode: ProductCode,
productFeeId: Option[String],
name: String,
isActive: Boolean,
moreInfo: String,
currency: String,
amount: BigDecimal,
frequency: String,
`type`: String,
callContext: Option[CallContext]
): OBPReturnType[Box[ProductFeeTrait]] = {
ProductFeeX.productFeeProvider.vend.createOrUpdateProductFee(
bankId: BankId,
productCode: ProductCode,
productFeeId: Option[String],
name: String,
isActive: Boolean,
moreInfo: String,
currency: String,
amount: BigDecimal,
frequency: String,
`type`: String
) map {
(_, callContext)
}
}
override def getProductFeesFromProvider(
bankId: BankId,
productCode: ProductCode,
callContext: Option[CallContext]
): OBPReturnType[Box[List[ProductFeeTrait]]] = {
ProductFeeX.productFeeProvider.vend.getProductFeesFromProvider(bankId: BankId, productCode: ProductCode) map {
(_, callContext)
}
}
override def getProductFeeById(
productFeeId: String,
callContext: Option[CallContext]
): OBPReturnType[Box[ProductFeeTrait]] = {
ProductFeeX.productFeeProvider.vend.getProductFeeById(productFeeId) map {
(_, callContext)
}
}
override def deleteProductFee(
productFeeId: String,
callContext: Option[CallContext]
): OBPReturnType[Box[Boolean]] = {
ProductFeeX.productFeeProvider.vend.deleteProductFee(productFeeId) map {
(_, callContext)
}
}
override def createOrUpdateProduct(bankId: String,
code: String,
@ -2616,6 +2853,7 @@ object LocalMappedConnector extends Connector with MdcLoggable {
family: String,
superFamily: String,
moreInfoUrl: String,
termsAndConditionsUrl: String,
details: String,
description: String,
metaLicenceId: String,
@ -2637,6 +2875,7 @@ object LocalMappedConnector extends Connector with MdcLoggable {
.mFamily(family)
.mSuperFamily(superFamily)
.mMoreInfoUrl(moreInfoUrl)
.mTermsAndConditionsUrl(termsAndConditionsUrl)
.mDetails(details)
.mDescription(description)
.mLicenseId(metaLicenceId)
@ -2654,6 +2893,7 @@ object LocalMappedConnector extends Connector with MdcLoggable {
.mFamily(family)
.mSuperFamily(superFamily)
.mMoreInfoUrl(moreInfoUrl)
.mTermsAndConditionsUrl(termsAndConditionsUrl)
.mDetails(details)
.mDescription(description)
.mLicenseId(metaLicenceId)
@ -3295,8 +3535,9 @@ object LocalMappedConnector extends Connector with MdcLoggable {
productCode: ProductCode,
productAttributeId: Option[String],
name: String,
attributType: ProductAttributeType.Value,
attributeType: ProductAttributeType.Value,
value: String,
isActive: Option[Boolean],
callContext: Option[CallContext]
): OBPReturnType[Box[ProductAttribute]] =
ProductAttributeX.productAttributeProvider.vend.createOrUpdateProductAttribute(
@ -3304,8 +3545,30 @@ object LocalMappedConnector extends Connector with MdcLoggable {
productCode: ProductCode,
productAttributeId: Option[String],
name: String,
attributType: ProductAttributeType.Value,
value: String) map {
attributeType: ProductAttributeType.Value,
value: String, isActive: Option[Boolean]) map {
(_, callContext)
}
override def createOrUpdateBankAttribute(bankId: BankId,
bankAttributeId: Option[String],
name: String,
bankAttributeType: BankAttributeType.Value,
value: String,
isActive: Option[Boolean],
callContext: Option[CallContext]
): OBPReturnType[Box[BankAttribute]] =
BankAttributeX.bankAttributeProvider.vend.createOrUpdateBankAttribute(
bankId: BankId,
bankAttributeId: Option[String],
name: String,
bankAttributeType: BankAttributeType.Value,
value: String, isActive: Option[Boolean]) map {
(_, callContext)
}
override def getBankAttributesByBank(bank: BankId, callContext: Option[CallContext]): OBPReturnType[Box[List[BankAttribute]]] =
BankAttributeX.bankAttributeProvider.vend.getBankAttributesFromProvider(bank: BankId) map {
(_, callContext)
}
@ -3318,6 +3581,11 @@ object LocalMappedConnector extends Connector with MdcLoggable {
(_, callContext)
}
override def getBankAttributeById(bankAttributeId: String, callContext: Option[CallContext]): OBPReturnType[Box[BankAttribute]] =
BankAttributeX.bankAttributeProvider.vend.getBankAttributeById(bankAttributeId: String) map {
(_, callContext)
}
override def getProductAttributeById(
productAttributeId: String,
callContext: Option[CallContext]
@ -3326,6 +3594,12 @@ object LocalMappedConnector extends Connector with MdcLoggable {
(_, callContext)
}
override def deleteBankAttribute(bankAttributeId: String,
callContext: Option[CallContext]): OBPReturnType[Box[Boolean]] =
BankAttributeX.bankAttributeProvider.vend.deleteBankAttribute(bankAttributeId: String) map {
(_, callContext)
}
override def deleteProductAttribute(
productAttributeId: String,
callContext: Option[CallContext]
@ -3351,7 +3625,7 @@ object LocalMappedConnector extends Connector with MdcLoggable {
productCode: ProductCode,
accountAttributeId: Option[String],
name: String,
attributType: AccountAttributeType.Value,
attributeType: AccountAttributeType.Value,
value: String,
callContext: Option[CallContext]
): OBPReturnType[Box[AccountAttribute]] = {
@ -3360,7 +3634,7 @@ object LocalMappedConnector extends Connector with MdcLoggable {
productCode: ProductCode,
accountAttributeId: Option[String],
name: String,
attributType: AccountAttributeType.Value,
attributeType: AccountAttributeType.Value,
value: String) map {
(_, callContext)
}

View File

@ -76,7 +76,7 @@ trait StoredProcedureConnector_vDec2019 extends Connector with MdcLoggable {
val connectorName = "stored_procedure_vDec2019"
//---------------- dynamic start -------------------please don't modify this line
// ---------- created on 2020-12-14T15:30:08Z
// ---------- created on 2021-08-24T13:22:36Z
messageDocs += getAdapterInfoDoc
def getAdapterInfoDoc = MessageDoc(
@ -692,7 +692,9 @@ trait StoredProcedureConnector_vDec2019 extends Connector with MdcLoggable {
azp=Some("string"),
email=Some(emailExample.value),
emailVerified=Some(emailVerifiedExample.value),
name=Some(userNameExample.value)))
name=Some(userNameExample.value),
userAuthContext=Some(List( BasicUserAuthContext(key=keyExample.value,
value=valueExample.value)))))
),
adapterImplementation = Some(AdapterImplementation("- Core", 1))
)
@ -726,7 +728,9 @@ trait StoredProcedureConnector_vDec2019 extends Connector with MdcLoggable {
azp=Some("string"),
email=Some(emailExample.value),
emailVerified=Some(emailVerifiedExample.value),
name=Some(userNameExample.value)))
name=Some(userNameExample.value),
userAuthContext=Some(List( BasicUserAuthContext(key=keyExample.value,
value=valueExample.value)))))
),
adapterImplementation = Some(AdapterImplementation("- Core", 1))
)
@ -1498,7 +1502,10 @@ trait StoredProcedureConnector_vDec2019 extends Connector with MdcLoggable {
idGivenByProvider="string",
provider=providerExample.value,
emailAddress=emailAddressExample.value,
name=userNameExample.value))
name=userNameExample.value,
createdByConsentId=Some("string"),
createdByUserInvitationId=Some("string"),
isDeleted=Some(true)))
),
exampleInboundMessage = (
InBoundGetPhysicalCardsForUser(status=MessageDocsSwaggerDefinitions.inboundStatus,
@ -1670,7 +1677,10 @@ trait StoredProcedureConnector_vDec2019 extends Connector with MdcLoggable {
idGivenByProvider="string",
provider=providerExample.value,
emailAddress=emailAddressExample.value,
name=userNameExample.value),
name=userNameExample.value,
createdByConsentId=Some("string"),
createdByUserInvitationId=Some("string"),
isDeleted=Some(true)),
limit=limitExample.value.toInt,
offset=offsetExample.value.toInt,
fromDate=fromDateExample.value,
@ -1986,7 +1996,10 @@ trait StoredProcedureConnector_vDec2019 extends Connector with MdcLoggable {
idGivenByProvider="string",
provider=providerExample.value,
emailAddress=emailAddressExample.value,
name=userNameExample.value),
name=userNameExample.value,
createdByConsentId=Some("string"),
createdByUserInvitationId=Some("string"),
isDeleted=Some(true)),
viewId=ViewId(viewIdExample.value),
fromAccount= BankAccountCommons(accountId=AccountId(accountIdExample.value),
accountType=accountTypeExample.value,
@ -2127,7 +2140,10 @@ trait StoredProcedureConnector_vDec2019 extends Connector with MdcLoggable {
idGivenByProvider="string",
provider=providerExample.value,
emailAddress=emailAddressExample.value,
name=userNameExample.value),
name=userNameExample.value,
createdByConsentId=Some("string"),
createdByUserInvitationId=Some("string"),
isDeleted=Some(true)),
viewId=ViewId(viewIdExample.value),
fromAccount= BankAccountCommons(accountId=AccountId(accountIdExample.value),
accountType=accountTypeExample.value,
@ -2300,7 +2316,10 @@ trait StoredProcedureConnector_vDec2019 extends Connector with MdcLoggable {
idGivenByProvider="string",
provider=providerExample.value,
emailAddress=emailAddressExample.value,
name=userNameExample.value),
name=userNameExample.value,
createdByConsentId=Some("string"),
createdByUserInvitationId=Some("string"),
isDeleted=Some(true)),
fromAccount= BankAccountCommons(accountId=AccountId(accountIdExample.value),
accountType=accountTypeExample.value,
balance=BigDecimal(balanceExample.value),
@ -2814,6 +2833,7 @@ trait StoredProcedureConnector_vDec2019 extends Connector with MdcLoggable {
family=familyExample.value,
superFamily=superFamilyExample.value,
moreInfoUrl=moreInfoUrlExample.value,
termsAndConditionsUrl=termsAndConditionsUrlExample.value,
details=detailsExample.value,
description=descriptionExample.value,
meta=Meta( License(id=idExample.value,
@ -2851,6 +2871,7 @@ trait StoredProcedureConnector_vDec2019 extends Connector with MdcLoggable {
family=familyExample.value,
superFamily=superFamilyExample.value,
moreInfoUrl=moreInfoUrlExample.value,
termsAndConditionsUrl=termsAndConditionsUrlExample.value,
details=detailsExample.value,
description=descriptionExample.value,
meta=Meta( License(id=idExample.value,
@ -3087,7 +3108,20 @@ trait StoredProcedureConnector_vDec2019 extends Connector with MdcLoggable {
isAccessible=Some(isAccessibleExample.value.toBoolean),
locatedAt=Some(locatedAtExample.value),
moreInfo=Some(moreInfoExample.value),
hasDepositCapability=Some(hasDepositCapabilityExample.value.toBoolean)))
hasDepositCapability=Some(hasDepositCapabilityExample.value.toBoolean),
supportedLanguages=Some(supportedLanguagesExample.value.split("[,;]").toList),
services=Some(listExample.value.split("[,;]").toList),
accessibilityFeatures=Some(accessibilityFeaturesExample.value.split("[,;]").toList),
supportedCurrencies=Some(supportedCurrenciesExample.value.split("[,;]").toList),
notes=Some(listExample.value.split("[,;]").toList),
locationCategories=Some(listExample.value.split("[,;]").toList),
minimumWithdrawal=Some("string"),
branchIdentification=Some("string"),
siteIdentification=Some(siteIdentification.value),
siteName=Some("string"),
cashWithdrawalNationalFee=Some(cashWithdrawalNationalFeeExample.value),
cashWithdrawalInternationalFee=Some(cashWithdrawalInternationalFeeExample.value),
balanceInquiryFee=Some(balanceInquiryFeeExample.value)))
),
adapterImplementation = Some(AdapterImplementation("- Core", 1))
)
@ -3153,7 +3187,20 @@ trait StoredProcedureConnector_vDec2019 extends Connector with MdcLoggable {
isAccessible=Some(isAccessibleExample.value.toBoolean),
locatedAt=Some(locatedAtExample.value),
moreInfo=Some(moreInfoExample.value),
hasDepositCapability=Some(hasDepositCapabilityExample.value.toBoolean))))
hasDepositCapability=Some(hasDepositCapabilityExample.value.toBoolean),
supportedLanguages=Some(supportedLanguagesExample.value.split("[,;]").toList),
services=Some(listExample.value.split("[,;]").toList),
accessibilityFeatures=Some(accessibilityFeaturesExample.value.split("[,;]").toList),
supportedCurrencies=Some(supportedCurrenciesExample.value.split("[,;]").toList),
notes=Some(listExample.value.split("[,;]").toList),
locationCategories=Some(listExample.value.split("[,;]").toList),
minimumWithdrawal=Some("string"),
branchIdentification=Some("string"),
siteIdentification=Some(siteIdentification.value),
siteName=Some("string"),
cashWithdrawalNationalFee=Some(cashWithdrawalNationalFeeExample.value),
cashWithdrawalInternationalFee=Some(cashWithdrawalInternationalFeeExample.value),
balanceInquiryFee=Some(balanceInquiryFeeExample.value))))
),
adapterImplementation = Some(AdapterImplementation("- Core", 1))
)
@ -3211,7 +3258,10 @@ trait StoredProcedureConnector_vDec2019 extends Connector with MdcLoggable {
idGivenByProvider="string",
provider=providerExample.value,
emailAddress=emailAddressExample.value,
name=userNameExample.value),
name=userNameExample.value,
createdByConsentId=Some("string"),
createdByUserInvitationId=Some("string"),
isDeleted=Some(true)),
fromAccount= BankAccountCommons(accountId=AccountId(accountIdExample.value),
accountType=accountTypeExample.value,
balance=BigDecimal(balanceExample.value),
@ -3327,7 +3377,10 @@ trait StoredProcedureConnector_vDec2019 extends Connector with MdcLoggable {
idGivenByProvider="string",
provider=providerExample.value,
emailAddress=emailAddressExample.value,
name=userNameExample.value),
name=userNameExample.value,
createdByConsentId=Some("string"),
createdByUserInvitationId=Some("string"),
isDeleted=Some(true)),
fromAccount= BankAccountCommons(accountId=AccountId(accountIdExample.value),
accountType=accountTypeExample.value,
balance=BigDecimal(balanceExample.value),
@ -3418,7 +3471,10 @@ trait StoredProcedureConnector_vDec2019 extends Connector with MdcLoggable {
idGivenByProvider="string",
provider=providerExample.value,
emailAddress=emailAddressExample.value,
name=userNameExample.value),
name=userNameExample.value,
createdByConsentId=Some("string"),
createdByUserInvitationId=Some("string"),
isDeleted=Some(true)),
viewId=ViewId(viewIdExample.value),
fromAccount= BankAccountCommons(accountId=AccountId(accountIdExample.value),
accountType=accountTypeExample.value,
@ -4723,7 +4779,8 @@ trait StoredProcedureConnector_vDec2019 extends Connector with MdcLoggable {
productAttributeId=Some(productAttributeIdExample.value),
name=nameExample.value,
productAttributeType=com.openbankproject.commons.model.enums.ProductAttributeType.example,
value=valueExample.value)
value=valueExample.value,
isActive=Some(isActiveExample.value.toBoolean))
),
exampleInboundMessage = (
InBoundCreateOrUpdateProductAttribute(inboundAdapterCallContext=MessageDocsSwaggerDefinitions.inboundAdapterCallContext,
@ -4733,14 +4790,15 @@ trait StoredProcedureConnector_vDec2019 extends Connector with MdcLoggable {
productAttributeId=productAttributeIdExample.value,
name=nameExample.value,
attributeType=com.openbankproject.commons.model.enums.ProductAttributeType.example,
value=valueExample.value))
value=valueExample.value,
isActive=Some(isActiveExample.value.toBoolean)))
),
adapterImplementation = Some(AdapterImplementation("- Core", 1))
)
override def createOrUpdateProductAttribute(bankId: BankId, productCode: ProductCode, productAttributeId: Option[String], name: String, productAttributeType: ProductAttributeType.Value, value: String, callContext: Option[CallContext]): OBPReturnType[Box[ProductAttribute]] = {
override def createOrUpdateProductAttribute(bankId: BankId, productCode: ProductCode, productAttributeId: Option[String], name: String, productAttributeType: ProductAttributeType.Value, value: String, isActive: Option[Boolean], callContext: Option[CallContext]): OBPReturnType[Box[ProductAttribute]] = {
import com.openbankproject.commons.dto.{InBoundCreateOrUpdateProductAttribute => InBound, OutBoundCreateOrUpdateProductAttribute => OutBound}
val req = OutBound(callContext.map(_.toOutboundAdapterCallContext).orNull, bankId, productCode, productAttributeId, name, productAttributeType, value)
val req = OutBound(callContext.map(_.toOutboundAdapterCallContext).orNull, bankId, productCode, productAttributeId, name, productAttributeType, value, isActive)
val response: Future[Box[InBound]] = sendRequest[InBound]("obp_create_or_update_product_attribute", req, callContext)
response.map(convertToTuple[ProductAttributeCommons](callContext))
}
@ -4764,7 +4822,8 @@ trait StoredProcedureConnector_vDec2019 extends Connector with MdcLoggable {
productAttributeId=productAttributeIdExample.value,
name=nameExample.value,
attributeType=com.openbankproject.commons.model.enums.ProductAttributeType.example,
value=valueExample.value))
value=valueExample.value,
isActive=Some(isActiveExample.value.toBoolean)))
),
adapterImplementation = Some(AdapterImplementation("- Core", 1))
)
@ -4796,7 +4855,8 @@ trait StoredProcedureConnector_vDec2019 extends Connector with MdcLoggable {
productAttributeId=productAttributeIdExample.value,
name=nameExample.value,
attributeType=com.openbankproject.commons.model.enums.ProductAttributeType.example,
value=valueExample.value)))
value=valueExample.value,
isActive=Some(isActiveExample.value.toBoolean))))
),
adapterImplementation = Some(AdapterImplementation("- Core", 1))
)
@ -5024,7 +5084,8 @@ trait StoredProcedureConnector_vDec2019 extends Connector with MdcLoggable {
productAttributeId=productAttributeIdExample.value,
name=nameExample.value,
attributeType=com.openbankproject.commons.model.enums.ProductAttributeType.example,
value=valueExample.value)))
value=valueExample.value,
isActive=Some(isActiveExample.value.toBoolean))))
),
exampleInboundMessage = (
InBoundCreateAccountAttributes(inboundAdapterCallContext=MessageDocsSwaggerDefinitions.inboundAdapterCallContext,
@ -5642,6 +5703,7 @@ trait StoredProcedureConnector_vDec2019 extends Connector with MdcLoggable {
family=familyExample.value,
superFamily=superFamilyExample.value,
moreInfoUrl=moreInfoUrlExample.value,
termsAndConditionsUrl=termsAndConditionsUrlExample.value,
details=detailsExample.value,
description=descriptionExample.value,
meta=Meta( License(id=idExample.value,
@ -5651,7 +5713,8 @@ trait StoredProcedureConnector_vDec2019 extends Connector with MdcLoggable {
productAttributeId=productAttributeIdExample.value,
name=nameExample.value,
attributeType=com.openbankproject.commons.model.enums.ProductAttributeType.example,
value=valueExample.value)))))
value=valueExample.value,
isActive=Some(isActiveExample.value.toBoolean))))))
),
adapterImplementation = Some(AdapterImplementation("- Core", 1))
)
@ -5678,13 +5741,19 @@ trait StoredProcedureConnector_vDec2019 extends Connector with MdcLoggable {
idGivenByProvider="string",
provider=providerExample.value,
emailAddress=emailAddressExample.value,
name=userNameExample.value),
name=userNameExample.value,
createdByConsentId=Some("string"),
createdByUserInvitationId=Some("string"),
isDeleted=Some(true)),
customerUser= UserCommons(userPrimaryKey=UserPrimaryKey(123),
userId=userIdExample.value,
idGivenByProvider="string",
provider=providerExample.value,
emailAddress=emailAddressExample.value,
name=userNameExample.value),
name=userNameExample.value,
createdByConsentId=Some("string"),
createdByUserInvitationId=Some("string"),
isDeleted=Some(true)),
providerId=providerIdExample.value,
purposeId=purposeIdExample.value,
when=toDate(whenExample),
@ -5745,7 +5814,10 @@ trait StoredProcedureConnector_vDec2019 extends Connector with MdcLoggable {
idGivenByProvider="string",
provider=providerExample.value,
emailAddress=emailAddressExample.value,
name=userNameExample.value))
name=userNameExample.value,
createdByConsentId=Some("string"),
createdByUserInvitationId=Some("string"),
isDeleted=Some(true)))
),
exampleInboundMessage = (
InBoundGetMeetings(inboundAdapterCallContext=MessageDocsSwaggerDefinitions.inboundAdapterCallContext,
@ -5793,7 +5865,10 @@ trait StoredProcedureConnector_vDec2019 extends Connector with MdcLoggable {
idGivenByProvider="string",
provider=providerExample.value,
emailAddress=emailAddressExample.value,
name=userNameExample.value),
name=userNameExample.value,
createdByConsentId=Some("string"),
createdByUserInvitationId=Some("string"),
isDeleted=Some(true)),
meetingId=meetingIdExample.value)
),
exampleInboundMessage = (
@ -6136,7 +6211,10 @@ trait StoredProcedureConnector_vDec2019 extends Connector with MdcLoggable {
idGivenByProvider="string",
provider=providerExample.value,
emailAddress=emailAddressExample.value,
name=userNameExample.value),
name=userNameExample.value,
createdByConsentId=Some("string"),
createdByUserInvitationId=Some("string"),
isDeleted=Some(true)),
bankId=BankId(bankIdExample.value),
message=messageExample.value,
fromDepartment=fromDepartmentExample.value,
@ -6298,8 +6376,8 @@ trait StoredProcedureConnector_vDec2019 extends Connector with MdcLoggable {
response.map(convertToTuple[Boolean](callContext))
}
// ---------- created on 2020-12-14T15:30:08Z
//---------------- dynamic end ---------------------please don't modify this line
// ---------- created on 2021-08-24T13:22:36Z
//---------------- dynamic end ---------------------please don't modify this line
private val availableOperation = DynamicEntityOperation.values.map(it => s""""$it"""").mkString("[", ", ", "]")

View File

@ -1037,8 +1037,6 @@ trait KafkaMappedConnector_vJune2017 extends Connector with KafkaHelper with Mdc
} yield {
(transaction,callContext)
}
case Full((data,status,callContext)) if (status.errorCode!="") =>
Failure("INTERNAL-"+ status.errorCode+". + CoreBank-Status:"+ status.backendMessages)
case Empty =>
Failure(ErrorMessages.InvalidConnectorResponse, Empty, Empty)
case Failure(msg, e, c) =>

View File

@ -0,0 +1,57 @@
package code.endpointTag
/* For Connector endpoint routing, star connector use this provider to find proxy connector name */
import com.openbankproject.commons.model.{Converter, JsonFieldReName}
import net.liftweb.common.Box
import net.liftweb.json.Formats
import net.liftweb.json.JsonAST.{JField, JNull, JObject, JString}
import net.liftweb.util.SimpleInjector
object EndpointTagProvider extends SimpleInjector {
val endpointTagProvider = new Inject(buildOne _) {}
def buildOne: MappedEndpointTagProvider.type = MappedEndpointTagProvider
}
trait EndpointTagT {
def endpointTagId: Option[String]
def operationId: String
def tagName: String
def bankId: Option[String]
}
case class EndpointTagCommons(
endpointTagId: Option[String],
operationId: String,
tagName: String,
bankId: Option[String],
) extends EndpointTagT with JsonFieldReName {
/**
* when serialized to json, the Option field will be not shown, this endpoint just generate a full fields json, include all None value fields
* @return JObject include all fields
*/
def toJson(implicit format: Formats) = {
JObject(List(
JField("operation_id", JString(this.operationId)),
JField("endpoint_mapping_id", this.endpointTagId.map(JString(_)).getOrElse(JNull)),
JField("tagName", JString(this.tagName)),
JField("bankId", JString(this.bankId.getOrElse("")))
))
}
}
object EndpointTagCommons extends Converter[EndpointTagT, EndpointTagCommons]
trait EndpointTagProvider {
def getById(endpointTagId: String): Box[EndpointTagT]
def getByOperationId(operationId: String): Box[EndpointTagT]
def getAllEndpointTags: List[EndpointTagT]
def createOrUpdate(endpointTag: EndpointTagT): Box[EndpointTagT]
def delete(endpointTagId: String):Box[Boolean]
}

View File

@ -0,0 +1,63 @@
package code.endpointTag
import code.api.util.CustomJsonFormats
import code.util.MappedUUID
import net.liftweb.common.{Box, Empty, EmptyBox, Full}
import net.liftweb.mapper._
import net.liftweb.util.Helpers.tryo
import org.apache.commons.lang3.StringUtils
object MappedEndpointTagProvider extends EndpointTagProvider with CustomJsonFormats{
override def getById(endpointTagId: String): Box[EndpointTagT] = {
getByEndpointTagId(endpointTagId)
}
override def getByOperationId(operationId: String): Box[EndpointTagT] = {
EndpointTag.find(By(EndpointTag.OperationId, operationId))
}
override def createOrUpdate(endpointTag: EndpointTagT): Box[EndpointTagT] = {
//to find exists endpointTag, if endpointTagId supplied, query by endpointTagId, or use endpointName and endpointTagId to do query
val existsEndpointTag: Box[EndpointTag] = endpointTag.endpointTagId match {
case Some(id) if (StringUtils.isNotBlank(id)) => getByEndpointTagId(id)
case _ => Empty
}
val entityToPersist = existsEndpointTag match {
case _: EmptyBox => EndpointTag.create
case Full(endpointTag) => endpointTag
}
tryo{
entityToPersist
.OperationId(endpointTag.operationId)
.TagName(endpointTag.tagName)
.saveMe()
}
}
override def delete(endpointTagId: String): Box[Boolean] = getByEndpointTagId(endpointTagId).map(_.delete_!)
private[this] def getByEndpointTagId(endpointTagId: String): Box[EndpointTag] = EndpointTag.find(By(EndpointTag.EndpointTagId, endpointTagId))
override def getAllEndpointTags: List[EndpointTagT] = EndpointTag.findAll()
}
class EndpointTag extends EndpointTagT with LongKeyedMapper[EndpointTag] with IdPK with CreatedUpdated with CustomJsonFormats{
override def getSingleton = EndpointTag
object EndpointTagId extends MappedUUID(this)
object OperationId extends MappedString(this, 255)
object TagName extends MappedString(this, 255)
object BankId extends MappedString(this, 255)
override def endpointTagId: Option[String] = Option(EndpointTagId.get)
override def operationId: String = OperationId.get
override def tagName: String = TagName.get
override def bankId: Option[String] = if (BankId.get == null || BankId.get.isEmpty) None else Some(BankId.get)
}
object EndpointTag extends EndpointTag with LongKeyedMetaMapper[EndpointTag] {
override def dbIndexes = UniqueIndex(EndpointTagId) ::super.dbIndexes
}

View File

@ -10,15 +10,15 @@ import net.liftweb.util.SimpleInjector
object Thing extends SimpleInjector {
val thingProvider = new Inject(buildOne _) {}
// def buildOne: ThingProvider = MappedThingProvider
def buildOne: ThingProvider = MappedThingProvider
// This determines the provider we use
def buildOne: ThingProvider =
APIUtil.getPropsValue("provider.thing").openOr("mapped") match {
case "mapped" => MappedThingProvider
case _ => MappedThingProvider
}
//If you set props `provider.thing`, you can set to different providers
// // This determines the provider we use
// def buildOne: ThingProvider =
// APIUtil.getPropsValue("provider.thing").openOr("mapped") match {
// case "mapped" => MappedThingProvider
// case _ => MappedThingProvider
// }
}

View File

@ -27,21 +27,33 @@ object fx extends MdcLoggable {
// Make this easier
//get data from : http://www.xe.com/de/currencyconverter/convert/?Amount=1&From=AUD&To=EUR
val fallbackExchangeRates = {
// Currently There are 14 currencies with 14 mappings etc.
// We don't actually need to store the Same:Same currency (1:1) but it makes editing the map less error prone in terms of data entry!
// So keep the Map balanced.
// If get compile error with type mismatch;
// found : AnyVal
// required: Double
// check the map is complete for all combinations - (We could use getOrElse (1.0) (double) as defaults in calling function but then we risk having missing values below)
// and make sure to sure explicit doubles e.g. 1.0 rather than 1 !!
// Note: If you add a non ISO standard currency below, you will also need to add it also to isValidCurrencyISOCode otherwise FX endpoints etc will fail.
val fallbackExchangeRates: Map[String, Map[String, Double]] = {
Map(
"GBP" -> Map("EUR" -> 1.16278, "USD" -> 1.24930, "JPY" -> 141.373, "AED" -> 4.58882, "INR" -> 84.0950, "KRW" -> 1433.92, "XAF" -> 762.826, "JOD" -> 0.936707, "ILS" -> 4.70020, "AUD" -> 1.63992 ,"HKD" -> 10.1468, "MXN" -> 29.2420),
"EUR" -> Map("GBP" -> 0.860011, "USD" -> 1.07428, "JPY" -> 121.567, "AED" -> 3.94594, "INR" -> 72.3136, "KRW" -> 1233.03, "XAF" -> 655.957, "JOD" -> 0.838098, "ILS" -> 4.20494, "AUD" -> 1.49707 ,"HKD" -> 8.88926, "MXN" -> 26.0359),
"USD" -> Map("GBP" -> 0.800446, "EUR" -> 0.930886, "JPY" -> 113.161, "AED" -> 3.67310, "INR" -> 67.3135, "KRW" -> 1147.78, "XAF" -> 610.601, "JOD" -> 0.708659, "ILS" -> 3.55495, "AUD" -> 1.27347 ,"HKD" -> 7.84766, "MXN" -> 21.7480),
"JPY" -> Map("GBP" -> 0.00707350, "EUR" -> 0.00822592, "USD" -> 0.00883695, "AED" -> 0.0324590, "INR" -> 0.594846, "KRW" -> 10.1428, "XAF" -> 5.39585, "JOD" -> 0.00639777, "ILS" -> 0.0320926, "AUD" -> 0.0114819 ,"HKD" -> 0.0709891, "MXN" -> 0.2053),
"AED" -> Map("GBP" -> 0.217921, "EUR" -> 0.253425, "USD" -> 0.272250, "JPY" -> 30.8081, "INR" -> 18.3255, "KRW" -> 312.482, "XAF" -> 166.236, "AED" -> 0.192964, "ILS" -> 0.968033, "AUD" -> 0.346779 ,"HKD" -> 2.13685, "MXN" -> 5.9217),
"INR" -> Map("GBP" -> 0.0118913, "EUR" -> 0.0138287, "USD" -> 0.0148559, "JPY" -> 1.68111, "AED" -> 0.0545671, "KRW" -> 17.0512, "XAF" -> 9.07101, "JOD" -> 0.0110959 , "ILS" -> 0.0556764, "AUD" -> 0.0198319 ,"HKD" -> 0.109972, "MXN" -> 0.2983),
"KRW" -> Map("GBP" -> 0.000697389, "EUR" -> 0.000811008, "USD" -> 0.000871250, "JPY" -> 0.0985917, "AED" -> 0.00320019, "INR" -> 0.0586469, "XAF" -> 0.531986, "JOD" -> 0.000630634, "ILS" -> 0.00316552,"AUD" -> 0.00111694,"HKD" -> 0.00697233, "MXN" -> 0.0183),
"XAF" -> Map("GBP" -> 0.00131092, "EUR" -> 0.00152449, "USD" -> 0.00163773, "JPY" -> 0.185328, "AED" -> 0.00601555, "INR" -> 0.110241, "KRW" -> 1.87975, "JOD" -> 0.00127784, "ILS" -> 0.00641333,"AUD" -> 0.00228226,"HKD" -> 0.0135503, "MXN" -> 0.0396 ),
"JOD" -> Map("GBP" -> 1.06757, "EUR" -> 0.237707, "USD" -> 1.41112, "JPY" -> 156.304, "AED" -> 5.18231, "INR" -> 90.1236, "KRW" -> 1585.68, "XAF" -> 782.572, "ILS" -> 5.02018, "AUD" -> 1.63992 ,"HKD" -> 11.0687, "MXN" -> 30.8336),
"ILS" -> Map("GBP" -> 0.212763, "EUR" -> 1.19318, "USD" -> 0.281298, "JPY" -> 31.1599, "AED" -> 1.03302, "INR" -> 17.9609, "KRW" -> 315.903, "XAF" -> 155.925, "JOD" -> 0.199196, "AUD" -> 0.352661 ,"HKD" -> 2.16985, "MXN" -> 6.4871),
"AUD" -> Map("GBP" -> 0.609788, "EUR" -> 0.667969, "USD" -> 0.785256, "JPY" -> 87.0936, "AED" -> 2.88368, "INR" -> 50.4238, "KRW" -> 895.304, "XAF" -> 438.162, "JOD" -> 0.556152, "ILS" -> 2.83558 ,"HKD" -> 5.61346 , "MXN" -> 16.0826),
"HKD" -> Map("GBP" -> 0.0985443, "EUR" -> 0.112495, "USD" -> 0.127427, "JPY" -> 14.0867, "AED" -> 0.467977, "INR" -> 9.09325, "KRW" -> 143.424, "XAF" -> 73.8049, "JOD" -> 0.0903452, "ILS" -> 0.460862 ,"AUD" -> 0.178137, "MXN" -> 2.8067),
"MXN" -> Map("GBP" -> 0.0341, "EUR" -> 0.0384, "USD" -> 0.0459, "JPY" -> 4.8687, "AED" -> 0.1688, "INR" -> 3.3513, "KRW" -> 54.4512, "XAF" -> 25.1890, "JOD" -> 0.0324, "ILS" -> 0.1541 , "AUD" -> 0.0621, "HKD" -> 0.3562 )
"GBP" -> Map("GBP" -> 1.0, "EUR" -> 1.16278, "USD" -> 1.24930, "JPY" -> 141.373, "AED" -> 4.58882, "INR" -> 84.0950, "KRW" -> 1433.92, "XAF" -> 762.826, "JOD" -> 0.936707, "ILS" -> 4.70020, "AUD" -> 1.63992, "HKD" -> 10.1468, "MXN" -> 29.2420, "XBT" -> 0.000022756409956),
"EUR" -> Map("GBP" -> 0.860011, "EUR" -> 1.0, "USD" -> 1.07428, "JPY" -> 121.567, "AED" -> 3.94594, "INR" -> 72.3136, "KRW" -> 1233.03, "XAF" -> 655.957, "JOD" -> 0.838098, "ILS" -> 4.20494, "AUD" -> 1.49707, "HKD" -> 8.88926, "MXN" -> 26.0359, "XBT" -> 0.000019087905636),
"USD" -> Map("GBP" -> 0.800446, "EUR" -> 0.930886, "USD" -> 1.0, "JPY" -> 113.161, "AED" -> 3.67310, "INR" -> 67.3135, "KRW" -> 1147.78, "XAF" -> 610.601, "JOD" -> 0.708659, "ILS" -> 3.55495, "AUD" -> 1.27347, "HKD" -> 7.84766, "MXN" -> 21.7480, "XBT" -> 0.0000169154),
"JPY" -> Map("GBP" -> 0.00707350, "EUR" -> 0.00822592, "USD" -> 0.00883695, "JPY" -> 1.0, "AED" -> 0.0324590, "INR" -> 0.594846, "KRW" -> 10.1428, "XAF" -> 5.39585, "JOD" -> 0.00639777, "ILS" -> 0.0320926, "AUD" -> 0.0114819, "HKD" -> 0.0709891, "MXN" -> 0.2053, "XBT" -> 0.000000147171931),
"AED" -> Map("GBP" -> 0.217921, "EUR" -> 0.253425, "USD" -> 0.272250, "JPY" -> 30.8081, "AED" -> 1.0, "INR" -> 18.3255, "KRW" -> 312.482, "XAF" -> 166.236, "JOD" -> 0.1930565, "ILS" -> 0.968033, "AUD" -> 0.346779, "HKD" -> 2.13685, "MXN" -> 5.9217, "XBT" -> 0.000004603349217),
"INR" -> Map("GBP" -> 0.0118913, "EUR" -> 0.0138287, "USD" -> 0.0148559, "JPY" -> 1.68111, "AED" -> 0.0545671, "INR" -> 1.0, "KRW" -> 17.0512, "XAF" -> 9.07101, "JOD" -> 0.0110959, "ILS" -> 0.0556764, "AUD" -> 0.0198319, "HKD" -> 0.109972, "MXN" -> 0.2983, "XBT" -> 0.00000022689396),
"KRW" -> Map("GBP" -> 0.000697389, "EUR" -> 0.000811008, "USD" -> 0.000871250, "JPY" -> 0.0985917, "AED" -> 0.00320019, "INR" -> 0.0586469, "KRW" -> 1.0, "XAF" -> 0.531986, "JOD" -> 0.000630634, "ILS" -> 0.00316552, "AUD" -> 0.00111694,"HKD" -> 0.00697233,"MXN" -> 0.0183, "XBT" -> 0.000000014234725),
"XAF" -> Map("GBP" -> 0.00131092, "EUR" -> 0.00152449, "USD" -> 0.00163773, "JPY" -> 0.185328, "AED" -> 0.00601555, "INR" -> 0.110241, "KRW" -> 1.87975, "XAF" -> 1.0, "JOD" -> 0.00127784, "ILS" -> 0.00641333, "AUD" -> 0.00228226,"HKD" -> 0.0135503, "MXN" -> 0.0396, "XBT" -> 0.000000029074795),
"JOD" -> Map("GBP" -> 1.06757, "EUR" -> 0.237707, "USD" -> 1.41112, "JPY" -> 156.304, "AED" -> 5.18231, "INR" -> 90.1236, "KRW" -> 1585.68, "XAF" -> 782.572, "JOD" -> 1.0, "ILS" -> 5.02018, "AUD" -> 1.63992, "HKD" -> 11.0687, "MXN" -> 30.8336, "XBT" -> 0.000023803244006),
"ILS" -> Map("GBP" -> 0.212763, "EUR" -> 1.19318, "USD" -> 0.281298, "JPY" -> 31.1599, "AED" -> 1.03302, "INR" -> 17.9609, "KRW" -> 315.903, "XAF" -> 155.925, "JOD" -> 0.199196, "ILS" -> 1.0, "AUD" -> 0.352661, "HKD" -> 2.16985, "MXN" -> 6.4871, "XBT" -> 0.000005452272147),
"AUD" -> Map("GBP" -> 0.609788, "EUR" -> 0.667969, "USD" -> 0.785256, "JPY" -> 87.0936, "AED" -> 2.88368, "INR" -> 50.4238, "KRW" -> 895.304, "XAF" -> 438.162, "JOD" -> 0.556152, "ILS" -> 2.83558, "AUD" -> 1.0, "HKD" -> 5.61346, "MXN" -> 16.0826, "XBT" -> 0.000012284055924),
"HKD" -> Map("GBP" -> 0.0985443, "EUR" -> 0.112495, "USD" -> 0.127427, "JPY" -> 14.0867, "AED" -> 0.467977, "INR" -> 9.09325, "KRW" -> 143.424, "XAF" -> 73.8049, "JOD" -> 0.0903452, "ILS" -> 0.460862, "AUD" -> 0.178137, "HKD" -> 1.0, "MXN" -> 2.8067, "XBT" -> 0.000002164242461),
"MXN" -> Map("GBP" -> 0.0341, "EUR" -> 0.0384, "USD" -> 0.0459, "JPY" -> 4.8687, "AED" -> 0.1688, "INR" -> 3.3513, "KRW" -> 54.4512, "XAF" -> 25.1890, "JOD" -> 0.0324, "ILS" -> 0.1541, "AUD" -> 0.0621, "HKD" -> 0.3562, "MXN" -> 1.0, "XBT" -> 0.00000081112586),
"XBT" -> Map("GBP" -> 44188.118, "EUR" -> 52436.431, "USD" -> 59245.918, "JPY" -> 6805170.8, "AED" -> 217414.47, "INR" -> 4407607.74,"KRW" -> 70131575, "XAF" -> 34353824, "JOD" -> 41960.111, "ILS" -> 182981.21, "AUD" -> 81168.603, "HKD" -> 460448.90, "MXN" -> 1230503.30,"XBT" -> 1.0)
)
}
@ -104,7 +116,6 @@ object fx extends MdcLoggable {
} else {
//logger.debug(s"fromAmount is $fromAmount, toCurrency is ${toCurrency}")
val rate: Option[Double] = try {
// Get the translated name out of the map
Some(fallbackExchangeRates.get(fromCurrency).get(toCurrency))
}
catch {

View File

@ -199,6 +199,7 @@ case class BankAccountExtended(val bankAccount: BankAccount) extends MdcLoggable
val createdByConsentId = None
val createdByUserInvitationId = None
val isDeleted = None
val lastMarketingAgreementSignedDate = None
})
} else {
accountHolders

View File

@ -131,7 +131,7 @@ object UserX {
}
def createResourceUser(provider: String, providerId: Option[String], createdByConsentId: Option[String], name: Option[String], email: Option[String], userId: Option[String], company: Option[String]) = {
Users.users.vend.createResourceUser(provider, providerId, createdByConsentId, name, email, userId, None, company)
Users.users.vend.createResourceUser(provider, providerId, createdByConsentId, name, email, userId, None, company, None)
}
def createUnsavedResourceUser(provider: String, providerId: Option[String], name: Option[String], email: Option[String], userId: Option[String]) = {
@ -141,6 +141,22 @@ object UserX {
def saveResourceUser(ru: ResourceUser) = {
Users.users.vend.saveResourceUser(ru)
}
def getOrCreateDauthResourceUser(username: String, provider: String) = {
findByUserName(username).or( //first try to find the user by userId
Users.users.vend.createResourceUser( // Otherwise create a new user
provider = provider,
providerId = Some(username),
None,
name = Some(username),
email = None,
userId = None,
createdByUserInvitationId = None,
company = None,
lastMarketingAgreementSignedDate = None
)
)
}
//def bulkDeleteAllResourceUsers(): Box[Boolean] = {
// Users.users.vend.bulkDeleteAllResourceUsers()

View File

@ -28,7 +28,7 @@ package code.model.dataAccess
import code.UserRefreshes.UserRefreshes
import code.accountholders.AccountHolders
import code.api.util.APIUtil.{hasAnOAuthHeader, isValidStrongPassword, logger, _}
import code.api.util.APIUtil.{hasAnOAuthHeader, logger, validatePasswordOnCreation, _}
import code.api.util.ErrorMessages._
import code.api.util._
import code.api.v4_0_0.dynamic.DynamicEndpointHelper
@ -37,6 +37,7 @@ import code.bankconnectors.Connector
import code.context.UserAuthContextProvider
import code.entitlement.Entitlement
import code.loginattempts.LoginAttempt
import code.token.TokensOpenIDConnect
import code.users.Users
import code.util.Helper
import code.util.Helper.MdcLoggable
@ -89,7 +90,7 @@ class AuthUser extends MegaProtoUser[AuthUser] with MdcLoggable {
override lazy val firstName = new MyFirstName
protected class MyFirstName extends MappedString(this, 32) {
protected class MyFirstName extends MappedString(this, 100) {
def isEmpty(msg: => String)(value: String): List[FieldError] =
value match {
case null => List(FieldError(this, Text(msg))) // issue 179
@ -114,7 +115,7 @@ class AuthUser extends MegaProtoUser[AuthUser] with MdcLoggable {
override lazy val lastName = new MyLastName
protected class MyLastName extends MappedString(this, 32) {
protected class MyLastName extends MappedString(this, 100) {
def isEmpty(msg: => String)(value: String): List[FieldError] =
value match {
case null => List(FieldError(this, Text(msg))) // issue 179
@ -139,6 +140,7 @@ class AuthUser extends MegaProtoUser[AuthUser] with MdcLoggable {
}
/**
* Username is a valid email address or the regex below:
* Regex to validate a username
*
* ^(?=.{8,100}$)(?![_.])(?!.*[_.]{2})[a-zA-Z0-9._]+(?<![_.])$
@ -160,7 +162,7 @@ class AuthUser extends MegaProtoUser[AuthUser] with MdcLoggable {
* The username field for the User.
*/
lazy val username: userName = new userName()
class userName extends MappedString(this, 64) {
class userName extends MappedString(this, 100) {
def isEmpty(msg: => String)(value: String): List[FieldError] =
value match {
case null => List(FieldError(this, Text(msg))) // issue 179
@ -170,6 +172,7 @@ class AuthUser extends MegaProtoUser[AuthUser] with MdcLoggable {
def usernameIsValid(msg: => String)(e: String) = e match {
case null => List(FieldError(this, Text(msg)))
case e if e.trim.isEmpty => List(FieldError(this, Text(msg)))
case e if emailRegex.findFirstMatchIn(e).isDefined => Nil // Email is valid username
case e if usernameRegex.findFirstMatchIn(e).isDefined => Nil
case _ => List(FieldError(this, Text(msg)))
}
@ -268,7 +271,7 @@ class AuthUser extends MegaProtoUser[AuthUser] with MdcLoggable {
invalidMsg = Helper.i18n("please.enter.your.password")
S.error("authuser_password_repeat", Text(Helper.i18n("please.re-enter.your.password")))
case false =>
if (isValidStrongPassword(passwordValue))
if (validatePasswordOnCreation(passwordValue))
invalidPw = false
else {
invalidPw = true
@ -309,7 +312,7 @@ class AuthUser extends MegaProtoUser[AuthUser] with MdcLoggable {
* The provider field for the User.
*/
lazy val provider: userProvider = new userProvider()
class userProvider extends MappedString(this, 64) {
class userProvider extends MappedString(this, 100) {
override def displayName = S.?("provider")
override val fieldId = Some(Text("txtProvider"))
}
@ -484,13 +487,26 @@ import net.liftweb.util.Helpers._
*/
def getCurrentUserUsername: String = {
getCurrentUser match {
case Full(user) if user.provider.contains("google") => user.emailAddress
case Full(user) if user.provider.contains("yahoo") => user.emailAddress
case Full(user) if user.provider.contains("google") && !user.emailAddress.isEmpty => user.emailAddress
case Full(user) if user.provider.contains("yahoo") && !user.emailAddress.isEmpty => user.emailAddress
case Full(user) if user.provider.contains("microsoft") && !user.emailAddress.isEmpty => user.emailAddress
case Full(user) => user.name
case _ => "" //TODO need more error handling for different user cases
}
}
def getIDTokenOfCurrentUser(): String = {
if(APIUtil.getPropsAsBoolValue("openid_connect.show_tokens", false)) {
AuthUser.currentUser match {
case Full(authUser) =>
TokensOpenIDConnect.tokens.vend.getOpenIDConnectTokenByAuthUser(authUser.id.get).map(_.idToken).getOrElse("")
case _ => ""
}
} else {
"This information is not allowed at this instance."
}
}
/**
* get current user.userId
* Note: 1.resourceuser has two ids: id(Long) and userid_(String),
@ -1138,43 +1154,45 @@ def restoreSomeSessions(): Unit = {
}
def grantEntitlementsToUseDynamicEndpointsInSpaces(user: AuthUser) = {
val createdByProcess = "grantEntitlementsToUseDynamicEndpointsInSpaces"
val userId = user.user.obj.map(_.userId).getOrElse("")
if(emailDomainToSpaceMappings.nonEmpty) {
val createdByProcess = "grantEntitlementsToUseDynamicEndpointsInSpaces"
val userId = user.user.obj.map(_.userId).getOrElse("")
// user's already auto granted entitlements.
val entitlementsGrantedByThisProcess = Entitlement.entitlement.vend.getEntitlementsByUserId(userId)
.map(_.filter(role => role.createdByProcess == createdByProcess))
.getOrElse(Nil)
// user's already auto granted entitlements.
val entitlementsGrantedByThisProcess = Entitlement.entitlement.vend.getEntitlementsByUserId(userId)
.map(_.filter(role => role.createdByProcess == createdByProcess))
.getOrElse(Nil)
def alreadyHasEntitlement(role:ApiRole, bankId: String): Boolean =
entitlementsGrantedByThisProcess.exists(entitlement => entitlement.roleName == role.toString() && entitlement.bankId == bankId)
def alreadyHasEntitlement(role:ApiRole, bankId: String): Boolean =
entitlementsGrantedByThisProcess.exists(entitlement => entitlement.roleName == role.toString() && entitlement.bankId == bankId)
//call mySpaces --> get BankIds --> listOfRolesToUseAllDynamicEndpointsAOneBank (at each bank)--> Grant roles (for each role)
val allCurrentDynamicRoleToBankIdPairs: List[(ApiRole, String)] = for {
BankId(bankId) <- mySpaces(user: AuthUser)
role <- DynamicEndpointHelper.listOfRolesToUseAllDynamicEndpointsAOneBank(Some(bankId))
} yield {
if (!alreadyHasEntitlement(role, bankId)) {
Entitlement.entitlement.vend.addEntitlement(bankId, userId, role.toString, createdByProcess)
//call mySpaces --> get BankIds --> listOfRolesToUseAllDynamicEndpointsAOneBank (at each bank)--> Grant roles (for each role)
val allCurrentDynamicRoleToBankIdPairs: List[(ApiRole, String)] = for {
BankId(bankId) <- mySpaces(user: AuthUser)
role <- DynamicEndpointHelper.listOfRolesToUseAllDynamicEndpointsAOneBank(Some(bankId))
} yield {
if (!alreadyHasEntitlement(role, bankId)) {
Entitlement.entitlement.vend.addEntitlement(bankId, userId, role.toString, createdByProcess)
}
role -> bankId
}
role -> bankId
}
// if user's auto granted entitlement invalid, delete it.
// invalid happens when some dynamic endpoints are removed, so the entitlements linked to the deleted dynamic endpoints are invalid.
for {
grantedEntitlement <- entitlementsGrantedByThisProcess
grantedEntitlementRoleName = grantedEntitlement.roleName
grantedEntitlementBankId = grantedEntitlement.bankId
} {
val isInValidEntitlement = !allCurrentDynamicRoleToBankIdPairs.exists { roleToBankIdPair =>
val(role, roleBankId) = roleToBankIdPair
role.toString() == grantedEntitlementRoleName && roleBankId == grantedEntitlementBankId
}
// if user's auto granted entitlement invalid, delete it.
// invalid happens when some dynamic endpoints are removed, so the entitlements linked to the deleted dynamic endpoints are invalid.
for {
grantedEntitlement <- entitlementsGrantedByThisProcess
grantedEntitlementRoleName = grantedEntitlement.roleName
grantedEntitlementBankId = grantedEntitlement.bankId
} {
val isInValidEntitlement = !allCurrentDynamicRoleToBankIdPairs.exists { roleToBankIdPair =>
val(role, roleBankId) = roleToBankIdPair
role.toString() == grantedEntitlementRoleName && roleBankId == grantedEntitlementBankId
}
if(isInValidEntitlement) {
Entitlement.entitlement.vend.deleteEntitlement(Full(grantedEntitlement))
if(isInValidEntitlement) {
Entitlement.entitlement.vend.deleteEntitlement(Full(grantedEntitlement))
}
}
}
}
@ -1372,8 +1390,11 @@ def restoreSomeSessions(): Unit = {
val bind = "type=submit" #> signupSubmitButton(signupSubmitButtonValue, testSignup _)
bind(signupXhtml(theUser))
}
innerSignup
if(APIUtil.getPropsAsBoolValue("user_invitation.mandatory", false))
S.redirectTo("/user-invitation-info")
else
innerSignup
}
def scrambleAuthUser(userPrimaryKey: UserPrimaryKey): Box[Boolean] = tryo {

View File

@ -26,6 +26,8 @@ TESOBE (http://www.tesobe.com/)
*/
package code.model.dataAccess
import java.util.Date
import code.api.util.APIUtil
import code.util.MappedUUID
import com.openbankproject.commons.model.{User, UserPrimaryKey}
@ -59,7 +61,7 @@ class ResourceUser extends LongKeyedMapper[ResourceUser] with User with ManyToMa
object id extends MappedLongIndex(this)
object userId_ extends MappedUUID(this)
object email extends MappedEmail(this, 48){
object email extends MappedEmail(this, 100){
override def required_? = false
}
object name_ extends MappedString(this, 100){
@ -81,6 +83,7 @@ class ResourceUser extends LongKeyedMapper[ResourceUser] with User with ManyToMa
object IsDeleted extends MappedBoolean(this) {
override def defaultValue = false
}
object LastMarketingAgreementSignedDate extends MappedDate(this)
def emailAddress = {
val e = email.get
@ -108,7 +111,8 @@ class ResourceUser extends LongKeyedMapper[ResourceUser] with User with ManyToMa
override def createdByConsentId = if(CreatedByConsentId.get == null) None else if (CreatedByConsentId.get.isEmpty) None else Some(CreatedByConsentId.get) //null --> None
override def createdByUserInvitationId = if(CreatedByUserInvitationId.get == null) None else if (CreatedByUserInvitationId.get.isEmpty) None else Some(CreatedByUserInvitationId.get) //null --> None
override def isDeleted: Option[Boolean] = if(IsDeleted.get == null) None else Some(IsDeleted.get) // null --> false
override def isDeleted: Option[Boolean] = if(IsDeleted.jdbcFriendly(IsDeleted.calcFieldName) == null) None else Some(IsDeleted.get) // null --> None
override def lastMarketingAgreementSignedDate: Option[Date] = if(IsDeleted.jdbcFriendly(LastMarketingAgreementSignedDate.calcFieldName) == null) None else Some(LastMarketingAgreementSignedDate.get) // null --> None
}
object ResourceUser extends ResourceUser with LongKeyedMetaMapper[ResourceUser]{

View File

@ -76,7 +76,7 @@ class HelloWorldServer(executionContext: ExecutionContext) { self =>
val (bankList, _) = it
val json40: BanksJson400 = JSONFactory400.createBanksJson(bankList)
val grpcBanks: List[BankJson400Grpc] = json40.banks.map(bank => {
val BankJson400(id, short_name, full_name, logo, website, bank_routings) = bank
val BankJson400(id, short_name, full_name, logo, website, bank_routings, None) = bank
val bankRoutingGrpcs = bank_routings.map(routings => BankRoutingJsonV121Grpc(routings.scheme, routings.address))
BankJson400Grpc(id, short_name, full_name, logo, website, bankRoutingGrpcs)
})

View File

@ -2,12 +2,12 @@ package code.productAttributeattribute
import code.productattribute.ProductAttributeProvider
import code.util.{AttributeQueryTrait, MappedUUID, UUIDString}
import com.openbankproject.commons.ExecutionContext.Implicits.global
import com.openbankproject.commons.model.enums.ProductAttributeType
import com.openbankproject.commons.model.{BankId, ProductAttribute, ProductCode}
import net.liftweb.common.{Box, Empty, Full}
import net.liftweb.mapper.{BaseMappedField, _}
import net.liftweb.mapper.{BaseMappedField, MappedBoolean, _}
import net.liftweb.util.Helpers.tryo
import com.openbankproject.commons.ExecutionContext.Implicits.global
import scala.concurrent.Future
@ -30,8 +30,9 @@ object MappedProductAttributeProvider extends ProductAttributeProvider {
productCode: ProductCode,
productAttributeId: Option[String],
name: String,
attributType: ProductAttributeType.Value,
value: String): Future[Box[ProductAttribute]] = {
attributeType: ProductAttributeType.Value,
value: String,
isActive: Option[Boolean]): Future[Box[ProductAttribute]] = {
productAttributeId match {
case Some(id) => Future {
MappedProductAttribute.find(By(MappedProductAttribute.mProductAttributeId, id)) match {
@ -39,8 +40,9 @@ object MappedProductAttributeProvider extends ProductAttributeProvider {
attribute.mBankId(bankId.value)
.mCode(productCode.value)
.mName(name)
.mType(attributType.toString)
.mType(attributeType.toString)
.mValue(value)
.IsActive(isActive.getOrElse(true))
.saveMe()
}
case _ => Empty
@ -52,8 +54,9 @@ object MappedProductAttributeProvider extends ProductAttributeProvider {
.mBankId(bankId.value)
.mCode(productCode.value)
.mName(name)
.mType(attributType.toString())
.mType(attributeType.toString())
.mValue(value)
.IsActive(isActive.getOrElse(true))
.saveMe()
}
}
@ -82,6 +85,10 @@ class MappedProductAttribute extends ProductAttribute with LongKeyedMapper[Mappe
object mValue extends MappedString(this, 255)
object IsActive extends MappedBoolean(this) {
override def defaultValue = true
}
override def bankId: BankId = BankId(mBankId.get)
@ -94,8 +101,9 @@ class MappedProductAttribute extends ProductAttribute with LongKeyedMapper[Mappe
override def attributeType: ProductAttributeType.Value = ProductAttributeType.withName(mType.get)
override def value: String = mValue.get
override def isActive: Option[Boolean] = if (IsActive.jdbcFriendly(IsActive.calcFieldName) == null) { None } else Some(IsActive.get)
}
//

View File

@ -46,8 +46,9 @@ trait ProductAttributeProvider {
productCode: ProductCode,
productAttributeId: Option[String],
name: String,
attributType: ProductAttributeType.Value,
value: String): Future[Box[ProductAttribute]]
attributeType: ProductAttributeType.Value,
value: String,
isActive: Option[Boolean]): Future[Box[ProductAttribute]]
def deleteProductAttribute(productAttributeId: String): Future[Box[Boolean]]
// End of Trait
}
@ -61,8 +62,9 @@ class RemotedataProductAttributeCaseClasses {
productCode: ProductCode,
productAttributeId: Option[String],
name: String,
attributType: ProductAttributeType.Value,
value: String)
attributeType: ProductAttributeType.Value,
value: String,
isActive: Option[Boolean])
case class deleteProductAttribute(productAttributeId: String)
}

View File

@ -0,0 +1,141 @@
package code.productfee
import code.api.util.APIUtil
import code.api.util.ErrorMessages.{CreateProductFeeError, UpdateProductFeeError}
import code.util.UUIDString
import com.openbankproject.commons.model.{BankId, ProductCode, ProductFeeTrait}
import net.liftweb.common.{Box, Empty, Full}
import net.liftweb.mapper.{MappedBoolean, _}
import net.liftweb.util.Helpers.tryo
import java.math.MathContext
import scala.math.BigDecimal
import com.openbankproject.commons.ExecutionContext.Implicits.global
import scala.concurrent.Future
object MappedProductFeeProvider extends ProductFeeProvider {
override def getProductFeesFromProvider(bankId: BankId, productCode: ProductCode): Future[Box[List[ProductFeeTrait]]] =
Future {
Box !! ProductFee.findAll(
By(ProductFee.BankId, bankId.value),
By(ProductFee.ProductCode, productCode.value)
)
}
override def getProductFeeById(productFeeId: String): Future[Box[ProductFeeTrait]] = Future {
ProductFee.find(By(ProductFee.ProductFeeId, productFeeId))
}
override def createOrUpdateProductFee(
bankId: BankId,
productCode: ProductCode,
productFeeId: Option[String],
name: String,
isActive: Boolean,
moreInfo: String,
currency: String,
amount: BigDecimal,
frequency: String,
`type`: String
): Future[Box[ProductFeeTrait]] = {
productFeeId match {
case Some(id) => Future {
ProductFee.find(By(ProductFee.ProductFeeId, id)) match {
case Full(productFee) => tryo {
productFee
.BankId(bankId.value)
.ProductCode(productCode.value)
.Name(name)
.IsActive(isActive)
.MoreInfo(moreInfo)
.Currency(currency)
.Amount(amount)
.Frequency(frequency)
.Type(`type`)
.saveMe()
} ?~! s"$UpdateProductFeeError"
case _ => Empty
}
}
case None => Future {
tryo {
ProductFee
.create
.ProductFeeId(APIUtil.generateUUID)
.BankId(bankId.value)
.ProductCode(productCode.value)
.Name(name)
.IsActive(isActive)
.MoreInfo(moreInfo)
.Currency(currency)
.Amount(amount)
.Frequency(frequency)
.Type(`type`)
.saveMe()
} ?~! s"$CreateProductFeeError"
}
}
}
override def deleteProductFee(productFeeId: String): Future[Box[Boolean]] = Future {
tryo(
ProductFee.bulkDelete_!!(By(ProductFee.ProductFeeId, productFeeId))
)
}
}
class ProductFee extends ProductFeeTrait with LongKeyedMapper[ProductFee] with IdPK {
override def getSingleton = ProductFee
object BankId extends UUIDString(this)
object ProductCode extends MappedString(this, 50)
object ProductFeeId extends UUIDString(this)
object Name extends MappedString(this, 100)
object IsActive extends MappedBoolean(this) {
override def defaultValue = true
}
object MoreInfo extends MappedString(this, 255)
object Currency extends MappedString(this, 50)
object Amount extends MappedDecimal(this, MathContext.DECIMAL128, 2)
object Frequency extends MappedString(this, 255)
object Type extends MappedString(this, 255)
override def bankId: BankId = com.openbankproject.commons.model.BankId(BankId.get)
override def productCode: ProductCode = com.openbankproject.commons.model.ProductCode(ProductCode.get)
override def productFeeId: String = ProductFeeId.get
override def name: String = Name.get
override def isActive: Boolean = IsActive.get
override def moreInfo: String = MoreInfo.get
override def currency: String = Currency.get
override def amount: BigDecimal = Amount.get
override def frequency: String = Frequency.get
override def `type`: String = Type.get
}
object ProductFee extends ProductFee with LongKeyedMetaMapper[ProductFee] {
override def dbIndexes = Index(BankId) :: Index(ProductFeeId) :: super.dbIndexes
}

View File

@ -0,0 +1,76 @@
package code.productfee
/* For ProductFee */
import code.api.util.APIUtil
import com.openbankproject.commons.model.{BankId, ProductCode, ProductFeeTrait}
import net.liftweb.common.{Box, Logger}
import net.liftweb.util.SimpleInjector
import scala.concurrent.Future
import scala.math.BigDecimal
object ProductFeeX extends SimpleInjector {
val productFeeProvider = new Inject(buildOne _) {}
def buildOne: ProductFeeProvider = MappedProductFeeProvider
// Helper to get the count out of an option
def countOfProductFee(listOpt: Option[List[ProductFeeTrait]]): Int = {
val count = listOpt match {
case Some(list) => list.size
case None => 0
}
count
}
}
trait ProductFeeProvider {
private val logger = Logger(classOf[ProductFeeProvider])
def getProductFeesFromProvider(bankId: BankId, productCode: ProductCode): Future[Box[List[ProductFeeTrait]]]
def getProductFeeById(productFeeId: String): Future[Box[ProductFeeTrait]]
def createOrUpdateProductFee(
bankId: BankId,
productCode: ProductCode,
productFeeId: Option[String],
name: String,
isActive: Boolean,
moreInfo: String,
currency: String,
amount: BigDecimal,
frequency: String,
`type`: String
): Future[Box[ProductFeeTrait]]
def deleteProductFee(productFeeId: String): Future[Box[Boolean]]
}
class RemotedataProductFeeCaseClasses {
case class getProductFeesFromProvider(bankId: BankId, productCode: ProductCode)
case class getProductFeeById(productFeeId: String)
case class createOrUpdateProductFee(
bankId: BankId,
productCode: ProductCode,
productFeeId: Option[String],
name: String,
isActive: Boolean,
moreInfo: String,
currency: String,
amount: BigDecimal,
frequency: String,
`type`: String
)
case class deleteProductFee(productFeeId: String)
}
object RemotedataProductFeeCaseClasses extends RemotedataProductFeeCaseClasses

View File

@ -39,6 +39,7 @@ class MappedProduct extends Product with LongKeyedMapper[MappedProduct] with IdP
object mFamily extends MappedString(this, 50)
object mSuperFamily extends MappedString(this, 50)
object mMoreInfoUrl extends MappedString(this, 2000) // use URL field?
object mTermsAndConditionsUrl extends MappedString(this, 2000) // use URL field?
object mDetails extends MappedString(this, 2000)
object mDescription extends MappedString(this, 2000)
@ -57,6 +58,7 @@ class MappedProduct extends Product with LongKeyedMapper[MappedProduct] with IdP
override def family : String = mFamily.get
override def superFamily : String = mSuperFamily.get
override def moreInfoUrl: String = mMoreInfoUrl.get
override def termsAndConditionsUrl: String = mTermsAndConditionsUrl.get
override def details: String = mDetails.get
override def description: String = mDescription.get

View File

@ -43,7 +43,7 @@ trait ProductsProvider {
case Some(products) => {
val productsWithLicense = for {
// Only return products that have a license set unless its for an admin view
product <- products if (adminView || (product.meta.license.name.size > 3 && product.meta.license.name.size > 3))
product <- products if (adminView || (product.meta.license.name.size > 3))
} yield product
Option(productsWithLicense)
}

View File

@ -66,7 +66,8 @@ object RemotedataActors extends MdcLoggable {
ActorProps[RemotedataUserInvitationActor] -> RemotedataUserInvitation.actorName,
ActorProps[RemotedataConsentAuthContextActor] -> RemotedataConsentAuthContext.actorName,
ActorProps[RemotedataTransactionRequestAttributeActor] -> RemotedataTransactionRequestAttribute.actorName,
ActorProps[RemotedataUserAgreementActor] -> RemotedataUserAgreement.actorName
ActorProps[RemotedataUserAgreementActor] -> RemotedataUserAgreement.actorName,
ActorProps[RemotedataBankAttributeActor] -> RemotedataBankAttribute.actorName
)
actorsRemotedata.foreach { a => logger.info(actorSystem.actorOf(a._1, name = a._2)) }

View File

@ -0,0 +1,29 @@
package code.remotedata
import akka.pattern.ask
import code.actorsystem.ObpActorInit
import code.bankattribute.{BankAttribute, BankAttributeProviderTrait, RemotedataBankAttributeCaseClasses}
import com.openbankproject.commons.model.BankId
import com.openbankproject.commons.model.enums.BankAttributeType
import net.liftweb.common.Box
import scala.collection.immutable.List
import scala.concurrent.Future
object RemotedataBankAttribute extends ObpActorInit with BankAttributeProviderTrait {
val cc = RemotedataBankAttributeCaseClasses
override def getBankAttributesFromProvider(bankId: BankId): Future[Box[List[BankAttribute]]] =
(actor ? cc.getBankAttributesFromProvider(bankId)).mapTo[Box[List[BankAttribute]]]
override def getBankAttributeById(bankAttributeId: String): Future[Box[BankAttribute]] =
(actor ? cc.getBankAttributeById(bankAttributeId)).mapTo[Box[BankAttribute]]
override def createOrUpdateBankAttribute(bankId: BankId, bankAttributeId: Option[String], name: String, attributType: BankAttributeType.Value, value: String, isActive: Option[Boolean]): Future[Box[BankAttribute]] =
(actor ? cc.createOrUpdateBankAttribute(bankId, bankAttributeId , name , attributType , value, isActive)).mapTo[Box[BankAttribute]]
override def deleteBankAttribute(bankAttributeId: String): Future[Box[Boolean]] =
(actor ? cc.deleteBankAttribute(bankAttributeId)).mapTo[Box[Boolean]]
}

View File

@ -0,0 +1,50 @@
package code.remotedata
import akka.actor.Actor
import akka.pattern.pipe
import code.actorsystem.ObpActorHelper
import code.bankattribute.{BankAttributeProvider, RemotedataBankAttributeCaseClasses}
import code.util.Helper.MdcLoggable
import com.openbankproject.commons.ExecutionContext.Implicits.global
import com.openbankproject.commons.model.BankId
import com.openbankproject.commons.model.enums.BankAttributeType
class RemotedataBankAttributeActor extends Actor with ObpActorHelper with MdcLoggable {
val mapper = BankAttributeProvider
val cc = RemotedataBankAttributeCaseClasses
def receive = {
case cc.getBankAttributesFromProvider(bankId: BankId) =>
logger.debug(s"getBankAttributesFromProvider(${bankId})")
mapper.getBankAttributesFromProvider(bankId) pipeTo sender
case cc.getBankAttributeById(bankAttributeId: String) =>
logger.debug(s"getBankAttributeById(${bankAttributeId})")
mapper.getBankAttributeById(bankAttributeId) pipeTo sender
case cc.createOrUpdateBankAttribute(bankId: BankId,
productAttributeId: Option[String],
name: String,
attributType: BankAttributeType.Value,
value: String,
isActive: Option[Boolean]) =>
logger.debug(s"createOrUpdateBankAttribute(${bankId}, ${productAttributeId}, ${name}, ${attributType}, ${value}, ${isActive})")
mapper.createOrUpdateBankAttribute(bankId,
productAttributeId,
name,
attributType,
value,
isActive) pipeTo sender
case cc.deleteBankAttribute(bankAttributeId: String) =>
logger.debug(s"deleteBankAttribute(${bankAttributeId})")
mapper.deleteBankAttribute(bankAttributeId) pipeTo sender
case message => logger.warn("[AKKA ACTOR ERROR - REQUEST NOT RECOGNIZED] " + message)
}
}

View File

@ -19,7 +19,7 @@ object RemotedataProductAttribute extends ObpActorInit with ProductAttributeProv
override def getProductAttributeById(productAttributeId: String): Future[Box[ProductAttribute]] = (actor ? cc.getProductAttributeById(productAttributeId)).mapTo[Box[ProductAttribute]]
override def createOrUpdateProductAttribute(bankId: BankId, productCode: ProductCode, productAttributeId: Option[String], name: String, attributType: ProductAttributeType.Value, value: String): Future[Box[ProductAttribute]] = (actor ? cc.createOrUpdateProductAttribute(bankId, productCode, productAttributeId , name , attributType , value )).mapTo[Box[ProductAttribute]]
override def createOrUpdateProductAttribute(bankId: BankId, productCode: ProductCode, productAttributeId: Option[String], name: String, attributeType: ProductAttributeType.Value, value: String, isActive: Option[Boolean]): Future[Box[ProductAttribute]] = (actor ? cc.createOrUpdateProductAttribute(bankId, productCode, productAttributeId , name , attributeType , value, isActive)).mapTo[Box[ProductAttribute]]
override def deleteProductAttribute(productAttributeId: String): Future[Box[Boolean]] = (actor ? cc.deleteProductAttribute(productAttributeId)).mapTo[Box[Boolean]]
}

View File

@ -30,15 +30,17 @@ class RemotedataProductAttributeActor extends Actor with ObpActorHelper with Mdc
productCode: ProductCode,
productAttributeId: Option[String],
name: String,
attributType: ProductAttributeType.Value,
value: String) =>
logger.debug(s"createOrUpdateProductAttribute(${bankId}, ${productCode}, ${productAttributeId}, ${name}, ${attributType}, ${value})")
attributeType: ProductAttributeType.Value,
value: String,
isActive: Option[Boolean]) =>
logger.debug(s"createOrUpdateProductAttribute(${bankId}, ${productCode}, ${productAttributeId}, ${name}, ${attributeType}, ${value}, ${isActive})")
mapper.createOrUpdateProductAttribute(bankId,
productCode,
productAttributeId,
name,
attributType,
value) pipeTo sender
attributeType,
value,
isActive) pipeTo sender
case cc.deleteProductAttribute(productAttributeId: String) =>
logger.debug(s"deleteProductAttribute(${productAttributeId})")

View File

@ -10,7 +10,13 @@ object RemotedataUserAgreement extends ObpActorInit with UserAgreementProvider {
val cc = RemotedataUserAgreementProviderCaseClass
def createOrUpdateUserAgreement(userId: String, summary: String, agreementText: String, acceptMarketingInfo: Boolean): Box[UserAgreement] = getValueFromFuture(
(actor ? cc.createOrUpdateUserAgreement(userId, summary, agreementText, acceptMarketingInfo)).mapTo[Box[UserAgreement]]
def createOrUpdateUserAgreement(userId: String, agreementType: String, agreementText: String): Box[UserAgreement] = getValueFromFuture(
(actor ? cc.createOrUpdateUserAgreement(userId, agreementType, agreementText)).mapTo[Box[UserAgreement]]
)
def createUserAgreement(userId: String, agreementType: String, agreementText: String): Box[UserAgreement] = getValueFromFuture(
(actor ? cc.createOrUpdateUserAgreement(userId, agreementType, agreementText)).mapTo[Box[UserAgreement]]
)
def getUserAgreement(userId: String, agreementType: String): Box[UserAgreement] = getValueFromFuture(
(actor ? cc.getUserAgreement(userId, agreementType)).mapTo[Box[UserAgreement]]
)
}

View File

@ -12,9 +12,17 @@ class RemotedataUserAgreementActor extends Actor with ObpActorHelper with MdcLog
def receive: PartialFunction[Any, Unit] = {
case cc.createOrUpdateUserAgreement(userId: String, summary: String, agreementText: String, acceptMarketingInfo: Boolean) =>
logger.debug(s"createOrUpdateUserAgreement($userId, $summary, $agreementText, $acceptMarketingInfo)")
sender ! (mapper.createOrUpdateUserAgreement(userId, summary, agreementText, acceptMarketingInfo))
case cc.createOrUpdateUserAgreement(userId: String, agreementType: String, agreementText: String) =>
logger.debug(s"createOrUpdateUserAgreement($userId, $agreementType, $agreementText)")
sender ! (mapper.createOrUpdateUserAgreement(userId, agreementType, agreementText))
case cc.createUserAgreement(userId: String, agreementType: String, agreementText: String) =>
logger.debug(s"createUserAgreement($userId, $agreementType, $agreementText)")
sender ! (mapper.createUserAgreement(userId, agreementType, agreementText))
case cc.getUserAgreement(userId: String, agreementType: String) =>
logger.debug(s"getUserAgreement($userId, $agreementType)")
sender ! (mapper.getUserAgreement(userId, agreementType))
case message => logger.warn("[AKKA ACTOR ERROR - REQUEST NOT RECOGNIZED] " + message)

View File

@ -1,11 +1,13 @@
package code.remotedata
import java.util.Date
import akka.pattern.ask
import code.actorsystem.ObpActorInit
import code.api.util.OBPQueryParam
import code.entitlement.Entitlement
import code.model.dataAccess.ResourceUser
import code.users.{RemotedataUsersCaseClasses, Users}
import code.users.{RemotedataUsersCaseClasses, UserAgreement, Users}
import com.openbankproject.commons.model.{User, UserPrimaryKey}
import net.liftweb.common.Box
@ -61,6 +63,9 @@ object RemotedataUsers extends ObpActorInit with Users {
def getUserByEmailFuture(email : String) : Future[List[(ResourceUser, Box[List[Entitlement]])]] =
(actor ? cc.getUserByEmailFuture(email)).mapTo[List[(ResourceUser, Box[List[Entitlement]])]]
def getUsersByEmail(email : String) : Future[List[(ResourceUser, Box[List[Entitlement]], Option[List[UserAgreement]])]] =
(actor ? cc.getUsersByEmail(email)).mapTo[List[(ResourceUser, Box[List[Entitlement]], Option[List[UserAgreement]])]]
def getAllUsers() : Box[List[ResourceUser]] = getValueFromFuture(
(actor ? cc.getAllUsers()).mapTo[Box[List[ResourceUser]]]
@ -70,9 +75,14 @@ object RemotedataUsers extends ObpActorInit with Users {
val res = (actor ? cc.getAllUsersF(queryParams))
res.mapTo[List[(ResourceUser, Box[List[Entitlement]])]]
}
def getUsers(queryParams: List[OBPQueryParam]): Future[List[(ResourceUser, Box[List[Entitlement]], Option[List[UserAgreement]])]] = {
val res = (actor ? cc.getUsers(queryParams))
res.mapTo[List[(ResourceUser, Box[List[Entitlement]], Option[List[UserAgreement]])]]
}
def createResourceUser(provider: String, providerId: Option[String], createdByConsentId: Option[String], name: Option[String], email: Option[String], userId: Option[String], createdByUserInvitationId: Option[String], company: Option[String]) : Box[ResourceUser] = getValueFromFuture(
(actor ? cc.createResourceUser(provider, providerId, createdByConsentId, name, email, userId, createdByUserInvitationId, company)).mapTo[Box[ResourceUser]]
def createResourceUser(provider: String, providerId: Option[String], createdByConsentId: Option[String], name: Option[String], email: Option[String], userId: Option[String], createdByUserInvitationId: Option[String], company: Option[String], lastMarketingAgreementSignedDate: Option[Date]) : Box[ResourceUser] = getValueFromFuture(
(actor ? cc.createResourceUser(provider, providerId, createdByConsentId, name, email, userId, createdByUserInvitationId, company, lastMarketingAgreementSignedDate)).mapTo[Box[ResourceUser]]
)
def createUnsavedResourceUser(provider: String, providerId: Option[String], name: Option[String], email: Option[String], userId: Option[String]) : Box[ResourceUser] = getValueFromFuture(

View File

@ -1,5 +1,7 @@
package code.remotedata
import java.util.Date
import akka.actor.Actor
import akka.pattern.pipe
import code.actorsystem.ObpActorHelper
@ -70,6 +72,10 @@ class RemotedataUsersActor extends Actor with ObpActorHelper with MdcLoggable {
case cc.getUserByEmailFuture(email: String) =>
logger.debug("getUserByEmailFuture(" + email +")")
sender ! (mapper.getUserByEmailF(email))
case cc.getUsersByEmail(email: String) =>
logger.debug("getUsersByEmail(" + email +")")
mapper.getUsersByEmail(email) pipeTo sender
case cc.getAllUsers() =>
logger.debug("getAllUsers()")
@ -78,10 +84,14 @@ class RemotedataUsersActor extends Actor with ObpActorHelper with MdcLoggable {
case cc.getAllUsersF(queryParams: List[OBPQueryParam]) =>
logger.debug(s"getAllUsersF(queryParams: ($queryParams))")
mapper.getAllUsersF(queryParams) pipeTo sender
case cc.getUsers(queryParams: List[OBPQueryParam]) =>
logger.debug(s"getUsers(queryParams: ($queryParams))")
mapper.getUsers(queryParams) pipeTo sender
case cc.createResourceUser(provider: String, providerId: Option[String], createdByConsentId: Option[String], name: Option[String], email: Option[String], userId: Option[String], createdByUserInvitationId: Option[String], company: Option[String]) =>
logger.debug("createResourceUser(" + provider + ", " + providerId.getOrElse("None") + ", " + name.getOrElse("None") + ", " + email.getOrElse("None") + ", " + userId.getOrElse("None") + ", " + createdByUserInvitationId.getOrElse("None") + ", " + company.getOrElse("None") + ")")
sender ! (mapper.createResourceUser(provider, providerId, createdByConsentId, name, email, userId, createdByUserInvitationId, company))
case cc.createResourceUser(provider: String, providerId: Option[String], createdByConsentId: Option[String], name: Option[String], email: Option[String], userId: Option[String], createdByUserInvitationId: Option[String], company: Option[String], lastMarketingAgreementSignedDate: Option[Date]) =>
logger.debug("createResourceUser(" + provider + ", " + providerId.getOrElse("None") + ", " + name.getOrElse("None") + ", " + email.getOrElse("None") + ", " + userId.getOrElse("None") + ", " + createdByUserInvitationId.getOrElse("None") + ", " + company.getOrElse("None") + ", " + lastMarketingAgreementSignedDate.getOrElse("None") + ")")
sender ! (mapper.createResourceUser(provider, providerId, createdByConsentId, name, email, userId, createdByUserInvitationId, company, lastMarketingAgreementSignedDate))
case cc.createUnsavedResourceUser(provider: String, providerId: Option[String], name: Option[String], email: Option[String], userId: Option[String]) =>
logger.debug("createUnsavedResourceUser(" + provider + ", " + providerId.getOrElse("None") + ", " + name.getOrElse("None") + ", " + email.getOrElse("None") + ", " + userId.getOrElse("None") + ")")

View File

@ -1,5 +1,7 @@
package code.sandbox
import code.api.util.APIUtil.validatePasswordOnCreation
import code.api.util.ErrorMessages
import code.model.dataAccess.{AuthUser, ResourceUser}
import code.users.Users
import net.liftweb.common.{Box, Failure, Full}
@ -36,7 +38,8 @@ trait CreateAuthUsers {
.validated(true)
val validationErrors = authUser.validate
if(!validationErrors.isEmpty) Failure(s"Errors: ${validationErrors.map(_.msg)}")
if (!validatePasswordOnCreation(u.password)) Failure(ErrorMessages.InvalidStrongPasswordFormat)
else if(!validationErrors.isEmpty) Failure(s"Errors: ${validationErrors.map(_.msg)}")
else Full(asSaveable(authUser))
}
}

View File

@ -3,9 +3,12 @@ package code.snippet
import java.net.URI
import code.api.OpenIdConnectConfig
import code.api.util.APIUtil
import com.nimbusds.jwt.JWT
import com.nimbusds.oauth2.sdk.id.{ClientID, State}
import com.nimbusds.oauth2.sdk.{ResponseType, Scope}
import com.nimbusds.openid.connect.sdk.{AuthenticationRequest, Nonce}
import com.nimbusds.oauth2.sdk.pkce.{CodeChallenge, CodeChallengeMethod}
import com.nimbusds.oauth2.sdk.{ResponseMode, ResponseType, Scope}
import com.nimbusds.openid.connect.sdk.{AuthenticationRequest, Display, Nonce, OIDCClaimsRequest, OIDCScopeValue, Prompt}
import net.liftweb.common.{Box, Empty, Full, Loggable}
import net.liftweb.http.js.{JsCmd, JsCmds}
import net.liftweb.http.{S, SHtml, SessionVar}
@ -36,17 +39,61 @@ object OpenidConnectInvoke extends Loggable {
// Generate nonce
val nonce = new Nonce()
val responseMode = APIUtil.getPropsValue("openid_connect.response_mode", "form_post") match {
case "query" => ResponseMode.QUERY
case "fragment" => ResponseMode.FRAGMENT
case "form_post" => ResponseMode.FORM_POST
case "query.jwt" => ResponseMode.QUERY_JWT
case "fragment.jwt" => ResponseMode.FRAGMENT_JWT
case "form_post.jwt" => ResponseMode.FORM_POST_JWT
case "jwt" => ResponseMode.JWT
case _ => ResponseMode.FORM_POST
}
val responseType = APIUtil.getPropsValue("openid_connect.response_type", "code") match {
case "code" => new ResponseType("code")
case "id_token" => new ResponseType("id_token")
case "code id_token" => new ResponseType("code", "id_token")
case _ => new ResponseType("code")
}
val scope = APIUtil.getPropsValue("openid_connect.scope", "openid email profile") match {
case "openid email profile" =>
val scope: Scope = new Scope();
scope.add(OIDCScopeValue.OPENID);
scope.add(OIDCScopeValue.EMAIL);
scope.add(OIDCScopeValue.PROFILE);
scope
case "openid email" =>
val scope: Scope = new Scope();
scope.add(OIDCScopeValue.OPENID);
scope.add(OIDCScopeValue.EMAIL);
scope
case "openid" =>
val scope: Scope = new Scope();
scope.add(OIDCScopeValue.OPENID);
scope
case _ =>
val scope: Scope = new Scope();
scope.add(OIDCScopeValue.OPENID);
scope.add(OIDCScopeValue.EMAIL);
scope.add(OIDCScopeValue.PROFILE);
scope
}
// Compose the request (in code flow)
val req =
new AuthenticationRequest(
new URI(OpenIdConnectConfig.get(identityProvider).authorization_endpoint),
new ResponseType("code"),
Scope.parse("openid email profile"),
clientID,
callback,
state,
nonce
)
val req = new AuthenticationRequest(
new URI(OpenIdConnectConfig.get(identityProvider).authorization_endpoint),
responseType,
responseMode,
scope,
clientID,
callback,
state,
nonce,
null, null, -1, null, null,
null, null, null, null.asInstanceOf[OIDCClaimsRequest], null,
null, null,
null, null,
null, false, null)
OpenIDConnectSessionState.set(Full(state))
val accessType = if (OpenIdConnectConfig.get(identityProvider).access_type_offline) "&access_type=offline" else ""
val redirectTo = req.toHTTPRequest.getURL() + "?" + req.toHTTPRequest.getQuery() + accessType

View File

@ -0,0 +1,66 @@
/**
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 code.snippet
import code.api.util.ErrorMessages.attemptedToOpenAnEmptyBox
import code.model.dataAccess.AuthUser
import code.util.Helper.MdcLoggable
import com.openbankproject.commons.model.User
import net.liftweb.http.{RequestVar, SHtml}
import net.liftweb.util.CssSel
import net.liftweb.util.Helpers._
import scala.xml.NodeSeq
class UserInformation extends MdcLoggable {
private object idTokenVar extends RequestVar("")
private object providerVar extends RequestVar("")
private object devEmailVar extends RequestVar("")
private object usernameVar extends RequestVar("")
def show: CssSel = {
if(!AuthUser.loggedIn_?) {
"*" #> NodeSeq.Empty
} else if (AuthUser.getCurrentUser.isEmpty) {
"*" #> NodeSeq.Empty
} else {
val user: User = AuthUser.getCurrentUser.openOrThrowException(attemptedToOpenAnEmptyBox)
usernameVar.set(user.name)
devEmailVar.set(user.emailAddress)
providerVar.set(user.provider)
idTokenVar.set(AuthUser.getIDTokenOfCurrentUser)
"form" #> {
"#user-info-username" #> SHtml.text(usernameVar, usernameVar(_)) &
"#user-info-provider" #> SHtml.text(providerVar.is, providerVar(_)) &
"#user-info-email" #> SHtml.text(devEmailVar, devEmailVar(_)) &
"#user-info-id-token" #> SHtml.text(idTokenVar, idTokenVar(_))
} & "#register-consumer-success" #> ""
}
}
}

View File

@ -27,8 +27,9 @@ TESOBE (http://www.tesobe.com/)
package code.snippet
import java.time.{Duration, ZoneId, ZoneOffset, ZonedDateTime}
import java.util.Date
import code.api.util.APIUtil
import code.api.util.{APIUtil, SecureRandomUtil}
import code.model.dataAccess.{AuthUser, ResourceUser}
import code.users
import code.users.{UserAgreementProvider, UserInvitationProvider, Users}
@ -36,9 +37,9 @@ import code.util.Helper
import code.util.Helper.MdcLoggable
import code.webuiprops.MappedWebUiPropsProvider.getWebUiPropsValue
import com.openbankproject.commons.model.User
import net.liftweb.common.Box
import net.liftweb.common.{Box, Empty, Failure, Full}
import net.liftweb.http.{RequestVar, S, SHtml}
import net.liftweb.util.CssSel
import net.liftweb.util.{CssSel, Helpers}
import net.liftweb.util.Helpers._
import scala.collection.immutable.List
@ -53,14 +54,17 @@ class UserInvitation extends MdcLoggable {
private object usernameVar extends RequestVar("")
private object termsCheckboxVar extends RequestVar(false)
private object marketingInfoCheckboxVar extends RequestVar(false)
private object consentForCollectingCheckboxVar extends RequestVar(false)
private object consentForCollectingMandatoryCheckboxVar extends RequestVar(true)
private object privacyCheckboxVar extends RequestVar(false)
val ttl = APIUtil.getPropsAsLongValue("user_invitation.ttl.seconds", 86400)
val registrationConsumerButtonValue: String = getWebUiPropsValue("webui_post_user_invitation_submit_button_value", "Register as a Developer")
val privacyConditionsValue: String = getWebUiPropsValue("webui_post_user_invitation_privacy_conditions_value", "Privacy conditions..")
val termsAndConditionsValue: String = getWebUiPropsValue("webui_post_user_invitation_terms_and_conditions_value", "Terms and Conditions..")
val privacyConditionsValue: String = getWebUiPropsValue("webui_privacy_policy", "")
val termsAndConditionsValue: String = getWebUiPropsValue("webui_terms_and_conditions", "")
val termsAndConditionsCheckboxValue: String = getWebUiPropsValue("webui_post_user_invitation_terms_and_conditions_checkbox_value", "I agree to the above Developer Terms and Conditions")
val personalDataCollectionConsentCountryWaiverList = getWebUiPropsValue("personal_data_collection_consent_country_waiver_list", "").split(",").toList.map(_.trim)
def registerForm: CssSel = {
@ -74,8 +78,14 @@ class UserInvitation extends MdcLoggable {
devEmailVar.set(email)
companyVar.set(userInvitation.map(_.company).getOrElse("None"))
countryVar.set(userInvitation.map(_.country).getOrElse("None"))
usernameVar.set(firstNameVar.is.toLowerCase + "." + lastNameVar.is.toLowerCase())
// Propose the username only for the first time. In case an end user manually change it we must not override it.
if(usernameVar.isEmpty) usernameVar.set(firstNameVar.is.toLowerCase + "." + lastNameVar.is.toLowerCase())
if(personalDataCollectionConsentCountryWaiverList.exists(_.toLowerCase == countryVar.is.toLowerCase) == true) {
consentForCollectingMandatoryCheckboxVar.set(false)
} else {
consentForCollectingMandatoryCheckboxVar.set(true)
}
def submitButtonDefense(): Unit = {
val verifyingTime = ZonedDateTime.now(ZoneOffset.UTC)
val createdAt = userInvitation.map(_.createdAt.get).getOrElse(time(239932800))
@ -90,35 +100,48 @@ class UserInvitation extends MdcLoggable {
else if(Users.users.vend.getUserByUserName(usernameVar.is).isDefined) showErrorsForUsername()
else if(privacyCheckboxVar.is == false) showErrorsForPrivacyConditions()
else if(termsCheckboxVar.is == false) showErrorsForTermsAndConditions()
else if(personalDataCollectionConsentCountryWaiverList.exists(_.toLowerCase == countryVar.is.toLowerCase) == false && consentForCollectingCheckboxVar.is == false) showErrorsForConsentForCollectingPersonalData()
else {
// Resource User table
createResourceUser(
provider = "OBP-User-Invitation",
providerId = Some(usernameVar.is),
name = Some(firstNameVar.is + " " + lastNameVar.is),
name = Some(usernameVar.is),
email = Some(email),
userInvitationId = userInvitation.map(_.userInvitationId).toOption,
company = userInvitation.map(_.company).toOption
company = userInvitation.map(_.company).toOption,
lastMarketingAgreementSignedDate = if(marketingInfoCheckboxVar.is) Some(new Date()) else None
).map{ u =>
// AuthUser table
createAuthUser(user = u, firstName = firstNameVar.is, lastName = lastNameVar.is, password = "")
// Use Agreement table
UserAgreementProvider.userAgreementProvider.vend.createOrUpdateUserAgreement(
u.userId, privacyConditionsValue, termsAndConditionsValue, marketingInfoCheckboxVar.is)
// Set the status of the user invitation to "FINISHED"
UserInvitationProvider.userInvitationProvider.vend.updateStatusOfUserInvitation(userInvitation.map(_.userInvitationId).getOrElse(""), "FINISHED")
// Set a new password
val resetLink = AuthUser.passwordResetUrl(u.idGivenByProvider, u.emailAddress, u.userId) + "?action=set"
S.redirectTo(resetLink)
createAuthUser(user = u, firstName = firstNameVar.is, lastName = lastNameVar.is) match {
case Failure(msg,_,_) =>
Users.users.vend.deleteResourceUser(u.id.get)
showError(msg)
case _ =>
// User Agreement table
UserAgreementProvider.userAgreementProvider.vend.createOrUpdateUserAgreement(
u.userId, "privacy_conditions", privacyConditionsValue)
UserAgreementProvider.userAgreementProvider.vend.createOrUpdateUserAgreement(
u.userId, "terms_and_conditions", termsAndConditionsValue)
UserAgreementProvider.userAgreementProvider.vend.createOrUpdateUserAgreement(
u.userId, "accept_marketing_info", marketingInfoCheckboxVar.is.toString)
UserAgreementProvider.userAgreementProvider.vend.createOrUpdateUserAgreement(
u.userId, "consent_for_collecting_personal_data", consentForCollectingCheckboxVar.is.toString)
// Set the status of the user invitation to "FINISHED"
UserInvitationProvider.userInvitationProvider.vend.updateStatusOfUserInvitation(userInvitation.map(_.userInvitationId).getOrElse(""), "FINISHED")
// Set a new password
val resetLink = AuthUser.passwordResetUrl(u.idGivenByProvider, u.emailAddress, u.userId) + "?action=set"
S.redirectTo(resetLink)
}
}
}
}
def showError(usernameError: String) = {
S.error("register-consumer-errors", usernameError)
S.error("data-area-errors", usernameError)
register &
"#register-consumer-errors *" #> {
"#data-area-errors *" #> {
".error *" #>
List(usernameError).map({ e =>
".errorContent *" #> e
@ -130,7 +153,7 @@ class UserInvitation extends MdcLoggable {
showError(Helper.i18n("your.secret.link.is.not.valid"))
}
def showErrorsForUsername() = {
showError(Helper.i18n("unique.username"))
showError(Helper.i18n("your.username.is.not.unique"))
}
def showErrorsForStatus() = {
showError(Helper.i18n("user.invitation.is.already.finished"))
@ -144,6 +167,9 @@ class UserInvitation extends MdcLoggable {
def showErrorsForPrivacyConditions() = {
showError(Helper.i18n("privacy.conditions.are.not.selected"))
}
def showErrorsForConsentForCollectingPersonalData() = {
showError(Helper.i18n("consent.to.collect.personal.data.is.not.selected"))
}
def register = {
"form" #> {
@ -153,30 +179,49 @@ class UserInvitation extends MdcLoggable {
"#companyName" #> SHtml.text(companyVar.is, companyVar(_)) &
"#devEmail" #> SHtml.text(devEmailVar, devEmailVar(_)) &
"#username" #> SHtml.text(usernameVar, usernameVar(_)) &
"#privacy" #> SHtml.textarea(privacyConditionsValue, privacyConditionsValue => privacyConditionsValue) &
"#privacy_checkbox" #> SHtml.checkbox(privacyCheckboxVar, privacyCheckboxVar(_)) &
"#terms" #> SHtml.textarea(termsAndConditionsValue, termsAndConditionsValue => termsAndConditionsValue) &
"#terms_checkbox" #> SHtml.checkbox(termsCheckboxVar, termsCheckboxVar(_)) &
"#marketing_info_checkbox" #> SHtml.checkbox(marketingInfoCheckboxVar, marketingInfoCheckboxVar(_)) &
"#consent_for_collecting_checkbox" #> SHtml.checkbox(consentForCollectingCheckboxVar, consentForCollectingCheckboxVar(_), "id" -> "consent_for_collecting_checkbox") &
"#consent_for_collecting_mandatory" #> SHtml.checkbox(consentForCollectingMandatoryCheckboxVar, consentForCollectingMandatoryCheckboxVar(_), "id" -> "consent_for_collecting_mandatory", "hidden" -> "true") &
"type=submit" #> SHtml.submit(s"$registrationConsumerButtonValue", () => submitButtonDefense)
} &
"#register-consumer-success" #> ""
"#data-area-success" #> ""
}
userInvitation match {
case Full(payload) if payload.status == "CREATED" => // All good
case _ =>
// Clear all data
firstNameVar.set("None")
lastNameVar.set("None")
devEmailVar.set("None")
companyVar.set("None")
countryVar.set("None")
usernameVar.set("None")
// and the redirect
S.redirectTo("/user-invitation-invalid")
}
register
}
private def createAuthUser(user: User, firstName: String, lastName: String, password: String): Box[AuthUser] = tryo {
private def createAuthUser(user: User, firstName: String, lastName: String): Box[AuthUser] = {
val newUser = AuthUser.create
.firstName(firstName)
.lastName(lastName)
.email(user.emailAddress)
.user(user.userPrimaryKey.value)
.username(user.idGivenByProvider)
.username(user.name)
.provider(user.provider)
.password(password)
.password(SecureRandomUtil.alphanumeric(10))
.validated(true)
// Save the user
newUser.saveMe()
newUser.validate match {
case Nil =>
// Save the user
Full(newUser.saveMe())
case xs => S.error(xs)
Failure(xs.map(i => i.msg).mkString(";"))
}
}
private def createResourceUser(provider: String,
@ -184,7 +229,8 @@ class UserInvitation extends MdcLoggable {
name: Option[String],
email: Option[String],
userInvitationId: Option[String],
company: Option[String]
company: Option[String],
lastMarketingAgreementSignedDate: Option[Date],
): Box[ResourceUser] = {
Users.users.vend.createResourceUser(
provider = provider,
@ -194,7 +240,8 @@ class UserInvitation extends MdcLoggable {
email = email,
userId = None,
createdByUserInvitationId = userInvitationId,
company = company
company = company,
lastMarketingAgreementSignedDate = lastMarketingAgreementSignedDate
)
}

View File

@ -63,6 +63,8 @@ class WebUI extends MdcLoggable{
}
}
// Cookie Consent button.
// Note we don't currently (7th Jan 2017) need to display the cookie consent message due to our limited use of cookies
// If a deployment does make more use of cookies we would need to add a No button and we might want to make use of the
@ -140,7 +142,23 @@ class WebUI extends MdcLoggable{
"#main-showcases *" #> scala.xml.Unparsed(sdksHtmlContent)
}
val mainFaqHtmlLink = getWebUiPropsValue("webui_main_faq_external_link","")
val mainFaqHtmlContent = try{
if (mainFaqHtmlLink.isEmpty)//If the webui_featured_sdks_external_link is not set, we will read the internal sdks.html file instead.
LiftRules.getResource("/main-faq.html").map{ url =>
Source.fromURL(url, "UTF-8").mkString
}.openOrThrowException("Please check the content of this file: src/main/webapp/main-faq.html")
else
Source.fromURL(mainFaqHtmlLink, "UTF-8").mkString
}catch {
case _ : Throwable => "<h1>FAQs is wrong, please check the props `webui_main_faq_external_link` </h1>"
}
// webui_featured_sdks_external_link props, we can set the sdks here. check the `SDK Showcases` in Homepage, and you can see all the sdks.
def mainFaqHtml: CssSel = {
"#main-faq *" #> scala.xml.Unparsed(mainFaqHtmlContent)
}
val brandString = activeBrand match {
case Some(v) => s"&brand=$v"
@ -175,6 +193,10 @@ class WebUI extends MdcLoggable{
def apiTesterLink: CssSel = {
".api-tester-link a [href]" #> scala.xml.Unparsed(getWebUiPropsValue("webui_api_tester_url", ""))
}
// Link to Hola app
def apiHolaLink: CssSel = {
".api-hola-link a [href]" #> scala.xml.Unparsed(getWebUiPropsValue("webui_api_hola_url", "#"))
}
// Link to API
def apiLink: CssSel = {
@ -198,9 +220,27 @@ class WebUI extends MdcLoggable{
// Terms&Conditions
def termsAndConditions: CssSel = {
".termsAndConditions-link a [href]" #> scala.xml.Unparsed(getWebUiPropsValue("webui_agree_terms_url", ""))
".termsAndConditions-link a [href]" #> scala.xml.Unparsed(getWebUiPropsValue("webui_agree_terms_url", "/terms-and-conditions"))
}
def termsAndConditionsText = {
val webUiPropsValue = getWebUiPropsValue("webui_terms_and_conditions", "")
"#terms-and-conditions-page" #> scala.xml.Unparsed(makeHtml(webUiPropsValue))
}
// Points to the documentation. Probably a sandbox specific link is good.
def privacyPolicyLink: CssSel = {
".privacy-policy-link a [href]" #> scala.xml.Unparsed(getWebUiPropsValue("webui_privacy_policy_url", "/privacy-policy"))
}
def privacyPolicyText = {
val webUiPropsValue = getWebUiPropsValue("webui_privacy_policy", "")
"#privacy-policy-page" #> scala.xml.Unparsed(makeHtml(webUiPropsValue))
}
def supportEmail = {
val webUiPropsValue = getWebUiPropsValue("webui_support_email", "contact@openbankproject.com")
"#webui-support-email a *" #> scala.xml.Unparsed(webUiPropsValue) &
"#webui-support-email a [href]" #> scala.xml.Unparsed(s"mailto:$webUiPropsValue")
}
def sandboxIntroductionLink: CssSel = {
val webUiApiDocumentation = getWebUiPropsValue("webui_api_documentation_url",s"${getServerUrl}/introduction")
val apiDocumentation =
@ -236,7 +276,10 @@ class WebUI extends MdcLoggable{
"#api_documentation_content *" #> scala.xml.Unparsed(htmlDescription)
}
// Points to the documentation. Probably a sandbox specific link is good.
def apiDocumentationLink: CssSel = {
".api-documentation-link a [href]" #> scala.xml.Unparsed(getWebUiPropsValue("webui_api_documentation_bottom_url", "https://github.com/OpenBankProject/OBP-API/wiki"))
}
// For example customers and credentials
// This relies on the page for sandbox documentation having an anchor called example-customer-logins
def exampleSandboxCredentialsLink: CssSel = {
@ -302,7 +345,7 @@ class WebUI extends MdcLoggable{
// Support platform link
def supportPlatformLink: CssSel = {
val supportplatformlink = scala.xml.Unparsed(getWebUiPropsValue("webui_support_platform_url", "https://slack.openbankproject.com/"))
val supportplatformlink = scala.xml.Unparsed(getWebUiPropsValue("webui_support_platform_url", "https://chat.openbankproject.com"))
".support-platform-link a [href]" #> supportplatformlink &
".support-platform-link a *" #> supportplatformlink.toString().replace("https://","").replace("http://", "")
}
@ -318,6 +361,9 @@ class WebUI extends MdcLoggable{
"#main_style_sheet [href]" #> scala.xml.Unparsed(getWebUiPropsValue("webui_main_style_sheet", "/media/css/website.css"))
}
def faviconLink: CssSel = {
"#favicon_link [href]" #> scala.xml.Unparsed(getWebUiPropsValue("webui_favicon_link_url", "/favicon.ico"))
}
def getStartedText: CssSel = {

View File

@ -82,4 +82,19 @@ object WebUITemplate {
|</html>
|
|""".stripMargin
webUIDoc += WebUIDoc(
webUiPropsName = "webui_terms_and_conditions",
defaultValue = "",
typeOfValue = "markdown",
placeholders = List()
)
webUIDoc += WebUIDoc(
webUiPropsName = "webui_privacy_policy",
defaultValue = "",
typeOfValue = "markdown",
placeholders = List()
)
}

View File

@ -1,5 +1,7 @@
package code.token
import java.util.Date
import net.liftweb.common.Box
import net.liftweb.mapper._
@ -9,7 +11,8 @@ object MappedOpenIDConnectTokensProvider extends OpenIDConnectTokensProvider {
idToken: String,
refreshToken: String,
scope: String,
expiresIn: Long): Box[OpenIDConnectToken] = Box.tryo {
expiresIn: Long,
authUserPrimaryKey: Long): Box[OpenIDConnectToken] = Box.tryo {
OpenIDConnectToken.create
.TokenType(tokenType.toString())
.AccessToken(accessToken)
@ -17,8 +20,12 @@ object MappedOpenIDConnectTokensProvider extends OpenIDConnectTokensProvider {
.RefreshToken(refreshToken)
.Scope(scope)
.ExpiresIn(expiresIn)
.AuthUserPrimaryKey(authUserPrimaryKey)
.saveMe()
}
def getOpenIDConnectTokenByAuthUser(authUserPrimaryKey: Long) =
OpenIDConnectToken.findAll(By(OpenIDConnectToken.AuthUserPrimaryKey, authUserPrimaryKey))
.sortBy(_.createdAt.get)(Ordering[Date].reverse).headOption
}
@ -31,6 +38,7 @@ class OpenIDConnectToken extends OpenIDConnectTokenTrait with LongKeyedMapper[Op
object Scope extends MappedString(this, 250)
object TokenType extends MappedString(this, 250)
object ExpiresIn extends MappedLong(this)
object AuthUserPrimaryKey extends MappedLong(this)
override def accessToken: String = AccessToken.get
override def idToken: String = IDToken.get
@ -38,6 +46,7 @@ class OpenIDConnectToken extends OpenIDConnectTokenTrait with LongKeyedMapper[Op
override def scope: String = Scope.get
override def tokenType: String = TokenType.get
override def expiresIn: Long = ExpiresIn.get
override def authUserPrimaryKey: Long = AuthUserPrimaryKey.get
}

View File

@ -14,7 +14,10 @@ trait OpenIDConnectTokensProvider {
idToken: String,
refreshToken: String,
scope: String,
expiresIn: Long): Box[OpenIDConnectToken]
expiresIn: Long,
authUserPrimaryKey: Long): Box[OpenIDConnectToken]
def getOpenIDConnectTokenByAuthUser(authUserPrimaryKey: Long): Box[OpenIDConnectToken]
}
trait OpenIDConnectTokenTrait {
@ -24,4 +27,5 @@ trait OpenIDConnectTokenTrait {
def scope: String
def tokenType: String
def expiresIn: Long
def authUserPrimaryKey: Long
}

View File

@ -1,5 +1,7 @@
package code.users
import java.util.Date
import code.api.util._
import code.entitlement.Entitlement
import code.loginattempts.LoginAttempt.maxBadLoginAttempts
@ -60,7 +62,8 @@ object LiftUsers extends Users with MdcLoggable{
email = email,
userId = None,
createdByUserInvitationId = None,
company = None
company = None,
lastMarketingAgreementSignedDate = None
)
(newUser, true)
}
@ -111,6 +114,25 @@ object LiftUsers extends Users with MdcLoggable{
(user, Entitlement.entitlement.vend.getEntitlementsByUserId(user.userId).map(_.sortWith(_.roleName < _.roleName)))
}
}
override def getUsersByEmail(email: String): Future[List[(ResourceUser, Box[List[Entitlement]], Option[List[UserAgreement]])]] = Future {
val users = ResourceUser.findAll(By(ResourceUser.email, email))
for {
user <- users
} yield {
val entitlements = Entitlement.entitlement.vend.getEntitlementsByUserId(user.userId).map(_.sortWith(_.roleName < _.roleName))
// val agreements = getUserAgreements(user)
(user, entitlements, None)
}
}
private def getUserAgreements(user: ResourceUser) = {
val acceptMarketingInfo = UserAgreementProvider.userAgreementProvider.vend.getUserAgreement(user.userId, "accept_marketing_info")
val termsAndConditions = UserAgreementProvider.userAgreementProvider.vend.getUserAgreement(user.userId, "terms_and_conditions")
val privacyConditions = UserAgreementProvider.userAgreementProvider.vend.getUserAgreement(user.userId, "privacy_conditions")
val agreements = acceptMarketingInfo.toList ::: termsAndConditions.toList ::: privacyConditions.toList
agreements
}
override def getUserByEmailFuture(email: String): Future[List[(ResourceUser, Box[List[Entitlement]])]] = {
Future {
@ -123,11 +145,21 @@ object LiftUsers extends Users with MdcLoggable{
}
override def getAllUsersF(queryParams: List[OBPQueryParam]): Future[List[(ResourceUser, Box[List[Entitlement]])]] = {
Future {
for {
user <- getUsersCommon(queryParams)
} yield {
(user, Entitlement.entitlement.vend.getEntitlementsByUserId(user.userId).map(_.sortWith(_.roleName < _.roleName)))
}
}
}
private def getUsersCommon(queryParams: List[OBPQueryParam]) = {
val limit = queryParams.collect { case OBPLimit(value) => MaxRows[ResourceUser](value) }.headOption
val offset: Option[StartAt[ResourceUser]] = queryParams.collect { case OBPOffset(value) => StartAt[ResourceUser](value) }.headOption
val locked: Option[String] = queryParams.collect { case OBPLockedStatus(value) => value }.headOption
val deleted = queryParams.collect {
val deleted = queryParams.collect {
case OBPIsDeleted(value) if value == true => // ?is_deleted=true
By(ResourceUser.IsDeleted, true)
case OBPIsDeleted(value) if value == false => // ?is_deleted=false
@ -135,13 +167,14 @@ object LiftUsers extends Users with MdcLoggable{
}.headOption.orElse(
Some(By(ResourceUser.IsDeleted, false)) // There is no query parameter "is_deleted"
)
val optionalParams: Seq[QueryParam[ResourceUser]] = Seq(limit.toSeq, offset.toSeq, deleted.toSeq).flatten
def getAllResourceUsers(): List[ResourceUser] = ResourceUser.findAll(optionalParams: _*)
val showUsers: List[ResourceUser] = locked.map(_.toLowerCase()) match {
case Some("active") =>
val lockedUsers: immutable.Seq[MappedBadLoginAttempt] =
val lockedUsers: immutable.Seq[MappedBadLoginAttempt] =
MappedBadLoginAttempt.findAll(
By_>(MappedBadLoginAttempt.mBadAttemptsSinceLastSuccessOrReset, maxBadLoginAttempts.toInt)
)
@ -157,11 +190,17 @@ object LiftUsers extends Users with MdcLoggable{
case _ =>
getAllResourceUsers()
}
showUsers
}
override def getUsers(queryParams: List[OBPQueryParam]): Future[List[(ResourceUser, Box[List[Entitlement]], Option[List[UserAgreement]])]] = {
Future {
for {
user <- showUsers
user <- getUsersCommon(queryParams)
} yield {
(user, Entitlement.entitlement.vend.getEntitlementsByUserId(user.userId).map(_.sortWith(_.roleName < _.roleName)))
val entitlements = Entitlement.entitlement.vend.getEntitlementsByUserId(user.userId).map(_.sortWith(_.roleName < _.roleName))
// val agreements = getUserAgreements(user)
(user, entitlements, None)
}
}
}
@ -174,7 +213,8 @@ object LiftUsers extends Users with MdcLoggable{
email: Option[String],
userId: Option[String],
createdByUserInvitationId: Option[String],
company: Option[String]): Box[ResourceUser] = {
company: Option[String],
lastMarketingAgreementSignedDate: Option[Date]): Box[ResourceUser] = {
val ru = ResourceUser.create
ru.provider_(provider)
providerId match {
@ -205,6 +245,10 @@ object LiftUsers extends Users with MdcLoggable{
case Some(v) => ru.Company(v)
case None =>
}
lastMarketingAgreementSignedDate match {
case Some(v) => ru.LastMarketingAgreementSignedDate(v)
case None =>
}
Full(ru.saveMe())
}

View File

@ -9,29 +9,46 @@ import net.liftweb.common.{Box, Empty, Full}
import net.liftweb.mapper._
object MappedUserAgreementProvider extends UserAgreementProvider {
override def createOrUpdateUserAgreement(userId: String, summary: String, agreementText: String, acceptMarketingInfo: Boolean): Box[UserAgreement] = {
UserAgreement.find(By(UserAgreement.UserId, userId)) match {
override def createOrUpdateUserAgreement(userId: String, agreementType: String, agreementText: String): Box[UserAgreement] = {
UserAgreement.find(
By(UserAgreement.UserId, userId),
By(UserAgreement.AgreementType, agreementType)
) match {
case Full(existingUser) =>
Full(
existingUser
.Summary(summary)
.AgreementType(agreementType)
.AgreementText(agreementText)
.AcceptMarketingInfo(acceptMarketingInfo)
.saveMe()
)
case Empty =>
Full(
UserAgreement.create
.UserId(userId)
.Summary(summary)
.AgreementType(agreementType)
.AgreementText(agreementText)
.AcceptMarketingInfo(acceptMarketingInfo)
.Date(new Date)
.saveMe()
)
case everythingElse => everythingElse
}
}
override def createUserAgreement(userId: String, agreementType: String, agreementText: String): Box[UserAgreement] = {
Full(
UserAgreement.create
.UserId(userId)
.AgreementType(agreementType)
.AgreementText(agreementText)
.Date(new Date)
.saveMe()
)
}
override def getUserAgreement(userId: String, agreementType: String): Box[UserAgreement] = {
UserAgreement.findAll(
By(UserAgreement.UserId, userId),
By(UserAgreement.AgreementType, agreementType)
).sortBy(_.Date.get)(Ordering[Date].reverse).headOption
}
}
class UserAgreement extends UserAgreementTrait with LongKeyedMapper[UserAgreement] with IdPK with CreatedUpdated {
@ -42,19 +59,18 @@ class UserAgreement extends UserAgreementTrait with LongKeyedMapper[UserAgreemen
}
object UserId extends MappedString(this, 255)
object Date extends MappedDate(this)
object Summary extends MappedText(this)
object AgreementType extends MappedString(this, 64)
object AgreementText extends MappedText(this)
object AgreementHash extends MappedString(this, 64) {
override def defaultValue: String = HashUtil.Sha256Hash(AgreementText.get)
}
object AcceptMarketingInfo extends MappedBoolean(this)
override def userInvitationId: String = UserAgreementId.get
override def userId: String = UserId.get
override def summary: String = Summary.get
override def agreementType: String = AgreementType.get
override def agreementText: String = AgreementText.get
override def agreementHash: String = AgreementHash.get
override def acceptMarketingInfo: Boolean = AcceptMarketingInfo.get
override def date: Date = Date.get
}
object UserAgreement extends UserAgreement with LongKeyedMetaMapper[UserAgreement] {

View File

@ -1,8 +1,9 @@
package code.users
import java.util.Date
import code.api.util.APIUtil
import code.remotedata.{RemotedataUserAgreement, RemotedataUserInvitation}
import com.openbankproject.commons.model.BankId
import code.remotedata.RemotedataUserAgreement
import net.liftweb.common.Box
import net.liftweb.util.SimpleInjector
@ -20,11 +21,15 @@ object UserAgreementProvider extends SimpleInjector {
}
trait UserAgreementProvider {
def createOrUpdateUserAgreement(userId: String, summary: String, agreementText: String, acceptMarketingInfo: Boolean): Box[UserAgreement]
def createOrUpdateUserAgreement(userId: String, agreementType: String, agreementText: String): Box[UserAgreement]
def createUserAgreement(userId: String, agreementType: String, agreementText: String): Box[UserAgreement]
def getUserAgreement(userId: String, agreementType: String): Box[UserAgreement]
}
class RemotedataUserAgreementProviderCaseClass {
case class createOrUpdateUserAgreement(userId: String, summary: String, agreementText: String, acceptMarketingInfo: Boolean)
case class createOrUpdateUserAgreement(userId: String, agreementType: String, agreementText: String)
case class createUserAgreement(userId: String, agreementType: String, agreementText: String)
case class getUserAgreement(userId: String, agreementType: String)
}
object RemotedataUserAgreementProviderCaseClass extends RemotedataUserAgreementProviderCaseClass
@ -32,8 +37,8 @@ object RemotedataUserAgreementProviderCaseClass extends RemotedataUserAgreementP
trait UserAgreementTrait {
def userInvitationId: String
def userId: String
def summary: String
def agreementType: String
def agreementText: String
def agreementHash: String
def acceptMarketingInfo: Boolean
def date: Date
}

View File

@ -1,5 +1,7 @@
package code.users
import java.util.Date
import code.api.util.{APIUtil, OBPQueryParam}
import code.entitlement.Entitlement
import code.model.dataAccess.ResourceUser
@ -46,12 +48,23 @@ trait Users {
def getUserByEmail(email: String) : Box[List[ResourceUser]]
def getUserByEmailFuture(email: String) : Future[List[(ResourceUser, Box[List[Entitlement]])]]
def getUsersByEmail(email: String) : Future[List[(ResourceUser, Box[List[Entitlement]], Option[List[UserAgreement]])]]
def getAllUsers() : Box[List[ResourceUser]]
def getAllUsersF(queryParams: List[OBPQueryParam]) : Future[List[(ResourceUser, Box[List[Entitlement]])]]
def createResourceUser(provider: String, providerId: Option[String], createdByConsentId: Option[String], name: Option[String], email: Option[String], userId: Option[String], createdByUserInvitationId: Option[String], company: Option[String]) : Box[ResourceUser]
def getUsers(queryParams: List[OBPQueryParam]): Future[List[(ResourceUser, Box[List[Entitlement]], Option[List[UserAgreement]])]]
def createResourceUser(provider: String,
providerId: Option[String],
createdByConsentId: Option[String],
name: Option[String],
email: Option[String],
userId: Option[String],
createdByUserInvitationId: Option[String],
company: Option[String],
lastMarketingAgreementSignedDate: Option[Date]) : Box[ResourceUser]
def createUnsavedResourceUser(provider: String, providerId: Option[String], name: Option[String], email: Option[String], userId: Option[String]) : Box[ResourceUser]
@ -78,9 +91,11 @@ class RemotedataUsersCaseClasses {
case class getUserByUserNameFuture(userName : String)
case class getUserByEmail(email : String)
case class getUserByEmailFuture(email : String)
case class getUsersByEmail(email : String)
case class getAllUsers()
case class getAllUsersF(queryParams: List[OBPQueryParam])
case class createResourceUser(provider: String, providerId: Option[String],createdByConsentId: Option[String], name: Option[String], email: Option[String], userId: Option[String], createdByUserInvitationId: Option[String], company: Option[String])
case class getUsers(queryParams: List[OBPQueryParam])
case class createResourceUser(provider: String, providerId: Option[String],createdByConsentId: Option[String], name: Option[String], email: Option[String], userId: Option[String], createdByUserInvitationId: Option[String], company: Option[String], lastMarketingAgreementSignedDate: Option[Date])
case class createUnsavedResourceUser(provider: String, providerId: Option[String], name: Option[String], email: Option[String], userId: Option[String])
case class saveResourceUser(resourceUser: ResourceUser)
case class deleteResourceUser(userId: Long)

View File

@ -1,76 +1,76 @@
package code.yearlycustomercharges
import code.util.UUIDString
import com.openbankproject.commons.model.{BankId, CustomerId}
import net.liftweb.common.Box
import net.liftweb.mapper._
object MappedYearlyChargeProvider extends YearlyChargeProvider {
// override protected def getYearlyChargeFromProvider(thingId: YearlyChargeId): Option[YearlyCharge] =
// MappedYearlyCharge.find(By(MappedYearlyCharge.thingId_, thingId.value))
override protected def getYearlyChargesFromProvider(bankId: BankId, customerId: CustomerId, year: Int): Option[List[YearlyCharge]] = {
Some(MappedYearlyCharge.findAll(By(MappedYearlyCharge.bankId_, bankId.value), By(MappedYearlyCharge.customerId_, customerId.value)))
}
}
class MappedYearlyCharge extends YearlyCharge with LongKeyedMapper[MappedYearlyCharge] with IdPK {
override def getSingleton = MappedYearlyCharge
object bankId_ extends UUIDString(this)
object customerId_ extends UUIDString(this)
object year_ extends MappedInt(this)
//override def yearlyChargeId: YearlyChargeId = YearlyChargeId(id.get)
override def year: Int = year_.get
// override def getSingleton = MappedYearlyCustomerCharge
//
// WIP
// object mCustomerNumber extends MappedString(this,123)
//
// object mYear extends MappedInt(this)
//
// object mCategoryId extends UUIDString(this)
// object mForcastIndictor extends MappedString(this,123)
// object mTypeId extends MappedString(this,123)
// object mNatureId extends UUIDString(this)
//
//
// object mCharge_Currency extends MappedString(this,3)
// object mCharge_Amount extends MappedString(this,32)
//
// object mUpdateDate extends MappedDateTime(this)
//
//
// //override def bankId: String = mBankId.get
// override def customerId: String = mCustomerId.get // id.toString
// override def customerNumber: String = mCustomerNumber.get
// override def year: Integer = mYear.get
//
// override def categoryId: String = mCategoryId.get
// override def forcastIndictor: String = mForcastIndictor.get
// override def typeId: String = mTypeId.get
// override def natureId : String = mNatureId.get
//
// override def charge: AmountOfMoney = AmountOfMoney(mCharge_Currency.get, mCharge_Amount.get)
// override def updateDate : Date = mUpdateDate.get
}
object MappedYearlyCharge extends MappedYearlyCharge with LongKeyedMetaMapper[MappedYearlyCharge] {
override def dbIndexes = UniqueIndex(bankId_, customerId_, year_) :: Index(bankId_) :: super.dbIndexes
}
//package code.yearlycustomercharges
//
//import code.util.UUIDString
//import com.openbankproject.commons.model.{BankId, CustomerId}
//import net.liftweb.common.Box
//import net.liftweb.mapper._
//
//
//
//object MappedYearlyChargeProvider extends YearlyChargeProvider {
//
//// override protected def getYearlyChargeFromProvider(thingId: YearlyChargeId): Option[YearlyCharge] =
//// MappedYearlyCharge.find(By(MappedYearlyCharge.thingId_, thingId.value))
//
// override protected def getYearlyChargesFromProvider(bankId: BankId, customerId: CustomerId, year: Int): Option[List[YearlyCharge]] = {
// Some(MappedYearlyCharge.findAll(By(MappedYearlyCharge.bankId_, bankId.value), By(MappedYearlyCharge.customerId_, customerId.value)))
// }
//}
//
//class MappedYearlyCharge extends YearlyCharge with LongKeyedMapper[MappedYearlyCharge] with IdPK {
//
// override def getSingleton = MappedYearlyCharge
//
// object bankId_ extends UUIDString(this)
// object customerId_ extends UUIDString(this)
//
// object year_ extends MappedInt(this)
//
//
//
// //override def yearlyChargeId: YearlyChargeId = YearlyChargeId(id.get)
// override def year: Int = year_.get
//
//
// // override def getSingleton = MappedYearlyCustomerCharge
// //
// // WIP
// // object mCustomerNumber extends MappedString(this,123)
// //
// // object mYear extends MappedInt(this)
// //
// // object mCategoryId extends UUIDString(this)
// // object mForcastIndictor extends MappedString(this,123)
// // object mTypeId extends MappedString(this,123)
// // object mNatureId extends UUIDString(this)
// //
// //
// // object mCharge_Currency extends MappedString(this,3)
// // object mCharge_Amount extends MappedString(this,32)
// //
// // object mUpdateDate extends MappedDateTime(this)
// //
// //
// // //override def bankId: String = mBankId.get
// // override def customerId: String = mCustomerId.get // id.toString
// // override def customerNumber: String = mCustomerNumber.get
// // override def year: Integer = mYear.get
// //
// // override def categoryId: String = mCategoryId.get
// // override def forcastIndictor: String = mForcastIndictor.get
// // override def typeId: String = mTypeId.get
// // override def natureId : String = mNatureId.get
// //
// // override def charge: AmountOfMoney = AmountOfMoney(mCharge_Currency.get, mCharge_Amount.get)
// // override def updateDate : Date = mUpdateDate.get
//
//
//
//
//}
//
//
//object MappedYearlyCharge extends MappedYearlyCharge with LongKeyedMetaMapper[MappedYearlyCharge] {
// override def dbIndexes = UniqueIndex(bankId_, customerId_, year_) :: Index(bankId_) :: super.dbIndexes
//}
//

View File

@ -1,71 +1,71 @@
package code.yearlycustomercharges
import code.api.util.APIUtil
import com.openbankproject.commons.model.{BankId, CustomerId}
import net.liftweb.common.Logger
import net.liftweb.util.SimpleInjector
object YearlyCharge extends SimpleInjector {
val yearlyChargeProvider = new Inject(buildOne _) {}
// This determines the provider we use
def buildOne: YearlyChargeProvider =
APIUtil.getPropsValue("provider.thing").openOr("mapped") match {
case "mapped" => MappedYearlyChargeProvider
case _ => MappedYearlyChargeProvider
}
}
case class YearlyChargeId(value : String)
// WIP
trait YearlyCharge {
def year : Int
// def customerNumber : String
//
//
// def categoryId : String
// def forcastIndictor : String
// def typeId : String
// def natureId : String
// def charge : AmountOfMoney
// def updateDate : Date
}
trait YearlyChargeProvider {
private val logger = Logger(classOf[YearlyChargeProvider])
/*
Common logic for returning or changing Things
Datasource implementation details are in Thing provider
*/
final def getYearlyCharges(bankId : BankId, customerId: CustomerId, year: Int) : Option[List[YearlyCharge]] = {
getYearlyChargesFromProvider(bankId, customerId, year) match {
case Some(things) => {
val certainThings = for {
thing <- things // if filter etc. if need be
} yield thing
Option(certainThings)
}
case None => None
}
}
protected def getYearlyChargesFromProvider(bank : BankId, customerId: CustomerId, year: Int) : Option[List[YearlyCharge]]
}
//package code.yearlycustomercharges
//
//import code.api.util.APIUtil
//import com.openbankproject.commons.model.{BankId, CustomerId}
//import net.liftweb.common.Logger
//import net.liftweb.util.SimpleInjector
//
//object YearlyCharge extends SimpleInjector {
//
// val yearlyChargeProvider = new Inject(buildOne _) {}
//
//
// // This determines the provider we use
// def buildOne: YearlyChargeProvider =
// APIUtil.getPropsValue("provider.thing").openOr("mapped") match {
// case "mapped" => MappedYearlyChargeProvider
// case _ => MappedYearlyChargeProvider
// }
//
//}
//
//case class YearlyChargeId(value : String)
//
//// WIP
//trait YearlyCharge {
// def year : Int
//
// // def customerNumber : String
// //
// //
// // def categoryId : String
// // def forcastIndictor : String
// // def typeId : String
// // def natureId : String
// // def charge : AmountOfMoney
// // def updateDate : Date
//
//
//
//}
//
//
//
//
//trait YearlyChargeProvider {
//
// private val logger = Logger(classOf[YearlyChargeProvider])
//
//
// /*
// Common logic for returning or changing Things
// Datasource implementation details are in Thing provider
// */
// final def getYearlyCharges(bankId : BankId, customerId: CustomerId, year: Int) : Option[List[YearlyCharge]] = {
// getYearlyChargesFromProvider(bankId, customerId, year) match {
// case Some(things) => {
//
// val certainThings = for {
// thing <- things // if filter etc. if need be
// } yield thing
// Option(certainThings)
// }
// case None => None
// }
// }
//
//
// protected def getYearlyChargesFromProvider(bank : BankId, customerId: CustomerId, year: Int) : Option[List[YearlyCharge]]
//
//}
//

Some files were not shown because too many files have changed in this diff Show More