Merge pull request #1990 from OpenBankProject/develop

regular code cycle
This commit is contained in:
tesobe-daniel 2021-12-13 13:22:07 +01:00 committed by GitHub
commit e61560bbbc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
122 changed files with 4548 additions and 7034 deletions

View File

@ -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>

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,7 +372,8 @@ 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

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 = #
@ -420,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.
@ -430,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 =
@ -619,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
@ -633,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
@ -646,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
@ -664,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)
@ -787,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 -----------------------------------------------------
@ -1074,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

@ -403,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)
@ -424,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 }")
}
@ -510,6 +521,7 @@ 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",

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

@ -96,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)
}
@ -285,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
)
}
}

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

@ -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.{BankAttributeBankResponseJsonV400, _}
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(
@ -1206,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
@ -1320,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")
@ -1701,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
)
@ -1965,6 +1992,9 @@ object SwaggerDefinitionsJSON {
val usersJsonV200 = UsersJsonV200(
users = List(userJsonV200)
)
val usersJsonV400 = UsersJsonV400(
users = List(userJsonV400)
)
val counterpartiesJSON = CounterpartiesJSON(
counterparties = List(coreCounterpartyJSON)
@ -3136,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)
@ -3150,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)
@ -3163,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,
@ -3474,7 +3521,7 @@ object SwaggerDefinitionsJSON {
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",
@ -3482,7 +3529,7 @@ object SwaggerDefinitionsJSON {
)
val productAttributeResponseJsonV400 = ProductAttributeResponseJsonV400(
bank_id = bankIdExample.value,
product_code = "saving1",
product_code = productCodeExample.value,
product_attribute_id = "613c83ea-80f9-4560-8404-b9cd4ec42a7f",
name = "OVERDRAFT_START_DATE",
`type` = "DATE_WITH_DAY",
@ -3490,7 +3537,7 @@ object SwaggerDefinitionsJSON {
is_active = Some(true)
)
val productAttributeResponseWithoutBankIdJsonV400 = ProductAttributeResponseWithoutBankIdJsonV400(
product_code = "saving1",
product_code = productCodeExample.value,
product_attribute_id = "613c83ea-80f9-4560-8404-b9cd4ec42a7f",
name = "OVERDRAFT_START_DATE",
`type` = "DATE_WITH_DAY",
@ -3520,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",
@ -3541,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,
@ -3562,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",
@ -3579,7 +3626,7 @@ object SwaggerDefinitionsJSON {
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))
@ -3765,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),
@ -3801,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,
@ -3829,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(
@ -3972,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)
@ -4031,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"))
@ -4198,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)
@ -4218,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,
@ -4426,6 +4493,17 @@ object SwaggerDefinitionsJSON {
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

@ -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" );

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

@ -33,6 +33,7 @@ import java.nio.charset.Charset
import java.text.{ParsePosition, SimpleDateFormat}
import java.util.concurrent.ConcurrentHashMap
import java.util.{Calendar, Date, UUID}
import code.UserRefreshes.UserRefreshes
import code.accountholders.AccountHolders
import code.api.Constant._
@ -44,12 +45,10 @@ 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)
}
@ -1659,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
@ -2242,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"
*
@ -2706,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)))
@ -2789,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.
@ -2989,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))
@ -3848,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._
@ -3911,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)
}
)

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

@ -821,6 +821,9 @@ object ApiRole {
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. "
@ -372,6 +386,8 @@ object ErrorMessages {
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."
@ -396,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"
@ -486,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}."
@ -521,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."
@ -564,6 +584,7 @@ object ErrorMessages {
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
@ -100,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)
@ -819,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)
@ -1194,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)
@ -1302,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)
@ -1602,7 +1609,7 @@ object ExampleValue {
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)
@ -2055,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

@ -635,10 +635,12 @@ object Glossary extends MdcLoggable {
title = "Bank",
description =
"""
|A Bank (aka Space) represents a financial institution, brand or organisaitonal unit under which resources such as endpoints and entities exist.
|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.
@ -1842,10 +1844,10 @@ object Glossary extends MdcLoggable {
|# 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
|
@ -1879,15 +1881,15 @@ object Glossary extends MdcLoggable {
| 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
|```
|
|
@ -1922,8 +1924,8 @@ object Glossary extends MdcLoggable {
|
|```
|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
|```
|
|
@ -1960,12 +1962,12 @@ object Glossary extends MdcLoggable {
|```
|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,
@ -1978,7 +1980,7 @@ object Glossary extends MdcLoggable {
|}
|
|
|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'
@ -2014,6 +2016,171 @@ object Glossary extends MdcLoggable {
""")
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)",
@ -2150,7 +2317,7 @@ object Glossary extends MdcLoggable {
glossaryItems += GlossaryItem(
title = "API Collections",
title = "API Collection",
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.
@ -2186,28 +2353,48 @@ object Glossary extends MdcLoggable {
glossaryItems += GlossaryItem(
title = "Dynamic Entity",
title = "Dynamic Entity Manage",
description =
s"""If you want to create, store and custom data in OBP, you can create "Dynamic Entities".
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.
|
|Upon successful creation of a Dynamic Entity, OBP automatically:
|When creating a Dynamic Entity, OBP automatically:
|
|*Creates Create, Read, Update and Delete endpoints to operate on the Entity so you can insert, get, modify and delete records.
|*Creates Roles to guard the above endpoints.
|* 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.
|
|Each Dynamic Entity gets a dynamicEntityId which uniquely identifies it and the userId which identifies the user who created the Entity.
|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.
|
|For more information see the endpoints.
|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:
|
@ -2216,6 +2403,314 @@ object Glossary extends MdcLoggable {
|
""".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
@ -408,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)
@ -824,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 {
@ -1025,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,
@ -3064,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

@ -84,6 +84,10 @@ object Migration extends MdcLoggable {
populateTheFieldIsActiveAtProductAttribute(startedBeforeSchemifier)
alterColumnUsernameProviderFirstnameAndLastnameAtAuthUser(startedBeforeSchemifier)
alterColumnEmailAtResourceUser(startedBeforeSchemifier)
alterColumnNameAtProductFee(startedBeforeSchemifier)
addFastFirehoseAccountsView(startedBeforeSchemifier)
addFastFirehoseAccountsMaterializedView(startedBeforeSchemifier)
alterUserAuthContextColumnKeyAndValueLength(startedBeforeSchemifier)
}
private def dummyScript(): Boolean = {
@ -283,6 +287,52 @@ object Migration extends MdcLoggable {
}
}
}
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,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,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

@ -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

@ -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))

View File

@ -1450,7 +1450,6 @@ trait APIMethods310 {
List(
UserNotLoggedIn,
UserHasMissingRoles,
CreateUserAuthContextError,
UnknownError
),
List(apiTagUser, apiTagNewStyle),

File diff suppressed because it is too large Load Diff

View File

@ -28,32 +28,33 @@ 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._
import code.api.v3_0_0._
import code.api.v3_1_0.JSONFactory310.{createAccountAttributeJson, createProductAttributesJson}
import code.api.v3_1_0.{AccountAttributeResponseJson, ProductAttributeResponseWithoutBankIdJson, RedisCallLimitJson}
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 code.users.{UserAgreement, UserInvitation}
import com.openbankproject.commons.model.{DirectDebitTrait, ProductFeeTrait, _}
import net.liftweb.common.{Box, Full}
import net.liftweb.json.JValue
@ -126,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)
@ -200,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,
@ -312,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)
@ -637,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]
@ -645,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 (
@ -887,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,
@ -895,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,
@ -909,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
)
)
)
}
@ -1127,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 = {
@ -1402,6 +1486,7 @@ object JSONFactory400 {
apiCollection.userId,
apiCollection.apiCollectionName,
apiCollection.isSharable,
apiCollection.description
)
}
def createIbanCheckerJson(iban: IbanChecker): IbanCheckerJsonV400 = {
@ -1671,5 +1756,37 @@ object JSONFactory400 {
))))
)
}
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

@ -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

@ -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

@ -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,8 +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
@ -45,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
@ -503,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)

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}
@ -48,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._
@ -95,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._
@ -799,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 {

View File

@ -37,10 +37,12 @@ import code.api.cache.Caching
import code.api.util.APIUtil.{AdapterImplementation, MessageDoc, OBPReturnType, saveConnectorMetric, _}
import code.api.util.ErrorMessages._
import code.api.util.ExampleValue._
import code.api.util.RSAUtil.{computeXSign, getPrivateKeyFromString}
import code.api.util.{APIUtil, CallContext, OBPQueryParam}
import code.api.v4_0_0.dynamic.MockResponseHolder
import code.bankconnectors._
import code.bankconnectors.vJune2017.AuthInfo
import code.context.UserAuthContextProvider
import code.customer.internalMapping.MappedCustomerIdMappingProvider
import code.kafka.KafkaHelper
import code.model.dataAccess.internalMapping.MappedAccountIdMappingProvider
@ -63,6 +65,7 @@ import net.liftweb.json.{JValue, _}
import net.liftweb.util.Helpers.tryo
import org.apache.commons.lang3.StringUtils
import java.time.Instant
import scala.collection.immutable.List
import scala.collection.mutable.ArrayBuffer
import scala.concurrent.duration._
@ -6522,7 +6525,7 @@ trait RestConnector_vMar2019 extends Connector with KafkaHelper with MdcLoggable
}
val jsonToSend = if(jValue == JNothing) "" else compactRender(jValue)
val request = prepareHttpRequest(paramUrl, method, HttpProtocol("HTTP/1.1"), jsonToSend).withHeaders(callContext)
val request = prepareHttpRequest(paramUrl, method, HttpProtocol("HTTP/1.1"), jsonToSend).withHeaders(buildHeaders(paramUrl,jsonToSend,callContext))
logger.debug(s"RestConnector_vMar2019 request is : $request")
val responseFuture = makeHttpRequest(request)
@ -6572,15 +6575,37 @@ trait RestConnector_vMar2019 extends Connector with KafkaHelper with MdcLoggable
//In RestConnector, we use the headers to propagate the parameters to Adapter. The parameters come from the CallContext.outboundAdapterAuthInfo.userAuthContext
//We can set them from UserOauthContext or the http request headers.
private[this] implicit def buildHeaders(callContext: Option[CallContext]): List[HttpHeader] = {
private[this] def buildHeaders(
uri: String,
entityJsonString: String,
callContext: Option[CallContext]
): List[HttpHeader] = {
val generalContext = callContext.flatMap(_.toOutboundAdapterCallContext.generalContext).getOrElse(List.empty[BasicGeneralContext])
val needSignatureHead = APIUtil.getPropsAsBoolValue("rest_connector_sends_x-sign_header", false)
val generalContext = callContext.map(createBasicUserAuthContextJsonFromCallContext(_)).getOrElse(List.empty[BasicGeneralContext])
val headersFromGeneralContext = generalContext.map(generalContext => RawHeader(generalContext.key,generalContext.value))
val basicUserAuthContexts: List[BasicUserAuthContext] = callContext.flatMap(_.toOutboundAdapterCallContext.outboundAdapterAuthInfo.flatMap(_.userAuthContext)).getOrElse(List.empty[BasicUserAuthContext])
val headersFromUserAuthContext = basicUserAuthContexts.map(userAuthContext => RawHeader(userAuthContext.key,userAuthContext.value))
val basicUserAuthContexts = UserAuthContextProvider.userAuthContextProvider.vend.getUserAuthContextsBox(callContext.map(_.userId).getOrElse("")).getOrElse(List.empty[UserAuthContext])
val headersFromUserAuthContext = basicUserAuthContexts.filterNot(_.key == "private-key").map(userAuthContext =>RawHeader(userAuthContext.key,userAuthContext.value))
headersFromGeneralContext++headersFromUserAuthContext
val timeStamp = Instant.now.getEpochSecond.toString
logger.debug(s"x-timestamp: $timeStamp")
val extraHeaders = if(needSignatureHead){
val inputMessage = s"""${timeStamp}${uri}${entityJsonString}"""
val privateKeyValue = basicUserAuthContexts.find(_.key =="private-key").map(_.value).getOrElse("")
val privateKey = getPrivateKeyFromString(privateKeyValue)
val xSign = computeXSign(inputMessage, privateKey)
logger.debug(s"x-sign: $xSign")
List(RawHeader("x-timestamp",timeStamp),RawHeader("x-sign",xSign))
} else {
List(RawHeader("x-timestamp",timeStamp))
}
val headers = headersFromUserAuthContext++extraHeaders++headersFromGeneralContext
logger.debug(s"obp headers: ${headers}")
headers
}
@ -6686,7 +6711,7 @@ trait RestConnector_vMar2019 extends Connector with KafkaHelper with MdcLoggable
compactRender(builtJson)
case _ => net.liftweb.json.Serialization.write(outBound)
}
val request = prepareHttpRequest(url, method, HttpProtocol("HTTP/1.1"), outBoundJson).withHeaders(callContext)
val request = prepareHttpRequest(url, method, HttpProtocol("HTTP/1.1"), outBoundJson).withHeaders(buildHeaders(url, outBoundJson, callContext))
logger.debug(s"RestConnector_vMar2019 request is : $request")
val responseFuture = makeHttpRequest(request)
responseFuture.map {

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

@ -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, validatePasswordOnCreation, 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
@ -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._]+(?<![_.])$
@ -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)))
}
@ -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))
}
}
}
}

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}
@ -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
@ -109,6 +112,7 @@ 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.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

@ -1,6 +1,7 @@
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}
@ -54,12 +55,12 @@ object MappedProductFeeProvider extends ProductFeeProvider {
.Frequency(frequency)
.Type(`type`)
.saveMe()
}
} ?~! s"$UpdateProductFeeError"
case _ => Empty
}
}
case None => Future {
Full {
tryo {
ProductFee
.create
.ProductFeeId(APIUtil.generateUUID)
@ -73,7 +74,7 @@ object MappedProductFeeProvider extends ProductFeeProvider {
.Frequency(frequency)
.Type(`type`)
.saveMe()
}
} ?~! s"$CreateProductFeeError"
}
}
}
@ -95,7 +96,7 @@ class ProductFee extends ProductFeeTrait with LongKeyedMapper[ProductFee] with I
object ProductFeeId extends UUIDString(this)
object Name extends MappedString(this, 50)
object Name extends MappedString(this, 100)
object IsActive extends MappedBoolean(this) {
override def defaultValue = true

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

@ -16,4 +16,7 @@ object RemotedataUserAgreement extends ObpActorInit with UserAgreementProvider {
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

@ -20,6 +20,10 @@ class RemotedataUserAgreementActor extends Actor with ObpActorHelper with MdcLog
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

@ -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, Full}
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,6 +54,8 @@ 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)
@ -61,6 +64,7 @@ class UserInvitation extends MdcLoggable {
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 = {
@ -76,7 +80,12 @@ class UserInvitation extends MdcLoggable {
countryVar.set(userInvitation.map(_.country).getOrElse("None"))
// 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))
@ -91,39 +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, "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)
// 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
@ -149,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" #> {
@ -161,9 +182,11 @@ class UserInvitation extends MdcLoggable {
"#privacy_checkbox" #> SHtml.checkbox(privacyCheckboxVar, privacyCheckboxVar(_)) &
"#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
@ -181,18 +204,24 @@ class UserInvitation extends MdcLoggable {
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,
@ -200,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,
@ -210,7 +240,8 @@ class UserInvitation extends MdcLoggable {
email = email,
userId = None,
createdByUserInvitationId = userInvitationId,
company = company
company = company,
lastMarketingAgreementSignedDate = lastMarketingAgreementSignedDate
)
}

View File

@ -142,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"
@ -177,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 = {
@ -325,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://", "")
}

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

@ -43,6 +43,12 @@ object MappedUserAgreementProvider extends UserAgreementProvider {
.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 {
@ -64,6 +70,7 @@ class UserAgreement extends UserAgreementTrait with LongKeyedMapper[UserAgreemen
override def agreementType: String = AgreementType.get
override def agreementText: String = AgreementText.get
override def agreementHash: String = AgreementHash.get
override def date: Date = Date.get
}
object UserAgreement extends UserAgreement with LongKeyedMetaMapper[UserAgreement] {

View File

@ -1,5 +1,7 @@
package code.users
import java.util.Date
import code.api.util.APIUtil
import code.remotedata.RemotedataUserAgreement
import net.liftweb.common.Box
@ -21,11 +23,13 @@ object UserAgreementProvider extends SimpleInjector {
trait UserAgreementProvider {
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, agreementType: String, agreementText: String)
case class createUserAgreement(userId: String, agreementType: String, agreementText: String)
case class getUserAgreement(userId: String, agreementType: String)
}
object RemotedataUserAgreementProviderCaseClass extends RemotedataUserAgreementProviderCaseClass
@ -36,4 +40,5 @@ trait UserAgreementTrait {
def agreementType: String
def agreementText: String
def agreementHash: String
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]]
//
//}
//

View File

@ -1,6 +1,14 @@
drop view v_resource_user cascade;
create or replace view v_resource_user as select userid_ resource_user_id, name_ username, email, id numeric_resource_user_id, provider_ provider, providerid provider_id from resourceuser;
create or replace view v_resource_user as
select
userid_ resource_user_id,
name_ username,
email,
id numeric_resource_user_id,
provider_ provider,
providerid provider_id
from resourceuser;
drop view v_auth_user cascade;
create view v_auth_user as

View File

@ -104,8 +104,8 @@ Berlin 13359, Germany
<div class="collapse" id="collapse_signing_alg">
<div class="well">
The signing algorithm name of request object and client_assertion.
Reference <a target="_blank" href="https://openid.net/specs/openid-connect-core-1_0.html#RequestObject">6.1. Passing a Request Object by Value</a>
and <a target="_blank" href="https://openid.net/specs/openid-connect-core-1_0.html#ClientAuthentication">9. Client Authentication</a>
Reference <a target="_blank" href="https://openid.net/specs/openid-connect-core-1_0.html#RequestObject" rel="noopener">6.1. Passing a Request Object by Value</a>
and <a target="_blank" href="https://openid.net/specs/openid-connect-core-1_0.html#ClientAuthentication" rel="noopener">9. Client Authentication</a>
</div>
</div>
<select name="app-signing_alg" id="app-signing_alg" class="form-control js-example-basic-single">
@ -116,7 +116,7 @@ Berlin 13359, Germany
</div>
<div class="form-group">
<label for="app-request_uri" id="request_uri_label">
request_uri (Optional), <a href="https://openid.net/specs/openid-connect-core-1_0.html#RequestUriParameter" target="_blank">reference</a>
request_uri (Optional), <a href="https://openid.net/specs/openid-connect-core-1_0.html#RequestUriParameter" target="_blank" rel="noopener">reference</a>
</label>
<input type="url" name="app-request_uri" id="app-request_uri" class="form-control">
<div id = "consumer-registration-app-request_uri-error-div" class="hide">
@ -125,7 +125,7 @@ Berlin 13359, Germany
</div>
<div class="form-group">
<label for="app-jwks_uri" id="request_jwks_uri">
jwks_uri (Optional), <a href="https://openid.net/specs/openid-connect-core-1_0.html#RotateSigKeys" target="_blank">reference</a>
jwks_uri (Optional), <a href="https://openid.net/specs/openid-connect-core-1_0.html#RotateSigKeys" target="_blank" rel="noopener">reference</a>
</label>
<input type="url" name="app-jwks_uri" id="app-jwks_uri" class="form-control">
</div>
@ -137,8 +137,8 @@ Berlin 13359, Germany
</label>
<div class="collapse" id="collapse_jwks">
<div class="well">
Content of <b>jwks_uri</b>. <b>jwks_uri</b> and <b>jwks</b> should not both have value at the same time.
Reference <a target="_blank" href="https://openid.net/specs/openid-connect-core-1_0.html#RotateSigKeys">10.1.1. Rotation of Asymmetric Signing Keys</a>
Content of <strong>jwks_uri</strong>. <strong>jwks_uri</strong> and <strong>jwks</strong> should not both have value at the same time.
Reference <a target="_blank" href="https://openid.net/specs/openid-connect-core-1_0.html#RotateSigKeys" rel="noopener">10.1.1. Rotation of Asymmetric Signing Keys</a>
</div>
</div>
<textarea rows="4" name="app-jwks" id="app-jwks" class="form-control" placeholder="Content of jwks"></textarea>
@ -217,7 +217,7 @@ Berlin 13359, Germany
</div>
<div class="row" id="dummy-user-tokens">
<div class="col-xs-12 col-sm-4">Dummy Users' Direct Login Tokens</div>
<div class="col-xs-12 col-sm-8"><span id="create-directlogin"><a target="_blank" href="#">Get dummy users' token</a></span></div>
<div class="col-xs-12 col-sm-8"><span id="create-directlogin"><a target="_blank" href="#" rel="noopener">Get dummy users' token</a></span></div>
</div>
<div class="row">
<div class="col-xs-12 col-sm-4">Direct Login Endpoint</div>

View File

@ -255,166 +255,7 @@ Berlin 13359, Germany
</div>
<div id="main-faq">
<h2 id="technical-faqs">FAQs</h2>
<div class="row">
<div class="col-xs-12 col-sm-6 main-faq-left" onclick="mouseClickMainMaq(this)">
<div data-toggle="collapse" data-target="#main-faq-item0" class="collapsed main-faq-button">
<hr/>
<h3>
<button aria-expanded="false" onclick="mouseClick(this)" onKeyDown="EnterKeyPressed(this)">
What is the correct base URL for this sandbox?
</button>
</h3>
<p id="main-faq-item0" class="collapse">
The base URL is<br/>
<a class="api-link" data-lift="WebUI.apiLink"
href="">http://apisandbox.openbankproject.com</a><br/>
Please make sure you are using this in all your API calls
</p>
<img src="/media/images/icons/chevron_down_thick.svg" alt="">
</div>
</div>
<div class="col-xs-12 col-sm-6 main-faq-right" onclick="mouseClickMainMaq(this)">
<div data-toggle="collapse" data-target="#main-faq-item1" class="collapsed main-faq-button">
<hr/>
<h3>
<button aria-expanded="false" onclick="mouseClick(this)" onKeyDown="EnterKeyPressed(this)">
I got a 404 error, what am I doing wrong
</button>
</h3>
<ol id="main-faq-item1" class="collapse">
<li>
Avoid using trailing slashes, else, you would get a 404 error. Example:<br/>
.../obp/v1.4.0 200 OK<br/>
.../obp/v1.4.0/ 404 Not Found<br/>
</li>
<li>Double check parameters are spelt correctly (including http vs https etc.)</li>
<li>Check your encoding (use UTF8)</li>
</ol>
<img src="/media/images/icons/chevron_down_thick.svg" alt="">
</div>
</div>
</div>
<div class="row">
<div class="col-xs-12 col-sm-6 main-faq-left" onclick="mouseClickMainMaq(this)">
<div data-toggle="collapse" data-target="#main-faq-item2" class="collapsed main-faq-button">
<hr/>
<h3>
<button aria-expanded="false" onclick="mouseClick(this)" onKeyDown="EnterKeyPressed(this)">
How should I login?
</button>
</h3>
<p id="main-faq-item2" class="collapse">
There are two ways to authenticate a user: <a
href="https://github.com/OpenBankProject/OBP-API/wiki/OAuth-Client-SDKS">OAuth</a> and <a
href="https://github.com/OpenBankProject/OBP-API/wiki/Direct-Login">Direct Login</a>. If you
are using this sandbox for a hackathon, we recommend you use Direct Login to authenticate as it
is easier than the OAuth workflow.
</p>
<img src="/media/images/icons/chevron_down_thick.svg" alt="">
</div>
</div>
<div class="col-xs-12 col-sm-6 main-faq-right" onclick="mouseClickMainMaq(this)">
<div data-toggle="collapse" data-target="#main-faq-item3" class="collapsed main-faq-button">
<hr/>
<h3>
<button aria-expanded="false" onclick="mouseClick(this)" onKeyDown="EnterKeyPressed(this)">
How can I use OAuth or Direct Login?
</button>
</h3>
<p id="main-faq-item3" class="collapse">
If you want to use OBP with OAuth, we recommend you use (and fork) one of our <a
href="https://github.com/OpenBankProject/OBP-API/wiki/OAuth-Client-SDKS">OAuth Starter
SDKs</a>.
If you are using this sandbox for a hackathon, we recommend you use <a
class="direct-login-documentation-url" data-lift="WebUI.directLoginDocumentationUrl"
href="">Direct Login</a>.
For an OAuth walkthrough example with sample code, please see <a href="https://github.com/OpenBankProject/Hello-OBP-OAuth1.0a-Python">here</a>.
We use OAuth 1.0a. For deepish technical details of the flow <a
class="oauth-1-documentation-url" data-lift="WebUI.oauth1DocumentationUrl" href="">see
here.</a><br/>
We also support OAuth 2.0. For the technical details of using OBP API with OAuth 2.0, please <a
class="oauth-2-documentation-url" data-lift="WebUI.oauth2DocumentationUrl" href="">see
here.</a><br/>
</p>
<img src="/media/images/icons/chevron_down_thick.svg" alt="">
</div>
</div>
</div>
<div class="row">
<div class="col-xs-12 col-sm-6 main-faq-left" onclick="mouseClickMainMaq(this)">
<div data-toggle="collapse" data-target="#main-faq-item4" class="collapsed main-faq-button">
<hr/>
<h3>
<button aria-expanded="false" onclick="mouseClick(this)" onKeyDown="EnterKeyPressed(this)">
Where can I read the API documentation?
</button>
</h3>
<p id="main-faq-item4" class="collapse">
Please use the <a class="api-explorer-link" data-lift="WebUI.apiExplorerLink" href="">API
Explorer</a>
</p>
<img src="/media/images/icons/chevron_down_thick.svg" alt="">
</div>
</div>
<div class="col-xs-12 col-sm-6 main-faq-right" onclick="mouseClickMainMaq(this)">
<div data-toggle="collapse" data-target="#main-faq-item5" class="collapsed main-faq-button">
<hr/>
<h3>
<button aria-expanded="false" onclick="mouseClick(this)" onKeyDown="EnterKeyPressed(this)">
How can I use the example customer data?
</button>
</h3>
<p id="main-faq-item5" class="collapse">
You will need to login to the API as a sandbox customer. You can do this using the API Explorer,
or any REST client. You can find some example credentials <a
class="example_sandbox_credentials_link" data-lift="WebUI.exampleSandboxCredentialsLink"
href="https://github.com/OpenBankProject/OBP-API/wiki/">here</a>.</p>
<img src="/media/images/icons/chevron_down_thick.svg" alt="">
</div>
</div>
</div>
<div class="row">
<div class="col-xs-12 col-sm-6 main-faq-left" onclick="mouseClickMainMaq(this)">
<div data-toggle="collapse" data-target="#main-faq-item6" class="collapsed main-faq-button">
<hr/>
<h3>
<button aria-expanded="false" onclick="mouseClick(this)" onKeyDown="EnterKeyPressed(this)">
Where does the data come from?
</button>
</h3>
<p id="main-faq-item6" class="collapse faq-data-text" data-lift="WebUI.faqDataText">Text is
replaced</p>
<img src="/media/images/icons/chevron_down_thick.svg" alt="">
</div>
</div>
<div class="col-xs-12 col-sm-6 main-faq-right" onclick="mouseClickMainMaq(this)">
<div data-toggle="collapse" data-target="#main-faq-item7" class="collapsed main-faq-button">
<hr/>
<h3>
<button aria-expanded="false" onclick="mouseClick(this)" onKeyDown="EnterKeyPressed(this)">
Where can i get more information and support?
</button>
</h3>
<p id="main-faq-item7" class="collapse">
Please refer to our <a class="faq-link" data-lift="WebUI.faqLink" href=""
target="_blank">FAQ</a>, <a class="glossary-link"
data-lift="WebUI.glossaryLink" href=""
target="_blank">Glossary</a>, join our <a
class="support-platform-link" data-lift="WebUI.supportPlatformLink" href="" target="_blank">Slack
channels</a> or email us at <a class="faq-email" data-lift="WebUI.faqEmail" href="">contact@openbankproject.com</a>
</p>
<img src="/media/images/icons/chevron_down_thick.svg" alt="">
</div>
</div>
</div>
<hr>
<div id="main-faq" data-lift="WebUI.mainFaqHtml">
</div>
@ -447,9 +288,9 @@ Berlin 13359, Germany
<div class="col-xs-1" type="hidden"></div>
<div class="main-support-item col-xs-3">
<img class="support-slack" src="/media/images/icons/support/chat.svg" width="48" height="48" alt="slack"/>
<img class="support-Rocket-Chat" src="/media/images/icons/support/chat.svg" width="48" height="48" alt="Rocket-Chat"/>
<h3>Chat</h3>
<a class="support-platform-link" data-lift="WebUI.supportPlatformLink" href="" target="_blank">Slack
<a class="support-platform-link" data-lift="WebUI.supportPlatformLink" href="" target="_blank">Rocket-Chat
channel</a>
</div>
</div>
@ -484,6 +325,8 @@ Berlin 13359, Germany
href="">OBP CLI</a>
<a class="api-tester-link btn btn-default pull-left"
data-lift="WebUI.apiTesterLink" href="">API Tester</a>
<a class="api-hola-link btn btn-default pull-left"
data-lift="WebUI.apiHolaLink" href="">Hola</a>
</div>
</div>
</div>

View File

@ -0,0 +1,160 @@
<h2 id="technical-faqs">FAQs</h2>
<div class="row">
<div class="col-xs-12 col-sm-6 main-faq-left" onclick="mouseClickMainMaq(this)">
<div data-toggle="collapse" data-target="#main-faq-item0" class="collapsed main-faq-button">
<hr/>
<h3>
<button aria-expanded="false" onclick="mouseClick(this)" onKeyDown="EnterKeyPressed(this)">
What is the correct base URL for this sandbox?
</button>
</h3>
<p id="main-faq-item0" class="collapse">
The base URL is<br/>
<a class="api-link" data-lift="WebUI.apiLink"
href="">http://apisandbox.openbankproject.com</a><br/>
Please make sure you are using this in all your API calls
</p>
<img src="/media/images/icons/chevron_down_thick.svg" alt="">
</div>
</div>
<div class="col-xs-12 col-sm-6 main-faq-right" onclick="mouseClickMainMaq(this)">
<div data-toggle="collapse" data-target="#main-faq-item1" class="collapsed main-faq-button">
<hr/>
<h3>
<button aria-expanded="false" onclick="mouseClick(this)" onKeyDown="EnterKeyPressed(this)">
I got a 404 error, what am I doing wrong
</button>
</h3>
<ol id="main-faq-item1" class="collapse">
<li>
Avoid using trailing slashes, else, you would get a 404 error. Example:<br/>
.../obp/v1.4.0 200 OK<br/>
.../obp/v1.4.0/ 404 Not Found<br/>
</li>
<li>Double check parameters are spelt correctly (including http vs https etc.)</li>
<li>Check your encoding (use UTF8)</li>
</ol>
<img src="/media/images/icons/chevron_down_thick.svg" alt="">
</div>
</div>
</div>
<div class="row">
<div class="col-xs-12 col-sm-6 main-faq-left" onclick="mouseClickMainMaq(this)">
<div data-toggle="collapse" data-target="#main-faq-item2" class="collapsed main-faq-button">
<hr/>
<h3>
<button aria-expanded="false" onclick="mouseClick(this)" onKeyDown="EnterKeyPressed(this)">
How should I login?
</button>
</h3>
<p id="main-faq-item2" class="collapse">
There are two ways to authenticate a user: <a
href="https://github.com/OpenBankProject/OBP-API/wiki/OAuth-Client-SDKS">OAuth</a> and <a
href="https://github.com/OpenBankProject/OBP-API/wiki/Direct-Login">Direct Login</a>. If you
are using this sandbox for a hackathon, we recommend you use Direct Login to authenticate as it
is easier than the OAuth workflow.
</p>
<img src="/media/images/icons/chevron_down_thick.svg" alt="">
</div>
</div>
<div class="col-xs-12 col-sm-6 main-faq-right" onclick="mouseClickMainMaq(this)">
<div data-toggle="collapse" data-target="#main-faq-item3" class="collapsed main-faq-button">
<hr/>
<h3>
<button aria-expanded="false" onclick="mouseClick(this)" onKeyDown="EnterKeyPressed(this)">
How can I use OAuth or Direct Login?
</button>
</h3>
<p id="main-faq-item3" class="collapse">
If you want to use OBP with OAuth, we recommend you use (and fork) one of our <a
href="https://github.com/OpenBankProject/OBP-API/wiki/OAuth-Client-SDKS">OAuth Starter
SDKs</a>.
If you are using this sandbox for a hackathon, we recommend you use <a
class="direct-login-documentation-url" data-lift="WebUI.directLoginDocumentationUrl"
href="">Direct Login</a>.
For an OAuth walkthrough example with sample code, please see <a
href="https://github.com/OpenBankProject/Hello-OBP-OAuth1.0a-Python">here</a>.
We use OAuth 1.0a. For deepish technical details of the flow <a
class="oauth-1-documentation-url" data-lift="WebUI.oauth1DocumentationUrl" href="">see
here.</a><br/>
We also support OAuth 2.0. For the technical details of using OBP API with OAuth 2.0, please <a
class="oauth-2-documentation-url" data-lift="WebUI.oauth2DocumentationUrl" href="">see
here.</a><br/>
</p>
<img src="/media/images/icons/chevron_down_thick.svg" alt="">
</div>
</div>
</div>
<div class="row">
<div class="col-xs-12 col-sm-6 main-faq-left" onclick="mouseClickMainMaq(this)">
<div data-toggle="collapse" data-target="#main-faq-item4" class="collapsed main-faq-button">
<hr/>
<h3>
<button aria-expanded="false" onclick="mouseClick(this)" onKeyDown="EnterKeyPressed(this)">
Where can I read the API documentation?
</button>
</h3>
<p id="main-faq-item4" class="collapse">
Please use the <a class="api-explorer-link" data-lift="WebUI.apiExplorerLink" href="">API
Explorer</a>
</p>
<img src="/media/images/icons/chevron_down_thick.svg" alt="">
</div>
</div>
<div class="col-xs-12 col-sm-6 main-faq-right" onclick="mouseClickMainMaq(this)">
<div data-toggle="collapse" data-target="#main-faq-item5" class="collapsed main-faq-button">
<hr/>
<h3>
<button aria-expanded="false" onclick="mouseClick(this)" onKeyDown="EnterKeyPressed(this)">
How can I use the example customer data?
</button>
</h3>
<p id="main-faq-item5" class="collapse">
You will need to login to the API as a sandbox customer. You can do this using the API Explorer,
or any REST client. You can find some example credentials <a
class="example_sandbox_credentials_link" data-lift="WebUI.exampleSandboxCredentialsLink"
href="https://github.com/OpenBankProject/OBP-API/wiki/">here</a>.</p>
<img src="/media/images/icons/chevron_down_thick.svg" alt="">
</div>
</div>
</div>
<div class="row">
<div class="col-xs-12 col-sm-6 main-faq-left" onclick="mouseClickMainMaq(this)">
<div data-toggle="collapse" data-target="#main-faq-item6" class="collapsed main-faq-button">
<hr/>
<h3>
<button aria-expanded="false" onclick="mouseClick(this)" onKeyDown="EnterKeyPressed(this)">
Where does the data come from?
</button>
</h3>
<p id="main-faq-item6" class="collapse faq-data-text" data-lift="WebUI.faqDataText">Text is
replaced</p>
<img src="/media/images/icons/chevron_down_thick.svg" alt="">
</div>
</div>
<div class="col-xs-12 col-sm-6 main-faq-right" onclick="mouseClickMainMaq(this)">
<div data-toggle="collapse" data-target="#main-faq-item7" class="collapsed main-faq-button">
<hr/>
<h3>
<button aria-expanded="false" onclick="mouseClick(this)" onKeyDown="EnterKeyPressed(this)">
Where can i get more information and support?
</button>
</h3>
<p id="main-faq-item7" class="collapse">
Please refer to our <a class="faq-link" data-lift="WebUI.faqLink" href=""
target="_blank">FAQ</a>, <a class="glossary-link"
data-lift="WebUI.glossaryLink" href=""
target="_blank">Glossary</a>, join our <a
class="support-platform-link" data-lift="WebUI.supportPlatformLink" href="" target="_blank">Rocket-Chat
channels</a> or email us at <a class="faq-email" data-lift="WebUI.faqEmail" href="">contact@openbankproject.com</a>
</p>
<img src="/media/images/icons/chevron_down_thick.svg" alt="">
</div>
</div>
</div>
<hr>

View File

@ -48,7 +48,7 @@
font-size: 16px;
line-height: 24px;
font-weight:normal;
font-family: Roboto-Regular;
font-family: Roboto-Regular,sans-serif;
/*paragraph-height: 12px*/
}
@ -58,12 +58,12 @@
line-height: 24px;
font-weight:normal;
margin: 8px 0;
font-family: Roboto-Regular;
font-family: Roboto-Regular,sans-serif;
/*paragraph-height: 12px*/
}
.container #api_documentation_content a{
font-family: Roboto-Regular;
font-family: Roboto-Regular,sans-serif;
font-size: 16px;
color: #333333;
line-height: 24px;

View File

@ -5,7 +5,7 @@
}
#authorise h1 {
font-family: Roboto-Light;
font-family: Roboto-Light,sans-serif;
font-size: 28px;
color: #333333;
line-height: 36px;
@ -18,7 +18,7 @@
#login-form-password{
margin-bottom: 8px;
margin-top: 32px;
font-family: Roboto-Regular;
font-family: Roboto-Regular,sans-serif;
font-size: 16px;
color: #333333;
line-height: 24px;
@ -31,7 +31,7 @@
float: right;
height: 44px;
width: 92px;
font-family: Roboto-Regular;
font-family: Roboto-Regular,sans-serif;
font-size: 16px;
text-align: center;
line-height: 24px;
@ -73,7 +73,7 @@
}
#authorise #authorise-recover-password a,
#authorise #oauth-authorise-recover-password a{
font-family: Roboto-Regular;
font-family: Roboto-Regular,sans-serif;
font-size: 14px;
color: #333333;
letter-spacing: 0;
@ -84,7 +84,7 @@
#authorise .login-or {
margin-top: 30px;
float: left;
font-family: Roboto-Regular;
font-family: Roboto-Regular,sans-serif;
font-size: 18px;
color: #929292;
letter-spacing: 0;
@ -102,7 +102,7 @@
color: red;
}
#authorise #thanks-h1 {
font-family: Roboto-Light;
font-family: Roboto-Light,sans-serif;
font-size: 28px;
color: #333333;
letter-spacing: 0;
@ -110,7 +110,7 @@
}
#authorise #thanks-detail{
font-family: Roboto-Regular;
font-family: Roboto-Regular,sans-serif;
font-size: 16px;
color: #333333;
line-height: 24px;
@ -142,7 +142,7 @@
#authorise #login-form-username-error,
#authorise #login-form-password-error{
font-family: Roboto-Regular;
font-family: Roboto-Regular,sans-serif;
font-size: 14px;
color: #333333;
line-height: 20px;

View File

@ -1,6 +1,6 @@
#cookies-consent {
background: #252525;
font-family: Roboto-Regular;
font-family: Roboto-Regular,sans-serif;
font-size: 16px;
display:none;
text-align: center;
@ -17,7 +17,7 @@
position: absolute;
left: 100px;
top: 32px;
font-family: Roboto-Regular;
font-family: Roboto-Regular,sans-serif;
font-size: 16px;
color: #FFFFFF;
line-height: 24px;
@ -30,7 +30,7 @@
position: absolute;
left: 100px;
top: 80px;
font-family: Roboto-Regular;
font-family: Roboto-Regular,sans-serif;
font-size: 16px;
color: #FFFFFF;
text-align: center;

View File

@ -0,0 +1,151 @@
#data-area {
background-color: #53C4EF;
padding: 20px 10px;
color: white;
margin: 30px 0 90px;
}
#data-area #data-area-explanation,
#data-area form,
#data-area-errors,
#data-area-success {
max-width: 610px;
margin: 0 auto;
color: #333333;
}
#data-area-errors span{
color: black;
}
#data-area h1 {
font-family: Roboto-Light,sans-serif;
font-size: 28px;
color: #333333;
letter-spacing: 0;
line-height: 36px;
text-align: left;
margin-bottom: 32px;
font-weight: normal;
}
#data-area #data-area-explanation p:nth-child(2) {
font-family: Roboto-Light,sans-serif;
font-size: 22px;
color: #333333;
letter-spacing: 0;
line-height: 31px;
margin-bottom: 8px;
font-weight: normal;
text-align: left;
}
#data-area #data-area-explanation p:last-child{
margin-top: 8px;
font-family: Roboto-Regular,sans-serif;
font-size: 14px;
color: #333333;
letter-spacing: 0;
line-height: 20px;
margin-bottom: 32px;
}
#data-area-input form label{
margin-bottom: 8px;
margin-top: 17px;
font-family: Roboto-Regular,sans-serif;
font-size: 16px;
color: #333333;
line-height: 24px;
}
#data-area-input form select{
height: 44px;
border: 1px solid #767676;
-webkit-appearance: none;
-webkit-border-radius: 0px;
}
#data-area #data-area-explanation {
margin-top: 32px;
margin-bottom: 20px;
color: #333333;
padding: 0 15px;
}
#data-area textarea {
height: 96px;
border: 1px solid #767676;
border-radius: 0px;
}
#data-area #data-area-errors {
margin-bottom: 20px;
}
#data-area #data-area-success {
margin-top: 30px;
}
#data-area #data-area-success h1 {
padding-left: 15px;
}
#data-area #data-area-success #data-area-success-message{
font-family: Roboto-Light,sans-serif;
font-size: 22px;
color: #333333;
letter-spacing: 0;
line-height: 31px;
padding-left: 15px;
}
#data-area #data-area-success p {
font-family: Roboto-Light,sans-serif;
font-size: 22px;
color: #333333;
letter-spacing: 0;
margin-bottom: 44px;
line-height: 31px;
padding-left: 15px;
}
#data-area-success a{
text-decoration:underline;
}
#data-area-success span,
#data-area-success a{
font-family: Roboto-Regular,sans-serif;
font-size: 16px;
color: #333333;
line-height: 24px;
}
#data-area #data-area-success .row {
margin-bottom: 20px;
}
#data-area #data-area-success .row div:nth-child(1) {
font-family: Roboto-Medium,sans-serif;
font-size: 16px;
color: #333333;
line-height: 24px;
}
#data-area-input form .btn-danger{
margin-left: 0;
margin-top: 33px;
}
#data-area-input #data-area-errors-div{
margin-top: 8px;
}
#data-area-input #data-area-errors{
font-family: Roboto-Regular,sans-serif;
font-size: 14px;
color: #333333;
line-height: 20px;
background-image: url(/media/images/icons/status_error_onlight.svg);
background-repeat:no-repeat;
background-position: left 12px;
background-size: 18px 15.70px;
padding-top: 8px;
padding-left: 26px;
}

View File

@ -1,17 +1,17 @@
/*https://fonts.google.com/specimen/Roboto?selection.family=Roboto&sidebar.open=true*/
/*We use the Roboto font as the OBP default font*/
@font-face {
font-family: Roboto-Light;
font-family: Roboto-Light,sans-serif;
src: url(../font/Roboto-Light.ttf);
}
@font-face {
font-family: Roboto-Medium;
font-family: Roboto-Medium,sans-serif;
src: url(../font/Roboto-Medium.ttf);
}
@font-face {
font-family: Roboto-Regular;
font-family: Roboto-Regular,sans-serif;
src: url(../font/Roboto-Regular.ttf);
}

View File

@ -9,7 +9,7 @@ footer a,span,
footer a:hover,
footer a:focus,
span:hover {
font-family: Roboto-Regular;
font-family: Roboto-Regular,sans-serif;
font-size: 14px;
color: #FFFFFF;
letter-spacing: 0;

View File

@ -28,13 +28,13 @@
margin-bottom: 17px;
}
#main-get-started .main-get-started-text p{
font-family: Roboto-Regular;
font-family: Roboto-Regular,sans-serif;
font-size: 16px;
color: #333333;
line-height: 24px;
}
#main-get-started .main-get-started-text p a{
font-family: Roboto-Regular;
font-family: Roboto-Regular,sans-serif;
font-size: 16px;
color: #333333;
line-height: 24px;
@ -64,7 +64,7 @@
}
#main-get-started .btn-primary a{
font-family: Roboto-Regular;
font-family: Roboto-Regular,sans-serif;
font-size: 16px;
color: #FFFFFF;
text-align: center;

View File

@ -30,7 +30,7 @@
margin-left: 7.2px;
}
#main-apis .main-apis-text p{
font-family: Roboto-Regular;
font-family: Roboto-Regular,sans-serif;
font-size: 16px;
color: #333333;
line-height: 24px;
@ -59,7 +59,7 @@
background-color: #EDEDED;
}
#main-apis .btn-secondary a{
font-family: Roboto-Regular;
font-family: Roboto-Regular,sans-serif;
font-size: 16px;
color: #333333;
text-align: center;

View File

@ -17,7 +17,7 @@
}
#main-faq h3 {
font-family: Roboto-Regular;
font-family: Roboto-Regular,sans-serif;
font-size: 16px;
color: #333333;
line-height: 24px;
@ -35,7 +35,7 @@
}
#main-faq p, #main-faq ol{
margin-top: 18px;
font-family: Roboto-Light;
font-family: Roboto-Light,sans-serif;
font-size: 16px;
color: #333333;
line-height: 24px;
@ -44,7 +44,7 @@
#main-faq div a{
margin-top: 10px;
font-family: Roboto-Regular;
font-family: Roboto-Regular,sans-serif;
font-size: 16px;
color: #333333;
line-height: 24px;
@ -57,7 +57,7 @@
padding: 0;
border: 1px solid transparent;
background-color: #FFFFFF;
font-family: Roboto-Regular;
font-family: Roboto-Regular,sans-serif;
font-size: 16px;
color: #333333;
line-height: 24px;

View File

@ -16,7 +16,7 @@
display: inline-block;
margin: 30px 40px;
width: 300px;
font-family: Roboto-Regular;
font-family: Roboto-Regular,sans-serif;
font-size: 16px;
}
@ -24,7 +24,7 @@
#main-showcases p
{
color: white;
font-family: Roboto-Regular;
font-family: Roboto-Regular,sans-serif;
font-size: 16px;
line-height: 24px;
}

View File

@ -28,7 +28,7 @@
padding: 64px 0;
}
#main-start #main-start_building .btn-default a{
font-family: Roboto-Regular;
font-family: Roboto-Regular,sans-serif;
font-size: 16px;
color: #333333;
text-align: center;

View File

@ -14,7 +14,7 @@
margin-bottom: 9px ;
}
.main-support-item a {
font-family: Roboto-Regular;
font-family: Roboto-Regular,sans-serif;
font-size: 16px;
color: #333333;
line-height: 24px;

View File

@ -25,7 +25,7 @@ nav .navbar-collapse.collapse {
}
.navbar-default .navbar-nav > li > a {
background-color: white;
font-family: Roboto-Light;
font-family: Roboto-Light,sans-serif;
font-size: 18px;
color: #333333;
line-height: 31px;
@ -53,7 +53,7 @@ nav .navbar-collapse.collapse {
margin-right: 0;
}
.navbar-default .navbar-right .navitem p #register-link {
font-family: Roboto-Regular;
font-family: Roboto-Regular,sans-serif;
font-size: 16px;
color: #333333;
line-height: 24px;
@ -79,7 +79,7 @@ nav .navbar-collapse.collapse {
.navbar-default .navbar-right .navbar-btn #loggedIn-username {
margin-right: 24px;
font-family: Roboto-Regular;
font-family: Roboto-Regular,sans-serif;
font-size: 16px;
color: #333333;
line-height: 24px;
@ -130,7 +130,7 @@ nav .navbar-collapse.collapse {
}
#small-screen-navbar #small-nav-log-on-button .login{
font-family: Roboto-Regular;
font-family: Roboto-Regular,sans-serif;
font-size: 12px;
color: #FFFFFF;
letter-spacing: 0;
@ -157,7 +157,7 @@ nav .navbar-collapse.collapse {
.sidebar a {
text-decoration: none;
font-family: Roboto-Regular;
font-family: Roboto-Regular,sans-serif;
font-size: 16px;
color: #333333;
line-height: 24px;

View File

@ -26,7 +26,7 @@
#toast-container .toast-title {
float: left;
font-weight: bold;
font-family: Roboto-Medium;
font-family: Roboto-Medium,sans-serif;
font-size: 16px;
color: #333333;
line-height: 24px;
@ -43,7 +43,7 @@
}
#toast-container .toast-message {
font-family: Roboto-Regular;
font-family: Roboto-Regular,sans-serif;
font-size: 16px;
color: #333333;
line-height: 24px;

View File

@ -1,6 +1,6 @@
#recover-password h1 {
margin-top: 52px;
font-family: Roboto-Light;
font-family: Roboto-Light,sans-serif;
font-size: 35px;
color: #333333;
text-align: center;
@ -23,7 +23,7 @@
border: 0;
height: 44px;
width: 92px;
font-family: Roboto-Regular;
font-family: Roboto-Regular,sans-serif;
font-size: 16px;
text-align: center;
line-height: 24px;
@ -50,7 +50,6 @@
margin-top: 20px;
display: block;
width: 100%;
height: 34px;
padding: 6px 12px;
font-size: 14px;
line-height: 1.42857143;

View File

@ -17,7 +17,7 @@
color: black;
}
#register-consumer h1 {
font-family: Roboto-Light;
font-family: Roboto-Light,sans-serif;
font-size: 28px;
color: #333333;
letter-spacing: 0;
@ -28,7 +28,7 @@
}
#register-consumer #register-consumer-explanation p:nth-child(2) {
font-family: Roboto-Light;
font-family: Roboto-Light,sans-serif;
font-size: 22px;
color: #333333;
letter-spacing: 0;
@ -40,7 +40,7 @@
#register-consumer #register-consumer-explanation p:last-child{
margin-top: 8px;
font-family: Roboto-Regular;
font-family: Roboto-Regular,sans-serif;
font-size: 14px;
color: #333333;
letter-spacing: 0;
@ -51,7 +51,7 @@
#register-consumer-input form label{
margin-bottom: 8px;
margin-top: 17px;
font-family: Roboto-Regular;
font-family: Roboto-Regular,sans-serif;
font-size: 16px;
color: #333333;
line-height: 24px;
@ -88,7 +88,7 @@
}
#register-consumer #register-consumer-success #register-consumer-success-message{
font-family: Roboto-Light;
font-family: Roboto-Light,sans-serif;
font-size: 22px;
color: #333333;
letter-spacing: 0;
@ -98,7 +98,7 @@
#register-consumer #register-consumer-success p {
font-family: Roboto-Light;
font-family: Roboto-Light,sans-serif;
font-size: 22px;
color: #333333;
letter-spacing: 0;
@ -114,7 +114,7 @@
#register-consumer-success span,
#register-consumer-success a{
font-family: Roboto-Regular;
font-family: Roboto-Regular,sans-serif;
font-size: 16px;
color: #333333;
line-height: 24px;
@ -123,7 +123,7 @@
margin-bottom: 20px;
}
#register-consumer #register-consumer-success .row div:nth-child(1) {
font-family: Roboto-Medium;
font-family: Roboto-Medium,sans-serif;
font-size: 16px;
color: #333333;
line-height: 24px;
@ -144,7 +144,7 @@
#register-consumer-input #consumer-registration-app-description-error,
#register-consumer-input #consumer-registration-app-developer-error,
#register-consumer-input #register-consumer-errors{
font-family: Roboto-Regular;
font-family: Roboto-Regular,sans-serif;
font-size: 14px;
color: #333333;
line-height: 20px;
@ -157,7 +157,7 @@
}
#dummy-user-tokens .col-xs-12:last-child{
font-family: Roboto-Light;
font-family: Roboto-Light,sans-serif;
font-size: 20px;
color: #333333;
line-height: 24px;

View File

@ -22,7 +22,7 @@ time, mark, audio, video {
font: inherit;
font-size: 100%;
vertical-align: baseline;
font-family: Roboto-Light;
font-family: Roboto-Light,sans-serif;
}
/* HTML5 display-role reset for older browsers */
article, aside, details, figcaption, figure,

View File

@ -55,7 +55,7 @@
display: block;
}
h1 {
font-family: Roboto-Light;
font-family: Roboto-Light,sans-serif;
font-size: 33px;
line-height: 40px;
color: #333333;
@ -64,21 +64,21 @@
}
h2 {
font-size: 28px;
font-family: Roboto-Light;
font-family: Roboto-Light,sans-serif;
line-height: 36px;
color: #333333;
font-weight: lighter;
text-align: center;
}
h3 {
font-family: Roboto-Light;
font-family: Roboto-Light,sans-serif;
font-size: 23px;
color: #333333;
line-height: 30px;
font-weight: lighter;
}
h4 {
font-family: Roboto-Light;
font-family: Roboto-Light,sans-serif;
font-size: 19px;
color: #333333;
line-height: 27px;
@ -204,7 +204,7 @@
@media only screen and (min-width: 760px) and (max-width: 959px) {
h1 {
font-family: Roboto-Light;
font-family: Roboto-Light,sans-serif;
font-size: 33px;
line-height: 40px;
color: #333333;
@ -213,21 +213,21 @@
}
h2 {
font-size: 28px;
font-family: Roboto-Light;
font-family: Roboto-Light,sans-serif;
line-height: 36px;
color: #333333;
font-weight: lighter;
text-align: center;
}
h3 {
font-family: Roboto-Light;
font-family: Roboto-Light,sans-serif;
font-size: 23px;
color: #333333;
line-height: 15px;
font-weight: lighter;
}
h4 {
font-family: Roboto-Light;
font-family: Roboto-Light,sans-serif;
font-size: 19px;
color: #333333;
line-height: 27px;
@ -319,7 +319,7 @@
@media only screen and (min-width: 960px) and (max-width: 1279px) {
h1 {
font-family: Roboto-Light;
font-family: Roboto-Light,sans-serif;
font-size: 44px;
color: #333333;
letter-spacing: 0;
@ -327,7 +327,7 @@
}
h2 {
font-family: Roboto-Light;
font-family: Roboto-Light,sans-serif;
font-size: 35px;
color: #333333;
text-align: center;
@ -335,7 +335,7 @@
}
h3 {
font-family: Roboto-Light;
font-family: Roboto-Light,sans-serif;
font-size: 28px;
color: #333333;
line-height: 36px;
@ -387,7 +387,7 @@
@media only screen and (min-width: 1280px) {
h1 {
font-family: Roboto-Light;
font-family: Roboto-Light,sans-serif;
font-size: 44px;
color: #333333;
letter-spacing: 0;
@ -395,7 +395,7 @@
}
h2 {
font-family: Roboto-Light;
font-family: Roboto-Light,sans-serif;
font-size: 35px;
color: #333333;
text-align: center;
@ -403,7 +403,7 @@
}
h3 {
font-family: Roboto-Light;
font-family: Roboto-Light,sans-serif;
font-size: 28px;
color: #333333;
line-height: 36px;

View File

@ -10,7 +10,7 @@
}
#signup form h1 {
font-family: Roboto-Light;
font-family: Roboto-Light,sans-serif;
font-size: 28px;
color: #333333;
letter-spacing: 0;
@ -19,7 +19,7 @@
}
#signup form span{
font-family: Roboto-Regular;
font-family: Roboto-Regular,sans-serif;
font-size: 16px;
color: #333333;
line-height: 24px;
@ -32,7 +32,7 @@
#signup form #repeat-password{
margin-top: 32px;
font-family: Roboto-Regular;
font-family: Roboto-Regular,sans-serif;
font-size: 16px;
color: #333333;
line-height: 24px;
@ -43,7 +43,7 @@
}
#signup form label{
font-family: Roboto-Regular;
font-family: Roboto-Regular,sans-serif;
font-size: 16px;
color: #333333;
line-height: 24px;
@ -138,7 +138,7 @@
font-size: 16px;
}
#signup-agree-terms #signup-agree-terms-content {
font-family: Roboto-Regular;
font-family: Roboto-Regular,sans-serif;
font-size: 16px;
color: #333333;
letter-spacing: 0;
@ -149,9 +149,8 @@
}
#signup #signup-submit input {
color: #FFF;
background-color: #53C4EF;
font-family: Roboto-Regular;
font-family: Roboto-Regular,sans-serif;
font-size: 16px;
color: #FFFFFF;
text-align: center;
@ -223,7 +222,7 @@
margin-bottom: 0px;
}
#signup-legal-notice{
font-family: Roboto-Regular;
font-family: Roboto-Regular,sans-serif;
font-size: 14px;
color: #333333;
letter-spacing: 0;

View File

@ -8,6 +8,7 @@
@import url(/media/css/main-start.css);
@import url(/media/css/authorise.css);
@import url(/media/css/register-consumer.css);
@import url(/media/css/data-area.css);
@import url(/media/css/signup.css);
@import url(/media/css/recover-password.css);
@import url(/media/css/main-showcases.css);
@ -18,7 +19,7 @@
@import url(/media/css/fonts.css);
html {
font-family: "Roboto-Regular";
font-family: Roboto-Regular,sans-serif;
height: 100%
}
@ -30,7 +31,7 @@ body {
}
a {
font-family: Roboto-Regular;
font-family: Roboto-Regular,sans-serif;
color: #333333;
text-decoration: none;
}
@ -132,7 +133,7 @@ header #lift__noticesContainer__ {
}
.btn-danger {
font-family: Roboto-Regular;
font-family: Roboto-Regular,sans-serif;
font-size: 16px;
color: #FFFFFF;
text-align: center;
@ -163,8 +164,7 @@ header #lift__noticesContainer__ {
padding-bottom: 11px;
padding-left: 20px;
padding-right: 20px;
font-family: Roboto-Regular;
font-size: 16px;
font-family: Roboto-Regular,sans-serif;
color: #333333;
text-align: center;
line-height: 24px;
@ -212,7 +212,7 @@ input[type="text"]{
}
.select2-results__option {
padding: 6px;
font-family: Roboto-Regular;
font-family: Roboto-Regular,sans-serif;
font-size: 16px;
color: #333333;
line-height: 24px;
@ -223,7 +223,7 @@ input[type="text"]{
}
.select2-container--default .select2-selection--single .select2-selection__rendered {
font-family: Roboto-Regular;
font-family: Roboto-Regular,sans-serif;
font-size: 16px;
color: #333333;
line-height: 24px;
@ -274,7 +274,7 @@ input[type="text"]{
}
body {
font-family: "Roboto-Light";
font-family: Roboto-Light,sans-serif;
}
header #header-decoration,
@ -284,6 +284,7 @@ header #header-decoration,
#signup,
#recover-password,
#register-consumer,
#data-area ,
#create-account,
.navbar-default .navbar-toggle:hover,
.navbar-default .navbar-toggle:focus,
@ -298,7 +299,6 @@ header #header-decoration,
min-width: 43%;
padding: 6%;
padding-top: 6%;
padding-right: 6%;
padding-bottom: 6%;
padding-left: 6%;
padding-right: 10%;
@ -424,9 +424,8 @@ input{
#add-user-auth-context-update-request-div #identifier-error .error,
#confirm-user-auth-context-update-request-div #otp-value-error .error{
color: black;
background-color: white;
font-family: Roboto-Regular;
font-family: Roboto-Regular,sans-serif;
font-size: 16px;
color: #333333;
line-height: 24px;

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -120,9 +120,6 @@ $(document).ready(function() {
$("#small-screen-navbar #small-nav-log-on-button").css("width","63px");
}
$(".main-support-item .support-platform-link").text("chat.openbankproject.com");
var htmlTitle = $(document).find("title").text();
if (htmlTitle.indexOf("Get API") > -1){
@ -343,6 +340,13 @@ $(document).ready(function() {
} else{
consumerRegistrationAppRequestUriError.parent().addClass('hide');
}
var dataAreaErrors = $('#data-area-input #data-area-errors');
if (dataAreaErrors.length > 0 && dataAreaErrors.html().length > 0) {
dataAreaErrors.parent().removeClass('hide');
} else{
dataAreaErrors.parent().addClass('hide');
}
{
var consumerRegistrationJwksError = $('#register-consumer-input #consumer-registration-app-signing_jwks-error');

View File

@ -1,4 +1,5 @@
<html>
<!DOCTYPE html>
<html lang="en">
<head><title>Example HTML</title></head>
<body bgcolor="green">
<h1>I am some Example HTML</h1>

View File

@ -47,7 +47,7 @@ Berlin 13359, Germany
<script src="/media/js/jquery.min.js" type="text/javascript"></script>
<script src="/media/js/bootstrap.min.js" type="text/javascript"></script>
<script src="/media/js/select2.js"></script>
<script src="/media/js/select2.min.js"></script>
<script src="/media/js/toastr.min.js" type="text/javascript"></script>
<script src="/media/js/website.js" type="text/javascript"></script>
<script src="/media/js/cookies-consent.js"></script>
@ -70,9 +70,11 @@ Berlin 13359, Germany
<div id="cookie-ipaddress-concurrent-logins" data-lift="WebUI.concurrentLoginsCookiesCheck"></div>
<div id="top-text" data-lift="WebUI.topText"></div>
<table id="table-header">
<table id="table-header" aria-describedby="Home Page Logo">
<tr class="row">
<td></td>
<td>
<th scope="col"></th>
</td>
<td id="td-logo-left-xs">
<div data-lift="WebUI.headerLogoLeft">
<a href="/" aria-label="Home Page"><img src="" id="logo-left-xs" align="left" alt="left logo image"></a>
@ -133,7 +135,7 @@ Berlin 13359, Germany
</li>
<li class="navitem" data-lift="Login.loggedIn" >
<!-- LOGGED IN -->
<p class="navbar-btn"><span id="loggedIn-username">username</span><a href="#" class="btn btn-default logout">Log off</a></p>
<p class="navbar-btn"><a href="/user-information"><span id="loggedIn-username">username</span></a><a href="#" class="btn btn-default logout">Log off</a></p>
</li>
</ul>
</div><!--/.nav-collapse -->

View File

@ -0,0 +1,64 @@
<!--
Open Bank Project - API
Copyright (C) 2011-2017, 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
Osloerstrasse 16/17
Berlin 13359, Germany
This product includes software developed at
TESOBE (http://www.tesobe.com/)
-->
<div id="register-consumer" data-lift="surround?with=default;at=content">
<div data-lift="UserInformation.show">
<div id="register-consumer-explanation">
<h1>User Information</h1>
<form>
<style>
.form-control[disabled],
.form-control[readonly],
fieldset[disabled] .form-control {
background-color: #ffffff;
}
</style>
<div id="register-consumer-success" class="wrap-text">
<div id="user-information">
<div class="form-group">
<label for="user-info-email">Email</label>
<input readonly type="text" id="user-info-email" class="form-control" aria-describedby="consumer-registration-app-name-error">
</div>
<div class="form-group">
<label for="user-info-username">Username</label>
<input readonly type="text" id="user-info-username" class="form-control" aria-describedby="consumer-registration-app-name-error">
</div>
<div class="form-group">
<label for="user-info-provider">Provider</label>
<input readonly type="text" id="user-info-provider" class="form-control" aria-describedby="consumer-registration-app-name-error">
</div>
<div class="form-group">
<label for="user-info-id-token">ID Token</label>
<input readonly type="text" id="user-info-id-token" class="form-control" aria-describedby="consumer-registration-app-name-error">
</div>
</div>
</div>
</form>
</div>
</div>
</div>

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