Merge branch 'develop' of github.com:OpenBankProject/OBP-API into feature/dynamic_message_doc_js

This commit is contained in:
Shuang 2022-05-22 21:47:16 +08:00
commit ff67f6493f
151 changed files with 6355 additions and 980 deletions

5
.github/Dockerfile_PreBuild vendored Normal file
View File

@ -0,0 +1,5 @@
FROM jetty:9.4-jre11-slim
# Copy OBP source code
# Copy build artifact (.war file) into jetty from 'maven' stage.
COPY /obp-api/target/obp-api-1.*.war /var/lib/jetty/webapps/ROOT.war

70
.github/workflows/build_package.yml vendored Normal file
View File

@ -0,0 +1,70 @@
name: build and publish container
on: [push]
env:
## Sets environment variable
DOCKER_HUB_ORGANIZATION: openbankproject
DOCKER_HUB_REPOSITORY: obp-api
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up JDK 11
uses: actions/setup-java@v2
with:
java-version: '11'
distribution: 'adopt'
cache: maven
- name: Build with Maven
run: |
echo connector=mapped > obp-api/src/main/resources/props/test.default.props
echo hostname=http://localhost:8016 >> obp-api/src/main/resources/props/test.default.props
echo tests.port=8016 >> obp-api/src/main/resources/props/test.default.props
echo End of minimum settings >> obp-api/src/main/resources/props/test.default.props
echo payments_enabled=false >> obp-api/src/main/resources/props/test.default.props
echo importer_secret=change_me >> obp-api/src/main/resources/props/test.default.props
echo messageQueue.updateBankAccountsTransaction=false >> obp-api/src/main/resources/props/test.default.props
echo messageQueue.createBankAccounts=false >> obp-api/src/main/resources/props/test.default.props
echo allow_sandbox_account_creation=true >> obp-api/src/main/resources/props/test.default.props
echo allow_sandbox_data_import=true >> obp-api/src/main/resources/props/test.default.props
echo sandbox_data_import_secret=change_me >> obp-api/src/main/resources/props/test.default.props
echo allow_account_deletion=true >> obp-api/src/main/resources/props/test.default.props
echo allowed_internal_redirect_urls = /,/oauth/authorize >> obp-api/src/main/resources/props/test.default.props
echo transactionRequests_enabled=true >> obp-api/src/main/resources/props/test.default.props
echo transactionRequests_supported_types=SEPA,SANDBOX_TAN,FREE_FORM,COUNTERPARTY,ACCOUNT,SIMPLE >> obp-api/src/main/resources/props/test.default.props
echo SIMPLE_OTP_INSTRUCTION_TRANSPORT=dummy >> obp-api/src/main/resources/props/test.default.props
echo openredirects.hostname.whitlelist=http://127.0.0.1,http://localhost >> obp-api/src/main/resources/props/test.default.props
echo remotedata.secret = foobarbaz >> obp-api/src/main/resources/props/test.default.props
echo allow_public_views=true >> obp-api/src/main/resources/props/test.default.props
echo SIMPLE_OTP_INSTRUCTION_TRANSPORT=dummy >> obp-api/src/main/resources/props/test.default.props
echo ACCOUNT_OTP_INSTRUCTION_TRANSPORT=dummy >> obp-api/src/main/resources/props/test.default.props
echo SEPA_OTP_INSTRUCTION_TRANSPORT=dummy >> obp-api/src/main/resources/props/test.default.props
echo FREE_FORM_OTP_INSTRUCTION_TRANSPORT=dummy >> obp-api/src/main/resources/props/test.default.props
echo COUNTERPARTY_OTP_INSTRUCTION_TRANSPORT=dummy >> obp-api/src/main/resources/props/test.default.props
echo SEPA_CREDIT_TRANSFERS_OTP_INSTRUCTION_TRANSPORT=dummy >> obp-api/src/main/resources/props/test.default.props
echo kafka.akka.timeout = 9 >> obp-api/src/main/resources/props/test.default.props
echo remotedata.timeout = 10 >> obp-api/src/main/resources/props/test.default.props
echo allow_oauth2_login=true >> obp-api/src/main/resources/props/test.default.props
echo oauth2.jwk_set.url=https://www.googleapis.com/oauth2/v3/certs >> obp-api/src/main/resources/props/test.default.props
echo ResetPasswordUrlEnabled=true >> obp-api/src/main/resources/props/test.default.props
echo consents.allowed=true >> obp-api/src/main/resources/props/test.default.props
MAVEN_OPTS="-Xmx3G -Xss2m" mvn package
- name: Build the Docker image
run: |
echo "${{ secrets.DOCKER_HUB_TOKEN }}" | docker login -u "${{ secrets.DOCKER_HUB_USERNAME }}" --password-stdin docker.io
docker build . --file .github/Dockerfile_PreBuild --tag docker.io/${{ env.DOCKER_HUB_ORGANIZATION }}/${{ env.DOCKER_HUB_REPOSITORY }}:$GITHUB_SHA --tag docker.io/${{ env.DOCKER_HUB_ORGANIZATION }}/${{ env.DOCKER_HUB_REPOSITORY }}:latest --tag docker.io/${{ env.DOCKER_HUB_ORGANIZATION }}/${{ env.DOCKER_HUB_REPOSITORY }}:develop
docker push docker.io/${{ env.DOCKER_HUB_ORGANIZATION }}/${{ env.DOCKER_HUB_REPOSITORY }} --all-tags
echo docker done

View File

@ -106,7 +106,7 @@ To compile and run jetty, install Maven 3, create your configuration in obp-api/
Set memory options
export MAVEN_OPTS="-Xmx3000m -XX:MaxPermSize=512m"
export MAVEN_OPTS="-Xmx3000m -Xss2m"
Run one test

View File

@ -639,7 +639,7 @@
<plugin>
<groupId>pl.project13.maven</groupId>
<artifactId>git-commit-id-plugin</artifactId>
<version>2.2.1</version>
<version>4.9.10</version>
<executions>
<execution>
<goals>

View File

@ -0,0 +1,387 @@
api.explorer = Explorador API
introduction = Introducción
support = Soporte
register = Registrarse
logon = Ingresar
invalid.email.address = Invalid email address
password.must.be.set = Password must be set
password.too.short = Password too short
passwords.do.not.match = Passwords do not match
number.required = A numeric value must be provided
ajax.error=The server cannot be contacted at this time
invalid.zip.code = Invalid ZIP code
invalid.postal.code = Invalid postal code
unique.email.address = The email address must be unique
must.be.logged.in = You must be logged in
already.logged.in = already logged in. Please logout first.
login = Login
logout = Logout
log.in = Log In
log.out = Log Out
sign.up = Sign Up
logged.in = Logged In
logout.first = Please logout first.
lost.password = Lost Password
reset.password = Reset Password
change.password = Change Password
password.changed = Password Changed
edit.user = Edit User
validate.user = Validate User
edit.profile = Edit Profile
sign.up.confirmation = Sign up confirmation
sign.up.message = You have signed up. A validation email message will be sent to you.
sign.up.validation.link=Click on this link to complete signup:
welcome = Welcome
account.validated = Account Validated
invalid.validation.link = Validation link invalid
account.validation.error = Your account has not been validated. Please check your email for a validation link.
invalid.credentials = Invalid Username/Password
enter.email = Enter your email address and we'll email you a link to reset your password
email.address = Email address
reset.password.confirmation = Reset Password Confirmation
dear = Dear
click.reset.link = Click on this link to reset your password
thank.you = Thank you
reset.password.request = Reset Password Request
password.reset.email.sent = Password Reset Email sent
account.validation.resent = Account Validation Re-sent
email.address.not.found = Email address not found
send.it = Send It
reset.your.password = Reset your password
enter.your.new.password = Enter your new password
repeat.your.new.password = Enter your new password (repeat)
set.password = Set Password
password.link.invalid = Password reset link invalid
wrong.old.password = Wrong old password
old.password = Old password
new.password = New password
repeat.password = New password (repeat)
repeat = Repeat
edit = Edit
cancel = Cancel
ok = OK
change = Change
password = Password
recover.password = Recover Password
profile.updated = You have updated your profile
male = Male
female = Female
first.name = First name
last.name = Last name
locale = Locale
time.zone = Time Zone
msg.notice = Notice
msg.warning = Warning
msg.error = Error
crudify.menu.view.displayName=View %s
crudify.menu.edit.displayName=Edit %s
crudify.menu.delete.displayName=Delete %s
paginator.norecords = There are no records to display
paginator.displayingrecords = Displaying %s-%s of %s
# Country names
country_1 = United States
country_2 = Afghanistan
country_3 = Albania
country_4 = Algeria
country_5 = Andorra
country_6 = Angola
country_7 = Antigua and Barbuda
country_8 = Argentina
country_9 = Armenia
country_10 = Australia
country_11 = Austria
country_12 = Azerbaijan
country_13 = Bahamas, The
country_14 = Bahrain
country_15 = Bangladesh
country_16 = Barbados
country_17 = Belarus
country_18 = Belgium
country_19 = Belize
country_20 = Benin
country_21 = Bhutan
country_22 = Bolivia
country_23 = Bosnia and Herzegovina
country_24 = Botswana
country_25 = Brazil
country_26 = Brunei
country_27 = Bulgaria
country_28 = Burkina Faso
country_29 = Burundi
country_30 = Cambodia
country_31 = Cameroon
country_32 = Canada
country_33 = Cape Verde
country_34 = Central African Republic
country_35 = Chad
country_36 = Chile
country_37 = China, People's Republic of
country_38 = Colombia
country_39 = Comoros
country_40 = Congo, Democratic Republic of the (Congo Kinshasa)
country_41 = Congo, Republic of the (Congo Brazzaville)
country_42 = Costa Rica
country_43 = Cote d'Ivoire (Ivory Coast)
country_44 = Croatia
country_45 = Cuba
country_46 = Cyprus
country_47 = Czech Republic
country_48 = Denmark
country_49 = Djibouti
country_50 = Dominica
country_51 = Dominican Republic
country_52 = Ecuador
country_53 = Egypt
country_54 = El Salvador
country_55 = Equatorial Guinea
country_56 = Eritrea
country_57 = Estonia
country_58 = Ethiopia
country_59 = Fiji
country_60 = Finland
country_61 = France
country_62 = Gabon
country_63 = Gambia, The
country_64 = Georgia
country_65 = Germany
country_66 = Ghana
country_67 = Greece
country_68 = Grenada
country_69 = Guatemala
country_70 = Guinea
country_71 = Guinea-Bissau
country_72 = Guyana
country_73 = Haiti
country_74 = Honduras
country_75 = Hungary
country_76 = Iceland
country_77 = India
country_78 = Indonesia
country_79 = Iran
country_80 = Iraq
country_81 = Ireland
country_82 = Israel
country_83 = Italy
country_84 = Jamaica
country_85 = Japan
country_86 = Jordan
country_87 = Kazakhstan
country_88 = Kenya
country_89 = Kiribati
country_90 = Korea, Democratic People's Republic of (North Korea)
country_91 = Korea, Republic of (South Korea)
country_92 = Kuwait
country_93 = Kyrgyzstan
country_94 = Laos
country_95 = Latvia
country_96 = Lebanon
country_97 = Lesotho
country_98 = Liberia
country_99 = Libya
country_100 = Liechtenstein
country_101 = Lithuania
country_102 = Luxembourg
country_103 = Macedonia
country_104 = Madagascar
country_105 = Malawi
country_106 = Malaysia
country_107 = Maldives
country_108 = Mali
country_109 = Malta
country_110 = Marshall Islands
country_111 = Mauritania
country_112 = Mauritius
country_113 = Mexico
country_114 = Micronesia
country_115 = Moldova
country_116 = Monaco
country_117 = Mongolia
country_118 = Montenegro
country_119 = Morocco
country_120 = Mozambique
country_121 = Myanmar (Burma)
country_122 = Namibia
country_123 = Nauru
country_124 = Nepal
country_125 = Netherlands
country_126 = New Zealand
country_127 = Nicaragua
country_128 = Niger
country_129 = Nigeria
country_130 = Norway
country_131 = Oman
country_132 = Pakistan
country_133 = Palau
country_134 = Panama
country_135 = Papua New Guinea
country_136 = Paraguay
country_137 = Peru
country_138 = Philippines
country_139 = Poland
country_140 = Portugal
country_141 = Qatar
country_142 = Romania
country_143 = Russia
country_144 = Rwanda
country_145 = Saint Kitts and Nevis
country_146 = Saint Lucia
country_147 = Saint Vincent and the Grenadines
country_148 = Samoa
country_149 = San Marino
country_150 = Sao Tome and Principe
country_151 = Saudi Arabia
country_152 = Senegal
country_153 = Serbia
country_154 = Seychelles
country_155 = Sierra Leone
country_156 = Singapore
country_157 = Slovakia
country_158 = Slovenia
country_159 = Solomon Islands
country_160 = Somalia
country_161 = South Africa
country_162 = Spain
country_163 = Sri Lanka
country_164 = Sudan
country_165 = Suriname
country_166 = Swaziland
country_167 = Sweden
country_168 = Switzerland
country_169 = Syria
country_170 = Tajikistan
country_171 = Tanzania
country_172 = Thailand
country_173 = Timor-Leste (East Timor)
country_174 = Togo
country_175 = Tonga
country_176 = Trinidad and Tobago
country_177 = Tunisia
country_178 = Turkey
country_179 = Turkmenistan
country_180 = Tuvalu
country_181 = Uganda
country_182 = Ukraine
country_183 = United Arab Emirates
country_184 = United Kingdom
country_185 = Uruguay
country_186 = Uzbekistan
country_187 = Vanuatu
country_188 = Vatican City
country_189 = Venezuela
country_190 = Vietnam
country_191 = Yemen
country_192 = Zambia
country_193 = Zimbabwe
country_194 = Abkhazia
country_195 = China, Republic of (Taiwan)
country_196 = Nagorno-Karabakh
country_197 = Northern Cyprus
country_198 = Pridnestrovie (Transnistria)
country_199 = Somaliland
country_200 = South Ossetia
country_201 = Ashmore and Cartier Islands
country_202 = Christmas Island
country_203 = Cocos (Keeling) Islands
country_204 = Coral Sea Islands
country_205 = Heard Island and McDonald Islands
country_206 = Norfolk Island
country_207 = New Caledonia
country_208 = French Polynesia
country_209 = Mayotte
country_210 = Saint Barthelemy
country_211 = Saint Martin
country_212 = Saint Pierre and Miquelon
country_213 = Wallis and Futuna
country_214 = French Southern and Antarctic Lands
country_215 = Clipperton Island
country_216 = Bouvet Island
country_217 = Cook Islands
country_218 = Niue
country_219 = Tokelau
country_220 = Guernsey
country_221 = Isle of Man
country_222 = Jersey
country_223 = Anguilla
country_224 = Bermuda
country_225 = British Indian Ocean Territory
country_226 = British Sovereign Base Areas
country_227 = British Virgin Islands
country_228 = Cayman Islands
country_229 = Falkland Islands (Islas Malvinas)
country_230 = Gibraltar
country_231 = Montserrat
country_232 = Pitcairn Islands
country_233 = Saint Helena
country_234 = South Georgia and the South Sandwich Islands
country_235 = Turks and Caicos Islands
country_236 = Northern Mariana Islands
country_237 = Puerto Rico
country_238 = American Samoa
country_239 = Baker Island
country_240 = Guam
country_241 = Howland Island
country_242 = Jarvis Island
country_243 = Johnston Atoll
country_244 = Kingman Reef
country_245 = Midway Islands
country_246 = Navassa Island
country_247 = Palmyra Atoll
country_248 = U.S. Virgin Islands
country_249 = Wake Island
country_250 = Hong Kong
country_251 = Macau
country_252 = Faroe Islands
country_253 = Greenland
country_254 = French Guiana
country_255 = Guadeloupe
country_256 = Martinique
country_257 = Reunion
country_258 = Aland
country_259 = Aruba
country_260 = Netherlands Antilles
country_261 = Svalbard
country_262 = Ascension
country_263 = Tristan da Cunha
country_264 = Antarctica
country_265 = Kosovo
country_266 = Palestinian Territories (Gaza Strip and West Bank)
country_267 = Western Sahara
country_268 = Australian Antarctic Territory
country_269 = Ross Dependency
country_270 = Peter I Island
country_271 = Queen Maud Land
country_272 = British Antarctic Territory
# LiftScreen + Wizard
Next = Next
Previous = Previous
Finish = Finish
Cancel = Cancel
# Crudify
Create = Create
Save = Save
Edit = Edit
Delete = Delete
delete = delete
View = View
List = List %s
Created = Created
Edited = Edited
Deleted = Deleted
#OBP specific fields
consumer.registration.nav.name=Obtener llave API
invalid.login.credentials=Invalid Login Credentials
invalid.username=Invalid Username: \
1) The ONLY allowed characters in Usernames are: a-z A-Z 0-9 . _ \
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 . \
6) Any valid email address is allowed as the Username
your.username.is.not.unique = Your username is not unique. Please enter a different one.
# Those 2 messages must have the same output in order to prevent leakage of information
user.invitation.is.already.finished = Looks like the invitation link is invalid. Still need help? Please send us a message using API Playground Support.
your.secret.link.is.not.valid = Looks like the invitation link is invalid. Still need help? Please send us a message using API Playground Support.

View File

@ -131,7 +131,9 @@ jwt.use.ssl=false
# Paths to the SSL keystore files - has to be jks
#keystore.path=/path/to/api.keystore.jks
#keystore password
#keystore.password = redf1234
# private key password
#keystore.passphrase = redf1234
#keystore.alias = localhost
#truststore.path=/path/to/api.truststore.jks
@ -239,6 +241,9 @@ mail.smtp.port=25
## Oauth token timeout
token_expiration_weeks=4
## payment challenge answer timeout,default is 600 seconds/10 minutes
transaction_request_challenge_ttl=600
### Sandbox
@ -264,7 +269,7 @@ transactionRequests_enabled=true
transactionRequests_connector=mapped
## Transaction Request Types that are supported on this server. Possible values might include SANDBOX_TAN, COUNTERPARTY, SEPA, FREE_FORM
transactionRequests_supported_types=SANDBOX_TAN,COUNTERPARTY,SEPA,ACCOUNT_OTP,ACCOUNT
transactionRequests_supported_types=SANDBOX_TAN,COUNTERPARTY,SEPA,ACCOUNT_OTP,ACCOUNT,SIMPLE
## Transaction request challenge threshold. Level at which challenge is created and needs to be answered.
## The Currency is EUR unless set with transactionRequests_challenge_currency.
@ -540,6 +545,12 @@ webui_register_consumer_success_message_email = Thank you for registering to use
## End of webui_ section ########
# Set Locale
language_tag = en-GB
## API Options
apiOptions.getBranchesIsPublic = true
apiOptions.getAtmsIsPublic = true
@ -859,6 +870,7 @@ database_messages_scheduler_interval=3600
# -- SCA (Strong Customer Authentication) method for OTP challenge-------
# ACCOUNT_OTP_INSTRUCTION_TRANSPORT=DUMMY
# SIMPLE_OTP_INSTRUCTION_TRANSPORT=DUMMY
# SEPA_OTP_INSTRUCTION_TRANSPORT=DUMMY
# FREE_FORM_OTP_INSTRUCTION_TRANSPORT=DUMMY
# COUNTERPARTY_OTP_INSTRUCTION_TRANSPORT=DUMMY
@ -920,8 +932,8 @@ stored_procedure_connector.poolFactoryName=commons-dbcp2
# Set whether DynamicEntity display name starts with underscore, default is true
dynamic_entities_have_prefix=true
# Url prefix of dynamic endpoints, default is dynamic. e.g if set to foobar, one url can be /obp/v4.0.0/foobar/Address
dynamic_endpoints_url_prefix=dynamic
# Url prefix of dynamic endpoints, default is empty. e.g if set to foobar, one url can be /obp/dynamic-endpoint/foobar/Address
dynamic_endpoints_url_prefix=
# --- Locking a user due to consecutively failed login attempts ------
# Defines consecutively failed login attempts before a user is locked
@ -1154,4 +1166,12 @@ dynamic_code_compile_validate_dependencies=[\
]
## when api response json field value in the json array, the field will be excluded
excluded.response.field.values=["String", "", null, []]
excluded.response.field.values=["String", "", null, []]
# If you want to make the Lift inactivity timeout shorter than
# the container inactivity timeout, set the inactivity timeout here
session_inactivity_timeout_in_minutes = 30
# Defines redirect URL after user account is validated
# In case is not defined default value is the home page of this application
user_account_validated_redirect_url =

View File

@ -1,9 +1,13 @@
#this is a sample props file you should edit and rename
#see https://www.assembla.com/wiki/show/liftweb/Properties for all the naming options, or just use "default.props" in this same folder
## REQUIREMENTS
# see https://github.com/OpenBankProject/OBP-API#from-the-command-line for minimal memory requirements
####################################
## Minimum Settings
### Log level
#logger.loglevel=INFO
@ -104,7 +108,14 @@ sandbox_data_import_secret=change_me
allow_account_deletion=true
# This needs to be a list all the types of transaction_requests that we have tests for. Else those tests will fail
transactionRequests_supported_types=SANDBOX_TAN,FREE_FORM,SEPA,COUNTERPARTY,TRANSFER_TO_PHONE
transactionRequests_supported_types=SANDBOX_TAN,COUNTERPARTY,SEPA,ACCOUNT_OTP,ACCOUNT,SIMPLE
ACCOUNT_OTP_INSTRUCTION_TRANSPORT=dummy
SIMPLE_OTP_INSTRUCTION_TRANSPORT=dummy
SEPA_OTP_INSTRUCTION_TRANSPORT=dummy
FREE_FORM_OTP_INSTRUCTION_TRANSPORT=dummy
COUNTERPARTY_OTP_INSTRUCTION_TRANSPORT=dummy
SEPA_CREDIT_TRANSFERS_OTP_INSTRUCTION_TRANSPORT=dummy
# control the create and access to public views.
allow_public_views =true

View File

@ -127,7 +127,7 @@ import code.util.{Helper, HydraUtil}
import code.validation.JsonSchemaValidation
import code.views.Views
import code.views.system.{AccountAccess, ViewDefinition}
import code.webhook.{MappedAccountWebhook, WebhookHelperActors}
import code.webhook.{BankAccountNotificationWebhook, MappedAccountWebhook, SystemAccountNotificationWebhook, WebhookHelperActors}
import code.webuiprops.WebUiProps
import com.openbankproject.commons.model.ErrorMessage
import com.openbankproject.commons.util.Functions.Implicits._
@ -138,6 +138,7 @@ import net.liftweb.common._
import net.liftweb.db.DBLogEntry
import net.liftweb.http.LiftRules.DispatchPF
import net.liftweb.http._
import net.liftweb.http.provider.HTTPCookie
import net.liftweb.json.Extraction
import net.liftweb.mapper._
import net.liftweb.sitemap.Loc._
@ -407,7 +408,10 @@ class Boot extends MdcLoggable {
enableVersionIfAllowed(ApiVersion.v3_0_0)
enableVersionIfAllowed(ApiVersion.v3_1_0)
enableVersionIfAllowed(ApiVersion.v4_0_0)
enableVersionIfAllowed(ApiVersion.v5_0_0)
enableVersionIfAllowed(ApiVersion.b1)
enableVersionIfAllowed(ApiVersion.`dynamic-endpoint`)
enableVersionIfAllowed(ApiVersion.`dynamic-entity`)
def enableOpenIdConnectApis = {
// OpenIdConnect endpoint and validator
@ -584,14 +588,33 @@ class Boot extends MdcLoggable {
LiftRules.explicitlyParsedSuffixes = Helpers.knownSuffixes &~ (Set("com"))
//set base localization to english (instead of computer default)
Locale.setDefault(Locale.ENGLISH)
logger.info("Current Project Locale is :" +Locale.getDefault)
//override locale calculated from client request with default (until we have translations)
val locale = I18NUtil.getLocale()
Locale.setDefault(locale)
logger.info("Default Project Locale is :" + locale)
// Cookie name
val localeCookieName = "SELECTED_LOCALE"
LiftRules.localeCalculator = {
case fullReq @ Full(req) => Locale.ENGLISH
case _ => Locale.ENGLISH
case fullReq @ Full(req) => {
// Check against a set cookie, or the locale sent in the request
def currentLocale : Locale = {
S.findCookie(localeCookieName).flatMap {
cookie => cookie.value.map(I18NUtil.computeLocale)
} openOr locale
}
// Check to see if the user explicitly requests a new locale
// In case it's true we use that value to set up a new cookie value
S.param("locale") match {
case Full(requestedLocale) if requestedLocale != null => {
val computedLocale = I18NUtil.computeLocale(requestedLocale)
S.addCookie(HTTPCookie(localeCookieName, requestedLocale))
computedLocale
}
case _ => currentLocale
}
}
case _ => locale
}
//for XSS vulnerability, set X-Frame-Options header as DENY
@ -700,6 +723,7 @@ class Boot extends MdcLoggable {
val owner = Views.views.vend.getOrCreateSystemView(SYSTEM_OWNER_VIEW_ID).isDefined
val auditor = Views.views.vend.getOrCreateSystemView(SYSTEM_AUDITOR_VIEW_ID).isDefined
val accountant = Views.views.vend.getOrCreateSystemView(SYSTEM_ACCOUNTANT_VIEW_ID).isDefined
val smallPaymentVerified = Views.views.vend.getOrCreateSystemView(SYSTEM_SMALL_PAYMENT_VERIFIED_VIEW_ID).isDefined
// Only create Firehose view if they are enabled at instance.
val accountFirehose = if (ApiPropsWithAlias.allowAccountFirehose)
Views.views.vend.getOrCreateSystemView(SYSTEM_FIREHOSE_VIEW_ID).isDefined
@ -711,6 +735,7 @@ class Boot extends MdcLoggable {
|System view ${SYSTEM_AUDITOR_VIEW_ID} exists/created at the instance: ${auditor}
|System view ${SYSTEM_ACCOUNTANT_VIEW_ID} exists/created at the instance: ${accountant}
|System view ${SYSTEM_FIREHOSE_VIEW_ID} exists/created at the instance: ${accountFirehose}
|System view ${SYSTEM_SMALL_PAYMENT_VERIFIED_VIEW_ID} exists/created at the instance: ${smallPaymentVerified}
|""".stripMargin
logger.info(comment)
@ -746,6 +771,14 @@ class Boot extends MdcLoggable {
if(HydraUtil.mirrorConsumerInHydra) {
createHydraClients()
}
Props.get("session_inactivity_timeout_in_minutes") match {
case Full(x) if tryo(x.toLong).isDefined =>
LiftRules.sessionInactivityTimeout.default.set(Full((x.toLong.minutes): Long))
case _ =>
// Do not change default value
}
}
private def sanityCheckOPropertiesRegardingScopes() = {
@ -835,8 +868,8 @@ class Boot extends MdcLoggable {
*/
private def createDefaultBankAndDefaultAccountsIfNotExisting() ={
val defaultBankId= APIUtil.defaultBankId
val incomingAccountId= INCOMING_ACCOUNT_ID
val outgoingAccountId= OUTGOING_ACCOUNT_ID
val incomingAccountId= INCOMING_SETTLEMENT_ACCOUNT_ID
val outgoingAccountId= OUTGOING_SETTLEMENT_ACCOUNT_ID
MappedBank.find(By(MappedBank.permalink, defaultBankId)) match {
case Full(b) =>
@ -965,6 +998,8 @@ object ToSchemify {
MappedCurrency,
MappedTransactionRequestTypeCharge,
MappedAccountWebhook,
SystemAccountNotificationWebhook,
BankAccountNotificationWebhook,
MappedCustomerIdMapping,
MappedProductAttribute,
MappedConsent,

View File

@ -4,6 +4,7 @@ import java.util.UUID.randomUUID
import code.api.OBPRestHelper
import code.api.builder.OBP_APIBuilder
import code.api.cache.Caching
import code.api.dynamic.endpoint.helper.{DynamicEndpointHelper, DynamicEndpoints, DynamicEntityHelper}
import code.api.util.APIUtil._
import code.api.util.ApiRole.{canReadDynamicResourceDocsAtOneBank, canReadResourceDoc, canReadStaticResourceDoc}
import code.api.util.ApiTag._
@ -14,7 +15,6 @@ import code.api.v1_4_0.{APIMethods140, JSONFactory1_4_0, OBPAPI1_4_0}
import code.api.v2_2_0.{APIMethods220, OBPAPI2_2_0}
import code.api.v3_0_0.OBPAPI3_0_0
import code.api.v3_1_0.OBPAPI3_1_0
import code.api.v4_0_0.dynamic.{DynamicEndpointHelper, DynamicEndpoints, DynamicEntityHelper}
import code.api.v4_0_0.{APIMethods400, OBPAPI4_0_0}
import code.apicollectionendpoint.MappedApiCollectionEndpointsProvider
import code.util.Helper.MdcLoggable
@ -33,8 +33,10 @@ import net.liftweb.json.JsonAST.{JField, JString, JValue}
import net.liftweb.json._
import net.liftweb.util.Helpers.tryo
import net.liftweb.util.Props
import java.util.concurrent.ConcurrentHashMap
import code.api.util.NewStyle.HttpCode
import code.api.v5_0_0.OBPAPI5_0_0
import code.util.Helper
import scala.collection.immutable.{List, Nil}
@ -117,6 +119,7 @@ trait ResourceDocsAPIMethods extends MdcLoggable with APIMethods220 with APIMeth
val resourceDocs = requestedApiVersion match {
case ApiVersion.`b1` => OBP_APIBuilder.allResourceDocs
case ApiVersion.v5_0_0 => OBPAPI5_0_0.allResourceDocs
case ApiVersion.v4_0_0 => OBPAPI4_0_0.allResourceDocs
case ApiVersion.v3_1_0 => OBPAPI3_1_0.allResourceDocs
case ApiVersion.v3_0_0 => OBPAPI3_0_0.allResourceDocs
@ -134,6 +137,7 @@ trait ResourceDocsAPIMethods extends MdcLoggable with APIMethods220 with APIMeth
val versionRoutes = requestedApiVersion match {
case ApiVersion.`b1` => OBP_APIBuilder.routes
case ApiVersion.v5_0_0 => OBPAPI5_0_0.routes
case ApiVersion.v4_0_0 => OBPAPI4_0_0.routes
case ApiVersion.v3_1_0 => OBPAPI3_1_0.routes
case ApiVersion.v3_0_0 => OBPAPI3_0_0.routes
@ -311,7 +315,7 @@ trait ResourceDocsAPIMethods extends MdcLoggable with APIMethods220 with APIMeth
}
}
private def getResourceDocsObpDynamicCached(requestedApiVersion : ApiVersion,
private def getResourceDocsObpDynamicCached(
resourceDocTags: Option[List[ResourceDocTag]],
partialFunctionNames: Option[List[String]],
languageParam: Option[LanguageParam],
@ -325,11 +329,10 @@ trait ResourceDocsAPIMethods extends MdcLoggable with APIMethods220 with APIMeth
Caching.memoizeSyncWithProvider (Some(cacheKey.toString())) (getDynamicResourceDocsTTL second) {
val dynamicDocs = (DynamicEntityHelper.doc ++ DynamicEndpointHelper.doc ++ DynamicEndpoints.dynamicResourceDocs)
.filter(rd => if (bankId.isDefined) rd.createdByBankId == bankId else true)
.filter(rd => rd.implementedInApiVersion == requestedApiVersion)
.map(it => it.specifiedUrl match {
case Some(_) => it
case _ =>
it.specifiedUrl = Some(s"/${it.implementedInApiVersion.urlPrefix}/${requestedApiVersion.vDottedApiVersion}${it.requestUrl}")
it.specifiedUrl = if(it.partialFunctionName.startsWith("dynamicEntity"))Some(s"/${it.implementedInApiVersion.urlPrefix}/${ApiVersion.`dynamic-entity`}${it.requestUrl}") else Some(s"/${it.implementedInApiVersion.urlPrefix}/${ApiVersion.`dynamic-endpoint`}${it.requestUrl}")
it
})
.toList
@ -581,7 +584,7 @@ trait ResourceDocsAPIMethods extends MdcLoggable with APIMethods220 with APIMeth
case _ =>
contentParam match {
case Some(DYNAMIC) =>
val dynamicDocs: Box[JValue] = getResourceDocsObpDynamicCached(requestedApiVersion, tags, partialFunctions, languageParam, contentParam, cacheModifierParam, None, isVersion4OrHigher)
val dynamicDocs: Box[JValue] = getResourceDocsObpDynamicCached(tags, partialFunctions, languageParam, contentParam, cacheModifierParam, None, isVersion4OrHigher)
Future(dynamicDocs.map(successJsonResponse(_)))
case Some(STATIC) =>
val staticDocs: Box[JValue] = getStaticResourceDocsObpCached(requestedApiVersion, tags, partialFunctions, languageParam, contentParam, cacheModifierParam, isVersion4OrHigher)
@ -632,7 +635,7 @@ trait ResourceDocsAPIMethods extends MdcLoggable with APIMethods220 with APIMeth
}
requestedApiVersion <- NewStyle.function.tryons(s"$InvalidApiVersionString $requestedApiVersionString", 400, callContext) {ApiVersionUtils.valueOf(requestedApiVersionString)}
json <- NewStyle.function.tryons(s"$UnknownError Can not create dynamic resource docs.", 400, callContext) {
getResourceDocsObpDynamicCached(requestedApiVersion, tags, partialFunctions, languageParam, contentParam, cacheModifierParam, Some(bankId), false).map(successJsonResponse(_)).get
getResourceDocsObpDynamicCached(tags, partialFunctions, languageParam, contentParam, cacheModifierParam, Some(bankId), false).map(successJsonResponse(_)).get
}
} yield {
(Full(json), HttpCode.`200`(callContext))

View File

@ -1,12 +1,12 @@
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
import code.api.UKOpenBanking.v2_0_0.JSONFactory_UKOpenBanking_200.{Account, AccountBalancesUKV200, AccountInner, AccountList, Accounts, BalanceJsonUKV200, BalanceUKOpenBankingJson, BankTransactionCodeJson, CreditLineJson, DataJsonUKV200, Links, MetaBisJson, MetaInnerJson, TransactionCodeJson, TransactionInnerJson, TransactionsInnerJson, TransactionsJsonUKV200}
import code.api.berlin.group.v1.JSONFactory_BERLIN_GROUP_1.{AccountBalanceV1, AccountBalances, AmountOfMoneyV1, ClosingBookedBody, ExpectedBody, TransactionJsonV1, TransactionsJsonV1, ViewAccount}
import code.api.dynamic.endpoint.helper.practise.PractiseEndpoint
import code.api.util.APIUtil.{defaultJValue, _}
import code.api.util.ApiRole._
import code.api.util.ExampleValue._
@ -18,6 +18,7 @@ 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.{AccountMinimalJson400, BankAttributeBankResponseJsonV400, CustomerMinimalJsonV400, FastFirehoseAccountsJsonV400, PostHistoricalTransactionAtBankJson, _}
import code.api.v3_1_0.{AccountBalanceV310, AccountsBalancesV310Json, BadLoginStatusJson, ContactDetailsJson, InviteeJson, ObpApiLoopbackJson, PhysicalCardWithAttributesJsonV310, PutUpdateCustomerEmailJsonV310, _}
import code.api.v5_0_0._
import code.branches.Branches.{Branch, DriveUpString, LobbyString}
import code.consent.ConsentStatus
import code.connectormethod.{JsonConnectorMethod, JsonConnectorMethodMethodBody}
@ -33,8 +34,8 @@ import com.openbankproject.commons.model.enums.{AttributeCategory, CardAttribute
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 java.net.URLEncoder
import code.endpointMapping.EndpointMappingCommons
import scala.collection.immutable.List
@ -178,7 +179,10 @@ object SwaggerDefinitionsJSON {
"can_see_bank_account_credit_limit",
//v400
"can_create_direct_debit",
"can_create_standing_order"
"can_create_standing_order",
//payments
"can_add_transaction_request_to_any_account"
)
)
@ -435,7 +439,7 @@ object SwaggerDefinitionsJSON {
other_bank_routing_scheme= counterpartyOtherBankRoutingSchemeExample.value,
other_bank_routing_address= counterpartyOtherBankRoutingAddressExample.value,
is_beneficiary= true,
future_date = Some("20881230")
future_date = Some(futureDateExample.value)
)
val adapterImplementationJson = AdapterImplementationJson("CORE",3)
@ -1168,7 +1172,7 @@ object SwaggerDefinitionsJSON {
end_date = DateWithDayExampleObject,
challenge = transactionRequestChallenge,
charge = transactionRequestCharge,
charge_policy = "String",
charge_policy = chargePolicyExample.value,
counterparty_id = counterpartyIdSwagger,
name = counterpartyNameExample.value,
this_bank_id = bankIdSwagger,
@ -2066,23 +2070,23 @@ object SwaggerDefinitionsJSON {
counterpartyIdJson,
amountOfMoneyJsonV121,
"A description for the transaction to the counterparty",
"SHARED",
Some("20881230")
chargePolicyExample.value,
Some(futureDateExample.value)
)
val transactionRequestBodySEPAJSON = TransactionRequestBodySEPAJSON(
amountOfMoneyJsonV121,
ibanJson,
"This is a SEPA Transaction Request",
"SHARED",
Some("20881230")
chargePolicyExample.value,
Some(futureDateExample.value)
)
val transactionRequestBodySEPAJsonV400 = TransactionRequestBodySEPAJsonV400(
amountOfMoneyJsonV121,
ibanJson,
description = "This is a SEPA Transaction Request",
charge_policy = "SHARED",
future_date = Some("20881230"),
charge_policy = chargePolicyExample.value,
future_date = Some(futureDateExample.value),
reasons = Some(List(
TransactionRequestReasonJsonV400(
code = "410",
@ -3505,13 +3509,15 @@ object SwaggerDefinitionsJSON {
key = "CUSTOMER_NUMBER",
value = "78987432"
)
val postUserAuthContextUpdateJsonV310 = PostUserAuthContextUpdateJsonV310(answer = "123")
val userAuthContextJson = UserAuthContextJson(
user_auth_context_id = "613c83ea-80f9-4560-8404-b9cd4ec42a7f",
user_id = ExampleValue.userIdExample.value,
key = "CUSTOMER_NUMBER",
value = "78987432",
timeStamp = parseDate(timeStampExample.value).getOrElse(sys.error("timeStampExample.value is not validate date format."))
time_stamp = parseDate(timeStampExample.value).getOrElse(sys.error("timeStampExample.value is not validate date format."))
)
val userAuthContextUpdateJson = UserAuthContextUpdateJson(
@ -3896,7 +3902,7 @@ object SwaggerDefinitionsJSON {
posted = DateWithSecondsExampleString,
completed= DateWithSecondsExampleString,
`type`= SANDBOX_TAN.toString,
charge_policy= "SHARED"
charge_policy= chargePolicyExample.value
)
val postHistoricalTransactionAtBankJson = PostHistoricalTransactionAtBankJson(
from_account_id = "",
@ -3906,7 +3912,7 @@ object SwaggerDefinitionsJSON {
posted = DateWithSecondsExampleString,
completed= DateWithSecondsExampleString,
`type`= SANDBOX_TAN.toString,
charge_policy= "SHARED"
charge_policy = chargePolicyExample.value
)
val postHistoricalTransactionResponseJson = PostHistoricalTransactionResponseJson(
@ -3918,7 +3924,7 @@ object SwaggerDefinitionsJSON {
posted = DateWithMsExampleObject,
completed= DateWithMsExampleObject,
transaction_request_type= SANDBOX_TAN.toString,
charge_policy= "SHARED"
charge_policy = chargePolicyExample.value
)
val viewBasicCommons = ViewBasic(
@ -4318,6 +4324,27 @@ object SwaggerDefinitionsJSON {
challenges = List(challengeJsonV400),
charge = transactionRequestChargeJsonV200
)
val postSimpleCounterpartyJson400 = PostSimpleCounterpartyJson400(
name = counterpartyNameExample.value,
description = transactionDescriptionExample.value,
other_account_routing_scheme = counterpartyOtherAccountRoutingSchemeExample.value,
other_account_routing_address = counterpartyOtherAccountRoutingAddressExample.value,
other_account_secondary_routing_scheme = counterpartyOtherAccountSecondaryRoutingSchemeExample.value,
other_account_secondary_routing_address = counterpartyOtherAccountSecondaryRoutingAddressExample.value,
other_bank_routing_scheme = counterpartyOtherBankRoutingSchemeExample.value,
other_bank_routing_address = counterpartyOtherBankRoutingAddressExample.value,
other_branch_routing_scheme = counterpartyOtherBranchRoutingSchemeExample.value,
other_branch_routing_address = counterpartyOtherBranchRoutingAddressExample.value
)
val transactionRequestBodySimpleJsonV400 = TransactionRequestBodySimpleJsonV400(
to= postSimpleCounterpartyJson400,
amountOfMoneyJsonV121,
descriptionExample.value,
chargePolicyExample.value,
Some(futureDateExample.value)
)
val postApiCollectionJson400 = PostApiCollectionJson400(apiCollectionNameExample.value, true, Some(descriptionExample.value))
@ -4570,7 +4597,7 @@ object SwaggerDefinitionsJSON {
messages = List(customerMessageJsonV400)
)
val requestRootJsonClass = dynamic.practise.PractiseEndpoint.RequestRootJsonClass(name = nameExample.value, age=ageExample.value.toLong, Nil)
val requestRootJsonClass = PractiseEndpoint.RequestRootJsonClass(name = nameExample.value, age=ageExample.value.toLong, Nil)
val entitlementJsonV400 = EntitlementJsonV400(
entitlement_id = entitlementIdExample.value,
@ -4583,6 +4610,48 @@ object SwaggerDefinitionsJSON {
list = List(entitlementJsonV400)
)
val accountNotificationWebhookPostJson = AccountNotificationWebhookPostJson(
url = "https://localhost.openbankproject.com",
http_method = "POST",
http_protocol = "HTTP/1.1"
)
val systemAccountNotificationWebhookJson = SystemAccountNotificationWebhookJson(
webhook_id = "fc23a7e2-7dd2-4bdf-a0b4-ae31232a4762",
trigger_name = ApiTrigger.onCreateTransaction.toString(),
url = "https://localhost.openbankproject.com",
http_method = "POST",
http_protocol = "HTTP/1.1",
created_by_user_id = ExampleValue.userIdExample.value
)
val bankAccountNotificationWebhookJson = BankAccountNotificationWebhookJson(
webhook_id = "fc23a7e2-7dd2-4bdf-a0b4-ae31232a4762",
bank_id = bankIdExample.value,
trigger_name = ApiTrigger.onCreateTransaction.toString(),
url = "https://localhost.openbankproject.com",
http_method = "POST",
http_protocol = "HTTP/1.1",
created_by_user_id = ExampleValue.userIdExample.value
)
val userAuthContextJsonV500 = UserAuthContextJsonV500(
user_auth_context_id = "613c83ea-80f9-4560-8404-b9cd4ec42a7f",
user_id = ExampleValue.userIdExample.value,
key = "CUSTOMER_NUMBER",
value = "78987432",
time_stamp = parseDate(timeStampExample.value).getOrElse(sys.error("timeStampExample.value is not validate date format.")),
consumer_id = consumerIdExample.value
)
val userAuthContextUpdateJsonV500 = UserAuthContextUpdateJsonV500(
user_auth_context_update_id = "613c83ea-80f9-4560-8404-b9cd4ec42a7f",
user_id = ExampleValue.userIdExample.value,
key = "CUSTOMER_NUMBER",
value = "78987432",
status = UserAuthContextUpdateStatus.INITIATED.toString,
consumer_id = consumerIdExample.value
)
//The common error or success format.
//Just some helper format to use in Json

View File

@ -26,6 +26,7 @@ object Constant extends MdcLoggable {
final val SYSTEM_AUDITOR_VIEW_ID = "auditor"
final val SYSTEM_ACCOUNTANT_VIEW_ID = "accountant"
final val SYSTEM_FIREHOSE_VIEW_ID = "firehose"
final val SYSTEM_SMALL_PAYMENT_VERIFIED_VIEW_ID = "SmallPaymentVerified"
final val SYSTEM_READ_ACCOUNTS_BASIC_VIEW_ID = "ReadAccountsBasic"
final val SYSTEM_READ_ACCOUNTS_DETAIL_VIEW_ID = "ReadAccountsDetail"
final val SYSTEM_READ_BALANCES_VIEW_ID = "ReadBalances"
@ -38,8 +39,9 @@ object Constant extends MdcLoggable {
final val SYSTEM_READ_TRANSACTIONS_BERLIN_GROUP_VIEW_ID = "ReadTransactionsBerlinGroup"
//These are the default incoming and outgoing account ids. we will create both during the boot.scala.
final val INCOMING_ACCOUNT_ID= "OBP_DEFAULT_INCOMING_ACCOUNT_ID"
final val OUTGOING_ACCOUNT_ID= "OBP_DEFAULT_OUTGOING_ACCOUNT_ID"
final val INCOMING_SETTLEMENT_ACCOUNT_ID = "OBP-INCOMING-SETTLEMENT-ACCOUNT"
final val OUTGOING_SETTLEMENT_ACCOUNT_ID = "OBP-OUTGOING-SETTLEMENT-ACCOUNT"
final val ALL_CONSUMERS = "ALL_CONSUMERS"
}

View File

@ -0,0 +1,211 @@
package code.api.dynamic.endpoint
import code.DynamicData.{DynamicData, DynamicDataProvider}
import code.api.dynamic.endpoint.helper.{DynamicEndpointHelper, DynamicEntityHelper, DynamicEntityInfo, EntityName, MockResponseHolder}
import code.api.dynamic.endpoint.helper.DynamicEndpointHelper.DynamicReq
import code.api.dynamic.endpoint.helper.MockResponseHolder
import code.api.util.APIUtil.{fullBoxOrException, _}
import code.api.util.ErrorMessages._
import code.api.util.NewStyle.HttpCode
import code.api.util._
import code.endpointMapping.EndpointMappingCommons
import code.transactionrequests.TransactionRequests.TransactionRequestTypes.{apply => _}
import code.util.Helper
import com.openbankproject.commons.ExecutionContext.Implicits.global
import com.openbankproject.commons.model._
import com.openbankproject.commons.model.enums.DynamicEntityOperation._
import com.openbankproject.commons.model.enums._
import com.openbankproject.commons.util.{ApiVersion, JsonUtils}
import net.liftweb.common._
import net.liftweb.http.rest.RestHelper
import net.liftweb.http.{JsonResponse, Req}
import net.liftweb.json.JsonAST.JValue
import net.liftweb.json.JsonDSL._
import net.liftweb.json._
import net.liftweb.util.StringHelpers
import org.apache.commons.lang3.StringUtils
import scala.collection.immutable.List
import scala.collection.mutable.ArrayBuffer
import scala.concurrent.Future
trait APIMethodsDynamicEndpoint {
self: RestHelper =>
val ImplementationsDynamicEndpoint = new ImplementationsDynamicEndpoint()
class ImplementationsDynamicEndpoint {
val implementedInApiVersion = ApiVersion.`dynamic-endpoint`
private val staticResourceDocs = ArrayBuffer[ResourceDoc]()
// createDynamicEntityDoc and updateDynamicEntityDoc are dynamic, So here dynamic create resourceDocs
def resourceDocs = staticResourceDocs
val apiRelations = ArrayBuffer[ApiRelation]()
val codeContext = CodeContext(staticResourceDocs, apiRelations)
private def unboxResult[T: Manifest](box: Box[T], entityName: String): T = {
if (box.isInstanceOf[Failure]) {
val failure = box.asInstanceOf[Failure]
// change the internal db column name 'dynamicdataid' to entity's id name
val msg = failure.msg.replace(DynamicData.DynamicDataId.dbColumnName, StringUtils.uncapitalize(entityName) + "Id")
val changedMsgFailure = failure.copy(msg = s"$InternalServerError $msg")
fullBoxOrException[T](changedMsgFailure)
}
box.openOrThrowException("impossible error")
}
lazy val dynamicEndpoint: OBPEndpoint = {
case DynamicReq(url, json, method, params, pathParams, role, operationId, mockResponse, bankId) => { cc =>
// process before authentication interceptor, get intercept result
val resourceDoc = DynamicEndpointHelper.doc.find(_.operationId == operationId)
val callContext = cc.copy(operationId = Some(operationId), resourceDocument = resourceDoc)
val beforeInterceptResult: Box[JsonResponse] = beforeAuthenticateInterceptResult(Option(callContext), operationId)
if (beforeInterceptResult.isDefined) beforeInterceptResult
else for {
(Full(u), callContext) <- authenticatedAccess(callContext) // Inject operationId into Call Context. It's used by Rate Limiting.
_ <- NewStyle.function.hasEntitlement(bankId.getOrElse(""), u.userId, role, callContext)
// validate request json payload
httpRequestMethod = cc.verb
path = StringUtils.substringAfter(cc.url, DynamicEndpointHelper.urlPrefix)
// process after authentication interceptor, get intercept result
jsonResponse: Box[ErrorMessage] = afterAuthenticateInterceptResult(callContext, operationId).collect({
case JsonResponseExtractor(message, code) => ErrorMessage(code, message)
})
_ <- Helper.booleanToFuture(failMsg = jsonResponse.map(_.message).orNull, failCode = jsonResponse.map(_.code).openOr(400), cc = callContext) {
jsonResponse.isEmpty
}
(box, callContext) <- if (DynamicEndpointHelper.isDynamicEntityResponse(url)) {
for {
(endpointMapping, callContext) <- if (DynamicEndpointHelper.isDynamicEntityResponse(url)) {
NewStyle.function.getEndpointMappingByOperationId(bankId, operationId, cc.callContext)
} else {
Future.successful((EndpointMappingCommons(None, "", "", "", None), callContext))
}
requestMappingString = endpointMapping.requestMapping
requestMappingJvalue = net.liftweb.json.parse(requestMappingString)
responseMappingString = endpointMapping.responseMapping
responseMappingJvalue = net.liftweb.json.parse(responseMappingString)
responseBody <- if (method.value.equalsIgnoreCase("get")) {
for {
(entityName, entityIdKey, entityIdValueFromUrl) <- NewStyle.function.tryons(s"$InvalidEndpointMapping `response_mapping` must be linked to at least one valid dynamic entity!", 400, cc.callContext) {
DynamicEndpointHelper.getEntityNameKeyAndValue(responseMappingString, pathParams)
}
dynamicData <- Future {
DynamicDataProvider.connectorMethodProvider.vend.getAll(entityName)
}
dynamicJsonData = JArray(dynamicData.map(it => net.liftweb.json.parse(it.dataJson)).map(_.asInstanceOf[JObject]))
// //We only get the value, but not sure the field name of it.
// // we can get the field name from the mapping: `primary_query_key`
// //requestBodyMapping --> Convert `RequestJson` --> `DynamicEntity Model.`
// targetRequestBody = JsonUtils.buildJson(json, requestBodySchemeJvalue)
// requestBody = targetRequestBody match {
// case j@JObject(_) => Some(j)
// case _ => None
// }
result = if (method.value.equalsIgnoreCase("get") && entityIdValueFromUrl.isDefined) {
DynamicEndpointHelper.getObjectByKeyValuePair(dynamicJsonData, entityIdKey, entityIdValueFromUrl.get)
} else {
val newParams = DynamicEndpointHelper.convertToMappingQueryParams(responseMappingJvalue, params)
DynamicEndpointHelper.getObjectsByParams(dynamicJsonData, newParams)
}
responseBodyScheme = DynamicEndpointHelper.prepareMappingFields(responseMappingJvalue)
responseBody = JsonUtils.buildJson(result, responseBodyScheme)
} yield {
responseBody
}
} else if (method.value.equalsIgnoreCase("post")) {
for {
(entityName, entityIdKey, entityIdValueFromUrl) <- NewStyle.function.tryons(s"$InvalidEndpointMapping `response_mapping` must be linked to at least one valid dynamic entity!", 400, cc.callContext) {
DynamicEndpointHelper.getEntityNameKeyAndValue(responseMappingString, pathParams)
}
//build the entity body according to the request json and mapping
entityBody = JsonUtils.buildJson(json, requestMappingJvalue)
(box, _) <- NewStyle.function.invokeDynamicConnector(CREATE, entityName, Some(entityBody.asInstanceOf[JObject]), None, None, None, Some(cc))
singleObject: JValue = unboxResult(box.asInstanceOf[Box[JValue]], entityName)
responseBodyScheme = DynamicEndpointHelper.prepareMappingFields(responseMappingJvalue)
responseBody = JsonUtils.buildJson(singleObject, responseBodyScheme)
} yield {
responseBody
}
} else if (method.value.equalsIgnoreCase("delete")) {
for {
(entityName, entityIdKey, entityIdValueFromUrl) <- NewStyle.function.tryons(s"$InvalidEndpointMapping `response_mapping` must be linked to at least one valid dynamic entity!", 400, cc.callContext) {
DynamicEndpointHelper.getEntityNameKeyAndValue(responseMappingString, pathParams)
}
dynamicData = DynamicDataProvider.connectorMethodProvider.vend.getAll(entityName)
dynamicJsonData = JArray(dynamicData.map(it => net.liftweb.json.parse(it.dataJson)).map(_.asInstanceOf[JObject]))
entityObject = DynamicEndpointHelper.getObjectByKeyValuePair(dynamicJsonData, entityIdKey, entityIdValueFromUrl.get)
isDeleted <- NewStyle.function.tryons(s"$InvalidEndpointMapping `response_mapping` must be linked to at least one valid dynamic entity!", 400, cc.callContext) {
val entityIdName = DynamicEntityHelper.createEntityId(entityName)
val entityIdValue = (entityObject \ entityIdName).asInstanceOf[JString].s
DynamicDataProvider.connectorMethodProvider.vend.delete(entityName, entityIdValue).head
}
} yield {
JBool(isDeleted)
}
} else if (method.value.equalsIgnoreCase("put")) {
for {
(entityName, entityIdKey, entityIdValueFromUrl) <- NewStyle.function.tryons(s"$InvalidEndpointMapping `response_mapping` must be linked to at least one valid dynamic entity!", 400, cc.callContext) {
DynamicEndpointHelper.getEntityNameKeyAndValue(responseMappingString, pathParams)
}
dynamicData = DynamicDataProvider.connectorMethodProvider.vend.getAll(entityName)
dynamicJsonData = JArray(dynamicData.map(it => net.liftweb.json.parse(it.dataJson)).map(_.asInstanceOf[JObject]))
entityObject = DynamicEndpointHelper.getObjectByKeyValuePair(dynamicJsonData, entityIdKey, entityIdValueFromUrl.get)
_ <- NewStyle.function.tryons(s"$InvalidEndpointMapping `response_mapping` must be linked to at least one valid dynamic entity!", 400, cc.callContext) {
val entityIdName = DynamicEntityHelper.createEntityId(entityName)
val entityIdValue = (entityObject \ entityIdName).asInstanceOf[JString].s
DynamicDataProvider.connectorMethodProvider.vend.delete(entityName, entityIdValue).head
}
entityBody = JsonUtils.buildJson(json, requestMappingJvalue)
(box, _) <- NewStyle.function.invokeDynamicConnector(CREATE, entityName, Some(entityBody.asInstanceOf[JObject]), None, None, None, Some(cc))
singleObject: JValue = unboxResult(box.asInstanceOf[Box[JValue]], entityName)
responseBodyScheme = DynamicEndpointHelper.prepareMappingFields(responseMappingJvalue)
responseBody = JsonUtils.buildJson(singleObject, responseBodyScheme)
} yield {
responseBody
}
} else {
NewStyle.function.tryons(s"$InvalidEndpointMapping `request_mapping` must be linked to at least one valid dynamic entity!", 400, cc.callContext) {
DynamicEndpointHelper.getEntityNameKeyAndValue(responseMappingString, pathParams)
}
throw new RuntimeException(s"$NotImplemented We only support the Http Methods GET and POST . The current method is: ${method.value}")
}
} yield {
(Full(("code", 200) ~ ("value", responseBody)), callContext)
}
} else {
MockResponseHolder.init(mockResponse) { // if target url domain is `obp_mock`, set mock response to current thread
NewStyle.function.dynamicEndpointProcess(url, json, method, params, pathParams, callContext)
}
}
} yield {
box match {
case Full(v) =>
val code = (v \ "code").asInstanceOf[JInt].num.toInt
(v \ "value", callContext.map(_.copy(httpCode = Some(code))))
case e: Failure =>
val changedMsgFailure = e.copy(msg = s"$InternalServerError ${e.msg}")
fullBoxOrException[JValue](changedMsgFailure)
??? // will not execute to here, Because the failure message is thrown by upper line.
}
}
}
}
}
}
object APIMethodsDynamicEndpoint extends RestHelper with APIMethodsDynamicEndpoint {
lazy val newStyleEndpoints: List[(String, String)] = ImplementationsDynamicEndpoint.resourceDocs.map {
rd => (rd.partialFunctionName, rd.implementedInApiVersion.toString())
}.toList
}

View File

@ -0,0 +1,89 @@
/**
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.dynamic.endpoint
import APIMethodsDynamicEndpoint.ImplementationsDynamicEndpoint
import code.api.OBPRestHelper
import code.api.dynamic.endpoint.helper.DynamicEndpoints
import code.api.util.APIUtil.OBPEndpoint
import code.api.util.{APIUtil, VersionedOBPApis}
import code.api.v5_0_0.OBPAPI5_0_0.{allResourceDocs, apiPrefix, registerRoutes, routes}
import code.util.Helper.MdcLoggable
import com.openbankproject.commons.util.ApiVersion
import net.liftweb.common.{Box, Full}
import net.liftweb.http.{LiftResponse, PlainTextResponse}
import org.apache.http.HttpStatus
/*
This file defines which endpoints from all the versions are available in v4.0.0
*/
object OBPAPIDynamicEndpoint extends OBPRestHelper with MdcLoggable with VersionedOBPApis{
val version : ApiVersion = ApiVersion.`dynamic-endpoint`
val versionStatus = "BLEEDING-EDGE" // TODO this should be a property of ApiVersion.
// if old version ResourceDoc objects have the same name endpoint with new version, omit old version ResourceDoc.
def allResourceDocs = collectResourceDocs(ImplementationsDynamicEndpoint.resourceDocs)
val routes : List[OBPEndpoint] = List(APIUtil.dynamicEndpointStub,
//This is for the dynamic endpoints which are created by dynamic swagger files
ImplementationsDynamicEndpoint.dynamicEndpoint,
/**
* Here is the place where we register the dynamicEndpoint, all the dynamic resource docs endpoints are here.
* Actually, we only register one endpoint for all the dynamic resource docs endpoints.
* For Liftweb, it just need to handle one endpoint,
* all the router functionalities are in OBP code.
* details: please also check code/api/vDynamic/dynamic/DynamicEndpoints.findEndpoint method
* NOTE: this must be the last one endpoint to register into Liftweb
* Because firstly, Liftweb should look for the static endpoints --> then the dynamic ones.
* This is for the dynamic endpoints which are createdy by dynamic resourceDocs
*/
DynamicEndpoints.dynamicEndpoint
)
routes.map(endpoint => oauthServe(apiPrefix{endpoint}, None))
logger.info(s"version $version has been run! There are ${routes.length} routes.")
// specified response for OPTIONS request.
private val corsResponse: Box[LiftResponse] = Full{
val corsHeaders = List(
"Access-Control-Allow-Origin" -> "*",
"Access-Control-Allow-Methods" -> "GET, POST, OPTIONS, PUT, PATCH, DELETE",
"Access-Control-Allow-Headers" -> "*",
"Access-Control-Allow-Credentials" -> "true",
"Access-Control-Max-Age" -> "1728000" //Tell client that this pre-flight info is valid for 20 days
)
PlainTextResponse("", corsHeaders, HttpStatus.SC_NO_CONTENT)
}
/*
* process OPTIONS http request, just return no content and status is 204
*/
this.serve({
case req if req.requestType.method == "OPTIONS" => corsResponse
})
}

View File

@ -1,4 +1,4 @@
package code.api.v4_0_0.dynamic
package code.api.dynamic.endpoint.helper
import code.api.util.APIUtil.{OBPEndpoint, OBPReturnType, futureToBoxedResponse, scalaFutureToLaFuture}
import code.api.util.DynamicUtil.{Sandbox, Validation}

View File

@ -1,4 +1,4 @@
package code.api.v4_0_0.dynamic
package code.api.dynamic.endpoint.helper
import akka.http.scaladsl.model.{HttpMethods, HttpMethod => AkkaHttpMethod}
import code.DynamicData.{DynamicDataProvider, DynamicDataT}
@ -7,7 +7,7 @@ import code.api.util.APIUtil.{BigDecimalBody, BigIntBody, BooleanBody, DoubleBod
import code.api.util.ApiTag._
import code.api.util.ErrorMessages.{DynamicDataNotFound, InvalidUrlParameters, UnknownError, UserHasMissingRoles, UserNotLoggedIn}
import code.api.util.{APIUtil, ApiRole, ApiTag, CustomJsonFormats, NewStyle}
import com.openbankproject.commons.util.ApiVersion
import com.openbankproject.commons.util.{ApiShortVersions, ApiStandards, ApiVersion}
import com.openbankproject.commons.util.Functions.Memo
import io.swagger.v3.oas.models.PathItem.HttpMethod
import io.swagger.v3.oas.models.media._
@ -50,7 +50,7 @@ object DynamicEndpointHelper extends RestHelper {
/**
* dynamic endpoints url prefix
*/
val urlPrefix = APIUtil.getPropsValue("dynamic_endpoints_url_prefix", "dynamic")
val urlPrefix = APIUtil.getPropsValue("dynamic_endpoints_url_prefix", "")
private val implementedInApiVersion = ApiVersion.v4_0_0
private val IsDynamicEntityUrl = """.*dynamic_entity.*"""
private val IsMockUrlString = """.*obp_mock(?::\d+)?.*"""
@ -173,15 +173,18 @@ object DynamicEndpointHelper extends RestHelper {
* @return (adapterUrl, requestBodyJson, httpMethod, requestParams, pathParams, role, operationId, mockResponseCode->mockResponseBody)
*/
def unapply(r: Req): Option[(String, JValue, AkkaHttpMethod, Map[String, List[String]], Map[String, String], ApiRole, String, Option[(Int, JValue)], Option[String])] = {
val partPath = r.path.partPath//eg: List("dynamic","feature-test")
if (!testResponse_?(r) || partPath.headOption != Option(urlPrefix))//if check the Content-Type contains json or not, and check the if it is the `dynamic_endpoints_url_prefix`
val requestUri = r.request.uri //eg: `/obp/dynamic-endpoint/fashion-brand-list/BRAND_ID`
val partPath = r.path.partPath //eg: List("fashion-brand-list","BRAND_ID"), the dynamic is from OBP URL, not in the partPath now.
if (!testResponse_?(r) || !requestUri.startsWith(s"/${ApiStandards.obp.toString}/${ApiShortVersions.`dynamic-endpoint`.toString}"+urlPrefix))//if check the Content-Type contains json or not, and check the if it is the `dynamic_endpoints_url_prefix`
None //if do not match `URL and Content-Type`, then can not find this endpoint. return None.
else {
val akkaHttpMethod = HttpMethods.getForKeyCaseInsensitive(r.requestType.method).get
val httpMethod = HttpMethod.valueOf(r.requestType.method)
val urlQueryParameters = r.params
// url that match original swagger endpoint.
val url = partPath.tail.mkString("/", "/", "") // eg: --> /feature-test
val url = partPath.mkString("/", "/", "") // eg: --> /feature-test
val foundDynamicEndpoint: Option[(String, String, Int, ResourceDoc, Option[String])] = dynamicEndpointInfos
.map(_.findDynamicEndpoint(httpMethod, url))
.collectFirst {
@ -195,7 +198,7 @@ object DynamicEndpointHelper extends RestHelper {
val pathParams: Map[String, String] = if(endpointUrl == url) {
Map.empty[String, String]
} else {
val tuples: Array[(String, String)] = StringUtils.split(endpointUrl, "/").zip(partPath.tail)
val tuples: Array[(String, String)] = StringUtils.split(endpointUrl, "/").zip(partPath)
tuples.collect {
case (ExpressionRegx(name), value) => name->value
}.toMap

View File

@ -1,8 +1,10 @@
package code.api.v4_0_0.dynamic
package code.api.dynamic.endpoint.helper
import code.api.dynamic.endpoint.helper.practise.{DynamicEndpointCodeGenerator, PractiseEndpointGroup}
import code.api.dynamic.endpoint.helper.practise.PractiseEndpointGroup
import code.api.util.DynamicUtil.{Sandbox, Validation}
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}
import net.liftweb.http.{JsonResponse, Req}
import net.liftweb.json.{JNothing, JValue}

View File

@ -0,0 +1,443 @@
package code.api.dynamic.endpoint.helper
import code.api.util.APIUtil.{EmptyBody, ResourceDoc, authenticationRequiredMessage, generateUUID}
import code.api.util.ApiRole.getOrCreateDynamicApiRole
import code.api.util.ApiTag._
import code.api.util.ErrorMessages.{InvalidJsonFormat, UnknownError, UserHasMissingRoles, UserNotLoggedIn}
import code.api.util._
import com.openbankproject.commons.model.enums.{DynamicEntityFieldType, DynamicEntityOperation}
import com.openbankproject.commons.util.ApiVersion
import net.liftweb.json.JsonDSL._
import net.liftweb.json._
import net.liftweb.util.StringHelpers
import org.apache.commons.lang3.StringUtils
import scala.collection.immutable.{List, Nil}
import scala.collection.mutable
import scala.collection.mutable.ArrayBuffer
object EntityName {
// unapply result structure: (BankId, entityName, id)
def unapply(url: List[String]): Option[(Option[String], String, String)] = url match {
//no bank:
//eg: /FooBar21
case entityName :: Nil =>
DynamicEntityHelper.definitionsMap.find(definitionMap => definitionMap._1 == entityName && definitionMap._2.bankId.isEmpty)
.map(_ => (None, entityName, ""))
//eg: /FooBar21/FOO_BAR21_ID
case entityName :: id :: Nil =>
DynamicEntityHelper.definitionsMap.find(definitionMap => definitionMap._1 == entityName && definitionMap._2.bankId.isEmpty)
.map(_ => (None, entityName, id))
//contains Bank:
//eg: /Banks/BANK_ID/FooBar21
case "banks" :: bankId :: entityName :: Nil =>
DynamicEntityHelper.definitionsMap.find(definitionMap => definitionMap._1 == entityName && definitionMap._2.bankId == Some(bankId))
.map(_ => (Some(bankId), entityName, ""))
//eg: /Banks/BANK_ID/FooBar21/FOO_BAR21_ID
case "banks" :: bankId :: entityName :: id :: Nil =>
DynamicEntityHelper.definitionsMap.find(definitionMap => definitionMap._1 == entityName && definitionMap._2.bankId == Some(bankId))
.map(_ => (Some(bankId),entityName, id))
case _ => None
}
}
object DynamicEntityHelper {
private val implementedInApiVersion = ApiVersion.v4_0_0
def definitionsMap: Map[String, DynamicEntityInfo] = NewStyle.function.getDynamicEntities(None).map(it => (it.entityName, DynamicEntityInfo(it.metadataJson, it.entityName, it.bankId))).toMap
def dynamicEntityRoles: List[String] = NewStyle.function.getDynamicEntities(None).flatMap(dEntity => DynamicEntityInfo.roleNames(dEntity.entityName, dEntity.bankId))
def doc: ArrayBuffer[ResourceDoc] = {
val docs = operationToResourceDoc.values.toList
collection.mutable.ArrayBuffer(docs:_*)
}
def createEntityId(entityName: String) = {
// (?<=[a-z0-9])(?=[A-Z]) --> mean `Positive Lookbehind (?<=[a-z0-9])` && Positive Lookahead (?=[A-Z]) --> So we can find the space to replace to `_`
val regexPattern = "(?<=[a-z0-9])(?=[A-Z])|-"
// eg: entityName = PetEntity => entityIdName = pet_entity_id
s"${entityName}_Id".replaceAll(regexPattern, "_").toLowerCase
}
def operationToResourceDoc: Map[(DynamicEntityOperation, String), ResourceDoc] = {
val addPrefix = APIUtil.getPropsAsBoolValue("dynamic_entities_have_prefix", true)
// record exists tag names, to avoid duplicated dynamic tag name.
var existsTagNames = ApiTag.staticTagNames
// match string that start with _, e.g: "_abc"
val Regex = "(_+)(.+)".r
//convert entity name to tag name, example:
// Csem-case -> Csem Case
// _Csem-case -> _Csem Case
// Csem_case -> Csem Case
// _Csem_case -> _Csem Case
// csem-case -> Csem Case
def prettyTagName(s: String) = s.capitalize.split("(?<=[^-_])[-_]+").reduceLeft(_ + " " + _.capitalize)
def apiTag(entityName: String, singularName: String): ResourceDocTag = {
val existsSameStaticEntity: Boolean = existsTagNames
.exists(it => it.equalsIgnoreCase(singularName) || it.equalsIgnoreCase(entityName))
val tagName = if(addPrefix || existsSameStaticEntity) {
var name = singularName match {
case Regex(a,b) => s"$a${b.capitalize}"
case v => s"_${v.capitalize}"
}
while(existsTagNames.exists(it => it.equalsIgnoreCase(name))) {
name = s"_$name"
}
prettyTagName(name)
} else {
prettyTagName(singularName.capitalize)
}
existsTagNames += tagName
ApiTag(tagName)
}
val fun: DynamicEntityInfo => mutable.Map[(DynamicEntityOperation, String), ResourceDoc] = createDocs(apiTag)
val docs: Iterable[((DynamicEntityOperation, String), ResourceDoc)] = definitionsMap.values.flatMap(fun)
docs.toMap
}
// TODO the requestBody and responseBody is not correct ref type
/**
*
* @param fun (singularName, entityName) => ResourceDocTag
* @param dynamicEntityInfo dynamicEntityInfo
* @return all ResourceDoc of given dynamicEntity
*/
private def createDocs(fun: (String, String) => ResourceDocTag)
(dynamicEntityInfo: DynamicEntityInfo): mutable.Map[(DynamicEntityOperation, String), ResourceDoc] = {
val entityName = dynamicEntityInfo.entityName
// e.g: "someMultiple-part_Name" -> ["Some", "Multiple", "Part", "Name"]
val capitalizedNameParts = entityName.split("(?<=[a-z0-9])(?=[A-Z])|-|_").map(_.capitalize).filterNot(_.trim.isEmpty)
val splitName = capitalizedNameParts.mkString(" ")
val idNameInUrl = StringHelpers.snakify(dynamicEntityInfo.idName).toUpperCase()
val listName = dynamicEntityInfo.listName
val bankId = dynamicEntityInfo.bankId
val resourceDocUrl = if(bankId.isDefined) s"/banks/BANK_ID/$entityName" else s"/$entityName"
val endPoint = APIUtil.dynamicEndpointStub
// (operationType, entityName) -> ResourceDoc
val resourceDocs = scala.collection.mutable.Map[(DynamicEntityOperation, String),ResourceDoc]()
val apiTag: ResourceDocTag = fun(splitName, entityName)
resourceDocs += (DynamicEntityOperation.GET_ALL, entityName) -> ResourceDoc(
endPoint,
implementedInApiVersion,
buildGetAllFunctionName(entityName),
"GET",
s"$resourceDocUrl",
s"Get $splitName List",
s"""Get $splitName List.
|${dynamicEntityInfo.description}
|
|${dynamicEntityInfo.fieldsDescription}
|
|${methodRoutingExample(entityName)}
|
|${authenticationRequiredMessage(true)}
|
|Can do filter on the fields
|e.g: /${entityName}?name=James%20Brown&number=123.456&number=11.11
|Will do filter by this rule: name == "James Brown" && (number==123.456 || number=11.11)
|""".stripMargin,
EmptyBody,
dynamicEntityInfo.getExampleList,
List(
UserNotLoggedIn,
UserHasMissingRoles,
UnknownError
),
List(apiTag, apiTagNewStyle, apiTagDynamicEntity, apiTagDynamic),
Some(List(dynamicEntityInfo.canGetRole)),
createdByBankId= dynamicEntityInfo.bankId
)
resourceDocs += (DynamicEntityOperation.GET_ONE, entityName) -> ResourceDoc(
endPoint,
implementedInApiVersion,
buildGetOneFunctionName(entityName),
"GET",
s"$resourceDocUrl/$idNameInUrl",
s"Get $splitName by id",
s"""Get $splitName by id.
|${dynamicEntityInfo.description}
|
|${dynamicEntityInfo.fieldsDescription}
|
|${methodRoutingExample(entityName)}
|
|${authenticationRequiredMessage(true)}
|""".stripMargin,
EmptyBody,
dynamicEntityInfo.getSingleExample,
List(
UserNotLoggedIn,
UserHasMissingRoles,
UnknownError
),
List(apiTag, apiTagNewStyle, apiTagDynamicEntity, apiTagDynamic),
Some(List(dynamicEntityInfo.canGetRole)),
createdByBankId= dynamicEntityInfo.bankId
)
resourceDocs += (DynamicEntityOperation.CREATE, entityName) -> ResourceDoc(
endPoint,
implementedInApiVersion,
buildCreateFunctionName(entityName),
"POST",
s"$resourceDocUrl",
s"Create new $splitName",
s"""Create new $splitName.
|${dynamicEntityInfo.description}
|
|${dynamicEntityInfo.fieldsDescription}
|
|${methodRoutingExample(entityName)}
|
|${authenticationRequiredMessage(true)}
|
|""",
dynamicEntityInfo.getSingleExampleWithoutId,
dynamicEntityInfo.getSingleExample,
List(
UserNotLoggedIn,
UserHasMissingRoles,
InvalidJsonFormat,
UnknownError
),
List(apiTag, apiTagNewStyle, apiTagDynamicEntity, apiTagDynamic),
Some(List(dynamicEntityInfo.canCreateRole)),
createdByBankId= dynamicEntityInfo.bankId
)
resourceDocs += (DynamicEntityOperation.UPDATE, entityName) -> ResourceDoc(
endPoint,
implementedInApiVersion,
buildUpdateFunctionName(entityName),
"PUT",
s"$resourceDocUrl/$idNameInUrl",
s"Update $splitName",
s"""Update $splitName.
|${dynamicEntityInfo.description}
|
|${dynamicEntityInfo.fieldsDescription}
|
|${methodRoutingExample(entityName)}
|
|${authenticationRequiredMessage(true)}
|
|""",
dynamicEntityInfo.getSingleExampleWithoutId,
dynamicEntityInfo.getSingleExample,
List(
UserNotLoggedIn,
UserHasMissingRoles,
InvalidJsonFormat,
UnknownError
),
List(apiTag, apiTagNewStyle, apiTagDynamicEntity, apiTagDynamic),
Some(List(dynamicEntityInfo.canUpdateRole)),
createdByBankId= dynamicEntityInfo.bankId
)
resourceDocs += (DynamicEntityOperation.DELETE, entityName) -> ResourceDoc(
endPoint,
implementedInApiVersion,
buildDeleteFunctionName(entityName),
"DELETE",
s"$resourceDocUrl/$idNameInUrl",
s"Delete $splitName by id",
s"""Delete $splitName by id
|
|${methodRoutingExample(entityName)}
|
|${authenticationRequiredMessage(true)}
|
|""",
dynamicEntityInfo.getSingleExampleWithoutId,
dynamicEntityInfo.getSingleExample,
List(
UserNotLoggedIn,
UserHasMissingRoles,
InvalidJsonFormat,
UnknownError
),
List(apiTag, apiTagNewStyle, apiTagDynamicEntity, apiTagDynamic),
Some(List(dynamicEntityInfo.canDeleteRole)),
createdByBankId= dynamicEntityInfo.bankId
)
resourceDocs
}
private def buildCreateFunctionName(entityName: String) = s"dynamicEntity_create$entityName"
private def buildUpdateFunctionName(entityName: String) = s"dynamicEntity_update$entityName"
private def buildDeleteFunctionName(entityName: String) = s"dynamicEntity_delete$entityName"
private def buildGetOneFunctionName(entityName: String) = s"dynamicEntity_getSingle$entityName"
private def buildGetAllFunctionName(entityName: String) = s"dynamicEntity_get${entityName}List"
@inline
private def buildOperationId(entityName: String, fun: String => String): String = {
APIUtil.buildOperationId(implementedInApiVersion, fun(entityName))
}
def buildCreateOperationId(entityName: String) = buildOperationId(entityName, buildCreateFunctionName)
def buildUpdateOperationId(entityName: String) = buildOperationId(entityName, buildUpdateFunctionName)
def buildDeleteOperationId(entityName: String) = buildOperationId(entityName, buildDeleteFunctionName)
def buildGetOneOperationId(entityName: String) = buildOperationId(entityName, buildGetOneFunctionName)
def buildGetAllOperationId(entityName: String) = buildOperationId(entityName, buildGetAllFunctionName)
private def methodRoutingExample(entityName: String) =
s"""
|MethodRouting settings example:
|
|<details>
|
|```
|{
| "is_bank_id_exact_match":false,
| "method_name":"dynamicEntityProcess",
| "connector_name":"rest_vMar2019",
| "bank_id_pattern":".*",
| "parameters":[
| {
| "key":"entityName",
| "value":"$entityName"
| }
| {
| "key":"url",
| "value":"http://mydomain.com/xxx"
| }
| ]
|}
|```
|
|</details>
|""".stripMargin
}
case class DynamicEntityInfo(definition: String, entityName: String, bankId: Option[String]) {
import net.liftweb.json
val subEntities: List[DynamicEntityInfo] = Nil
val idName = StringUtils.uncapitalize(entityName) + "Id"
val listName = StringHelpers.snakify(entityName).replaceFirst("[-_]*$", "_list")
val singleName = StringHelpers.snakify(entityName).replaceFirst("[-_]*$", "")
val jsonTypeMap: Map[String, Class[_]] = DynamicEntityFieldType.nameToValue.mapValues(_.jValueType)
val definitionJson = json.parse(definition).asInstanceOf[JObject]
val entity = (definitionJson \ entityName).asInstanceOf[JObject]
val description = entity \ "description" match {
case JString(s) if StringUtils.isNotBlank(s) =>
s"""
|${s.capitalize}
|""".stripMargin
case _ => ""
}
val fieldsDescription = {
val descriptions = (entity \ "properties")
.asInstanceOf[JObject]
.obj
.filter(field =>
field.value \ "description" match {
case JString(s) if StringUtils.isNotBlank(s) => true
case _ => false
}
)
if(descriptions.nonEmpty) {
descriptions
.map(field => s"""* ${field.name}: ${(field.value \ "description").asInstanceOf[JString].s}""")
.mkString("**Property List:** \n\n", "\n", "")
} else {
""
}
}
def toResponse(result: JObject, id: Option[String]): JObject = {
val fieldNameToTypeName: Map[String, String] = (entity \ "properties")
.asInstanceOf[JObject]
.obj
.map(field => (field.name, (field.value \ "type").asInstanceOf[JString].s))
.toMap
val fieldNameToType: Map[String, Class[_]] = fieldNameToTypeName
.mapValues(jsonTypeMap(_))
val fields = result.obj.filter(it => fieldNameToType.keySet.contains(it.name))
(id, fields.exists(_.name == idName)) match {
case (Some(idValue), false) => JObject(JField(idName, JString(idValue)) :: fields)
case _ => JObject(fields)
}
}
def getSingleExampleWithoutId: JObject = {
val fields = (entity \ "properties").asInstanceOf[JObject].obj
def extractExample(typeAndExample: JValue): JValue = {
val example = typeAndExample \ "example"
(example, (typeAndExample \ "type")) match {
case (JString(s), JString("boolean")) => JBool(s.toLowerCase().toBoolean)
case (JString(s), JString("integer")) => JInt(s.toLong)
case (JString(s), JString("number")) => JDouble(s.toDouble)
case _ => example
}
}
val exampleFields = fields.map(field => JField(field.name, extractExample(field.value)))
JObject(exampleFields)
}
val bankIdJObject: JObject = ("bank-id" -> ExampleValue.bankIdExample.value)
def getSingleExample: JObject = if (bankId.isDefined){
val SingleObject: JObject = (singleName -> (JObject(JField(idName, JString(generateUUID())) :: getSingleExampleWithoutId.obj)))
bankIdJObject merge SingleObject
} else{
(singleName -> (JObject(JField(idName, JString(generateUUID())) :: getSingleExampleWithoutId.obj)))
}
def getExampleList: JObject = if (bankId.isDefined){
val objectList: JObject = (listName -> JArray(List(getSingleExample)))
bankIdJObject merge objectList
} else{
(listName -> JArray(List(getSingleExample)))
}
val canCreateRole: ApiRole = DynamicEntityInfo.canCreateRole(entityName, bankId)
val canUpdateRole: ApiRole = DynamicEntityInfo.canUpdateRole(entityName, bankId)
val canGetRole: ApiRole = DynamicEntityInfo.canGetRole(entityName, bankId)
val canDeleteRole: ApiRole = DynamicEntityInfo.canDeleteRole(entityName, bankId)
}
object DynamicEntityInfo {
def canCreateRole(entityName: String, bankId:Option[String]): ApiRole = getOrCreateDynamicApiRole("CanCreateDynamicEntity_" + entityName, bankId.isDefined)
def canUpdateRole(entityName: String, bankId:Option[String]): ApiRole = getOrCreateDynamicApiRole("CanUpdateDynamicEntity_" + entityName, bankId.isDefined)
def canGetRole(entityName: String, bankId:Option[String]): ApiRole = getOrCreateDynamicApiRole("CanGetDynamicEntity_" + entityName, bankId.isDefined)
def canDeleteRole(entityName: String, bankId:Option[String]): ApiRole = getOrCreateDynamicApiRole("CanDeleteDynamicEntity_" + entityName, bankId.isDefined)
def roleNames(entityName: String, bankId:Option[String]): List[String] = List(
canCreateRole(entityName, bankId),
canUpdateRole(entityName, bankId),
canGetRole(entityName, bankId),
canDeleteRole(entityName, bankId)
).map(_.toString())
}

View File

@ -1,4 +1,4 @@
package code.api.v4_0_0.dynamic
package code.api.dynamic.endpoint.helper
import code.api.util.APIUtil.ResourceDoc
import code.api.util.{APIUtil, ApiRole, ApiTag}

View File

@ -1,4 +1,4 @@
package code.api.v4_0_0.dynamic.practise
package code.api.dynamic.endpoint.helper.practise
import code.api.util.APIUtil.ResourceDoc
import code.api.v4_0_0.ResourceDocFragment

View File

@ -1,4 +1,6 @@
package code.api.v4_0_0.dynamic.practise
package code.api.dynamic.endpoint.helper.practise
import code.api.dynamic.endpoint.helper.DynamicCompileEndpoint
// any import statement here need be moved into the process method body
@ -13,7 +15,7 @@ package code.api.v4_0_0.dynamic.practise
*
*
*/
object PractiseEndpoint extends code.api.v4_0_0.dynamic.DynamicCompileEndpoint {
object PractiseEndpoint extends DynamicCompileEndpoint {
// don't modify these import statement
import code.api.util.CallContext
import code.api.util.ErrorMessages.{InvalidJsonFormat, InvalidRequestPayload}
@ -26,7 +28,7 @@ object PractiseEndpoint extends code.api.v4_0_0.dynamic.DynamicCompileEndpoint {
import scala.concurrent.Future
import com.openbankproject.commons.ExecutionContext.Implicits.global
import code.api.v4_0_0.dynamic.DynamicCompileEndpoint._
import code.api.dynamic.endpoint.helper.DynamicCompileEndpoint._
// request method

View File

@ -1,11 +1,11 @@
package code.api.v4_0_0.dynamic.practise
package code.api.dynamic.endpoint.helper.practise
import code.api.ResourceDocs1_4_0.SwaggerDefinitionsJSON.requestRootJsonClass
import code.api.dynamic.endpoint.helper.EndpointGroup
import code.api.util.APIUtil
import code.api.util.APIUtil.{ResourceDoc, StringBody}
import code.api.util.ApiTag.{apiTagDynamicResourceDoc, apiTagNewStyle}
import code.api.util.ErrorMessages.UnknownError
import code.api.v4_0_0.dynamic.EndpointGroup
import com.openbankproject.commons.util.ApiVersion
import scala.collection.immutable.List
@ -31,7 +31,7 @@ object PractiseEndpointGroup extends EndpointGroup{
|* [Dynamic resourceDoc version1](https://vimeo.com/623381607)
|
|The endpoint return the response from PractiseEndpoint code.
|Here, code.api.v4_0_0.dynamic.practise.PractiseEndpoint.process
|Here, code.api.DynamicEndpoints.dynamic.practise.PractiseEndpoint.process
|You can test the method body grammar, and try the business logic, but need to restart the OBP-API code .
|
|""",

View File

@ -0,0 +1,281 @@
package code.api.dynamic.entity
import code.DynamicData.{DynamicData, DynamicDataProvider}
import code.api.dynamic.endpoint.helper.{DynamicEndpointHelper, DynamicEntityHelper, DynamicEntityInfo, EntityName, MockResponseHolder}
import code.api.dynamic.endpoint.helper.DynamicEndpointHelper.DynamicReq
import code.api.dynamic.endpoint.helper.MockResponseHolder
import code.api.util.APIUtil.{fullBoxOrException, _}
import code.api.util.ErrorMessages._
import code.api.util.NewStyle.HttpCode
import code.api.util._
import code.endpointMapping.EndpointMappingCommons
import code.transactionrequests.TransactionRequests.TransactionRequestTypes.{apply => _}
import code.util.Helper
import com.openbankproject.commons.ExecutionContext.Implicits.global
import com.openbankproject.commons.model._
import com.openbankproject.commons.model.enums.DynamicEntityOperation._
import com.openbankproject.commons.model.enums._
import com.openbankproject.commons.util.{ApiVersion, JsonUtils}
import net.liftweb.common._
import net.liftweb.http.rest.RestHelper
import net.liftweb.http.{JsonResponse, Req}
import net.liftweb.json.JsonAST.JValue
import net.liftweb.json.JsonDSL._
import net.liftweb.json._
import net.liftweb.util.StringHelpers
import org.apache.commons.lang3.StringUtils
import scala.collection.immutable.List
import scala.collection.mutable.ArrayBuffer
import scala.concurrent.Future
trait APIMethodsDynamicEntity {
self: RestHelper =>
val ImplementationsDynamicEntity = new ImplementationsDynamicEntity()
class ImplementationsDynamicEntity {
val implementedInApiVersion = ApiVersion.`dynamic-entity`
private val staticResourceDocs = ArrayBuffer[ResourceDoc]()
// createDynamicEntityDoc and updateDynamicEntityDoc are dynamic, So here dynamic create resourceDocs
def resourceDocs = staticResourceDocs
val apiRelations = ArrayBuffer[ApiRelation]()
val codeContext = CodeContext(staticResourceDocs, apiRelations)
private def unboxResult[T: Manifest](box: Box[T], entityName: String): T = {
if (box.isInstanceOf[Failure]) {
val failure = box.asInstanceOf[Failure]
// change the internal db column name 'dynamicdataid' to entity's id name
val msg = failure.msg.replace(DynamicData.DynamicDataId.dbColumnName, StringUtils.uncapitalize(entityName) + "Id")
val changedMsgFailure = failure.copy(msg = s"$InternalServerError $msg")
fullBoxOrException[T](changedMsgFailure)
}
box.openOrThrowException("impossible error")
}
//TODO temp solution to support query by field name and value
private def filterDynamicObjects(resultList: JArray, req: Req): JArray = {
req.params match {
case map if map.isEmpty => resultList
case params =>
val filteredWithFieldValue = resultList.arr.filter { jValue =>
params.forall { kv =>
val (path, values) = kv
values.exists(JsonUtils.isFieldEquals(jValue, path, _))
}
}
JArray(filteredWithFieldValue)
}
}
lazy val genericEndpoint: OBPEndpoint = {
case EntityName(bankId, entityName, id) JsonGet req => { cc =>
val listName = StringHelpers.snakify(entityName).replaceFirst("[-_]*$", "_list")
val singleName = StringHelpers.snakify(entityName).replaceFirst("[-_]*$", "")
val isGetAll = StringUtils.isBlank(id)
val operation: DynamicEntityOperation = if (StringUtils.isBlank(id)) GET_ALL else GET_ONE
val resourceDoc = DynamicEntityHelper.operationToResourceDoc.get(operation -> entityName)
val operationId = resourceDoc.map(_.operationId).orNull
val callContext = cc.copy(operationId = Some(operationId), resourceDocument = resourceDoc)
// process before authentication interceptor, get intercept result
val beforeInterceptResult: Box[JsonResponse] = beforeAuthenticateInterceptResult(Option(callContext), operationId)
if (beforeInterceptResult.isDefined) beforeInterceptResult
else for {
(Full(u), callContext) <- authenticatedAccess(callContext) // Inject operationId into Call Context. It's used by Rate Limiting.
(_, callContext) <-
if (bankId.isDefined) { //if it is the bank level entity, we need to check the bankId
NewStyle.function.getBank(bankId.map(BankId(_)).orNull, callContext)
} else {
Future.successful {
("", callContext)
}
}
_ <- NewStyle.function.hasEntitlement(bankId.getOrElse(""), u.userId, DynamicEntityInfo.canGetRole(entityName, bankId), callContext)
// process after authentication interceptor, get intercept result
jsonResponse: Box[ErrorMessage] = afterAuthenticateInterceptResult(callContext, operationId).collect({
case JsonResponseExtractor(message, code) => ErrorMessage(code, message)
})
_ <- Helper.booleanToFuture(failMsg = jsonResponse.map(_.message).orNull, failCode = jsonResponse.map(_.code).openOr(400), cc = callContext) {
jsonResponse.isEmpty
}
(box, _) <- NewStyle.function.invokeDynamicConnector(operation, entityName, None, Option(id).filter(StringUtils.isNotBlank), bankId, None, Some(cc))
_ <- Helper.booleanToFuture(EntityNotFoundByEntityId, 404, cc = callContext) {
box.isDefined
}
} yield {
val jValue = if (isGetAll) {
val resultList: JArray = unboxResult(box.asInstanceOf[Box[JArray]], entityName)
if (bankId.isDefined) {
val bankIdJobject: JObject = ("bank_id" -> bankId.getOrElse(""))
val result: JObject = (listName -> filterDynamicObjects(resultList, req))
bankIdJobject merge result
} else {
val result: JObject = (listName -> filterDynamicObjects(resultList, req))
result
}
} else {
val singleObject: JValue = unboxResult(box.asInstanceOf[Box[JValue]], entityName)
if (bankId.isDefined) {
val bankIdJobject: JObject = ("bank_id" -> bankId.getOrElse(""))
val result: JObject = (singleName -> singleObject)
bankIdJobject merge result
} else {
val result: JObject = (singleName -> singleObject)
result
}
}
(jValue, HttpCode.`200`(Some(cc)))
}
}
case EntityName(bankId, entityName, _) JsonPost json -> _ => { cc =>
val singleName = StringHelpers.snakify(entityName).replaceFirst("[-_]*$", "")
val operation: DynamicEntityOperation = CREATE
val resourceDoc = DynamicEntityHelper.operationToResourceDoc.get(operation -> entityName)
val operationId = resourceDoc.map(_.operationId).orNull
val callContext = cc.copy(operationId = Some(operationId), resourceDocument = resourceDoc)
// process before authentication interceptor, get intercept result
val beforeInterceptResult: Box[JsonResponse] = beforeAuthenticateInterceptResult(Option(callContext), operationId)
if (beforeInterceptResult.isDefined) beforeInterceptResult
else for {
(Full(u), callContext) <- authenticatedAccess(callContext) // Inject operationId into Call Context. It's used by Rate Limiting.
(_, callContext) <-
if (bankId.isDefined) { //if it is the bank level entity, we need to check the bankId
NewStyle.function.getBank(bankId.map(BankId(_)).orNull, callContext)
} else {
Future.successful {
("", callContext)
}
}
_ <- NewStyle.function.hasEntitlement(bankId.getOrElse(""), u.userId, DynamicEntityInfo.canCreateRole(entityName, bankId), callContext)
// process after authentication interceptor, get intercept result
jsonResponse: Box[ErrorMessage] = afterAuthenticateInterceptResult(callContext, operationId).collect({
case JsonResponseExtractor(message, code) => ErrorMessage(code, message)
})
_ <- Helper.booleanToFuture(failMsg = jsonResponse.map(_.message).orNull, failCode = jsonResponse.map(_.code).openOr(400), cc = callContext) {
jsonResponse.isEmpty
}
(box, _) <- NewStyle.function.invokeDynamicConnector(operation, entityName, Some(json.asInstanceOf[JObject]), None, bankId, None, Some(cc))
singleObject: JValue = unboxResult(box.asInstanceOf[Box[JValue]], entityName)
} yield {
val result: JObject = (singleName -> singleObject)
val entity = if (bankId.isDefined) {
val bankIdJobject: JObject = ("bank_id" -> bankId.getOrElse(""))
bankIdJobject merge result
} else {
result
}
(entity, HttpCode.`201`(Some(cc)))
}
}
case EntityName(bankId, entityName, id) JsonPut json -> _ => { cc =>
val singleName = StringHelpers.snakify(entityName).replaceFirst("[-_]*$", "")
val operation: DynamicEntityOperation = UPDATE
val resourceDoc = DynamicEntityHelper.operationToResourceDoc.get(operation -> entityName)
val operationId = resourceDoc.map(_.operationId).orNull
val callContext = cc.copy(operationId = Some(operationId), resourceDocument = resourceDoc)
// process before authentication interceptor, get intercept result
val beforeInterceptResult: Box[JsonResponse] = beforeAuthenticateInterceptResult(Option(callContext), operationId)
if (beforeInterceptResult.isDefined) beforeInterceptResult
else for {
(Full(u), callContext) <- authenticatedAccess(callContext) // Inject operationId into Call Context. It's used by Rate Limiting.
(_, callContext) <-
if (bankId.isDefined) { //if it is the bank level entity, we need to check the bankId
NewStyle.function.getBank(bankId.map(BankId(_)).orNull, callContext)
} else {
Future.successful {
("", callContext)
}
}
_ <- NewStyle.function.hasEntitlement(bankId.getOrElse(""), u.userId, DynamicEntityInfo.canUpdateRole(entityName, bankId), callContext)
// process after authentication interceptor, get intercept result
jsonResponse: Box[ErrorMessage] = afterAuthenticateInterceptResult(callContext, operationId).collect({
case JsonResponseExtractor(message, code) => ErrorMessage(code, message)
})
_ <- Helper.booleanToFuture(failMsg = jsonResponse.map(_.message).orNull, failCode = jsonResponse.map(_.code).openOr(400), cc = callContext) {
jsonResponse.isEmpty
}
(box, _) <- NewStyle.function.invokeDynamicConnector(GET_ONE, entityName, None, Some(id), bankId, None, Some(cc))
_ <- Helper.booleanToFuture(EntityNotFoundByEntityId, 404, cc = callContext) {
box.isDefined
}
(box: Box[JValue], _) <- NewStyle.function.invokeDynamicConnector(operation, entityName, Some(json.asInstanceOf[JObject]), Some(id), bankId, None, Some(cc))
singleObject: JValue = unboxResult(box.asInstanceOf[Box[JValue]], entityName)
} yield {
val result: JObject = (singleName -> singleObject)
val entity = if (bankId.isDefined) {
val bankIdJobject: JObject = ("bank_id" -> bankId.getOrElse(""))
bankIdJobject merge result
} else {
result
}
(entity, HttpCode.`200`(Some(cc)))
}
}
case EntityName(bankId, entityName, id) JsonDelete _ => { cc =>
val operation: DynamicEntityOperation = DELETE
val resourceDoc = DynamicEntityHelper.operationToResourceDoc.get(operation -> entityName)
val operationId = resourceDoc.map(_.operationId).orNull
val callContext = cc.copy(operationId = Some(operationId), resourceDocument = resourceDoc)
// process before authentication interceptor, get intercept result
val beforeInterceptResult: Box[JsonResponse] = beforeAuthenticateInterceptResult(Option(callContext), operationId)
if (beforeInterceptResult.isDefined) beforeInterceptResult
else for {
(Full(u), callContext) <- authenticatedAccess(callContext) // Inject operationId into Call Context. It's used by Rate Limiting.
(_, callContext) <-
if (bankId.isDefined) { //if it is the bank level entity, we need to check the bankId
NewStyle.function.getBank(bankId.map(BankId(_)).orNull, callContext)
} else {
Future.successful {
("", callContext)
}
}
_ <- NewStyle.function.hasEntitlement(bankId.getOrElse(""), u.userId, DynamicEntityInfo.canDeleteRole(entityName, bankId), callContext)
// process after authentication interceptor, get intercept result
jsonResponse: Box[ErrorMessage] = afterAuthenticateInterceptResult(callContext, operationId).collect({
case JsonResponseExtractor(message, code) => ErrorMessage(code, message)
})
_ <- Helper.booleanToFuture(failMsg = jsonResponse.map(_.message).orNull, failCode = jsonResponse.map(_.code).openOr(400), cc = callContext) {
jsonResponse.isEmpty
}
(box, _) <- NewStyle.function.invokeDynamicConnector(GET_ONE, entityName, None, Some(id), bankId, None, Some(cc))
_ <- Helper.booleanToFuture(EntityNotFoundByEntityId, 404, cc = callContext) {
box.isDefined
}
(box, _) <- NewStyle.function.invokeDynamicConnector(operation, entityName, None, Some(id), bankId, None, Some(cc))
deleteResult: JBool = unboxResult(box.asInstanceOf[Box[JBool]], entityName)
} yield {
(deleteResult, HttpCode.`204`(Some(cc)))
}
}
}
}
}
object APIMethodsDynamicEntity extends RestHelper with APIMethodsDynamicEntity {
lazy val newStyleEndpoints: List[(String, String)] = ImplementationsDynamicEntity.resourceDocs.map {
rd => (rd.partialFunctionName, rd.implementedInApiVersion.toString())
}.toList
}

View File

@ -0,0 +1,75 @@
/**
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.dynamic.entity
import APIMethodsDynamicEntity.ImplementationsDynamicEntity
import code.api.OBPRestHelper
import code.api.dynamic.endpoint.helper.DynamicEndpoints
import code.api.util.APIUtil.OBPEndpoint
import code.api.util.{APIUtil, VersionedOBPApis}
import code.api.v5_0_0.OBPAPI5_0_0.{allResourceDocs, apiPrefix, registerRoutes, routes}
import code.util.Helper.MdcLoggable
import com.openbankproject.commons.util.ApiVersion
import net.liftweb.common.{Box, Full}
import net.liftweb.http.{LiftResponse, PlainTextResponse}
import org.apache.http.HttpStatus
/*
This file defines which endpoints from all the versions are available in v4.0.0
*/
object OBPAPIDynamicEntity extends OBPRestHelper with MdcLoggable with VersionedOBPApis{
val version : ApiVersion = ApiVersion.`dynamic-entity`
val versionStatus = "BLEEDING-EDGE" // TODO this should be a property of ApiVersion.
// if old version ResourceDoc objects have the same name endpoint with new version, omit old version ResourceDoc.
def allResourceDocs = collectResourceDocs(ImplementationsDynamicEntity.resourceDocs)
val routes : List[OBPEndpoint] = List(ImplementationsDynamicEntity.genericEndpoint)
routes.map(endpoint => oauthServe(apiPrefix{endpoint}, None))
logger.info(s"version $version has been run! There are ${routes.length} routes.")
// specified response for OPTIONS request.
private val corsResponse: Box[LiftResponse] = Full{
val corsHeaders = List(
"Access-Control-Allow-Origin" -> "*",
"Access-Control-Allow-Methods" -> "GET, POST, OPTIONS, PUT, PATCH, DELETE",
"Access-Control-Allow-Headers" -> "*",
"Access-Control-Allow-Credentials" -> "true",
"Access-Control-Max-Age" -> "1728000" //Tell client that this pre-flight info is valid for 20 days
)
PlainTextResponse("", corsHeaders, HttpStatus.SC_NO_CONTENT)
}
/*
* process OPTIONS http request, just return no content and status is 204
*/
this.serve({
case req if req.requestType.method == "OPTIONS" => corsResponse
})
}

View File

@ -1,4 +1,4 @@
package code.api.v4_0_0.dynamic
package code.api.dynamic.entity.helper
import code.api.util.APIUtil.{EmptyBody, ResourceDoc, authenticationRequiredMessage, generateUUID}
import code.api.util.ApiRole.getOrCreateDynamicApiRole

View File

@ -33,12 +33,13 @@ 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._
import code.api.OAuthHandshake._
import code.api.builder.OBP_APIBuilder
import code.api.dynamic.endpoint.OBPAPIDynamicEndpoint
import code.api.dynamic.endpoint.helper.{DynamicEndpointHelper, DynamicEndpoints, DynamicEntityHelper}
import code.api.oauth1a.Arithmetics
import code.api.oauth1a.OauthParams._
import code.api.util.APIUtil.ResourceDoc.{findPathVariableNames, isPathVariable}
@ -48,7 +49,8 @@ import code.api.util.Glossary.GlossaryItem
import code.api.util.RateLimitingJson.CallLimit
import code.api.v1_2.ErrorMessage
import code.api.v2_0_0.CreateEntitlementJSON
import code.api.v4_0_0.dynamic.{DynamicEndpointHelper, DynamicEndpoints, DynamicEntityHelper}
import code.api.dynamic.endpoint.helper.DynamicEndpointHelper
import code.api.dynamic.entity.OBPAPIDynamicEntity
import code.api.{DirectLogin, _}
import code.authtypevalidation.AuthenticationTypeValidationProvider
import code.bankconnectors.Connector
@ -103,8 +105,8 @@ import javassist.{ClassPool, LoaderClassPath}
import javassist.expr.{ExprEditor, MethodCall}
import org.apache.commons.io.IOUtils
import org.apache.commons.lang3.StringUtils
import java.security.AccessControlException
import java.security.AccessControlException
import scala.collection.mutable
import scala.collection.mutable.{ArrayBuffer, ListBuffer}
import scala.concurrent.Future
@ -542,7 +544,6 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
def getHeadersCommonPart() = headers ::: List((ResponseHeader.`Correlation-Id`, getCorrelationId()))
def getHeaders() = getHeadersCommonPart() ::: getGatewayResponseHeader()
case class CustomResponseHeaders(list: List[(String, String)])
//This is used for get the value from props `email_domain_to_space_mappings`
case class EmailDomainToSpaceMapping(
@ -2444,6 +2445,9 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
case ApiVersion.v3_0_0 => LiftRules.statelessDispatch.append(v3_0_0.OBPAPI3_0_0)
case ApiVersion.v3_1_0 => LiftRules.statelessDispatch.append(v3_1_0.OBPAPI3_1_0)
case ApiVersion.v4_0_0 => LiftRules.statelessDispatch.append(v4_0_0.OBPAPI4_0_0)
case ApiVersion.v5_0_0 => LiftRules.statelessDispatch.append(v5_0_0.OBPAPI5_0_0)
case ApiVersion.`dynamic-endpoint` => LiftRules.statelessDispatch.append(OBPAPIDynamicEndpoint)
case ApiVersion.`dynamic-entity` => LiftRules.statelessDispatch.append(OBPAPIDynamicEntity)
case ApiVersion.`b1` => LiftRules.statelessDispatch.append(OBP_APIBuilder)
case version: ScannedApiVersion => LiftRules.statelessDispatch.append(ScannedApis.versionMapScannedApis(version))
case _ => logger.info(s"There is no ${version.toString}")
@ -3321,7 +3325,7 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
* to the account specified by parameter bankIdAccountId over the view specified by parameter viewId
* Note: The public views means you can use anonymous access which implies that the user is an optional value
*/
final def checkViewAccessAndReturnView(viewId : ViewId, bankIdAccountId: BankIdAccountId, user: Option[User]): Box[View] = {
final def checkViewAccessAndReturnView(viewId : ViewId, bankIdAccountId: BankIdAccountId, user: Option[User], consumerId: Option[String] = None): Box[View] = {
val customView = Views.views.vend.customView(viewId, bankIdAccountId)
customView match { // CHECK CUSTOM VIEWS
// 1st: View is Pubic and Public views are NOT allowed on this instance.
@ -3329,7 +3333,7 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
// 2nd: View is Pubic and Public views are allowed on this instance.
case Full(v) if(isPublicView(v)) => customView
// 3rd: The user has account access to this custom view
case Full(v) if(user.isDefined && user.get.hasAccountAccess(v, bankIdAccountId)) => customView
case Full(v) if(user.isDefined && user.get.hasAccountAccess(v, bankIdAccountId, consumerId)) => customView
// The user has NO account access via custom view
case _ =>
val systemView = Views.views.vend.systemView(viewId)
@ -3339,7 +3343,7 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
// 2nd: View is Pubic and Public views are allowed on this instance.
case Full(v) if(isPublicView(v)) => systemView
// 3rd: The user has account access to this system view
case Full(v) if (user.isDefined && user.get.hasAccountAccess(v, bankIdAccountId)) => systemView
case Full(v) if (user.isDefined && user.get.hasAccountAccess(v, bankIdAccountId, consumerId)) => systemView
// 4th: The user has firehose access to this system view
case Full(v) if (user.isDefined && hasAccountFirehoseAccess(v, user.get)) => systemView
// 5th: The user has firehose access at a bank to this system view
@ -4195,4 +4199,6 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
|* You can define a collection (also known as baskets or buckets) of products using Product Collections.
|
""".stripMargin
val transactionRequestChallengeTtl = APIUtil.getPropsAsLongValue("transaction_request_challenge_ttl", 600)
}

View File

@ -2,6 +2,7 @@ package code.api.util
import java.util.Date
import code.accountholders.AccountHolders
import code.api.Constant
import code.api.util.APIUtil.getPropsAsBoolValue
import code.api.util.ApiRole.{CanCreateAccount, CanCreateHistoricalTransactionAtBank}
@ -15,7 +16,7 @@ import code.ratelimiting.{RateLimiting, RateLimitingDI}
import code.users.{UserInitActionProvider, Users}
import code.util.Helper.MdcLoggable
import code.views.Views
import com.openbankproject.commons.model.{AccountId, Bank, BankAccount, User, ViewId}
import com.openbankproject.commons.model.{AccountId, Bank, BankAccount, BankId, BankIdAccountId, User, ViewId}
import net.liftweb.common.{Box, Empty, Failure, Full}
import com.openbankproject.commons.ExecutionContext.Implicits.global
import net.liftweb.mapper.By
@ -178,12 +179,14 @@ object AfterApiAuth extends MdcLoggable{
}.isDefined
UserInitActionProvider.createOrUpdateInitAction(resourceUser.userId, "add-entitlement", CanCreateHistoricalTransactionAtBank.toString(), addCanCreateHistoricalTransactionAtBank)
// Create Cash account
val bankAccount = getOrCreateBankAccount(bank, "cash", "cash-flow").flatMap( account =>
Views.views.vend.systemView(ViewId(Constant.SYSTEM_OWNER_VIEW_ID)).flatMap( view =>
val bankAccount = getOrCreateBankAccount(bank, "cash", "cash-flow").flatMap { account =>
Views.views.vend.systemView(ViewId(Constant.SYSTEM_OWNER_VIEW_ID)).flatMap(view =>
// Grant account access
Views.views.vend.grantAccessToSystemView(bank.bankId, account.accountId, view, resourceUser)
)
).isDefined
// Create account holder
AccountHolders.accountHolders.vend.getOrCreateAccountHolder(resourceUser, BankIdAccountId(bank.bankId, account.accountId))
}.isDefined
UserInitActionProvider.createOrUpdateInitAction(resourceUser.userId, "add-bank-account", "cache", bankAccount)
addCanCreateAccount && addCanCreateHistoricalTransactionAtBank && bankAccount
case _ =>

View File

@ -1,7 +1,9 @@
package code.api.util
import code.api.dynamic.endpoint.helper.{DynamicEndpointHelper, DynamicEntityHelper}
import java.util.concurrent.ConcurrentHashMap
import code.api.v4_0_0.dynamic.{DynamicEndpointHelper, DynamicEntityHelper}
import code.api.dynamic.endpoint.helper.DynamicEndpointHelper
import com.openbankproject.commons.util.{JsonAble, ReflectUtils}
import net.liftweb.json.{Formats, JsonAST}
import net.liftweb.json.JsonDSL._
@ -116,6 +118,9 @@ object ApiRole {
case class CanUpdateCustomerCreditRatingAndSource(requiresBankId: Boolean = true) extends ApiRole
lazy val canUpdateCustomerCreditRatingAndSource = CanUpdateCustomerCreditRatingAndSource()
case class CanUpdateCustomerCreditRatingAndSourceAtAnyBank(requiresBankId: Boolean = false) extends ApiRole
lazy val canUpdateCustomerCreditRatingAndSourceAtAnyBank = CanUpdateCustomerCreditRatingAndSourceAtAnyBank()
case class CanCreateCustomerAtAnyBank(requiresBankId: Boolean = false) extends ApiRole
lazy val canCreateCustomerAtAnyBank = CanCreateCustomerAtAnyBank()
@ -265,9 +270,21 @@ object ApiRole {
case class CanCreateCounterpartyAtAnyBank(requiresBankId: Boolean = false) extends ApiRole
lazy val canCreateCounterpartyAtAnyBank = CanCreateCounterpartyAtAnyBank()
case class CanDeleteCounterparty(requiresBankId: Boolean = true) extends ApiRole
lazy val canDeleteCounterparty = CanDeleteCounterparty()
case class CanDeleteCounterpartyAtAnyBank(requiresBankId: Boolean = false) extends ApiRole
lazy val canDeleteCounterpartyAtAnyBank = CanDeleteCounterpartyAtAnyBank()
case class CanGetCounterparty(requiresBankId: Boolean = true) extends ApiRole
lazy val canGetCounterparty = CanGetCounterparty()
lazy val canGetCounterparty = CanGetCounterparty()
case class CanGetCounterpartiesAtAnyBank(requiresBankId: Boolean = false) extends ApiRole
lazy val canGetCounterpartiesAtAnyBank = CanGetCounterpartiesAtAnyBank()
case class CanGetCounterparties(requiresBankId: Boolean = true) extends ApiRole
lazy val canGetCounterparties = CanGetCounterparties()
case class CanGetApiCollection(requiresBankId: Boolean = false) extends ApiRole
lazy val canGetApiCollection = CanGetApiCollection()
@ -386,6 +403,12 @@ object ApiRole {
case class CanCreateWebhook(requiresBankId: Boolean = true) extends ApiRole
lazy val canCreateWebhook = CanCreateWebhook()
case class CanCreateSystemAccountNotificationWebhook(requiresBankId: Boolean = false) extends ApiRole
lazy val canCreateSystemAccountNotificationWebhook = CanCreateSystemAccountNotificationWebhook()
case class CanCreateAccountNotificationWebhookAtOneBank(requiresBankId: Boolean = true) extends ApiRole
lazy val canCreateAccountNotificationWebhookAtOneBank = CanCreateAccountNotificationWebhookAtOneBank()
case class CanUpdateWebhook(requiresBankId: Boolean = true) extends ApiRole
lazy val canUpdateWebhook = CanUpdateWebhook()
@ -591,18 +614,33 @@ object ApiRole {
case class CanCreateCustomerAttributeAtOneBank(requiresBankId: Boolean = true) extends ApiRole
lazy val canCreateCustomerAttributeAtOneBank = CanCreateCustomerAttributeAtOneBank()
case class CanCreateCustomerAttributeAtAnyBank(requiresBankId: Boolean = false) extends ApiRole
lazy val canCreateCustomerAttributeAtAnyBank = CanCreateCustomerAttributeAtAnyBank()
case class CanUpdateCustomerAttributeAtOneBank(requiresBankId: Boolean = true) extends ApiRole
lazy val canUpdateCustomerAttributeAtOneBank = CanUpdateCustomerAttributeAtOneBank()
case class CanUpdateCustomerAttributeAtAnyBank(requiresBankId: Boolean = false) extends ApiRole
lazy val canUpdateCustomerAttributeAtAnyBank = CanUpdateCustomerAttributeAtAnyBank()
case class CanDeleteCustomerAttributeAtOneBank(requiresBankId: Boolean = true) extends ApiRole
lazy val canDeleteCustomerAttributeAtOneBank = CanDeleteCustomerAttributeAtOneBank()
case class CanDeleteCustomerAttributeAtAnyBank(requiresBankId: Boolean = false) extends ApiRole
lazy val canDeleteCustomerAttributeAtAnyBank = CanDeleteCustomerAttributeAtAnyBank()
case class CanGetCustomerAttributesAtOneBank(requiresBankId: Boolean = true) extends ApiRole
lazy val canGetCustomerAttributesAtOneBank = CanGetCustomerAttributesAtOneBank()
case class CanGetCustomerAttributesAtAnyBank(requiresBankId: Boolean = false) extends ApiRole
lazy val canGetCustomerAttributesAtAnyBank = CanGetCustomerAttributesAtAnyBank()
case class CanGetCustomerAttributeAtOneBank(requiresBankId: Boolean = true) extends ApiRole
lazy val canGetCustomerAttributeAtOneBank = CanGetCustomerAttributeAtOneBank()
case class CanGetCustomerAttributeAtAnyBank(requiresBankId: Boolean = false) extends ApiRole
lazy val canGetCustomerAttributeAtAnyBank = CanGetCustomerAttributeAtAnyBank()
case class CanCreateTransactionAttributeAtOneBank(requiresBankId: Boolean = true) extends ApiRole
lazy val canCreateTransactionAttributeAtOneBank = CanCreateTransactionAttributeAtOneBank()

View File

@ -6,6 +6,9 @@ sealed trait ApiTrigger{
object ApiTrigger {
case class OnCreateTransaction() extends ApiTrigger
lazy val onCreateTransaction = OnCreateTransaction()
case class OnBalanceChange() extends ApiTrigger
lazy val onBalanceChange = OnBalanceChange()

View File

@ -16,6 +16,9 @@ object ApiVersionUtils {
v3_0_0 ::
v3_1_0 ::
v4_0_0 ::
v5_0_0 ::
`dynamic-endpoint` ::
`dynamic-entity` ::
b1::
scannedApis
@ -34,6 +37,9 @@ object ApiVersionUtils {
case v3_0_0.fullyQualifiedVersion | v3_0_0.apiShortVersion => v3_0_0
case v3_1_0.fullyQualifiedVersion | v3_1_0.apiShortVersion => v3_1_0
case v4_0_0.fullyQualifiedVersion | v4_0_0.apiShortVersion => v4_0_0
case v5_0_0.fullyQualifiedVersion | v5_0_0.apiShortVersion => v5_0_0
case `dynamic-endpoint`.fullyQualifiedVersion | `dynamic-endpoint`.apiShortVersion => `dynamic-endpoint`
case `dynamic-entity`.fullyQualifiedVersion | `dynamic-entity`.apiShortVersion => `dynamic-entity`
case b1.fullyQualifiedVersion | b1.apiShortVersion => b1
case version if(scannedApis.map(_.fullyQualifiedVersion).contains(version))
=>scannedApis.filter(_.fullyQualifiedVersion==version).head

View File

@ -1,26 +1,19 @@
package code.api.util
import code.api.{APIFailureNewStyle, JsonResponseException}
import code.api.util.APIUtil.ResourceDoc
import code.api.util.ErrorMessages.DynamicResourceDocMethodDependency
import code.api.util.NewStyle.HttpCode
import code.api.v4_0_0.JSONFactory400
import code.api.v4_0_0.dynamic.{CompiledObjects, DynamicCompileEndpoint}
import code.api.v4_0_0.dynamic.practise.PractiseEndpoint
import com.openbankproject.commons.ExecutionContext
import com.openbankproject.commons.model.BankId
import com.openbankproject.commons.util.Functions.Memo
import com.openbankproject.commons.util.{JsonUtils, ReflectUtils}
import javassist.{ClassPool, LoaderClassPath}
import net.liftweb.common.{Box, Empty, Failure, Full, ParamFailure}
import net.liftweb.http.JsonResponse
import net.liftweb.json.{Extraction, JValue, prettyRender}
import org.apache.commons.lang3.StringUtils
import org.graalvm.polyglot.{Context, Engine, HostAccess, PolyglotAccess}
import java.lang.reflect.ReflectPermission
import java.net.NetPermission
import java.security.{AccessControlContext, AccessController, CodeSource, Permission, PermissionCollection, Permissions, Policy, PrivilegedAction, ProtectionDomain}
import java.util.{PropertyPermission, UUID}
import java.util.UUID
import java.util.concurrent.ConcurrentHashMap
import java.util.function.Consumer
import java.util.regex.Pattern
@ -28,7 +21,6 @@ import javax.script.ScriptEngineManager
import scala.collection.immutable.List
import scala.collection.mutable.ListBuffer
import scala.concurrent.{Future, Promise}
import scala.reflect.api
import scala.reflect.runtime.universe
import scala.reflect.runtime.universe.runtimeMirror
import scala.runtime.NonLocalReturnControl
@ -246,7 +238,7 @@ object DynamicUtil {
|import code.api.util.ErrorMessages._
|import code.api.util.ExampleValue._
|import code.api.util.{APIUtil, CallContext, OBPQueryParam}
|import code.api.v4_0_0.dynamic.MockResponseHolder
|import code.api.dynamic.endpoint.helper.MockResponseHolder
|import code.bankconnectors._
|import code.customer.internalMapping.MappedCustomerIdMappingProvider
|import code.kafka.KafkaHelper
@ -278,8 +270,8 @@ object DynamicUtil {
|import code.api.util.NewStyle.HttpCode
|import code.api.util._
|import code.api.v4_0_0.JSONFactory400
|import code.api.v4_0_0.dynamic.{CompiledObjects, DynamicCompileEndpoint}
|import code.api.v4_0_0.dynamic.practise.PractiseEndpoint
|import code.api.dynamic.endpoint.helper.{CompiledObjects, DynamicCompileEndpoint}
|import code.api.dynamic.endpoint.helper.practise.PractiseEndpoint
|import com.openbankproject.commons.ExecutionContext
|import code.api.util.CustomJsonFormats
|import com.openbankproject.commons.model.BankId

View File

@ -93,6 +93,8 @@ object ErrorMessages {
"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."
val InvalidHttpMethod = "OBP-10037: Incorrect http_method."
val InvalidHttpProtocol = "OBP-10038: Incorrect http_protocol."
// General Sort and Paging
val FilterSortDirectionError = "OBP-10023: obp_sort_direction parameter can only take two values: DESC or ASC!" // was OBP-20023
@ -213,6 +215,8 @@ object ErrorMessages {
val Oauth2TokenHaveNoConsumer = "OBP-20209: The token have no linked consumer. "
val Oauth2TokenMatchCertificateFail = "OBP-20210: The token linked with a different client certificate. "
val OneTimePasswordExpired = "OBP-20211: The One Time Password (OTP) has expired. "
// X.509
val X509GeneralError = "OBP-20300: PEM Encoded Certificate issue."
val X509ParsingFailed = "OBP-20301: Parsing failed for PEM Encoded Certificate."
@ -419,6 +423,8 @@ object ErrorMessages {
val EntitlementCannotBeDeleted = "OBP-30219: EntitlementId cannot be deleted."
val EntitlementCannotBeGranted = "OBP-30220: Entitlement cannot be granted."
val EntitlementCannotBeGrantedGrantorIssue = "OBP-30221: Entitlement cannot be granted due to the grantor's insufficient privileges."
val CounterpartyNotFoundByRoutings = "OBP-30222: Counterparty not found. Please specify valid value for Routings."
val CreateSystemViewError = "OBP-30250: Could not create the system view"
val DeleteSystemViewError = "OBP-30251: Could not delete the system view"
@ -439,6 +445,9 @@ object ErrorMessages {
val UpdateAccountApplicationStatusError = "OBP-30315: AccountApplication Status can not be updated. "
val CreateAccountApplicationError = "OBP-30316: AccountApplication Status can not be created. "
val DeleteCounterpartyError = "OBP-30317: Could not delete the Counterparty."
val DeleteCounterpartyMetadataError = "OBP-30318: Could not delete CounterpartyMetadata"
// Branch related messages
val BranchesNotFoundLicense = "OBP-32001: No branches available. License may not be set."
val BranchesNotFound = "OBP-32002: No branches available."
@ -495,7 +504,12 @@ object ErrorMessages {
// Transaction Request related messages (OBP-40XXX)
val InvalidTransactionRequestType = "OBP-40001: Invalid value for TRANSACTION_REQUEST_TYPE"
val InsufficientAuthorisationToCreateTransactionRequest = "OBP-40002: Insufficient authorisation to create TransactionRequest. The Transaction Request could not be created because you don't have access to the owner view of the from account or you don't have access to canCreateAnyTransactionRequest."
val InsufficientAuthorisationToCreateTransactionRequest = "OBP-40002: Insufficient authorisation to create TransactionRequest. " +
"The Transaction Request could not be created " +
"because the login user doesn't have access to the view of the from account " +
"or the view don't have the `canAddTransactionRequestToAnyAccount` permission " +
"or your consumer doesn't not have the access to the view of the from account " +
"or you don't have the role CanCreateAnyTransactionRequest."
val InvalidTransactionRequestCurrency = "OBP-40003: Transaction Request Currency must be the same as From Account Currency."
val InvalidTransactionRequestId = "OBP-40004: Transaction Request Id not found."
val InsufficientAuthorisationToCreateTransactionType = "OBP-40005: Insufficient authorisation to Create Transaction Type offered by the bank. The Request could not be created because you don't have access to CanCreateTransactionType."
@ -510,8 +524,10 @@ object ErrorMessages {
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 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."
"The challenge answer may be expired." +
"Or you've used up your allowed attempts (3 times)." +
"Or if connector = mapped and transactionRequestType_OTP_INSTRUCTION_TRANSPORT = DUMMY and suggested_default_sca_method=DUMMY, the answer must be `123`. " +
"Or 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}."

View File

@ -173,6 +173,9 @@ object ExampleValue {
lazy val transactionIdExample = ConnectorField("2fg8a7e4-6d02-40e3-a129-0b2bf89de8ub", s"The Transaction ID used in URLs. Used to store Metadata for the Transaction.")
glossaryItems += makeGlossaryItem("Transaction.transactionId", transactionIdExample)
lazy val chargePolicyExample = ConnectorField("SHARED", s"The transaction fee charge policy, can be shared, debit account or credit account.")
glossaryItems += makeGlossaryItem("Transaction.charge_policy", chargePolicyExample)
lazy val transactionAttributeIdExample = ConnectorField("7uy8a7e4-6d02-40e3-a129-0b2bf89de8uh", s"Transaction attribute id")
glossaryItems += makeGlossaryItem("Transaction.attributeId", transactionAttributeIdExample)
@ -197,7 +200,7 @@ object ExampleValue {
lazy val transactionRequestAttributeValueExample = ConnectorField("123456789", s"Transaction Request attribute value.")
glossaryItems += makeGlossaryItem("Transaction Requests.attributeValue", transactionRequestAttributeValueExample)
lazy val transactionDescriptionExample = ConnectorField("For the piano lesson in June 2018 - Invoice No: 68", s"A description or reference for the transaction")
lazy val transactionDescriptionExample = ConnectorField("The piano lession-Invoice No:68", s"A description or reference for the transaction")
glossaryItems += makeGlossaryItem("Transaction.transactionDescription", transactionDescriptionExample)
lazy val transactionTypeExample = ConnectorField("DEBIT", s"A code for the type of transaction")
@ -1562,9 +1565,6 @@ object ExampleValue {
lazy val currentStateExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
glossaryItems += makeGlossaryItem("current_state", currentStateExample)
lazy val chargePolicyExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
glossaryItems += makeGlossaryItem("charge_policy", chargePolicyExample)
lazy val customersExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
glossaryItems += makeGlossaryItem("customers", customersExample)
@ -1679,7 +1679,7 @@ object ExampleValue {
lazy val canCreateDirectDebitExample = ConnectorField(booleanTrue,NoDescriptionProvided)
glossaryItems += makeGlossaryItem("can_create_direct_debit", canCreateDirectDebitExample)
lazy val futureDateExample = ConnectorField("2020-01-27",NoDescriptionProvided)
lazy val futureDateExample = ConnectorField("20200127",NoDescriptionProvided)
glossaryItems += makeGlossaryItem("future_date", futureDateExample)
lazy val toTransferToAccountExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)

View File

@ -708,8 +708,56 @@ object Glossary extends MdcLoggable {
title = "Transaction",
description =
"""
|Records of successful movements of money from / to an `Account`. OBP Transactions don't contain any "draft" or "pending" Transactions. (see Transaction Requests). Transactions contain infomration including type, description, from, to, currency, amount and new balance information.
|Transactions are records of successful movements of value into or out of an `Account`.
|
|OBP Transactions don't contain any "draft" or "pending" Transactions; pending transactions see represented by Transaction Requests.
|
|OBP Transactions are modelled on a Bank statement where everything is based on the perspective of my account.
|That is, if I look at "my account", I see credits (positive numbers) and debits (negative numbers)
|An OBP transaction stores information including the:
|Bank ID
|Account ID
|Currency
|Amount (positive for a credit, negative for a debit)
|Date
|Counterparty (information that describes the other party in the transaction)
|- optionally description and new balance.
|
|Note, OBP operates a Double-Entry Bookkeeping system which means that every transfer of value within OBP is represented by *two* transactions.
|
|For instance, to represent 5 Euros going from Account A to Account B, we would have 2 transactions:
|
|Transaction 1.
|
|Account: A
|Currency: EUR
|Amount: -5
|Counterparty: Account B
|
|Transaction 2.
|
|Account: B
|Currency: EUR
|Amount: +5
|Counterparty: Account A
|
|The sum of the two transactions must be zero.
|
|What about representing value coming into or out of the system? Here we use "settlement accounts":
|
|OBP-INCOMING-SETTLEMENT-ACCOUNT is typically the ID for a default incoming settlement account
|
|OBP-OUTGOING-SETTLEMENT-ACCOUNT is typically the ID for a default outgoing settlement account
|
|See the following diagram:
|
|![OBP Double-Entry Bookkeeping](https://user-images.githubusercontent.com/485218/167990092-e76e6265-faa2-4425-b366-e570ed3301b9.png)
|
|See the [Get Double Entry Transaction](/index?version=OBPv4.0.0&operation_id=OBPv4_0_0-getDoubleEntryTransaction&currentTag=Transaction#OBPv4_0_0-getDoubleEntryTransaction) endpoint
|
|
|
""")
glossaryItems += GlossaryItem(
@ -1355,6 +1403,138 @@ object Glossary extends MdcLoggable {
|
|""")
glossaryItems += GlossaryItem(
title = "Scenario 7: Onboarding a User with multiple User Auth Context records",
description =
s"""
|### 1) Assuming a User is registered.
|
|The User can authenticate using OAuth, OIDC, Direct Login etc.
|
|### 2) Create a first User Auth Context record e.g. ACCOUNT_NUMBER
|
| The setting of the first User Auth Context record for a User, typically involves sending an SMS to the User.
| The phone number used for the SMS is retrieved from the bank's Core Banking System via an Account Number to Phone Number lookup.
| If this step succeeds we can be reasonably confident that the User who initiated it has access to a SIM card that can use the Phone Number linked to the Bank Account on the Core Banking System.
|
|Action: Create User Auth Context Update Request
|
| POST $getObpApiRoot/obp/v5.0.0/banks/BANK_ID/users/current/auth-context-updates/SMS
|
|Body:
|
| { "key":"ACCOUNT_NUMBER", "value":"78987432"}
|
|Headers:
|
| Content-Type: application/json
|
| DirectLogin: token="your-token-from-direct-login"
|
| When customer get the the challenge answer from SMS, then need to call `Answer Auth Context Update Challenge` to varify the challenge.
| Then the customer create the 1st `User Auth Context` successfully.
|
|
|Action: Answer Auth Context Update Challenge
|
| POST $getObpApiRoot/obp/v5.0.0/banks/BANK_ID/users/current/auth-context-updates/AUTH_CONTEXT_UPDATE_ID/challenge
|
|Body:
|
| { "answer": "12345678"}
|
|Headers:
|
| Content-Type: application/json
|
| DirectLogin: token="your-token-from-direct-login"
|
|### 3) Create a second User Auth Context record e.g. SMALL_PAYMENT_VERIFIED
|
| Once the first User Auth Context record is set, we can require the App to set a second record which builds on the information of the first.
|
|Action: Create User Auth Context Update Request
|
| POST $getObpApiRoot/obp/v5.0.0/banks/BANK_ID/users/current/auth-context-updates/SMS
|
|Body:
|
| { "key":"SMALL_PAYMENT_VERIFIED", "value":"78987432"}
|
|Headers:
|
| Content-Type: application/json
|
| DirectLogin: token="your-token-from-direct-login"
|
|
|
|Following `Create User Auth Context Update Request` request the API will send a small payment with a random code from the Users bank account specified in the SMALL_PAYMENT_VERIFIED key value.
|
|In order to answer the challenge, the User must have access to the online banking statement (or some other App that already can read transactions in realtime) so they can read the code in the description of the payment.
|
|
|Then Action:Answer Auth Context Update Challenge
|
| POST $getObpApiRoot/obp/v5.0.0/banks/BANK_ID/users/current/auth-context-updates/AUTH_CONTEXT_UPDATE_ID/challenge
|
|Body:
|
| { "answer": "12345678"}
|
|Headers:
|
| Content-Type: application/json
|
| DirectLogin: token="your-token-from-direct-login"
|
| Note! The above logic must be encoded in a dynamic connector method for the OBP internal function validateUserAuthContextUpdateRequest which is used by the endpoint Create User Auth Context Update Request See the next step.
|
|### 4) Create or Update Connector Method for validateUserAuthContextUpdateRequest
|
| Using this endpoint you can modify the Scala logic
|
|Action:
|
| POST $getObpApiRoot/obp/v4.0.0/management/connector-methods
|
|Body:
|
| { "method_name":"validateUserAuthContextUpdateRequest", "method_body":"%20%20%20%20%20%20Future.successful%28%0A%20%20%20%20%20%20%20%20Full%28%28BankCommons%28%0A%20%20%20%20%20%20%20%20%20%20BankId%28%22Hello%20bank%20id%22%29%2C%0A%20%20%20%20%20%20%20%20%20%20%221%22%2C%0A%20%20%20%20%20%20%20%20%20%20%221%22%2C%0A%20%20%20%20%20%20%20%20%20%20%221%22%2C%0A%20%20%20%20%20%20%20%20%20%20%221%22%2C%0A%20%20%20%20%20%20%20%20%20%20%221%22%2C%0A%20%20%20%20%20%20%20%20%20%20%221%22%2C%0A%20%20%20%20%20%20%20%20%20%20%221%22%2C%0A%20%20%20%20%20%20%20%20%20%20%228%22%0A%20%20%20%20%20%20%20%20%29%2C%20None%29%29%0A%20%20%20%20%20%20%29"}
|
|Headers:
|
| Content-Type: application/json
|
| DirectLogin: token="your-token-from-direct-login"
|
|### 5) Allow automated access to the App with Create Consent (SMS)
|
|
| Following the creation of User Auth Context records, OBP will create the relevant Account Access Views which allows the User to access their account(s).
| The App can then request an OBP consent which can be used as a bearer token and have automated access to the accounts.
| The Consent can be deleted at any time by the User.
|
| The Consent can have access to everything the User has access to, or a subset of this.
|
|Action:
|
| POST $getObpApiRoot/obp/v4.0.0/banks/BANK_ID/my/consents/SMS
|
|Body:
|
| { "everything":false, "views":[{ "bank_id":"gh.29.uk", "account_id":"8ca8a7e4-6d02-40e3-a129-0b2bf89de9f0", "view_id":"owner" }], "entitlements":[{ "bank_id":"gh.29.uk", "role_name":"CanGetCustomer" }], "consumer_id":"7uy8a7e4-6d02-40e3-a129-0b2bf89de8uh", "phone_number":"+44 07972 444 876", "valid_from":"2022-04-29T10:40:03Z", "time_to_live":3600}
|
|Headers:
|
| Content-Type: application/json
|
| DirectLogin: token="your-token-from-direct-login"
|
|![OBP User Auth Context, Views, Consents 2022](https://user-images.githubusercontent.com/485218/165982767-f656c965-089b-46de-a5e6-9f05b14db182.png)
|
|
""")
glossaryItems += GlossaryItem(
@ -2728,11 +2908,11 @@ object Glossary extends MdcLoggable {
title = "Connector Method",
description =
s"""
| The developer can override all the existing Connector methods on their own.
| Developers can override all the existing Connector methods.
| 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.
| When we 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.
|For example, the GetBanks endpoint calls the connector "getBanks" method. Then, 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)
@ -2745,13 +2925,19 @@ object Glossary extends MdcLoggable {
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.
| In OBP we largely define our endpoints using an internal case class or model called ResourceDoc
|
| Using this endpoint, developers can create their own Resource Docs at run time thus creating fully featured
| Open Bank Project style endpoints dynamically.
|
|
| In order to do this you need to prepare your desired Resource Doc as JSON.
| The business logic code can be written in the *method_body* field as encoded Scala code.
|
| It is still working in the processing ..
| This feature is somewhat work in progress (WIP).
|
|The following videos are available:
|* [Introduction for dConnector Method] (https://vimeo.com/623381607)
|* [Introduction to Dynamic Resource Docs] (https://vimeo.com/623381607)
|
|""".stripMargin)
@ -2759,16 +2945,21 @@ object Glossary extends MdcLoggable {
title = "Dynamic Message Doc",
description =
s"""
| The developers can create their own scala methods in OBP code.
| In OBP we represent messages sent by a Connector method / function as MessageDocs.
| A MessageDoc defines the message the Connector sends to an Adapter and the response it expects from the Adapter.
|
| Using this endpoint, developers can create their own scala methods aka Connectors 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.
|
| When you call the Dynamic Resource Doc endpoints, sometimes you need to call internal Scala methods which
|don't yet exist in the OBP code. In this case you can use these endpoints to create your own internal Scala methods.
|
| And you can use these endpoints to design your own helper methods in OBP code.
|You can also use these endpoints to create your own helper methods in OBP code.
|
| It is still working in the processing ..
| This feature is somewhat work in progress (WIP).
|
|The following videos are available:
|* [Introduction for Connector Method] (https://vimeo.com/623317747)
|* [Introduction to Dynamic Message Doc] (https://vimeo.com/623317747)
|
|""".stripMargin)

View File

@ -0,0 +1,35 @@
package code.api.util
import java.util.{Date, Locale}
import net.liftweb.http.S
import net.liftweb.util.Props
object I18NUtil {
// Copied from Sofit
def getLocalDate(date: Date): String = {
import java.text.DateFormat
val df = DateFormat.getDateInstance(DateFormat.LONG, currentLocale())
val formattedDate = df.format(date)
formattedDate
}
def getLocale(): Locale = Locale.getAvailableLocales().toList.filter { l =>
l.toLanguageTag == Props.get("language_tag", "en-GB")
}.headOption.getOrElse(Locale.ENGLISH)
def currentLocale() : Locale = {
// Cookie name
val localeCookieName = "SELECTED_LOCALE"
S.findCookie(localeCookieName).flatMap {
cookie => cookie.value.map(computeLocale)
} openOr getLocale()
}
// Properly convert a language tag to a Locale
def computeLocale(tag : String) = tag.split(Array('-', '_')) match {
case Array(lang) => new Locale(lang)
case Array(lang, country) => new Locale(lang, country)
case Array(lang, country, variant) => new Locale(lang, country, variant)
}
}

View File

@ -3,13 +3,12 @@ package code.api.util
import java.util.Date
import java.util.UUID.randomUUID
import akka.http.scaladsl.model.HttpMethod
import code.DynamicEndpoint.{DynamicEndpointProvider, DynamicEndpointT}
import code.api.APIFailureNewStyle
import code.api.Constant.SYSTEM_READ_ACCOUNTS_BERLIN_GROUP_VIEW_ID
import code.api.cache.Caching
import code.api.util.APIUtil.{EntitlementAndScopeStatus, OBPReturnType, afterAuthenticateInterceptResult, canGrantAccessToViewCommon, canRevokeAccessToViewCommon, connectorEmptyResponse, createHttpParamsByUrl, createHttpParamsByUrlFuture, createQueriesByHttpParamsFuture, fullBoxOrException, generateUUID, unboxFull, unboxFullOrFail}
import code.api.util.APIUtil._
import code.api.util.ApiRole.canCreateAnyTransactionRequest
import code.api.util.ErrorMessages.{InsufficientAuthorisationToCreateTransactionRequest, _}
import code.api.ResourceDocs1_4_0.ResourceDocs140.ImplementationsResourceDocs
@ -54,8 +53,8 @@ import net.liftweb.json.JsonDSL._
import net.liftweb.json.{JField, JInt, JNothing, JNull, JObject, JString, JValue, _}
import net.liftweb.util.Helpers.tryo
import org.apache.commons.lang3.StringUtils
import java.security.AccessControlException
import java.security.AccessControlException
import scala.collection.immutable.List
import scala.concurrent.Future
import scala.math.BigDecimal
@ -64,8 +63,9 @@ import code.validation.{JsonSchemaValidationProvider, JsonValidation}
import net.liftweb.http.JsonResponse
import net.liftweb.util.Props
import code.api.JsonResponseException
import code.api.dynamic.endpoint.helper.{DynamicEndpointHelper, DynamicEntityHelper, DynamicEntityInfo}
import code.api.v4_0_0.JSONFactory400
import code.api.v4_0_0.dynamic.{DynamicEndpointHelper, DynamicEntityHelper, DynamicEntityInfo}
import code.api.dynamic.endpoint.helper.DynamicEndpointHelper
import code.bankattribute.BankAttribute
import code.connectormethod.{ConnectorMethodProvider, JsonConnectorMethod}
import code.dynamicMessageDoc.{DynamicMessageDocProvider, JsonDynamicMessageDoc}
@ -73,6 +73,8 @@ import code.dynamicResourceDoc.{DynamicResourceDocProvider, JsonDynamicResourceD
import code.endpointMapping.{EndpointMappingProvider, EndpointMappingT}
import code.endpointTag.EndpointTagT
import code.util.Helper.MdcLoggable
import code.views.system.AccountAccess
import net.liftweb.mapper.By
object NewStyle extends MdcLoggable{
lazy val endpoints: List[(String, String)] = List(
@ -591,15 +593,30 @@ object NewStyle extends MdcLoggable{
def checkAuthorisationToCreateTransactionRequest(viewId : ViewId, bankAccountId: BankIdAccountId, user: User, callContext: Option[CallContext]) : Future[Boolean] = {
Future{
APIUtil.hasEntitlement(bankAccountId.bankId.value, user.userId, canCreateAnyTransactionRequest) match {
case true => Full(true)
case false => user.hasOwnerViewAccess(BankIdAccountId(bankAccountId.bankId,bankAccountId.accountId)) match {
case true => Full(true)
case false => Empty
}
lazy val hasCanCreateAnyTransactionRequestRole = APIUtil.hasEntitlement(bankAccountId.bankId.value, user.userId, canCreateAnyTransactionRequest)
lazy val consumerIdFromCallContext = callContext.map(_.consumer.map(_.consumerId.get).getOrElse(""))
lazy val view = APIUtil.checkViewAccessAndReturnView(viewId, bankAccountId, Some(user), consumerIdFromCallContext)
lazy val canAddTransactionRequestToAnyAccount = view.map(_.canAddTransactionRequestToAnyAccount).getOrElse(false)
//1st check the admin level role/entitlement `canCreateAnyTransactionRequest`
if(hasCanCreateAnyTransactionRequestRole) {
Full(true)
//2rd: check if the user have the view access and the view has the `canAddTransactionRequestToAnyAccount` permission
} else if (canAddTransactionRequestToAnyAccount) {
Full(true)
} else{
Empty
}
} map {
unboxFullOrFail(_, callContext, s"$InsufficientAuthorisationToCreateTransactionRequest")
unboxFullOrFail(_, callContext, s"$InsufficientAuthorisationToCreateTransactionRequest " +
s"Current ViewId(${viewId.value})," +
s"current UserId(${user.userId})"+
s"current ConsumerId(${callContext.map(_.consumer.map(_.consumerId.get).getOrElse("")).getOrElse("")})"
)
}
}
@ -884,17 +901,18 @@ object NewStyle extends MdcLoggable{
def getMetadata(bankId : BankId, accountId : AccountId, counterpartyId : String, callContext: Option[CallContext]): Future[CounterpartyMetadata] = {
Future(Counterparties.counterparties.vend.getMetadata(bankId, accountId, counterpartyId)) map {
x => fullBoxOrException(x ~> APIFailureNewStyle(CounterpartyMetadataNotFound, 400, callContext.map(_.toLight)))
x => fullBoxOrException(x ~> APIFailureNewStyle(CounterpartyNotFoundByCounterpartyId, 400, callContext.map(_.toLight)))
} map { unboxFull(_) }
}
def getCounterpartyTrait(bankId : BankId, accountId : AccountId, counterpartyId : String, callContext: Option[CallContext]): OBPReturnType[CounterpartyTrait] = {
Connector.connector.vend.getCounterpartyTrait(bankId, accountId, counterpartyId, callContext) map { i=>
(connectorEmptyResponse(i._1, callContext), i._2)
}
def getCounterpartyTrait(bankId : BankId, accountId : AccountId, counterpartyId : String, callContext: Option[CallContext]): OBPReturnType[CounterpartyTrait] =
{
Connector.connector.vend.getCounterpartyTrait(bankId, accountId, counterpartyId, callContext) map { i =>
(unboxFullOrFail(i._1, callContext, s"$CounterpartyNotFoundByCounterpartyId Current counterpartyId($counterpartyId) ", 400),
i._2)
}
}
def isEnabledTransactionRequests(callContext: Option[CallContext]): Future[Box[Unit]] = Helper.booleanToFuture(failMsg = TransactionRequestsNotEnabled, cc=callContext)(APIUtil.getPropsAsBoolValue("transactionRequests_enabled", false))
/**
@ -1032,18 +1050,14 @@ object NewStyle extends MdcLoggable{
validateRequestPayload(callContext)(boxResult)
}
def createUserAuthContext(userId: String, key: String, value: String, callContext: Option[CallContext]): OBPReturnType[UserAuthContext] = {
Connector.connector.vend.createUserAuthContext(userId, key, value, callContext) map {
def createUserAuthContext(user: User, key: String, value: String, callContext: Option[CallContext]): OBPReturnType[UserAuthContext] = {
Connector.connector.vend.createUserAuthContext(user.userId, key, value, callContext) map {
i => (connectorEmptyResponse(i._1, callContext), i._2)
} map {
result =>
//We will call the `refreshUserAccountAccess` after we successfully create the UserAuthContext
// because `createUserAuthContext` is a connector method, here is the entry point for OBP to refreshUserAccountAccess
if(callContext.isDefined && callContext.get.user.isDefined) {
AuthUser.refreshUser(callContext.get.user.head, callContext)
} else {
logger.info(s"AuthUser.refreshUserAccountAccess can not be run properly. The user is missing in the current callContext.")
}
// because `createUserAuthContext` is a connector method, here is the entry point for OBP to refreshUser
AuthUser.refreshUser(user, callContext)
result
}
}
@ -1063,9 +1077,15 @@ object NewStyle extends MdcLoggable{
}
}
def deleteUserAuthContextById(userAuthContextId: String, callContext: Option[CallContext]): OBPReturnType[Boolean] = {
def deleteUserAuthContextById(user: User, userAuthContextId: String, callContext: Option[CallContext]): OBPReturnType[Boolean] = {
Connector.connector.vend.deleteUserAuthContextById(userAuthContextId, callContext) map {
i => (connectorEmptyResponse(i._1, callContext), i._2)
}map {
result =>
// We will call the `refreshUserAccountAccess` after we successfully delete the UserAuthContext
// because `deleteUserAuthContextById` is a connector method, here is the entry point for OBP to refreshUser
AuthUser.refreshUser(user, callContext)
result
}
}
@ -1172,6 +1192,14 @@ object NewStyle extends MdcLoggable{
i._2)
}
}
def deleteCounterpartyByCounterpartyId(counterpartyId: CounterpartyId, callContext: Option[CallContext]): OBPReturnType[Boolean] =
{
Connector.connector.vend.deleteCounterpartyByCounterpartyId(counterpartyId: CounterpartyId, callContext: Option[CallContext]) map { i =>
(unboxFullOrFail(i._1, callContext, s"$DeleteCounterpartyError Current counterpartyId($counterpartyId) ", 400),
i._2)
}
}
def getBankAccountFromCounterparty(counterparty: CounterpartyTrait, isOutgoingAccount: Boolean, callContext: Option[CallContext]) : Future[BankAccount] =
{
Future{BankAccountX.getBankAccountFromCounterparty(counterparty, isOutgoingAccount)} map {
@ -1206,6 +1234,92 @@ object NewStyle extends MdcLoggable{
i._2)
}
}
def getOrCreateCounterparty(
name: String,
description: String,
currency: String,
createdByUserId: String,
thisBankId: String,
thisAccountId: String,
thisViewId: String,
otherBankRoutingScheme: String,
otherBankRoutingAddress: String,
otherBranchRoutingScheme: String,
otherBranchRoutingAddress: String,
otherAccountRoutingScheme: String,
otherAccountRoutingAddress: String,
otherAccountSecondaryRoutingScheme: String,
otherAccountSecondaryRoutingAddress: String,
callContext: Option[CallContext]
) : OBPReturnType[CounterpartyTrait] =
{
Connector.connector.vend.getOrCreateCounterparty(
name: String,
description: String,
currency: String,
createdByUserId: String,
thisBankId: String,
thisAccountId: String,
thisViewId: String,
otherBankRoutingScheme: String,
otherBankRoutingAddress: String,
otherBranchRoutingScheme: String,
otherBranchRoutingAddress: String,
otherAccountRoutingScheme: String,
otherAccountRoutingAddress: String,
otherAccountSecondaryRoutingScheme: String,
otherAccountSecondaryRoutingAddress: String,
callContext: Option[CallContext]
) map { i =>
(unboxFullOrFail(
i._1,
callContext,
s"$CreateCounterpartyError.",
404),
i._2)
}
}
def getCounterpartyByRoutings(
otherBankRoutingScheme: String,
otherBankRoutingAddress: String,
otherBranchRoutingScheme: String,
otherBranchRoutingAddress: String,
otherAccountRoutingScheme: String,
otherAccountRoutingAddress: String,
otherAccountSecondaryRoutingScheme: String,
otherAccountSecondaryRoutingAddress: String,
callContext: Option[CallContext]
) : OBPReturnType[CounterpartyTrait] =
{
Connector.connector.vend.getCounterpartyByRoutings(
otherBankRoutingScheme: String,
otherBankRoutingAddress: String,
otherBranchRoutingScheme: String,
otherBranchRoutingAddress: String,
otherAccountRoutingScheme: String,
otherAccountRoutingAddress: String,
otherAccountSecondaryRoutingScheme: String,
otherAccountSecondaryRoutingAddress: String,
callContext: Option[CallContext]
) map { i =>
(unboxFullOrFail(
i._1,
callContext,
s"$CounterpartyNotFoundByRoutings. Current routings: " +
s"otherBankRoutingScheme($otherBankRoutingScheme), " +
s"otherBankRoutingAddress($otherBankRoutingAddress)"+
s"otherBranchRoutingScheme($otherBranchRoutingScheme)"+
s"otherBranchRoutingAddress($otherBranchRoutingAddress)"+
s"otherAccountRoutingScheme($otherAccountRoutingScheme)"+
s"otherAccountRoutingAddress($otherAccountRoutingAddress)"+
s"otherAccountSecondaryRoutingScheme($otherAccountSecondaryRoutingScheme)"+
s"otherAccountSecondaryRoutingAddress($otherAccountSecondaryRoutingAddress)"+
404),
i._2)
}
}
def getTransactionRequestImpl(transactionRequestId: TransactionRequestId, callContext: Option[CallContext]): OBPReturnType[TransactionRequest] =
{
@ -1384,6 +1498,10 @@ object NewStyle extends MdcLoggable{
Connector.connector.vend.getDoubleEntryBookTransaction(bankId: BankId, accountId: AccountId, transactionId: TransactionId, callContext: Option[CallContext]) map { i =>
(unboxFullOrFail(i._1, callContext, s"$DoubleEntryTransactionNotFound ", 404), i._2)
}
def getBalancingTransaction(transactionId: TransactionId, callContext: Option[CallContext]): OBPReturnType[DoubleEntryTransaction] =
Connector.connector.vend.getBalancingTransaction(transactionId: TransactionId, callContext: Option[CallContext]) map { i =>
(unboxFullOrFail(i._1, callContext, s"$DoubleEntryTransactionNotFound ", 404), i._2)
}
def cancelPaymentV400(transactionId: TransactionId, callContext: Option[CallContext]): OBPReturnType[CancelPayment] = {
Connector.connector.vend.cancelPaymentV400(transactionId: TransactionId, callContext) map { i =>
@ -3216,6 +3334,18 @@ object NewStyle extends MdcLoggable{
counterpartyName:String
), callContext, CreateOrUpdateCounterpartyMetadataError), callContext)}
}
def deleteMetadata(
bankId: BankId,
accountId : AccountId,
counterpartyId:String,
callContext: Option[CallContext]
) : OBPReturnType[Boolean]= {
Future{(unboxFullOrFail(Counterparties.counterparties.vend.deleteMetadata(
bankId: BankId,
accountId : AccountId,
counterpartyId:String
), callContext, DeleteCounterpartyMetadataError), callContext)}
}
def getPhysicalCardsForUser(user : User, callContext: Option[CallContext]) : OBPReturnType[List[PhysicalCard]] = {
Connector.connector.vend.getPhysicalCardsForUser(user : User, callContext) map {

View File

@ -61,6 +61,7 @@ object Migration extends MdcLoggable {
def executeScripts(startedBeforeSchemifier: Boolean): Boolean = executeScript {
dummyScript()
addAccountAccessConsumerId()
populateTableViewDefinition()
populateTableAccountAccess()
generateAndPopulateMissingCustomerUUIDs()
@ -90,6 +91,8 @@ object Migration extends MdcLoggable {
alterUserAuthContextColumnKeyAndValueLength(startedBeforeSchemifier)
dropIndexAtColumnUsernameAtTableAuthUser(startedBeforeSchemifier)
dropIndexAtUserAuthContext()
alterWebhookColumnUrlLength()
dropConsentAuthContextDropIndex()
}
private def dummyScript(): Boolean = {
@ -354,6 +357,27 @@ object Migration extends MdcLoggable {
}
}
private def addAccountAccessConsumerId(): Boolean = {
val name = nameOf(addAccountAccessConsumerId)
runOnce(name) {
MigrationOfAccountAccessAddedConsumerId.addAccountAccessConsumerId(name)
}
}
private def alterWebhookColumnUrlLength(): Boolean = {
val name = nameOf(alterWebhookColumnUrlLength)
runOnce(name) {
MigrationOfWebhookUrlFieldLength.alterColumnUrlLength(name)
}
}
private def dropConsentAuthContextDropIndex(): Boolean = {
val name = nameOf(dropConsentAuthContextDropIndex)
runOnce(name) {
MigrationOfConsentAuthContextDropIndex.dropUniqueIndex(name)
}
}
}
/**

View File

@ -0,0 +1,65 @@
package code.api.util.migration
import code.api.Constant.ALL_CONSUMERS
import code.api.util.APIUtil
import code.api.util.migration.Migration.{DbFunction, saveLog}
import code.views.system.AccountAccess
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 MigrationOfAccountAccessAddedConsumerId {
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 addAccountAccessConsumerId(name: String): Boolean = {
DbFunction.tableExists(AccountAccess, (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") =>
() =>
s"""
|ALTER TABLE accountaccess ADD COLUMN "consumer_id" character varying(255) DEFAULT '$ALL_CONSUMERS';
|DROP INDEX IF EXISTS accountaccess_bank_id_account_id_view_fk_user_fk;
|""".stripMargin
case _ =>
() =>
s"""
|ALTER TABLE accountaccess ADD COLUMN "consumer_id" character varying(255) DEFAULT '$ALL_CONSUMERS';
|DROP INDEX IF EXISTS accountaccess_bank_id_account_id_view_fk_user_fk;
|""".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"""${AccountAccess._dbTableNameLC} table does not exist""".stripMargin
saveLog(name, commitId, isSuccessful, startDate, endDate, comment)
isSuccessful
}
}
}

View File

@ -0,0 +1,85 @@
package code.api.util.migration
import code.api.util.APIUtil
import code.api.util.migration.Migration.{DbFunction, saveLog}
import code.context.MappedConsentAuthContext
import net.liftweb.common.Full
import net.liftweb.mapper.{DB, Schemifier}
import net.liftweb.util.DefaultConnectionIdentifier
import scalikejdbc.DB.CPContext
import scalikejdbc._
import java.time.format.DateTimeFormatter
import java.time.{ZoneId, ZonedDateTime}
object MigrationOfConsentAuthContextDropIndex {
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'")
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)
}
def dropUniqueIndex(name: String): Boolean = {
DbFunction.tableExists(MappedConsentAuthContext, (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") =>
() => "DROP INDEX IF EXISTS consentauthcontext_consentid_key_c;"
case _ =>
() => "DROP INDEX IF EXISTS consentauthcontext_consentid_key_c;"
}
}
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"""${MappedConsentAuthContext._dbTableNameLC} table does not exist""".stripMargin
saveLog(name, commitId, isSuccessful, startDate, endDate, comment)
isSuccessful
}
}
}

View File

@ -3,7 +3,7 @@ package code.api.util.migration
import java.time.format.DateTimeFormatter
import java.time.{ZoneId, ZonedDateTime}
import code.api.Constant.{INCOMING_ACCOUNT_ID, OUTGOING_ACCOUNT_ID}
import code.api.Constant.{INCOMING_SETTLEMENT_ACCOUNT_ID, OUTGOING_SETTLEMENT_ACCOUNT_ID}
import code.api.util.APIUtil
import code.api.util.migration.Migration.saveLog
import code.model.dataAccess.{MappedBank, MappedBankAccount}
@ -30,17 +30,17 @@ object MigrationOfSettlementAccounts {
// Insert the default settlement accounts if they doesn't exist
val insertedIncomingSettlementAccount = MappedBankAccount.find(By(MappedBankAccount.bank, bank.bankId.value), By(MappedBankAccount.theAccountId, INCOMING_ACCOUNT_ID)) match {
val insertedIncomingSettlementAccount = MappedBankAccount.find(By(MappedBankAccount.bank, bank.bankId.value), By(MappedBankAccount.theAccountId, INCOMING_SETTLEMENT_ACCOUNT_ID)) match {
case Full(_) =>
Try {
Console.println(s"Settlement BankAccount(${bank.bankId.value}, $INCOMING_ACCOUNT_ID) found.")
Console.println(s"Settlement BankAccount(${bank.bankId.value}, $INCOMING_SETTLEMENT_ACCOUNT_ID) found.")
0
}
case _ =>
Try {
MappedBankAccount.create
.bank(bank.bankId.value)
.theAccountId(INCOMING_ACCOUNT_ID)
.theAccountId(INCOMING_SETTLEMENT_ACCOUNT_ID)
.accountCurrency("EUR")
.accountBalance(0)
.kind("SETTLEMENT")
@ -48,22 +48,22 @@ object MigrationOfSettlementAccounts {
.accountName("Default incoming settlement account")
.accountLabel("Settlement account: Do not delete!")
.saveMe()
Console.println(s"Creating settlement BankAccount(${bank.bankId.value}, $INCOMING_ACCOUNT_ID).")
Console.println(s"Creating settlement BankAccount(${bank.bankId.value}, $INCOMING_SETTLEMENT_ACCOUNT_ID).")
1
}
}
val insertedOutgoingSettlementAccount = MappedBankAccount.find(By(MappedBankAccount.bank, bank.bankId.value), By(MappedBankAccount.theAccountId, OUTGOING_ACCOUNT_ID)) match {
val insertedOutgoingSettlementAccount = MappedBankAccount.find(By(MappedBankAccount.bank, bank.bankId.value), By(MappedBankAccount.theAccountId, OUTGOING_SETTLEMENT_ACCOUNT_ID)) match {
case Full(_) =>
Try {
Console.println(s"Settlement BankAccount(${bank.bankId.value}, $OUTGOING_ACCOUNT_ID) found.")
Console.println(s"Settlement BankAccount(${bank.bankId.value}, $OUTGOING_SETTLEMENT_ACCOUNT_ID) found.")
0
}
case _ =>
Try {
MappedBankAccount.create
.bank(bank.bankId.value)
.theAccountId(OUTGOING_ACCOUNT_ID)
.theAccountId(OUTGOING_SETTLEMENT_ACCOUNT_ID)
.accountCurrency("EUR")
.accountBalance(0)
.kind("SETTLEMENT")
@ -71,7 +71,7 @@ object MigrationOfSettlementAccounts {
.accountName("Default outgoing settlement account")
.accountLabel("Settlement account: Do not delete!")
.saveMe()
Console.println(s"Creating settlement BankAccount(${bank.bankId.value}, $OUTGOING_ACCOUNT_ID).")
Console.println(s"Creating settlement BankAccount(${bank.bankId.value}, $OUTGOING_SETTLEMENT_ACCOUNT_ID).")
1
}
}

View File

@ -91,6 +91,8 @@ object TableViewDefinition {
viewDefinition
.canAddCounterparty_(view.canAddCounterparty)
.canGetCounterparty_(view.canGetCounterparty)
.canDeleteCounterparty_(view.canDeleteCounterparty)
.canDeleteCorporateLocation_(view.canDeleteCorporateLocation)
.canDeletePhysicalLocation_(view.canDeletePhysicalLocation)
.canEditOwnerComment_(view.canEditOwnerComment)

View File

@ -0,0 +1,72 @@
package code.api.util.migration
import java.time.format.DateTimeFormatter
import java.time.{ZoneId, ZonedDateTime}
import code.api.util.APIUtil
import code.api.util.migration.Migration.{DbFunction, saveLog}
import code.webhook.MappedAccountWebhook
import code.webhook.BankAccountNotificationWebhook
import code.webhook.SystemAccountNotificationWebhook
import net.liftweb.common.Full
import net.liftweb.mapper.{DB, Schemifier}
import net.liftweb.util.DefaultConnectionIdentifier
object MigrationOfWebhookUrlFieldLength {
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 alterColumnUrlLength(name: String): Boolean = {
DbFunction.tableExists(SystemAccountNotificationWebhook, (DB.use(DefaultConnectionIdentifier){ conn => conn})) &&
DbFunction.tableExists(BankAccountNotificationWebhook, (DB.use(DefaultConnectionIdentifier){ conn => conn}))&&
DbFunction.tableExists(MappedAccountWebhook, (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 SystemAccountNotificationWebhook ALTER COLUMN Url varchar(1024);
|ALTER TABLE BankAccountNotificationWebhook ALTER COLUMN Url varchar(1024);
|ALTER TABLE MappedAccountWebhook ALTER COLUMN mUrl varchar(1024);
|""".stripMargin
case _ =>
() =>
"""
|ALTER TABLE SystemAccountNotificationWebhook ALTER COLUMN Url TYPE character varying(1024);
|ALTER TABLE BankAccountNotificationWebhook ALTER COLUMN Url TYPE character varying(1024);
|ALTER TABLE MappedAccountWebhook ALTER COLUMN mUrl TYPE character varying(1024);
|""".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"""${MappedAccountWebhook._dbTableNameLC} table does not exist or
|${BankAccountNotificationWebhook._dbTableNameLC} table does not exist or
|${SystemAccountNotificationWebhook._dbTableNameLC} table does not exist""".stripMargin
saveLog(name, commitId, isSuccessful, startDate, endDate, comment)
isSuccessful
}
}
}

View File

@ -637,11 +637,6 @@ trait APIMethods210 {
existingTransactionRequest.challenge.id.equals(challengeAnswerJson.id)
}
//Check the allowed attemps, Note: not support yet, the default value is 3
_ <- Helper.booleanToFuture(s"${AllowedAttemptsUsedUp}", cc=callContext) {
existingTransactionRequest.challenge.allowed_attempts > 0
}
//Check the challenge type, Note: not support yet, the default value is SANDBOX_TAN
_ <- Helper.booleanToFuture(s"${InvalidChallengeType} ", cc=callContext) {
existingTransactionRequest.challenge.challenge_type == TransactionChallengeTypes.OTP_VIA_API.toString

View File

@ -312,8 +312,8 @@ trait APIMethods220 {
(Full(u), callContext) <- authenticatedAccess(cc)
(account, callContext) <- NewStyle.function.checkBankAccountExists(bankId, accountId, callContext)
view <- NewStyle.function.checkViewAccessAndReturnView(viewId, BankIdAccountId(account.bankId, account.accountId), Some(u), callContext)
_ <- Helper.booleanToFuture(failMsg = s"${NoViewPermission}canAddCounterparty", cc=callContext) {
view.canAddCounterparty == true
_ <- Helper.booleanToFuture(failMsg = s"${NoViewPermission} can_get_counterparty", cc=callContext) {
view.canGetCounterparty == true
}
(counterparties, callContext) <- NewStyle.function.getCounterparties(bankId,accountId,viewId, callContext)
//Here we need create the metadata for all the explicit counterparties. maybe show them in json response.
@ -362,8 +362,8 @@ trait APIMethods220 {
(Full(u), callContext) <- authenticatedAccess(cc)
(account, callContext) <- NewStyle.function.checkBankAccountExists(bankId, accountId, callContext)
view <- NewStyle.function.checkViewAccessAndReturnView(viewId, BankIdAccountId(account.bankId, account.accountId), Some(u), callContext)
_ <- Helper.booleanToFuture(failMsg = s"${NoViewPermission}canAddCounterparty", cc=callContext) {
view.canAddCounterparty == true
_ <- Helper.booleanToFuture(failMsg = s"${NoViewPermission}can_get_counterparty", cc=callContext) {
view.canGetCounterparty == true
}
counterpartyMetadata <- NewStyle.function.getMetadata(bankId, accountId, counterpartyId.value, callContext)
(counterparty, callContext) <- NewStyle.function.getCounterpartyTrait(bankId, accountId, counterpartyId.value, callContext)

View File

@ -1341,10 +1341,7 @@ trait APIMethods300 {
fullBoxOrException(Empty ?~! BranchesNotFound)
case Full((List(), callContext)) =>
Full(List())
case Full((list, callContext)) =>
val branchesWithLicense = for { branch <- list if branch.meta.license.name.size > 3 } yield branch
if (branchesWithLicense.size == 0) fullBoxOrException(Empty ?~! BranchesNotFoundLicense)
else Full(branchesWithLicense)
case Full((list, callContext)) => Full(list)
case Failure(msg, _, _) => fullBoxOrException(Empty ?~! msg)
case ParamFailure(msg,_,_,_) => fullBoxOrException(Empty ?~! msg)
} map { unboxFull(_) } map {
@ -1465,10 +1462,7 @@ trait APIMethods300 {
fullBoxOrException(Empty ?~! atmsNotFound)
case Full((List(), callContext)) =>
Full(List())
case Full((list, _)) =>
val branchesWithLicense = for { branch <- list if branch.meta.license.name.size > 3 } yield branch
if (branchesWithLicense.size == 0) fullBoxOrException(Empty ?~! atmsNotFoundLicense)
else Full(branchesWithLicense)
case Full((list, _)) => Full(list)
case Failure(msg, _, _) => fullBoxOrException(Empty ?~! msg)
case ParamFailure(msg,_,_,_) => fullBoxOrException(Empty ?~! msg)
} map { unboxFull(_) } map {
@ -1812,7 +1806,7 @@ trait APIMethods300 {
val msg = s"$InvalidJsonFormat The Json body should be the $CreateEntitlementRequestJSON "
x => unboxFullOrFail(x, callContext, msg)
}
_ <- Future { if (postedData.bank_id == "") Full() else NewStyle.function.getBank(BankId(postedData.bank_id), callContext)}
_ <- if (postedData.bank_id == "") Future.successful("") else NewStyle.function.getBank(BankId(postedData.bank_id), callContext)
_ <- Helper.booleanToFuture(failMsg = IncorrectRoleName + postedData.role_name + ". Possible roles are " + ApiRole.availableRoles.sorted.mkString(", "), cc=callContext) {
availableRoles.exists(_ == postedData.role_name)

View File

@ -1424,8 +1424,8 @@ trait APIMethods310 {
postedData <- NewStyle.function.tryons(failMsg, 400, callContext) {
json.extract[PostUserAuthContextJson]
}
(_, callContext) <- NewStyle.function.findByUserId(userId, callContext)
(userAuthContext, callContext) <- NewStyle.function.createUserAuthContext(userId, postedData.key, postedData.value, callContext)
(user, callContext) <- NewStyle.function.findByUserId(userId, callContext)
(userAuthContext, callContext) <- NewStyle.function.createUserAuthContext(user, postedData.key, postedData.value, callContext)
} yield {
(JSONFactory310.createUserAuthContextJson(userAuthContext), HttpCode.`201`(callContext))
}
@ -1538,8 +1538,8 @@ trait APIMethods310 {
for {
(Full(u), callContext) <- authenticatedAccess(cc)
_ <- NewStyle.function.hasEntitlement("", u.userId, canDeleteUserAuthContext, callContext)
(_, callContext) <- NewStyle.function.findByUserId(userId, callContext)
(userAuthContext, callContext) <- NewStyle.function.deleteUserAuthContextById(userAuthContextId, callContext)
(user, callContext) <- NewStyle.function.findByUserId(userId, callContext)
(userAuthContext, callContext) <- NewStyle.function.deleteUserAuthContextById(user, userAuthContextId, callContext)
} yield {
(Full(userAuthContext), HttpCode.`200`(callContext))
}
@ -3535,9 +3535,14 @@ trait APIMethods310 {
postConsentEmailJson <- NewStyle.function.tryons(failMsg, 400, callContext) {
json.extract[PostConsentEmailJsonV310]
}
params = PlainMailBodyType(challengeText) :: List(To(postConsentEmailJson.email))
_ <- Future{Mailer.sendMail(From("challenge@tesobe.com"), Subject("OBP Consent Challenge"), params :_*)}
} yield Future{true}
(Full(status), callContext) <- Connector.connector.vend.sendCustomerNotification(
StrongCustomerAuthentication.EMAIL,
postConsentEmailJson.email,
Some("OBP Consent Challenge"),
challengeText,
callContext
)
} yield Future{status}
case v if v == StrongCustomerAuthentication.SMS.toString => // Not implemented
for {
failMsg <- Future {
@ -3547,27 +3552,15 @@ trait APIMethods310 {
json.extract[PostConsentPhoneJsonV310]
}
phoneNumber = postConsentPhoneJson.phone_number
failMsg =s"$MissingPropsValueAtThisInstance sca_phone_api_key"
smsProviderApiKey <- NewStyle.function.tryons(failMsg, 400, callContext) {
APIUtil.getPropsValue("sca_phone_api_key").openOrThrowException(s"")
}
failMsg = s"$MissingPropsValueAtThisInstance sca_phone_api_secret"
smsProviderApiSecret <- NewStyle.function.tryons(failMsg, 400, callContext) {
APIUtil.getPropsValue("sca_phone_api_secret").openOrThrowException(s"")
}
client = new NexmoClient.Builder()
.apiKey(smsProviderApiKey)
.apiSecret(smsProviderApiSecret)
.build();
messageText = challengeText;
message = new TextMessage("OBP-API", phoneNumber, messageText);
response <- Future{client.getSmsClient().submitMessage(message)}
failMsg = s"$SmsServerNotResponding: $phoneNumber. Or Please to use EMAIL first."
_ <- Helper.booleanToFuture(failMsg, cc=callContext) {
response.getMessages.get(0).getStatus == com.nexmo.client.sms.MessageStatus.OK
}
} yield Future{true}
case _ =>Future{true}
(Full(status), callContext) <- Connector.connector.vend.sendCustomerNotification(
StrongCustomerAuthentication.SMS,
phoneNumber,
None,
challengeText,
callContext
)
} yield Future{status}
case _ =>Future{"Success"}
}
} yield {
(ConsentJsonV310(createdConsent.consentId, consentJWT, createdConsent.status), HttpCode.`201`(callContext))
@ -3790,11 +3783,12 @@ trait APIMethods310 {
json.extract[PostUserAuthContextUpdateJsonV310]
}
(userAuthContextUpdate, callContext) <- NewStyle.function.checkAnswer(authContextUpdateId, postUserAuthContextUpdateJson.answer, callContext)
(user, callContext) <- NewStyle.function.getUserByUserId(userAuthContextUpdate.userId, callContext)
(_, callContext) <-
userAuthContextUpdate.status match {
case status if status == UserAuthContextUpdateStatus.ACCEPTED.toString =>
NewStyle.function.createUserAuthContext(
userAuthContextUpdate.userId,
user,
userAuthContextUpdate.key,
userAuthContextUpdate.value,
callContext).map(x => (Some(x._1), x._2))
@ -3807,7 +3801,7 @@ trait APIMethods310 {
NewStyle.function.getOCreateUserCustomerLink(
bankId,
userAuthContextUpdate.value, // Customer number
userAuthContextUpdate.userId,
user.userId,
callContext
)
case _ =>
@ -4638,7 +4632,7 @@ trait APIMethods310 {
UnknownError
),
List(apiTagCustomer, apiTagNewStyle),
Some(canUpdateCustomerCreditRatingAndSource :: Nil)
Some(canUpdateCustomerCreditRatingAndSource :: canUpdateCustomerCreditRatingAndSourceAtAnyBank :: Nil)
)
lazy val updateCustomerCreditRatingAndSource : OBPEndpoint = {
@ -4647,7 +4641,7 @@ trait APIMethods310 {
for {
(Full(u), callContext) <- authenticatedAccess(cc)
(_, callContext) <- NewStyle.function.getBank(bankId, callContext)
_ <- NewStyle.function.hasEntitlement(bankId.value, u.userId, canUpdateCustomerCreditRatingAndSource, callContext)
_ <- NewStyle.function.hasAtLeastOneEntitlement(bankId.value, u.userId, List(canUpdateCustomerCreditRatingAndSource,canUpdateCustomerCreditRatingAndSourceAtAnyBank), callContext)
failMsg = s"$InvalidJsonFormat The Json body should be the $PutUpdateCustomerCreditRatingAndSourceJsonV310 "
putData <- NewStyle.function.tryons(failMsg, 400, callContext) {
json.extract[PutUpdateCustomerCreditRatingAndSourceJsonV310]

View File

@ -305,7 +305,7 @@ case class UserAuthContextJson(
user_id: String,
key: String,
value: String,
timeStamp: Date
time_stamp: Date
)
case class UserAuthContextUpdateJson(
user_auth_context_update_id: String,
@ -994,7 +994,7 @@ object JSONFactory310{
user_id = userAuthContext.userId,
key = userAuthContext.key,
value = userAuthContext.value,
timeStamp = userAuthContext.timeStamp
time_stamp = userAuthContext.timeStamp
)
}

File diff suppressed because it is too large Load Diff

View File

@ -37,7 +37,7 @@ import code.api.v1_2_1.{BankRoutingJsonV121, JSONFactory, UserJSONV121, ViewJSON
import code.api.v1_4_0.JSONFactory1_4_0.{LocationJsonV140, MetaJsonV140, TransactionRequestAccountJsonV140, transformToLocationFromV140, transformToMetaFromV140}
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_1_0.{IbanJson, JSONFactory210, PostCounterpartyBespokeJson, ResourceUserJSON, TransactionRequestBodyCounterpartyJSON}
import code.api.v2_2_0.CounterpartyMetadataJson
import code.api.v3_0_0.JSONFactory300._
import code.api.v3_0_0._
@ -58,6 +58,7 @@ import code.transactionrequests.TransactionRequests.TransactionChallengeTypes
import code.userlocks.UserLocks
import code.users.{UserAgreement, UserAttribute, UserInvitation}
import code.views.system.AccountAccess
import code.webhook.{AccountWebhook, BankAccountNotificationWebhookTrait, SystemAccountNotificationWebhookTrait}
import com.openbankproject.commons.model.{DirectDebitTrait, ProductFeeTrait, _}
import net.liftweb.common.{Box, Full}
import net.liftweb.json.JValue
@ -389,6 +390,26 @@ case class TransactionRequestBodySEPAJsonV400(
future_date: Option[String] = None,
reasons: Option[List[TransactionRequestReasonJsonV400]] = None
) extends TransactionRequestCommonBodyJSON
case class PostSimpleCounterpartyJson400(
name: String,
description: String,
other_bank_routing_scheme: String,
other_bank_routing_address: String,
other_account_routing_scheme: String,
other_account_routing_address: String,
other_account_secondary_routing_scheme: String,
other_account_secondary_routing_address: String,
other_branch_routing_scheme: String,
other_branch_routing_address: String
)
case class TransactionRequestBodySimpleJsonV400(
to: PostSimpleCounterpartyJson400,
value: AmountOfMoneyJsonV121,
description: String,
charge_policy: String,
future_date: Option[String] = None
) extends TransactionRequestCommonBodyJSON
case class TransactionRequestReasonJsonV400(
code: String,
@ -559,7 +580,8 @@ case class DatabaseInfoJson(product_name: String, product_version: String)
case class ChallengeJson(
challenge_id: String,
transaction_request_id: String,
expected_user_id: String
expected_user_id: String,
allowed_attempts: Int
)
case class SettlementAccountRequestJson(
@ -1003,6 +1025,31 @@ case class CustomerMessagesJsonV400(
messages: List[CustomerMessageJsonV400]
)
case class AccountNotificationWebhookPostJson(
url: String,
http_method: String,
http_protocol: String,
)
case class SystemAccountNotificationWebhookJson(
webhook_id: String,
trigger_name: String,
url: String,
http_method: String,
http_protocol: String,
created_by_user_id: String
)
case class BankAccountNotificationWebhookJson(
webhook_id: String,
bank_id: String,
trigger_name: String,
url: String,
http_method: String,
http_protocol: String,
created_by_user_id: String,
)
case class CustomerMessageJsonV400(
id: String,
date: Date,
@ -1186,7 +1233,7 @@ object JSONFactory400 {
case _ => ""
}
challenges.map(
e => ChallengeJsonV400(id = stringOrNull(e.challenge_id), user_id = e.expected_user_id, allowed_attempts = tr.challenge.allowed_attempts, challenge_type = stringOrNull(tr.challenge.challenge_type), link = link)
e => ChallengeJsonV400(id = stringOrNull(e.challenge_id), user_id = e.expected_user_id, allowed_attempts = e.allowed_attempts, challenge_type = stringOrNull(tr.challenge.challenge_type), link = link)
)
}
// catch { case _ : Throwable => ChallengeJSON (id = "", allowed_attempts = 0, challenge_type = "")}
@ -1911,7 +1958,29 @@ object JSONFactory400 {
def createCustomersMinimalJson(customers : List[Customer]) : CustomersMinimalJsonV400 = {
CustomersMinimalJsonV400(customers.map(i => CustomerMinimalJsonV400(i.bankId, i.customerId)))
}
def createSystemLevelAccountWebhookJsonV400(wh: SystemAccountNotificationWebhookTrait) = {
SystemAccountNotificationWebhookJson(
webhook_id = wh.webhookId,
trigger_name = wh.triggerName,
url = wh.url,
http_method = wh.httpMethod,
http_protocol = wh.httpProtocol,
created_by_user_id = wh.createdByUserId
)
}
def createBankLevelAccountWebhookJsonV400(wh: BankAccountNotificationWebhookTrait) = {
BankAccountNotificationWebhookJson(
webhook_id = wh.webhookId,
bank_id = wh.bankId,
trigger_name = wh.triggerName,
url = wh.url,
http_method = wh.httpMethod,
http_protocol = wh.httpProtocol,
created_by_user_id = wh.createdByUserId
)
}
}

View File

@ -36,9 +36,7 @@ import code.api.v2_1_0.APIMethods210
import code.api.v2_2_0.APIMethods220
import code.api.v3_0_0.APIMethods300
import code.api.v3_0_0.custom.CustomAPIMethods300
import code.api.v3_1_0.OBPAPI3_1_0.Implementations3_1_0
import code.api.v3_1_0.{APIMethods310, OBPAPI3_1_0}
import code.api.v4_0_0.dynamic.DynamicEndpoints
import code.util.Helper.MdcLoggable
import com.github.dwickern.macros.NameOf.nameOf
import com.openbankproject.commons.util.ApiVersion
@ -57,7 +55,7 @@ object OBPAPI4_0_0 extends OBPRestHelper with APIMethods130 with APIMethods140 w
// Possible Endpoints from 4.0.0, exclude one endpoint use - method,exclude multiple endpoints use -- method,
// e.g getEndpoints(Implementations4_0_0) -- List(Implementations4_0_0.genericEndpoint, Implementations4_0_0.root)
val endpointsOf4_0_0 = getEndpoints(Implementations4_0_0) - Implementations4_0_0.genericEndpoint - Implementations4_0_0.dynamicEndpoint
val endpointsOf4_0_0 = getEndpoints(Implementations4_0_0)
lazy val excludeEndpoints =
nameOf(Implementations1_2_1.addPermissionForUserForBankAccountForMultipleViews) ::
@ -77,30 +75,12 @@ object OBPAPI4_0_0 extends OBPRestHelper with APIMethods130 with APIMethods140 w
private val endpoints: List[OBPEndpoint] = OBPAPI3_1_0.routes ++ endpointsOf4_0_0
// Filter the possible endpoints by the disabled / enabled Props settings and add them together
val routes : List[OBPEndpoint] =
APIUtil.dynamicEndpointStub :: // corresponding all dynamic generated endpoints's OBPEndpoint
Implementations4_0_0.root :: // For now we make this mandatory
val routes : List[OBPEndpoint] = Implementations4_0_0.root :: // For now we make this mandatory
getAllowedEndpoints(endpoints, allResourceDocs)
// register v4.0.0 apis first, Make them available for use!
registerRoutes(routes, allResourceDocs, apiPrefix, true)
//This is the dynamic endpoints which are created by dynamic entities
oauthServe(apiPrefix{Implementations4_0_0.genericEndpoint}, None)
//This is for the dynamic endpoints which are created by dynamic swagger files
oauthServe(apiPrefix{Implementations4_0_0.dynamicEndpoint}, None)
/**
* Here is the place where we register the dynamicEndpoint, all the dynamic resource docs endpoints are here.
* Actually, we only register one endpoint for all the dynamic resource docs endpoints.
* For Liftweb, it just need to handle one endpoint,
* all the router functionalities are in OBP code.
* details: please also check code/api/v4_0_0/dynamic/DynamicEndpoints.findEndpoint method
* NOTE: this must be the last one endpoint to register into Liftweb
* Because firstly, Liftweb should look for the static endpoints --> then the dynamic ones.
* This is for the dynamic endpoints which are createdy by dynamic resourceDocs
*/
oauthServe(apiPrefix{DynamicEndpoints.dynamicEndpoint}, None)
logger.info(s"version $version has been run! There are ${routes.length} routes.")
// specified response for OPTIONS request.

View File

@ -0,0 +1,237 @@
package code.api.v5_0_0
import code.api.ResourceDocs1_4_0.SwaggerDefinitionsJSON._
import code.api.util.APIUtil._
import code.api.util.ApiRole.{CanCreateUserAuthContextUpdate, canCreateUserAuthContext, canGetUserAuthContext}
import code.api.util.ApiTag._
import code.api.util.ErrorMessages._
import code.api.util.{ApiRole, NewStyle}
import code.api.util.NewStyle.HttpCode
import code.api.v3_1_0.{PostUserAuthContextJson, PostUserAuthContextUpdateJsonV310}
import code.transactionrequests.TransactionRequests.TransactionRequestTypes.{apply => _}
import code.util.Helper
import com.github.dwickern.macros.NameOf.nameOf
import com.openbankproject.commons.ExecutionContext.Implicits.global
import com.openbankproject.commons.model.{BankId, UserAuthContextUpdateStatus}
import com.openbankproject.commons.model.enums.StrongCustomerAuthentication
import com.openbankproject.commons.util.ApiVersion
import net.liftweb.common.Full
import net.liftweb.http.rest.RestHelper
import scala.collection.immutable.{List, Nil}
import scala.collection.mutable.ArrayBuffer
import scala.concurrent.Future
trait APIMethods500 {
self: RestHelper =>
val Implementations5_0_0 = new Implementations500()
class Implementations500 {
val implementedInApiVersion = ApiVersion.v5_0_0
private val staticResourceDocs = ArrayBuffer[ResourceDoc]()
def resourceDocs = staticResourceDocs
val apiRelations = ArrayBuffer[ApiRelation]()
val codeContext = CodeContext(staticResourceDocs, apiRelations)
staticResourceDocs += ResourceDoc(
createUserAuthContext,
implementedInApiVersion,
nameOf(createUserAuthContext),
"POST",
"/users/USER_ID/auth-context",
"Create User Auth Context",
s"""Create User Auth Context. These key value pairs will be propagated over connector to adapter. Normally used for mapping OBP user and
| Bank User/Customer.
|${authenticationRequiredMessage(true)}
|""",
postUserAuthContextJson,
userAuthContextJsonV500,
List(
UserNotLoggedIn,
InvalidJsonFormat,
CreateUserAuthContextError,
UnknownError
),
List(apiTagUser, apiTagNewStyle),
Some(List(canCreateUserAuthContext)))
lazy val createUserAuthContext : OBPEndpoint = {
case "users" :: userId ::"auth-context" :: Nil JsonPost json -> _ => {
cc =>
for {
(Full(u), callContext) <- authenticatedAccess(cc)
_ <- NewStyle.function.hasEntitlement("", u.userId, canCreateUserAuthContext, callContext)
failMsg = s"$InvalidJsonFormat The Json body should be the $PostUserAuthContextJson "
postedData <- NewStyle.function.tryons(failMsg, 400, callContext) {
json.extract[PostUserAuthContextJson]
}
(user, callContext) <- NewStyle.function.findByUserId(userId, callContext)
(userAuthContext, callContext) <- NewStyle.function.createUserAuthContext(user, postedData.key, postedData.value, callContext)
} yield {
(JSONFactory500.createUserAuthContextJson(userAuthContext), HttpCode.`201`(callContext))
}
}
}
staticResourceDocs += ResourceDoc(
getUserAuthContexts,
implementedInApiVersion,
nameOf(getUserAuthContexts),
"GET",
"/users/USER_ID/auth-context",
"Get User Auth Contexts",
s"""Get User Auth Contexts for a User.
|
|
|${authenticationRequiredMessage(true)}
|
|""",
emptyObjectJson,
userAuthContextJsonV500,
List(
UserNotLoggedIn,
UserHasMissingRoles,
UnknownError
),
List(apiTagUser, apiTagNewStyle),
Some(canGetUserAuthContext :: Nil)
)
lazy val getUserAuthContexts : OBPEndpoint = {
case "users" :: userId :: "auth-context" :: Nil JsonGet _ => {
cc =>
for {
(Full(u), callContext) <- authenticatedAccess(cc)
_ <- NewStyle.function.hasEntitlement("", u.userId, canGetUserAuthContext, callContext)
(_, callContext) <- NewStyle.function.findByUserId(userId, callContext)
(userAuthContexts, callContext) <- NewStyle.function.getUserAuthContexts(userId, callContext)
} yield {
(JSONFactory500.createUserAuthContextsJson(userAuthContexts), HttpCode.`200`(callContext))
}
}
}
staticResourceDocs += ResourceDoc(
createUserAuthContextUpdateRequest,
implementedInApiVersion,
nameOf(createUserAuthContextUpdateRequest),
"POST",
"/banks/BANK_ID/users/current/auth-context-updates/SCA_METHOD",
"Create User Auth Context Update Request",
s"""Create User Auth Context Update Request.
|${authenticationRequiredMessage(true)}
|
|A One Time Password (OTP) (AKA security challenge) is sent Out of Band (OOB) to the User via the transport defined in SCA_METHOD
|SCA_METHOD is typically "SMS" or "EMAIL". "EMAIL" is used for testing purposes.
|
|""",
postUserAuthContextJson,
userAuthContextUpdateJsonV500,
List(
UserNotLoggedIn,
InvalidJsonFormat,
CreateUserAuthContextError,
UnknownError
),
List(apiTagUser, apiTagNewStyle),
None
)
lazy val createUserAuthContextUpdateRequest : OBPEndpoint = {
case "banks" :: BankId(bankId) :: "users" :: "current" ::"auth-context-updates" :: scaMethod :: Nil JsonPost json -> _ => {
cc =>
for {
(Full(user), callContext) <- authenticatedAccess(cc)
_ <- Helper.booleanToFuture(failMsg = ConsumerHasMissingRoles + CanCreateUserAuthContextUpdate, cc=callContext) {
checkScope(bankId.value, getConsumerPrimaryKey(callContext), ApiRole.canCreateUserAuthContextUpdate)
}
(_, callContext) <- NewStyle.function.getBank(bankId, callContext)
_ <- Helper.booleanToFuture(ConsentAllowedScaMethods, cc=callContext){
List(StrongCustomerAuthentication.SMS.toString(), StrongCustomerAuthentication.EMAIL.toString()).exists(_ == scaMethod)
}
failMsg = s"$InvalidJsonFormat The Json body should be the $PostUserAuthContextJson "
postedData <- NewStyle.function.tryons(failMsg, 400, callContext) {
json.extract[PostUserAuthContextJson]
}
(userAuthContextUpdate, callContext) <- NewStyle.function.validateUserAuthContextUpdateRequest(bankId.value, user.userId, postedData.key, postedData.value, scaMethod, callContext)
} yield {
(JSONFactory500.createUserAuthContextUpdateJson(userAuthContextUpdate), HttpCode.`201`(callContext))
}
}
}
staticResourceDocs += ResourceDoc(
answerUserAuthContextUpdateChallenge,
implementedInApiVersion,
nameOf(answerUserAuthContextUpdateChallenge),
"POST",
"/banks/BANK_ID/users/current/auth-context-updates/AUTH_CONTEXT_UPDATE_ID/challenge",
"Answer Auth Context Update Challenge",
s"""
|Answer Auth Context Update Challenge.
|""",
postUserAuthContextUpdateJsonV310,
userAuthContextUpdateJsonV500,
List(
UserNotLoggedIn,
BankNotFound,
InvalidJsonFormat,
InvalidConnectorResponse,
UnknownError
),
apiTagUser :: apiTagNewStyle :: Nil)
lazy val answerUserAuthContextUpdateChallenge : OBPEndpoint = {
case "banks" :: BankId(bankId) :: "users" :: "current" ::"auth-context-updates" :: authContextUpdateId :: "challenge" :: Nil JsonPost json -> _ => {
cc =>
for {
(_, callContext) <- authenticatedAccess(cc)
failMsg = s"$InvalidJsonFormat The Json body should be the $PostUserAuthContextUpdateJsonV310 "
postUserAuthContextUpdateJson <- NewStyle.function.tryons(failMsg, 400, callContext) {
json.extract[PostUserAuthContextUpdateJsonV310]
}
(userAuthContextUpdate, callContext) <- NewStyle.function.checkAnswer(authContextUpdateId, postUserAuthContextUpdateJson.answer, callContext)
(user, callContext) <- NewStyle.function.getUserByUserId(userAuthContextUpdate.userId, callContext)
(_, callContext) <-
userAuthContextUpdate.status match {
case status if status == UserAuthContextUpdateStatus.ACCEPTED.toString =>
NewStyle.function.createUserAuthContext(
user,
userAuthContextUpdate.key,
userAuthContextUpdate.value,
callContext).map(x => (Some(x._1), x._2))
case _ =>
Future((None, callContext))
}
(_, callContext) <-
userAuthContextUpdate.key match {
case "CUSTOMER_NUMBER" =>
NewStyle.function.getOCreateUserCustomerLink(
bankId,
userAuthContextUpdate.value, // Customer number
user.userId,
callContext
)
case _ =>
Future((None, callContext))
}
} yield {
(JSONFactory500.createUserAuthContextUpdateJson(userAuthContextUpdate), HttpCode.`200`(callContext))
}
}
}
}
}
object APIMethods500 extends RestHelper with APIMethods500 {
lazy val newStyleEndpoints: List[(String, String)] = Implementations5_0_0.resourceDocs.map {
rd => (rd.partialFunctionName, rd.implementedInApiVersion.toString())
}.toList
}

View File

@ -0,0 +1,83 @@
/**
* 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
* Osloerstrasse 16/17
* Berlin 13359, Germany
* *
* This product includes software developed at
* TESOBE (http://www.tesobe.com/)
*
*/
package code.api.v5_0_0
import com.openbankproject.commons.model.{UserAuthContext, UserAuthContextUpdate}
import java.util.Date
case class UserAuthContextJsonV500(
user_auth_context_id: String,
user_id: String,
key: String,
value: String,
time_stamp: Date,
consumer_id: String,
)
case class UserAuthContextsJsonV500(
user_auth_contexts: List[UserAuthContextJsonV500]
)
case class UserAuthContextUpdateJsonV500(
user_auth_context_update_id: String,
user_id: String,
key: String,
value: String,
status: String,
consumer_id: String,
)
object JSONFactory500 {
def createUserAuthContextJson(userAuthContext: UserAuthContext): UserAuthContextJsonV500 = {
UserAuthContextJsonV500(
user_auth_context_id= userAuthContext.userAuthContextId,
user_id = userAuthContext.userId,
key = userAuthContext.key,
value = userAuthContext.value,
time_stamp = userAuthContext.timeStamp,
consumer_id = userAuthContext.consumerId,
)
}
def createUserAuthContextsJson(userAuthContext: List[UserAuthContext]): UserAuthContextsJsonV500 = {
UserAuthContextsJsonV500(userAuthContext.map(createUserAuthContextJson))
}
def createUserAuthContextUpdateJson(userAuthContextUpdate: UserAuthContextUpdate): UserAuthContextUpdateJsonV500 = {
UserAuthContextUpdateJsonV500(
user_auth_context_update_id= userAuthContextUpdate.userAuthContextUpdateId,
user_id = userAuthContextUpdate.userId,
key = userAuthContextUpdate.key,
value = userAuthContextUpdate.value,
status = userAuthContextUpdate.status,
consumer_id = userAuthContextUpdate.consumerId
)
}
}

View File

@ -0,0 +1,110 @@
/**
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.v5_0_0
import code.api.OBPRestHelper
import code.api.util.APIUtil.{OBPEndpoint, getAllowedEndpoints}
import code.api.util.{APIUtil, VersionedOBPApis}
import code.api.v1_3_0.APIMethods130
import code.api.v1_4_0.APIMethods140
import code.api.v2_0_0.APIMethods200
import code.api.v2_1_0.APIMethods210
import code.api.v2_2_0.APIMethods220
import code.api.v3_0_0.APIMethods300
import code.api.v3_0_0.custom.CustomAPIMethods300
import code.api.v3_1_0.{APIMethods310, OBPAPI3_1_0}
import code.api.v4_0_0.{APIMethods400, OBPAPI4_0_0}
import code.api.v4_0_0.OBPAPI4_0_0.{Implementations4_0_0, endpointsOf4_0_0}
import code.util.Helper.MdcLoggable
import com.github.dwickern.macros.NameOf.nameOf
import com.openbankproject.commons.util.ApiVersion
import net.liftweb.common.{Box, Full}
import net.liftweb.http.{LiftResponse, PlainTextResponse}
import org.apache.http.HttpStatus
/*
This file defines which endpoints from all the versions are available in v5.0.0
*/
object OBPAPI5_0_0 extends OBPRestHelper
with APIMethods130
with APIMethods140
with APIMethods200
with APIMethods210
with APIMethods220
with APIMethods300
with CustomAPIMethods300
with APIMethods310
with APIMethods400
with APIMethods500
with MdcLoggable
with VersionedOBPApis{
val version : ApiVersion = ApiVersion.v5_0_0
val versionStatus = "BLEEDING-EDGE" // TODO this should be a property of ApiVersion.
// Possible Endpoints from 5.0.0, exclude one endpoint use - method,exclude multiple endpoints use -- method,
// e.g getEndpoints(Implementations5_0_0) -- List(Implementations5_0_0.genericEndpoint, Implementations5_0_0.root)
val endpointsOf5_0_0 = getEndpoints(Implementations5_0_0)
// if old version ResourceDoc objects have the same name endpoint with new version, omit old version ResourceDoc.
def allResourceDocs = collectResourceDocs(
OBPAPI4_0_0.allResourceDocs,
Implementations5_0_0.resourceDocs
)
// all endpoints
private val endpoints: List[OBPEndpoint] = OBPAPI4_0_0.routes ++ endpointsOf5_0_0
// Filter the possible endpoints by the disabled / enabled Props settings and add them together
val routes : List[OBPEndpoint] = Implementations4_0_0.root :: // For now we make this mandatory
getAllowedEndpoints(endpoints, allResourceDocs)
// register v5.0.0 apis first, Make them available for use!
registerRoutes(routes, allResourceDocs, apiPrefix, true)
logger.info(s"version $version has been run! There are ${routes.length} routes.")
// specified response for OPTIONS request.
private val corsResponse: Box[LiftResponse] = Full{
val corsHeaders = List(
"Access-Control-Allow-Origin" -> "*",
"Access-Control-Allow-Methods" -> "GET, POST, OPTIONS, PUT, PATCH, DELETE",
"Access-Control-Allow-Headers" -> "*",
"Access-Control-Allow-Credentials" -> "true",
"Access-Control-Max-Age" -> "1728000" //Tell client that this pre-flight info is valid for 20 days
)
PlainTextResponse("", corsHeaders, HttpStatus.SC_NO_CONTENT)
}
/*
* process OPTIONS http request, just return no content and status is 204
*/
this.serve({
case req if req.requestType.method == "OPTIONS" => corsResponse
})
}

View File

@ -89,23 +89,14 @@ trait AtmsProvider {
*/
final def getAtms(bankId : BankId, queryParams: List[OBPQueryParam]) : Option[List[AtmT]] = {
// If we get atms filter them
getAtmsFromProvider(bankId,queryParams) match {
case Some(atms) => {
val atmsWithLicense = for {
branch <- atms if branch.meta.license.name.size > 3
} yield branch
Option(atmsWithLicense)
}
case None => None
}
getAtmsFromProvider(bankId,queryParams)
}
/*
Return one Atm
*/
final def getAtm(bankId: BankId, branchId : AtmId) : Option[AtmT] = {
// Filter out if no license data
getAtmFromProvider(bankId,branchId).filter(x => x.meta.license.id != "" && x.meta.license.name != "")
getAtmFromProvider(bankId,branchId)
}
protected def getAtmFromProvider(bankId: BankId, branchId : AtmId) : Option[AtmT]

View File

@ -558,6 +558,8 @@ trait Connector extends MdcLoggable {
def getCounterpartyByCounterpartyIdLegacy(counterpartyId: CounterpartyId, callContext: Option[CallContext]): Box[(CounterpartyTrait, Option[CallContext])]= Failure(setUnimplementedError)
def getCounterpartyByCounterpartyId(counterpartyId: CounterpartyId, callContext: Option[CallContext]): OBPReturnType[Box[CounterpartyTrait]] = Future{(Failure(setUnimplementedError), callContext)}
def deleteCounterpartyByCounterpartyId(counterpartyId: CounterpartyId, callContext: Option[CallContext]): OBPReturnType[Box[Boolean]] = Future{(Failure(setUnimplementedError), callContext)}
/**
@ -567,6 +569,37 @@ trait Connector extends MdcLoggable {
def getCounterpartyByIban(iban: String, callContext: Option[CallContext]) : OBPReturnType[Box[CounterpartyTrait]] = Future {(Failure(setUnimplementedError), callContext)}
def getCounterpartyByIbanAndBankAccountId(iban: String, bankId: BankId, accountId: AccountId, callContext: Option[CallContext]) : OBPReturnType[Box[CounterpartyTrait]] = Future {(Failure(setUnimplementedError), callContext)}
def getOrCreateCounterparty(
name: String,
description: String,
currency: String,
createdByUserId: String,
thisBankId: String,
thisAccountId: String,
thisViewId: String,
other_bank_routing_scheme: String,
other_bank_routing_address: String,
other_branch_routing_scheme: String,
other_branch_routing_address: String,
other_account_routing_scheme: String,
other_account_routing_address: String,
other_account_secondary_routing_scheme: String,
other_account_secondary_routing_address: String,
callContext: Option[CallContext]
): OBPReturnType[Box[CounterpartyTrait]] = Future {(Failure(setUnimplementedError), callContext)}
def getCounterpartyByRoutings(
otherBankRoutingScheme: String,
otherBankRoutingAddress: String,
otherBranchRoutingScheme: String,
otherBranchRoutingAddress: String,
otherAccountRoutingScheme: String,
otherAccountRoutingAddress: String,
otherAccountSecondaryRoutingScheme: String,
otherAccountSecondaryRoutingAddress: String,
callContext: Option[CallContext]
): OBPReturnType[Box[CounterpartyTrait]] = Future {(Failure(setUnimplementedError), callContext)}
def getCounterpartiesLegacy(thisBankId: BankId, thisAccountId: AccountId, viewId :ViewId, callContext: Option[CallContext] = None): Box[(List[CounterpartyTrait], Option[CallContext])]= Failure(setUnimplementedError)
@ -739,6 +772,8 @@ trait Connector extends MdcLoggable {
def getDoubleEntryBookTransaction(bankId: BankId, accountId: AccountId, transactionId: TransactionId,
callContext: Option[CallContext]): OBPReturnType[Box[DoubleEntryTransaction]]= Future{(Failure(setUnimplementedError), callContext)}
def getBalancingTransaction(transactionId: TransactionId,
callContext: Option[CallContext]): OBPReturnType[Box[DoubleEntryTransaction]]= Future{(Failure(setUnimplementedError), callContext)}
protected def makePaymentImpl(fromAccount: BankAccount, toAccount: BankAccount, transactionRequestCommonBody: TransactionRequestCommonBodyJSON, amt: BigDecimal, description: String, transactionRequestType: TransactionRequestType, chargePolicy: String): Box[TransactionId]= Failure(setUnimplementedError)
@ -2514,4 +2549,12 @@ trait Connector extends MdcLoggable {
def checkAnswer(authContextUpdateId: String, challenge: String, callContext: Option[CallContext]): OBPReturnType[Box[UserAuthContextUpdate]] = Future{(Failure(setUnimplementedError), callContext)}
def sendCustomerNotification(
scaMethod: StrongCustomerAuthentication,
recipient: String,
subject: Option[String], //Only for EMAIL, SMS do not need it, so here it is Option
message: String,
callContext: Option[CallContext]
): OBPReturnType[Box[String]] = Future{(Failure(setUnimplementedError), callContext)}
}

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}
@ -10,7 +9,7 @@ import code.accountapplication.AccountApplicationX
import code.accountattribute.AccountAttributeX
import code.accountholders.{AccountHolders, MapperAccountHolders}
import code.api.BerlinGroup.{AuthenticationType, ScaStatus}
import code.api.Constant.{INCOMING_ACCOUNT_ID, OUTGOING_ACCOUNT_ID}
import code.api.Constant.{INCOMING_SETTLEMENT_ACCOUNT_ID, OUTGOING_SETTLEMENT_ACCOUNT_ID}
import code.api.ResourceDocs1_4_0.SwaggerDefinitionsJSON
import code.api.attributedefinition.{AttributeDefinition, AttributeDefinitionDI}
import code.api.cache.Caching
@ -20,6 +19,7 @@ import code.api.util.ErrorMessages.{attemptedToOpenAnEmptyBox, _}
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.{PostSimpleCounterpartyJson400, TransactionRequestBodySimpleJsonV400}
import code.atms.Atms.Atm
import code.atms.MappedAtm
import code.bankattribute.{BankAttribute, BankAttributeX}
@ -978,6 +978,10 @@ object LocalMappedConnector extends Connector with MdcLoggable {
(Counterparties.counterparties.vend.getCounterparty(counterpartyId.value), callContext)
}
override def deleteCounterpartyByCounterpartyId(counterpartyId: CounterpartyId, callContext: Option[CallContext]): OBPReturnType[Box[Boolean]] = Future {
(Counterparties.counterparties.vend.deleteCounterparty(counterpartyId.value), callContext)
}
override def getCounterpartyByIban(iban: String, callContext: Option[CallContext]): OBPReturnType[Box[CounterpartyTrait]] = {
Future(Counterparties.counterparties.vend.getCounterpartyByIban(iban), callContext)
}
@ -986,6 +990,118 @@ object LocalMappedConnector extends Connector with MdcLoggable {
Future(Counterparties.counterparties.vend.getCounterpartyByIbanAndBankAccountId(iban, bankId, accountId), callContext)
}
override def getCounterpartyByRoutings(
otherBankRoutingScheme: String,
otherBankRoutingAddress: String,
otherBranchRoutingScheme: String,
otherBranchRoutingAddress: String,
otherAccountRoutingScheme: String,
otherAccountRoutingAddress: String,
otherAccountSecondaryRoutingScheme: String,
otherAccountSecondaryRoutingAddress: String,
callContext: Option[CallContext]
): OBPReturnType[Box[CounterpartyTrait]] = Future {
lazy val counterpartyFromRoutings= Counterparties.counterparties.vend.getCounterpartyByRoutings(
otherBankRoutingScheme: String,
otherBankRoutingAddress: String,
otherBranchRoutingScheme: String,
otherBranchRoutingAddress: String,
otherAccountRoutingScheme: String,
otherAccountRoutingAddress: String
)
lazy val counterpartyFromSecondaryRouting = Counterparties.counterparties.vend.getCounterpartyBySecondaryRouting(
otherAccountSecondaryRoutingScheme: String,
otherAccountSecondaryRoutingAddress: String
)
if(counterpartyFromRoutings.isDefined) {
(counterpartyFromRoutings, callContext)
} else if(counterpartyFromSecondaryRouting.isDefined) {
(counterpartyFromSecondaryRouting, callContext)
} else {
(Failure(CounterpartyNotFoundByRoutings), callContext)
}
}
override def getOrCreateCounterparty(
name: String,
description: String,
currency: String,
createdByUserId: String,
thisBankId: String,
thisAccountId: String,
thisViewId: String,
otherBankRoutingScheme: String,
otherBankRoutingAddress: String,
otherBranchRoutingScheme: String,
otherBranchRoutingAddress: String,
otherAccountRoutingScheme: String,
otherAccountRoutingAddress: String,
otherAccountSecondaryRoutingScheme: String,
otherAccountSecondaryRoutingAddress: String,
callContext: Option[CallContext]
): OBPReturnType[Box[CounterpartyTrait]] = Future {
lazy val counterpartyFromRoutings= Counterparties.counterparties.vend.getCounterpartyByRoutings(
otherBankRoutingScheme: String,
otherBankRoutingAddress: String,
otherBranchRoutingScheme: String,
otherBranchRoutingAddress: String,
otherAccountRoutingScheme: String,
otherAccountRoutingAddress: String
)
lazy val counterpartyFromSecondaryRouting = Counterparties.counterparties.vend.getCounterpartyBySecondaryRouting(
otherAccountSecondaryRoutingScheme: String,
otherAccountSecondaryRoutingAddress: String
)
if(counterpartyFromRoutings.isDefined) {
(counterpartyFromRoutings, callContext)
} else if(counterpartyFromSecondaryRouting.isDefined) {
(counterpartyFromSecondaryRouting, callContext)
} else{
val newCounterparty = for{
_ <- Helper.booleanToBox(
Counterparties.counterparties.vend.checkCounterpartyExists(
name: String,
thisBankId: String,
thisAccountId: String,
thisViewId: String
).isEmpty,
CounterpartyAlreadyExists.replace("value for BANK_ID or ACCOUNT_ID or VIEW_ID or NAME.",
s"COUNTERPARTY_NAME(${name}) for the BANK_ID(${thisBankId}) and ACCOUNT_ID(${thisAccountId}) and VIEW_ID($thisViewId)")
)
counterparty <- Counterparties.counterparties.vend.createCounterparty(
createdByUserId = createdByUserId,
thisBankId = thisBankId,
thisAccountId = thisAccountId,
thisViewId = thisViewId,
name = name,
otherAccountRoutingScheme = otherAccountRoutingScheme,
otherAccountRoutingAddress = otherAccountRoutingAddress,
otherBankRoutingScheme = otherBankRoutingScheme,
otherBankRoutingAddress = otherBankRoutingAddress,
otherBranchRoutingScheme = otherBranchRoutingScheme,
otherBranchRoutingAddress = otherBranchRoutingAddress,
isBeneficiary = true,
otherAccountSecondaryRoutingScheme = otherAccountSecondaryRoutingScheme,
otherAccountSecondaryRoutingAddress = otherAccountSecondaryRoutingAddress,
description = description,
currency = currency,
bespoke = Nil
)
} yield{
counterparty
}
(newCounterparty, callContext)
}
}
override def getPhysicalCardsForUser(user: User, callContext: Option[CallContext]): OBPReturnType[Box[List[PhysicalCard]]] = Future {
val list = code.cards.PhysicalCard.physicalCardProvider.vend.getPhysicalCards(user)
@ -1409,6 +1525,16 @@ object LocalMappedConnector extends Connector with MdcLoggable {
))
).map(doubleEntryTransaction => (doubleEntryTransaction, callContext))
}
override def getBalancingTransaction(transactionId: TransactionId,
callContext: Option[CallContext]): OBPReturnType[Box[DoubleEntryTransaction]] = {
Future(
DoubleEntryBookTransaction.find(
By(DoubleEntryBookTransaction.DebitTransactionId, transactionId.value)
).or(DoubleEntryBookTransaction.find(
By(DoubleEntryBookTransaction.CreditTransactionId, transactionId.value)
))
).map(doubleEntryTransaction => (doubleEntryTransaction, callContext))
}
override def makePaymentV400(transactionRequest: TransactionRequest,
reasons: Option[List[TransactionRequestReason]],
@ -1486,12 +1612,12 @@ object LocalMappedConnector extends Connector with MdcLoggable {
// If it doesn't exist, we look for a default settlement account regarding the currency
.or(BankAccountX(toAccount.bankId, AccountId("DEFAULT_SETTLEMENT_ACCOUNT_" + fromAccount.currency), callContext))
// If no specific settlement account exist for this currency, we use the default incoming account (EUR)
.or(BankAccountX(toAccount.bankId, AccountId(INCOMING_ACCOUNT_ID), callContext))
.or(BankAccountX(toAccount.bankId, AccountId(INCOMING_SETTLEMENT_ACCOUNT_ID), callContext))
}
settlementAccount.flatMap(settlementAccount => {
val fromTransAmtSettlementAccount: BigDecimal = {
// In the case we selected the default settlement account INCOMING_ACCOUNT_ID account and that the counterparty currency is different from EUR, we need to calculate the amount in EUR
if (settlementAccount._1.accountId.value == INCOMING_ACCOUNT_ID && settlementAccount._1.currency != fromAccount.currency) {
if (settlementAccount._1.accountId.value == INCOMING_SETTLEMENT_ACCOUNT_ID && settlementAccount._1.currency != fromAccount.currency) {
val rate = fx.exchangeRate(currency, settlementAccount._1.currency, Some(bankIdExchangeRate.bankId.value))
Try(-fx.convert(amount, rate)).getOrElse(throw new Exception(s"$InvalidCurrency The requested currency conversion ($currency to ${settlementAccount._1.currency}) is not supported."))
} else fromTransAmt
@ -1512,11 +1638,11 @@ object LocalMappedConnector extends Connector with MdcLoggable {
// If it doesn't exist, we look for a default settlement account regarding the currency
.or(BankAccountX(fromAccount.bankId, AccountId("DEFAULT_SETTLEMENT_ACCOUNT_" + toAccount.currency), callContext))
// If no specific settlement account exist for this currency, we use the default outgoing account (EUR)
.or(BankAccountX(fromAccount.bankId, AccountId(OUTGOING_ACCOUNT_ID), callContext))
.or(BankAccountX(fromAccount.bankId, AccountId(OUTGOING_SETTLEMENT_ACCOUNT_ID), callContext))
settlementAccount.flatMap(settlementAccount => {
val toTransAmtSettlementAccount: BigDecimal = {
// In the case we selected the default settlement account OUTGOING_ACCOUNT_ID account and that the counterparty currency is different from EUR, we need to calculate the amount in EUR
if (settlementAccount._1.accountId.value == OUTGOING_ACCOUNT_ID && settlementAccount._1.currency != toAccount.currency) {
if (settlementAccount._1.accountId.value == OUTGOING_SETTLEMENT_ACCOUNT_ID && settlementAccount._1.currency != toAccount.currency) {
val rate = fx.exchangeRate(currency, settlementAccount._1.currency, Some(bankIdExchangeRate.bankId.value))
Try(fx.convert(amount, rate)).getOrElse(throw new Exception(s"$InvalidCurrency The requested currency conversion ($currency to ${settlementAccount._1.currency}) is not supported."))
} else toTransAmt
@ -1527,8 +1653,8 @@ object LocalMappedConnector extends Connector with MdcLoggable {
}
)
debitTransaction = debitTransactionBox.openOrThrowException(s"Error while opening debitTransaction. This error can happen when no settlement can be found, please check that $INCOMING_ACCOUNT_ID exists at bank ${toAccount.bankId.value}")
creditTransaction = creditTransactionBox.openOrThrowException(s"Error while opening creditTransaction. This error can happen when no settlement can be found, please check that $OUTGOING_ACCOUNT_ID exists at bank ${fromAccount.bankId.value}")
debitTransaction = debitTransactionBox.openOrThrowException(s"Error while opening debitTransaction. This error can happen when no settlement can be found, please check that $INCOMING_SETTLEMENT_ACCOUNT_ID exists at bank ${toAccount.bankId.value}")
creditTransaction = creditTransactionBox.openOrThrowException(s"Error while opening creditTransaction. This error can happen when no settlement can be found, please check that $OUTGOING_SETTLEMENT_ACCOUNT_ID exists at bank ${fromAccount.bankId.value}")
_ <- NewStyle.function.saveDoubleEntryBookTransaction(
DoubleEntryTransaction(
@ -1643,11 +1769,11 @@ object LocalMappedConnector extends Connector with MdcLoggable {
// If it doesn't exist, we look for a default settlement account regarding the currency
.or(BankAccountX(toAccount.bankId, AccountId("DEFAULT_SETTLEMENT_ACCOUNT_" + fromAccount.currency), callContext))
// If no specific settlement account exist for this currency, we use the default incoming account (EUR)
.or(BankAccountX(toAccount.bankId, AccountId(INCOMING_ACCOUNT_ID), callContext))
.or(BankAccountX(toAccount.bankId, AccountId(INCOMING_SETTLEMENT_ACCOUNT_ID), callContext))
settlementAccount.flatMap(settlementAccount => {
val fromTransAmtSettlementAccount = {
// In the case we selected the default settlement account INCOMING_ACCOUNT_ID account and that the counterparty currency is different from EUR, we need to calculate the amount in EUR
if (settlementAccount._1.accountId.value == INCOMING_ACCOUNT_ID && settlementAccount._1.currency != fromAccount.currency) {
if (settlementAccount._1.accountId.value == INCOMING_SETTLEMENT_ACCOUNT_ID && settlementAccount._1.currency != fromAccount.currency) {
val rate = fx.exchangeRate(transactionCurrency, settlementAccount._1.currency, Some(bankIdExchangeRate.bankId.value))
Try(-fx.convert(amount, rate)).getOrElse(throw new Exception(s"$InvalidCurrency The requested currency conversion ($transactionCurrency to ${settlementAccount._1.currency}) is not supported."))
} else fromTransAmt
@ -1668,11 +1794,11 @@ object LocalMappedConnector extends Connector with MdcLoggable {
// If it doesn't exist, we look for a default settlement account regarding the currency
.or(BankAccountX(fromAccount.bankId, AccountId("DEFAULT_SETTLEMENT_ACCOUNT_" + toAccount.currency), callContext))
// If no specific settlement account exist for this currency, we use the default outgoing account (EUR)
.or(BankAccountX(fromAccount.bankId, AccountId(OUTGOING_ACCOUNT_ID), callContext))
.or(BankAccountX(fromAccount.bankId, AccountId(OUTGOING_SETTLEMENT_ACCOUNT_ID), callContext))
settlementAccount.flatMap(settlementAccount => {
val toTransAmtSettlementAccount = {
// In the case we selected the default settlement account OUTGOING_ACCOUNT_ID account and that the counterparty currency is different from EUR, we need to calculate the amount in EUR
if (settlementAccount._1.accountId.value == OUTGOING_ACCOUNT_ID && settlementAccount._1.currency != toAccount.currency) {
if (settlementAccount._1.accountId.value == OUTGOING_SETTLEMENT_ACCOUNT_ID && settlementAccount._1.currency != toAccount.currency) {
val rate = fx.exchangeRate(transactionCurrency, settlementAccount._1.currency, Some(bankIdExchangeRate.bankId.value))
Try(fx.convert(amount, rate)).getOrElse(throw new Exception(s"$InvalidCurrency The requested currency conversion ($transactionCurrency to ${settlementAccount._1.currency}) is not supported."))
} else toTransAmt
@ -1683,8 +1809,8 @@ object LocalMappedConnector extends Connector with MdcLoggable {
}
}
debitTransaction = debitTransactionBox.openOrThrowException(s"Error while opening debitTransaction. This error can happen when no settlement can be found, please check that $INCOMING_ACCOUNT_ID exists at bank ${toAccount.bankId.value}")
creditTransaction = creditTransactionBox.openOrThrowException(s"Error while opening creditTransaction. This error can happen when no settlement can be found, please check that $OUTGOING_ACCOUNT_ID exists at bank ${fromAccount.bankId.value}")
debitTransaction = debitTransactionBox.openOrThrowException(s"Error while opening debitTransaction. This error can happen when no settlement can be found, please check that $INCOMING_SETTLEMENT_ACCOUNT_ID exists at bank ${toAccount.bankId.value}")
creditTransaction = creditTransactionBox.openOrThrowException(s"Error while opening creditTransaction. This error can happen when no settlement can be found, please check that $OUTGOING_SETTLEMENT_ACCOUNT_ID exists at bank ${fromAccount.bankId.value}")
_ <- NewStyle.function.saveDoubleEntryBookTransaction(
DoubleEntryTransaction(
@ -3168,36 +3294,36 @@ object LocalMappedConnector extends Connector with MdcLoggable {
}
// Insert the default settlement accounts if they doesn't exist
MappedBankAccount.find(By(MappedBankAccount.bank, bankId), By(MappedBankAccount.theAccountId, INCOMING_ACCOUNT_ID)) match {
MappedBankAccount.find(By(MappedBankAccount.bank, bankId), By(MappedBankAccount.theAccountId, INCOMING_SETTLEMENT_ACCOUNT_ID)) match {
case Full(_) =>
logger.debug(s"BankAccount(${bankId}, $INCOMING_ACCOUNT_ID) is found.")
logger.debug(s"BankAccount(${bankId}, $INCOMING_SETTLEMENT_ACCOUNT_ID) is found.")
case _ =>
MappedBankAccount.create
.bank(bankId)
.theAccountId(INCOMING_ACCOUNT_ID)
.theAccountId(INCOMING_SETTLEMENT_ACCOUNT_ID)
.accountCurrency("EUR")
.kind("SETTLEMENT")
.holder(fullBankName)
.holder(fullBankName)// TODO Consider to use the table MapperAccountHolder
.accountName("Default incoming settlement account")
.accountLabel("Settlement account: Do not delete!")
.saveMe()
logger.debug(s"creating BankAccount(${bankId}, $INCOMING_ACCOUNT_ID).")
logger.debug(s"creating BankAccount(${bankId}, $INCOMING_SETTLEMENT_ACCOUNT_ID).")
}
MappedBankAccount.find(By(MappedBankAccount.bank, bankId), By(MappedBankAccount.theAccountId, OUTGOING_ACCOUNT_ID)) match {
MappedBankAccount.find(By(MappedBankAccount.bank, bankId), By(MappedBankAccount.theAccountId, OUTGOING_SETTLEMENT_ACCOUNT_ID)) match {
case Full(_) =>
logger.debug(s"BankAccount(${bankId}, $OUTGOING_ACCOUNT_ID) is found.")
logger.debug(s"BankAccount(${bankId}, $OUTGOING_SETTLEMENT_ACCOUNT_ID) is found.")
case _ =>
MappedBankAccount.create
.bank(bankId)
.theAccountId(OUTGOING_ACCOUNT_ID)
.theAccountId(OUTGOING_SETTLEMENT_ACCOUNT_ID)
.accountCurrency("EUR")
.kind("SETTLEMENT")
.holder(fullBankName)
.accountName("Default outgoing settlement account")
.accountLabel("Settlement account: Do not delete!")
.saveMe()
logger.debug(s"creating BankAccount(${bankId}, $OUTGOING_ACCOUNT_ID).")
logger.debug(s"creating BankAccount(${bankId}, $OUTGOING_SETTLEMENT_ACCOUNT_ID).")
}
bank
@ -3514,18 +3640,22 @@ object LocalMappedConnector extends Connector with MdcLoggable {
override def createUserAuthContext(userId: String,
key: String,
value: String,
callContext: Option[CallContext]): OBPReturnType[Box[UserAuthContext]] =
UserAuthContextProvider.userAuthContextProvider.vend.createUserAuthContext(userId, key, value) map {
callContext: Option[CallContext]): OBPReturnType[Box[UserAuthContext]] = {
val consumerId = callContext.map(_.consumer.map(_.consumerId.get).getOrElse("")).getOrElse("")
UserAuthContextProvider.userAuthContextProvider.vend.createUserAuthContext(userId, key, value, consumerId) map {
(_, callContext)
}
}
override def createUserAuthContextUpdate(userId: String,
key: String,
value: String,
callContext: Option[CallContext]): OBPReturnType[Box[UserAuthContextUpdate]] =
UserAuthContextUpdateProvider.userAuthContextUpdateProvider.vend.createUserAuthContextUpdates(userId, key, value) map {
callContext: Option[CallContext]): OBPReturnType[Box[UserAuthContextUpdate]] = {
val consumerId = callContext.map(_.consumer.map(_.consumerId.get).getOrElse("")).getOrElse("")
UserAuthContextUpdateProvider.userAuthContextUpdateProvider.vend.createUserAuthContextUpdates(userId,consumerId, key, value) map {
(_, callContext)
}
}
override def getUserAuthContexts(userId: String,
callContext: Option[CallContext]): OBPReturnType[Box[List[UserAuthContext]]] =
@ -5017,6 +5147,55 @@ object LocalMappedConnector extends Connector with MdcLoggable {
} yield {
(transactionId, callContext)
}
case SIMPLE =>
for {
bodyToSimple <- NewStyle.function.tryons(s"$TransactionRequestDetailsExtractException It can not extract to $TransactionRequestBodyCounterpartyJSON", 400, callContext) {
body.to_simple.get
}
(toCounterparty, callContext) <- NewStyle.function.getCounterpartyByRoutings(
bodyToSimple.otherBankRoutingScheme,
bodyToSimple.otherBankRoutingAddress,
bodyToSimple.otherBranchRoutingScheme,
bodyToSimple.otherBranchRoutingAddress,
bodyToSimple.otherAccountRoutingScheme,
bodyToSimple.otherAccountRoutingAddress,
bodyToSimple.otherAccountSecondaryRoutingScheme,
bodyToSimple.otherAccountSecondaryRoutingAddress,
callContext
)
toAccount <- NewStyle.function.getBankAccountFromCounterparty(toCounterparty, true, callContext)
counterpartyBody = TransactionRequestBodySimpleJsonV400(
to = PostSimpleCounterpartyJson400(
name = toCounterparty.name,
description = toCounterparty.description,
other_bank_routing_scheme = toCounterparty.otherBankRoutingScheme,
other_bank_routing_address = toCounterparty.otherBankRoutingAddress,
other_account_routing_scheme = toCounterparty.otherAccountRoutingScheme,
other_account_routing_address = toCounterparty.otherAccountRoutingAddress,
other_account_secondary_routing_scheme = toCounterparty.otherAccountSecondaryRoutingScheme,
other_account_secondary_routing_address = toCounterparty.otherAccountSecondaryRoutingAddress,
other_branch_routing_scheme = toCounterparty.otherBranchRoutingScheme,
other_branch_routing_address = toCounterparty.otherBranchRoutingAddress,
),
value = AmountOfMoneyJsonV121(body.value.currency, body.value.amount),
description = body.description,
charge_policy = transactionRequest.charge_policy,
future_date = transactionRequest.future_date
)
(transactionId, callContext) <- NewStyle.function.makePaymentv210(
fromAccount,
toAccount,
transactionRequest.id,
transactionRequestCommonBody = counterpartyBody,
BigDecimal(counterpartyBody.value.amount),
counterpartyBody.description,
TransactionRequestType(transactionRequestType),
transactionRequest.charge_policy,
callContext
)
} yield {
(transactionId, callContext)
}
// In the case of a REFUND (currently working only implemented for SEPA refund request)
case REFUND =>
for {
@ -5349,4 +5528,42 @@ object LocalMappedConnector extends Connector with MdcLoggable {
override def checkAnswer(authContextUpdateId: String, challenge: String, callContext: Option[CallContext]) =
UserAuthContextUpdateProvider.userAuthContextUpdateProvider.vend.checkAnswer(authContextUpdateId, challenge) map { ( _, callContext) }
override def sendCustomerNotification(
scaMethod: StrongCustomerAuthentication,
recipient: String,
subject: Option[String], //Only for EMAIL, SMS do not need it, so here it is Option
message: String,
callContext: Option[CallContext]
): OBPReturnType[Box[String]] = {
if (scaMethod == StrongCustomerAuthentication.EMAIL){ // Send the email
val params = PlainMailBodyType(message) :: List(To(recipient))
Mailer.sendMail(From("challenge@tesobe.com"), Subject("OBP Consent Challenge"), params :_*)
Future{(Full("Success"), callContext)}
} else if (scaMethod == StrongCustomerAuthentication.SMS){ // Send the SMS
for {
phoneNumber <- Future.successful(recipient)
failMsg =s"$MissingPropsValueAtThisInstance sca_phone_api_key"
smsProviderApiKey <- NewStyle.function.tryons(failMsg, 400, callContext) {
APIUtil.getPropsValue("sca_phone_api_key").openOrThrowException(s"")
}
failMsg = s"$MissingPropsValueAtThisInstance sca_phone_api_secret"
smsProviderApiSecret <- NewStyle.function.tryons(failMsg, 400, callContext) {
APIUtil.getPropsValue("sca_phone_api_secret").openOrThrowException(s"")
}
client = new NexmoClient.Builder()
.apiKey(smsProviderApiKey)
.apiSecret(smsProviderApiSecret)
.build();
messageSent = new TextMessage("OBP-API", phoneNumber, message);
response <- Future{client.getSmsClient().submitMessage(messageSent)}
failMsg = s"$SmsServerNotResponding: $phoneNumber. Or Please to use EMAIL first."
_ <- Helper.booleanToFuture(failMsg, cc=callContext) {
response.getMessages.get(0).getStatus == com.nexmo.client.sms.MessageStatus.OK
}
}yield Future{(Full("Success"), callContext)}
} else
Future{(Full("Success"), callContext)}
}
}

View File

@ -4480,7 +4480,9 @@ object AkkaConnector_vDec2018 extends Connector with AkkaConnectorActorInit {
userId=userIdExample.value,
key=keyExample.value,
value=valueExample.value,
timeStamp=toDate(timeStampExample)))
timeStamp=toDate(timeStampExample),
consumerId=consumerIdExample.value
))
),
adapterImplementation = Some(AdapterImplementation("- Core", 1))
)
@ -4513,7 +4515,9 @@ object AkkaConnector_vDec2018 extends Connector with AkkaConnectorActorInit {
key=keyExample.value,
value=valueExample.value,
challenge=challengeExample.value,
status=statusExample.value))
status=statusExample.value,
consumerId=consumerIdExample.value
))
),
adapterImplementation = Some(AdapterImplementation("- Core", 1))
)
@ -4595,7 +4599,8 @@ object AkkaConnector_vDec2018 extends Connector with AkkaConnectorActorInit {
userId=userIdExample.value,
key=keyExample.value,
value=valueExample.value,
timeStamp=toDate(timeStampExample))))
timeStamp=toDate(timeStampExample),
consumerId=consumerIdExample.value)))
),
adapterImplementation = Some(AdapterImplementation("- Core", 1))
)

View File

@ -26,7 +26,6 @@ Berlin 13359, Germany
import java.net.{ConnectException, URLEncoder, UnknownHostException}
import java.util.Date
import java.util.UUID.randomUUID
import _root_.akka.stream.StreamTcpException
import akka.http.scaladsl.model.headers.RawHeader
import akka.http.scaladsl.model.{HttpProtocol, _}
@ -34,12 +33,12 @@ import akka.util.ByteString
import code.api.APIFailureNewStyle
import code.api.ResourceDocs1_4_0.MessageDocsSwaggerDefinitions
import code.api.cache.Caching
import code.api.dynamic.endpoint.helper.MockResponseHolder
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.context.UserAuthContextProvider
import code.customer.internalMapping.MappedCustomerIdMappingProvider
@ -4669,7 +4668,8 @@ trait RestConnector_vMar2019 extends Connector with KafkaHelper with MdcLoggable
userId=userIdExample.value,
key=keyExample.value,
value=valueExample.value,
timeStamp=toDate(timeStampExample)))
timeStamp=toDate(timeStampExample),
consumerId=consumerIdExample.value))
),
adapterImplementation = Some(AdapterImplementation("- Core", 1))
)
@ -4702,7 +4702,8 @@ trait RestConnector_vMar2019 extends Connector with KafkaHelper with MdcLoggable
key=keyExample.value,
value=valueExample.value,
challenge=challengeExample.value,
status=statusExample.value))
status=statusExample.value,
consumerId=consumerIdExample.value))
),
adapterImplementation = Some(AdapterImplementation("- Core", 1))
)
@ -4784,7 +4785,8 @@ trait RestConnector_vMar2019 extends Connector with KafkaHelper with MdcLoggable
userId=userIdExample.value,
key=keyExample.value,
value=valueExample.value,
timeStamp=toDate(timeStampExample))))
timeStamp=toDate(timeStampExample),
consumerId=consumerIdExample.value)))
),
adapterImplementation = Some(AdapterImplementation("- Core", 1))
)

View File

@ -4647,7 +4647,8 @@ trait StoredProcedureConnector_vDec2019 extends Connector with MdcLoggable {
userId=userIdExample.value,
key=keyExample.value,
value=valueExample.value,
timeStamp=toDate(timeStampExample)))
timeStamp=toDate(timeStampExample),
consumerId=consumerIdExample.value))
),
adapterImplementation = Some(AdapterImplementation("- Core", 1))
)
@ -4680,8 +4681,8 @@ trait StoredProcedureConnector_vDec2019 extends Connector with MdcLoggable {
key=keyExample.value,
value=valueExample.value,
challenge=challengeExample.value,
status=statusExample.value))
),
status=statusExample.value,
consumerId=consumerIdExample.value))),
adapterImplementation = Some(AdapterImplementation("- Core", 1))
)
@ -4762,7 +4763,8 @@ trait StoredProcedureConnector_vDec2019 extends Connector with MdcLoggable {
userId=userIdExample.value,
key=keyExample.value,
value=valueExample.value,
timeStamp=toDate(timeStampExample))))
timeStamp=toDate(timeStampExample),
consumerId=consumerIdExample.value)))
),
adapterImplementation = Some(AdapterImplementation("- Core", 1))
)

View File

@ -220,29 +220,14 @@ trait BranchesProvider {
// If we get branches filter them
val branches: Option[List[BranchT]] = getBranchesFromProvider(bankId : BankId ,queryParams)
branches match {
case Some(branches) => {
logger.debug(s"getBranches says there are ${branches.length} branches with or without license")
val branchesWithLicense = for {
branch <- branches if branch.meta.license.name.size > 3
} yield branch
logger.debug(s"getBranches says there are ${branches.length} branchesWithLicense")
Option(branchesWithLicense)
}
case None => {
logger.debug(s"getBranches says there are None branches")
None
}
}
branches
}
/*
Return one Branch
*/
final def getBranch(bankId: BankId, branchId : BranchId) : Option[BranchT] = {
// Filter out if no license data
getBranchFromProvider(bankId,branchId).filter(x => x.meta.license.id != "" && x.meta.license.name != "")
getBranchFromProvider(bankId,branchId)
}
protected def getBranchFromProvider(bankId: BankId, branchId : BranchId) : Option[BranchT]

View File

@ -3,6 +3,7 @@ package code.consumer
import code.api.util.APIUtil
import code.model.{AppType, Consumer, MappedConsumersProvider}
import code.remotedata.RemotedataConsumers
import com.openbankproject.commons.model.{BankIdAccountId, User, View}
import net.liftweb.common.Box
import net.liftweb.util.SimpleInjector
@ -50,6 +51,7 @@ trait ConsumersProvider {
redirectURL: Option[String],
createdByUserId: Option[String]): Box[Consumer]
def populateMissingUUIDs(): Boolean
}

View File

@ -17,9 +17,10 @@ class MappedConsentAuthContext extends ConsentAuthContext with LongKeyedMapper[M
override def key = Key.get
override def value = Value.get
override def consentAuthContextId = ConsentAuthContextId.get
override def timeStamp = createdAt.get
}
object MappedConsentAuthContext extends MappedConsentAuthContext with LongKeyedMetaMapper[MappedConsentAuthContext] {
override def dbTableName = "ConsentAuthContext" // define a custom DB table name
override def dbIndexes = UniqueIndex(ConsentId, Key) :: super.dbIndexes
override def dbIndexes = UniqueIndex(ConsentId, Key, createdAt) :: super.dbIndexes
}

View File

@ -12,13 +12,15 @@ class MappedUserAuthContext extends UserAuthContext with LongKeyedMapper[MappedU
object mUserId extends UUIDString(this)
object mKey extends MappedString(this, 255)
object mValue extends MappedString(this, 255)
object mConsumerId extends MappedString(this, 255)
override def userId = mUserId.get
override def key = mKey.get
override def value = mValue.get
override def userAuthContextId = mUserAuthContextId.get
override def timeStamp = createdAt.get
override def consumerId = mConsumerId.get
}
object MappedUserAuthContext extends MappedUserAuthContext with LongKeyedMetaMapper[MappedUserAuthContext] {

View File

@ -1,6 +1,7 @@
package code.context
import code.api.util.ErrorMessages
import code.api.util.ErrorMessages.CreateUserAuthContextError
import code.util.Helper.MdcLoggable
import net.liftweb.common.{Box, Empty, Full}
import net.liftweb.mapper.By
@ -13,13 +14,17 @@ import scala.concurrent.Future
object MappedUserAuthContextProvider extends UserAuthContextProvider with MdcLoggable {
override def createUserAuthContext(userId: String, key: String, value: String): Future[Box[MappedUserAuthContext]] =
override def createUserAuthContext(userId: String, key: String, value: String, consumerId: String): Future[Box[MappedUserAuthContext]] =
Future {
createUserAuthContextAkka(userId, key, value)
createUserAuthContextAkka(userId, key, value, consumerId)
}
def createUserAuthContextAkka(userId: String, key: String, value: String): Box[MappedUserAuthContext] =
def createUserAuthContextAkka(userId: String, key: String, value: String, consumerId: String): Box[MappedUserAuthContext] =
tryo {
MappedUserAuthContext.create.mUserId(userId).mKey(key).mValue(value).saveMe()
if(consumerId.isEmpty || consumerId == null){
throw new RuntimeException(s"$CreateUserAuthContextError current consumerId is empty here.")
}else{
MappedUserAuthContext.create.mUserId(userId).mKey(key).mValue(value).mConsumerId(consumerId).saveMe()
}
}
override def getUserAuthContexts(userId: String): Future[Box[List[MappedUserAuthContext]]] = Future {

View File

@ -13,6 +13,7 @@ class MappedUserAuthContextUpdate extends UserAuthContextUpdate with LongKeyedMa
object mUserAuthContextUpdateId extends MappedUUID(this)
object mUserId extends UUIDString(this)
object mConsumerId extends MappedString(this, 255)
object mKey extends MappedString(this, 50)
object mValue extends MappedString(this, 50)
object mChallenge extends MappedString(this, 10) {
@ -21,6 +22,7 @@ class MappedUserAuthContextUpdate extends UserAuthContextUpdate with LongKeyedMa
object mStatus extends MappedString(this, 20)
override def userId = mUserId.get
override def consumerId: String = mConsumerId.get
override def key = mKey.get
override def value = mValue.get
override def userAuthContextUpdateId = mUserAuthContextUpdateId.get

View File

@ -12,12 +12,13 @@ import scala.concurrent.Future
object MappedUserAuthContextUpdateProvider extends UserAuthContextUpdateProvider with MdcLoggable {
override def createUserAuthContextUpdates(userId: String, key: String, value: String): Future[Box[MappedUserAuthContextUpdate]] =
override def createUserAuthContextUpdates(userId: String, consumerId:String, key: String, value: String): Future[Box[MappedUserAuthContextUpdate]] =
Future {
tryo {
MappedUserAuthContextUpdate
.create
.mUserId(userId)
.mConsumerId(consumerId)
.mKey(key)
.mValue(value)
.mStatus(UserAuthContextUpdateStatus.INITIATED.toString)

View File

@ -22,7 +22,7 @@ object UserAuthContextProvider extends SimpleInjector {
}
trait UserAuthContextProvider {
def createUserAuthContext(userId: String, key: String, value: String): Future[Box[UserAuthContext]]
def createUserAuthContext(userId: String, key: String, value: String, consumerId: String): Future[Box[UserAuthContext]]
def getUserAuthContexts(userId: String): Future[Box[List[UserAuthContext]]]
def getUserAuthContextsBox(userId: String): Box[List[UserAuthContext]]
def createOrUpdateUserAuthContexts(userId: String, userAuthContexts: List[BasicUserAuthContext]): Box[List[UserAuthContext]]
@ -31,7 +31,7 @@ trait UserAuthContextProvider {
}
class RemotedataUserAuthContextCaseClasses {
case class createUserAuthContext(userId: String, key: String, value: String)
case class createUserAuthContext(userId: String, key: String, value: String, consumerId: String)
case class getUserAuthContexts(userId: String)
case class getUserAuthContextsBox(userId: String)
case class createOrUpdateUserAuthContexts(userId: String, userAuthContext: List[BasicUserAuthContext])

View File

@ -21,7 +21,7 @@ object UserAuthContextUpdateProvider extends SimpleInjector {
}
trait UserAuthContextUpdateProvider {
def createUserAuthContextUpdates(userId: String, key: String, value: String): Future[Box[UserAuthContextUpdate]]
def createUserAuthContextUpdates(userId: String, consumerId:String, key: String, value: String): Future[Box[UserAuthContextUpdate]]
def getUserAuthContextUpdates(userId: String): Future[Box[List[UserAuthContextUpdate]]]
def getUserAuthContextUpdatesBox(userId: String): Box[List[UserAuthContextUpdate]]
def deleteUserAuthContextUpdates(userId: String): Future[Box[Boolean]]
@ -30,7 +30,7 @@ trait UserAuthContextUpdateProvider {
}
class RemotedataUserAuthContextUpdateCaseClasses {
case class createUserAuthContextUpdate(userId: String, key: String, value: String)
case class createUserAuthContextUpdate(userId: String, consumerId:String, key: String, value: String)
case class getUserAuthContextUpdates(userId: String)
case class getUserAuthContextUpdatesBox(userId: String)
case class deleteUserAuthContextUpdates(userId: String)

View File

@ -2,8 +2,8 @@ package code.DynamicEndpoint
import java.util.UUID.randomUUID
import code.api.cache.Caching
import code.api.dynamic.endpoint.helper.DynamicEndpointHelper
import code.api.util.{APIUtil, CustomJsonFormats}
import code.api.v4_0_0.dynamic.DynamicEndpointHelper
import code.util.MappedUUID
import com.tesobe.CacheKeyFromArguments
import net.liftweb.common.Box

View File

@ -1,8 +1,8 @@
package code.entitlement
import code.api.dynamic.endpoint.helper.DynamicEntityInfo
import code.api.util.ApiRole.{CanCreateEntitlementAtAnyBank, CanCreateEntitlementAtOneBank}
import code.api.util.ErrorMessages
import code.api.v4_0_0.dynamic.DynamicEntityInfo
import code.util.{MappedUUID, UUIDString}
import net.liftweb.common.{Box, Failure, Full}
import net.liftweb.mapper._

View File

@ -59,7 +59,7 @@ trait ThingProvider {
case Some(things) => {
val certainThings = for {
thing <- things // if thing.meta.license.name.size > 3
thing <- things
} yield thing
Option(certainThings)
}

View File

@ -32,11 +32,29 @@ trait Counterparties {
def getMetadata(originalPartyBankId: BankId, originalPartyAccountId : AccountId, counterpartyMetadataId : String) : Box[CounterpartyMetadata]
def deleteMetadata(originalPartyBankId: BankId, originalPartyAccountId: AccountId, counterpartyMetadataId: String): Box[Boolean]
def getCounterparty(counterpartyId : String): Box[CounterpartyTrait]
def deleteCounterparty(counterpartyId : String): Box[Boolean]
def getCounterpartyByIban(iban : String): Box[CounterpartyTrait]
def getCounterpartyByIbanAndBankAccountId(iban: String, bankId: BankId, accountId: AccountId): Box[CounterpartyTrait]
def getCounterpartyByRoutings(
otherBankRoutingScheme: String,
otherBankRoutingAddress: String,
otherBranchRoutingScheme: String,
otherBranchRoutingAddress: String,
otherAccountRoutingScheme: String,
otherAccountRoutingAddress: String
): Box[CounterpartyTrait]
def getCounterpartyBySecondaryRouting(
otherAccountSecondaryRoutingScheme: String,
otherAccountSecondaryRoutingAddress: String
): Box[CounterpartyTrait]
def getCounterparties(thisBankId: BankId, thisAccountId: AccountId, viewId: ViewId): Box[List[CounterpartyTrait]]
@ -94,8 +112,12 @@ class RemotedataCounterpartiesCaseClasses {
case class getMetadatas(originalPartyBankId: BankId, originalPartyAccountId: AccountId)
case class getMetadata(originalPartyBankId: BankId, originalPartyAccountId: AccountId, counterpartyMetadataId: String)
case class deleteMetadata(originalPartyBankId: BankId, originalPartyAccountId: AccountId, counterpartyMetadataId: String)
case class getCounterparty(counterpartyId: String)
case class deleteCounterparty(counterpartyId: String)
case class getCounterpartyByIban(iban: String)
@ -103,6 +125,20 @@ class RemotedataCounterpartiesCaseClasses {
case class getCounterparties(thisBankId: BankId, thisAccountId: AccountId, viewId: ViewId)
case class getCounterpartyByRoutings(
otherBankRoutingScheme: String,
otherBankRoutingAddress: String,
otherBranchRoutingScheme: String,
otherBranchRoutingAddress: String,
otherAccountRoutingScheme: String,
otherAccountRoutingAddress: String
)
case class getCounterpartyBySecondaryRouting(
otherAccountSecondaryRoutingScheme: String,
otherAccountSecondaryRoutingAddress: String
)
case class createCounterparty(
createdByUserId: String,
thisBankId: String,

View File

@ -111,6 +111,14 @@ object MapperCounterparties extends Counterparties with MdcLoggable {
)
}
override def deleteMetadata(originalPartyBankId: BankId, originalPartyAccountId: AccountId, counterpartyMetadataId: String): Box[Boolean] = {
MappedCounterpartyMetadata.find(
By(MappedCounterpartyMetadata.thisBankId, originalPartyBankId.value),
By(MappedCounterpartyMetadata.thisAccountId, originalPartyAccountId.value),
By(MappedCounterpartyMetadata.counterpartyId, counterpartyMetadataId)
).map(_.delete_!)
}
def addMetadata(bankId: BankId, accountId : AccountId): Box[CounterpartyMetadata] = {
Full(
MappedCounterpartyMetadata.create
@ -124,6 +132,10 @@ object MapperCounterparties extends Counterparties with MdcLoggable {
override def getCounterparty(counterpartyId : String): Box[CounterpartyTrait] = {
MappedCounterparty.find(By(MappedCounterparty.mCounterPartyId, counterpartyId))
}
override def deleteCounterparty(counterpartyId : String): Box[Boolean] = {
MappedCounterparty.find(By(MappedCounterparty.mCounterPartyId, counterpartyId)).map(_.delete_!)
}
//TODO, here has a problem, MappedCounterparty has no unique constrain on IBan. But we get Counterparty By Iban. For now, we do not support update Counterpary endpoint. Here we only return the latest record.
override def getCounterpartyByIban(iban : String)= {
@ -141,6 +153,36 @@ object MapperCounterparties extends Counterparties with MdcLoggable {
)
}
override def getCounterpartyByRoutings(
otherBankRoutingScheme: String,
otherBankRoutingAddress: String,
otherBranchRoutingScheme: String,
otherBranchRoutingAddress: String,
otherAccountRoutingScheme: String,
otherAccountRoutingAddress: String
): Box[CounterpartyTrait] = {
MappedCounterparty.find(
By(MappedCounterparty.mOtherBankRoutingScheme,otherBankRoutingScheme),
By(MappedCounterparty.mOtherBankRoutingAddress,otherBankRoutingAddress),
By(MappedCounterparty.mOtherBranchRoutingScheme,otherBranchRoutingScheme),
By(MappedCounterparty.mOtherBranchRoutingAddress,otherBranchRoutingAddress),
By(MappedCounterparty.mOtherAccountRoutingScheme,otherAccountRoutingScheme),
By(MappedCounterparty.mOtherAccountRoutingAddress,otherAccountRoutingAddress),
)
}
override def getCounterpartyBySecondaryRouting(
otherAccountSecondaryRoutingScheme: String,
otherAccountSecondaryRoutingAddress: String
): Box[CounterpartyTrait] ={
MappedCounterparty.find(
By(MappedCounterparty.mOtherAccountSecondaryRoutingScheme, otherAccountSecondaryRoutingScheme),
By(MappedCounterparty.mOtherAccountSecondaryRoutingAddress, otherAccountSecondaryRoutingAddress),
)
}
override def getCounterparties(thisBankId: BankId, thisAccountId: AccountId, viewId: ViewId): Box[List[CounterpartyTrait]] = {
Full(MappedCounterparty.findAll(By(MappedCounterparty.mThisAccountId, thisAccountId.value),
By(MappedCounterparty.mThisBankId, thisBankId.value),
@ -439,17 +481,21 @@ class MappedCounterparty extends CounterpartyTrait with LongKeyedMapper[MappedCo
object mThisAccountId extends AccountIdString(this)
object mThisViewId extends MappedString(this, 36)
object mCounterPartyId extends UUIDString(this)
object mOtherAccountRoutingScheme extends MappedString(this, 255)
object mOtherAccountRoutingAddress extends MappedString(this, 255)
object mOtherBankRoutingScheme extends MappedString(this, 255)
object mOtherBankRoutingAddress extends MappedString(this, 255)
object mOtherBranchRoutingScheme extends MappedString(this, 255)
object mOtherBranchRoutingAddress extends MappedString(this, 255)
object mOtherAccountRoutingScheme extends MappedString(this, 255)
object mOtherAccountRoutingAddress extends MappedString(this, 255)
object mOtherAccountSecondaryRoutingScheme extends MappedString(this, 255)
object mOtherAccountSecondaryRoutingAddress extends MappedString(this, 255)
object mIsBeneficiary extends MappedBoolean(this)
object mDescription extends MappedString(this, 36)
object mCurrency extends MappedString(this, 255)
object mOtherAccountSecondaryRoutingScheme extends MappedString(this, 255)
object mOtherAccountSecondaryRoutingAddress extends MappedString(this, 255)
object mBespoke extends MappedOneToMany(MappedCounterpartyBespoke, MappedCounterpartyBespoke.mCounterparty, OrderBy(MappedCounterpartyBespoke.id, Ascending))
override def createdByUserId = mCreatedByUserId.get
@ -458,17 +504,20 @@ class MappedCounterparty extends CounterpartyTrait with LongKeyedMapper[MappedCo
override def thisAccountId = mThisAccountId.get
override def thisViewId = mThisViewId.get
override def counterpartyId = mCounterPartyId.get
override def otherAccountRoutingScheme = mOtherAccountRoutingScheme.get
override def otherAccountRoutingAddress: String = mOtherAccountRoutingAddress.get
override def otherBankRoutingScheme: String = mOtherBankRoutingScheme.get
override def otherBankRoutingAddress: String = mOtherBankRoutingAddress.get
override def otherBranchRoutingScheme: String = mOtherBranchRoutingScheme.get
override def otherBranchRoutingAddress: String = mOtherBranchRoutingAddress.get
override def otherBankRoutingAddress: String = mOtherBankRoutingAddress.get
override def otherAccountRoutingScheme = mOtherAccountRoutingScheme.get
override def otherAccountRoutingAddress: String = mOtherAccountRoutingAddress.get
override def otherAccountSecondaryRoutingScheme: String = mOtherAccountSecondaryRoutingScheme.get
override def otherAccountSecondaryRoutingAddress: String = mOtherAccountSecondaryRoutingAddress.get
override def isBeneficiary: Boolean = mIsBeneficiary.get
override def description: String = mDescription.get
override def currency: String = mCurrency.toString
override def otherAccountSecondaryRoutingScheme: String = mOtherAccountSecondaryRoutingScheme.get
override def otherAccountSecondaryRoutingAddress: String = mOtherAccountSecondaryRoutingAddress.get
override def bespoke: List[CounterpartyBespoke] =
CounterpartyBespokes.counterpartyBespokers.vend
.getCounterpartyBespokesByCounterpartyId(this.id.get)
@ -478,5 +527,10 @@ class MappedCounterparty extends CounterpartyTrait with LongKeyedMapper[MappedCo
}
object MappedCounterparty extends MappedCounterparty with LongKeyedMetaMapper[MappedCounterparty] {
override def dbIndexes = UniqueIndex(mCounterPartyId) :: UniqueIndex(mName, mThisBankId, mThisAccountId, mThisViewId) :: super.dbIndexes
override def dbIndexes =
UniqueIndex(mCounterPartyId) ::
UniqueIndex(mName, mThisBankId, mThisAccountId, mThisViewId) ::
// UniqueIndex(mOtherBankRoutingScheme,mOtherBankRoutingAddress,mOtherBranchRoutingScheme,mOtherBranchRoutingAddress,mOtherAccountRoutingScheme,mOtherAccountRoutingAddress) ::
// UniqueIndex(mOtherAccountSecondaryRoutingScheme, mOtherAccountSecondaryRoutingAddress) ::
super.dbIndexes
}

View File

@ -31,6 +31,8 @@ object MongoCounterparties extends Counterparties with MdcLoggable {
} yield m
}
def deleteMetadata(originalPartyBankId: BankId, originalPartyAccountId: AccountId, counterpartyMetadataId: String): Box[Boolean] = Empty
def getOrCreateMetadata(bankId: BankId, accountId : AccountId, counterpartyId:String, counterpartyName:String) : Box[CounterpartyMetadata] = {
/**
@ -108,11 +110,27 @@ object MongoCounterparties extends Counterparties with MdcLoggable {
}
override def getCounterparty(counterpartyId : String): Box[CounterpartyTrait] = Empty
override def deleteCounterparty(counterpartyId : String): Box[Boolean] = Empty
override def getCounterpartyByIban(iban : String): Box[CounterpartyTrait] = Empty
override def getCounterpartyByIbanAndBankAccountId(iban : String, bankId: BankId, accountId: AccountId): Box[CounterpartyTrait] = Empty
override def getCounterpartyByRoutings(
otherBankRoutingScheme: String,
otherBankRoutingAddress: String,
otherBranchRoutingScheme: String,
otherBranchRoutingAddress: String,
otherAccountRoutingScheme: String,
otherAccountRoutingAddress: String
): Box[CounterpartyTrait] = Empty
override def getCounterpartyBySecondaryRouting(
otherAccountSecondaryRoutingScheme: String,
otherAccountSecondaryRoutingAddress: String
): Box[CounterpartyTrait] = Empty
override def createCounterparty(
createdByUserId: String,
thisBankId: String,

View File

@ -39,8 +39,10 @@ import code.users.Users
import code.util.Helper.MdcLoggable
import code.util.HydraUtil
import code.util.HydraUtil._
import code.views.system.{AccountAccess, ViewDefinition}
import com.github.dwickern.macros.NameOf
import com.openbankproject.commons.ExecutionContext.Implicits.global
import com.openbankproject.commons.model.{BankIdAccountId, User, View}
import net.liftweb.common._
import net.liftweb.http.S
import net.liftweb.mapper.{LongKeyedMetaMapper, _}

View File

@ -57,16 +57,39 @@ case class UserExtended(val user: User) extends MdcLoggable {
* But it need the view object in the parameters.
* @param view the view object, need check the existence before calling the method
* @param bankIdAccountId for the system view there is not ids in the view, so we need get it from parameters.
* @param consumerId the consumerId, we will check if any accountAccess contains this consumerId or not.
* @return if has the input view access, return true, otherwise false.
*/
final def hasAccountAccess(view: View, bankIdAccountId: BankIdAccountId): Boolean ={
*/
final def hasAccountAccess(view: View, bankIdAccountId: BankIdAccountId, consumerId:Option[String] = None): Boolean ={
val viewDefinition = view.asInstanceOf[ViewDefinition]
!(AccountAccess.count(
By(AccountAccess.user_fk, this.userPrimaryKey.value),
By(AccountAccess.view_fk, viewDefinition.id),
By(AccountAccess.bank_id, bankIdAccountId.bankId.value),
By(AccountAccess.account_id, bankIdAccountId.accountId.value),
) == 0)
val consumerAccountAccess = {
//If we find the AccountAccess by consumerId, this mean the accountAccess already assigned to some consumers
val explictConsumerHasAccountAccess = if(consumerId.isDefined){
AccountAccess.find(
By(AccountAccess.bank_id, bankIdAccountId.bankId.value),
By(AccountAccess.account_id, bankIdAccountId.accountId.value),
By(AccountAccess.view_fk, viewDefinition.id),
By(AccountAccess.user_fk, this.userPrimaryKey.value),
By(AccountAccess.consumer_id, consumerId.get)).isDefined
} else {
false
}
if(explictConsumerHasAccountAccess) {
true
}else{
//If we can not find accountAccess by consumerId, then we will find AccountAccess by default "ALL_CONSUMERS" , this mean the accountAccess can be used for all consumers
AccountAccess.find(
By(AccountAccess.bank_id, bankIdAccountId.bankId.value),
By(AccountAccess.account_id, bankIdAccountId.accountId.value),
By(AccountAccess.view_fk, viewDefinition.id),
By(AccountAccess.user_fk, this.userPrimaryKey.value),
By(AccountAccess.consumer_id, ALL_CONSUMERS)
).isDefined
}
}
consumerAccountAccess
}
final def checkOwnerViewAccessAndReturnOwnerView(bankIdAccountId: BankIdAccountId) = {

View File

@ -29,10 +29,10 @@ package code.model.dataAccess
import code.api.util.CommonFunctions.validUri
import code.UserRefreshes.UserRefreshes
import code.accountholders.AccountHolders
import code.api.dynamic.endpoint.helper.DynamicEndpointHelper
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
import code.api.{APIFailure, Constant, DirectLogin, GatewayLogin, OAuthHandshake}
import code.bankconnectors.Connector
import code.context.UserAuthContextProvider
@ -615,7 +615,14 @@ import net.liftweb.util.Helpers._
grantDefaultEntitlementsToAuthUser(user)
logUserIn(user, () => {
S.notice(S.?("account.validated"))
S.redirectTo(homePage)
APIUtil.getPropsValue("user_account_validated_redirect_url") match {
case Full(redirectUrl) =>
logger.debug(s"user_account_validated_redirect_url = $redirectUrl")
S.redirectTo(redirectUrl)
case _ =>
logger.debug(s"user_account_validated_redirect_url is NOT defined")
S.redirectTo(homePage)
}
})
case _ => S.error(S.?("invalid.validation.link")); S.redirectTo(homePage)
@ -1263,7 +1270,7 @@ def restoreSomeSessions(): Unit = {
(accountsHeld, _) <- Connector.connector.vend.getBankAccountsForUser(user.name,callContext) map {
connectorEmptyResponse(_, callContext)
}
_ = logger.debug(s"-->AuthUser.refreshUserAccountAccess.accounts : ${accountsHeld}")
_ = logger.debug(s"--> for user($user): AuthUser.refreshUserAccountAccess.accounts : ${accountsHeld}")
}yield {
refreshViewsAccountAccessAndHolders(user, accountsHeld)
}

View File

@ -156,6 +156,8 @@ class ViewImpl extends View with LongKeyedMapper[ViewImpl] with ManyToMany with
canAddPublicAlias_(actions.exists(_ == "can_add_public_alias"))
canAddPrivateAlias_(actions.exists(_ == "can_add_private_alias"))
canAddCounterparty_(actions.exists(_ == "can_add_counterparty"))
canGetCounterparty_(actions.exists(_ == "can_get_counterparty"))
canDeleteCounterparty_(actions.exists(_ == "can_delete_counterparty"))
canDeleteCorporateLocation_(actions.exists(_ == "can_delete_corporate_location"))
canDeletePhysicalLocation_(actions.exists(_ == "can_delete_physical_location"))
canEditOwnerComment_(actions.exists(_ == "can_edit_narrative"))
@ -370,7 +372,13 @@ class ViewImpl extends View with LongKeyedMapper[ViewImpl] with ManyToMany with
override def defaultValue = false
}
object canAddCounterparty_ extends MappedBoolean(this){
override def defaultValue = true
override def defaultValue = false
}
object canGetCounterparty_ extends MappedBoolean(this){
override def defaultValue = false
}
object canDeleteCounterparty_ extends MappedBoolean(this){
override def defaultValue = false
}
object canDeleteCorporateLocation_ extends MappedBoolean(this){
override def defaultValue = false
@ -505,6 +513,8 @@ class ViewImpl extends View with LongKeyedMapper[ViewImpl] with ManyToMany with
def canAddPublicAlias : Boolean = canAddPublicAlias_.get
def canAddPrivateAlias : Boolean = canAddPrivateAlias_.get
def canAddCounterparty : Boolean = canAddCounterparty_.get
def canGetCounterparty : Boolean = canGetCounterparty_.get
def canDeleteCounterparty : Boolean = canDeleteCounterparty_.get
def canDeleteCorporateLocation : Boolean = canDeleteCorporateLocation_.get
def canDeletePhysicalLocation : Boolean = canDeletePhysicalLocation_.get

View File

@ -38,25 +38,14 @@ trait ProductsProvider {
*/
final def getProducts(bankId : BankId, adminView: Boolean = false) : Option[List[Product]] = {
logger.info(s"Hello from getProducts bankId is: $bankId")
getProductsFromProvider(bankId) match {
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))
} yield product
Option(productsWithLicense)
}
case None => None
}
getProductsFromProvider(bankId)
}
/*
Return one Product at a bank
*/
final def getProduct(bankId : BankId, productCode : ProductCode, adminView: Boolean = false) : Option[Product] = {
// Filter out if no license data
getProductFromProvider(bankId, productCode).filter(x => (adminView || (x.meta.license.id != "" && x.meta.license.name != "")))
getProductFromProvider(bankId, productCode)
}
protected def getProductFromProvider(bankId : BankId, productCode : ProductCode) : Option[Product]

View File

@ -28,10 +28,18 @@ object RemotedataCounterparties extends ObpActorInit with Counterparties {
(actor ? cc.getMetadata(originalPartyBankId: BankId, originalPartyAccountId: AccountId, counterpartyMetadataId: String)).mapTo[Box[CounterpartyMetadata]]
)
override def deleteMetadata(originalPartyBankId: BankId, originalPartyAccountId: AccountId, counterpartyMetadataId: String): Box[Boolean] = getValueFromFuture(
(actor ? cc.deleteMetadata(originalPartyBankId: BankId, originalPartyAccountId: AccountId, counterpartyMetadataId: String)).mapTo[Box[Boolean]]
)
override def getCounterparty(counterpartyId: String): Box[CounterpartyTrait] = getValueFromFuture(
(actor ? cc.getCounterparty(counterpartyId: String)).mapTo[Box[CounterpartyTrait]]
)
override def deleteCounterparty(counterpartyId : String): Box[Boolean]= getValueFromFuture(
(actor ? cc.deleteCounterparty(counterpartyId: String)).mapTo[Box[Boolean]]
)
override def getCounterpartyByIban(iban: String): Box[CounterpartyTrait] = getValueFromFuture(
(actor ? cc.getCounterpartyByIban(iban: String)).mapTo[Box[CounterpartyTrait]]
)
@ -43,6 +51,34 @@ object RemotedataCounterparties extends ObpActorInit with Counterparties {
override def getCounterparties(thisBankId: BankId, thisAccountId: AccountId, viewId: ViewId): Box[List[CounterpartyTrait]] = getValueFromFuture(
(actor ? cc.getCounterparties(thisBankId, thisAccountId, viewId)).mapTo[Box[List[CounterpartyTrait]]]
)
override def getCounterpartyByRoutings(
otherBankRoutingScheme: String,
otherBankRoutingAddress: String,
otherBranchRoutingScheme: String,
otherBranchRoutingAddress: String,
otherAccountRoutingScheme: String,
otherAccountRoutingAddress: String
): Box[CounterpartyTrait] = getValueFromFuture(
(actor ? cc.getCounterpartyByRoutings(
otherBankRoutingScheme: String,
otherBankRoutingAddress: String,
otherBranchRoutingScheme: String,
otherBranchRoutingAddress: String,
otherAccountRoutingScheme: String,
otherAccountRoutingAddress: String
)).mapTo[Box[CounterpartyTrait]]
)
override def getCounterpartyBySecondaryRouting(
otherAccountSecondaryRoutingScheme: String,
otherAccountSecondaryRoutingAddress: String
): Box[CounterpartyTrait] = getValueFromFuture(
(actor ? cc.getCounterpartyBySecondaryRouting(
otherAccountSecondaryRoutingScheme: String,
otherAccountSecondaryRoutingAddress: String
)).mapTo[Box[CounterpartyTrait]]
)
override def createCounterparty(
createdByUserId: String,

View File

@ -77,9 +77,17 @@ class RemotedataCounterpartiesActor extends Actor with ObpActorHelper with MdcLo
logger.debug(s"getMetadata($originalPartyBankId, $originalPartyAccountId)")
sender ! (mapper.getMetadata(originalPartyBankId: BankId, originalPartyAccountId: AccountId, counterpartyMetadataId: String))
case cc.deleteMetadata(originalPartyBankId: BankId, originalPartyAccountId: AccountId, counterpartyMetadataId: String) =>
logger.debug(s"deleteMetadata($originalPartyBankId, $originalPartyAccountId, $counterpartyMetadataId)")
sender ! (mapper.deleteMetadata(originalPartyBankId: BankId, originalPartyAccountId: AccountId, counterpartyMetadataId: String))
case cc.getCounterparty(counterpartyId: String) =>
logger.debug(s"getCounterparty($counterpartyId)")
sender ! (mapper.getCounterparty(counterpartyId: String))
case cc.deleteCounterparty(counterpartyId: String) =>
logger.debug(s"deleteCounterparty($counterpartyId)")
sender ! (mapper.deleteCounterparty(counterpartyId: String))
case cc.getCounterparties(thisBankId: BankId, thisAccountId: AccountId, viewId: ViewId) =>
logger.debug(s"getCounterparties($thisBankId)")

View File

@ -24,8 +24,8 @@ object RemotedataUserAuthContext extends ObpActorInit with UserAuthContextProvid
(actor ? cc.createOrUpdateUserAuthContexts(userId, userAuthContexts)).mapTo[Box[List[UserAuthContext]]]
)
def createUserAuthContext(userId: String, key: String, value: String): Future[Box[UserAuthContext]] =
(actor ? cc.createUserAuthContext(userId, key, value)).mapTo[Box[UserAuthContext]]
def createUserAuthContext(userId: String, key: String, value: String, consumerId: String): Future[Box[UserAuthContext]] =
(actor ? cc.createUserAuthContext(userId, key, value, consumerId)).mapTo[Box[UserAuthContext]]
override def deleteUserAuthContexts(userId: String): Future[Box[Boolean]] =
(actor ? cc.deleteUserAuthContexts(userId)).mapTo[Box[Boolean]]

View File

@ -21,8 +21,8 @@ object RemotedataUserAuthContextUpdate extends ObpActorInit with UserAuthContext
(actor ? cc.getUserAuthContextUpdatesBox(userId)).mapTo[Box[List[UserAuthContextUpdate]]]
)
def createUserAuthContextUpdates(userId: String, key: String, value: String): Future[Box[UserAuthContextUpdate]] =
(actor ? cc.createUserAuthContextUpdate(userId, key, value)).mapTo[Box[UserAuthContextUpdate]]
def createUserAuthContextUpdates(userId: String, consumerId:String, key: String, value: String): Future[Box[UserAuthContextUpdate]] =
(actor ? cc.createUserAuthContextUpdate(userId: String, consumerId:String, key, value)).mapTo[Box[UserAuthContextUpdate]]
override def deleteUserAuthContextUpdates(userId: String): Future[Box[Boolean]] =
(actor ? cc.deleteUserAuthContextUpdates(userId)).mapTo[Box[Boolean]]

View File

@ -15,9 +15,9 @@ class RemotedataUserAuthContextActor extends Actor with ObpActorHelper with MdcL
def receive = {
case cc.createUserAuthContext(userId: String, key: String, value: String) =>
logger.debug(s"createUserAuthContext($userId, $key, $value)")
sender ! (mapper.createUserAuthContextAkka(userId, key, value))
case cc.createUserAuthContext(userId: String, key: String, value: String, consumerId: String) =>
logger.debug(s"createUserAuthContext($userId, $key, $value, $consumerId)")
sender ! (mapper.createUserAuthContextAkka(userId, key, value, consumerId))
case cc.getUserAuthContexts(userId: String) =>
logger.debug(s"getUserAuthContexts($userId)")

View File

@ -15,9 +15,9 @@ class RemotedataUserAuthContextUpdateActor extends Actor with ObpActorHelper wit
def receive = {
case cc.createUserAuthContextUpdate(userId: String, key: String, value: String) =>
case cc.createUserAuthContextUpdate(userId: String, consumerId:String, key: String, value: String) =>
logger.debug("createUserAuthContext(" + userId + ", " + key + ", " + value + ")")
mapper.createUserAuthContextUpdates(userId, key, value) pipeTo sender
mapper.createUserAuthContextUpdates(userId,consumerId, key, value) pipeTo sender
case cc.getUserAuthContextUpdates(userId: String) =>
logger.debug("getUserAuthContexts(" + userId + ")")

View File

@ -174,4 +174,12 @@ object RemotedataViews extends ObpActorInit with Views {
)
def revokeAccessToSystemViewForConsumer(bankId: BankId, accountId: AccountId, view : View, consumerId : String) : Box[Boolean] = getValueFromFuture(
(actor ? cc.revokeAccessToSystemViewForConsumer(bankId: BankId, accountId: AccountId, view : View, consumerId : String)).mapTo[Box[Boolean]]
)
def revokeAccessToCustomViewForConsumer(view : View, consumerId : String) : Box[Boolean] = getValueFromFuture(
(actor ? cc.revokeAccessToCustomViewForConsumer(view : View, consumerId : String)).mapTo[Box[Boolean]]
)
}

View File

@ -249,7 +249,7 @@ trait OBPDataImport extends MdcLoggable {
// Check the data products is OK before calling calling createSaveableProducts
logger.debug("Get existing products that match the bank id and product code")
val existing = data.products.flatMap(p => Products.productsProvider.vend.getProduct(BankId(p.bank_id), ProductCode(p.code), true))
val existing = data.products.flatMap(p => Products.productsProvider.vend.getProduct(BankId(p.bank_id), ProductCode(p.code)))
val allNewCodes = data.products.map(_.code)
val emptyCodes = allNewCodes.filter(_.isEmpty)

View File

@ -132,6 +132,9 @@ class UserInvitation extends MdcLoggable {
// Set the status of the user invitation to "FINISHED"
UserInvitationProvider.userInvitationProvider.vend.updateStatusOfUserInvitation(userInvitation.map(_.userInvitationId).getOrElse(""), "FINISHED")
// Set a new password
// Please note that the query parameter is used to alter the message at password reset page i.e. at next code:
// <h1>{if(S.queryString.isDefined) Helper.i18n("set.your.password") else S.?("reset.your.password")}</h1>
// placed into function AuthZUser.passwordResetXhtml
val resetLink = AuthUser.passwordResetUrl(u.idGivenByProvider, u.emailAddress, u.userId) + "?action=set"
S.redirectTo(resetLink)
}

View File

@ -1,21 +1,23 @@
package code.transaction
import code.accountholders.AccountHolders
import code.actorsystem.ObpLookupSystem
import code.api.util.{APIUtil, ApiTrigger}
import code.bankconnectors.{Connector, LocalMappedConnector}
import code.bankconnectors.LocalMappedConnector.getBankAccountCommon
import code.model._
import code.usercustomerlinks.UserCustomerLink
import code.util.Helper.MdcLoggable
import code.util._
import code.webhook.WebhookActor
import code.webhook.WebhookActor.RelatedEntity
import com.openbankproject.commons.model._
import net.liftweb.common._
import net.liftweb.common.Box.tryo
import net.liftweb.mapper._
class MappedTransaction extends LongKeyedMapper[MappedTransaction] with IdPK with CreatedUpdated with TransactionUUID {
private val logger = Logger(classOf[MappedTransaction])
class MappedTransaction extends LongKeyedMapper[MappedTransaction] with IdPK with CreatedUpdated with TransactionUUID with MdcLoggable {
def getSingleton = MappedTransaction
@ -58,7 +60,7 @@ class MappedTransaction extends LongKeyedMapper[MappedTransaction] with IdPK wit
//The following are the fields from CounterpartyTrait, previous just save BankAccount to simulate the counterparty.
//Now we save the real Counterparty data
//CP--> CounterParty
//CP means CounterParty
object CPCounterPartyId extends UUIDString(this)
object CPOtherAccountProvider extends MappedString(this, 36)
object CPOtherAccountRoutingScheme extends MappedString(this, 255)
@ -242,23 +244,49 @@ object MappedTransaction extends MappedTransaction with LongKeyedMetaMapper[Mapp
Helper.smallestCurrencyUnitToBigDecimal(value, t.currency.get).toString() + " " + t.currency.get
}
def sendMessage(apiTrigger: ApiTrigger): Unit = {
actor ! WebhookActor.WebhookRequest(
apiTrigger,
APIUtil.generateUUID(),
t.theBankId.value,
t.theAccountId.value,
getAmount(t.amount.get),
getAmount(t.newAccountBalance.get)
)
if(apiTrigger.equals(ApiTrigger.onCreateTransaction)){
val userIdCustomerIdPairs: List[(String, String)] = for{
holder <- AccountHolders.accountHolders.vend.getAccountHolders(t.theBankId, t.theAccountId).toList
userCustomerLink <- UserCustomerLink.userCustomerLink.vend.getUserCustomerLinksByUserId(holder.userId)
} yield{
(holder.userId, userCustomerLink.customerId)
}
val userIdCustomerIdsPairs: Map[String, List[String]] = userIdCustomerIdPairs.groupBy(_._1).map( a => (a._1,a._2.map(_._2)))
val eventId = APIUtil.generateUUID()
logger.debug("Before firing WebhookActor.AccountNotificationWebhookRequest.eventId: " + eventId)
actor ! WebhookActor.AccountNotificationWebhookRequest(
apiTrigger,
eventId,
t.theBankId.value,
t.theAccountId.value,
t.theTransactionId.value,
userIdCustomerIdsPairs.map(pair => RelatedEntity(pair._1, pair._2)).toList
)
} else{
val eventId = APIUtil.generateUUID()
logger.debug("Before firing WebhookActor.WebhookRequest.eventId: " + eventId)
actor ! WebhookActor.WebhookRequest(
apiTrigger,
eventId,
t.theBankId.value,
t.theAccountId.value,
getAmount(t.amount.get),
getAmount(t.newAccountBalance.get)
)
}
}
t.amount.get match {
case amount if amount > 0 =>
sendMessage(ApiTrigger.onBalanceChange)
sendMessage(ApiTrigger.onCreditTransaction)
sendMessage(ApiTrigger.onCreateTransaction)
case amount if amount < 0 =>
sendMessage(ApiTrigger.onBalanceChange)
sendMessage(ApiTrigger.onDebitTransaction)
sendMessage(ApiTrigger.onCreateTransaction)
case _ =>
// Do not send anything
}

View File

@ -1,15 +1,19 @@
package code.transactionChallenge
import code.api.util.ErrorMessages
import code.api.util.APIUtil.transactionRequestChallengeTtl
import code.api.util.{APIUtil, ErrorMessages}
import com.openbankproject.commons.model.{ChallengeTrait, ErrorMessage}
import com.openbankproject.commons.model.enums.StrongCustomerAuthentication.SCA
import com.openbankproject.commons.model.enums.StrongCustomerAuthenticationStatus
import com.openbankproject.commons.model.enums.StrongCustomerAuthenticationStatus.SCAStatus
import net.liftweb.common.{Box, Failure, Full}
import net.liftweb.mapper.By
import net.liftweb.util.Helpers
import org.mindrot.jbcrypt.BCrypt
import net.liftweb.util.Helpers.tryo
import scala.compat.Platform
object MappedChallengeProvider extends ChallengeProvider {
override def saveChallenge(
@ -54,23 +58,41 @@ object MappedChallengeProvider extends ChallengeProvider {
): Box[ChallengeTrait] = {
val challenge = getChallenge(challengeId).openOrThrowException(s"${ErrorMessages.InvalidChallengeAnswer}")
val currentAttemptCounterValue = challenge.attemptCounter
//We update the counter anyway.
challenge.mAttemptCounter(currentAttemptCounterValue+1).saveMe()
val currentHashedAnswer = BCrypt.hashpw(challengeAnswer, challenge.salt).substring(0, 44)
val expectedHashedAnswer = challenge.expectedAnswer
userId match {
case None =>
if(currentHashedAnswer==expectedHashedAnswer) {
tryo{challenge.mSuccessful(true).mScaStatus(StrongCustomerAuthenticationStatus.finalised.toString).saveMe()}
} else {
Failure(s"${ErrorMessages.InvalidChallengeAnswer}")
}
case Some(id) =>
if(currentHashedAnswer==expectedHashedAnswer && id==challenge.expectedUserId) {
tryo{challenge.mSuccessful(true).mScaStatus(StrongCustomerAuthenticationStatus.finalised.toString).saveMe()}
} else {
Failure(s"${ErrorMessages.InvalidChallengeAnswer}")
val createDateTime = challenge.createdAt.get
val challengeTTL : Long = Helpers.seconds(APIUtil.transactionRequestChallengeTtl)
val expiredDateTime: Long = createDateTime.getTime+challengeTTL
val currentTime: Long = Platform.currentTime
//TODO, add column maxAttemptsAllowed (Int) to `mappedexpectedchallengeanswer` table instead of this hardcode number 3
if(currentAttemptCounterValue <3){
if(expiredDateTime > currentTime) {
val currentHashedAnswer = BCrypt.hashpw(challengeAnswer, challenge.salt).substring(0, 44)
val expectedHashedAnswer = challenge.expectedAnswer
userId match {
case None =>
if(currentHashedAnswer==expectedHashedAnswer) {
tryo{challenge.mSuccessful(true).mScaStatus(StrongCustomerAuthenticationStatus.finalised.toString).saveMe()}
} else {
Failure(s"${ErrorMessages.InvalidChallengeAnswer}")
}
case Some(id) =>
if(currentHashedAnswer==expectedHashedAnswer && id==challenge.expectedUserId) {
tryo{challenge.mSuccessful(true).mScaStatus(StrongCustomerAuthenticationStatus.finalised.toString).saveMe()}
} else {
Failure(s"${ErrorMessages.InvalidChallengeAnswer}")
}
}
}else{
Failure(s"${ErrorMessages.OneTimePasswordExpired} Current expiration time is $transactionRequestChallengeTtl seconds")
}
}else{
Failure(s"${ErrorMessages.AllowedAttemptsUsedUp}")
}
}
}

View File

@ -24,6 +24,9 @@ class MappedExpectedChallengeAnswer extends ChallengeTrait with LongKeyedMapper[
object mScaStatus extends MappedString(this,100)
object mConsentId extends MappedString(this,100)
object mAuthenticationMethodId extends MappedString(this,100)
object mAttemptCounter extends MappedInt(this){
override def defaultValue = 0
}
override def challengeId: String = mChallengeId.get
override def challengeType: String = mChallengeType.get
@ -36,6 +39,7 @@ class MappedExpectedChallengeAnswer extends ChallengeTrait with LongKeyedMapper[
override def scaMethod: Option[SCA] = Option(StrongCustomerAuthentication.withName(mScaMethod.get))
override def scaStatus: Option[SCAStatus] = Option(StrongCustomerAuthenticationStatus.withName(mScaStatus.get))
override def authenticationMethodId: Option[String] = Option(mAuthenticationMethodId.get)
override def attemptCounter: Int = mAttemptCounter.get
}
object MappedExpectedChallengeAnswer extends MappedExpectedChallengeAnswer with LongKeyedMetaMapper[MappedExpectedChallengeAnswer] {

View File

@ -285,7 +285,37 @@ class MappedTransactionRequest extends LongKeyedMapper[MappedTransactionRequest]
else
None
val t_to_transfer_to_phone = if (TransactionRequestTypes.withName(transactionType) == TransactionRequestTypes.TRANSFER_TO_PHONE && details.nonEmpty)
val t_to_simple = if (TransactionRequestTypes.withName(transactionType) == TransactionRequestTypes.SIMPLE && details.nonEmpty){
val transactionRequestSimples = for {
JObject(child) <- parsedDetails
JField("other_bank_routing_scheme", JString(otherBankRoutingScheme)) <- child
JField("other_bank_routing_address", JString(otherBankRoutingAddress)) <- child
JField("other_branch_routing_scheme", JString(otherBranchRoutingScheme)) <- child
JField("other_branch_routing_address", JString(otherBranchRoutingAddress)) <- child
JField("other_account_routing_scheme", JString(otherAccountRoutingScheme)) <- child
JField("other_account_routing_address", JString(otherAccountRoutingAddress)) <- child
JField("other_account_secondary_routing_scheme", JString(otherAccountSecondaryRoutingScheme)) <- child
JField("other_account_secondary_routing_address", JString(otherAccountSecondaryRoutingAddress)) <- child
} yield
TransactionRequestSimple (
otherBankRoutingScheme,
otherBankRoutingAddress,
otherBranchRoutingScheme,
otherBranchRoutingAddress,
otherAccountRoutingScheme,
otherAccountRoutingAddress,
otherAccountSecondaryRoutingScheme,
otherAccountSecondaryRoutingAddress
)
if(transactionRequestSimples.isEmpty)
Some(TransactionRequestSimple("","","","","","","",""))
else
Some(transactionRequestSimples.head)
}
else
None
val t_to_transfer_to_phone = if (TransactionRequestTypes.withName(transactionType) == TransactionRequestTypes.TRANSFER_TO_PHONE && details.nonEmpty)
Some(parsedDetails.extract[TransactionRequestTransferToPhone])
else
None
@ -309,6 +339,7 @@ class MappedTransactionRequest extends LongKeyedMapper[MappedTransactionRequest]
to_sandbox_tan = t_to_sandbox_tan,
to_sepa = t_to_sepa,
to_counterparty = t_to_counterparty,
to_simple = t_to_simple,
to_transfer_to_phone = t_to_transfer_to_phone,
to_transfer_to_atm = t_to_transfer_to_atm,
to_transfer_to_account = t_to_transfer_to_account,

View File

@ -22,7 +22,8 @@ object TransactionRequests extends SimpleInjector {
object TransactionRequestTypes extends Enumeration {
type TransactionRequestTypes = Value
val SANDBOX_TAN, ACCOUNT, ACCOUNT_OTP, COUNTERPARTY, SEPA, FREE_FORM, TRANSFER_TO_PHONE, TRANSFER_TO_ATM, TRANSFER_TO_ACCOUNT, TRANSFER_TO_REFERENCE_ACCOUNT,
val SANDBOX_TAN, ACCOUNT, ACCOUNT_OTP, COUNTERPARTY, SEPA, FREE_FORM, SIMPLE,
TRANSFER_TO_PHONE, TRANSFER_TO_ATM, TRANSFER_TO_ACCOUNT, TRANSFER_TO_REFERENCE_ACCOUNT,
//The following are BerlinGroup Standard
SEPA_CREDIT_TRANSFERS, INSTANT_SEPA_CREDIT_TRANSFERS, TARGET_2_PAYMENTS, CROSS_BORDER_CREDIT_TRANSFERS, REFUND = Value
}

View File

@ -230,6 +230,35 @@ object MapperViews extends Views with MdcLoggable {
}
res
}
//Custom View will have bankId and accountId inside the `View`, so no need both in the parameters
def revokeAccessToCustomViewForConsumer(view : View, consumerId : String) : Box[Boolean] = {
for {
viewDefinition <- ViewDefinition.findCustomView(view.bankId.value, view.accountId.value, view.viewId.value)
accountAccess <- AccountAccess.find(
By(AccountAccess.consumer_id, consumerId),
By(AccountAccess.view_fk, viewDefinition.id),
By(AccountAccess.bank_id, view.bankId.value),
By(AccountAccess.account_id, view.accountId.value)
) ?~! CannotFindAccountAccess
} yield {
accountAccess.delete_!
}
}
//System View only have the viewId in inside the `View`, both bankId and accountId are empty in the `View`. So we need both in the parameters
def revokeAccessToSystemViewForConsumer(bankId: BankId, accountId: AccountId, view : View, consumerId : String) : Box[Boolean] = {
for {
viewDefinition <- ViewDefinition.find(By(ViewDefinition.id_, view.id))
accountAccess <- AccountAccess.find(
By(AccountAccess.consumer_id, consumerId),
By(AccountAccess.bank_id, bankId.value),
By(AccountAccess.account_id, accountId.value),
By(AccountAccess.view_fk, viewDefinition.id)) ?~! CannotFindAccountAccess
} yield {
accountAccess.delete_!
}
}
//returns Full if deletable, Failure if not
def canRevokeAccessAsBox(viewImpl : ViewDefinition, user : User) : Box[Unit] = {
@ -536,6 +565,7 @@ object MapperViews extends Views with MdcLoggable {
val publicView = CUSTOM_PUBLIC_VIEW_ID.equals(viewId.toLowerCase)
val accountantsView = SYSTEM_ACCOUNTANT_VIEW_ID.equals(viewId.toLowerCase)
val auditorsView = SYSTEM_AUDITOR_VIEW_ID.equals(viewId.toLowerCase)
val smallPaymentVerifiedView = SYSTEM_SMALL_PAYMENT_VERIFIED_VIEW_ID.equals(viewId.toLowerCase)
val theView =
if (ownerView)
@ -546,6 +576,8 @@ object MapperViews extends Views with MdcLoggable {
getOrCreateSystemView(SYSTEM_ACCOUNTANT_VIEW_ID)
else if (auditorsView)
getOrCreateSystemView(SYSTEM_AUDITOR_VIEW_ID)
else if (smallPaymentVerifiedView)
getOrCreateSystemView(SYSTEM_SMALL_PAYMENT_VERIFIED_VIEW_ID)
else {
logger.error(ViewIdNotSupported+ s"Your input viewId is :$viewId")
Failure(ViewIdNotSupported+ s"Your input viewId is :$viewId")
@ -838,6 +870,8 @@ object MapperViews extends Views with MdcLoggable {
.canAddPublicAlias_(true)
.canAddPrivateAlias_(true)
.canAddCounterparty_(true)
.canGetCounterparty_(true)
.canDeleteCounterparty_(true)
.canDeleteCorporateLocation_(true)
.canDeletePhysicalLocation_(true)
.canEditOwnerComment_(true)
@ -925,6 +959,8 @@ object MapperViews extends Views with MdcLoggable {
.canAddPublicAlias_(true)
.canAddPrivateAlias_(true)
.canAddCounterparty_(true)
.canGetCounterparty_(true)
.canDeleteCounterparty_(true)
.canDeleteCorporateLocation_(true)
.canDeletePhysicalLocation_(true)
.canEditOwnerComment_(true)
@ -1013,6 +1049,8 @@ object MapperViews extends Views with MdcLoggable {
.canAddPublicAlias_(true)
.canAddPrivateAlias_(true)
.canAddCounterparty_(true)
.canGetCounterparty_(true)
.canDeleteCounterparty_(true)
.canDeleteCorporateLocation_(true)
.canDeletePhysicalLocation_(true)
.canEditOwnerComment_(true)
@ -1112,6 +1150,8 @@ object MapperViews extends Views with MdcLoggable {
canAddPublicAlias_(true).
canAddPrivateAlias_(true).
canAddCounterparty_(true).
canGetCounterparty_(true).
canDeleteCounterparty_(true).
canDeleteCorporateLocation_(true).
canDeletePhysicalLocation_(true).
canEditOwnerComment_(true).
@ -1208,6 +1248,8 @@ object MapperViews extends Views with MdcLoggable {
canAddPublicAlias_(true).
canAddPrivateAlias_(true).
canAddCounterparty_(true).
canGetCounterparty_(true).
canDeleteCounterparty_(true).
canDeleteCorporateLocation_(true).
canDeletePhysicalLocation_(true).
canEditOwnerComment_(true).
@ -1303,6 +1345,8 @@ Auditors
canAddPublicAlias_(true).
canAddPrivateAlias_(true).
canAddCounterparty_(true).
canGetCounterparty_(true).
canDeleteCounterparty_(true).
canDeleteCorporateLocation_(true).
canDeletePhysicalLocation_(true).
canEditOwnerComment_(true).

View File

@ -48,6 +48,9 @@ trait Views {
def revokeAllAccountAccess(bankId : BankId, accountId : AccountId, user : User) : Box[Boolean]
def revokeAccountAccessByUser(bankId : BankId, accountId : AccountId, user : User) : Box[Boolean]
def revokeAccessToSystemViewForConsumer(bankId: BankId, accountId: AccountId, view : View, consumerId : String) : Box[Boolean]
def revokeAccessToCustomViewForConsumer(view : View, consumerId : String) : Box[Boolean]
def customView(viewId : ViewId, bankAccountId: BankIdAccountId) : Box[View]
def systemView(viewId : ViewId) : Box[View]
def customViewFuture(viewId : ViewId, bankAccountId: BankIdAccountId) : Future[Box[View]]
@ -168,6 +171,10 @@ class RemotedataViewsCaseClasses {
case class removeAllViews(bankId: BankId, accountId: AccountId)
case class bulkDeleteAllPermissionsAndViews()
case class revokeAccessToSystemViewForConsumer(bankId: BankId, accountId: AccountId, view : View, consumerId : String)
case class revokeAccessToCustomViewForConsumer(view : View, consumerId : String)
}
object RemotedataViewsCaseClasses extends RemotedataViewsCaseClasses

View File

@ -1,5 +1,6 @@
package code.views.system
import code.api.Constant.ALL_CONSUMERS
import code.model.dataAccess.ResourceUser
import code.util.UUIDString
import net.liftweb.mapper._
@ -11,10 +12,16 @@ class AccountAccess extends LongKeyedMapper[AccountAccess] with IdPK with Create
def getSingleton = AccountAccess
object user_fk extends MappedLongForeignKey(this, ResourceUser)
object bank_id extends MappedString(this, 255)
//If consumer_id is `ALL-CONSUMERS`, any consumers can use this record
//If consumer_id is consumerId (obp UUID), only same consumer can use this record
object consumer_id extends MappedString(this, 255){
override def defaultValue = ALL_CONSUMERS
}
object account_id extends MappedString(this, 255)
object view_id extends UUIDString(this)
object view_fk extends MappedLongForeignKey(this, ViewDefinition)
}
object AccountAccess extends AccountAccess with LongKeyedMetaMapper[AccountAccess] {
override def dbIndexes: List[BaseIndex[AccountAccess]] = UniqueIndex(bank_id, account_id, view_fk, user_fk) :: super.dbIndexes
override def dbIndexes: List[BaseIndex[AccountAccess]] = UniqueIndex(bank_id, account_id, view_fk, user_fk, consumer_id) :: super.dbIndexes
}

View File

@ -221,7 +221,13 @@ class ViewDefinition extends View with LongKeyedMapper[ViewDefinition] with Many
override def defaultValue = false
}
object canAddCounterparty_ extends MappedBoolean(this){
override def defaultValue = true
override def defaultValue = false
}
object canGetCounterparty_ extends MappedBoolean(this){
override def defaultValue = false
}
object canDeleteCounterparty_ extends MappedBoolean(this){
override def defaultValue = false
}
object canDeleteCorporateLocation_ extends MappedBoolean(this){
override def defaultValue = false
@ -259,9 +265,13 @@ class ViewDefinition extends View with LongKeyedMapper[ViewDefinition] with Many
object canDeleteWhereTag_ extends MappedBoolean(this){
override def defaultValue = false
}
//internal transfer between my own accounts
object canAddTransactionRequestToOwnAccount_ extends MappedBoolean(this){
override def defaultValue = false
}
// transfer to any account
object canAddTransactionRequestToAnyAccount_ extends MappedBoolean(this){
override def defaultValue = false
}
@ -354,6 +364,8 @@ class ViewDefinition extends View with LongKeyedMapper[ViewDefinition] with Many
canAddPublicAlias_(actions.exists(_ == "can_add_public_alias"))
canAddPrivateAlias_(actions.exists(_ == "can_add_private_alias"))
canAddCounterparty_(actions.exists(_ == "can_add_counterparty"))
canDeleteCounterparty_(actions.exists(_ == "can_delete_counterparty"))
canGetCounterparty_(actions.exists(_ == "can_get_counterparty"))
canDeleteCorporateLocation_(actions.exists(_ == "can_delete_corporate_location"))
canDeletePhysicalLocation_(actions.exists(_ == "can_delete_physical_location"))
canEditOwnerComment_(actions.exists(_ == "can_edit_narrative"))
@ -465,6 +477,8 @@ class ViewDefinition extends View with LongKeyedMapper[ViewDefinition] with Many
def canAddPublicAlias : Boolean = canAddPublicAlias_.get
def canAddPrivateAlias : Boolean = canAddPrivateAlias_.get
def canAddCounterparty : Boolean = canAddCounterparty_.get
def canGetCounterparty : Boolean = canGetCounterparty_.get
def canDeleteCounterparty : Boolean = canDeleteCounterparty_.get
def canDeleteCorporateLocation : Boolean = canDeleteCorporateLocation_.get
def canDeletePhysicalLocation : Boolean = canDeletePhysicalLocation_.get

View File

@ -0,0 +1,39 @@
package code.webhook
import code.api.util.OBPQueryParam
import net.liftweb.common.Box
import net.liftweb.util.SimpleInjector
import scala.collection.immutable.List
import scala.concurrent.Future
object BankAccountNotificationWebhookTrait extends SimpleInjector {
val bankAccountNotificationWebhook = new Inject(buildOne _) {}
def buildOne: BankAccountNotificationWebhookProvider = MappedBankAccountNotificationWebhookProvider
}
trait BankAccountNotificationWebhookProvider {
def getBankAccountNotificationWebhookByIdFuture(webhookId: String): Future[Box[BankAccountNotificationWebhookTrait]]
def getBankAccountNotificationWebhooksByUserIdFuture(userId: String): Future[Box[List[BankAccountNotificationWebhookTrait]]]
def getBankAccountNotificationWebhooksFuture(queryParams: List[OBPQueryParam]): Future[Box[List[BankAccountNotificationWebhookTrait]]]
def createBankAccountNotificationWebhookFuture(
bankId: String,
userId: String,
triggerName: String,
url: String,
httpMethod: String,
httpProtocol: String
): Future[Box[BankAccountNotificationWebhookTrait]]
def deleteBankAccountNotificationWebhookFuture(webhookId: String): Future[Box[Boolean]]
}
trait BankAccountNotificationWebhookTrait extends SystemAccountNotificationWebhookTrait{
def bankId: String
}

View File

@ -0,0 +1,93 @@
package code.webhook
import code.api.util._
import code.util.{AccountIdString, MappedUUID, UUIDString}
import net.liftweb.common.{Box, Full}
import net.liftweb.mapper._
import net.liftweb.util.Helpers.tryo
import scala.collection.immutable.List
import com.openbankproject.commons.ExecutionContext.Implicits.global
import scala.concurrent.Future
object MappedBankAccountNotificationWebhookProvider extends BankAccountNotificationWebhookProvider {
override def getBankAccountNotificationWebhookByIdFuture(webhookId: String): Future[Box[BankAccountNotificationWebhookTrait]] = {
Future(
BankAccountNotificationWebhook.find(
By(BankAccountNotificationWebhook.WebhookId, webhookId)
)
)
}
override def getBankAccountNotificationWebhooksByUserIdFuture(userId: String): Future[Box[List[BankAccountNotificationWebhookTrait]]] = {
Future(
Full(
BankAccountNotificationWebhook.findAll(
By(BankAccountNotificationWebhook.CreatedByUserId, userId),
OrderBy(BankAccountNotificationWebhook.updatedAt, Descending)
)
)
)
}
override def getBankAccountNotificationWebhooksFuture(queryParams: List[OBPQueryParam]): Future[Box[List[BankAccountNotificationWebhookTrait]]] = {
val limit = queryParams.collectFirst { case OBPLimit(value) => MaxRows[BankAccountNotificationWebhook](value) }
val offset = queryParams.collectFirst { case OBPOffset(value) => StartAt[BankAccountNotificationWebhook](value) }
val userId = queryParams.collectFirst { case OBPUserId(value) => By(BankAccountNotificationWebhook.CreatedByUserId, value) }
val optionalParams: Seq[QueryParam[BankAccountNotificationWebhook]] = Seq(limit.toSeq, offset.toSeq, userId.toSeq).flatten
Future(
Full(
BankAccountNotificationWebhook.findAll(optionalParams: _*)
)
)
}
override def createBankAccountNotificationWebhookFuture(
bankId: String,
userId: String,
triggerName: String,
url: String,
httpMethod: String,
httpProtocol: String,
): Future[Box[BankAccountNotificationWebhookTrait]] = {
val createBankAccountNotificationWebhook = BankAccountNotificationWebhook.create
.BankId(bankId)
.CreatedByUserId(userId)
.TriggerName(triggerName)
.Url(url)
.HttpMethod(httpMethod)
.HttpProtocol(httpProtocol)
.saveMe()
Future(Full(createBankAccountNotificationWebhook))
}
override def deleteBankAccountNotificationWebhookFuture(webhookId: String): Future[Box[Boolean]] = {
Future{BankAccountNotificationWebhook.find(By(BankAccountNotificationWebhook.WebhookId, webhookId)).map(_.delete_!)}
}
}
class BankAccountNotificationWebhook extends BankAccountNotificationWebhookTrait with LongKeyedMapper[BankAccountNotificationWebhook] with IdPK with CreatedUpdated {
def getSingleton = BankAccountNotificationWebhook
object WebhookId extends MappedUUID(this)
object BankId extends UUIDString(this)
object TriggerName extends MappedString(this, 64)
object Url extends MappedString(this, 1024)
object HttpMethod extends MappedString(this, 64)
object HttpProtocol extends MappedString(this, 64)
object CreatedByUserId extends UUIDString(this)
def webhookId: String = WebhookId.get
def bankId: String = BankId.get
def triggerName: String = TriggerName.get
def url: String = Url.get
def httpMethod: String = HttpMethod.get
def httpProtocol: String = HttpProtocol.get
def createdByUserId: String = CreatedByUserId.get
}
object BankAccountNotificationWebhook extends BankAccountNotificationWebhook with LongKeyedMetaMapper[BankAccountNotificationWebhook] {
override def dbIndexes = UniqueIndex(WebhookId) :: super.dbIndexes
}

View File

@ -90,7 +90,7 @@ class MappedAccountWebhook extends AccountWebhook with LongKeyedMapper[MappedAcc
object mBankId extends UUIDString(this)
object mAccountId extends AccountIdString(this)
object mTriggerName extends MappedString(this, 64)
object mUrl extends MappedString(this, 64)
object mUrl extends MappedString(this, 1024)
object mHttpMethod extends MappedString(this, 64)
object mHttpProtocol extends MappedString(this, 64)
object mCreatedByUserId extends UUIDString(this)

View File

@ -0,0 +1,50 @@
package code.webhook
import code.api.util.OBPQueryParam
import net.liftweb.common.Box
import net.liftweb.util.SimpleInjector
import scala.collection.immutable.List
import scala.concurrent.Future
object SystemAccountNotificationWebhookTrait extends SimpleInjector {
val systemAccountNotificationWebhook = new Inject(buildOne _) {}
def buildOne: SystemAccountNotificationWebhookProvider = MappedSystemAccountNotificationWebhookProvider
}
trait SystemAccountNotificationWebhookProvider {
def getSystemAccountNotificationWebhookByIdFuture(webhookId: String): Future[Box[SystemAccountNotificationWebhookTrait]]
def getSystemAccountNotificationWebhooksByUserIdFuture(userId: String): Future[Box[List[SystemAccountNotificationWebhookTrait]]]
def getSystemAccountNotificationWebhooksFuture(queryParams: List[OBPQueryParam]): Future[Box[List[SystemAccountNotificationWebhookTrait]]]
def createSystemAccountNotificationWebhookFuture(
userId: String,
triggerName: String,
url: String,
httpMethod: String,
httpProtocol: String
): Future[Box[SystemAccountNotificationWebhookTrait]]
def deleteSystemAccountNotificationWebhookFuture(webhookId: String): Future[Box[Boolean]]
}
trait SystemAccountNotificationWebhookTrait {
def webhookId: String
def triggerName: String
def url: String
def httpMethod: String
def httpProtocol: String
def createdByUserId: String
}

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