diff --git a/.github/workflows/build_package.yml b/.github/workflows/build_package.yml index eb2eb0839..ecc424c90 100644 --- a/.github/workflows/build_package.yml +++ b/.github/workflows/build_package.yml @@ -21,8 +21,9 @@ jobs: cache: maven - name: Build with Maven run: | + cp obp-api/src/main/resources/props/sample.props.template obp-api/src/main/resources/props/default.props echo connector=star > obp-api/src/main/resources/props/test.default.props - echo starConnector_supported_types=mapped,internal > obp-api/src/main/resources/props/test.default.props + echo starConnector_supported_types=mapped,internal >> 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 diff --git a/obp-api/pom.xml b/obp-api/pom.xml index 773ed3154..0da2fe907 100644 --- a/obp-api/pom.xml +++ b/obp-api/pom.xml @@ -62,7 +62,12 @@ org.slf4j log4j-over-slf4j - 1.7.25 + 1.7.26 + + + slf4j-ext + org.slf4j + 1.7.26 org.apache.logging.log4j @@ -110,12 +115,12 @@ com.oracle.database.jdbc ojdbc8 - 21.4.0.0 + 21.5.0.0 com.h2database h2 - 1.4.191 + 2.1.214 runtime @@ -138,7 +143,7 @@ junit junit - 4.13.1 + 4.13.2 test @@ -201,41 +206,27 @@ amqp_3.1_${scala.version} 1.5.0 - - com.tokbox - opentok-server-sdk - 3.0.0-beta.2 - - - com.fasterxml.jackson.core - jackson-databind - - - + + + + + + + + + + + org.elasticsearch elasticsearch 6.8.17 + com.sksamuel.elastic4s - elastic4s-http_${scala.version} - 6.1.1 - + elastic4s-client-esjava_${scala.version} + 8.2.1 @@ -356,10 +347,11 @@ 0.5.7 + - net.manub - scalatest-embedded-kafka_2.12 - 2.0.0 + io.github.embeddedkafka + embedded-kafka_2.12 + 2.4.1.1 test @@ -444,7 +436,7 @@ sh.ory.hydra hydra-client - 1.7.0 + 1.11.8 diff --git a/obp-api/src/main/resources/i18n/lift-core_es_ES.properties b/obp-api/src/main/resources/i18n/lift-core_es_ES.properties index d85c91ebd..87c8ccaf3 100644 --- a/obp-api/src/main/resources/i18n/lift-core_es_ES.properties +++ b/obp-api/src/main/resources/i18n/lift-core_es_ES.properties @@ -1,84 +1,163 @@ -api.explorer = Explorador API -introduction = Introducción +# The main drawback here is that all application resources for a given locale must exist +# in the same file, and that extended characters such as œ aren’t properly encoded by +# default. This is due to Java properties bundles using ISO 8859-1 encoding and thus +# requiring conversion to escaped format, such as \u0153. Fortunately this process can +# be automated using tools like native2ascii +# https://native2ascii.net/ +# This tool will allow you to convert national language characters to and from their Unicode equivalents in plain ASCII text. +api.explorer = API Explorer +api_explorer = API Explorer +api_manager = Gestor API +introduction = Introducci\u00f3n support = Soporte register = Registrarse logon = Ingresar +terms_conditions = Terminos y condiciones +privacy_policy = Pol\u00edtica de privacidad +api_documentation = Documentaci\u00f3n API +api_host = Host del API +api_tester = Evaluador API +view_api_explorer = Ver API Explorer +get_api_key = Obten llave API +welcome_to = Bienvenido a la instancia de prueba del API Sandbox del Open Bank Project! +get_started_building_your_application = Empiece a crear su aplicaci\ufffdn +for_banks = Para Bancos -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 + +Get_started = Empezar +Create_an_account = Crea una cuenta +Description_Create_an_account = En primer lugar, crea una cuenta de desarrollador gratuita en este sandbox y solicita una clave de desarrollador. En esta fase se te pedir\ufffd que env\ufffdes informaci\ufffdn b\ufffdsica sobre tu aplicaci\ufffdn. +Connect_your_app = Conecta tu app +Connect_your_app_description = Utiliza nuestros SDKs para conectar tu aplicaci\ufffdn a las APIs de Open Bank Project. Necesitar\ufffds tu clave de desarrollador, que deber\ufffdas tener desde que creaste tu cuenta. Consulta todas las APIs disponibles en el Explorador de APIs, pero aseg\ufffdrate de que est\ufffds utilizando la URL base correcta. +Test_your_app = Pruebe su aplicaci\ufffdn con datos de clientes +Test_your_app_description = una vez que su aplicaci\ufffdn est\ufffd conectada, puede probarla utilizando las credenciales del cliente de prueba. + + + +register_for_an_account = Registrar una cuenta nueva + +# Explore APIs Section +explore_api_title = Explorar los titulos de las API +explore_api_accounts_title = Cuentas +explore_api_accounts = Acceso a cuentas (XS21) y tarjetas. Proporcionar acceso de grano fino a los invitados (auditor, contable o p\ufffdblico). +explore_api_branches_title = Sucursales, cajeros y productos +explore_api_branches = Acceda a los datos abiertos relacionados con los bancos, incluidas las sucursales y los cajeros autom\ufffdticos, as\ufffd como la geolocalizaci\ufffdn y los horarios de apertura. +explore_api_transactions_title = Transacciones +explore_api_transactions = Acceda al historial de transacciones y a los metadatos de las mismas. +explore_api_metadata_title = Metadata +explore_api_metadata = Enriquezca las transacciones y las contrapartes con metadatos que incluyan geolocalizaciones, comentarios, im\ufffdgenes y etiquetas (por ejemplo, categor\ufffda de gasto). +explore_api_counterparties_title = Metadata +explore_api_counterparties = Acceda a los pagadores y beneficiarios de una cuenta, incluyendo metadatos como sus alias, etiquetas, logotipos y p\ufffdginas de inicio. +explore_api_webhooks_title = Webhooks +explore_api_webhooks = Llamar a servicios web externos basados en eventos de la Cuenta. +explore_api_customer_title = Incorporaci\ufffdn de clientes y KYC +explore_api_customer = Realizar la creaci\ufffdn de usuarios, clientes y cuentas. Gestionar los documentos, los medios y el estado de Conozca a su Cliente (KYC). Crear reuniones y mensajes de clientes. +explore_api_roles_title = Funciones, m\ufffdtricas y documentaci\ufffdn de la API +explore_api_roles = Controle el acceso a los puntos finales, obtenga m\ufffdtricas y documentaci\ufffdn de la API. +explore_api_payments_title = Pagos y transferencias +explore_api_payments = Iniciar solicitudes de transacciones (transferencias y pagos). Ver y confirmar cargos (seg\ufffdn la PSD2). Responder a los retos de autenticaci\ufffdn fuerte del cliente (SCA). +explore_api_warehouse_title = Buscar en el almac\ufffdn +explore_api_warehouse = Realice b\ufffdsquedas avanzadas y consultas estad\ufffdsticas en el almac\ufffdn de datos. + +# Get Started Section +get_started_title = Empezar +get_started_create_account_title = Crea una cuenta +get_started_create_account = En primer lugar, crea una cuenta de desarrollador gratuita en este sandbox y solicita una clave de desarrollador. En esta fase se te pedir\u00e1 que env\u00edes informaci\u00f3n b\u00e1sica sobre tu aplicaci\u00f3n. +get_started_create_account_sign_up = Register for an account +get_started_connect_your_app_title = Conecta tu app +get_started_connect_your_app = Utiliza nuestros SDKs para conectar tu aplicaci\u00f3n a las APIs de Open Bank Project. Necesitar\u00e1s tu clave de desarrollador, que deber\u00edas tener desde que creaste tu cuenta. Consulta todas las APIs disponibles en el Explorador de APIs, pero aseg\u00farate de que est\u00e1s utilizando la URL base correcta. +get_started_test_your_app_title = Pruebe su aplicaci\u00f3n con datos de clientes +get_started_test_your_app = Una vez que su aplicaci\u00f3n est\u00e9 conectada, puede probarla utilizando las credenciales del cliente de prueba. +get_started_test_your_app_sandbox_date = View sandbox customer log ons. + +username = Nombre de usuario +logontext = Acceda a la API del Open Bank Project +passwordlog = Contrase\ufffda +Forgotten_password = \ufffdHas olvidado tu contrase\ufffda? +don't_have_account = \ufffdNo Tienes una cuenta? +or_login_with_openid = o con\ufffdctate con OpenID +or = o + + +invalid.email.address = Direcci\ufffdn de correo electr\ufffdnico no v\ufffdlida +password.must.be.set = Hay que poner la contrase\ufffda +password.too.short = Contrase\ufffda demasiado corta +passwords.do.not.match = Las contrase\ufffdas no coinciden +number.required = Se debe proporcionar un valor num\ufffdrico +ajax.error=El servidor no puede ser contactado en este momento +invalid.zip.code = C\ufffddigo postal inv\ufffdlido +invalid.postal.code = C\ufffddigo postal inv\ufffdlido +unique.email.address = La direcci\ufffdn de correo electr\ufffdnico debe ser \ufffdnica +must.be.logged.in = Debe estar conectado +already.logged.in = ya ha iniciado la sesi\ufffdn. Por favor, cierre la sesi\ufffdn primero. +login = Ingresar +logout = Salir +log.in = Ingresar +log.out = Salir +sign.up = Registrarse +logged.in = Ingresar +logout.first = Por favor, cierre la sesi\ufffdn primero. +lost.password = Contrase\ufffda perdida +reset.password = Restablecer contrase\ufffda +change.password = Cambiar la contrase\ufffda +password.changed = Contrase\ufffda cambiada +edit.user = Editar usuario +validate.user = Validar usuario +edit.profile = Editar perfil +sign.up.confirmation = Confirmaci\ufffdn de inscripci\ufffdn +sign.up.message = Se ha inscrito. Se le enviar\ufffd un mensaje de correo electr\ufffdnico de validaci\ufffdn. +sign.up.validation.link=Haga clic en este enlace para completar la inscripci\ufffdn: +welcome = Bienvenido +account.validated = Cuenta validada +invalid.validation.link = Enlace de validaci\ufffdn inv\ufffdlido +account.validation.error = Su cuenta no ha sido validada. Por favor, compruebe su correo electr\ufffdnico para un enlace de validaci\ufffdn. +invalid.credentials = Nombre de usuario/contrase\ufffda no v\ufffdlidos +enter.email = Introduzca su direcci\ufffdn de correo electr\ufffdnico y le enviaremos un enlace para restablecer su contrase\ufffda +email.address = Correo electr\ufffdnico +reset.password.confirmation = Confirmacion para reestablecer la contrase\ufffda +dear = Apreciable +click.reset.link = Da click en el enlace para reestablecer tu contrase\ufffda +thank.you = Gracias +reset.password.request = Solicitud para reestablecer contrase\ufffda +password.reset.email.sent = Contrase\ufffda para reestablecer el correo ha sido enviada +account.validation.resent = Validaci\ufffdn de la cuenta enviado de nuevo +email.address.not.found = Direcci\ufffdn de correo no encontrado +send.it = Enviar +reset.your.password = Resetea tu contrase\ufffda +enter.your.new.password = Escribe tu contrase\ufffda +repeat.your.new.password = Escribe tu contrase\ufffda (repitela) +set.password = Establecer contrase\ufffda +password.link.invalid = Enlace para cambio de contrase\ufffda invalido +wrong.old.password = Contrase\ufffda equivocada +old.password = Contrase\ufffda antigua +new.password = Nueva contrase\ufffda +repeat.password = Nueva contrase\ufffda (repetir) +repeat = Repetir +edit = Editar 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 +change = Cambiar +password = Contrase\ufffda +recover.password = Recuperar contrase\ufffda +profile.updated = Has actualizado tu perfil +male = Masculino +female = Femenino +first.name = Primer Nombre +last.name = Apellido +locale = Localidad +time.zone = Zona Horaria +msg.notice = Advertencia +msg.warning = Peligro 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 +crudify.menu.view.displayName=Ver %s +crudify.menu.edit.displayName=Editar %s +crudify.menu.delete.displayName=Borrar %s +paginator.norecords = No hay informaci\ufffdn que mostrar +paginator.displayingrecords = Mostrando %s-%s de %s + +open_bank_project_is = Open Bank Project es +and_commercial_licenses = TESOBE y licencias comerciales + # Country names country_1 = United States country_2 = Afghanistan @@ -353,35 +432,37 @@ 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 +Next = Siguiente +Previous = Anterior +Finish = Terminado +Cancel = Cancelado # Crudify -Create = Create -Save = Save -Edit = Edit -Delete = Delete -delete = delete -View = View -List = List %s -Created = Created -Edited = Edited -Deleted = Deleted +Create = Crear +Save = Guardar +Edit = Editar +Delete = Borrar +delete = borrar +View = Ver +List = lista %s +Created = Creado +Edited = Editado +Deleted = Borrado #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 +invalid.login.credentials= Credenciales invalidas +invalid.username=Nombre de usuario invalido: \ +1) Los \ufffdnicos caracteres permitidos en los nombres de usuario son: a-z A-Z 0-9 . _ \ +2) Los nombres de usuario DEBEN tener entre 8 y 100 caracteres \ +3) Los nombres de usuario NO DEBEN empezar por _ o . \ +4) Los nombres de usuario NO DEBEN contener __ o ._ o ._ o .. \ +5) Los nombres de usuario NO DEBEN terminar con _ o . \ +6) Cualquier direcci\ufffdn de correo electr\ufffdnico v\ufffdlida est\ufffd permitida como nombre de usuario -your.username.is.not.unique = Your username is not unique. Please enter a different one. +your.username.is.not.unique = Su nombre de usuario no es \ufffdnico. Por favor, introduzca uno diferente. # 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. \ No newline at end of file +user.invitation.is.already.finished = Parece que el enlace de invitaci\ufffdn no es v\ufffdlido. \ufffdTodav\ufffda necesitas ayuda? Por favor, env\ufffdanos un mensaje usando el soporte de API Playground. +your.secret.link.is.not.valid = Parece que el enlace de invitaci\ufffdn no es v\ufffdlido. \ufffdTodav\ufffda necesitas ayuda? Por favor, env\ufffdanos un mensaje usando el soporte de API Playground. + +OBP-30001 = El usuario no ha iniciado sesi\u00c3\u00b3n. \u00c2\u00a1Se requiere autenticaci\u00c3\u00b3n! \ No newline at end of file diff --git a/obp-api/src/main/resources/i18n/lift-core_zh_CN.properties b/obp-api/src/main/resources/i18n/lift-core_zh_CN.properties new file mode 100644 index 000000000..e48956f7a --- /dev/null +++ b/obp-api/src/main/resources/i18n/lift-core_zh_CN.properties @@ -0,0 +1,347 @@ +invalid.email.address = \u65e0\u6548\u7535\u5b50\u90ae\u7bb1\u5730\u5740 +password.must.be.set = \u5bc6\u7801\u4e0d\u80fd\u4e3a\u7a7a +password.too.short = \u5bc6\u7801\u592a\u77ed +passwords.do.not.match = \u5bc6\u7801\u4e0d\u5339\u914d +number.required = \u53ea\u80fd\u63d0\u4f9b\u6570\u5b57\u503c +ajax.error=\u8fde\u63a5\u670d\u52a1\u5668\u5931\u8d25 +invalid.zip.code = \u65e0\u6548\u90ae\u653f\u7f16\u7801 +invalid.postal.code = \u65e0\u6548\u90ae\u653f\u7f16\u7801 +unique.email.address = \u7535\u5b50\u90ae\u7bb1\u5730\u5740\u5df2\u5b58\u5728 +must.be.logged.in = \u8bf7\u5148\u767b\u5f55 +already.logged.in = \u5df2\u7ecf\u767b\u5f55\uff0c\u8bf7\u5148\u767b\u51fa +login = \u767b\u5f55 +logout = \u767b\u51fa +log.in = \u767b\u5f55 +log.out = \u767b\u51fa +sign.up = \u6ce8\u518c +logged.in = \u5df2\u767b\u5f55 +logout.first = \u8bf7\u5148\u767b\u51fa +lost.password = \u5fd8\u8bb0\u5bc6\u7801 +reset.password = \u91cd\u7f6e\u5bc6\u7801 +change.password = \u4fee\u6539\u5bc6\u7801 +password.changed = \u5bc6\u7801\u5df2\u4fee\u6539 +edit.user = \u8bbe\u7f6e\u7528\u6237 +validate.user = \u6821\u9a8c\u7528\u6237 +edit.profile = \u4fee\u6539\u8d44\u6599 +sign.up.confirmation = \u6ce8\u518c\u4fe1\u606f\u786e\u8ba4 +sign.up.message = \u6ce8\u518c\u6210\u529f\uff0c\u7cfb\u7edf\u4f1a\u53d1\u9001\u4e00\u5c01\u5e10\u53f7\u9a8c\u8bc1\u90ae\u4ef6\u5230\u4f60\u7684\u7535\u5b50\u90ae\u7bb1 +sign.up.validation.link=\u70b9\u51fb\u6b64\u94fe\u63a5\u5b8c\u6210\u6ce8\u518c\uff1a +welcome = \u6b22\u8fce +account.validated = \u5e10\u53f7\u9a8c\u8bc1\u6210\u529f +invalid.validation.link = \u9a8c\u8bc1\u94fe\u63a5\u65e0\u6548 +account.validation.error = \u60a8\u7684\u5e10\u53f7\u8fd8\u6ca1\u9a8c\u8bc1\uff0c\u8bf7\u68c0\u67e5\u60a8\u7684\u90ae\u7bb1\u83b7\u5f97\u9a8c\u8bc1\u94fe\u63a5 +invalid.credentials = \u7528\u6237\u540d/\u5bc6\u7801\u9519\u8bef +enter.email = \u8f93\u5165\u4e00\u4e2a\u7535\u5b50\u90ae\u7bb1\u5730\u5740\uff0c\u6211\u4eec\u4f1a\u53d1\u9001\u4e00\u4e2a\u5bc6\u7801\u91cd\u7f6e\u94fe\u63a5\u5230\u8be5\u90ae\u7bb1 +email.address = \u7535\u5b50\u90ae\u7bb1\u5730\u5740 +reset.password.confirmation = \u91cd\u7f6e\u5bc6\u7801\u4fe1\u606f\u786e\u8ba4 +dear = \u5c0a\u656c\u7684 +click.reset.link = \u70b9\u51fb\u6b64\u94fe\u63a5\u91cd\u7f6e\u60a8\u7684\u5bc6\u7801 +thank.you = \u8c22\u8c22 +reset.password.request = \u8bf7\u6c42\u5bc6\u7801\u91cd\u7f6e +password.reset.email.sent = \u5bc6\u7801\u91cd\u7f6e\u90ae\u4ef6\u5df2\u53d1\u9001 +account.validation.resent = \u91cd\u53d1\u5e10\u53f7\u9a8c\u8bc1\u90ae\u4ef6 +email.address.not.found = \u7535\u5b50\u90ae\u7bb1\u5730\u5740\u4e0d\u5b58\u5728 +send.it = \u53d1\u9001 +reset.your.password = \u91cd\u7f6e\u5bc6\u7801 +enter.your.new.password = \u8f93\u5165\u65b0\u5bc6\u7801 +repeat.your.new.password = \u8f93\u5165\u65b0\u5bc6\u7801\uff08\u786e\u8ba4\uff09 +set.password = \u8bbe\u7f6e\u5bc6\u7801 +password.link.invalid = \u5bc6\u7801\u91cd\u7f6e\u94fe\u7ed3\u65e0\u6548 +wrong.old.password = \u539f\u5bc6\u7801\u9519\u8bef +old.password = \u539f\u5bc6\u7801 +new.password = \u65b0\u5bc6\u7801 +repeat.password = \u65b0\u5bc6\u7801\u786e\u8ba4 +repeat = \u786e\u8ba4 +edit = \u4fee\u6539 +cancel = \u53d6\u6d88 +ok = \u786e\u5b9a +change = \u4fee\u6539 +password = \u5bc6\u7801 +recover.password = \u6062\u590d\u5bc6\u7801 +profile.updated = \u60a8\u7684\u8d44\u6599\u4fee\u6539\u6210\u529f +male = \u7537 +female = \u5973 +first.name = \u540d +last.name = \u59d3 +locale = \u533a\u57df +time.zone = \u65f6\u533a +msg.notice = \u4fe1\u606f +msg.warning = \u8b66\u544a +msg.error = \u9519\u8bef +crudify.menu.view.displayName=\u67e5\u770b %s +crudify.menu.edit.displayName=\u8bbe\u7f6e %s +crudify.menu.delete.displayName=\u5220\u9664 %s +country_1 = \u7f8e\u56fd +country_2 = \u963f\u5bcc\u6c57 +country_3 = \u963f\u5c14\u5df4\u5c3c\u4e9a +country_4 = \u963f\u5c14\u53ca\u5229\u4e9a +country_5 = \u5b89\u9053\u5c14 +country_6 = \u5b89\u54e5\u62c9 +country_7 = \u5b89\u63d0\u74dc\u548c\u5df4\u5e03\u8fbe +country_8 = \u963f\u6839\u5ef7 +country_9 = \u4e9a\u7f8e\u5c3c\u4e9a +country_10 = \u6fb3\u5927\u5229\u4e9a +country_11 = \u5965\u5730\u5229 +country_12 = \u963f\u585e\u62dc\u7586 +country_13 = \u5df4\u54c8\u9a6c +country_14 = \u5df4\u6797 +country_15 = \u5b5f\u52a0\u62c9\u56fd +country_16 = \u5df4\u5df4\u591a\u65af +country_17 = \u767d\u4fc4\u7f57\u65af +country_18 = \u6bd4\u5229\u65f6 +country_19 = \u4f2f\u5229\u5179 +country_20 = \u8d1d\u5b81 +country_21 = \u4e0d\u4e39 +country_22 = \u73bb\u5229\u7ef4\u4e9a +country_23 = \u6ce2\u65af\u5c3c\u4e9a\u548c\u9ed1\u585e\u54e5\u7ef4 +country_24 = \u535a\u8328\u74e6\u7eb3 +country_25 = \u5df4\u897f +country_26 = \u6587\u83b1 +country_27 = \u4fdd\u52a0\u5229\u4e9a +country_28 = \u5e03\u57fa\u7eb3\u6cd5\u7d22 +country_29 = \u5e03\u9686\u8fea +country_30 = \u67ec\u57d4\u5be8 +country_31 = \u5580\u9ea6\u9686 +country_32 = \u52a0\u62ff\u5927 +country_33 = \u4f5b\u5f97\u89d2 +country_34 = \u4e2d\u975e\u5171\u548c\u56fd +country_35 = \u4e4d\u5f97 +country_36 = \u667a\u5229 +country_37 = \u4e2d\u56fd +country_38 = \u54e5\u4f26\u6bd4\u4e9a +country_39 = \u79d1\u6469\u7f57 +country_40 = \u521a\u679c\u6c11\u4e3b\u5171\u548c\u56fd +country_41 = \u521a\u679c\u5171\u548c\u56fd +country_42 = \u54e5\u65af\u8fbe\u9ece\u52a0 +country_43 = \u79d1\u7279\u8fea\u74e6 +country_44 = \u514b\u7f57\u5730\u4e9a +country_45 = \u53e4\u5df4 +country_46 = \u585e\u6d66\u8def\u65af +country_47 = \u6377\u514b\u5171\u548c\u56fd +country_48 = \u4e39\u9ea6 +country_49 = \u5409\u5e03\u63d0 +country_50 = \u591a\u7c73\u5c3c\u52a0 +country_51 = \u591a\u7c73\u5c3c\u52a0\u5171\u548c\u56fd +country_52 = \u5384\u74dc\u591a\u5c14 +country_53 = \u57c3\u53ca +country_54 = \u8428\u5c14\u74e6\u591a +country_55 = \u8d64\u9053\u51e0\u5185\u4e9a +country_56 = \u5384\u7acb\u7279\u91cc\u4e9a +country_57 = \u7231\u6c99\u5c3c\u4e9a +country_58 = \u57c3\u585e\u4fc4\u6bd4\u4e9a +country_59 = \u6590\u6d4e +country_60 = \u82ac\u5170 +country_61 = \u6cd5\u56fd +country_62 = \u52a0\u84ec +country_63 = \u5188\u6bd4\u4e9a\uff0c +country_64 = \u683c\u9c81\u5409\u4e9a +country_65 = \u5fb7\u56fd +country_66 = \u52a0\u7eb3 +country_67 = \u5e0c\u814a +country_68 = \u683c\u6797\u7eb3\u8fbe +country_69 = \u5371\u5730\u9a6c\u62c9 +country_70 = \u51e0\u5185\u4e9a +country_71 = \u51e0\u5185\u4e9a\u6bd4\u7ecd +country_72 = \u572d\u4e9a\u90a3 +country_73 = \u6d77\u5730 +country_74 = \u6d2a\u90fd\u62c9\u65af +country_75 = \u5308\u7259\u5229 +country_76 = \u51b0\u5c9b +country_77 = \u5370\u5ea6 +country_78 = \u5370\u5ea6\u5c3c\u897f\u4e9a +country_79 = \u4f0a\u6717 +country_80 = \u4f0a\u62c9\u514b +country_81 = \u7231\u5c14\u5170 +country_82 = \u4ee5\u8272\u5217 +country_83 = \u610f\u5927\u5229 +country_84 = \u7259\u4e70\u52a0 +country_85 = \u65e5\u672c +country_86 = \u7ea6\u65e6 +country_87 = \u54c8\u8428\u514b\u65af\u5766 +country_88 = \u80af\u5c3c\u4e9a +country_89 = \u57fa\u91cc\u5df4\u65af +country_90 = \u671d\u9c9c +country_91 = \u97e9\u56fd +country_92 = \u79d1\u5a01\u7279 +country_93 = \u5409\u5c14\u5409\u65af\u65af\u5766 +country_94 = \u8001\u631d +country_95 = \u62c9\u8131\u7ef4\u4e9a +country_96 = \u9ece\u5df4\u5ae9 +country_97 = \u83b1\u7d22\u6258 +country_98 = \u5229\u6bd4\u91cc\u4e9a +country_99 = \u5229\u6bd4\u4e9a +country_100 = \u5217\u652f\u6566\u58eb\u767b +country_101 = \u7acb\u9676\u5b9b +country_102 = \u5362\u68ee\u5821 +country_103 = \u9a6c\u5176\u987f +country_104 = \u9a6c\u8fbe\u52a0\u65af\u52a0 +country_105 = \u9a6c\u62c9\u7ef4 +country_106 = \u9a6c\u6765\u897f\u4e9a +country_107 = \u9a6c\u5c14\u4ee3\u592b +country_108 = \u9a6c\u91cc +country_109 = \u9a6c\u8033\u4ed6 +country_110 = \u9a6c\u7ecd\u5c14\u7fa4\u5c9b +country_111 = \u6bdb\u91cc\u5854\u5c3c\u4e9a +country_112 = \u6bdb\u91cc\u6c42\u65af +country_113 = \u58a8\u897f\u54e5 +country_114 = \u5bc6\u514b\u7f57\u5c3c\u897f\u4e9a +country_115 = \u6469\u5c14\u591a\u74e6 +country_116 = \u6469\u7eb3\u54e5 +country_117 = \u8499\u53e4 +country_118 = \u9ed1\u5c71 +country_119 = \u6469\u6d1b\u54e5 +country_120 = \u83ab\u6851\u6bd4\u514b +country_121 = \u7f05\u7538 +country_122 = \u7eb3\u7c73\u6bd4\u4e9a +country_123 = \u7459\u9c81 +country_124 = \u5c3c\u6cca\u5c14 +country_125 = \u8377\u5170 +country_126 = \u65b0\u897f\u5170 +country_127 = \u5c3c\u52a0\u62c9\u74dc +country_128 = \u5c3c\u65e5\u5c14 +country_129 = \u5c3c\u65e5\u5229\u4e9a +country_130 = \u632a\u5a01 +country_131 = \u963f\u66fc +country_132 = \u5df4\u57fa\u65af\u5766 +country_133 = \u5e15\u52b3 +country_134 = \u5df4\u62ff\u9a6c +country_135 = \u5df4\u5e03\u4e9a\u65b0\u51e0\u5185\u4e9a +country_136 = \u5df4\u62c9\u572d +country_137 = \u79d8\u9c81 +country_138 = \u83f2\u5f8b\u5bbe +country_139 = \u6ce2\u5170 +country_140 = \u8461\u8404\u7259 +country_141 = \u5361\u5854\u5c14 +country_142 = \u7f57\u9a6c\u5c3c\u4e9a +country_143 = \u4fc4\u7f57\u65af +country_144 = \u5362\u65fa\u8fbe +country_145 = \u5723\u57fa\u8328\u548c\u5c3c\u7ef4\u65af +country_146 = \u5723\u5362\u897f\u4e9a +country_147 = \u5723\u6587\u68ee\u7279\u548c\u683c\u6797\u7eb3\u4e01\u65af +country_148 = \u8428\u6469\u4e9a +country_149 = \u5723\u9a6c\u529b\u8bfa +country_150 = \u5723\u591a\u7f8e\u548c\u666e\u6797\u897f\u6bd4 +country_151 = \u6c99\u7279\u963f\u62c9\u4f2f +country_152 = \u585e\u5185\u52a0\u5c14 +country_153 = \u585e\u5c14\u7ef4\u4e9a +country_154 = \u585e\u820c\u5c14 +country_155 = \u585e\u62c9\u5229\u6602 +country_156 = \u65b0\u52a0\u5761 +country_157 = \u65af\u6d1b\u4f10\u514b +country_158 = \u65af\u6d1b\u6587\u5c3c\u4e9a +country_159 = \u6240\u7f57\u95e8\u7fa4\u5c9b +country_160 = \u7d22\u9a6c\u91cc +country_161 = \u5357\u975e +country_162 = \u897f\u73ed\u7259 +country_163 = \u65af\u91cc\u5170\u5361 +country_164 = \u82cf\u4e39 +country_165 = \u82cf\u91cc\u5357 +country_166 = \u65af\u5a01\u58eb\u5170 +country_167 = \u745e\u5178 +country_168 = \u745e\u58eb +country_169 = \u53d9\u5229\u4e9a +country_170 = \u5854\u5409\u514b\u65af\u5766 +country_171 = \u5766\u6851\u5c3c\u4e9a +country_172 = \u6cf0\u56fd +country_173 = \u4e1c\u5e1d\u6c76 +country_174 = \u591a\u54e5 +country_175 = \u6c64\u52a0 +country_176 = \u7279\u7acb\u5c3c\u8fbe\u548c\u591a\u5df4\u54e5 +country_177 = \u7a81\u5c3c\u65af +country_178 = \u571f\u8033\u5176 +country_179 = \u571f\u5e93\u66fc\u65af\u5766 +country_180 = \u56fe\u74e6\u5362 +country_181 = \u4e4c\u5e72\u8fbe +country_182 = \u4e4c\u514b\u5170 +country_183 = \u963f\u62c9\u4f2f\u8054\u5408\u914b\u957f\u56fd +country_184 = \u82f1\u56fd +country_185 = \u4e4c\u62c9\u572d +country_186 = \u4e4c\u5179\u522b\u514b\u65af\u5766 +country_187 = \u74e6\u52aa\u963f\u56fe +country_188 = \u68b5\u8482\u5188\u57ce +country_189 = \u59d4\u5185\u745e\u62c9 +country_190 = \u8d8a\u5357 +country_191 = \u4e5f\u95e8 +country_192 = \u8d5e\u6bd4\u4e9a +country_193 = \u6d25\u5df4\u5e03\u97e6 +country_194 = \u963f\u5e03\u54c8\u5179 +country_195 = \u53f0\u6e7e +country_196 = \u7eb3\u5361 +country_197 = \u5317\u585e\u6d66\u8def\u65af +country_198 = \u5fb7\u6d85\u65af\u7279\u6cb3\u6cbf\u5cb8\u5171\u548c\u56fd +country_199 = \u7d22\u9a6c\u91cc\u5170 +country_200 = \u5357\u5965\u585e\u68af +country_201 = \u963f\u4ec0\u83ab\u5c14\u548c\u5361\u6377\u5c9b +country_202 = \u5723\u8bde\u5c9b +country_203 = \u79d1\u79d1\u65af\uff08\u57fa\u6797\uff09\u7fa4\u5c9b +country_204 = \u73ca\u745a\u6d77\u7fa4\u5c9b +country_205 = \u8d6b\u5fb7\u5c9b\u548c\u9ea6\u5f53\u52b3\u7fa4\u5c9b +country_206 = \u8bfa\u798f\u514b\u5c9b +country_207 = \u65b0\u5580\u91cc\u591a\u5c3c\u4e9a +country_208 = \u6cd5\u5c5e\u6ce2\u5229\u5c3c\u897f\u4e9a +country_209 = \u9a6c\u7ea6\u7279 +country_210 = \u5723\u5df4\u6cf0\u52d2\u7c73 +country_211 = \u5723\u9a6c\u4e01 +country_212 = \u5723\u76ae\u57c3\u5c14\u548c\u5bc6\u514b\u9686 +country_213 = \u74e6\u5229\u65af\u7fa4\u5c9b\u548c\u5bcc\u56fe\u7eb3\u7fa4\u5c9b +country_214 = \u6cd5\u56fd\u5357\u90e8\u548c\u5357\u6781\u9886\u5730 +country_215 = \u514b\u5229\u73c0\u987f\u5c9b +country_216 = \u5e03\u7ef4\u5c9b +country_217 = \u5e93\u514b\u7fa4\u5c9b +country_218 = \u7ebd\u57c3 +country_219 = \u6258\u514b\u52b3 +country_220 = \u6839\u897f\u5c9b +country_221 = \u9a6c\u6069\u5c9b +country_222 = \u6cfd\u897f +country_223 = \u5b89\u572d\u62c9 +country_224 = \u767e\u6155\u5927 +country_225 = \u82f1\u5c5e\u5370\u5ea6\u6d0b\u9886\u5730 +country_226 = \u82f1\u56fd\u4e3b\u6743\u57fa\u5730\u533a +country_227 = \u82f1\u5c5e\u7ef4\u5c14\u4eac\u7fa4\u5c9b +country_228 = \u5f00\u66fc\u7fa4\u5c9b +country_229 = \u798f\u514b\u5170\u7fa4\u5c9b +country_230 = \u76f4\u5e03\u7f57\u9640 +country_231 = \u8499\u7279\u585e\u62c9\u7279 +country_232 = \u76ae\u7279\u51ef\u6069\u7fa4\u5c9b +country_233 = \u5723\u6d77\u4f26\u5a1c +country_234 = \u5357\u4e54\u6cbb\u4e9a\u5c9b\u548c\u5357\u6851\u5a01\u5947\u7fa4\u5c9b +country_235 = \u7279\u514b\u65af\u548c\u51ef\u79d1\u65af\u7fa4\u5c9b +country_236 = \u5317\u9a6c\u91cc\u4e9a\u7eb3\u7fa4\u5c9b +country_237 = \u6ce2\u591a\u9ece\u5404 +country_238 = \u7f8e\u5c5e\u8428\u6469\u4e9a +country_239 = \u8d1d\u514b\u5c9b +country_240 = \u5173\u5c9b +country_241 = \u8c6a\u5170\u5c9b +country_242 = \u8d3e\u7ef4\u65af\u5c9b +country_243 = \u7ea6\u7ff0\u65af\u987f\u73af\u7901 +country_244 = \u91d1\u66fc\u7901 +country_245 = \u4e2d\u9014\u5c9b +country_246 = \u7eb3\u74e6\u8428\u5c9b +country_247 = \u5df4\u5c14\u7c73\u62c9\u73af\u7901 +country_248 = \u7f8e\u56fd\u7ef4\u5c14\u4eac\u7fa4\u5c9b +country_249 = \u5a01\u514b\u5c9b +country_250 = \u9999\u6e2f +country_251 = \u6fb3\u95e8 +country_252 = \u6cd5\u7f57\u7fa4\u5c9b +country_253 = \u683c\u9675\u5170 +country_254 = \u6cd5\u5c5e\u572d\u4e9a\u90a3 +country_255 = \u74dc\u5fb7\u7f57\u666e\u5c9b +country_256 = \u9a6c\u63d0\u5c3c\u514b +country_257 = \u7559\u5c3c\u6c6a +country_258 = \u5965\u5170 +country_259 = \u963f\u9c81\u5df4 +country_260 = \u8377\u5c5e\u5b89\u7684\u5217\u65af +country_261 = \u65af\u74e6\u5c14\u5df4\u5fb7 +country_262 = \u963f\u68ee\u677e +country_263 = \u7279\u91cc\u65af\u5766\u8fbe\u5e93\u5c3c\u4e9a +country_264 = \u5357\u6781\u6d32 +country_265 = \u79d1\u7d22\u6c83 +country_266 = \u5df4\u52d2\u65af\u5766\u9886\u571f +country_267 = \u897f\u6492\u54c8\u62c9 +country_268 = \u6fb3\u5927\u5229\u4e9a\u5357\u6781\u9886\u5730 +country_269 = \u7f57\u65af\u5c5e\u5730 +country_270 = \u5f7c\u5f97\u4e00\u4e16\u5c9b +country_271 = \u6bdb\u5fb7\u7687\u540e\u5730 +country_272 = \u82f1\u5c5e\u5357\u6781\u9886\u5730 + +OBP-30001 = \u627e\u4e0d\u5230\u94f6\u884c\u3002\u0020\u8bf7\u4e3a\u0020\u0042\u0041\u004e\u004b\u005f\u0049\u0044\u0020\u6307\u5b9a\u4e00\u4e2a\u6709\u6548\u503c diff --git a/obp-api/src/main/resources/props/sample.props.template b/obp-api/src/main/resources/props/sample.props.template index 6bdb7c721..290a9da3b 100644 --- a/obp-api/src/main/resources/props/sample.props.template +++ b/obp-api/src/main/resources/props/sample.props.template @@ -243,7 +243,14 @@ mail.smtp.port=25 token_expiration_weeks=4 ## payment challenge answer timeout,default is 600 seconds/10 minutes -transaction_request_challenge_ttl=600 +transactionRequest.challenge.ttl.seconds=600 + +## auth context update request challenge answer timeout,default is 600 seconds/10 minutes +userAuthContextUpdateRequest.challenge.ttl.seconds=600 + +# the allowed attempts to answer the same transactionRequest Challenge, default is 3 times +#answer_transactionRequest_challenge_allowed_attempts=3 + @@ -283,14 +290,6 @@ transactionRequests_challenge_threshold_SEPA=1000 # To set a currency for the above value: #transactionRequests_challenge_currency=KRW - -## For video conference meetings (createMeeting) -meeting.tokbox_enabled=false -meeting.tokbox_api_key=changeme -meeting.tokbox_api_secret=changeme - - - ### Management modules ## RabbitMQ settings (used to communicate with HBCI project) diff --git a/obp-api/src/main/resources/props/test.default.props.template b/obp-api/src/main/resources/props/test.default.props.template index 0a54e2d3b..1aa12bb2b 100644 --- a/obp-api/src/main/resources/props/test.default.props.template +++ b/obp-api/src/main/resources/props/test.default.props.template @@ -66,10 +66,11 @@ tests.port=8016 End of minimum settings #################################### -#if connector is mapped, set a database backend. If not set, this will be set to an in-memory h2 database by default -#you can use a no config needed h2 database by setting db.driver=org.h2.Driver and not including db.url +# if connector is mapped, set a database backend. If not set, this will be set to an in-memory h2 database by default +# you can use a no config needed h2 database by setting db.driver=org.h2.Driver and not including db.url +# Please note that since update o version 2.1.214 we use NON_KEYWORDS=VALUE to bypass reserved word issue in SQL statements #db.driver=org.h2.Driver -#db.url=jdbc:h2:./lift_proto.db;DB_CLOSE_ON_EXIT=FALSE +#db.url=jdbc:h2:./lift_proto.db;NON_KEYWORDS=VALUE;DB_CLOSE_ON_EXIT=FALSE #set this to false if you don't want the api payments call to work payments_enabled=false diff --git a/obp-api/src/main/scala/bootstrap/liftweb/Boot.scala b/obp-api/src/main/scala/bootstrap/liftweb/Boot.scala index 3165a8766..12ac29629 100644 --- a/obp-api/src/main/scala/bootstrap/liftweb/Boot.scala +++ b/obp-api/src/main/scala/bootstrap/liftweb/Boot.scala @@ -55,7 +55,7 @@ import code.bankconnectors.{Connector, ConnectorEndpoints} import code.branches.MappedBranch import code.cardattribute.MappedCardAttribute import code.cards.{MappedPhysicalCard, PinReset} -import code.consent.MappedConsent +import code.consent.{ConsentRequest, MappedConsent} import code.consumer.Consumers import code.context.{MappedConsentAuthContext, MappedUserAuthContext, MappedUserAuthContextUpdate} import code.crm.MappedCrmEvent @@ -246,14 +246,14 @@ class Boot extends MdcLoggable { case Props.RunModes.Test => new StandardDBVendor( driver, - APIUtil.getPropsValue("db.url") openOr "jdbc:h2:mem:OBPTest;DB_CLOSE_DELAY=-1", + APIUtil.getPropsValue("db.url") openOr Constant.h2DatabaseDefaultUrlValue, APIUtil.getPropsValue("db.user").orElse(Empty), APIUtil.getPropsValue("db.password").orElse(Empty) ) case _ => new StandardDBVendor( driver, - "jdbc:h2:mem:OBPTest;DB_CLOSE_DELAY=-1", + h2DatabaseDefaultUrlValue, Empty, Empty) } @@ -1004,6 +1004,7 @@ object ToSchemify { MappedCustomerIdMapping, MappedProductAttribute, MappedConsent, + ConsentRequest, MigrationScriptLog, MethodRouting, EndpointMapping, diff --git a/obp-api/src/main/scala/code/api/OBPRestHelper.scala b/obp-api/src/main/scala/code/api/OBPRestHelper.scala index 3fc94438c..d9d13b5e3 100644 --- a/obp-api/src/main/scala/code/api/OBPRestHelper.scala +++ b/obp-api/src/main/scala/code/api/OBPRestHelper.scala @@ -31,12 +31,13 @@ import java.net.URLDecoder import code.api.Constant._ import code.api.OAuthHandshake._ import code.api.builder.AccountInformationServiceAISApi.APIMethods_AccountInformationServiceAISApi -import code.api.util.APIUtil._ +import code.api.util.APIUtil.{getClass, _} import code.api.util.ErrorMessages.{InvalidDAuthHeaderToken, UserIsDeleted, UsernameHasBeenLocked, attemptedToOpenAnEmptyBox} import code.api.util._ import code.api.v3_0_0.APIMethods300 import code.api.v3_1_0.APIMethods310 -import code.api.v4_0_0.APIMethods400 +import code.api.v4_0_0.{APIMethods400, OBPAPI4_0_0} +import code.api.v5_0_0.OBPAPI5_0_0 import code.loginattempts.LoginAttempt import code.model.dataAccess.AuthUser import code.util.Helper.MdcLoggable @@ -45,15 +46,18 @@ import com.openbankproject.commons.model.ErrorMessage import com.openbankproject.commons.util.{ApiVersion, ReflectUtils, ScannedApiVersion} import net.liftweb.common.{Box, Full, _} import net.liftweb.http.rest.RestHelper -import net.liftweb.http.{JsonResponse, LiftResponse, Req, S} +import net.liftweb.http.{JsonResponse, LiftResponse, LiftRules, Req, S, TransientRequestMemoize} import net.liftweb.json.Extraction import net.liftweb.json.JsonAST.JValue -import net.liftweb.util.Helpers +import net.liftweb.util.{Helpers, NamedPF, Props, ThreadGlobal} +import net.liftweb.util.Helpers.tryo +import java.util.{Locale, ResourceBundle} import scala.collection.immutable.List import scala.collection.mutable.ArrayBuffer import scala.math.Ordering import scala.util.control.NoStackTrace +import scala.xml.{Node, NodeSeq} trait APIFailure{ val msg : String @@ -72,7 +76,76 @@ object APIFailure { case class APIFailureNewStyle(failMsg: String, failCode: Int = 400, ccl: Option[CallContextLight] = None - ) + ){ + def translatedErrorMessage = { + + val errorCode = extractErrorMessageCode(failMsg) + val errorBody = extractErrorMessageBody(failMsg) + + val localeUrlParameter = getHttpRequestUrlParam(ccl.map(_.url).getOrElse(""),"Locale") + val locale = I18NUtil.computeLocale(localeUrlParameter) + + val liftCoreResourceBundle = tryo(ResourceBundle.getBundle(LiftRules.liftCoreResourceName, locale)).toList + + val _resBundle = new ThreadGlobal[List[ResourceBundle]] + object resourceValueCache extends TransientRequestMemoize[(String, Locale), String] + + def resourceBundles(loc: Locale): List[ResourceBundle] = { + _resBundle.box match { + case Full(bundles) => bundles + case _ => { + _resBundle.set( + LiftRules.resourceForCurrentLoc.vend() ::: + LiftRules.resourceNames.flatMap(name => tryo{ + if (Props.devMode) { + tryo{ + val clz = this.getClass.getClassLoader.loadClass("java.util.ResourceBundle") + val meth = clz.getDeclaredMethods. + filter{m => m.getName == "clearCache" && m.getParameterTypes.length == 0}. + toList.head + meth.invoke(null) + } + } + List(ResourceBundle.getBundle(name, loc)) + }.openOr( + NamedPF.applyBox((name, loc), LiftRules.resourceBundleFactories.toList).map(List(_)) openOr Nil + ))) + _resBundle.value + } + } + } + + + def resourceBundleList: List[ResourceBundle] = resourceBundles(locale) ++ liftCoreResourceBundle + + def ?!(str: String, resBundle: List[ResourceBundle]): String = + resBundle.flatMap( + r => tryo( + r.getObject(str) match { + case s: String => Full(s) + case n: Node => Full(n.text) + case ns: NodeSeq => Full(ns.text) + case _ => Empty + }) + .flatMap(s => s)).find(s => true) getOrElse { + LiftRules.localizationLookupFailureNotice.foreach(_ (str, locale)); + str + } + + def ?(str: String, locale: Locale): String = resourceValueCache.get( + str -> + locale, + if(?!(str, resourceBundleList)==str) //If can not find the value from props, then return the default error body. + errorBody + else + ?!(str, resourceBundleList) + + ) + + val translatedErrorBody = ?(errorCode, locale) + s"$errorCode$translatedErrorBody" + } +} //if you change this, think about backwards compatibility! All existing //versions of the API return this failure message, so if you change it, make sure @@ -586,8 +659,9 @@ trait OBPRestHelper extends RestHelper with MdcLoggable { apiPrefix:OBPEndpoint => OBPEndpoint, autoValidateAll: Boolean = false): Unit = { - def isAutoValidate(doc: ResourceDoc): Boolean = - doc.isValidateEnabled || (autoValidateAll && !doc.isValidateDisabled && doc.implementedInApiVersion == version) + def isAutoValidate(doc: ResourceDoc): Boolean = { //note: only support v5.0.0 and v4.0.0 at the moment. + doc.isValidateEnabled || (autoValidateAll && !doc.isValidateDisabled && List(OBPAPI5_0_0.version,OBPAPI4_0_0.version).contains(doc.implementedInApiVersion)) + } for(route <- routes) { // one endpoint can have multiple ResourceDocs, so here use filter instead of find, e.g APIMethods400.Implementations400.createTransactionRequest diff --git a/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/ResourceDocsAPIMethods.scala b/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/ResourceDocsAPIMethods.scala index 0ede25cf3..d23988202 100644 --- a/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/ResourceDocsAPIMethods.scala +++ b/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/ResourceDocsAPIMethods.scala @@ -248,7 +248,7 @@ trait ResourceDocsAPIMethods extends MdcLoggable with APIMethods220 with APIMeth Caching.memoizeSyncWithProvider (Some(cacheKey.toString())) (getStaticResourceDocsTTL second) { logger.debug(s"Generating OBP Resource Docs requestedApiVersion is $requestedApiVersion") - val resourceDocJson = resourceDocsToResourceDocJson(getResourceDocsList(requestedApiVersion), resourceDocTags, partialFunctionNames, isVersion4OrHigher) + val resourceDocJson = resourceDocsToResourceDocJson(getResourceDocsList(requestedApiVersion), resourceDocTags, partialFunctionNames, isVersion4OrHigher, languageParam) resourceDocJson.map(resourceDocsJsonToJsonResponse) } } @@ -309,7 +309,7 @@ trait ResourceDocsAPIMethods extends MdcLoggable with APIMethods220 with APIMeth val allDocs = staticDocs.map( _ ++ filteredDocs) - val resourceDocJson = resourceDocsToResourceDocJson(allDocs, resourceDocTags, partialFunctionNames, isVersion4OrHigher) + val resourceDocJson = resourceDocsToResourceDocJson(allDocs, resourceDocTags, partialFunctionNames, isVersion4OrHigher, languageParam) resourceDocJson.map(resourceDocsJsonToJsonResponse) } } @@ -353,25 +353,27 @@ trait ResourceDocsAPIMethods extends MdcLoggable with APIMethods220 with APIMeth case None => dynamicDocs } - val resourceDocJson = resourceDocsToResourceDocJson(Some(filteredDocs), resourceDocTags, partialFunctionNames, isVersion4OrHigher) + val resourceDocJson = resourceDocsToResourceDocJson(Some(filteredDocs), resourceDocTags, partialFunctionNames, isVersion4OrHigher, languageParam) resourceDocJson.map(resourceDocsJsonToJsonResponse) }}} private def resourceDocsToResourceDocJson(rd: Option[List[ResourceDoc]], - resourceDocTags: Option[List[ResourceDocTag]], - partialFunctionNames: Option[List[String]], - isVersion4OrHigher:Boolean): Option[ResourceDocsJson] = + resourceDocTags: Option[List[ResourceDocTag]], + partialFunctionNames: Option[List[String]], + isVersion4OrHigher:Boolean, + languageParam: Option[LanguageParam]): Option[ResourceDocsJson] = { for { resourceDocs <- rd } yield { // Filter val rdFiltered = ResourceDocsAPIMethodsUtil.filterResourceDocs(resourceDocs, resourceDocTags, partialFunctionNames) // Format the data as json - JSONFactory1_4_0.createResourceDocsJson(rdFiltered, isVersion4OrHigher) + JSONFactory1_4_0.createResourceDocsJson(rdFiltered, isVersion4OrHigher, languageParam) } - + } + private val getChineseVersionResourceDocs : Box[JsonResponse] = { val stream = getClass().getClassLoader().getResourceAsStream("ResourceDocs/ResourceDocs-Chinese.json") val chineseVersion = try { @@ -402,9 +404,9 @@ trait ResourceDocsAPIMethods extends MdcLoggable with APIMethods220 with APIMeth List(apiTagDocumentation)) - val exampleResourceDocsJson = JSONFactory1_4_0.createResourceDocsJson(List(exampleResourceDoc), false) + val exampleResourceDocsJson = JSONFactory1_4_0.createResourceDocsJson(List(exampleResourceDoc), false, None) - val exampleResourceDocsJsonV400 = JSONFactory1_4_0.createResourceDocsJson(List(exampleResourceDoc), true) + val exampleResourceDocsJsonV400 = JSONFactory1_4_0.createResourceDocsJson(List(exampleResourceDoc), true, None) @@ -578,7 +580,7 @@ trait ResourceDocsAPIMethods extends MdcLoggable with APIMethods220 with APIMeth case _ if (apiCollectionIdParam.isDefined) => val operationIds = MappedApiCollectionEndpointsProvider.getApiCollectionEndpoints(apiCollectionIdParam.getOrElse("")).map(_.operationId).map(getObpFormatOperationId) val resourceDocs = ResourceDoc.getResourceDocs(operationIds) - val resourceDocsJson = JSONFactory1_4_0.createResourceDocsJson(resourceDocs, isVersion4OrHigher) + val resourceDocsJson = JSONFactory1_4_0.createResourceDocsJson(resourceDocs, isVersion4OrHigher, languageParam) val resourceDocsJsonJValue = Full(resourceDocsJsonToJsonResponse(resourceDocsJson)) Future(resourceDocsJsonJValue.map(successJsonResponse(_))) case _ => @@ -918,6 +920,7 @@ object ResourceDocsAPIMethodsUtil extends MdcLoggable{ def stringToLanguageParam (x: String) : Option[LanguageParam] = x.toLowerCase match { case "en" => Some(EN) case "zh" => Some(ZH) + case "es" | "es_ES" => Some(ES) case _ => Empty } diff --git a/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/SwaggerDefinitionsJSON.scala b/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/SwaggerDefinitionsJSON.scala index 968165127..68c98aad2 100644 --- a/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/SwaggerDefinitionsJSON.scala +++ b/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/SwaggerDefinitionsJSON.scala @@ -3745,6 +3745,7 @@ object SwaggerDefinitionsJSON { valid_from = Some(new Date()), time_to_live = Some(3600) ) + val postConsentRequestJsonV310 = postConsentPhoneJsonV310.copy(consumer_id = None) val consentsJsonV310 = ConsentsJsonV310(List(consentJsonV310)) @@ -4658,6 +4659,33 @@ object SwaggerDefinitionsJSON { consumer_id = consumerIdExample.value ) + val consentRequestResponseJson = ConsentRequestResponseJson( + consent_request_id = consentRequestIdExample.value, + payload = json.parse(consentRequestPayloadExample.value), + consumer_id = consumerIdExample.value + ) + + val consentJsonV500 = ConsentJsonV500( + consent_id = "9d429899-24f5-42c8-8565-943ffa6a7945", + jwt = "eyJhbGciOiJIUzI1NiJ9.eyJlbnRpdGxlbWVudHMiOltdLCJjcmVhdGVkQnlVc2VySWQiOiJhYjY1MzlhOS1iMTA1LTQ0ODktYTg4My0wYWQ4ZDZjNjE2NTciLCJzdWIiOiIyMWUxYzhjYy1mOTE4LTRlYWMtYjhlMy01ZTVlZWM2YjNiNGIiLCJhdWQiOiJlanpuazUwNWQxMzJyeW9tbmhieDFxbXRvaHVyYnNiYjBraWphanNrIiwibmJmIjoxNTUzNTU0ODk5LCJpc3MiOiJodHRwczpcL1wvd3d3Lm9wZW5iYW5rcHJvamVjdC5jb20iLCJleHAiOjE1NTM1NTg0OTksImlhdCI6MTU1MzU1NDg5OSwianRpIjoiMDlmODhkNWYtZWNlNi00Mzk4LThlOTktNjYxMWZhMWNkYmQ1Iiwidmlld3MiOlt7ImFjY291bnRfaWQiOiJtYXJrb19wcml2aXRlXzAxIiwiYmFua19pZCI6ImdoLjI5LnVrLngiLCJ2aWV3X2lkIjoib3duZXIifSx7ImFjY291bnRfaWQiOiJtYXJrb19wcml2aXRlXzAyIiwiYmFua19pZCI6ImdoLjI5LnVrLngiLCJ2aWV3X2lkIjoib3duZXIifV19.8cc7cBEf2NyQvJoukBCmDLT7LXYcuzTcSYLqSpbxLp4", + status = ConsentStatus.INITIATED.toString, + consent_request_id = Some(consentRequestIdExample.value) + ) + + val postConsentRequestJsonV500 = PostConsentRequestJsonV500( + everything = false, + account_access = List(AccountAccessV500( + account_routing = accountRoutingJsonV121, + view_id = viewIdExample.value + )), + entitlements = Some(List(PostConsentEntitlementJsonV310(bankIdExample.value, "CanGetCustomer"))), + consumer_id = Some(consumerIdExample.value), + phone_number = Some(mobileNumberExample.value), + email = Some(emailExample.value), + valid_from = Some(new Date()), + time_to_live = Some(3600) + ) + //The common error or success format. //Just some helper format to use in Json case class NotSupportedYet() diff --git a/obp-api/src/main/scala/code/api/constant/constant.scala b/obp-api/src/main/scala/code/api/constant/constant.scala index df24e99d7..b6584e625 100644 --- a/obp-api/src/main/scala/code/api/constant/constant.scala +++ b/obp-api/src/main/scala/code/api/constant/constant.scala @@ -8,6 +8,8 @@ import com.openbankproject.commons.util.ApiStandards // Note: Import this with: import code.api.Constant._ object Constant extends MdcLoggable { logger.info("Instantiating Constants") + + final val h2DatabaseDefaultUrlValue = "jdbc:h2:mem:OBPTest_H2_v2.1.214;NON_KEYWORDS=VALUE;DB_CLOSE_DELAY=10" final val HostName = APIUtil.getPropsValue("hostname").openOrThrowException(ErrorMessages.HostnameNotSpecified) def localIdentityProvider = APIUtil.getPropsValue("local_identity_provider", HostName) diff --git a/obp-api/src/main/scala/code/api/directlogin.scala b/obp-api/src/main/scala/code/api/directlogin.scala index 60fcee4c0..d7d537e5d 100644 --- a/obp-api/src/main/scala/code/api/directlogin.scala +++ b/obp-api/src/main/scala/code/api/directlogin.scala @@ -202,7 +202,7 @@ object DirectLogin extends RestHelper with MdcLoggable { case "username" => checkUsernameString(parameterValue) case "password" => - validatePasswordOnUsage(parameterValue) + basicPasswordValidation(parameterValue) case "consumer_key" => checkMediumAlphaNumeric(parameterValue) case "token" => diff --git a/obp-api/src/main/scala/code/api/util/APIUtil.scala b/obp-api/src/main/scala/code/api/util/APIUtil.scala index 791bb7ed9..d9afae578 100644 --- a/obp-api/src/main/scala/code/api/util/APIUtil.scala +++ b/obp-api/src/main/scala/code/api/util/APIUtil.scala @@ -33,6 +33,7 @@ import java.nio.charset.Charset import java.text.{ParsePosition, SimpleDateFormat} import java.util.concurrent.ConcurrentHashMap import java.util.{Calendar, Date, UUID} + import code.UserRefreshes.UserRefreshes import code.accountholders.AccountHolders import code.api.Constant._ @@ -65,7 +66,7 @@ import code.scope.Scope import code.usercustomerlinks.UserCustomerLink import code.util.Helper.{MdcLoggable, SILENCE_IS_GOLDEN} import code.util.{Helper, JsonSchemaUtil} -import code.views.Views +import code.views.{MapperViews, Views} import code.webuiprops.MappedWebUiPropsProvider.getWebUiPropsValue import com.alibaba.ttl.internal.javassist.CannotCompileException import com.github.dwickern.macros.NameOf.{nameOf, nameOfType} @@ -105,8 +106,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 scala.collection.mutable import scala.collection.mutable.{ArrayBuffer, ListBuffer} import scala.concurrent.Future @@ -130,10 +131,10 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{ val DateWithMsFormat = new SimpleDateFormat(DateWithMs) val DateWithMsRollbackFormat = new SimpleDateFormat(DateWithMsRollback) - val DateWithDayExampleString: String = "2017-09-19" - val DateWithSecondsExampleString: String = "2017-09-19T02:31:05Z" - val DateWithMsExampleString: String = "2017-09-19T02:31:05.000Z" - val DateWithMsRollbackExampleString: String = "2017-09-19T02:31:05.000+0000" + val DateWithDayExampleString: String = "1100-01-01" + val DateWithSecondsExampleString: String = "1100-01-01T01:01:01Z" + val DateWithMsExampleString: String = "1100-01-01T01:01:01.000Z" + val DateWithMsRollbackExampleString: String = "1100-01-01T01:01:01.000+0000" // Use a fixed date far into the future (rather than current date/time so that cache keys are more static) // (Else caching is invalidated by constantly changing date) @@ -611,14 +612,14 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{ def errorJsonResponse(message : String = "error", httpCode : Int = 400, callContextLight: Option[CallContextLight] = None)(implicit headers: CustomResponseHeaders = CustomResponseHeaders(Nil)) : JsonResponse = { def check403(message: String): Boolean = { - message.contains(UserHasMissingRoles) || - message.contains(UserNoPermissionAccessView) || - message.contains(UserHasMissingRoles) || - message.contains(UserNotSuperAdminOrMissRole) || - message.contains(ConsumerHasMissingRoles) + message.contains(extractErrorMessageCode(UserHasMissingRoles)) || + message.contains(extractErrorMessageCode(UserNoPermissionAccessView)) || + message.contains(extractErrorMessageCode(UserHasMissingRoles)) || + message.contains(extractErrorMessageCode(UserNotSuperAdminOrMissRole)) || + message.contains(extractErrorMessageCode(ConsumerHasMissingRoles)) } def check401(message: String): Boolean = { - message.contains(UserNotLoggedIn) + message.contains(extractErrorMessageCode(UserNotLoggedIn)) } val (code, responseHeaders) = message match { @@ -677,7 +678,7 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{ * 1) length is >16 characters without validations but max length <= 512 * 2) or Min 10 characters with mixed numbers + letters + upper+lower case + at least one special character. * */ - def validatePasswordOnCreation(password: String): Boolean = { + def fullPasswordValidation(password: String): Boolean = { /** * (?=.*\d) //should contain at least one digit * (?=.*[a-z]) //should contain at least one lower case @@ -688,9 +689,8 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{ val regex = """^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[!"#$%&'\(\)*+,-./:;<=>?@\\[\\\\]^_\\`{|}~])([A-Za-z0-9!"#$%&'\(\)*+,-./:;<=>?@\\[\\\\]^_\\`{|}~]{10,16})$""".r password match { - case password if(validatePasswordOnUsage(password) ==SILENCE_IS_GOLDEN) => true - case password if(password.length > 16 && password.length <= 512) => true - case regex(password) => true + case password if(password.length > 16 && password.length <= 512 && basicPasswordValidation(password) ==SILENCE_IS_GOLDEN) => true + case regex(password) if(basicPasswordValidation(password) ==SILENCE_IS_GOLDEN) => true case _ => false } } @@ -725,7 +725,7 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{ /** only A-Z, a-z, 0-9, all allowed characters for password and max length <= 512 */ /** also support space now */ - def validatePasswordOnUsage(value:String): String ={ + def basicPasswordValidation(value:String): String ={ val valueLength = value.length val regex = """^([A-Za-z0-9!"#$%&'\(\)*+,-./:;<=>?@\\[\\\\]^_\\`{|}~ ]+)$""".r value match { @@ -3038,8 +3038,10 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{ throw new Exception("Empty Box not allowed") case obj1@ParamFailure(m,e,c,af: APIFailureNewStyle) => val obj = (m,e, c) match { - case ("", Empty, Empty) => Empty ?~! af.failMsg - case _ => Failure (m, e, c) ?~! af.failMsg + case ("", Empty, Empty) => + Empty ?~! af.translatedErrorMessage + case _ => + Failure (m, e, c) ?~! af.translatedErrorMessage } val failuresMsg = filterMessage(obj) val callContext = af.ccl.map(_.copy(httpCode = Some(af.failCode))) @@ -3326,7 +3328,7 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{ * 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], consumerId: Option[String] = None): Box[View] = { - val customView = Views.views.vend.customView(viewId, bankIdAccountId) + val customView = MapperViews.customView(viewId, bankIdAccountId) customView match { // CHECK CUSTOM VIEWS // 1st: View is Pubic and Public views are NOT allowed on this instance. case Full(v) if(v.isPublic && !allowPublicViews) => Failure(PublicViewsNotAllowedOnThisInstance) @@ -3336,7 +3338,7 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{ 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) + val systemView = MapperViews.systemView(viewId) systemView match { // CHECK SYSTEM VIEWS // 1st: View is Pubic and Public views are NOT allowed on this instance. case Full(v) if(v.isPublic && !allowPublicViews) => Failure(PublicViewsNotAllowedOnThisInstance) @@ -4200,5 +4202,25 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{ | """.stripMargin - val transactionRequestChallengeTtl = APIUtil.getPropsAsLongValue("transaction_request_challenge_ttl", 600) + val transactionRequestChallengeTtl = APIUtil.getPropsAsLongValue("transactionRequest.challenge.ttl.seconds", 600) + val userAuthContextUpdateRequestChallengeTtl = APIUtil.getPropsAsLongValue("userAuthContextUpdateRequest.challenge.ttl.seconds", 600) + + val obpErrorMessageCodeRegex = "^(OBP-\\d+):" + + //eg: UserHasMissingRoles = "OBP-20006: User is missing one or more roles:" --> + // errorCode = "OBP-20006:" + // So far we support the i180n, we need to separate the errorCode and errorBody + def extractErrorMessageCode (errorMessage: String) = { + val regex = obpErrorMessageCodeRegex.r + regex.findFirstIn(errorMessage).mkString + } + //eg: UserHasMissingRoles = "OBP-20006: User is missing one or more roles:" --> + // errorBody = " User is missing one or more roles:" + // So far we support the i180n, we need to separate the errorCode and errorBody + def extractErrorMessageBody(errorMessage: String) = { + + errorMessage.replaceFirst(obpErrorMessageCodeRegex,"") + } + + val allowedAnswerTransactionRequestChallengeAttempts = APIUtil.getPropsAsIntValue("answer_transactionRequest_challenge_allowed_attempts").openOr(3) } diff --git a/obp-api/src/main/scala/code/api/util/ErrorMessages.scala b/obp-api/src/main/scala/code/api/util/ErrorMessages.scala index 51437346a..046cb5512 100644 --- a/obp-api/src/main/scala/code/api/util/ErrorMessages.scala +++ b/obp-api/src/main/scala/code/api/util/ErrorMessages.scala @@ -69,7 +69,7 @@ object ErrorMessages { val FXCurrencyCodeCombinationsNotSupported = "OBP-10004: ISO Currency code combination not supported for FX. Please modify the FROM_CURRENCY_CODE or TO_CURRENCY_CODE. " val InvalidDateFormat = "OBP-10005: Invalid Date Format. Could not convert value to a Date." val InvalidCurrency = "OBP-10006: Invalid Currency Value." - val IncorrectRoleName = "OBP-10007: Incorrect Role name: " + val IncorrectRoleName = "OBP-10007: Incorrect Role name:" val CouldNotTransformJsonToInternalModel = "OBP-10008: Could not transform Json to internal model." val CountNotSaveOrUpdateResource = "OBP-10009: Could not save or update resource." val NotImplemented = "OBP-10010: Not Implemented " @@ -77,7 +77,7 @@ object ErrorMessages { val maximumLimitExceeded = "OBP-10012: Invalid value. Maximum number is 10000." val attemptedToOpenAnEmptyBox = "OBP-10013: Attempted to open an empty Box." val cannotDecryptValueOfProperty = "OBP-10014: Could not decrypt value of property " - val AllowedValuesAre = "OBP-10015: Allowed values are: " + val AllowedValuesAre = "OBP-10015: Allowed values are:" val InvalidFilterParameterFormat = "OBP-10016: Incorrect filter Parameters in URL. " val InvalidUrl = "OBP-10017: Incorrect URL Format. " val TooManyRequests = "OBP-10018: Too Many Requests." @@ -106,7 +106,7 @@ object ErrorMessages { val FilterIsDeletedFormatError = s"OBP-10036: is_deleted parameter can only take two values: TRUE or FALSE!" val InvalidApiVersionString = "OBP-00027: Invalid API Version string. We could not find the version specified." - val IncorrectTriggerName = "OBP-10028: Incorrect Trigger name: " + val IncorrectTriggerName = "OBP-10028: Incorrect Trigger name:" val ScaMethodNotDefined = "OBP-10030: Strong customer authentication method is not defined at this instance." @@ -114,11 +114,11 @@ object ErrorMessages { // Authentication / Authorisation / User messages (OBP-20XXX) val UserNotLoggedIn = "OBP-20001: User not logged in. Authentication is required!" - val DirectLoginMissingParameters = "OBP-20002: These DirectLogin parameters are missing: " - val DirectLoginInvalidToken = "OBP-20003: This DirectLogin token is invalid or expired: " + val DirectLoginMissingParameters = "OBP-20002: These DirectLogin parameters are missing:" + val DirectLoginInvalidToken = "OBP-20003: This DirectLogin token is invalid or expired:" val InvalidLoginCredentials = "OBP-20004: Invalid login credentials. Check username/password." val UserNotFoundById = "OBP-20005: User not found. Please specify a valid value for USER_ID." - val UserHasMissingRoles = "OBP-20006: User is missing one or more roles: " + val UserHasMissingRoles = "OBP-20006: User is missing one or more roles:" val UserNotFoundByEmail = "OBP-20007: User not found by email." val InvalidConsumerKey = "OBP-20008: Invalid Consumer Key." @@ -146,13 +146,13 @@ object ErrorMessages { val SystemViewsCanNotBeModified = "OBP-20021: System Views can not be modified. Only the created views can be modified." val ViewDoesNotPermitAccess = "OBP-20022: View does not permit the access." - val ConsumerHasMissingRoles = "OBP-20023: Consumer is missing one or more roles: " + val ConsumerHasMissingRoles = "OBP-20023: Consumer is missing one or more roles:" val ConsumerNotFoundById = "OBP-20024: Consumer not found. Please specify a valid value for CONSUMER_ID." val ScopeNotFound = "OBP-20025: Scope not found. Please specify a valid value for SCOPE_ID." val ConsumerDoesNotHaveScope = "OBP-20026: CONSUMER_ID does not have the SCOPE_ID " val UserNotFoundByUsername = "OBP-20027: User not found by username." - val GatewayLoginMissingParameters = "OBP-20028: These GatewayLogin parameters are missing: " + val GatewayLoginMissingParameters = "OBP-20028: These GatewayLogin parameters are missing:" val GatewayLoginUnknownError = "OBP-20029: Unknown Gateway login error." val GatewayLoginHostPropertyMissing = "OBP-20030: Property gateway.host is not defined." val GatewayLoginWhiteListAddresses = "OBP-20031: Gateway login can be done only from allowed addresses." @@ -172,7 +172,7 @@ object ErrorMessages { val NotEnoughtSearchStatisticsResults = "OBP-20052: Result set too small. Will not be displayed for reasons of privacy." val ElasticSearchEmptyQueryBody = "OBP-20053: The Elasticsearch query body cannot be empty" val InvalidAmount = "OBP-20054: Invalid amount. Please specify a valid value for amount." - val MissingQueryParams = "OBP-20055: These query parameters are missing: " + val MissingQueryParams = "OBP-20055: These query parameters are missing:" val ElasticSearchDisabled = "OBP-20056: Elasticsearch is disabled for this API instance." val UserNotFoundByUserId = "OBP-20057: User not found by userId." val ConsumerIsDisabled = "OBP-20058: Consumer is disabled." @@ -185,7 +185,7 @@ object ErrorMessages { val UserIsDeleted = "OBP-20064: The user is deleted!" val DAuthCannotGetOrCreateUser = "OBP-20065: Cannot get or create user during DAuth process." - val DAuthMissingParameters = "OBP-20066: These DAuth parameters are missing: " + val DAuthMissingParameters = "OBP-20066: These DAuth parameters are missing:" val DAuthUnknownError = "OBP-20067: Unknown DAuth login error." val DAuthHostPropertyMissing = "OBP-20068: Property dauth.host is not defined." val DAuthWhiteListAddresses = "OBP-20069: DAuth login can be done only from allowed addresses." @@ -198,7 +198,7 @@ object ErrorMessages { val UserAttributeNotFound = "OBP-20081: User Attribute not found by USER_ATTRIBUTE_ID." - val UserNotSuperAdminOrMissRole = "OBP-20101: Current User is not super admin or is missing entitlements: " + val UserNotSuperAdminOrMissRole = "OBP-20101: Current User is not super admin or is missing entitlements:" val CannotGetOrCreateUser = "OBP-20102: Cannot get or create user." val InvalidUserProvider = "OBP-20103: Invalid DAuth User Provider." @@ -267,7 +267,7 @@ object ErrorMessages { val CreateBankError = "OBP-30020: Could not create the Bank" val UpdateBankError = "OBP-30021: Could not update the Bank" - val NoViewPermission = "OBP-30022: The current view does not have the permission: " + val NoViewPermission = "OBP-30022: The current view does not have the permission:" val UpdateConsumerError = "OBP-30023: Cannot update Consumer " val CreateConsumerError = "OBP-30024: Could not create Consumer " val CreateOAuth2ConsumerError = "OBP-30077: Could not create OAuth2 Consumer." @@ -467,7 +467,7 @@ object ErrorMessages { val ConsentDisabled = "OBP-35007: Consents are not allowed at this instance. " val ConsentHeaderNotFound = "OBP-35008: Cannot get Consent-Id. " val ConsentAllowedScaMethods = "OBP-35009: Only SMS and EMAIL are supported as SCA methods. " - val SmsServerNotResponding = "OBP-35010: SMS server is not working or SMS server can not send the message to the phone number: " + val SmsServerNotResponding = "OBP-35010: SMS server is not working or SMS server can not send the message to the phone number:" val AuthorizationNotFound = "OBP-35011: Resource identification of the related Consent authorisation sub-resource not found by AUTHORIZATION_ID. " val ConsentAlreadyRevoked = "OBP-35012: Consent is already revoked. " val RolesAllowedInConsent = "OBP-35013: Consents can only contain Roles that you already have access to." @@ -485,6 +485,8 @@ object ErrorMessages { val ConsentUpdateStatusError = "OBP-35025: The Consent's status cannot be updated." val ConsentUserCannotBeAdded = "OBP-35026: The Consent's User cannot be added." val ConsentUserAuthContextCannotBeAdded = "OBP-35027: The Consent's User Auth Context cannot be added." + val ConsentRequestNotFound = "OBP-35028: Consent Request not found by CONSENT_REQUEST_ID. " + val ConsentRequestAlreadyUsed = "OBP-35029: The CONSENT_REQUEST_ID is used to create Consent. " //Authorisations val AuthorisationNotFound = "OBP-36001: Authorisation not found. Please specify valid values for PAYMENT_ID and AUTHORISATION_ID. " diff --git a/obp-api/src/main/scala/code/api/util/ExampleValue.scala b/obp-api/src/main/scala/code/api/util/ExampleValue.scala index c5a9fe57e..41caf7d9a 100644 --- a/obp-api/src/main/scala/code/api/util/ExampleValue.scala +++ b/obp-api/src/main/scala/code/api/util/ExampleValue.scala @@ -1456,7 +1456,35 @@ object ExampleValue { glossaryItems += makeGlossaryItem("direct_debit_id", directDebitIdExample) lazy val consentIdExample = ConnectorField(NoExampleProvided,NoDescriptionProvided) - glossaryItems += makeGlossaryItem("consent_id", consentIdExample) + glossaryItems += makeGlossaryItem("consent_id", consentIdExample) + + lazy val consentRequestPayloadExample = ConnectorField( + """{ + | "everything": false, + | "account_access": [ + | { + | "account_routing": { + | "scheme": "AccountNumber", + | "address": "4930396" + | }, + | "view_id": "owner" + | } + | ], + | "phone_number": "+44 07972 444 876", + | "valid_from": "2022-06-14T12:42:00Z", + | "time_to_live": 3600 + |}""".stripMargin, + "The whole create consent request json body." + ) + + glossaryItems += makeGlossaryItem("payload", consentRequestPayloadExample) + + lazy val consentRequestIdExample = ConnectorField ( + "8ca8a7e4-6d02-40e3-a129-0b2bf89de9f0", + s"A string that MUST uniquely identify the Consent Request on this OBP instance." + ) + + glossaryItems += makeGlossaryItem("consent_request_id", consentRequestIdExample) lazy val line2Example = ConnectorField(NoExampleProvided,NoDescriptionProvided) glossaryItems += makeGlossaryItem("line2", line2Example) diff --git a/obp-api/src/main/scala/code/api/util/Glossary.scala b/obp-api/src/main/scala/code/api/util/Glossary.scala index 78df8507a..8a2fb3d01 100644 --- a/obp-api/src/main/scala/code/api/util/Glossary.scala +++ b/obp-api/src/main/scala/code/api/util/Glossary.scala @@ -993,7 +993,7 @@ object Glossary extends MdcLoggable { | |Body: | - | { "legal_name":"Eveline Tripman", "mobile_phone_number":"+44 07972 444 876", "email":"eveline@example.com", "face_image":{ "url":"www.openbankproject", "date":"2017-09-19T00:00:00Z" }, "date_of_birth":"2017-09-19T00:00:00Z", "relationship_status":"single", "dependants":10, "dob_of_dependants":["2017-09-19T00:00:00Z"], "credit_rating":{ "rating":"OBP", "source":"OBP" }, "credit_limit":{ "currency":"EUR", "amount":"10" }, "highest_education_attained":"Master", "employment_status":"worker", "kyc_status":true, "last_ok_date":"2017-09-19T00:00:00Z", "title":"Dr.", "branch_id":"DERBY6", "name_suffix":"Sr"} + | { "legal_name":"Eveline Tripman", "mobile_phone_number":"+44 07972 444 876", "email":"eveline@example.com", "face_image":{ "url":"www.openbankproject", "date":"1100-01-01T00:00:00Z" }, "date_of_birth":"1100-01-01T00:00:00Z", "relationship_status":"single", "dependants":10, "dob_of_dependants":["1100-01-01T00:00:00Z"], "credit_rating":{ "rating":"OBP", "source":"OBP" }, "credit_limit":{ "currency":"EUR", "amount":"10" }, "highest_education_attained":"Master", "employment_status":"worker", "kyc_status":true, "last_ok_date":"1100-01-01T00:00:00Z", "title":"Dr.", "branch_id":"DERBY6", "name_suffix":"Sr"} | |Headers: | diff --git a/obp-api/src/main/scala/code/api/util/I18NUtil.scala b/obp-api/src/main/scala/code/api/util/I18NUtil.scala index f2d2d2a32..414ef4da9 100644 --- a/obp-api/src/main/scala/code/api/util/I18NUtil.scala +++ b/obp-api/src/main/scala/code/api/util/I18NUtil.scala @@ -2,6 +2,8 @@ package code.api.util import java.util.{Date, Locale} +import code.webuiprops.MappedWebUiPropsProvider.getWebUiPropsValue +import com.openbankproject.commons.model.enums.LanguageParam import net.liftweb.http.S import net.liftweb.util.Props @@ -31,5 +33,27 @@ object I18NUtil { case Array(lang, country) => new Locale(lang, country) case Array(lang, country, variant) => new Locale(lang, country, variant) } + + object ResourceDocTranslation { + def summary(operationId: String, language: Option[LanguageParam], default: String): String = { + language match { + case Some(value) => + val webUiKey = s"webui_resource_doc_operation_id_${operationId}_summary_${value}" + getWebUiPropsValue(webUiKey, default) + case None => + default + } + } + def description(operationId: String, language: Option[LanguageParam], default: String): String = { + language match { + case Some(value) => + val webUiKey = s"webui_resource_doc_operation_id_${operationId}_description_${value}" + getWebUiPropsValue(webUiKey, default) + case None => + default + } + } + } + } diff --git a/obp-api/src/main/scala/code/api/util/JwsUtil.scala b/obp-api/src/main/scala/code/api/util/JwsUtil.scala index 6f68a99a0..b6324357f 100644 --- a/obp-api/src/main/scala/code/api/util/JwsUtil.scala +++ b/obp-api/src/main/scala/code/api/util/JwsUtil.scala @@ -148,6 +148,7 @@ object JwsUtil extends MdcLoggable { val pathOfStandard = HashMap( "BGv1.3"->"berlin-group/v1.3", "OBPv4.0.0"->"obp/v4.0.0", + "OBPv5.0.0"->"obp/v5.0.0", "OBPv3.1.0"->"obp/v3.1.0", "UKv1.3"->"open-banking/v3.1" ).withDefaultValue("{Not found any standard to match}") diff --git a/obp-api/src/main/scala/code/api/util/migration/Migration.scala b/obp-api/src/main/scala/code/api/util/migration/Migration.scala index 54f9e117e..7bd8c1b25 100644 --- a/obp-api/src/main/scala/code/api/util/migration/Migration.scala +++ b/obp-api/src/main/scala/code/api/util/migration/Migration.scala @@ -64,15 +64,15 @@ object Migration extends MdcLoggable { addAccountAccessConsumerId() populateTableViewDefinition() populateTableAccountAccess() - generateAndPopulateMissingCustomerUUIDs() - generateAndPopulateMissingConsumersUUIDs() + generateAndPopulateMissingCustomerUUIDs(startedBeforeSchemifier) + generateAndPopulateMissingConsumersUUIDs(startedBeforeSchemifier) populateTableRateLimiting() updateTableViewDefinition() bankAccountHoldersAndOwnerViewAccessInfo() alterTableMappedConsent() alterColumnChallengeAtTableMappedConsent() alterTableOpenIDConnectToken() - alterTableMappedUserAuthContext() + alterTableMappedUserAuthContext(startedBeforeSchemifier) alterTableMappedUserAuthContextUpdate() populateNameAndAppTypeFieldsAtConsumerTable() populateAzpAndSubFieldsAtConsumerTable() @@ -80,7 +80,7 @@ object Migration extends MdcLoggable { populateSettlementBankAccounts() alterColumnStatusAtTableMappedConsent() alterColumnDetailsAtTableTransactionRequest() - deleteDuplicatedRowsInTheTableUserAuthContext() + deleteDuplicatedRowsInTheTableUserAuthContext(startedBeforeSchemifier) populateTheFieldDeletedAtResourceUser(startedBeforeSchemifier) populateTheFieldIsActiveAtProductAttribute(startedBeforeSchemifier) alterColumnUsernameProviderFirstnameAndLastnameAtAuthUser(startedBeforeSchemifier) @@ -94,6 +94,7 @@ object Migration extends MdcLoggable { alterWebhookColumnUrlLength() dropConsentAuthContextDropIndex() alterMappedExpectedChallengeAnswerChallengeTypeLength() + alterTransactionRequestChallengeChallengeTypeLength() } private def dummyScript(): Boolean = { @@ -123,36 +124,47 @@ object Migration extends MdcLoggable { } } - private def generateAndPopulateMissingCustomerUUIDs(): Boolean = { - val name = nameOf(generateAndPopulateMissingCustomerUUIDs) - runOnce(name) { - val startDate = System.currentTimeMillis() - val commitId: String = APIUtil.gitCommit - val isSuccessful = CustomerX.customerProvider.vend.populateMissingUUIDs() - val endDate = System.currentTimeMillis() + private def generateAndPopulateMissingCustomerUUIDs(startedBeforeSchemifier: Boolean): Boolean = { + if(startedBeforeSchemifier == true) { + logger.warn(s"Migration.database.generateAndPopulateMissingCustomerUUIDs(true) cannot be run before Schemifier.") + true + } else { + val name = nameOf(generateAndPopulateMissingCustomerUUIDs(startedBeforeSchemifier)) + runOnce(name) { + val startDate = System.currentTimeMillis() + val commitId: String = APIUtil.gitCommit + val isSuccessful = CustomerX.customerProvider.vend.populateMissingUUIDs() + val endDate = System.currentTimeMillis() - val comment: String = - s"""Execute `generateAndPopulateMissingCustomerUUIDs` - |Duration: ${endDate - startDate} ms; + val comment: String = + s"""Execute `generateAndPopulateMissingCustomerUUIDs` + |Duration: ${endDate - startDate} ms; """.stripMargin - saveLog(name, commitId, isSuccessful, startDate, endDate, comment) - isSuccessful + saveLog(name, commitId, isSuccessful, startDate, endDate, comment) + isSuccessful + } } + } - private def generateAndPopulateMissingConsumersUUIDs(): Boolean = { - val name = nameOf(generateAndPopulateMissingConsumersUUIDs) - runOnce(name) { - val startDate = System.currentTimeMillis() - val commitId: String = APIUtil.gitCommit - val isSuccessful = Consumers.consumers.vend.populateMissingUUIDs() - val endDate = System.currentTimeMillis() - val comment: String = - s"""Execute `generateAndPopulateMissingConsumersUUIDs` - |Duration: ${endDate - startDate} ms; + private def generateAndPopulateMissingConsumersUUIDs(startedBeforeSchemifier: Boolean): Boolean = { + if(startedBeforeSchemifier == true) { + logger.warn(s"Migration.database.generateAndPopulateMissingConsumersUUIDs(true) cannot be run before Schemifier.") + true + } else { + val name = nameOf(generateAndPopulateMissingConsumersUUIDs(startedBeforeSchemifier)) + runOnce(name) { + val startDate = System.currentTimeMillis() + val commitId: String = APIUtil.gitCommit + val isSuccessful = Consumers.consumers.vend.populateMissingUUIDs() + val endDate = System.currentTimeMillis() + val comment: String = + s"""Execute `generateAndPopulateMissingConsumersUUIDs` + |Duration: ${endDate - startDate} ms; """.stripMargin - saveLog(name, commitId, isSuccessful, startDate, endDate, comment) - isSuccessful + saveLog(name, commitId, isSuccessful, startDate, endDate, comment) + isSuccessful + } } } @@ -207,10 +219,15 @@ object Migration extends MdcLoggable { MigrationOfConsumer.populateAzpAndSub(name) } } - private def alterTableMappedUserAuthContext(): Boolean = { - val name = nameOf(alterTableMappedUserAuthContext) - runOnce(name) { - MigrationOfMappedUserAuthContext.dropUniqueIndex(name) + private def alterTableMappedUserAuthContext(startedBeforeSchemifier: Boolean): Boolean = { + if(startedBeforeSchemifier == true) { + logger.warn(s"Migration.database.alterTableMappedUserAuthContext(true) cannot be run before Schemifier.") + true + } else { + val name = nameOf(alterTableMappedUserAuthContext(startedBeforeSchemifier)) + runOnce(name) { + MigrationOfMappedUserAuthContext.dropUniqueIndex(name) + } } } private def alterTableMappedUserAuthContextUpdate(): Boolean = { @@ -243,10 +260,15 @@ object Migration extends MdcLoggable { MigrationOfTransactionRequerst.alterColumnDetails(name) } } - private def deleteDuplicatedRowsInTheTableUserAuthContext(): Boolean = { - val name = nameOf(deleteDuplicatedRowsInTheTableUserAuthContext) - runOnce(name) { - MigrationOfUserAuthContext.removeDuplicates(name) + private def deleteDuplicatedRowsInTheTableUserAuthContext(startedBeforeSchemifier: Boolean): Boolean = { + if(startedBeforeSchemifier == true) { + logger.warn(s"Migration.database.deleteDuplicatedRowsInTheTableUserAuthContext(true) cannot be run before Schemifier.") + true + } else { + val name = nameOf(deleteDuplicatedRowsInTheTableUserAuthContext(startedBeforeSchemifier)) + runOnce(name) { + MigrationOfUserAuthContext.removeDuplicates(name) + } } } private def populateTheFieldDeletedAtResourceUser(startedBeforeSchemifier: Boolean): Boolean = { @@ -386,6 +408,13 @@ object Migration extends MdcLoggable { } } + private def alterTransactionRequestChallengeChallengeTypeLength(): Boolean = { + val name = nameOf(alterTransactionRequestChallengeChallengeTypeLength) + runOnce(name) { + MigrationOfTransactionRequestChallengeChallengeTypeLength.alterColumnChallengeChallengeTypeLength(name) + } + } + } /** diff --git a/obp-api/src/main/scala/code/api/util/migration/MigrationOfConsentAuthContextDropIndex.scala b/obp-api/src/main/scala/code/api/util/migration/MigrationOfConsentAuthContextDropIndex.scala index 1b421bcb0..95ff17449 100644 --- a/obp-api/src/main/scala/code/api/util/migration/MigrationOfConsentAuthContextDropIndex.scala +++ b/obp-api/src/main/scala/code/api/util/migration/MigrationOfConsentAuthContextDropIndex.scala @@ -8,10 +8,11 @@ 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} +import code.api.Constant + object MigrationOfConsentAuthContextDropIndex { val oneDayAgo = ZonedDateTime.now(ZoneId.of("UTC")).minusDays(1) @@ -19,7 +20,7 @@ object MigrationOfConsentAuthContextDropIndex { 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 dbUrl = APIUtil.getPropsValue("db.url") openOr Constant.h2DatabaseDefaultUrlValue 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) diff --git a/obp-api/src/main/scala/code/api/util/migration/MigrationOfTransactionRequestChallengeChallengeTypeLength.scala b/obp-api/src/main/scala/code/api/util/migration/MigrationOfTransactionRequestChallengeChallengeTypeLength.scala new file mode 100644 index 000000000..62ef7dbb3 --- /dev/null +++ b/obp-api/src/main/scala/code/api/util/migration/MigrationOfTransactionRequestChallengeChallengeTypeLength.scala @@ -0,0 +1,57 @@ +package code.api.util.migration + +import code.api.util.APIUtil +import code.api.util.migration.Migration.{DbFunction, saveLog} +import code.transactionrequests.MappedTransactionRequest +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 MigrationOfTransactionRequestChallengeChallengeTypeLength { + + 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 alterColumnChallengeChallengeTypeLength(name: String): Boolean = { + DbFunction.tableExists(MappedTransactionRequest, (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 mappedtransactionrequest ALTER COLUMN mChallenge_ChallengeType varchar(100);" + case _ => + () => "ALTER TABLE mappedtransactionrequest ALTER COLUMN mChallenge_ChallengeType TYPE character varying(100);" + } + } + + 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"""${MappedTransactionRequest._dbTableNameLC} table does not exist""".stripMargin + saveLog(name, commitId, isSuccessful, startDate, endDate, comment) + isSuccessful + } + } + +} diff --git a/obp-api/src/main/scala/code/api/util/migration/MigrationOfUserAuthContext.scala b/obp-api/src/main/scala/code/api/util/migration/MigrationOfUserAuthContext.scala index 18146714b..1c5d67eb5 100644 --- a/obp-api/src/main/scala/code/api/util/migration/MigrationOfUserAuthContext.scala +++ b/obp-api/src/main/scala/code/api/util/migration/MigrationOfUserAuthContext.scala @@ -3,6 +3,7 @@ package code.api.util.migration import java.time.format.DateTimeFormatter import java.time.{ZoneId, ZonedDateTime} +import code.api.Constant import code.api.util.APIUtil import code.api.util.migration.Migration.{DbFunction, saveLog} import code.context.MappedUserAuthContext @@ -20,7 +21,7 @@ object MigrationOfUserAuthContext { 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 dbUrl = APIUtil.getPropsValue("db.url") openOr Constant.h2DatabaseDefaultUrlValue 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) @@ -51,6 +52,8 @@ object MigrationOfUserAuthContext { // Make back up DbFunction.makeBackUpOfTable(MappedUserAuthContext) + MappedUserAuthContext.findAll() + val startDate = System.currentTimeMillis() val commitId: String = APIUtil.gitCommit diff --git a/obp-api/src/main/scala/code/api/v1_4_0/JSONFactory1_4_0.scala b/obp-api/src/main/scala/code/api/v1_4_0/JSONFactory1_4_0.scala index 60e4ec7c3..a5cfd3dde 100644 --- a/obp-api/src/main/scala/code/api/v1_4_0/JSONFactory1_4_0.scala +++ b/obp-api/src/main/scala/code/api/v1_4_0/JSONFactory1_4_0.scala @@ -1,12 +1,12 @@ package code.api.v1_4_0 import code.api.berlin.group.v1_3.JvalueCaseClass - import java.util.Date + import code.api.util.APIUtil.{EmptyBody, PrimaryDataBody, ResourceDoc} import code.api.util.ApiTag.ResourceDocTag import code.api.util.Glossary.glossaryItems -import code.api.util.{APIUtil, ApiRole, ConnectorField, CustomJsonFormats, ExampleValue, PegdownOptions} +import code.api.util.{APIUtil, ApiRole, ConnectorField, CustomJsonFormats, ExampleValue, I18NUtil, PegdownOptions} import code.bankconnectors.LocalMappedConnector.getAllEndpointTagsBox import com.openbankproject.commons.model.ListResult import code.crm.CrmEvent.CrmEvent @@ -21,10 +21,11 @@ import net.liftweb.json.JsonAST.{JArray, JBool, JNothing, JObject, JValue} import net.liftweb.util.StringHelpers import code.util.Helper.MdcLoggable import org.apache.commons.lang3.StringUtils - import java.util.concurrent.ConcurrentHashMap import java.util.regex.Pattern +import com.openbankproject.commons.model.enums.LanguageParam + object JSONFactory1_4_0 extends MdcLoggable{ implicit def formats: Formats = CustomJsonFormats.formats case class PostCustomerJson( @@ -452,7 +453,7 @@ object JSONFactory1_4_0 extends MdcLoggable{ private val createResourceDocJsonMemo = new ConcurrentHashMap[ResourceDoc, ResourceDocJson] - def createResourceDocJson(rd: ResourceDoc, isVersion4OrHigher:Boolean) : ResourceDocJson = { + def createResourceDocJson(rd: ResourceDoc, isVersion4OrHigher:Boolean, languageParam: Option[LanguageParam]) : ResourceDocJson = { // if this calculate conversion already happened before, just return that value // if not calculated before, just do conversion val endpointTags = getAllEndpointTagsBox(rd.operationId).map(endpointTag =>ResourceDocTag(endpointTag.tagName)) @@ -489,13 +490,20 @@ object JSONFactory1_4_0 extends MdcLoggable{ urlParametersDescription ++ exampleRequestBodyFieldsDescription ++ responseFieldsDescription } - val description = resourceDocUpdatedTags.description.stripMargin.trim ++ fieldsDescription + val resourceDocDescription = I18NUtil.ResourceDocTranslation.description( + resourceDocUpdatedTags.operationId, + languageParam, + resourceDocUpdatedTags.description.stripMargin.trim + ) + val description = resourceDocDescription ++ fieldsDescription + val summary = resourceDocUpdatedTags.summary.replaceFirst("""\.(\s*)$""", "$1") // remove the ending dot in summary + val translatedSummary = I18NUtil.ResourceDocTranslation.summary(resourceDocUpdatedTags.operationId, languageParam, summary) ResourceDocJson( operation_id = resourceDocUpdatedTags.operationId, request_verb = resourceDocUpdatedTags.requestVerb, request_url = resourceDocUpdatedTags.requestUrl, - summary = resourceDocUpdatedTags.summary.replaceFirst("""\.(\s*)$""", "$1"), // remove the ending dot in summary + summary = translatedSummary, // Strip the margin character (|) and line breaks and convert from markdown to html description = PegdownOptions.convertPegdownToHtmlTweaked(description), //.replaceAll("\n", ""), description_markdown = description, @@ -516,14 +524,14 @@ object JSONFactory1_4_0 extends MdcLoggable{ }) } - def createResourceDocsJson(resourceDocList: List[ResourceDoc], isVersion4OrHigher:Boolean) : ResourceDocsJson = { + def createResourceDocsJson(resourceDocList: List[ResourceDoc], isVersion4OrHigher:Boolean, languageParam: Option[LanguageParam]) : ResourceDocsJson = { if(isVersion4OrHigher){ ResourceDocsJson( - resourceDocList.map(createResourceDocJson(_,isVersion4OrHigher)), + resourceDocList.map(createResourceDocJson(_,isVersion4OrHigher, languageParam)), meta=Some(ResourceDocMeta(new Date(), resourceDocList.length)) ) } else { - ResourceDocsJson(resourceDocList.map(createResourceDocJson(_,false))) + ResourceDocsJson(resourceDocList.map(createResourceDocJson(_,false, languageParam))) } } diff --git a/obp-api/src/main/scala/code/api/v2_0_0/APIMethods200.scala b/obp-api/src/main/scala/code/api/v2_0_0/APIMethods200.scala index 144dd07a7..87b606327 100644 --- a/obp-api/src/main/scala/code/api/v2_0_0/APIMethods200.scala +++ b/obp-api/src/main/scala/code/api/v2_0_0/APIMethods200.scala @@ -1477,7 +1477,7 @@ trait APIMethods200 { cc => for { postedData <- tryo {json.extract[CreateUserJson]} ?~! ErrorMessages.InvalidJsonFormat - _ <- tryo(assert(validatePasswordOnCreation(postedData.password))) ?~! ErrorMessages.InvalidStrongPasswordFormat + _ <- tryo(assert(fullPasswordValidation(postedData.password))) ?~! ErrorMessages.InvalidStrongPasswordFormat } yield { if (AuthUser.find(By(AuthUser.username, postedData.username)).isEmpty) { val userCreated = AuthUser.create @@ -1511,180 +1511,180 @@ trait APIMethods200 { - resourceDocs += ResourceDoc( - createMeeting, - apiVersion, - "createMeeting", - "POST", - "/banks/BANK_ID/meetings", - "Create Meeting (video conference/call)", - """Create Meeting: Initiate a video conference/call with the bank. - | - |The Meetings resource contains meta data about video/other conference sessions, not the video/audio/chat itself. - | - |The actual conferencing is handled by external providers. Currently OBP supports tokbox video conferences (WIP). - | - |This is not a recomendation of tokbox per se. - | - |provider_id determines the provider of the meeting / video chat service. MUST be url friendly (no spaces). - | - |purpose_id explains the purpose of the chat. onboarding | mortgage | complaint etc. MUST be url friendly (no spaces). - | - |Login is required. - | - |This call is **experimental**. Currently staff_user_id is not set. Further calls will be needed to correctly set this. - """.stripMargin, - CreateMeetingJson("tokbox", "onboarding"), - meetingJson, - List( - UserNotLoggedIn, - MeetingApiKeyNotConfigured, - MeetingApiSecretNotConfigured, - InvalidBankIdFormat, - BankNotFound, - InvalidJsonFormat, - MeetingsNotSupported, - UnknownError - ), - List(apiTagMeeting, apiTagCustomer, apiTagExperimental)) - - - lazy val createMeeting: OBPEndpoint = { - case "banks" :: BankId(bankId) :: "meetings" :: Nil JsonPost json -> _ => { - cc => - if (APIUtil.getPropsAsBoolValue("meeting.tokbox_enabled", false)) { - for { - // TODO use these keys to get session and tokens from tokbox - _ <- APIUtil.getPropsValue("meeting.tokbox_api_key") ~> APIFailure(MeetingApiKeyNotConfigured, 403) - _ <- APIUtil.getPropsValue("meeting.tokbox_api_secret") ~> APIFailure(MeetingApiSecretNotConfigured, 403) - u <- cc.user ?~! UserNotLoggedIn - _ <- tryo(assert(isValidID(bankId.value)))?~! InvalidBankIdFormat - (bank, callContext) <- BankX(bankId, Some(cc)) ?~! BankNotFound - postedData <- tryo {json.extract[CreateMeetingJson]} ?~! InvalidJsonFormat - now = Calendar.getInstance().getTime() - sessionId <- tryo{code.opentok.OpenTokUtil.getSession.getSessionId()} - customerToken <- tryo{code.opentok.OpenTokUtil.generateTokenForPublisher(60)} - staffToken <- tryo{code.opentok.OpenTokUtil.generateTokenForModerator(60)} - meeting <- Meetings.meetingProvider.vend.createMeeting(bank.bankId, u, u, postedData.provider_id, postedData.purpose_id, now, sessionId, customerToken, staffToken - ,null,null)//These two are used from V310 - } yield { - // Format the data as V2.0.0 json - val json = JSONFactory200.createMeetingJSON(meeting) - successJsonResponse(Extraction.decompose(json), 201) - } - } else { - Full(errorJsonResponse(MeetingsNotSupported)) - } - } - } - - - resourceDocs += ResourceDoc( - getMeetings, - apiVersion, - "getMeetings", - "GET", - "/banks/BANK_ID/meetings", - "Get Meetings", - """Meetings contain meta data about, and are used to facilitate, video conferences / chats etc. - | - |The actual conference/chats are handled by external services. - | - |Login is required. - | - |This call is **experimental** and will require further authorisation in the future. - """.stripMargin, - emptyObjectJson, - meetingsJson, - List( - UserNotLoggedIn, - MeetingApiKeyNotConfigured, - MeetingApiSecretNotConfigured, - BankNotFound, - MeetingsNotSupported, - UnknownError), - List(apiTagMeeting, apiTagCustomer, apiTagExperimental)) - - - lazy val getMeetings: OBPEndpoint = { - case "banks" :: BankId(bankId) :: "meetings" :: Nil JsonGet _ => { - cc => - if (APIUtil.getPropsAsBoolValue("meeting.tokbox_enabled", false)) { - for { - _ <- cc.user ?~! ErrorMessages.UserNotLoggedIn - (bank, callContext ) <- BankX(bankId, Some(cc)) ?~! BankNotFound - _ <- APIUtil.getPropsValue("meeting.tokbox_api_key") ~> APIFailure(ErrorMessages.MeetingApiKeyNotConfigured, 403) - _ <- APIUtil.getPropsValue("meeting.tokbox_api_secret") ~> APIFailure(ErrorMessages.MeetingApiSecretNotConfigured, 403) - u <- cc.user ?~! ErrorMessages.UserNotLoggedIn - (bank, callContext) <- BankX(bankId, Some(cc)) ?~! BankNotFound - // now = Calendar.getInstance().getTime() - meetings <- Meetings.meetingProvider.vend.getMeetings(bank.bankId, u) - } - yield { - // Format the data as V2.0.0 json - val json = JSONFactory200.createMeetingJSONs(meetings) - successJsonResponse(Extraction.decompose(json)) - } - } else { - Full(errorJsonResponse(MeetingsNotSupported)) - } - } - } - - - - resourceDocs += ResourceDoc( - getMeeting, - apiVersion, - "getMeeting", - "GET", - "/banks/BANK_ID/meetings/MEETING_ID", - "Get Meeting", - """Get Meeting specified by BANK_ID / MEETING_ID - |Meetings contain meta data about, and are used to facilitate, video conferences / chats etc. - | - |The actual conference/chats are handled by external services. - | - |Login is required. - | - |This call is **experimental** and will require further authorisation in the future. - """.stripMargin, - emptyObjectJson, - meetingJson, - List( - UserNotLoggedIn, - BankNotFound, - MeetingApiKeyNotConfigured, - MeetingApiSecretNotConfigured, - MeetingNotFound, - MeetingsNotSupported, - UnknownError - ), - List(apiTagMeeting, apiTagKyc, apiTagCustomer, apiTagExperimental)) - - - lazy val getMeeting: OBPEndpoint = { - case "banks" :: BankId(bankId) :: "meetings" :: meetingId :: Nil JsonGet _ => { - cc => - if (APIUtil.getPropsAsBoolValue("meeting.tokbox_enabled", false)) { - for { - u <- cc.user ?~! UserNotLoggedIn - (bank, callContext ) <- BankX(bankId, Some(cc)) ?~! BankNotFound - _ <- APIUtil.getPropsValue("meeting.tokbox_api_key") ~> APIFailure(ErrorMessages.MeetingApiKeyNotConfigured, 403) - _ <- APIUtil.getPropsValue("meeting.tokbox_api_secret") ~> APIFailure(ErrorMessages.MeetingApiSecretNotConfigured, 403) - (bank, callContext) <- BankX(bankId, Some(cc)) ?~! BankNotFound - meeting <- Meetings.meetingProvider.vend.getMeeting(bank.bankId, u, meetingId) ?~! {ErrorMessages.MeetingNotFound} - } - yield { - // Format the data as V2.0.0 json - val json = JSONFactory200.createMeetingJSON(meeting) - successJsonResponse(Extraction.decompose(json)) - } - } else { - Full(errorJsonResponse(ErrorMessages.MeetingsNotSupported)) - } - } - } +// resourceDocs += ResourceDoc( +// createMeeting, +// apiVersion, +// "createMeeting", +// "POST", +// "/banks/BANK_ID/meetings", +// "Create Meeting (video conference/call)", +// """Create Meeting: Initiate a video conference/call with the bank. +// | +// |The Meetings resource contains meta data about video/other conference sessions, not the video/audio/chat itself. +// | +// |The actual conferencing is handled by external providers. Currently OBP supports tokbox video conferences (WIP). +// | +// |This is not a recomendation of tokbox per se. +// | +// |provider_id determines the provider of the meeting / video chat service. MUST be url friendly (no spaces). +// | +// |purpose_id explains the purpose of the chat. onboarding | mortgage | complaint etc. MUST be url friendly (no spaces). +// | +// |Login is required. +// | +// |This call is **experimental**. Currently staff_user_id is not set. Further calls will be needed to correctly set this. +// """.stripMargin, +// CreateMeetingJson("tokbox", "onboarding"), +// meetingJson, +// List( +// UserNotLoggedIn, +// MeetingApiKeyNotConfigured, +// MeetingApiSecretNotConfigured, +// InvalidBankIdFormat, +// BankNotFound, +// InvalidJsonFormat, +// MeetingsNotSupported, +// UnknownError +// ), +// List(apiTagMeeting, apiTagCustomer, apiTagExperimental)) +// +// +// lazy val createMeeting: OBPEndpoint = { +// case "banks" :: BankId(bankId) :: "meetings" :: Nil JsonPost json -> _ => { +// cc => +// if (APIUtil.getPropsAsBoolValue("meeting.tokbox_enabled", false)) { +// for { +// // TODO use these keys to get session and tokens from tokbox +// _ <- APIUtil.getPropsValue("meeting.tokbox_api_key") ~> APIFailure(MeetingApiKeyNotConfigured, 403) +// _ <- APIUtil.getPropsValue("meeting.tokbox_api_secret") ~> APIFailure(MeetingApiSecretNotConfigured, 403) +// u <- cc.user ?~! UserNotLoggedIn +// _ <- tryo(assert(isValidID(bankId.value)))?~! InvalidBankIdFormat +// (bank, callContext) <- BankX(bankId, Some(cc)) ?~! BankNotFound +// postedData <- tryo {json.extract[CreateMeetingJson]} ?~! InvalidJsonFormat +// now = Calendar.getInstance().getTime() +// sessionId <- tryo{code.opentok.OpenTokUtil.getSession.getSessionId()} +// customerToken <- tryo{code.opentok.OpenTokUtil.generateTokenForPublisher(60)} +// staffToken <- tryo{code.opentok.OpenTokUtil.generateTokenForModerator(60)} +// meeting <- Meetings.meetingProvider.vend.createMeeting(bank.bankId, u, u, postedData.provider_id, postedData.purpose_id, now, sessionId, customerToken, staffToken +// ,null,null)//These two are used from V310 +// } yield { +// // Format the data as V2.0.0 json +// val json = JSONFactory200.createMeetingJSON(meeting) +// successJsonResponse(Extraction.decompose(json), 201) +// } +// } else { +// Full(errorJsonResponse(MeetingsNotSupported)) +// } +// } +// } +// +// +// resourceDocs += ResourceDoc( +// getMeetings, +// apiVersion, +// "getMeetings", +// "GET", +// "/banks/BANK_ID/meetings", +// "Get Meetings", +// """Meetings contain meta data about, and are used to facilitate, video conferences / chats etc. +// | +// |The actual conference/chats are handled by external services. +// | +// |Login is required. +// | +// |This call is **experimental** and will require further authorisation in the future. +// """.stripMargin, +// emptyObjectJson, +// meetingsJson, +// List( +// UserNotLoggedIn, +// MeetingApiKeyNotConfigured, +// MeetingApiSecretNotConfigured, +// BankNotFound, +// MeetingsNotSupported, +// UnknownError), +// List(apiTagMeeting, apiTagCustomer, apiTagExperimental)) +// +// +// lazy val getMeetings: OBPEndpoint = { +// case "banks" :: BankId(bankId) :: "meetings" :: Nil JsonGet _ => { +// cc => +// if (APIUtil.getPropsAsBoolValue("meeting.tokbox_enabled", false)) { +// for { +// _ <- cc.user ?~! ErrorMessages.UserNotLoggedIn +// (bank, callContext ) <- BankX(bankId, Some(cc)) ?~! BankNotFound +// _ <- APIUtil.getPropsValue("meeting.tokbox_api_key") ~> APIFailure(ErrorMessages.MeetingApiKeyNotConfigured, 403) +// _ <- APIUtil.getPropsValue("meeting.tokbox_api_secret") ~> APIFailure(ErrorMessages.MeetingApiSecretNotConfigured, 403) +// u <- cc.user ?~! ErrorMessages.UserNotLoggedIn +// (bank, callContext) <- BankX(bankId, Some(cc)) ?~! BankNotFound +// // now = Calendar.getInstance().getTime() +// meetings <- Meetings.meetingProvider.vend.getMeetings(bank.bankId, u) +// } +// yield { +// // Format the data as V2.0.0 json +// val json = JSONFactory200.createMeetingJSONs(meetings) +// successJsonResponse(Extraction.decompose(json)) +// } +// } else { +// Full(errorJsonResponse(MeetingsNotSupported)) +// } +// } +// } +// +// +// +// resourceDocs += ResourceDoc( +// getMeeting, +// apiVersion, +// "getMeeting", +// "GET", +// "/banks/BANK_ID/meetings/MEETING_ID", +// "Get Meeting", +// """Get Meeting specified by BANK_ID / MEETING_ID +// |Meetings contain meta data about, and are used to facilitate, video conferences / chats etc. +// | +// |The actual conference/chats are handled by external services. +// | +// |Login is required. +// | +// |This call is **experimental** and will require further authorisation in the future. +// """.stripMargin, +// emptyObjectJson, +// meetingJson, +// List( +// UserNotLoggedIn, +// BankNotFound, +// MeetingApiKeyNotConfigured, +// MeetingApiSecretNotConfigured, +// MeetingNotFound, +// MeetingsNotSupported, +// UnknownError +// ), +// List(apiTagMeeting, apiTagKyc, apiTagCustomer, apiTagExperimental)) +// +// +// lazy val getMeeting: OBPEndpoint = { +// case "banks" :: BankId(bankId) :: "meetings" :: meetingId :: Nil JsonGet _ => { +// cc => +// if (APIUtil.getPropsAsBoolValue("meeting.tokbox_enabled", false)) { +// for { +// u <- cc.user ?~! UserNotLoggedIn +// (bank, callContext ) <- BankX(bankId, Some(cc)) ?~! BankNotFound +// _ <- APIUtil.getPropsValue("meeting.tokbox_api_key") ~> APIFailure(ErrorMessages.MeetingApiKeyNotConfigured, 403) +// _ <- APIUtil.getPropsValue("meeting.tokbox_api_secret") ~> APIFailure(ErrorMessages.MeetingApiSecretNotConfigured, 403) +// (bank, callContext) <- BankX(bankId, Some(cc)) ?~! BankNotFound +// meeting <- Meetings.meetingProvider.vend.getMeeting(bank.bankId, u, meetingId) ?~! {ErrorMessages.MeetingNotFound} +// } +// yield { +// // Format the data as V2.0.0 json +// val json = JSONFactory200.createMeetingJSON(meeting) +// successJsonResponse(Extraction.decompose(json)) +// } +// } else { +// Full(errorJsonResponse(ErrorMessages.MeetingsNotSupported)) +// } +// } +// } resourceDocs += ResourceDoc( diff --git a/obp-api/src/main/scala/code/api/v2_0_0/OBPAPI2_0_0.scala b/obp-api/src/main/scala/code/api/v2_0_0/OBPAPI2_0_0.scala index 06d203b23..5577684bc 100644 --- a/obp-api/src/main/scala/code/api/v2_0_0/OBPAPI2_0_0.scala +++ b/obp-api/src/main/scala/code/api/v2_0_0/OBPAPI2_0_0.scala @@ -169,9 +169,9 @@ object OBPAPI2_0_0 extends OBPRestHelper with APIMethods130 with APIMethods140 w Implementations2_0_0.createAccount, Implementations2_0_0.getTransactionTypes, Implementations2_0_0.createUser, - Implementations2_0_0.createMeeting, - Implementations2_0_0.getMeetings, - Implementations2_0_0.getMeeting, +// Implementations2_0_0.createMeeting, +// Implementations2_0_0.getMeetings, +// Implementations2_0_0.getMeeting, Implementations2_0_0.createCustomer, Implementations2_0_0.getCurrentUser, Implementations2_0_0.getUser, diff --git a/obp-api/src/main/scala/code/api/v2_1_0/OBPAPI2_1_0.scala b/obp-api/src/main/scala/code/api/v2_1_0/OBPAPI2_1_0.scala index d16e0981c..68452255f 100644 --- a/obp-api/src/main/scala/code/api/v2_1_0/OBPAPI2_1_0.scala +++ b/obp-api/src/main/scala/code/api/v2_1_0/OBPAPI2_1_0.scala @@ -140,7 +140,7 @@ object OBPAPI2_1_0 extends OBPRestHelper with APIMethods130 with APIMethods140 w Implementations2_0_0.addSocialMediaHandle :: Implementations2_0_0.getPrivateAccountsAtOneBank :: Implementations2_0_0.createAccount :: - Implementations2_0_0.createMeeting :: +// Implementations2_0_0.createMeeting :: Implementations2_0_0.createUser :: Implementations2_0_0.createUserCustomerLinks :: Implementations2_0_0.deleteEntitlement :: @@ -155,8 +155,8 @@ object OBPAPI2_1_0 extends OBPRestHelper with APIMethods130 with APIMethods140 w Implementations2_0_0.getKycDocuments :: Implementations2_0_0.getKycMedia :: Implementations2_0_0.getKycStatuses :: - Implementations2_0_0.getMeeting :: - Implementations2_0_0.getMeetings :: +// Implementations2_0_0.getMeeting :: +// Implementations2_0_0.getMeetings :: Implementations2_0_0.getPermissionForUserForBankAccount :: Implementations2_0_0.getPermissionsForBankAccount :: Implementations2_0_0.getSocialMediaHandles :: diff --git a/obp-api/src/main/scala/code/api/v2_2_0/OBPAPI2_2_0.scala b/obp-api/src/main/scala/code/api/v2_2_0/OBPAPI2_2_0.scala index 499f91e38..cff08269e 100644 --- a/obp-api/src/main/scala/code/api/v2_2_0/OBPAPI2_2_0.scala +++ b/obp-api/src/main/scala/code/api/v2_2_0/OBPAPI2_2_0.scala @@ -115,7 +115,7 @@ object OBPAPI2_2_0 extends OBPRestHelper with APIMethods130 with APIMethods140 w Implementations2_0_0.getPrivateAccountsAtOneBank :: //now in V220 //Implementations2_0_0.createAccount :: - Implementations2_0_0.createMeeting :: +// Implementations2_0_0.createMeeting :: Implementations2_0_0.createUser :: Implementations2_0_0.createUserCustomerLinks :: Implementations2_0_0.deleteEntitlement :: @@ -130,8 +130,8 @@ object OBPAPI2_2_0 extends OBPRestHelper with APIMethods130 with APIMethods140 w Implementations2_0_0.getKycDocuments :: Implementations2_0_0.getKycMedia :: Implementations2_0_0.getKycStatuses :: - Implementations2_0_0.getMeeting :: - Implementations2_0_0.getMeetings :: +// Implementations2_0_0.getMeeting :: +// Implementations2_0_0.getMeetings :: Implementations2_0_0.getPermissionForUserForBankAccount :: Implementations2_0_0.getPermissionsForBankAccount :: Implementations2_0_0.getSocialMediaHandles :: diff --git a/obp-api/src/main/scala/code/api/v3_0_0/OBPAPI3_0_0.scala b/obp-api/src/main/scala/code/api/v3_0_0/OBPAPI3_0_0.scala index 3e267fad4..0b653aef6 100644 --- a/obp-api/src/main/scala/code/api/v3_0_0/OBPAPI3_0_0.scala +++ b/obp-api/src/main/scala/code/api/v3_0_0/OBPAPI3_0_0.scala @@ -151,7 +151,7 @@ object OBPAPI3_0_0 extends OBPRestHelper with APIMethods130 with APIMethods140 w Implementations2_0_0.getPrivateAccountsAtOneBank :: //now in V220 //Implementations2_0_0.createAccount :: - Implementations2_0_0.createMeeting :: +// Implementations2_0_0.createMeeting :: Implementations2_0_0.createUser :: Implementations2_0_0.createUserCustomerLinks :: Implementations2_0_0.deleteEntitlement :: @@ -166,8 +166,8 @@ object OBPAPI3_0_0 extends OBPRestHelper with APIMethods130 with APIMethods140 w Implementations2_0_0.getKycDocuments :: Implementations2_0_0.getKycMedia :: Implementations2_0_0.getKycStatuses :: - Implementations2_0_0.getMeeting :: - Implementations2_0_0.getMeetings :: +// Implementations2_0_0.getMeeting :: +// Implementations2_0_0.getMeetings :: Implementations2_0_0.getPermissionsForBankAccount :: Implementations2_0_0.getSocialMediaHandles :: Implementations2_0_0.getTransactionTypes :: diff --git a/obp-api/src/main/scala/code/api/v3_1_0/APIMethods310.scala b/obp-api/src/main/scala/code/api/v3_1_0/APIMethods310.scala index b9ac5cda9..1db102a4f 100644 --- a/obp-api/src/main/scala/code/api/v3_1_0/APIMethods310.scala +++ b/obp-api/src/main/scala/code/api/v3_1_0/APIMethods310.scala @@ -3,7 +3,6 @@ package code.api.v3_1_0 import java.text.SimpleDateFormat import java.util.UUID import java.util.regex.Pattern - import code.api.ResourceDocs1_4_0.SwaggerDefinitionsJSON._ import code.api.ResourceDocs1_4_0.{MessageDocsSwaggerDefinitions, ResourceDocsAPIMethodsUtil, SwaggerDefinitionsJSON, SwaggerJSONFactory} import code.api.util.APIUtil.{getWebUIPropsPairs, _} @@ -22,7 +21,7 @@ import code.api.v3_0_0.JSONFactory300.createAdapterInfoJson import code.api.v3_1_0.JSONFactory310._ import code.bankconnectors.rest.RestConnector_vMar2019 import code.bankconnectors.{Connector, LocalMappedConnector} -import code.consent.{ConsentStatus, Consents} +import code.consent.{ConsentRequests, ConsentStatus, Consents} import code.consumer.Consumers import code.context.UserAuthContextUpdateProvider import code.entitlement.Entitlement @@ -1425,7 +1424,7 @@ trait APIMethods310 { json.extract[PostUserAuthContextJson] } (user, callContext) <- NewStyle.function.findByUserId(userId, callContext) - (userAuthContext, callContext) <- NewStyle.function.createUserAuthContext(user, postedData.key, postedData.value, callContext) + (userAuthContext, callContext) <- NewStyle.function.createUserAuthContext(user, postedData.key.trim, postedData.value.trim, callContext) } yield { (JSONFactory310.createUserAuthContextJson(userAuthContext), HttpCode.`201`(callContext)) } @@ -3507,11 +3506,13 @@ trait APIMethods310 { } case None => Future(None, "Any application") } + + challengeAnswer = Props.mode match { case Props.RunModes.Test => Consent.challengeAnswerAtTestEnvironment case _ => Random.nextInt(99999999).toString() } - createdConsent <- Future(Consents.consentProvider.vend.createConsent(user, challengeAnswer)) map { + createdConsent <- Future(Consents.consentProvider.vend.createObpConsent(user, challengeAnswer, None)) map { i => connectorEmptyResponse(i, callContext) } consentJWT = diff --git a/obp-api/src/main/scala/code/api/v3_1_0/JSONFactory3.1.0.scala b/obp-api/src/main/scala/code/api/v3_1_0/JSONFactory3.1.0.scala index 8e521a29a..2682f551a 100644 --- a/obp-api/src/main/scala/code/api/v3_1_0/JSONFactory3.1.0.scala +++ b/obp-api/src/main/scala/code/api/v3_1_0/JSONFactory3.1.0.scala @@ -529,6 +529,7 @@ case class PostConsentBodyCommonJson( views: List[PostConsentViewJsonV310], entitlements: List[PostConsentEntitlementJsonV310], consumer_id: Option[String], + consent_request_id: Option[String], valid_from: Option[Date], time_to_live: Option[Long] ) extends PostConsentCommonBody diff --git a/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala b/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala index f8ed37700..d0031dd83 100644 --- a/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala +++ b/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala @@ -3,6 +3,7 @@ package code.api.v4_0_0 import java.net.URLEncoder import java.text.SimpleDateFormat import java.util.{Calendar, Date} + import code.DynamicData.{DynamicData, DynamicDataProvider} import code.DynamicEndpoint.DynamicEndpointSwagger import code.accountattribute.AccountAttributeX @@ -42,7 +43,7 @@ import code.apicollectionendpoint.MappedApiCollectionEndpointsProvider import code.authtypevalidation.JsonAuthTypeValidation import code.bankconnectors.{Connector, DynamicConnector, InternalConnector} import code.connectormethod.{JsonConnectorMethod, JsonConnectorMethodMethodBody} -import code.consent.{ConsentStatus, Consents} +import code.consent.{ConsentRequests, ConsentStatus, Consents} import code.dynamicEntity.{DynamicEntityCommons, ReferenceType} import code.dynamicMessageDoc.JsonDynamicMessageDoc import code.dynamicResourceDoc.JsonDynamicResourceDoc @@ -909,379 +910,421 @@ trait APIMethods400 { Some(List(canCreateAnyTransactionRequest))) - // Different Transaction Request approaches: - lazy val createTransactionRequestAccount = createTransactionRequest - lazy val createTransactionRequestAccountOtp = createTransactionRequest - lazy val createTransactionRequestSepa = createTransactionRequest - lazy val createTransactionRequestCounterparty = createTransactionRequest - lazy val createTransactionRequestRefund = createTransactionRequest - lazy val createTransactionRequestFreeForm = createTransactionRequest - lazy val createTransactionRequestSimple = createTransactionRequest + def createTransactionRequest(bankId: BankId, accountId: AccountId, viewId: ViewId, transactionRequestType: TransactionRequestType, json: JValue): Future[(TransactionRequestWithChargeJSON400, Option[CallContext])] = { + for { + (Full(u), fromAccount, callContext) <- SS.userAccount + _ <- NewStyle.function.isEnabledTransactionRequests(callContext) + _ <- Helper.booleanToFuture(InvalidAccountIdFormat, cc=callContext) { + isValidID(accountId.value) + } + _ <- Helper.booleanToFuture(InvalidBankIdFormat, cc=callContext) { + isValidID(bankId.value) + } - // This handles the above cases - lazy val createTransactionRequest: OBPEndpoint = { - case "banks" :: BankId(bankId) :: "accounts" :: AccountId(accountId) :: ViewId(viewId) :: "transaction-request-types" :: - TransactionRequestType(transactionRequestType) :: "transaction-requests" :: Nil JsonPost json -> _ => { - cc => - for { - (Full(u), fromAccount, callContext) <- SS.userAccount - _ <- NewStyle.function.isEnabledTransactionRequests(callContext) - _ <- Helper.booleanToFuture(InvalidAccountIdFormat, cc=callContext) { - isValidID(accountId.value) - } - _ <- Helper.booleanToFuture(InvalidBankIdFormat, cc=callContext) { - isValidID(bankId.value) - } + account = BankIdAccountId(bankId, accountId) + _ <- NewStyle.function.checkAuthorisationToCreateTransactionRequest(viewId, account, u, callContext) - account = BankIdAccountId(bankId, accountId) - _ <- NewStyle.function.checkAuthorisationToCreateTransactionRequest(viewId, account, u, callContext) + _ <- Helper.booleanToFuture(s"${InvalidTransactionRequestType}: '${transactionRequestType.value}'. Current Sandbox does not support it. ", cc=callContext) { + APIUtil.getPropsValue("transactionRequests_supported_types", "").split(",").contains(transactionRequestType.value) + } - _ <- Helper.booleanToFuture(s"${InvalidTransactionRequestType}: '${transactionRequestType.value}'. Current Sandbox does not support it. ", cc=callContext) { - APIUtil.getPropsValue("transactionRequests_supported_types", "").split(",").contains(transactionRequestType.value) - } + transactionRequestTypeValue <- NewStyle.function.tryons(s"$InvalidTransactionRequestType: '${transactionRequestType.value}'. OBP does not support it.", 400, callContext) { + TransactionRequestTypes.withName(transactionRequestType.value) + } - transactionRequestTypeValue <- NewStyle.function.tryons(s"$InvalidTransactionRequestType: '${transactionRequestType.value}'. OBP does not support it.", 400, callContext) { - TransactionRequestTypes.withName(transactionRequestType.value) - } + // Check the input JSON format, here is just check the common parts of all four types + transDetailsJson <- NewStyle.function.tryons(s"$InvalidJsonFormat The Json body should be the $TransactionRequestBodyCommonJSON ", 400, callContext) { + json.extract[TransactionRequestBodyCommonJSON] + } - // Check the input JSON format, here is just check the common parts of all four types - transDetailsJson <- NewStyle.function.tryons(s"$InvalidJsonFormat The Json body should be the $TransactionRequestBodyCommonJSON ", 400, callContext) { - json.extract[TransactionRequestBodyCommonJSON] - } + transactionAmountNumber <- NewStyle.function.tryons(s"$InvalidNumber Current input is ${transDetailsJson.value.amount} ", 400, callContext) { + BigDecimal(transDetailsJson.value.amount) + } - transactionAmountNumber <- NewStyle.function.tryons(s"$InvalidNumber Current input is ${transDetailsJson.value.amount} ", 400, callContext) { - BigDecimal(transDetailsJson.value.amount) - } + _ <- Helper.booleanToFuture(s"${NotPositiveAmount} Current input is: '${transactionAmountNumber}'", cc=callContext) { + transactionAmountNumber > BigDecimal("0") + } - _ <- Helper.booleanToFuture(s"${NotPositiveAmount} Current input is: '${transactionAmountNumber}'", cc=callContext) { - transactionAmountNumber > BigDecimal("0") - } + _ <- Helper.booleanToFuture(s"${InvalidISOCurrencyCode} Current input is: '${transDetailsJson.value.currency}'", cc=callContext) { + isValidCurrencyISOCode(transDetailsJson.value.currency) + } - _ <- Helper.booleanToFuture(s"${InvalidISOCurrencyCode} Current input is: '${transDetailsJson.value.currency}'", cc=callContext) { - isValidCurrencyISOCode(transDetailsJson.value.currency) - } + // Prevent default value for transaction request type (at least). + _ <- Helper.booleanToFuture(s"${InvalidISOCurrencyCode} Current input is: '${transDetailsJson.value.currency}'", cc=callContext) { + isValidCurrencyISOCode(transDetailsJson.value.currency) + } - // Prevent default value for transaction request type (at least). - _ <- Helper.booleanToFuture(s"${InvalidISOCurrencyCode} Current input is: '${transDetailsJson.value.currency}'", cc=callContext) { - isValidCurrencyISOCode(transDetailsJson.value.currency) - } + (createdTransactionRequest, callContext) <- transactionRequestTypeValue match { + case REFUND => { + for { + transactionRequestBodyRefundJson <- NewStyle.function.tryons(s"${InvalidJsonFormat}, it should be $ACCOUNT json format", 400, callContext) { + json.extract[TransactionRequestBodyRefundJsonV400] + } - (createdTransactionRequest, callContext) <- transactionRequestTypeValue match { - case REFUND => { - for { - transactionRequestBodyRefundJson <- NewStyle.function.tryons(s"${InvalidJsonFormat}, it should be $ACCOUNT json format", 400, callContext) { - json.extract[TransactionRequestBodyRefundJsonV400] - } + transactionId = TransactionId(transactionRequestBodyRefundJson.refund.transaction_id) - transactionId = TransactionId(transactionRequestBodyRefundJson.refund.transaction_id) + (fromAccount, toAccount, transaction, callContext) <- transactionRequestBodyRefundJson.to match { + case Some(refundRequestTo) if refundRequestTo.account_id.isDefined && refundRequestTo.bank_id.isDefined => + val toBankId = BankId(refundRequestTo.bank_id.get) + val toAccountId = AccountId(refundRequestTo.account_id.get) + for { + (transaction, callContext) <- NewStyle.function.getTransaction(fromAccount.bankId, fromAccount.accountId, transactionId, callContext) + (toAccount, callContext) <- NewStyle.function.checkBankAccountExists(toBankId, toAccountId, callContext) + } yield (fromAccount, toAccount, transaction, callContext) - (fromAccount, toAccount, transaction, callContext) <- transactionRequestBodyRefundJson.to match { - case Some(refundRequestTo) if refundRequestTo.account_id.isDefined && refundRequestTo.bank_id.isDefined => - val toBankId = BankId(refundRequestTo.bank_id.get) - val toAccountId = AccountId(refundRequestTo.account_id.get) - for { - (transaction, callContext) <- NewStyle.function.getTransaction(fromAccount.bankId, fromAccount.accountId, transactionId, callContext) - (toAccount, callContext) <- NewStyle.function.checkBankAccountExists(toBankId, toAccountId, callContext) - } yield (fromAccount, toAccount, transaction, callContext) + case Some(refundRequestTo) if refundRequestTo.counterparty_id.isDefined => + val toCounterpartyId = CounterpartyId(refundRequestTo.counterparty_id.get) + for { + (toCounterparty, callContext) <- NewStyle.function.getCounterpartyByCounterpartyId(toCounterpartyId, callContext) + toAccount <- NewStyle.function.getBankAccountFromCounterparty(toCounterparty, isOutgoingAccount = true, callContext) + _ <- Helper.booleanToFuture(s"$CounterpartyBeneficiaryPermit", cc=callContext) { + toCounterparty.isBeneficiary + } + (transaction, callContext) <- NewStyle.function.getTransaction(fromAccount.bankId, fromAccount.accountId, transactionId, callContext) + } yield (fromAccount, toAccount, transaction, callContext) - case Some(refundRequestTo) if refundRequestTo.counterparty_id.isDefined => - val toCounterpartyId = CounterpartyId(refundRequestTo.counterparty_id.get) - for { - (toCounterparty, callContext) <- NewStyle.function.getCounterpartyByCounterpartyId(toCounterpartyId, callContext) - toAccount <- NewStyle.function.getBankAccountFromCounterparty(toCounterparty, isOutgoingAccount = true, callContext) - _ <- Helper.booleanToFuture(s"$CounterpartyBeneficiaryPermit", cc=callContext) { - toCounterparty.isBeneficiary - } - (transaction, callContext) <- NewStyle.function.getTransaction(fromAccount.bankId, fromAccount.accountId, transactionId, callContext) - } yield (fromAccount, toAccount, transaction, callContext) + case None if transactionRequestBodyRefundJson.from.isDefined => + val fromCounterpartyId = CounterpartyId(transactionRequestBodyRefundJson.from.get.counterparty_id) + val toAccount = fromAccount + for { + (fromCounterparty, callContext) <- NewStyle.function.getCounterpartyByCounterpartyId(fromCounterpartyId, callContext) + fromAccount <- NewStyle.function.getBankAccountFromCounterparty(fromCounterparty, isOutgoingAccount = false, callContext) + _ <- Helper.booleanToFuture(s"$CounterpartyBeneficiaryPermit", cc=callContext) { + fromCounterparty.isBeneficiary + } + (transaction, callContext) <- NewStyle.function.getTransaction(toAccount.bankId, toAccount.accountId, transactionId, callContext) + } yield (fromAccount, toAccount, transaction, callContext) + } - case None if transactionRequestBodyRefundJson.from.isDefined => - val fromCounterpartyId = CounterpartyId(transactionRequestBodyRefundJson.from.get.counterparty_id) - val toAccount = fromAccount - for { - (fromCounterparty, callContext) <- NewStyle.function.getCounterpartyByCounterpartyId(fromCounterpartyId, callContext) - fromAccount <- NewStyle.function.getBankAccountFromCounterparty(fromCounterparty, isOutgoingAccount = false, callContext) - _ <- Helper.booleanToFuture(s"$CounterpartyBeneficiaryPermit", cc=callContext) { - fromCounterparty.isBeneficiary - } - (transaction, callContext) <- NewStyle.function.getTransaction(toAccount.bankId, toAccount.accountId, transactionId, callContext) - } yield (fromAccount, toAccount, transaction, callContext) - } + transDetailsSerialized <- NewStyle.function.tryons(UnknownError, 400, callContext) { + write(transactionRequestBodyRefundJson)(Serialization.formats(NoTypeHints)) + } - transDetailsSerialized <- NewStyle.function.tryons(UnknownError, 400, callContext) { - write(transactionRequestBodyRefundJson)(Serialization.formats(NoTypeHints)) - } + _ <- Helper.booleanToFuture(s"${RefundedTransaction} Current input amount is: '${transDetailsJson.value.amount}'. It can not be more than the original amount(${(transaction.amount).abs})", cc=callContext) { + (transaction.amount).abs >= transactionAmountNumber + } + //TODO, we need additional field to guarantee the transaction is refunded... + // _ <- Helper.booleanToFuture(s"${RefundedTransaction}") { + // !((transaction.description.toString contains(" Refund to ")) && (transaction.description.toString contains(" and transaction_id("))) + // } - _ <- Helper.booleanToFuture(s"${RefundedTransaction} Current input amount is: '${transDetailsJson.value.amount}'. It can not be more than the original amount(${(transaction.amount).abs})", cc=callContext) { - (transaction.amount).abs >= transactionAmountNumber - } - //TODO, we need additional field to guarantee the transaction is refunded... -// _ <- Helper.booleanToFuture(s"${RefundedTransaction}") { -// !((transaction.description.toString contains(" Refund to ")) && (transaction.description.toString contains(" and transaction_id("))) -// } + //we add the extra info (counterparty name + transaction_id) for this special Refund endpoint. + newDescription = s"${transactionRequestBodyRefundJson.description} - Refund for transaction_id: (${transactionId.value}) to ${transaction.otherAccount.counterpartyName}" - //we add the extra info (counterparty name + transaction_id) for this special Refund endpoint. - newDescription = s"${transactionRequestBodyRefundJson.description} - Refund for transaction_id: (${transactionId.value}) to ${transaction.otherAccount.counterpartyName}" + //This is the refund endpoint, the original fromAccount is the `toAccount` which will receive money. + refundToAccount = fromAccount + //This is the refund endpoint, the original toAccount is the `fromAccount` which will lose money. + refundFromAccount = toAccount - //This is the refund endpoint, the original fromAccount is the `toAccount` which will receive money. - refundToAccount = fromAccount - //This is the refund endpoint, the original toAccount is the `fromAccount` which will lose money. - refundFromAccount = toAccount + (createdTransactionRequest, callContext) <- NewStyle.function.createTransactionRequestv400(u, + viewId, + refundFromAccount, + refundToAccount, + transactionRequestType, + transactionRequestBodyRefundJson.copy(description = newDescription), + transDetailsSerialized, + sharedChargePolicy.toString, + Some(OBP_TRANSACTION_REQUEST_CHALLENGE), + getScaMethodAtInstance(transactionRequestType.value).toOption, + None, + None, + callContext) //in ACCOUNT, ChargePolicy set default "SHARED" - (createdTransactionRequest, callContext) <- NewStyle.function.createTransactionRequestv400(u, - viewId, - refundFromAccount, - refundToAccount, - transactionRequestType, - transactionRequestBodyRefundJson.copy(description = newDescription), - transDetailsSerialized, - sharedChargePolicy.toString, - Some(OBP_TRANSACTION_REQUEST_CHALLENGE), - getScaMethodAtInstance(transactionRequestType.value).toOption, - None, - None, - callContext) //in ACCOUNT, ChargePolicy set default "SHARED" + _ <- NewStyle.function.createOrUpdateTransactionRequestAttribute( + bankId = bankId, + transactionRequestId = createdTransactionRequest.id, + transactionRequestAttributeId = None, + name = "original_transaction_id", + attributeType = TransactionRequestAttributeType.withName("STRING"), + value = transactionId.value, + callContext = callContext + ) - _ <- NewStyle.function.createOrUpdateTransactionRequestAttribute( + refundReasonCode = transactionRequestBodyRefundJson.refund.reason_code + _ <- if (refundReasonCode.nonEmpty) { + NewStyle.function.createOrUpdateTransactionRequestAttribute( bankId = bankId, transactionRequestId = createdTransactionRequest.id, transactionRequestAttributeId = None, - name = "original_transaction_id", + name = "refund_reason_code", attributeType = TransactionRequestAttributeType.withName("STRING"), - value = transactionId.value, - callContext = callContext - ) + value = refundReasonCode, + callContext = callContext) + } else Future.successful() - refundReasonCode = transactionRequestBodyRefundJson.refund.reason_code - _ <- if (refundReasonCode.nonEmpty) { - NewStyle.function.createOrUpdateTransactionRequestAttribute( - bankId = bankId, - transactionRequestId = createdTransactionRequest.id, - transactionRequestAttributeId = None, - name = "refund_reason_code", - attributeType = TransactionRequestAttributeType.withName("STRING"), - value = refundReasonCode, - callContext = callContext) - } else Future.successful() + (newTransactionRequestStatus, callContext) <- NewStyle.function.notifyTransactionRequest(refundFromAccount, refundToAccount, createdTransactionRequest, callContext) + _ <- Future(Connector.connector.vend.saveTransactionRequestStatusImpl(createdTransactionRequest.id, newTransactionRequestStatus.toString)) + createdTransactionRequest <- Future(createdTransactionRequest.copy(status = newTransactionRequestStatus.toString)) - (newTransactionRequestStatus, callContext) <- NewStyle.function.notifyTransactionRequest(refundFromAccount, refundToAccount, createdTransactionRequest, callContext) - _ <- Future(Connector.connector.vend.saveTransactionRequestStatusImpl(createdTransactionRequest.id, newTransactionRequestStatus.toString)) - createdTransactionRequest <- Future(createdTransactionRequest.copy(status = newTransactionRequestStatus.toString)) - - } yield (createdTransactionRequest, callContext) - } - case ACCOUNT | SANDBOX_TAN => { - for { - transactionRequestBodySandboxTan <- NewStyle.function.tryons(s"${InvalidJsonFormat}, it should be $ACCOUNT json format", 400, callContext) { - json.extract[TransactionRequestBodySandBoxTanJSON] - } - - toBankId = BankId(transactionRequestBodySandboxTan.to.bank_id) - toAccountId = AccountId(transactionRequestBodySandboxTan.to.account_id) - (toAccount, callContext) <- NewStyle.function.checkBankAccountExists(toBankId, toAccountId, callContext) - - transDetailsSerialized <- NewStyle.function.tryons(UnknownError, 400, callContext) { - write(transactionRequestBodySandboxTan)(Serialization.formats(NoTypeHints)) - } - - (createdTransactionRequest, callContext) <- NewStyle.function.createTransactionRequestv400(u, - viewId, - fromAccount, - toAccount, - transactionRequestType, - transactionRequestBodySandboxTan, - transDetailsSerialized, - sharedChargePolicy.toString, - Some(OBP_TRANSACTION_REQUEST_CHALLENGE), - getScaMethodAtInstance(transactionRequestType.value).toOption, - None, - None, - callContext) //in ACCOUNT, ChargePolicy set default "SHARED" - } yield (createdTransactionRequest, callContext) - } - case ACCOUNT_OTP => { - for { - transactionRequestBodySandboxTan <- NewStyle.function.tryons(s"${InvalidJsonFormat}, it should be $ACCOUNT json format", 400, callContext) { - json.extract[TransactionRequestBodySandBoxTanJSON] - } - - toBankId = BankId(transactionRequestBodySandboxTan.to.bank_id) - toAccountId = AccountId(transactionRequestBodySandboxTan.to.account_id) - (toAccount, callContext) <- NewStyle.function.checkBankAccountExists(toBankId, toAccountId, callContext) - - transDetailsSerialized <- NewStyle.function.tryons(UnknownError, 400, callContext) { - write(transactionRequestBodySandboxTan)(Serialization.formats(NoTypeHints)) - } - - (createdTransactionRequest, callContext) <- NewStyle.function.createTransactionRequestv400(u, - viewId, - fromAccount, - toAccount, - transactionRequestType, - transactionRequestBodySandboxTan, - transDetailsSerialized, - sharedChargePolicy.toString, - Some(OBP_TRANSACTION_REQUEST_CHALLENGE), - getScaMethodAtInstance(transactionRequestType.value).toOption, - None, - None, - callContext) //in ACCOUNT, ChargePolicy set default "SHARED" - } yield (createdTransactionRequest, callContext) - } - case COUNTERPARTY => { - for { - //For COUNTERPARTY, Use the counterpartyId to find the toCounterparty and set up the toAccount - transactionRequestBodyCounterparty <- NewStyle.function.tryons(s"${InvalidJsonFormat}, it should be $COUNTERPARTY json format", 400, callContext) { - json.extract[TransactionRequestBodyCounterpartyJSON] - } - toCounterpartyId = transactionRequestBodyCounterparty.to.counterparty_id - (toCounterparty, callContext) <- NewStyle.function.getCounterpartyByCounterpartyId(CounterpartyId(toCounterpartyId), callContext) - toAccount <- NewStyle.function.getBankAccountFromCounterparty(toCounterparty, true, callContext) - // Check we can send money to it. - _ <- Helper.booleanToFuture(s"$CounterpartyBeneficiaryPermit", cc=callContext) { - toCounterparty.isBeneficiary - } - chargePolicy = transactionRequestBodyCounterparty.charge_policy - _ <- Helper.booleanToFuture(s"$InvalidChargePolicy", cc=callContext) { - ChargePolicy.values.contains(ChargePolicy.withName(chargePolicy)) - } - transDetailsSerialized <- NewStyle.function.tryons(UnknownError, 400, callContext) { - write(transactionRequestBodyCounterparty)(Serialization.formats(NoTypeHints)) - } - (createdTransactionRequest, callContext) <- NewStyle.function.createTransactionRequestv400(u, - viewId, - fromAccount, - toAccount, - transactionRequestType, - transactionRequestBodyCounterparty, - transDetailsSerialized, - chargePolicy, - Some(OBP_TRANSACTION_REQUEST_CHALLENGE), - getScaMethodAtInstance(transactionRequestType.value).toOption, - None, - None, - callContext) - } yield (createdTransactionRequest, callContext) - - } - case SIMPLE => { - for { - //For SAMPLE, we will create/get toCounterparty on site and set up the toAccount - transactionRequestBodySimple <- NewStyle.function.tryons(s"${InvalidJsonFormat}, it should be $SIMPLE json format", 400, callContext) { - json.extract[TransactionRequestBodySimpleJsonV400] - } - (toCounterparty, callContext) <- NewStyle.function.getOrCreateCounterparty( - name = transactionRequestBodySimple.to.name, - description = transactionRequestBodySimple.to.description, - currency = transactionRequestBodySimple.value.currency, - createdByUserId = u.userId, - thisBankId = bankId.value, - thisAccountId = accountId.value, - thisViewId = viewId.value, - otherBankRoutingScheme = transactionRequestBodySimple.to.other_bank_routing_scheme, - otherBankRoutingAddress = transactionRequestBodySimple.to.other_bank_routing_address, - otherBranchRoutingScheme = transactionRequestBodySimple.to.other_branch_routing_scheme, - otherBranchRoutingAddress = transactionRequestBodySimple.to.other_branch_routing_address, - otherAccountRoutingScheme = transactionRequestBodySimple.to.other_account_routing_scheme, - otherAccountRoutingAddress = transactionRequestBodySimple.to.other_account_routing_address, - otherAccountSecondaryRoutingScheme = transactionRequestBodySimple.to.other_account_secondary_routing_scheme, - otherAccountSecondaryRoutingAddress = transactionRequestBodySimple.to.other_account_secondary_routing_address, - callContext: Option[CallContext], - ) - toAccount <- NewStyle.function.getBankAccountFromCounterparty(toCounterparty, true, callContext) - // Check we can send money to it. - _ <- Helper.booleanToFuture(s"$CounterpartyBeneficiaryPermit", cc=callContext) { - toCounterparty.isBeneficiary - } - chargePolicy = transactionRequestBodySimple.charge_policy - _ <- Helper.booleanToFuture(s"$InvalidChargePolicy", cc=callContext) { - ChargePolicy.values.contains(ChargePolicy.withName(chargePolicy)) - } - transDetailsSerialized <- NewStyle.function.tryons(UnknownError, 400, callContext) { - write(transactionRequestBodySimple)(Serialization.formats(NoTypeHints)) - } - (createdTransactionRequest, callContext) <- NewStyle.function.createTransactionRequestv400(u, - viewId, - fromAccount, - toAccount, - transactionRequestType, - transactionRequestBodySimple, - transDetailsSerialized, - chargePolicy, - Some(OBP_TRANSACTION_REQUEST_CHALLENGE), - getScaMethodAtInstance(transactionRequestType.value).toOption, - None, - None, - callContext) - } yield (createdTransactionRequest, callContext) - - } - case SEPA => { - for { - //For SEPA, Use the iban to find the toCounterparty and set up the toAccount - transDetailsSEPAJson <- NewStyle.function.tryons(s"${InvalidJsonFormat}, it should be $SEPA json format", 400, callContext) { - json.extract[TransactionRequestBodySEPAJsonV400] - } - toIban = transDetailsSEPAJson.to.iban - (toCounterparty, callContext) <- NewStyle.function.getCounterpartyByIbanAndBankAccountId(toIban, fromAccount.bankId, fromAccount.accountId, callContext) - toAccount <- NewStyle.function.getBankAccountFromCounterparty(toCounterparty, true, callContext) - _ <- Helper.booleanToFuture(s"$CounterpartyBeneficiaryPermit", cc=callContext) { - toCounterparty.isBeneficiary - } - chargePolicy = transDetailsSEPAJson.charge_policy - _ <- Helper.booleanToFuture(s"$InvalidChargePolicy", cc=callContext) { - ChargePolicy.values.contains(ChargePolicy.withName(chargePolicy)) - } - transDetailsSerialized <- NewStyle.function.tryons(UnknownError, 400, callContext) { - write(transDetailsSEPAJson)(Serialization.formats(NoTypeHints)) - } - (createdTransactionRequest, callContext) <- NewStyle.function.createTransactionRequestv400(u, - viewId, - fromAccount, - toAccount, - transactionRequestType, - transDetailsSEPAJson, - transDetailsSerialized, - chargePolicy, - Some(OBP_TRANSACTION_REQUEST_CHALLENGE), - getScaMethodAtInstance(transactionRequestType.value).toOption, - transDetailsSEPAJson.reasons.map(_.map(_.transform)), - None, - callContext) - } yield (createdTransactionRequest, callContext) - } - case FREE_FORM => { - for { - transactionRequestBodyFreeForm <- NewStyle.function.tryons(s"${InvalidJsonFormat}, it should be $FREE_FORM json format", 400, callContext) { - json.extract[TransactionRequestBodyFreeFormJSON] - } - // Following lines: just transfer the details body, add Bank_Id and Account_Id in the Detail part. This is for persistence and 'answerTransactionRequestChallenge' - transactionRequestAccountJSON = TransactionRequestAccountJsonV140(bankId.value, accountId.value) - transDetailsSerialized <- NewStyle.function.tryons(UnknownError, 400, callContext) { - write(transactionRequestBodyFreeForm)(Serialization.formats(NoTypeHints)) - } - (createdTransactionRequest, callContext) <- NewStyle.function.createTransactionRequestv400(u, - viewId, - fromAccount, - fromAccount, - transactionRequestType, - transactionRequestBodyFreeForm, - transDetailsSerialized, - sharedChargePolicy.toString, - Some(OBP_TRANSACTION_REQUEST_CHALLENGE), - getScaMethodAtInstance(transactionRequestType.value).toOption, - None, - None, - callContext) - } yield - (createdTransactionRequest, callContext) - } + } yield (createdTransactionRequest, callContext) + } + case ACCOUNT | SANDBOX_TAN => { + for { + transactionRequestBodySandboxTan <- NewStyle.function.tryons(s"${InvalidJsonFormat}, it should be $ACCOUNT json format", 400, callContext) { + json.extract[TransactionRequestBodySandBoxTanJSON] + } + + toBankId = BankId(transactionRequestBodySandboxTan.to.bank_id) + toAccountId = AccountId(transactionRequestBodySandboxTan.to.account_id) + (toAccount, callContext) <- NewStyle.function.checkBankAccountExists(toBankId, toAccountId, callContext) + + transDetailsSerialized <- NewStyle.function.tryons(UnknownError, 400, callContext) { + write(transactionRequestBodySandboxTan)(Serialization.formats(NoTypeHints)) + } + + (createdTransactionRequest, callContext) <- NewStyle.function.createTransactionRequestv400(u, + viewId, + fromAccount, + toAccount, + transactionRequestType, + transactionRequestBodySandboxTan, + transDetailsSerialized, + sharedChargePolicy.toString, + Some(OBP_TRANSACTION_REQUEST_CHALLENGE), + getScaMethodAtInstance(transactionRequestType.value).toOption, + None, + None, + callContext) //in ACCOUNT, ChargePolicy set default "SHARED" + } yield (createdTransactionRequest, callContext) + } + case ACCOUNT_OTP => { + for { + transactionRequestBodySandboxTan <- NewStyle.function.tryons(s"${InvalidJsonFormat}, it should be $ACCOUNT json format", 400, callContext) { + json.extract[TransactionRequestBodySandBoxTanJSON] + } + + toBankId = BankId(transactionRequestBodySandboxTan.to.bank_id) + toAccountId = AccountId(transactionRequestBodySandboxTan.to.account_id) + (toAccount, callContext) <- NewStyle.function.checkBankAccountExists(toBankId, toAccountId, callContext) + + transDetailsSerialized <- NewStyle.function.tryons(UnknownError, 400, callContext) { + write(transactionRequestBodySandboxTan)(Serialization.formats(NoTypeHints)) + } + + (createdTransactionRequest, callContext) <- NewStyle.function.createTransactionRequestv400(u, + viewId, + fromAccount, + toAccount, + transactionRequestType, + transactionRequestBodySandboxTan, + transDetailsSerialized, + sharedChargePolicy.toString, + Some(OBP_TRANSACTION_REQUEST_CHALLENGE), + getScaMethodAtInstance(transactionRequestType.value).toOption, + None, + None, + callContext) //in ACCOUNT, ChargePolicy set default "SHARED" + } yield (createdTransactionRequest, callContext) + } + case COUNTERPARTY => { + for { + //For COUNTERPARTY, Use the counterpartyId to find the toCounterparty and set up the toAccount + transactionRequestBodyCounterparty <- NewStyle.function.tryons(s"${InvalidJsonFormat}, it should be $COUNTERPARTY json format", 400, callContext) { + json.extract[TransactionRequestBodyCounterpartyJSON] + } + toCounterpartyId = transactionRequestBodyCounterparty.to.counterparty_id + (toCounterparty, callContext) <- NewStyle.function.getCounterpartyByCounterpartyId(CounterpartyId(toCounterpartyId), callContext) + toAccount <- NewStyle.function.getBankAccountFromCounterparty(toCounterparty, true, callContext) + // Check we can send money to it. + _ <- Helper.booleanToFuture(s"$CounterpartyBeneficiaryPermit", cc=callContext) { + toCounterparty.isBeneficiary + } + chargePolicy = transactionRequestBodyCounterparty.charge_policy + _ <- Helper.booleanToFuture(s"$InvalidChargePolicy", cc=callContext) { + ChargePolicy.values.contains(ChargePolicy.withName(chargePolicy)) + } + transDetailsSerialized <- NewStyle.function.tryons(UnknownError, 400, callContext) { + write(transactionRequestBodyCounterparty)(Serialization.formats(NoTypeHints)) + } + (createdTransactionRequest, callContext) <- NewStyle.function.createTransactionRequestv400(u, + viewId, + fromAccount, + toAccount, + transactionRequestType, + transactionRequestBodyCounterparty, + transDetailsSerialized, + chargePolicy, + Some(OBP_TRANSACTION_REQUEST_CHALLENGE), + getScaMethodAtInstance(transactionRequestType.value).toOption, + None, + None, + callContext) + } yield (createdTransactionRequest, callContext) + + } + case SIMPLE => { + for { + //For SAMPLE, we will create/get toCounterparty on site and set up the toAccount + transactionRequestBodySimple <- NewStyle.function.tryons(s"${InvalidJsonFormat}, it should be $SIMPLE json format", 400, callContext) { + json.extract[TransactionRequestBodySimpleJsonV400] + } + (toCounterparty, callContext) <- NewStyle.function.getOrCreateCounterparty( + name = transactionRequestBodySimple.to.name, + description = transactionRequestBodySimple.to.description, + currency = transactionRequestBodySimple.value.currency, + createdByUserId = u.userId, + thisBankId = bankId.value, + thisAccountId = accountId.value, + thisViewId = viewId.value, + otherBankRoutingScheme = transactionRequestBodySimple.to.other_bank_routing_scheme, + otherBankRoutingAddress = transactionRequestBodySimple.to.other_bank_routing_address, + otherBranchRoutingScheme = transactionRequestBodySimple.to.other_branch_routing_scheme, + otherBranchRoutingAddress = transactionRequestBodySimple.to.other_branch_routing_address, + otherAccountRoutingScheme = transactionRequestBodySimple.to.other_account_routing_scheme, + otherAccountRoutingAddress = transactionRequestBodySimple.to.other_account_routing_address, + otherAccountSecondaryRoutingScheme = transactionRequestBodySimple.to.other_account_secondary_routing_scheme, + otherAccountSecondaryRoutingAddress = transactionRequestBodySimple.to.other_account_secondary_routing_address, + callContext: Option[CallContext], + ) + toAccount <- NewStyle.function.getBankAccountFromCounterparty(toCounterparty, true, callContext) + // Check we can send money to it. + _ <- Helper.booleanToFuture(s"$CounterpartyBeneficiaryPermit", cc=callContext) { + toCounterparty.isBeneficiary + } + chargePolicy = transactionRequestBodySimple.charge_policy + _ <- Helper.booleanToFuture(s"$InvalidChargePolicy", cc=callContext) { + ChargePolicy.values.contains(ChargePolicy.withName(chargePolicy)) + } + transDetailsSerialized <- NewStyle.function.tryons(UnknownError, 400, callContext) { + write(transactionRequestBodySimple)(Serialization.formats(NoTypeHints)) + } + (createdTransactionRequest, callContext) <- NewStyle.function.createTransactionRequestv400(u, + viewId, + fromAccount, + toAccount, + transactionRequestType, + transactionRequestBodySimple, + transDetailsSerialized, + chargePolicy, + Some(OBP_TRANSACTION_REQUEST_CHALLENGE), + getScaMethodAtInstance(transactionRequestType.value).toOption, + None, + None, + callContext) + } yield (createdTransactionRequest, callContext) + + } + case SEPA => { + for { + //For SEPA, Use the iban to find the toCounterparty and set up the toAccount + transDetailsSEPAJson <- NewStyle.function.tryons(s"${InvalidJsonFormat}, it should be $SEPA json format", 400, callContext) { + json.extract[TransactionRequestBodySEPAJsonV400] + } + toIban = transDetailsSEPAJson.to.iban + (toCounterparty, callContext) <- NewStyle.function.getCounterpartyByIbanAndBankAccountId(toIban, fromAccount.bankId, fromAccount.accountId, callContext) + toAccount <- NewStyle.function.getBankAccountFromCounterparty(toCounterparty, true, callContext) + _ <- Helper.booleanToFuture(s"$CounterpartyBeneficiaryPermit", cc=callContext) { + toCounterparty.isBeneficiary + } + chargePolicy = transDetailsSEPAJson.charge_policy + _ <- Helper.booleanToFuture(s"$InvalidChargePolicy", cc=callContext) { + ChargePolicy.values.contains(ChargePolicy.withName(chargePolicy)) + } + transDetailsSerialized <- NewStyle.function.tryons(UnknownError, 400, callContext) { + write(transDetailsSEPAJson)(Serialization.formats(NoTypeHints)) + } + (createdTransactionRequest, callContext) <- NewStyle.function.createTransactionRequestv400(u, + viewId, + fromAccount, + toAccount, + transactionRequestType, + transDetailsSEPAJson, + transDetailsSerialized, + chargePolicy, + Some(OBP_TRANSACTION_REQUEST_CHALLENGE), + getScaMethodAtInstance(transactionRequestType.value).toOption, + transDetailsSEPAJson.reasons.map(_.map(_.transform)), + None, + callContext) + } yield (createdTransactionRequest, callContext) + } + case FREE_FORM => { + for { + transactionRequestBodyFreeForm <- NewStyle.function.tryons(s"${InvalidJsonFormat}, it should be $FREE_FORM json format", 400, callContext) { + json.extract[TransactionRequestBodyFreeFormJSON] + } + // Following lines: just transfer the details body, add Bank_Id and Account_Id in the Detail part. This is for persistence and 'answerTransactionRequestChallenge' + transactionRequestAccountJSON = TransactionRequestAccountJsonV140(bankId.value, accountId.value) + transDetailsSerialized <- NewStyle.function.tryons(UnknownError, 400, callContext) { + write(transactionRequestBodyFreeForm)(Serialization.formats(NoTypeHints)) + } + (createdTransactionRequest, callContext) <- NewStyle.function.createTransactionRequestv400(u, + viewId, + fromAccount, + fromAccount, + transactionRequestType, + transactionRequestBodyFreeForm, + transDetailsSerialized, + sharedChargePolicy.toString, + Some(OBP_TRANSACTION_REQUEST_CHALLENGE), + getScaMethodAtInstance(transactionRequestType.value).toOption, + None, + None, + callContext) + } yield + (createdTransactionRequest, callContext) } - (challenges, callContext) <- NewStyle.function.getChallengesByTransactionRequestId(createdTransactionRequest.id.value, callContext) - } yield { - (JSONFactory400.createTransactionRequestWithChargeJSON(createdTransactionRequest, challenges), HttpCode.`201`(callContext)) } - } + (challenges, callContext) <- NewStyle.function.getChallengesByTransactionRequestId(createdTransactionRequest.id.value, callContext) + } yield { + (JSONFactory400.createTransactionRequestWithChargeJSON(createdTransactionRequest, challenges), HttpCode.`201`(callContext)) + } + } + + lazy val createTransactionRequestAccount: OBPEndpoint = { + case "banks" :: BankId(bankId) :: "accounts" :: AccountId(accountId) :: ViewId(viewId) :: "transaction-request-types" :: + "ACCOUNT" :: "transaction-requests" :: Nil JsonPost json -> _ => + cc => + val transactionRequestType = TransactionRequestType("ACCOUNT") + createTransactionRequest(bankId, accountId, viewId , transactionRequestType, json) + } + + lazy val createTransactionRequestAccountOtp: OBPEndpoint = { + case "banks" :: BankId(bankId) :: "accounts" :: AccountId(accountId) :: ViewId(viewId) :: "transaction-request-types" :: + "ACCOUNT_OTP" :: "transaction-requests" :: Nil JsonPost json -> _ => + cc => + val transactionRequestType = TransactionRequestType("ACCOUNT_OTP") + createTransactionRequest(bankId, accountId, viewId , transactionRequestType, json) + } + + lazy val createTransactionRequestSepa: OBPEndpoint = { + case "banks" :: BankId(bankId) :: "accounts" :: AccountId(accountId) :: ViewId(viewId) :: "transaction-request-types" :: + "SEPA" :: "transaction-requests" :: Nil JsonPost json -> _ => + cc => + val transactionRequestType = TransactionRequestType("SEPA") + createTransactionRequest(bankId, accountId, viewId , transactionRequestType, json) + } + + lazy val createTransactionRequestCounterparty: OBPEndpoint = { + case "banks" :: BankId(bankId) :: "accounts" :: AccountId(accountId) :: ViewId(viewId) :: "transaction-request-types" :: + "COUNTERPARTY" :: "transaction-requests" :: Nil JsonPost json -> _ => + cc => + val transactionRequestType = TransactionRequestType("COUNTERPARTY") + createTransactionRequest(bankId, accountId, viewId , transactionRequestType, json) + } + + lazy val createTransactionRequestRefund: OBPEndpoint = { + case "banks" :: BankId(bankId) :: "accounts" :: AccountId(accountId) :: ViewId(viewId) :: "transaction-request-types" :: + "REFUND" :: "transaction-requests" :: Nil JsonPost json -> _ => + cc => + val transactionRequestType = TransactionRequestType("REFUND") + createTransactionRequest(bankId, accountId, viewId , transactionRequestType, json) + } + + lazy val createTransactionRequestFreeForm: OBPEndpoint = { + case "banks" :: BankId(bankId) :: "accounts" :: AccountId(accountId) :: ViewId(viewId) :: "transaction-request-types" :: + "FREE_FORM" :: "transaction-requests" :: Nil JsonPost json -> _ => + cc => + val transactionRequestType = TransactionRequestType("FREE_FORM") + createTransactionRequest(bankId, accountId, viewId , transactionRequestType, json) + } + + lazy val createTransactionRequestSimple: OBPEndpoint = { + case "banks" :: BankId(bankId) :: "accounts" :: AccountId(accountId) :: ViewId(viewId) :: "transaction-request-types" :: + "SIMPLE" :: "transaction-requests" :: Nil JsonPost json -> _ => + cc => + val transactionRequestType = TransactionRequestType("SIMPLE") + createTransactionRequest(bankId, accountId, viewId , transactionRequestType, json) } @@ -8322,7 +8365,7 @@ trait APIMethods400 { } } } - + staticResourceDocs += ResourceDoc( addConsentUser, implementedInApiVersion, @@ -11471,8 +11514,7 @@ trait APIMethods400 { EmptyBody, atmsJsonV400, List( - $UserNotLoggedIn, - InvalidJsonFormat, + $BankNotFound, UnknownError ), List(apiTagATM, apiTagNewStyle) @@ -12435,7 +12477,7 @@ trait APIMethods400 { DynamicEndpointHelper.getRoles(dynamicEndpointInfo) } _ <- NewStyle.function.tryons(InvalidJsonFormat+"Can not generate OBP external Resource Docs", 400, cc.callContext) { - JSONFactory1_4_0.createResourceDocsJson(dynamicEndpointInfo.resourceDocs.toList, false) + JSONFactory1_4_0.createResourceDocsJson(dynamicEndpointInfo.resourceDocs.toList, false, None) } (dynamicEndpoint, callContext) <- NewStyle.function.createDynamicEndpoint(bankId, cc.userId, postedJson.swaggerString, cc.callContext) _ <- NewStyle.function.tryons(InvalidJsonFormat+s"Can not grant these roles ${roles.toString} ", 400, cc.callContext) { diff --git a/obp-api/src/main/scala/code/api/v4_0_0/JSONFactory4.0.0.scala b/obp-api/src/main/scala/code/api/v4_0_0/JSONFactory4.0.0.scala index c502ef11d..c21cfd92e 100644 --- a/obp-api/src/main/scala/code/api/v4_0_0/JSONFactory4.0.0.scala +++ b/obp-api/src/main/scala/code/api/v4_0_0/JSONFactory4.0.0.scala @@ -69,7 +69,6 @@ import scala.collection.immutable.List import scala.math.BigDecimal import scala.util.Try - case class CallLimitPostJsonV400( from_date : Date, to_date : Date, diff --git a/obp-api/src/main/scala/code/api/v4_0_0/OBPAPI4_0_0.scala b/obp-api/src/main/scala/code/api/v4_0_0/OBPAPI4_0_0.scala index e108258e0..cf9ecd612 100644 --- a/obp-api/src/main/scala/code/api/v4_0_0/OBPAPI4_0_0.scala +++ b/obp-api/src/main/scala/code/api/v4_0_0/OBPAPI4_0_0.scala @@ -51,7 +51,7 @@ object OBPAPI4_0_0 extends OBPRestHelper with APIMethods130 with APIMethods140 w val version : ApiVersion = ApiVersion.v4_0_0 - val versionStatus = "BLEEDING-EDGE" // TODO this should be a property of ApiVersion. + val versionStatus = "DRAFT" // TODO this should be a property of ApiVersion. // 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) @@ -81,7 +81,7 @@ object OBPAPI4_0_0 extends OBPRestHelper with APIMethods130 with APIMethods140 w // register v4.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.") + logger.info(s"version $version has been run! There are ${routes.length} routes, ${allResourceDocs.length} allResourceDocs.") // specified response for OPTIONS request. private val corsResponse: Box[LiftResponse] = Full{ diff --git a/obp-api/src/main/scala/code/api/v5_0_0/APIMethods500.scala b/obp-api/src/main/scala/code/api/v5_0_0/APIMethods500.scala index 9bfd1ef54..1408ce644 100644 --- a/obp-api/src/main/scala/code/api/v5_0_0/APIMethods500.scala +++ b/obp-api/src/main/scala/code/api/v5_0_0/APIMethods500.scala @@ -5,28 +5,51 @@ 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.{APIUtil, ApiRole, Consent, NewStyle} import code.api.util.NewStyle.HttpCode -import code.api.v3_1_0.{PostUserAuthContextJson, PostUserAuthContextUpdateJsonV310} +import code.api.v3_1_0.{PostConsentBodyCommonJson, PostConsentEmailJsonV310, PostConsentEntitlementJsonV310, PostConsentPhoneJsonV310, PostConsentViewJsonV310, PostUserAuthContextJson, PostUserAuthContextUpdateJsonV310} +import code.bankconnectors.Connector +import code.consent.{ConsentRequests, Consents} +import code.entitlement.Entitlement import code.transactionrequests.TransactionRequests.TransactionRequestTypes.{apply => _} import code.util.Helper +import code.views.Views 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.common.{Full} +import net.liftweb.http.{Req} import net.liftweb.http.rest.RestHelper +import net.liftweb.json +import net.liftweb.json.compactRender +import net.liftweb.util.Props import scala.collection.immutable.{List, Nil} import scala.collection.mutable.ArrayBuffer import scala.concurrent.Future +import scala.util.Random trait APIMethods500 { self: RestHelper => val Implementations5_0_0 = new Implementations500() + protected trait TestHead { + /** + * Test to see if the request is a GET and expecting JSON in the response. + * The path and the Req instance are extracted. + */ + def unapply(r: Req): Option[(List[String], Req)] = + if (r.requestType.head_? && testResponse_?(r)) + Some(r.path.partPath -> r) else None + + def testResponse_?(r: Req): Boolean + } + + lazy val JsonHead = new TestHead with JsonTest + class Implementations500 { val implementedInApiVersion = ApiVersion.v5_0_0 @@ -69,7 +92,7 @@ trait APIMethods500 { json.extract[PostUserAuthContextJson] } (user, callContext) <- NewStyle.function.findByUserId(userId, callContext) - (userAuthContext, callContext) <- NewStyle.function.createUserAuthContext(user, postedData.key, postedData.value, callContext) + (userAuthContext, callContext) <- NewStyle.function.createUserAuthContext(user, postedData.key.trim, postedData.value.trim, callContext) } yield { (JSONFactory500.createUserAuthContextJson(userAuthContext), HttpCode.`201`(callContext)) } @@ -90,7 +113,7 @@ trait APIMethods500 { |${authenticationRequiredMessage(true)} | |""", - emptyObjectJson, + EmptyBody, userAuthContextJsonV500, List( UserNotLoggedIn, @@ -132,6 +155,7 @@ trait APIMethods500 { userAuthContextUpdateJsonV500, List( UserNotLoggedIn, + $BankNotFound, InvalidJsonFormat, CreateUserAuthContextError, UnknownError @@ -148,7 +172,6 @@ trait APIMethods500 { _ <- 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) } @@ -156,7 +179,7 @@ trait APIMethods500 { 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) + (userAuthContextUpdate, callContext) <- NewStyle.function.validateUserAuthContextUpdateRequest(bankId.value, user.userId, postedData.key.trim, postedData.value.trim, scaMethod, callContext) } yield { (JSONFactory500.createUserAuthContextUpdateJson(userAuthContextUpdate), HttpCode.`201`(callContext)) @@ -170,15 +193,15 @@ trait APIMethods500 { nameOf(answerUserAuthContextUpdateChallenge), "POST", "/banks/BANK_ID/users/current/auth-context-updates/AUTH_CONTEXT_UPDATE_ID/challenge", - "Answer Auth Context Update Challenge", + "Answer User Auth Context Update Challenge", s""" - |Answer Auth Context Update Challenge. + |Answer User Auth Context Update Challenge. |""", postUserAuthContextUpdateJsonV310, userAuthContextUpdateJsonV500, List( UserNotLoggedIn, - BankNotFound, + $BankNotFound, InvalidJsonFormat, InvalidConnectorResponse, UnknownError @@ -201,8 +224,8 @@ trait APIMethods500 { case status if status == UserAuthContextUpdateStatus.ACCEPTED.toString => NewStyle.function.createUserAuthContext( user, - userAuthContextUpdate.key, - userAuthContextUpdate.value, + userAuthContextUpdate.key.trim, + userAuthContextUpdate.value.trim, callContext).map(x => (Some(x._1), x._2)) case _ => Future((None, callContext)) @@ -224,7 +247,373 @@ trait APIMethods500 { } } } + + staticResourceDocs += ResourceDoc( + createConsentRequest, + implementedInApiVersion, + nameOf(createConsentRequest), + "POST", + "/consumer/consent-requests", + "Create Consent Request", + s"""""", + postConsentRequestJsonV500, + consentRequestResponseJson, + List( + $BankNotFound, + InvalidJsonFormat, + ConsentMaxTTL, + UnknownError + ), + apiTagConsent :: apiTagPSD2AIS :: apiTagPsd2 :: apiTagNewStyle :: Nil + ) + + lazy val createConsentRequest : OBPEndpoint = { + case "consumer" :: "consent-requests" :: Nil JsonPost json -> _ => { + cc => + for { + (_, callContext) <- applicationAccess(cc) + _ <- passesPsd2Aisp(callContext) + failMsg = s"$InvalidJsonFormat The Json body should be the $PostConsentBodyCommonJson " + consentJson: PostConsentRequestJsonV500 <- NewStyle.function.tryons(failMsg, 400, callContext) { + json.extract[PostConsentRequestJsonV500] + } + maxTimeToLive = APIUtil.getPropsAsIntValue(nameOfProperty="consents.max_time_to_live", defaultValue=3600) + _ <- Helper.booleanToFuture(s"$ConsentMaxTTL ($maxTimeToLive)", cc=callContext){ + consentJson.time_to_live match { + case Some(ttl) => ttl <= maxTimeToLive + case _ => true + } + } + createdConsentRequest <- Future(ConsentRequests.consentRequestProvider.vend.createConsentRequest( + callContext.flatMap(_.consumer), + Some(compactRender(json)) + )) map { + i => connectorEmptyResponse(i, callContext) + } + } yield { + ( + ConsentRequestResponseJson( + createdConsentRequest.consentRequestId, + net.liftweb.json.parse(createdConsentRequest.payload), + createdConsentRequest.consumerId, + ), + HttpCode.`201`(callContext) + ) + } + } + } + staticResourceDocs += ResourceDoc( + getConsentRequest, + implementedInApiVersion, + nameOf(getConsentRequest), + "GET", + "/consumer/consent-requests/CONSENT_REQUEST_ID", + "Get Consent Request", + s"""""", + EmptyBody, + consentRequestResponseJson, + List( + $BankNotFound, + ConsentRequestNotFound, + UnknownError + ), + apiTagConsent :: apiTagPSD2AIS :: apiTagPsd2 :: apiTagNewStyle :: Nil + ) + + lazy val getConsentRequest : OBPEndpoint = { + case "consumer" :: "consent-requests" :: consentRequestId :: Nil JsonGet _ => { + cc => + for { + (_, callContext) <- applicationAccess(cc) + _ <- passesPsd2Aisp(callContext) + createdConsentRequest <- Future(ConsentRequests.consentRequestProvider.vend.getConsentRequestById( + consentRequestId + )) map { + i => unboxFullOrFail(i,callContext, ConsentRequestNotFound) + } + } yield { + (ConsentRequestResponseJson( + consent_request_id = createdConsentRequest.consentRequestId, + payload = json.parse(createdConsentRequest.payload), + consumer_id = createdConsentRequest.consumerId + ), + HttpCode.`200`(callContext) + ) + } + } + } + + staticResourceDocs += ResourceDoc( + getConsentByConsentRequestId, + implementedInApiVersion, + nameOf(getConsentByConsentRequestId), + "GET", + "/consumer/consent-requests/CONSENT_REQUEST_ID/consents", + "Get Consent By Consent Request Id", + s""" + | + |This endpoint gets the Consent By consent request id. + | + |${authenticationRequiredMessage(true)} + | + """.stripMargin, + EmptyBody, + consentJsonV500, + List( + $UserNotLoggedIn, + UnknownError + ), + List(apiTagConsent, apiTagPSD2AIS, apiTagPsd2, apiTagNewStyle)) + lazy val getConsentByConsentRequestId: OBPEndpoint = { + case "consumer" :: "consent-requests" :: consentRequestId :: "consents" :: Nil JsonGet _ => { + cc => + for { + (_, callContext) <- applicationAccess(cc) + consent<- Future { Consents.consentProvider.vend.getConsentByConsentRequestId(consentRequestId)} map { + unboxFullOrFail(_, callContext, ConsentRequestNotFound) + } + } yield { + ( + ConsentJsonV500( + consent.consentId, + consent.jsonWebToken, + consent.status, + Some(consent.consentRequestId) + ), + HttpCode.`200`(cc) + ) + } + } + } + + staticResourceDocs += ResourceDoc( + createConsentByConsentRequestIdEmail, + implementedInApiVersion, + nameOf(createConsentByConsentRequestIdEmail), + "POST", + "/consumer/consent-requests/CONSENT_REQUEST_ID/EMAIL/consents", + "Create Consent By Request Id(EMAIL)", + s""" + | + |This endpoint starts the process of creating a Consent by consent request id. + | + |""", + EmptyBody, + consentJsonV500, + List( + UserNotLoggedIn, + BankNotFound, + InvalidJsonFormat, + ConsentAllowedScaMethods, + RolesAllowedInConsent, + ViewsAllowedInConsent, + ConsumerNotFoundByConsumerId, + ConsumerIsDisabled, + InvalidConnectorResponse, + UnknownError + ), + apiTagConsent :: apiTagPSD2AIS :: apiTagPsd2 :: apiTagNewStyle :: Nil) + staticResourceDocs += ResourceDoc( + createConsentByConsentRequestIdSms, + implementedInApiVersion, + nameOf(createConsentByConsentRequestIdSms), + "POST", + "/consumer/consent-requests/CONSENT_REQUEST_ID/SMS/consents", + "Create Consent By Request Id (SMS)", + s""" + | + |This endpoint starts the process of creating a Consent. + | + |""", + EmptyBody, + consentJsonV500, + List( + UserNotLoggedIn, + $BankNotFound, + InvalidJsonFormat, + ConsentAllowedScaMethods, + RolesAllowedInConsent, + ViewsAllowedInConsent, + ConsumerNotFoundByConsumerId, + ConsumerIsDisabled, + MissingPropsValueAtThisInstance, + SmsServerNotResponding, + InvalidConnectorResponse, + UnknownError + ), + apiTagConsent :: apiTagPSD2AIS :: apiTagPsd2 ::apiTagNewStyle :: Nil) + + lazy val createConsentByConsentRequestIdEmail = createConsentByConsentRequestId + lazy val createConsentByConsentRequestIdSms = createConsentByConsentRequestId + + lazy val createConsentByConsentRequestId : OBPEndpoint = { + case "consumer" :: "consent-requests":: consentRequestId :: scaMethod :: "consents" :: Nil JsonPost _ -> _ => { + cc => + for { + (Full(user), callContext) <- authenticatedAccess(cc) + createdConsentRequest <- Future(ConsentRequests.consentRequestProvider.vend.getConsentRequestById( + consentRequestId + )) map { + i => unboxFullOrFail(i,callContext, ConsentRequestNotFound) + } + _ <- Helper.booleanToFuture(ConsentRequestAlreadyUsed, cc=callContext){ + Consents.consentProvider.vend.getConsentByConsentRequestId(consentRequestId).isEmpty + } + _ <- Helper.booleanToFuture(ConsentAllowedScaMethods, cc=callContext){ + List(StrongCustomerAuthentication.SMS.toString(), StrongCustomerAuthentication.EMAIL.toString()).exists(_ == scaMethod) + } + failMsg = s"$InvalidJsonFormat The Json body should be the $PostConsentBodyCommonJson " + consentRequestJson <- NewStyle.function.tryons(failMsg, 400, callContext) { + json.parse(createdConsentRequest.payload).extract[PostConsentRequestJsonV500] + } + maxTimeToLive = APIUtil.getPropsAsIntValue(nameOfProperty="consents.max_time_to_live", defaultValue=3600) + _ <- Helper.booleanToFuture(s"$ConsentMaxTTL ($maxTimeToLive)", cc=callContext){ + consentRequestJson.time_to_live match { + case Some(ttl) => ttl <= maxTimeToLive + case _ => true + } + } + requestedEntitlements = consentRequestJson.entitlements.getOrElse(Nil) + myEntitlements <- Entitlement.entitlement.vend.getEntitlementsByUserIdFuture(user.userId) + _ <- Helper.booleanToFuture(RolesAllowedInConsent, cc=callContext){ + requestedEntitlements.forall( + re => myEntitlements.getOrElse(Nil).exists( + e => e.roleName == re.role_name && e.bankId == re.bank_id + ) + ) + } + + postConsentViewJsons <- Future.sequence( + consentRequestJson.account_access.map( + access => + NewStyle.function.getBankAccountByRouting(None,access.account_routing.scheme, access.account_routing.address, cc.callContext) + .map(result =>PostConsentViewJsonV310( + result._1.bankId.value, + result._1.accountId.value, + access.view_id + )) + ) + ) + + (_, assignedViews) <- Future(Views.views.vend.privateViewsUserCanAccess(user)) + _ <- Helper.booleanToFuture(ViewsAllowedInConsent, cc=callContext){ + postConsentViewJsons.forall( + rv => assignedViews.exists{ + e => + e.view_id == rv.view_id && + e.bank_id == rv.bank_id && + e.account_id == rv.account_id + } + ) + } + (consumerId, applicationText) <- consentRequestJson.consumer_id match { + case Some(id) => NewStyle.function.checkConsumerByConsumerId(id, callContext) map { + c => (Some(c.consumerId.get), c.description) + } + case None => Future(None, "Any application") + } + + challengeAnswer = Props.mode match { + case Props.RunModes.Test => Consent.challengeAnswerAtTestEnvironment + case _ => Random.nextInt(99999999).toString() + } + createdConsent <- Future(Consents.consentProvider.vend.createObpConsent(user, challengeAnswer, Some(consentRequestId))) map { + i => connectorEmptyResponse(i, callContext) + } + + postConsentBodyCommonJson = PostConsentBodyCommonJson( + everything = consentRequestJson.everything, + views = postConsentViewJsons, + entitlements = consentRequestJson.entitlements.getOrElse(Nil), + consumer_id = consentRequestJson.consumer_id, + consent_request_id = Some(consentRequestId), + valid_from = consentRequestJson.valid_from, + time_to_live = consentRequestJson.time_to_live, + ) + + consentJWT = Consent.createConsentJWT( + user, + postConsentBodyCommonJson, + createdConsent.secret, + createdConsent.consentId, + consumerId, + postConsentBodyCommonJson.valid_from, + postConsentBodyCommonJson.time_to_live.getOrElse(3600) + ) + _ <- Future(Consents.consentProvider.vend.setJsonWebToken(createdConsent.consentId, consentJWT)) map { + i => connectorEmptyResponse(i, callContext) + } + challengeText = s"Your consent challenge : ${challengeAnswer}, Application: $applicationText" + _ <- scaMethod match { + case v if v == StrongCustomerAuthentication.EMAIL.toString => // Send the email + for{ + failMsg <- Future {s"$InvalidJsonFormat The Json body should be the $PostConsentEmailJsonV310"} + consentScaEmail <- NewStyle.function.tryons(failMsg, 400, callContext) { + consentRequestJson.email.head + } + (Full(status), callContext) <- Connector.connector.vend.sendCustomerNotification( + StrongCustomerAuthentication.EMAIL, + consentScaEmail, + Some("OBP Consent Challenge"), + challengeText, + callContext + ) + } yield Future{status} + case v if v == StrongCustomerAuthentication.SMS.toString => // Not implemented + for { + failMsg <- Future { + s"$InvalidJsonFormat The Json body should be the $PostConsentPhoneJsonV310" + } + consentScaPhoneNumber <- NewStyle.function.tryons(failMsg, 400, callContext) { + consentRequestJson.phone_number.head + } + (Full(status), callContext) <- Connector.connector.vend.sendCustomerNotification( + StrongCustomerAuthentication.SMS, + consentScaPhoneNumber, + None, + challengeText, + callContext + ) + } yield Future{status} + case _ =>Future{"Success"} + } + } yield { + (ConsentJsonV500(createdConsent.consentId, consentJWT, createdConsent.status, Some(createdConsent.consentRequestId)), HttpCode.`201`(callContext)) + } + } + } + + + staticResourceDocs += ResourceDoc( + headAtms, + implementedInApiVersion, + nameOf(headAtms), + "HEAD", + "/banks/BANK_ID/atms", + "Head Bank ATMS", + s"""Head Bank ATMS.""", + EmptyBody, + atmsJsonV400, + List( + $BankNotFound, + UnknownError + ), + List(apiTagATM, apiTagNewStyle) + ) + lazy val headAtms : OBPEndpoint = { + case "banks" :: BankId(bankId) :: "atms" :: Nil JsonHead _ => { + cc => + for { + (_, callContext) <- getAtmsIsPublic match { + case false => authenticatedAccess(cc) + case true => anonymousAccess(cc) + } + } yield { + ("", HttpCode.`200`(callContext)) + } + } + } } } diff --git a/obp-api/src/main/scala/code/api/v5_0_0/JSONFactory5.0.0.scala b/obp-api/src/main/scala/code/api/v5_0_0/JSONFactory5.0.0.scala index 5dacd69e7..52bf3bf68 100644 --- a/obp-api/src/main/scala/code/api/v5_0_0/JSONFactory5.0.0.scala +++ b/obp-api/src/main/scala/code/api/v5_0_0/JSONFactory5.0.0.scala @@ -26,9 +26,12 @@ */ package code.api.v5_0_0 -import com.openbankproject.commons.model.{UserAuthContext, UserAuthContextUpdate} +import code.api.v3_1_0.{PostConsentEntitlementJsonV310} +import com.openbankproject.commons.model.{AccountRoutingJsonV121, UserAuthContext, UserAuthContextUpdate} +import net.liftweb.json.JsonAST.JValue import java.util.Date +import scala.collection.immutable.List case class UserAuthContextJsonV500( user_auth_context_id: String, @@ -52,6 +55,33 @@ case class UserAuthContextUpdateJsonV500( consumer_id: String, ) + +case class PostConsentRequestResponseJson(consentRequestId: String) + +case class ConsentRequestResponseJson( + consent_request_id: String, + payload : JValue, + consumer_id : String +) +case class AccountAccessV500( +// bank_routing: Option[BankRoutingJsonV121], +// branch_routing: Option[BranchRoutingJsonV141], + account_routing: AccountRoutingJsonV121, + view_id: String +) + +case class PostConsentRequestJsonV500( + everything: Boolean, + account_access: List[AccountAccessV500], + entitlements: Option[List[PostConsentEntitlementJsonV310]], + consumer_id: Option[String], + email: Option[String], + phone_number: Option[String], + valid_from: Option[Date], + time_to_live: Option[Long] +) + +case class ConsentJsonV500(consent_id: String, jwt: String, status: String, consent_request_id: Option[String]) object JSONFactory500 { def createUserAuthContextJson(userAuthContext: UserAuthContext): UserAuthContextJsonV500 = { diff --git a/obp-api/src/main/scala/code/api/v5_0_0/OBPAPI5_0_0.scala b/obp-api/src/main/scala/code/api/v5_0_0/OBPAPI5_0_0.scala index 6b007dc09..adfc5d48a 100644 --- a/obp-api/src/main/scala/code/api/v5_0_0/OBPAPI5_0_0.scala +++ b/obp-api/src/main/scala/code/api/v5_0_0/OBPAPI5_0_0.scala @@ -81,14 +81,13 @@ object OBPAPI5_0_0 extends OBPRestHelper 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) + val routes : List[OBPEndpoint] = 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.") + logger.info(s"version $version has been run! There are ${routes.length} routes, ${allResourceDocs.length} allResourceDocs.") // specified response for OPTIONS request. private val corsResponse: Box[LiftResponse] = Full{ diff --git a/obp-api/src/main/scala/code/bankattribute/MappedBankAttributeProvider.scala b/obp-api/src/main/scala/code/bankattribute/MappedBankAttributeProvider.scala index 51482c050..96b4d9abb 100644 --- a/obp-api/src/main/scala/code/bankattribute/MappedBankAttributeProvider.scala +++ b/obp-api/src/main/scala/code/bankattribute/MappedBankAttributeProvider.scala @@ -37,7 +37,7 @@ object BankAttributeProvider extends BankAttributeProviderTrait { attribute.BankId_(bankId.value) .Name(name) .Type(attributType.toString) - .Value(value) + .`Value`(value) .IsActive(isActive.getOrElse(true)) .saveMe() } @@ -50,7 +50,7 @@ object BankAttributeProvider extends BankAttributeProviderTrait { .BankId_(bankId.value) .Name(name) .Type(attributType.toString()) - .Value(value) + .`Value`(value) .IsActive(isActive.getOrElse(true)) .saveMe() } @@ -73,7 +73,7 @@ class BankAttribute extends BankAttributeTrait with LongKeyedMapper[BankAttribut object BankAttributeId extends MappedUUID(this) object Name extends MappedString(this, 50) object Type extends MappedString(this, 50) - object Value extends MappedString(this, 255) + object `Value` extends MappedString(this, 255) object IsActive extends MappedBoolean(this) { override def defaultValue = true } @@ -83,7 +83,7 @@ class BankAttribute extends BankAttributeTrait with LongKeyedMapper[BankAttribut override def bankAttributeId: String = BankAttributeId.get override def name: String = Name.get override def attributeType: BankAttributeType.Value = BankAttributeType.withName(Type.get) - override def value: String = Value.get + override def value: String = `Value`.get override def isActive: Option[Boolean] = if (IsActive.jdbcFriendly(IsActive.calcFieldName) == null) { None } else Some(IsActive.get) } diff --git a/obp-api/src/main/scala/code/bankconnectors/LocalMappedConnector.scala b/obp-api/src/main/scala/code/bankconnectors/LocalMappedConnector.scala index bb1aefb19..75b9df1a1 100644 --- a/obp-api/src/main/scala/code/bankconnectors/LocalMappedConnector.scala +++ b/obp-api/src/main/scala/code/bankconnectors/LocalMappedConnector.scala @@ -10,6 +10,7 @@ import code.accountapplication.AccountApplicationX import code.accountattribute.AccountAttributeX import code.accountholders.{AccountHolders, MapperAccountHolders} import code.api.BerlinGroup.{AuthenticationType, ScaStatus} +import code.api.Constant 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} @@ -840,7 +841,7 @@ object LocalMappedConnector extends Connector with MdcLoggable { } private lazy val getDbConnectionParameters: (String, String, String) = { - val dbUrl = APIUtil.getPropsValue("db.url") openOr "jdbc:h2:mem:OBPTest;DB_CLOSE_DELAY=-1" + val dbUrl = APIUtil.getPropsValue("db.url") openOr Constant.h2DatabaseDefaultUrlValue 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) diff --git a/obp-api/src/main/scala/code/consent/ConsentProvider.scala b/obp-api/src/main/scala/code/consent/ConsentProvider.scala index 30f1d1486..d1ff8d088 100644 --- a/obp-api/src/main/scala/code/consent/ConsentProvider.scala +++ b/obp-api/src/main/scala/code/consent/ConsentProvider.scala @@ -17,10 +17,11 @@ object Consents extends SimpleInjector { trait ConsentProvider { def getConsentByConsentId(consentId: String): Box[MappedConsent] + def getConsentByConsentRequestId(consentRequestId: String): Box[MappedConsent] def updateConsentStatus(consentId: String, status: ConsentStatus): Box[MappedConsent] def updateConsentUser(consentId: String, user: User): Box[MappedConsent] def getConsentsByUser(userId: String): List[MappedConsent] - def createConsent(user: User, challenge: String): Box[MappedConsent] + def createObpConsent(user: User, challengeAnswer: String, consentRequestId:Option[String]): Box[MappedConsent] def setJsonWebToken(consentId: String, jwt: String): Box[MappedConsent] def revoke(consentId: String): Box[MappedConsent] def checkAnswer(consentId: String, challenge: String): Box[MappedConsent] @@ -86,6 +87,8 @@ trait Consent { * @return Consumer ID */ def consumerId: String + + def consentRequestId: String /** * This field identifies the standard of API of a related consent diff --git a/obp-api/src/main/scala/code/consent/ConsentRequesProvider.scala b/obp-api/src/main/scala/code/consent/ConsentRequesProvider.scala new file mode 100644 index 000000000..ccb351ff4 --- /dev/null +++ b/obp-api/src/main/scala/code/consent/ConsentRequesProvider.scala @@ -0,0 +1,31 @@ +package code.consent + +import code.model.Consumer +import com.openbankproject.commons.model.User +import net.liftweb.common.Box +import net.liftweb.util.SimpleInjector + +object ConsentRequests extends SimpleInjector { + val consentRequestProvider = new Inject(buildOne _) {} + def buildOne: ConsentRequestProvider = MappedConsentRequestProvider +} + +trait ConsentRequestProvider { + def getConsentRequestById(consentRequestId: String): Box[ConsentRequest] + def createConsentRequest(consumer: Option[Consumer], payload: Option[String]): Box[ConsentRequest] +} + +trait ConsentRequestTrait { + def consentRequestId: String + def payload: String + def consumerId: String +} + + + + + + + + + diff --git a/obp-api/src/main/scala/code/consent/ConsentRequest.scala b/obp-api/src/main/scala/code/consent/ConsentRequest.scala new file mode 100644 index 000000000..e307eeb07 --- /dev/null +++ b/obp-api/src/main/scala/code/consent/ConsentRequest.scala @@ -0,0 +1,45 @@ +package code.consent + +import code.model.Consumer +import code.util.MappedUUID +import net.liftweb.common.Box +import net.liftweb.mapper._ +import net.liftweb.util.Helpers.tryo + +object MappedConsentRequestProvider extends ConsentRequestProvider { + override def getConsentRequestById(consentRequestId: String): Box[ConsentRequest] = { + ConsentRequest.find( + By(ConsentRequest.ConsentRequestId, consentRequestId) + ) + } + override def createConsentRequest(consumer: Option[Consumer], payload: Option[String]): Box[ConsentRequest] ={ + tryo { + ConsentRequest + .create + .ConsumerId(consumer.map(_.consumerId.get).getOrElse(null)) + .Payload(payload.getOrElse("")) + .saveMe() + }} +} + +class ConsentRequest extends ConsentRequestTrait with LongKeyedMapper[ConsentRequest] with IdPK with CreatedUpdated { + + def getSingleton = ConsentRequest + + //the following are the obp consent. + object ConsentRequestId extends MappedUUID(this) + object Payload extends MappedText(this) + object ConsumerId extends MappedUUID(this) { + override def defaultValue = null + } + + + override def consentRequestId: String = ConsentRequestId.get + override def payload: String = Payload.get + override def consumerId: String = ConsumerId.get + +} + +object ConsentRequest extends ConsentRequest with LongKeyedMetaMapper[ConsentRequest] { + override def dbIndexes: List[BaseIndex[ConsentRequest]] = UniqueIndex(ConsentRequestId) :: super.dbIndexes +} diff --git a/obp-api/src/main/scala/code/consent/MappedConsent.scala b/obp-api/src/main/scala/code/consent/MappedConsent.scala index 7f2d2083b..e1004dbc6 100644 --- a/obp-api/src/main/scala/code/consent/MappedConsent.scala +++ b/obp-api/src/main/scala/code/consent/MappedConsent.scala @@ -20,6 +20,13 @@ object MappedConsentProvider extends ConsentProvider { By(MappedConsent.mConsentId, consentId) ) } + + override def getConsentByConsentRequestId(consentRequestId: String): Box[MappedConsent] ={ + MappedConsent.find( + By(MappedConsent.mConsentRequestId, consentRequestId) + ) + } + override def updateConsentStatus(consentId: String, status: ConsentStatus): Box[MappedConsent] = { MappedConsent.find(By(MappedConsent.mConsentId, consentId)) match { case Full(consent) => @@ -55,13 +62,14 @@ object MappedConsentProvider extends ConsentProvider { override def getConsentsByUser(userId: String): List[MappedConsent] = { MappedConsent.findAll(By(MappedConsent.mUserId, userId)) } - override def createConsent(user: User, challengeAnswer: String): Box[MappedConsent] = { + override def createObpConsent(user: User, challengeAnswer: String, consentRequestId:Option[String]): Box[MappedConsent] = { tryo { val salt = BCrypt.gensalt() val challengeAnswerHashed = BCrypt.hashpw(challengeAnswer, salt).substring(0, 44) MappedConsent .create .mUserId(user.userId) + .mConsentRequestId(consentRequestId.getOrElse(null)) .mChallenge(challengeAnswerHashed) .mSalt(salt) .mStatus(ConsentStatus.INITIATED.toString) @@ -234,6 +242,9 @@ class MappedConsent extends Consent with LongKeyedMapper[MappedConsent] with IdP object mConsumerId extends MappedUUID(this) { override def defaultValue = null } + object mConsentRequestId extends MappedUUID(this) { + override def defaultValue = null + } object mApiStandard extends MappedString(this, 50) object mApiVersion extends MappedString(this, 50) @@ -262,6 +273,7 @@ class MappedConsent extends Consent with LongKeyedMapper[MappedConsent] with IdP override def challenge: String = mChallenge.get override def jsonWebToken: String = mJsonWebToken.get override def consumerId: String = mConsumerId.get + override def consentRequestId: String = mConsentRequestId.get override def apiStandard: String = mApiStandard.get override def apiVersion: String = mApiVersion.get diff --git a/obp-api/src/main/scala/code/context/MappedConsentAuthContext.scala b/obp-api/src/main/scala/code/context/MappedConsentAuthContext.scala index 19a24eac3..8f57fb243 100644 --- a/obp-api/src/main/scala/code/context/MappedConsentAuthContext.scala +++ b/obp-api/src/main/scala/code/context/MappedConsentAuthContext.scala @@ -11,11 +11,11 @@ class MappedConsentAuthContext extends ConsentAuthContext with LongKeyedMapper[M object ConsentAuthContextId extends MappedUUID(this) object ConsentId extends UUIDString(this) object Key extends MappedString(this, 255) - object Value extends MappedString(this, 255) + object `Value` extends MappedString(this, 255) override def consentId = ConsentId.get override def key = Key.get - override def value = Value.get + override def value = `Value`.get override def consentAuthContextId = ConsentAuthContextId.get override def timeStamp = createdAt.get } diff --git a/obp-api/src/main/scala/code/context/MappedConsentAuthContextProvider.scala b/obp-api/src/main/scala/code/context/MappedConsentAuthContextProvider.scala index d509381a0..f25688d6c 100644 --- a/obp-api/src/main/scala/code/context/MappedConsentAuthContextProvider.scala +++ b/obp-api/src/main/scala/code/context/MappedConsentAuthContextProvider.scala @@ -19,7 +19,7 @@ object MappedConsentAuthContextProvider extends ConsentAuthContextProvider with } def createConsentAuthContextAkka(consentId: String, key: String, value: String): Box[MappedConsentAuthContext] = tryo { - MappedConsentAuthContext.create.ConsentId(consentId).Key(key).Value(value).saveMe() + MappedConsentAuthContext.create.ConsentId(consentId).Key(key).`Value`(value).saveMe() } override def getConsentAuthContexts(consentId: String): Future[Box[List[MappedConsentAuthContext]]] = Future { @@ -50,11 +50,11 @@ object MappedConsentAuthContextProvider extends ConsentAuthContextProvider with By(MappedConsentAuthContext.ConsentId, consentId), By(MappedConsentAuthContext.Key, authContext.key) ).map( authContext => - authContext.Key(authContext.key).Value(authContext.value).saveMe() + authContext.Key(authContext.key).`Value`(authContext.value).saveMe() ) ) val created = create.map( authContext => - MappedConsentAuthContext.create.ConsentId(consentId).Key(authContext.key).Value(authContext.value).saveMe() + MappedConsentAuthContext.create.ConsentId(consentId).Key(authContext.key).`Value`(authContext.value).saveMe() ) tryo { updated ::: created diff --git a/obp-api/src/main/scala/code/context/MappedUserAuthContextUpdateProvider.scala b/obp-api/src/main/scala/code/context/MappedUserAuthContextUpdateProvider.scala index c2f68d514..497f0695d 100644 --- a/obp-api/src/main/scala/code/context/MappedUserAuthContextUpdateProvider.scala +++ b/obp-api/src/main/scala/code/context/MappedUserAuthContextUpdateProvider.scala @@ -1,13 +1,16 @@ package code.context -import code.api.util.ErrorMessages +import code.api.util.APIUtil.transactionRequestChallengeTtl +import code.api.util.{APIUtil, ErrorMessages} import code.util.Helper.MdcLoggable import com.openbankproject.commons.model.UserAuthContextUpdateStatus import net.liftweb.common.{Box, Empty, Failure, Full} import net.liftweb.mapper.By import net.liftweb.util.Helpers.tryo - import com.openbankproject.commons.ExecutionContext.Implicits.global +import net.liftweb.util.Helpers + +import scala.compat.Platform import scala.concurrent.Future object MappedUserAuthContextUpdateProvider extends UserAuthContextUpdateProvider with MdcLoggable { @@ -49,12 +52,21 @@ object MappedUserAuthContextUpdateProvider extends UserAuthContextUpdateProvider override def checkAnswer(consentId: String, challenge: String): Future[Box[MappedUserAuthContextUpdate]] = Future { MappedUserAuthContextUpdate.find(By(MappedUserAuthContextUpdate.mUserAuthContextUpdateId, consentId)) match { case Full(consent) => - consent.status match { - case value if value == UserAuthContextUpdateStatus.INITIATED.toString => - val status = if (consent.challenge == challenge) UserAuthContextUpdateStatus.ACCEPTED.toString else UserAuthContextUpdateStatus.REJECTED.toString - tryo(consent.mStatus(status).saveMe()) - case _ => - Full(consent) + val createDateTime = consent.createdAt.get + val challengeTTL : Long = Helpers.seconds(APIUtil.userAuthContextUpdateRequestChallengeTtl) + val expiredDateTime: Long = createDateTime.getTime+challengeTTL + val currentTime: Long = Platform.currentTime + + if(expiredDateTime > currentTime) + consent.status match { + case value if value == UserAuthContextUpdateStatus.INITIATED.toString => + val status = if (consent.challenge == challenge) UserAuthContextUpdateStatus.ACCEPTED.toString else UserAuthContextUpdateStatus.REJECTED.toString + tryo(consent.mStatus(status).saveMe()) + case _ => + Full(consent) + } + else{ + Failure(s"${ErrorMessages.OneTimePasswordExpired} Current expiration time is ${APIUtil.userAuthContextUpdateRequestChallengeTtl} seconds") } case Empty => Empty ?~! ErrorMessages.UserAuthContextUpdateNotFound diff --git a/obp-api/src/main/scala/code/metrics/MappedMetrics.scala b/obp-api/src/main/scala/code/metrics/MappedMetrics.scala index a17806dd4..e644ad523 100644 --- a/obp-api/src/main/scala/code/metrics/MappedMetrics.scala +++ b/obp-api/src/main/scala/code/metrics/MappedMetrics.scala @@ -4,6 +4,7 @@ import java.sql.{PreparedStatement, Timestamp} import java.util.Date import java.util.UUID.randomUUID +import code.api.Constant import code.api.cache.Caching import code.api.util._ import code.model.MappedConsumersProvider @@ -65,7 +66,7 @@ object MappedMetrics extends APIMetrics with MdcLoggable{ } private lazy val getDbConnectionParameters: (String, String, String) = { - val dbUrl = APIUtil.getPropsValue("db.url") openOr "jdbc:h2:mem:OBPTest;DB_CLOSE_DELAY=-1" + val dbUrl = APIUtil.getPropsValue("db.url") openOr Constant.h2DatabaseDefaultUrlValue 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) diff --git a/obp-api/src/main/scala/code/model/OAuth.scala b/obp-api/src/main/scala/code/model/OAuth.scala index ab6982bf8..56b9d881c 100644 --- a/obp-api/src/main/scala/code/model/OAuth.scala +++ b/obp-api/src/main/scala/code/model/OAuth.scala @@ -716,7 +716,7 @@ object MappedNonceProvider extends NoncesProvider { case None => } value match { - case Some(v) => n.value(v) + case Some(v) => n.`value`(v) case None => } val nonce = n.saveMe() @@ -733,7 +733,7 @@ object MappedNonceProvider extends NoncesProvider { timestamp: Date, value: String): Long = { Nonce.count( - By(Nonce.value, value), + By(Nonce.`value`, value), By(Nonce.tokenKey, tokenKey), By(Nonce.consumerkey, consumerKey), By(Nonce.timestamp, timestamp) @@ -763,7 +763,7 @@ class Nonce extends LongKeyedMapper[Nonce] { timestamp.get.getTime().toString() } } - object value extends MappedString(this,250) + object `value` extends MappedString(this,250) } object Nonce extends Nonce with LongKeyedMetaMapper[Nonce]{} diff --git a/obp-api/src/main/scala/code/model/dataAccess/AuthUser.scala b/obp-api/src/main/scala/code/model/dataAccess/AuthUser.scala index 5ad281316..057f5e460 100644 --- a/obp-api/src/main/scala/code/model/dataAccess/AuthUser.scala +++ b/obp-api/src/main/scala/code/model/dataAccess/AuthUser.scala @@ -30,7 +30,7 @@ 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.APIUtil._ import code.api.util.ErrorMessages._ import code.api.util._ import code.api.{APIFailure, Constant, DirectLogin, GatewayLogin, OAuthHandshake} @@ -43,7 +43,7 @@ import code.users.Users import code.util.Helper import code.util.Helper.MdcLoggable import code.views.Views -import com.openbankproject.commons.model.{User, _} +import com.openbankproject.commons.model._ import net.liftweb.common._ import net.liftweb.http._ import net.liftweb.mapper._ @@ -274,7 +274,7 @@ class AuthUser extends MegaProtoUser[AuthUser] with CreatedUpdated with MdcLogga invalidMsg = Helper.i18n("please.enter.your.password") S.error("authuser_password_repeat", Text(Helper.i18n("please.re-enter.your.password"))) case false => - if (validatePasswordOnCreation(passwordValue)) + if (fullPasswordValidation(passwordValue)) invalidPw = false else { invalidPw = true @@ -947,7 +947,7 @@ def restoreSomeSessions(): Unit = { */ override def login: NodeSeq = { // This query parameter is specific to Hydra ORA login request - val loginChallenge = S.param("login_challenge").getOrElse("") + val loginChallenge: Box[String] = S.param("login_challenge").or(S.getSessionAttribute("login_challenge")) def redirectUri(): String = { loginRedirect.get match { case Full(url) => @@ -980,12 +980,16 @@ def restoreSomeSessions(): Unit = { // If there is the query parameter login_challenge in a url we know it is tha Hydra request // TODO Write standalone application for Login and Consent Request of Hydra as Identity Provider integrateWithHydra match { - case true if !loginChallenge.isEmpty => - val acceptLoginRequest = new AcceptLoginRequest - val adminApi: AdminApi = new AdminApi - acceptLoginRequest.setSubject(user.username.get) - val result = adminApi.acceptLoginRequest(loginChallenge, acceptLoginRequest) - S.redirectTo(result.getRedirectTo) + case true => + if (loginChallenge.isEmpty == false) { + val acceptLoginRequest = new AcceptLoginRequest + val adminApi: AdminApi = new AdminApi + acceptLoginRequest.setSubject(user.username.get) + val result = adminApi.acceptLoginRequest(loginChallenge.getOrElse(""), acceptLoginRequest) + S.redirectTo(result.getRedirectTo) + } else { + S.redirectTo(redirect) + } case false => S.redirectTo(redirect) } diff --git a/obp-api/src/main/scala/code/opentok/OpenTokUtil.scala b/obp-api/src/main/scala/code/opentok/OpenTokUtil.scala index e4234eb89..fba74bb52 100644 --- a/obp-api/src/main/scala/code/opentok/OpenTokUtil.scala +++ b/obp-api/src/main/scala/code/opentok/OpenTokUtil.scala @@ -1,47 +1,47 @@ -package code.opentok - -import code.api.util.APIUtil -import com.opentok._ -import com.opentok.exception.OpenTokException - -object OpenTokUtil { - private var session: Session = null - - def createOpenTok: OpenTok = { - // Set the following constants with the API key and API secret - // that you receive when you sign up to use the OpenTok API: - val apiKey: Int = APIUtil.getPropsValue("meeting.tokbox_api_key", "0000").toInt - val apiSecret: String = APIUtil.getPropsValue("meeting.tokbox_api_secret", "YOUR API SECRET") - val opentok: OpenTok = new OpenTok(apiKey, apiSecret) - return opentok - } - - @throws[OpenTokException] - def getSession: Session = { - if (session == null) { - // A session that uses the OpenTok Media Router: - session = createOpenTok.createSession(new SessionProperties.Builder().mediaMode(MediaMode.ROUTED).build) - } - return session - } - - @throws[OpenTokException] - def generateTokenForModerator(expireTimeInMinutes: Int): String = { - // Generate a token. Use the Role MODERATOR. Expire time is defined by parameter expireTimeInMinutes. - val token: String = session.generateToken(new TokenOptions.Builder().role(Role.MODERATOR).expireTime((System.currentTimeMillis / 1000L) + (expireTimeInMinutes * 60)).data // in expireTimeInMinutes - ("name=Simon").build) - return token - } - - @throws[OpenTokException] - def generateTokenForPublisher(expireTimeInMinutes: Int): String = { - // Generate a token. Use the Role PUBLISHER. Expire time is defined by parameter expireTimeInMinutes. - val token: String = session.generateToken(new TokenOptions.Builder().role(Role.PUBLISHER).expireTime((System.currentTimeMillis / 1000L) + (expireTimeInMinutes * 60)).data // in expireTimeInMinutes - ("name=Simon").build) - return token - } -} - -class OpenTokUtil() // Empty constructor - extends Exception { -} \ No newline at end of file +//package code.opentok +// +//import code.api.util.APIUtil +//import com.opentok._ +//import com.opentok.exception.OpenTokException +// +//object OpenTokUtil { +// private var session: Session = null +// +// def createOpenTok: OpenTok = { +// // Set the following constants with the API key and API secret +// // that you receive when you sign up to use the OpenTok API: +// val apiKey: Int = APIUtil.getPropsValue("meeting.tokbox_api_key", "0000").toInt +// val apiSecret: String = APIUtil.getPropsValue("meeting.tokbox_api_secret", "YOUR API SECRET") +// val opentok: OpenTok = new OpenTok(apiKey, apiSecret) +// return opentok +// } +// +// @throws[OpenTokException] +// def getSession: Session = { +// if (session == null) { +// // A session that uses the OpenTok Media Router: +// session = createOpenTok.createSession(new SessionProperties.Builder().mediaMode(MediaMode.ROUTED).build) +// } +// return session +// } +// +// @throws[OpenTokException] +// def generateTokenForModerator(expireTimeInMinutes: Int): String = { +// // Generate a token. Use the Role MODERATOR. Expire time is defined by parameter expireTimeInMinutes. +// val token: String = session.generateToken(new TokenOptions.Builder().role(Role.MODERATOR).expireTime((System.currentTimeMillis / 1000L) + (expireTimeInMinutes * 60)).data // in expireTimeInMinutes +// ("name=Simon").build) +// return token +// } +// +// @throws[OpenTokException] +// def generateTokenForPublisher(expireTimeInMinutes: Int): String = { +// // Generate a token. Use the Role PUBLISHER. Expire time is defined by parameter expireTimeInMinutes. +// val token: String = session.generateToken(new TokenOptions.Builder().role(Role.PUBLISHER).expireTime((System.currentTimeMillis / 1000L) + (expireTimeInMinutes * 60)).data // in expireTimeInMinutes +// ("name=Simon").build) +// return token +// } +//} +// +//class OpenTokUtil() // Empty constructor +// extends Exception { +//} \ No newline at end of file diff --git a/obp-api/src/main/scala/code/remotedata/RemotedataAccountHoldersActor.scala b/obp-api/src/main/scala/code/remotedata/RemotedataAccountHoldersActor.scala index ab0636081..c4c2fa7fd 100644 --- a/obp-api/src/main/scala/code/remotedata/RemotedataAccountHoldersActor.scala +++ b/obp-api/src/main/scala/code/remotedata/RemotedataAccountHoldersActor.scala @@ -34,7 +34,11 @@ class RemotedataAccountHoldersActor extends Actor with ObpActorHelper with MdcLo case cc.bulkDeleteAllAccountHolders() => logger.debug(s"bulkDeleteAllAccountHolders()") - sender ! (mapper.bulkDeleteAllAccountHolders()) + sender ! (mapper.bulkDeleteAllAccountHolders()) + + case cc.deleteAccountHolder(user: User, bankAccountUID :BankIdAccountId) => + logger.debug(s"deleteAccountHolder($user,$bankAccountUID)") + sender ! (mapper.deleteAccountHolder(user, bankAccountUID)) case message => logger.warn("[AKKA ACTOR ERROR - REQUEST NOT RECOGNIZED] " + message) } diff --git a/obp-api/src/main/scala/code/remotedata/RemotedataCounterpartiesActor.scala b/obp-api/src/main/scala/code/remotedata/RemotedataCounterpartiesActor.scala index bcc6bb90b..6963379d3 100644 --- a/obp-api/src/main/scala/code/remotedata/RemotedataCounterpartiesActor.scala +++ b/obp-api/src/main/scala/code/remotedata/RemotedataCounterpartiesActor.scala @@ -92,6 +92,17 @@ class RemotedataCounterpartiesActor extends Actor with ObpActorHelper with MdcLo case cc.getCounterparties(thisBankId: BankId, thisAccountId: AccountId, viewId: ViewId) => logger.debug(s"getCounterparties($thisBankId)") sender ! (mapper.getCounterparties(thisBankId, thisAccountId, viewId)) + + case cc.getCounterpartyByRoutings(otherBankRoutingScheme: String, + otherBankRoutingAddress: String, + otherBranchRoutingScheme: String, + otherBranchRoutingAddress: String, + otherAccountRoutingScheme: String, + otherAccountRoutingAddress: String) => + logger.debug(s"getCounterpartyByRoutings($otherBankRoutingScheme,$otherBankRoutingAddress,$otherBranchRoutingScheme,$otherBranchRoutingAddress,$otherAccountRoutingScheme,$otherAccountRoutingAddress)") + sender ! (mapper.getCounterpartyByRoutings(otherBankRoutingScheme, + otherBankRoutingAddress, otherBranchRoutingScheme, + otherBranchRoutingAddress, otherAccountRoutingScheme, otherAccountRoutingAddress)) case cc.getCounterpartyByIban(iban: String) => logger.debug(s"getOrCreateMetadata($iban)") diff --git a/obp-api/src/main/scala/code/sandbox/CreateOBPUsers.scala b/obp-api/src/main/scala/code/sandbox/CreateOBPUsers.scala index 3b7ffc6a3..55171e84b 100644 --- a/obp-api/src/main/scala/code/sandbox/CreateOBPUsers.scala +++ b/obp-api/src/main/scala/code/sandbox/CreateOBPUsers.scala @@ -1,6 +1,6 @@ package code.sandbox -import code.api.util.APIUtil.validatePasswordOnCreation +import code.api.util.APIUtil.fullPasswordValidation import code.api.util.ErrorMessages import code.model.dataAccess.{AuthUser, ResourceUser} import code.users.Users @@ -38,7 +38,7 @@ trait CreateAuthUsers { .validated(true) val validationErrors = authUser.validate - if (!validatePasswordOnCreation(u.password)) Failure(ErrorMessages.InvalidStrongPasswordFormat) + if (!fullPasswordValidation(u.password)) Failure(ErrorMessages.InvalidStrongPasswordFormat) else if(!validationErrors.isEmpty) Failure(s"Errors: ${validationErrors.map(_.msg)}") else Full(asSaveable(authUser)) } diff --git a/obp-api/src/main/scala/code/search/search.scala b/obp-api/src/main/scala/code/search/search.scala index 7e6e3dbf4..b0c6bdf7e 100644 --- a/obp-api/src/main/scala/code/search/search.scala +++ b/obp-api/src/main/scala/code/search/search.scala @@ -1,33 +1,26 @@ package code.search import java.nio.charset.Charset - -import dispatch.{Http, url} -import code.util.Helper.MdcLoggable - -import scala.concurrent.Await -import scala.concurrent.duration.Duration -import net.liftweb.http.{InMemoryResponse, JsonResponse, LiftResponse} -import net.liftweb.json.JsonAST._ -import net.liftweb.util.Helpers -import net.liftweb.util.Props -import dispatch._ -import Defaults._ -import net.liftweb.json import java.util.Date import code.api.util.APIUtil import code.api.util.ErrorMessages._ -import com.sksamuel.elastic4s.ElasticsearchClientUri -import org.elasticsearch.common.settings.Settings -import com.sksamuel.elastic4s.http.HttpClient -import com.sksamuel.elastic4s.mappings.FieldType._ -import com.sksamuel.elastic4s.http.ElasticDsl._ -import dispatch.as.String.charset +import code.util.Helper.MdcLoggable +import com.sksamuel.elastic4s.http.JavaClient +import com.sksamuel.elastic4s.{ElasticClient, ElasticProperties} +import dispatch.Defaults._ +import dispatch.{Http, url, _} import net.liftweb.common.{Box, Empty, Failure, Full} import net.liftweb.http.provider.HTTPCookie +import net.liftweb.http.{InMemoryResponse, JsonResponse, LiftResponse} +import net.liftweb.json import net.liftweb.json.JsonAST +import net.liftweb.json.JsonAST._ +import net.liftweb.util.Helpers +import org.elasticsearch.common.settings.Settings +import scala.concurrent.Await +import scala.concurrent.duration.Duration import scala.util.control.NoStackTrace @@ -250,22 +243,23 @@ class elasticsearchMetrics extends elasticsearch { if (esIndex.contains(",")) throw new RuntimeException("Props error: es.metrics.index can not be a list") - var client:HttpClient = null - + val props = ElasticProperties(s"http://$esHost:${esPortTCP.toInt}") + val client = ElasticClient(JavaClient(props)) + // we must import the dsl + import com.sksamuel.elastic4s.ElasticDsl._ + if (APIUtil.getPropsAsBoolValue("allow_elasticsearch", false) && APIUtil.getPropsAsBoolValue("allow_elasticsearch_metrics", false) ) { - val settings = Settings.builder().put("cluster.name", APIUtil.getPropsValue("es.cluster.name", "elasticsearch")).build() - client = HttpClient(ElasticsearchClientUri(esHost, esPortTCP.toInt)) try { client.execute { - createIndex(esIndex).mappings( - mapping("request") as ( - textField("userId"), - textField("url"), - dateField("date"), - textField("userName"), - textField("appName"), - textField("developerEmail"), - textField("correlationId") + createIndex(s"$esIndex/request").mapping( + properties ( + textField("userId"), + textField("url"), + dateField("date"), + textField("userName"), + textField("appName"), + textField("developerEmail"), + textField("correlationId") ) ) } @@ -278,8 +272,10 @@ class elasticsearchMetrics extends elasticsearch { def indexMetric(userId: String, url: String, date: Date, duration: Long, userName: String, appName: String, developerEmail: String, correlationId: String) { if (APIUtil.getPropsAsBoolValue("allow_elasticsearch", false) && APIUtil.getPropsAsBoolValue("allow_elasticsearch_metrics", false) ) { try { + // we must import the dsl + import com.sksamuel.elastic4s.ElasticDsl._ client.execute { - indexInto(esIndex / "request") fields ( + indexInto(s"$esIndex/request") fields ( "userId" -> userId, "url" -> url, "date" -> date, @@ -304,77 +300,12 @@ class elasticsearchWarehouse extends elasticsearch { override val esPortTCP = APIUtil.getPropsValue("es.warehouse.port.tcp","9300") override val esPortHTTP = APIUtil.getPropsValue("es.warehouse.port.http","9200") override val esIndex = APIUtil.getPropsValue("es.warehouse.index", "warehouse") - var client:HttpClient = null + val props = ElasticProperties(s"http://$esHost:${esPortTCP.toInt}") + var client: ElasticClient = null if (APIUtil.getPropsAsBoolValue("allow_elasticsearch", false) && APIUtil.getPropsAsBoolValue("allow_elasticsearch_warehouse", false) ) { val settings = Settings.builder().put("cluster.name", APIUtil.getPropsValue("es.cluster.name", "elasticsearch")).build() - client = HttpClient(ElasticsearchClientUri(esHost, esPortTCP.toInt)) + client = ElasticClient(JavaClient(props)) } } -/* -class elasticsearchOBP extends elasticsearch { - override val esHost = APIUtil.getPropsValue("es.obp.host","localhost") - override val esPortTCP = APIUtil.getPropsValue("es.obp.port.tcp","9300") - override val esPortHTTP = APIUtil.getPropsValue("es.obp.port.tcp","9200") - override val esIndex = APIUtil.getPropsValue("es.obp.index", "obp") - val accountIndex = "account_v1.2.1" - val transactionIndex = "transaction_v1.2.1" - - var client:TcpClient = null - - if (APIUtil.getPropsAsBoolValue("allow_elasticsearch", false) ) { - client = TcpClient.transport("elasticsearch://" + esHost + ":" + esPortTCP + ",") - - client.execute { - create index accountIndex mappings ( - "account" as ( - "viewId" typed StringType, - "account" typed ObjectType - ) - ) - } - - client.execute { - create index transactionIndex mappings ( - "transaction" as ( - "viewId" typed StringType, - "transaction" typed ObjectType - ) - ) - } - } - /* - Index objects in Elastic Search. - Use **the same** representations that we return in the REST API. - Use the name singular_object_name-version e.g. transaction-v1.2.1 for the index name / type - */ - - // Index a Transaction - // Put into a index that has the viewId and version in the name. - def indexTransaction(viewId: String, transaction: TransactionJSON) { - if (APIUtil.getPropsAsBoolValue("allow_elasticsearch", false) ) { - client.execute { - index into transactionIndex / "transaction" fields ( - "viewId" -> viewId, - "transaction" -> transaction - ) - } - } - } - - // Index an Account - // Put into a index that has the viewId and version in the name. - def indexAccount(viewId: String, account: AccountJSON) { - if (APIUtil.getPropsAsBoolValue("allow_elasticsearch", false) ) { - client.execute { - index into accountIndex / "account" fields ( - "viewId" -> viewId, - "account" -> account - ) - } - } - } - - } -*/ diff --git a/obp-api/src/main/scala/code/snippet/ConsumerRegistration.scala b/obp-api/src/main/scala/code/snippet/ConsumerRegistration.scala index 4e559d2ff..ac09c30e6 100644 --- a/obp-api/src/main/scala/code/snippet/ConsumerRegistration.scala +++ b/obp-api/src/main/scala/code/snippet/ConsumerRegistration.scala @@ -60,6 +60,7 @@ class ConsumerRegistration extends MdcLoggable { private object appType extends RequestVar("Unknown") private object clientCertificateVar extends RequestVar("") private object signingAlgVar extends RequestVar("") + private object oidcCheckboxVar extends RequestVar(false) private object jwksUriVar extends RequestVar("") private object jwksVar extends RequestVar("") private object submitButtonDefenseFlag extends RequestVar("") @@ -113,6 +114,7 @@ class ConsumerRegistration extends MdcLoggable { if(HydraUtil.integrateWithHydra) { "#app-client_certificate" #> SHtml.textarea(clientCertificateVar, clientCertificateVar (_))& "#app-request_uri" #> SHtml.text(requestUriVar, requestUriVar(_)) & + "#oidc_checkbox" #> SHtml.checkbox(oidcCheckboxVar, oidcCheckboxVar(_)) & "#app-signing_alg" #> SHtml.select(signingAlgs, Box!! signingAlgVar.is, signingAlgVar(_)) & "#app-jwks_uri" #> SHtml.text(jwksUriVar, jwksUriVar(_)) & "#app-jwks" #> SHtml.textarea(jwksVar, jwksVar(_)) @@ -135,12 +137,17 @@ class ConsumerRegistration extends MdcLoggable { if(HydraUtil.integrateWithHydra) { HydraUtil.createHydraClient(consumer, oAuth2Client => { val signingAlg = signingAlgVar.is + + if(oidcCheckboxVar.is == false) { + // TODO Set token_endpoint_auth_method in accordance to the Consumer.AppType value + // Consumer.AppType = Confidential => client_secret_post + // Consumer.AppType = Public => private_key_jwt + // Consumer.AppType = Unknown => private_key_jwt + oAuth2Client.setTokenEndpointAuthMethod(HydraUtil.hydraTokenEndpointAuthMethod) + } else { + oAuth2Client.setTokenEndpointAuthMethod(HydraUtil.clientSecretPost) + } - // TODO Set token_endpoint_auth_method in accordance to the Consumer.AppType value - // Consumer.AppType = Confidential => client_secret_post - // Consumer.AppType = Public => private_key_jwt - // Consumer.AppType = Unknown => private_key_jwt - oAuth2Client.setTokenEndpointAuthMethod(HydraUtil.hydraTokenEndpointAuthMethod) oAuth2Client.setTokenEndpointAuthSigningAlg(signingAlg) oAuth2Client.setRequestObjectSigningAlg(signingAlg) diff --git a/obp-api/src/main/scala/code/snippet/WebUI.scala b/obp-api/src/main/scala/code/snippet/WebUI.scala index af16f4390..8cd0a3821 100644 --- a/obp-api/src/main/scala/code/snippet/WebUI.scala +++ b/obp-api/src/main/scala/code/snippet/WebUI.scala @@ -44,6 +44,7 @@ import net.liftweb.util.PassThru import scala.xml.{NodeSeq, XML} import scala.io.Source import code.webuiprops.MappedWebUiPropsProvider.getWebUiPropsValue +import net.liftweb.common.{Box, Full} class WebUI extends MdcLoggable{ @@ -64,6 +65,27 @@ class WebUI extends MdcLoggable{ } } + def currentPage = { + def removeLocale(s: Box[String]) = { + s.map(_.replaceAll("&locale=es_ES", "") + .replaceAll("&locale=en_EN", "") + .replaceAll("\\?locale=es_ES", "") + .replaceAll("\\?locale=en_EN", "")) + } + val page = Constant.HostName + removeLocale(S.uriAndQueryString).getOrElse("") + S.queryString.map(_.replaceAll("locale=es_ES", "").replaceAll("locale=en_EN", "")) match { + case Full(queryString) if queryString.isEmpty => + "#es a [href]" #> scala.xml.Unparsed(s"${page}?locale=es_ES") & + "#en a [href]" #> scala.xml.Unparsed(s"${page}?locale=en_EN") + case Full(queryString) => + "#es a [href]" #> scala.xml.Unparsed(s"${page}&locale=es_ES") & + "#en a [href]" #> scala.xml.Unparsed(s"${page}&locale=en_EN") + case _ => + "#es a [href]" #> scala.xml.Unparsed(s"${page}?locale=es_ES") & + "#en a [href]" #> scala.xml.Unparsed(s"${page}?locale=en_EN") + } + + } // Cookie Consent button. diff --git a/obp-api/src/main/scala/code/transactionChallenge/MappedChallengeProvider.scala b/obp-api/src/main/scala/code/transactionChallenge/MappedChallengeProvider.scala index df5536bd9..629cdb3a2 100644 --- a/obp-api/src/main/scala/code/transactionChallenge/MappedChallengeProvider.scala +++ b/obp-api/src/main/scala/code/transactionChallenge/MappedChallengeProvider.scala @@ -68,7 +68,7 @@ object MappedChallengeProvider extends ChallengeProvider { expiredDateTime: Long = createDateTime.getTime+challengeTTL currentTime: Long = Platform.currentTime - challenge <- if(currentAttemptCounterValue <3){ + challenge <- if(currentAttemptCounterValue < APIUtil.allowedAnswerTransactionRequestChallengeAttempts){ if(expiredDateTime > currentTime) { val currentHashedAnswer = BCrypt.hashpw(challengeAnswer, challenge.salt).substring(0, 44) val expectedHashedAnswer = challenge.expectedAnswer diff --git a/obp-api/src/main/scala/code/transactionRequestAttribute/MappedTransactionRequestAttributeProvider.scala b/obp-api/src/main/scala/code/transactionRequestAttribute/MappedTransactionRequestAttributeProvider.scala index 01a1c5582..89e3f823e 100644 --- a/obp-api/src/main/scala/code/transactionRequestAttribute/MappedTransactionRequestAttributeProvider.scala +++ b/obp-api/src/main/scala/code/transactionRequestAttribute/MappedTransactionRequestAttributeProvider.scala @@ -99,7 +99,7 @@ object MappedTransactionRequestAttributeProvider extends TransactionRequestAttri .TransactionRequestId(transactionRequestId.value) .Name(name) .Type(attributeType.toString) - .Value(value) + .`Value`(value) .saveMe() } case _ => Empty @@ -112,7 +112,7 @@ object MappedTransactionRequestAttributeProvider extends TransactionRequestAttri .TransactionRequestId(transactionRequestId.value) .Name(name) .Type(attributeType.toString()) - .Value(value) + .`Value`(value) .saveMe() } } @@ -131,7 +131,7 @@ object MappedTransactionRequestAttributeProvider extends TransactionRequestAttri .BankId(bankId.value) .Name(transactionRequestAttribute.name) .Type(transactionRequestAttribute.attributeType.toString()) - .Value(transactionRequestAttribute.value) + .`Value`(transactionRequestAttribute.value) .saveMe() } } diff --git a/obp-api/src/main/scala/code/transactionRequestAttribute/TransactionRequestAttribute.scala b/obp-api/src/main/scala/code/transactionRequestAttribute/TransactionRequestAttribute.scala index ee8951339..99c55d000 100644 --- a/obp-api/src/main/scala/code/transactionRequestAttribute/TransactionRequestAttribute.scala +++ b/obp-api/src/main/scala/code/transactionRequestAttribute/TransactionRequestAttribute.scala @@ -21,7 +21,7 @@ class TransactionRequestAttribute extends TransactionRequestAttributeTrait with override def attributeType: TransactionRequestAttributeType.Value = TransactionRequestAttributeType.withName(Type.get) - override def value: String = Value.get + override def value: String = `Value`.get object BankId extends UUIDString(this) // combination of this @@ -33,7 +33,7 @@ class TransactionRequestAttribute extends TransactionRequestAttributeTrait with object Type extends MappedString(this, 50) - object Value extends MappedString(this, 255) + object `Value` extends MappedString(this, 255) } diff --git a/obp-api/src/main/scala/code/users/MappedUserAttribute.scala b/obp-api/src/main/scala/code/users/MappedUserAttribute.scala index d81decb22..6b281c10d 100644 --- a/obp-api/src/main/scala/code/users/MappedUserAttribute.scala +++ b/obp-api/src/main/scala/code/users/MappedUserAttribute.scala @@ -39,7 +39,7 @@ object MappedUserAttributeProvider extends UserAttributeProvider { .UserId(userId) .Name(name) .Type(attributeType.toString) - .Value(value) + .`Value`(value) .saveMe() } case _ => Empty @@ -51,7 +51,7 @@ object MappedUserAttributeProvider extends UserAttributeProvider { .UserId(userId) .Name(name) .Type(attributeType.toString()) - .Value(value) + .`Value`(value) .saveMe() } } @@ -67,13 +67,13 @@ class UserAttribute extends UserAttributeTrait with LongKeyedMapper[UserAttribut object UserId extends MappedUUID(this) object Name extends MappedString(this, 50) object Type extends MappedString(this, 50) - object Value extends MappedString(this, 255) + object `Value` extends MappedString(this, 255) override def userAttributeId: String = UserAttributeId.get override def userId: String = UserId.get override def name: String = Name.get override def attributeType: UserAttributeType.Value = UserAttributeType.withName(Type.get) - override def value: String = Value.get + override def value: String = `Value`.get override def insertDate: Date = createdAt.get } diff --git a/obp-api/src/main/scala/code/users/UserInitActionProvider.scala b/obp-api/src/main/scala/code/users/UserInitActionProvider.scala index 9f28b73d7..20265b676 100644 --- a/obp-api/src/main/scala/code/users/UserInitActionProvider.scala +++ b/obp-api/src/main/scala/code/users/UserInitActionProvider.scala @@ -1,6 +1,5 @@ package code.users -import cats.Now import code.util.Helper.MdcLoggable import net.liftweb.common.{Box, Full} import net.liftweb.mapper.By diff --git a/obp-api/src/main/scala/code/util/HydraUtil.scala b/obp-api/src/main/scala/code/util/HydraUtil.scala index 16c2d36a9..0a4f365a3 100644 --- a/obp-api/src/main/scala/code/util/HydraUtil.scala +++ b/obp-api/src/main/scala/code/util/HydraUtil.scala @@ -25,6 +25,8 @@ object HydraUtil extends MdcLoggable{ val mirrorConsumerInHydra = APIUtil.getPropsAsBoolValue("mirror_consumer_in_hydra", false) + val clientSecretPost = "client_secret_post" + val hydraTokenEndpointAuthMethod = APIUtil.getPropsValue("hydra_token_endpoint_auth_method", "private_key_jwt") @@ -40,7 +42,7 @@ object HydraUtil extends MdcLoggable{ .openOrThrowException(s"If props $INTEGRATE_WITH_HYDRA is true, hydra_client_scope value should not be blank") .trim.split("""\s*,\s*""").toList - private lazy val allConsents = hydraConsents.mkString("openid offline ", " ","") + private lazy val allConsents = hydraConsents.mkString("openid offline email profile ", " ","") val grantTypes = ("authorization_code" :: "client_credentials" :: "refresh_token" :: "implicit" :: Nil).asJava diff --git a/obp-api/src/main/scala/code/util/NewAttributeQueryTrait.scala b/obp-api/src/main/scala/code/util/NewAttributeQueryTrait.scala index 15399b269..4d5f14769 100644 --- a/obp-api/src/main/scala/code/util/NewAttributeQueryTrait.scala +++ b/obp-api/src/main/scala/code/util/NewAttributeQueryTrait.scala @@ -14,12 +14,12 @@ trait NewAttributeQueryTrait { // TODO Should we rename this column to attributeName private lazy val nameColumn = Name.dbColumnName // TODO Should we rename this column to attributeValue - private lazy val valueColumn = Value.dbColumnName + private lazy val valueColumn = `Value`.dbColumnName private lazy val parentIdColumn = ParentId.dbColumnName private lazy val bankIdColumn = BankId.dbColumnName val BankId: BaseMappedField val Name: BaseMappedField - val Value: BaseMappedField + val `Value`: BaseMappedField /** * Attribute entity's parent id, for example: CustomerAttribute.customerId, * need implemented in companion object diff --git a/obp-api/src/main/webapp/consumer-registration.html b/obp-api/src/main/webapp/consumer-registration.html index 68641846d..51bb8f81f 100644 --- a/obp-api/src/main/webapp/consumer-registration.html +++ b/obp-api/src/main/webapp/consumer-registration.html @@ -153,6 +153,10 @@ Berlin 13359, Germany +
+ + +
diff --git a/obp-api/src/main/webapp/index.html b/obp-api/src/main/webapp/index.html index 4a5944c90..3f125cb60 100644 --- a/obp-api/src/main/webapp/index.html +++ b/obp-api/src/main/webapp/index.html @@ -31,10 +31,12 @@ Berlin 13359, Germany
-

Welcome to the Open Bank Project API Sandbox test instance!

+

Welcome to the Open Bank Project API Sandbox test instance!

- View API Explorer - Introduction + + View API Explorer + + Introduction @@ -46,7 +48,7 @@ Berlin 13359, Germany
-

Get started

+

Get started

@@ -55,10 +57,9 @@ Berlin 13359, Germany item-1
-

Create an account

-

First, create a free developer account on this sandbox and request a developer key. You will be asked - to submit basic information about your app at this stage. Register for - an account.

+

Create an account

+

First, create a free developer account on this sandbox and request a developer key. You will be asked to submit basic information about your app at this stage.Register for an account + .

@@ -69,10 +70,10 @@ Berlin 13359, Germany item-2
-

Connect your app

-

Use our SDKs to connect your app to the Open Bank Project APIs. You’ll need your developer key, which +

Connect your app

+

Use our SDKs to connect your app to the Open Bank Project APIs. You’ll need your developer key, which you should have from when you created your account. Check out all the available APIs on the API - Explorer, but make sure that you’re using the correct base URL.

+ Explorer, but make sure that you’re using the correct base URL.

@@ -83,16 +84,17 @@ Berlin 13359, Germany item-3
-

Test your app using customer data

+

Test your app using customer data

- Once your app is connected, you can test it using test customer credentials. + Once your app is connected, you can test it using test customer credentials. + View sandbox customer log ons. + href="https://github.com/OpenBankProject/OBP-API/wiki/">View sandbox customer log ons.


@@ -110,15 +112,15 @@ Berlin 13359, Germany
-

Explore APIs

+

Explore APIs

-

Branches, ATMs and ProductsBranches chevron

-

Access open data related to banks including branches and ATMs including geolocation and opening hours.

+

Branches, ATMs and ProductsBranches chevron

+

Access open data related to banks including branches and ATMs including geolocation and opening hours.

@@ -146,8 +148,8 @@ Berlin 13359, Germany height="100" alt="transactions icon"/>
-

TransactionsTransactions chevron

-

Access the transaction history and transaction metadata.

+

TransactionsTransactions chevron

+

Access the transaction history and transaction metadata.

@@ -159,9 +161,9 @@ Berlin 13359, Germany height="100" alt="metadata"/>
-

MetadataMetadata chevron

-

Enrich transactions and counterparties with metadata including geolocations, comments, pictures - and tags (e.g. category of spending).

+

MetadataMetadata chevron

+

Enrich transactions and counterparties with metadata including geolocations, comments, pictures + and tags (e.g. category of spending).

@@ -178,9 +180,9 @@ Berlin 13359, Germany alt="counterparties"/>
-

CounterpartiesCounterparties chevron

-

Access the payers and payees of an account including metadata such as their aliases, labels, - logos and home pages.

+

MetadataCounterparties chevron

+

Access the payers and payees of an account including metadata such as their aliases, labels, + logos and home pages.

@@ -190,8 +192,8 @@ Berlin 13359, Germany src="/media/images/icons/apis/icon-entitlements.png" alt="entitlements"/>
-

WebhooksWebhooks chevron

-

Call external web services based on Account events.

+

WebhooksWebhooks chevron

+

Call external web services based on Account events.

@@ -203,8 +205,8 @@ Berlin 13359, Germany src="/media/images/icons/apis/icon-messages.png" alt="messages"/>
-

Customer onboarding and KYCCustomer onboarding and KYC chevron

-

Perform user, customer and account creation. Manage Know Your Customer (KYC) documents, media and status. Create customer meetings and messages.

+

Customer onboarding and KYCCustomer onboarding and KYC chevron

+

Perform user, customer and account creation. Manage Know Your Customer (KYC) documents, media and status. Create customer meetings and messages.

@@ -215,8 +217,8 @@ Berlin 13359, Germany src="/media/images/icons/apis/icon-security.png" alt="security"/>
-

API Roles, Metrics and DocumentationAPI Roles, Metrics and Documentation chevron

-

Control access to endpoints, get API metrics and documentation.

+

API Roles, Metrics and DocumentationAPI Roles, Metrics and Documentation chevron

+

Control access to endpoints, get API metrics and documentation.

@@ -229,8 +231,8 @@ Berlin 13359, Germany src="/media/images/icons/apis/icon-requests.png" alt="requests"/>
-

Payments & TransfersPayments & Transfers chevron

-

Initiate Transaction Requests (transfers and payments). View and confirm charges (as per PSD2). Answer strong customer authentication (SCA) challenges.

+

Payments & TransfersPayments & Transfers chevron

+

Initiate Transaction Requests (transfers and payments). View and confirm charges (as per PSD2). Answer strong customer authentication (SCA) challenges.

@@ -239,8 +241,8 @@ Berlin 13359, Germany kyc
-

Search warehousekyc chevron

-

Perform advanced searches and statistics queries on the data warehouse.

+

Search warehousekyc chevron

+

Perform advanced searches and statistics queries on the data warehouse.

@@ -268,13 +270,13 @@ Berlin 13359, Germany
-

Support

+

Support

mail -

Email

+

Email

contact@openbankproject.com
@@ -311,20 +313,23 @@ Berlin 13359, Germany
-

Get started building your application

+

Get started building your application

-

For banks

+

For Banks

API Manager + data-lift="WebUI.apiManagerLink" href=""> + API Manager OBP CLI API Tester + data-lift="WebUI.apiTesterLink" href=""> + API Tester Hola
diff --git a/obp-api/src/main/webapp/templates-hidden/_login.html b/obp-api/src/main/webapp/templates-hidden/_login.html index 4517637b6..d76df7c2f 100644 --- a/obp-api/src/main/webapp/templates-hidden/_login.html +++ b/obp-api/src/main/webapp/templates-hidden/_login.html @@ -1,19 +1,19 @@
-

Log on to the Open Bank Project API

+

Log on to the Open Bank Project API

Special Instructions