diff --git a/obp-api/pom.xml b/obp-api/pom.xml index 714d8986c..4b8194138 100644 --- a/obp-api/pom.xml +++ b/obp-api/pom.xml @@ -79,16 +79,6 @@ org.slf4j 1.7.26 - - org.apache.logging.log4j - log4j-api - ${log4j.version} - - - org.apache.logging.log4j - log4j-core - ${log4j.version} - org.apache.kafka kafka-clients @@ -360,16 +350,6 @@ geocalc 0.5.7 - - - - io.github.embeddedkafka - embedded-kafka_2.12 - 2.4.1.1 - test - - - com.twilio.sdk twilio diff --git a/obp-api/src/main/resources/props/sample.props.template b/obp-api/src/main/resources/props/sample.props.template index 401c2fe1a..1468010a1 100644 --- a/obp-api/src/main/resources/props/sample.props.template +++ b/obp-api/src/main/resources/props/sample.props.template @@ -865,7 +865,9 @@ create_just_in_time_entitlements=false # if create_just_in_time_entitlements=true then OBP does the following: # If a user is trying to use a Role (via an endpoint) and the user could grant them selves the required Role(s), then OBP automatically grants the Role! # i.e. if the User already has canCreateEntitlementAtOneBank or canCreateEntitlementAtAnyBank and then OBP will auto grant a role that could be granted by a manual process anyway. -# This speeds up the process of granting of roles. Certain roles are excluded from this automation. +# This speeds up the process of granting of roles. Certain roles are excluded from this automation: +# - CanCreateEntitlementAtOneBank +# - CanCreateEntitlementAtAnyBank # If create_just_in_time_entitlements is again set to false after it was true for a while, any auto granted Entitlements to roles are kept in place. # Note: In the entitlements model we set createdbyprocess="create_just_in_time_entitlements". For manual operations we set createdbyprocess="manual" # ------------------------------------------------------------- @@ -1039,9 +1041,18 @@ outboundAdapterCallContext.generalContext # ------------------------------ default entitlements end ------------------------------ ## Mirror request headers to response +# This feature is driven by FAPI requirements. For instance: +# The resource server with the FAPI endpoints +# - shall set the response header x-fapi-interaction-id to the value +# received from the corresponding fapi client request header or to a RFC4122 UUID value +# if the request header was not provided to track the interaction, e.g., +# x-fapi-interaction-id: c770aef3-6784-41f7-8e0e-ff5f97bddb3a; and +# - shall log the value of x-fapi-interaction-id in the log entry. # mirror_request_headers_to_response=x-fapi-interaction-id,x-jws-signature ## Echo all request headers to response +# Rename all request headers by prepending the word "echo_" and sends them back as response headers +# This feature helps to reveal information does every request header sent at a client side really reach a server side echo_request_headers=false ### enable or disable the feature of send "Force-Error" header, default value is false @@ -1275,4 +1286,8 @@ validate_iban=false # Show all dependent connector methods for each endpoint. The default value is false. # If set to true, it may consume a significant amount of heap memory. -#show_used_connector_methods=false \ No newline at end of file +#show_used_connector_methods=false + +# This returns Regulated Entities +# sample props regulated_entities = [{"certificate_authority_ca_owner_id":"CY_CBC","entity_certificate_public_key":"-----BEGIN CERTIFICATE-----MIICsjCCAZqgAwIBAgIGAYwQ62R0MA0GCSqGSIb3DQEBCwUAMBoxGDAWBgNVBAMMD2FwcC5leGFtcGxlLmNvbTAeFw0yMzExMjcxMzE1MTFaFw0yNTExMjYxMzE1MTFaMBoxGDAWBgNVBAMMD2FwcC5leGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK9WIodZHWzKyCcf9YfWEhPURbfO6zKuMqzHN27GdqHsVVEGxP4F/J4mso+0ENcRr6ur4u81iREaVdCc40rHDHVJNEtniD8Icbz7tcsqAewIVhc/q6WXGqImJpCq7hA0m247dDsaZT0lb/MVBiMoJxDEmAE/GYYnWTEn84R35WhJsMvuQ7QmLvNg6RkChY6POCT/YKe9NKwa1NqI1U+oA5RFzAaFtytvZCE3jtp+aR0brL7qaGfgxm6B7dEpGyhg0NcVCV7xMQNq2JxZTVdAr6lcsRGaAFulakmW3aNnmK+L35Wu8uW+OxNxwUuC6f3b4FVBa276FMuUTRfu7gc+k6kCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAAU5CjEyAoyTn7PgFpQD48ZNPuUsEQ19gzYgJvHMzFIoZ7jKBodjO5mCzWBcR7A4mpeAsdyiNBl2sTiZscSnNqxk61jVzP5Ba1D7XtOjjr7+3iqowrThj6BY40QqhYh/6BSY9fDzVZQiHnvlo6ZUM5kUK6OavZOovKlp5DIl5sGqoP0qAJnpQ4nhB2WVVsKfPlOXc+2KSsbJ23g9l8zaTMr+X0umlvfEKqyEl1Fa2L1dO0y/KFQ+ILmxcZLpRdq1hRAjd0quq9qGC8ucXhRWDgM4hslVpau0da68g0aItWNez3mc5lB82b3dcZpFMzO41bgw7gvw10AvvTfQDqEYIuQ==-----END CERTIFICATE-----","entity_code":"PSD_PICY_CBC!12345","entity_type":"PSD_PI","entity_address":"EXAMPLE COMPANY LTD, 5 SOME STREET","entity_town_city":"SOME CITY","entity_post_code":"1060","entity_country":"CY","entity_web_site":"www.example.com","services":[{"CY":["PS_010","PS_020","PS_03C","PS_04C"]}]}] +regulated_entities = [] \ No newline at end of file diff --git a/obp-api/src/main/scala/bootstrap/liftweb/Boot.scala b/obp-api/src/main/scala/bootstrap/liftweb/Boot.scala index 9ba6c1f1f..286662e31 100644 --- a/obp-api/src/main/scala/bootstrap/liftweb/Boot.scala +++ b/obp-api/src/main/scala/bootstrap/liftweb/Boot.scala @@ -29,7 +29,6 @@ package bootstrap.liftweb import java.io.{File, FileInputStream} import java.util.stream.Collectors import java.util.{Locale, TimeZone} - import code.CustomerDependants.MappedCustomerDependant import code.DynamicData.DynamicData import code.DynamicEndpoint.DynamicEndpoint @@ -107,6 +106,7 @@ import code.productcollectionitem.MappedProductCollectionItem import code.productfee.ProductFee import code.products.MappedProduct import code.ratelimiting.RateLimiting +import code.regulatedentities.MappedRegulatedEntity import code.remotedata.RemotedataActors import code.scheduler.{DataBaseCleanerScheduler, DatabaseDriverScheduler, JobScheduler, MetricsArchiveScheduler} import code.scope.{MappedScope, MappedUserScope} @@ -135,6 +135,7 @@ import code.webuiprops.WebUiProps import com.openbankproject.commons.model.ErrorMessage import com.openbankproject.commons.util.Functions.Implicits._ import com.openbankproject.commons.util.{ApiVersion, Functions} + import javax.mail.internet.MimeMessage import net.liftweb.common._ import net.liftweb.db.{DB, DBLogEntry} @@ -849,7 +850,7 @@ class Boot extends MdcLoggable { .map[String](_.getClientId) .collect(Collectors.toSet()) - Consumers.consumers.vend.getConsumersFuture().foreach{ consumers => + Consumers.consumers.vend.getConsumersFuture(Nil, None).foreach{ consumers => consumers.filter(consumer => consumer.isActive.get && !oAuth2ClientIds.contains(consumer.key.get)) .foreach(HydraUtil.createHydraClient(_)) } @@ -1034,6 +1035,7 @@ object ToSchemify { AuthUser, JobScheduler, MappedETag, + MappedRegulatedEntity, AtmAttribute, Admin, MappedBank, 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 cfd720dd6..84ce088ae 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 @@ -52,6 +52,35 @@ import scala.collection.immutable.List object SwaggerDefinitionsJSON { + lazy val regulatedEntitiesJsonV510: RegulatedEntitiesJsonV510 = RegulatedEntitiesJsonV510(List(regulatedEntityJsonV510)) + lazy val regulatedEntityJsonV510: RegulatedEntityJsonV510 = RegulatedEntityJsonV510( + entity_id = "0af807d7-3c39-43ef-9712-82bcfde1b9ca", + certificate_authority_ca_owner_id = "CY_CBC", + entity_certificate_public_key = "-----BEGIN CERTIFICATE-----MIICsjCCAZqgAwIBAgIGAYwQ62R0MA0GCSqGSIb3DQEBCwUAMBoxGDAWBgNVBAMMD2FwcC5leGFtcGxlLmNvbTAeFw0yMzExMjcxMzE1MTFaFw0yNTExMjYxMzE1MTFaMBoxGDAWBgNVBAMMD2FwcC5leGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK9WIodZHWzKyCcf9YfWEhPURbfO6zKuMqzHN27GdqHsVVEGxP4F/J4mso+0ENcRr6ur4u81iREaVdCc40rHDHVJNEtniD8Icbz7tcsqAewIVhc/q6WXGqImJpCq7hA0m247dDsaZT0lb/MVBiMoJxDEmAE/GYYnWTEn84R35WhJsMvuQ7QmLvNg6RkChY6POCT/YKe9NKwa1NqI1U+oA5RFzAaFtytvZCE3jtp+aR0brL7qaGfgxm6B7dEpGyhg0NcVCV7xMQNq2JxZTVdAr6lcsRGaAFulakmW3aNnmK+L35Wu8uW+OxNxwUuC6f3b4FVBa276FMuUTRfu7gc+k6kCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAAU5CjEyAoyTn7PgFpQD48ZNPuUsEQ19gzYgJvHMzFIoZ7jKBodjO5mCzWBcR7A4mpeAsdyiNBl2sTiZscSnNqxk61jVzP5Ba1D7XtOjjr7+3iqowrThj6BY40QqhYh/6BSY9fDzVZQiHnvlo6ZUM5kUK6OavZOovKlp5DIl5sGqoP0qAJnpQ4nhB2WVVsKfPlOXc+2KSsbJ23g9l8zaTMr+X0umlvfEKqyEl1Fa2L1dO0y/KFQ+ILmxcZLpRdq1hRAjd0quq9qGC8ucXhRWDgM4hslVpau0da68g0aItWNez3mc5lB82b3dcZpFMzO41bgw7gvw10AvvTfQDqEYIuQ==-----END CERTIFICATE-----", + entity_name = "EXAMPLE COMPANY LTD", + entity_code = "PSD_PICY_CBC!12345", + entity_type = "PSD_PI", + entity_address = "EXAMPLE COMPANY LTD, 5 SOME STREET", + entity_town_city = "SOME CITY", + entity_post_code = "1060", + entity_country = "CY", + entity_web_site = "www.example.com", + services = json.parse("""[{"CY":["PS_010","PS_020","PS_03C","PS_04C"]}]""") + ) + + lazy val regulatedEntityPostJsonV510: RegulatedEntityPostJsonV510 = RegulatedEntityPostJsonV510( + certificate_authority_ca_owner_id = "CY_CBC", + entity_certificate_public_key = "-----BEGIN CERTIFICATE-----MIICsjCCAZqgAwIBAgIGAYwQ62R0MA0GCSqGSIb3DQEBCwUAMBoxGDAWBgNVBAMMD2FwcC5leGFtcGxlLmNvbTAeFw0yMzExMjcxMzE1MTFaFw0yNTExMjYxMzE1MTFaMBoxGDAWBgNVBAMMD2FwcC5leGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK9WIodZHWzKyCcf9YfWEhPURbfO6zKuMqzHN27GdqHsVVEGxP4F/J4mso+0ENcRr6ur4u81iREaVdCc40rHDHVJNEtniD8Icbz7tcsqAewIVhc/q6WXGqImJpCq7hA0m247dDsaZT0lb/MVBiMoJxDEmAE/GYYnWTEn84R35WhJsMvuQ7QmLvNg6RkChY6POCT/YKe9NKwa1NqI1U+oA5RFzAaFtytvZCE3jtp+aR0brL7qaGfgxm6B7dEpGyhg0NcVCV7xMQNq2JxZTVdAr6lcsRGaAFulakmW3aNnmK+L35Wu8uW+OxNxwUuC6f3b4FVBa276FMuUTRfu7gc+k6kCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAAU5CjEyAoyTn7PgFpQD48ZNPuUsEQ19gzYgJvHMzFIoZ7jKBodjO5mCzWBcR7A4mpeAsdyiNBl2sTiZscSnNqxk61jVzP5Ba1D7XtOjjr7+3iqowrThj6BY40QqhYh/6BSY9fDzVZQiHnvlo6ZUM5kUK6OavZOovKlp5DIl5sGqoP0qAJnpQ4nhB2WVVsKfPlOXc+2KSsbJ23g9l8zaTMr+X0umlvfEKqyEl1Fa2L1dO0y/KFQ+ILmxcZLpRdq1hRAjd0quq9qGC8ucXhRWDgM4hslVpau0da68g0aItWNez3mc5lB82b3dcZpFMzO41bgw7gvw10AvvTfQDqEYIuQ==-----END CERTIFICATE-----", + entity_name = "EXAMPLE COMPANY LTD", + entity_code = "PSD_PICY_CBC!12345", + entity_type = "PSD_PI", + entity_address = "EXAMPLE COMPANY LTD, 5 SOME STREET", + entity_town_city = "SOME CITY", + entity_post_code = "1060", + entity_country = "CY", + entity_web_site = "www.example.com", + services = """[{"CY":["PS_010","PS_020","PS_03C","PS_04C"]}]""" + ) val license = License( id = licenseIdExample.value, @@ -2640,6 +2669,31 @@ object SwaggerDefinitionsJSON { enabled = true, created = DateWithDayExampleObject ) + lazy val pem = "-----BEGIN CERTIFICATE-----\nMIIFIjCCBAqgAwIBAgIIX3qsz7QQxngwDQYJKoZIhvcNAQELBQAwgZ8xCzAJBgNV\r\nBAYTAkRFMQ8wDQYDVQQIEwZCZXJsaW4xDzANBgNVBAcTBkJlcmxpbjEPMA0GA1UE\r\nChMGVEVTT0JFMRowGAYDVQQLExFURVNPQkUgT3BlcmF0aW9uczESMBAGA1UEAxMJ\r\nVEVTT0JFIENBMR8wHQYJKoZIhvcNAQkBFhBhZG1pbkB0ZXNvYmUuY29tMQwwCgYD\r\nVQQpEwNWUE4wHhcNMjMwNzE3MDg0MDAwWhcNMjQwNzE3MDg0MDAwWjCBizELMAkG\r\nA1UEBhMCREUxDzANBgNVBAgTBkJlcmxpbjEPMA0GA1UEBxMGQmVybGluMRQwEgYD\r\nVQQKEwtUZXNvYmUgR21iSDEPMA0GA1UECxMGc3lzb3BzMRIwEAYDVQQDEwlsb2Nh\r\nbGhvc3QxHzAdBgkqhkiG9w0BCQEWEGFkbWluQHRlc29iZS5jb20wggEiMA0GCSqG\r\nSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCwxGuWUN1H0d0IeYPYWdLA0I/5BXx4DLO6\r\nzfi1GGJlF8BIXRN0VTJckIY9C3J1RnXDs6p6ufA01iHe1PQdL6VzfcaC3j+jUSgV\r\n1z9ybEUPyUwq3PCCxqoVI9n8yh+O6FDn3dvu/9Q2NtBpJHUBDCLf7OO9TgsFU2sE\r\nMys+Hw5DuuX5n5OQ2VIwH+qlMTQnd+yw5y8FKHqAZT5hE60lF/x6sQnwi58hLGRW\r\nSqo/548c2ZpoeWtnyY1I6PyR7zUYGuhruLY8gVFfLE+610u/lj2wYTXMxntpV+tV\r\nralLFRMhvbqZXW/EpuDb/pEbCnLDNDxq5NarLVDzcHs7VhT9MPChAgMBAAGjggFy\r\nMIIBbjATBgNVHSUEDDAKBggrBgEFBQcDAjAaBgNVHREEEzARgglsb2NhbGhvc3SH\r\nBH8AAAEwggEGBggrBgEFBQcBAwSB+TCB9jAIBgYEAI5GAQEwOAYGBACORgEFMC4w\r\nLBYhaHR0cHM6Ly9leGFtcGxlLm9yZy9wa2lkaXNjbG9zdXJlEwdleGFtcGxlMIGI\r\nBgYEAIGYJwIwfjBMMBEGBwQAgZgnAQMMBlBTUF9BSTARBgcEAIGYJwEBDAZQU1Bf\r\nQVMwEQYHBACBmCcBAgwGUFNQX1BJMBEGBwQAgZgnAQQMBlBTUF9JQwwlRHVtbXkg\r\nRmluYW5jaWFsIFN1cGVydmlzaW9uIEF1dGhvcml0eQwHWFgtREZTQTAlBgYEAI5G\r\nAQYwGwYHBACORgEGAQYHBACORgEGAgYHBACORgEGAzARBglghkgBhvhCAQEEBAMC\r\nB4AwHgYJYIZIAYb4QgENBBEWD3hjYSBjZXJ0aWZpY2F0ZTANBgkqhkiG9w0BAQsF\r\nAAOCAQEAKTS7exS9A7rWJLRzWrlHoTu68Avm5g9Dz1GKjgt8rnvj3D21SE14Rf5p\r\n0JWHYH4SiCdnh8Tx+IA7o0TmPJ1JRfAXR3i/5R7TJi/HrnqL+V7SIx2Cuq/hkZEU\r\nAhVs07nnvHURcrlQGwcfn4TbgpCURpCPpYZlNsYySb6BS6I4qFaadHGqMTyEkphV\r\nwfXyB3brmzxj9V4Qgp0t+s/uFuFirWyIayRc9nSSC7vuNVYvib2Kim4y8kvuWpA4\r\nZ51+fFOmBqCqpmwfAADNgDsLJiA/741eBflVd/ZUeAzgOjMCMIaDGlwiwZlePKT7\r\n553GtfsGxZMf05oqfUrQEQfJaU+/+Q==\n-----END CERTIFICATE-----\n" + lazy val certificateInfoJsonV510 = CertificateInfoJsonV510( + subject_domain_name = "OID.2.5.4.41=VPN, EMAILADDRESS=admin@tesobe.com, CN=TESOBE CA, OU=TESOBE Operations, O=TESOBE, L=Berlin, ST=Berlin, C=DE", + issuer_domain_name = "CN=localhost, O=TESOBE GmbH, ST=Berlin, C=DE", + not_before = "2022-04-01T10:13:00.000Z", + not_after = "2032-04-01T10:13:00.000Z", + roles = None, + roles_info = Some("PEM Encoded Certificate does not contain PSD2 roles.") + ) + lazy val consumerJsonV510: ConsumerJsonV510 = ConsumerJsonV510( + consumer_id = "d0d7b08c-f0ec-4e57-ac99-7d9eafe99225", + consumer_key = "d0d7b08c-f0ec-4e57-ac99-7d9eafe99225", + consumer_secret = "d0d7b08c-f0ec-4e57-ac99-7d9eafe99225", + app_name = "SOFI", + app_type = "Web", + description = "Account Management", + developer_email = ExampleValue.emailExample.value, + company = ExampleValue.companyExample.value, + redirect_url = "www.openbankproject.com", + certificate_pem = pem, + certificate_info = Some(certificateInfoJsonV510), + created_by_user = resourceUserJSON, + enabled = true, + created = DateWithDayExampleObject + ) val consumersJson = ConsumersJson( list = List(consumerJSON) @@ -4173,15 +4227,6 @@ object SwaggerDefinitionsJSON { val oAuth2ServerJWKURIJson = OAuth2ServerJWKURIJson("https://www.googleapis.com/oauth2/v3/certs") val oAuth2ServerJwksUrisJson = OAuth2ServerJwksUrisJson(List(oAuth2ServerJWKURIJson)) - - val certificateInfoJsonV510 = CertificateInfoJsonV510( - subject_domain_name = "OID.2.5.4.41=VPN, EMAILADDRESS=admin@tesobe.com, CN=TESOBE CA, OU=TESOBE Operations, O=TESOBE, L=Berlin, ST=Berlin, C=DE", - issuer_domain_name = "CN=localhost, O=TESOBE GmbH, ST=Berlin, C=DE", - not_before = "2022-04-01T10:13:00.000Z", - not_after = "2032-04-01T10:13:00.000Z", - roles = None, - roles_info = Some("PEM Encoded Certificate does not contain PSD2 roles.") - ) val updateAccountRequestJsonV310 = UpdateAccountRequestJsonV310( label = "Label", 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 4bf30c681..16105575f 100644 --- a/obp-api/src/main/scala/code/api/util/APIUtil.scala +++ b/obp-api/src/main/scala/code/api/util/APIUtil.scala @@ -118,6 +118,8 @@ import java.security.AccessControlException import java.util.regex.Pattern import code.api.util.FutureUtil.{EndpointContext, EndpointTimeout} +import code.api.v2_1_0.OBPAPI2_1_0.Implementations2_1_0 +import code.api.v2_2_0.OBPAPI2_2_0.Implementations2_2_0 import code.etag.MappedETag import code.users.Users import net.liftweb.mapper.By @@ -643,7 +645,18 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{ val excludedFieldValues = APIUtil.getPropsValue("excluded.response.field.values").map[JArray](it => json.parse(it).asInstanceOf[JArray]) def successJsonResponseNewStyle(cc: Any, callContext: Option[CallContext], httpCode : Int = 200)(implicit headers: CustomResponseHeaders = CustomResponseHeaders(Nil)) : JsonResponse = { - val jsonAst: JValue = ApiSession.processJson((Extraction.decompose(cc)), callContext) + val jsonAst: JValue = { + val partialFunctionName = callContext.map(_.resourceDocument.map(_.partialFunctionName)).flatten.getOrElse("") + if ( + nameOf(code.api.v5_1_0.APIMethods510.Implementations5_1_0.getMetrics).equals(partialFunctionName) || + nameOf(code.api.v5_0_0.APIMethods500.Implementations5_0_0.getMetricsAtBank).equals(partialFunctionName) || + nameOf(Implementations2_2_0.getConnectorMetrics).equals(partialFunctionName) + ) { + ApiSession.processJson(Extraction.decompose(cc)(CustomJsonFormats.losslessFormats), callContext) + } else { + ApiSession.processJson((Extraction.decompose(cc)), callContext) + } + } val excludeOptionalFieldsParam = getHttpRequestUrlParam(callContext.map(_.url).getOrElse(""),"exclude-optional-fields") val excludedResponseBehaviour = APIUtil.getPropsAsBoolValue("excluded.response.behaviour", false) //excludeOptionalFieldsParamValue has top priority, then the excludedResponseBehaviour props. @@ -777,20 +790,6 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{ } } - def basicUrlValidation(urlString: String): Boolean = { - //in scala test - org.scalatest.FeatureSpecLike.scenario: - // redirectUrl = http%3A%2F%2Flocalhost%3A8016%3Foauth_token%3DEBRZBMOPDXEUGGJP421FPFGK01IY2DGM5O3TLVSK%26oauth_verifier%3D63461 - // URLDecoder.decode(urlString,"UTF-8")-->http://localhost:8016?oauth_token=EBRZBMOPDXEUGGJP421FPFGK01IY2DGM5O3TLVSK&oauth_verifier=63461 - val regex = - """((([A-Za-z]{3,9}:(?:\/\/)?)(?:[-;:&=\+\$,\w]+@)?[A-Za-z0-9.-]+(:[0-9]+)?|(?:www.|[-;:&=\+\$,\w]+@)[A-Za-z0-9.-]+)((?:\/[\+~%\/.\w-_]*)?\??(?:[-\+=&;%@.\w_\/]*)#?(?:[\w]*))?)""".r - val decodeUrlValue = URLDecoder.decode(urlString, "UTF-8").trim() - decodeUrlValue match { - case regex(_*) if (decodeUrlValue.length <= 2048) => true - case _ => false - } - } - - /** only A-Z, a-z, 0-9,-,_,. =, & and max length <= 2048 */ def basicUriAndQueryStringValidation(urlString: String): Boolean = { val regex = @@ -2100,7 +2099,7 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{ | |Possible custom url parameters for pagination: | - |* limit=NUMBER ==> default value: 50 + |* limit=NUMBER ==> default value: ${Constant.Pagination.limit} |* offset=NUMBER ==> default value: 0 | |eg1:?limit=100&offset=0 diff --git a/obp-api/src/main/scala/code/api/util/ApiRole.scala b/obp-api/src/main/scala/code/api/util/ApiRole.scala index d6a40c152..1263c2519 100644 --- a/obp-api/src/main/scala/code/api/util/ApiRole.scala +++ b/obp-api/src/main/scala/code/api/util/ApiRole.scala @@ -66,6 +66,11 @@ object RoleCombination { object ApiRole extends MdcLoggable{ + case class CanCreateRegulatedEntity(requiresBankId: Boolean = false) extends ApiRole + lazy val canCreateRegulatedEntity = CanCreateRegulatedEntity() + case class CanDeleteRegulatedEntity(requiresBankId: Boolean = false) extends ApiRole + lazy val canDeleteRegulatedEntity = CanDeleteRegulatedEntity() + case class CanSearchWarehouse(requiresBankId: Boolean = false) extends ApiRole lazy val canSearchWarehouse = CanSearchWarehouse() diff --git a/obp-api/src/main/scala/code/api/util/ApiTag.scala b/obp-api/src/main/scala/code/api/util/ApiTag.scala index 58b153bf8..59fc4b65c 100644 --- a/obp-api/src/main/scala/code/api/util/ApiTag.scala +++ b/obp-api/src/main/scala/code/api/util/ApiTag.scala @@ -100,6 +100,9 @@ object ApiTag { val apiTagPSD2PIIS=ResourceDocTag("Confirmation of Funds Service (PIIS)") val apiTagPSD2PIS=ResourceDocTag("Payment Initiation Service (PIS)") + + val apiTagDirectory = ResourceDocTag("Directory") + //Note: the followings are for the code generator -- UKOpenBankingV3.1.0 val apiTagUkAccountAccess = ResourceDocTag("UK-AccountAccess") 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 e96757dbc..429cddc2c 100644 --- a/obp-api/src/main/scala/code/api/util/ErrorMessages.scala +++ b/obp-api/src/main/scala/code/api/util/ErrorMessages.scala @@ -516,7 +516,12 @@ object ErrorMessages { // Bank related messages val bankIdAlreadyExists = "OBP-34000: Bank Id already exists. Please specify a different value." val updateBankError = "OBP-34001: Could not update the Bank" - + + val RegulatedEntityNotFound = "OBP-34100: Regulated Entity not found. Please specify a valid value for REGULATED_ENTITY_ID." + val RegulatedEntityNotDeleted = "OBP-34101: Regulated Entity cannot be deleted. Please specify a valid value for REGULATED_ENTITY_ID." + val RegulatedEntityNotFoundByCertificate = "OBP-34102: Regulated Entity cannot be found by provided certificate." + val PostJsonIsNotSigned = "OBP-34110: JWT at the post json cannot be verified." + // Consents val ConsentNotFound = "OBP-35001: Consent not found by CONSENT_ID. " val ConsentNotBeforeIssue = "OBP-35002: The time Consent-ID token was issued is set in the future. " 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 b60d9ab57..7a7657934 100644 --- a/obp-api/src/main/scala/code/api/util/Glossary.scala +++ b/obp-api/src/main/scala/code/api/util/Glossary.scala @@ -643,7 +643,9 @@ object Glossary extends MdcLoggable { |If Just in Time Entitlements are enabled then OBP does the following: |If a user is trying to use a Role (via an endpoint) and the user could grant them selves the required Role(s), then OBP automatically grants the Role. |i.e. if the User already has canCreateEntitlementAtOneBank or canCreateEntitlementAtAnyBank then OBP will automatically grant a role that would be granted by a manual process anyway. - |This speeds up the process of granting of roles. Certain roles are excluded from this automation. + |This speeds up the process of granting of roles. Certain roles are excluded from this automation: + | - CanCreateEntitlementAtOneBank + | - CanCreateEntitlementAtAnyBank |If create_just_in_time_entitlements is again set to false after it was true for a while, any auto granted Entitlements to roles are kept in place. |Note: In the entitlements model we set createdbyprocess=create_just_in_time_entitlements. For manual operations we set createdbyprocess=manual | @@ -2115,12 +2117,11 @@ object Glossary extends MdcLoggable { | |Register your App key [HERE]($getServerUrl/consumer-registration) | - |Copy and paste the CONSUMER_KEY, CONSUMER_SECRET and REDIRECT_URL for the subsequent steps below. + |Copy and paste the CLIENT ID (AKA CONSUMER KEY), CLIENT SECRET (AKA CONSUMER SECRET) and REDIRECT_URL for the subsequent steps below. | | |### Step 2: Initiate the OAuth 2.0 / OpenID Connect Flow | - | |Once you have registered your App you should initiate the OAuth2 / OIDC flow using the following URL | |${APIUtil.getHydraPublicServerUrl}/oauth2/auth @@ -2129,6 +2130,12 @@ object Glossary extends MdcLoggable { | |${APIUtil.getHydraPublicServerUrl}/oauth2/auth?client_id=YOUR-CLIENT-ID&response_type=code&state=GENERATED_BY_YOUR_APP&scope=openid+offline+ReadAccountsBasic+ReadAccountsDetail+ReadBalances+ReadTransactionsBasic+ReadTransactionsDebits+ReadTransactionsDetail&redirect_uri=https%3A%2F%2FYOUR-APP.com%2Fmain.html | + |### Step 3: Exchange the authorisation code for an access token + | + |The token endpoint is: + | + |${APIUtil.getHydraPublicServerUrl}/oauth2/token + | | |For further information please see [here](https://www.ory.sh/hydra/docs/concepts/login#initiating-the-oauth-20--openid-connect-flow) | diff --git a/obp-api/src/main/scala/code/api/util/JwtUtil.scala b/obp-api/src/main/scala/code/api/util/JwtUtil.scala index f77b05087..96ec3c33b 100644 --- a/obp-api/src/main/scala/code/api/util/JwtUtil.scala +++ b/obp-api/src/main/scala/code/api/util/JwtUtil.scala @@ -269,6 +269,15 @@ object JwtUtil extends MdcLoggable { jwk.toPublicJWK.toRSAKey } + def verifyJwt(jwtString: String, pemEncodedRsaPublicKey: String): Boolean = { + // Parse PEM-encoded key to RSA public / private JWK + val jwk: JWK = JWK.parseFromPEMEncodedObjects(pemEncodedRsaPublicKey); + val rsaPublicKey: RSAKey = jwk.toPublicJWK.toRSAKey + val signedJWT = SignedJWT.parse(jwtString) + val verifier = new RSASSAVerifier(rsaPublicKey) + signedJWT.verify(verifier) + } + def main(args: Array[String]): Unit = { val jwtToken = "eyJhbGciOiJSUzI1NiIsImtpZCI6IjhhYWQ2NmJkZWZjMWI0M2Q4ZGIyN2U2NWUyZTJlZjMwMTg3OWQzZTgiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLCJhenAiOiI0MDc0MDg3MTgxOTIuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJhdWQiOiI0MDc0MDg3MTgxOTIuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJzdWIiOiIxMTM5NjY4NTQyNDU3ODA4OTI5NTkiLCJhdF9oYXNoIjoiWGlpckZ1cnJ2X0ZxN3RHd25rLWt1QSIsIm5hbWUiOiJNYXJrbyBNaWxpxIciLCJwaWN0dXJlIjoiaHR0cHM6Ly9saDUuZ29vZ2xldXNlcmNvbnRlbnQuY29tLy1YZDQ0aG5KNlREby9BQUFBQUFBQUFBSS9BQUFBQUFBQUFBQS9BS3hyd2NhZHd6aG00TjR0V2s1RThBdnhpLVpLNmtzNHFnL3M5Ni1jL3Bob3RvLmpwZyIsImdpdmVuX25hbWUiOiJNYXJrbyIsImZhbWlseV9uYW1lIjoiTWlsacSHIiwibG9jYWxlIjoiZW4iLCJpYXQiOjE1NDczMTE3NjAsImV4cCI6MTU0NzMxNTM2MH0.UyOmM0rsO0-G_ibDH3DFogS94GcsNd9GtYVw7j3vSMjO1rZdIraV-N2HUtQN3yHopwdf35A2FEJaag6X8dbvEkJC7_GAynyLIpodoaHNtaLbww6XQSYuQYyF27aPMpROoGZUYkMpB_82LF3PbD4ecDPC2IA5oSyDF4Eya4yn-MzxYmXS7usVWvanREg8iNQSxpu7zZqj4UwhvSIv7wH0vskr_M-PnefQzNTrdUx74i-v9lVqC4E_bF5jWeDGO8k5dqWqg55QuZdyJdSh89KNiIjJXGZDWUBzGfsbetWRnObIgX264fuOW4SpRglUc8fzv41Sc7SSqjqRAFm05t60kg" diff --git a/obp-api/src/main/scala/code/api/util/NewStyle.scala b/obp-api/src/main/scala/code/api/util/NewStyle.scala index 20325761d..5042e4402 100644 --- a/obp-api/src/main/scala/code/api/util/NewStyle.scala +++ b/obp-api/src/main/scala/code/api/util/NewStyle.scala @@ -3056,7 +3056,7 @@ object NewStyle extends MdcLoggable{ private[this] val endpointMappingTTL = APIUtil.getPropsValue(s"endpointMapping.cache.ttl.seconds", "0").toInt - def getEndpointMappings(bankId: Option[String], callContext: Option[CallContext]): OBPReturnType[List[EndpointMappingT]] = { + def getEndpointMappings(bankId: Option[String], callContext: Option[CallContext]): OBPReturnType[List[EndpointMappingT]] = Future{ import scala.concurrent.duration._ validateBankId(bankId, callContext) @@ -3064,7 +3064,7 @@ object NewStyle extends MdcLoggable{ var cacheKey = (randomUUID().toString, randomUUID().toString, randomUUID().toString) CacheKeyFromArguments.buildCacheKey { Caching.memoizeSyncWithProvider(Some(cacheKey.toString()))(endpointMappingTTL second) { - Future{(EndpointMappingProvider.endpointMappingProvider.vend.getAllEndpointMappings(bankId), callContext)} + {(EndpointMappingProvider.endpointMappingProvider.vend.getAllEndpointMappings(bankId), callContext)} } } } diff --git a/obp-api/src/main/scala/code/api/util/X509.scala b/obp-api/src/main/scala/code/api/util/X509.scala index 1d2d6d71c..051353e07 100644 --- a/obp-api/src/main/scala/code/api/util/X509.scala +++ b/obp-api/src/main/scala/code/api/util/X509.scala @@ -10,7 +10,7 @@ import code.util.Helper.MdcLoggable import com.github.dwickern.macros.NameOf import com.nimbusds.jose.jwk.RSAKey import com.nimbusds.jose.util.X509CertUtils -import net.liftweb.common.{Box, Failure, Full} +import net.liftweb.common.{Box, Failure, Full, Empty} import org.bouncycastle.asn1._ import org.bouncycastle.asn1.x509.Extension import org.bouncycastle.asn1.x509.qualified.QCStatement @@ -246,5 +246,37 @@ object X509 extends MdcLoggable { case None => Failure(ErrorMessages.X509CannotGetCertificate) } } - + + def getCommonName(pem: Option[String]): Box[String] = { + getFieldCommon(pem, "CN") + } + def getOrganization(pem: Option[String]): Box[String] = { + getFieldCommon(pem, "O") + } + def getOrganizationUnit(pem: Option[String]): Box[String] = { + getFieldCommon(pem, "OU") + } + def getEmailAddress(pem: Option[String]): Box[String] = { + getFieldCommon(pem, "EMAILADDRESS") + .or(getFieldCommon(pem, "EMAILADDRESS".toLowerCase())) + } + + private def getFieldCommon(pem: Option[String], field: String) = { + pem match { + case Some(unboxedPem) => + extractCertificateInfo(unboxedPem).map { item => + val splitByComma: Array[String] = item.subject_domain_name.split(",") + val splitByKeyValuePair: Array[(String, String)] = splitByComma.map(i => i.split("=")(0).trim -> i.split("=")(1).trim) + val valuesAsMap: Map[String, List[String]] = splitByKeyValuePair.toList.groupBy(_._1).map { case (k, v) => (k, v.map(_._2)) } + val result: String = valuesAsMap.get(field).map(_.mkString).getOrElse("") + result + } match { + case Full(value) if value.isEmpty => Empty + case everythingElse => everythingElse + } + case _ => + Empty + } + } + } diff --git a/obp-api/src/main/scala/code/api/util/newstyle/Consumer.scala b/obp-api/src/main/scala/code/api/util/newstyle/Consumer.scala index 9625229a4..e71cab7db 100644 --- a/obp-api/src/main/scala/code/api/util/newstyle/Consumer.scala +++ b/obp-api/src/main/scala/code/api/util/newstyle/Consumer.scala @@ -1,7 +1,8 @@ package code.api.util.newstyle -import code.api.util.APIUtil.{OBPReturnType, unboxFull} +import code.api.util.APIUtil.{OBPReturnType, unboxFull, unboxFullOrFail} import code.api.util.CallContext +import code.api.util.ErrorMessages.CreateConsumerError import code.consumer.Consumers import code.model.{AppType, Consumer} @@ -18,6 +19,7 @@ object Consumer { appType: Option[AppType], description: Option[String], developerEmail: Option[String], + company: Option[String], redirectURL: Option[String], createdByUserId: Option[String], clientCertificate: Option[String], @@ -33,12 +35,13 @@ object Consumer { developerEmail, redirectURL, createdByUserId, - clientCertificate - ) map { - (_, callContext) - } + clientCertificate, + company + ) } map { - unboxFull(_) + (_, callContext) + } map { + x => (unboxFullOrFail(x._1, callContext, CreateConsumerError, 400), x._2) } } diff --git a/obp-api/src/main/scala/code/api/util/newstyle/RegulatedEntity.scala b/obp-api/src/main/scala/code/api/util/newstyle/RegulatedEntity.scala new file mode 100644 index 000000000..58f5f11bd --- /dev/null +++ b/obp-api/src/main/scala/code/api/util/newstyle/RegulatedEntity.scala @@ -0,0 +1,82 @@ +package code.api.util.newstyle + +import code.api.util.APIUtil.{OBPReturnType, unboxFull, unboxFullOrFail} +import code.api.util.ErrorMessages.{RegulatedEntityNotDeleted, RegulatedEntityNotFound} +import code.api.util.{APIUtil, CallContext} +import code.consumer.Consumers +import code.model.{AppType, Consumer} +import code.regulatedentities.MappedRegulatedEntityProvider +import com.openbankproject.commons.model.RegulatedEntityTrait +import net.liftweb.common.Box + +import scala.concurrent.Future + +object RegulatedEntityNewStyle { + + import com.openbankproject.commons.ExecutionContext.Implicits.global + + def createRegulatedEntityNewStyle(certificateAuthorityCaOwnerId: Option[String], + entityCertificatePublicKey: Option[String], + entityName: Option[String], + entityCode: Option[String], + entityType: Option[String], + entityAddress: Option[String], + entityTownCity: Option[String], + entityPostCode: Option[String], + entityCountry: Option[String], + entityWebSite: Option[String], + services: Option[String], + callContext: Option[CallContext]): OBPReturnType[RegulatedEntityTrait] = { + Future { + MappedRegulatedEntityProvider.createRegulatedEntity( + certificateAuthorityCaOwnerId: Option[String], + entityCertificatePublicKey: Option[String], + entityName: Option[String], + entityCode: Option[String], + entityType: Option[String], + entityAddress: Option[String], + entityTownCity: Option[String], + entityPostCode: Option[String], + entityCountry: Option[String], + entityWebSite: Option[String], + services: Option[String] + ) map { + (_, callContext) + } + } map { + unboxFull(_) + } + } + + def getRegulatedEntitiesNewStyle(callContext: Option[CallContext]): OBPReturnType[List[RegulatedEntityTrait]] = { + Future { + MappedRegulatedEntityProvider.getRegulatedEntities() + } map { + (_, callContext) + } + } + def getRegulatedEntityByEntityIdNewStyle(id: String, + callContext: Option[CallContext] + ): OBPReturnType[RegulatedEntityTrait] = { + Future { + MappedRegulatedEntityProvider.getRegulatedEntityByEntityId(id) + } map { + (_, callContext) + } map { + x => (unboxFullOrFail(x._1, callContext, RegulatedEntityNotFound, 404), x._2) + } + } + def deleteRegulatedEntityNewStyle(id: String, + callContext: Option[CallContext] + ): OBPReturnType[Boolean] = { + Future { + MappedRegulatedEntityProvider.deleteRegulatedEntity(id) + } map { + (_, callContext) + } map { + x => (unboxFullOrFail(x._1, callContext, RegulatedEntityNotDeleted, 400), x._2) + } + } + + +} 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 d0860b0ba..6448a4596 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 @@ -771,6 +771,10 @@ trait APIMethods310 { "Get Consumers", s"""Get the all Consumers. | + |${authenticationRequiredMessage(true)} + | + |${urlParametersDocument(true, true)} + | |""", EmptyBody, consumersJson310, @@ -790,7 +794,9 @@ trait APIMethods310 { for { (Full(u), callContext) <- authenticatedAccess(cc) _ <- NewStyle.function.hasEntitlement("", u.userId, ApiRole.canGetConsumers, callContext) - consumers <- Consumers.consumers.vend.getConsumersFuture() + httpParams <- NewStyle.function.extractHttpParamsFromUrl(cc.url) + (obpQueryParams, callContext) <- createQueriesByHttpParamsFuture(httpParams, callContext) + consumers <- Consumers.consumers.vend.getConsumersFuture(obpQueryParams, callContext) users <- Users.users.vend.getUsersByUserIdsFuture(consumers.map(_.createdByUserId.get)) } yield { (createConsumersJson(consumers, users), HttpCode.`200`(callContext)) 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 0f812e84a..855044d64 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 @@ -5295,6 +5295,7 @@ trait APIMethods400 extends MdcLoggable { appType = None, description = Some(postedJson.description), developerEmail = Some(postedJson.developer_email), + company = None, redirectURL = Some(postedJson.redirect_url), createdByUserId = Some(u.userId), clientCertificate = Some(postedJson.clientCertificate), diff --git a/obp-api/src/main/scala/code/api/v5_1_0/APIMethods510.scala b/obp-api/src/main/scala/code/api/v5_1_0/APIMethods510.scala index ef355f8ec..06677998b 100644 --- a/obp-api/src/main/scala/code/api/v5_1_0/APIMethods510.scala +++ b/obp-api/src/main/scala/code/api/v5_1_0/APIMethods510.scala @@ -2,27 +2,33 @@ package code.api.v5_1_0 import code.api.Constant -import code.api.ResourceDocs1_4_0.SwaggerDefinitionsJSON.{apiCollectionJson400, apiCollectionsJson400, apiInfoJson400, postApiCollectionJson400, revokedConsentJsonV310, _} +import code.api.ResourceDocs1_4_0.SwaggerDefinitionsJSON._ import code.api.util.APIUtil._ import code.api.util.ApiRole._ import code.api.util.ApiTag._ import code.api.util.ErrorMessages.{$UserNotLoggedIn, BankNotFound, ConsentNotFound, InvalidJsonFormat, UnknownError, UserNotFoundByUserId, UserNotLoggedIn, _} import code.api.util.FutureUtil.{EndpointContext, EndpointTimeout} +import code.api.util.JwtUtil.{getSignedPayloadAsJson, verifyJwt} import code.api.util.NewStyle.HttpCode +import code.api.util.X509.{getCommonName, getEmailAddress, getOrganization} import code.api.util._ -import code.api.v2_0_0.{EntitlementJSONs, JSONFactory200} +import code.api.util.newstyle.Consumer.createConsumerNewStyle +import code.api.util.newstyle.RegulatedEntityNewStyle.{createRegulatedEntityNewStyle, deleteRegulatedEntityNewStyle, getRegulatedEntitiesNewStyle, getRegulatedEntityByEntityIdNewStyle} +import code.api.v2_1_0.{ConsumerRedirectUrlJSON, JSONFactory210} import code.api.v3_0_0.JSONFactory300 import code.api.v3_0_0.JSONFactory300.createAggregateMetricJson import code.api.v3_1_0.ConsentJsonV310 import code.api.v3_1_0.JSONFactory310.createBadLoginStatusJson import code.api.v4_0_0.{JSONFactory400, PostApiCollectionJson400} -import code.api.v5_0_0.ConsentJsonV500 +import code.api.v5_1_0.JSONFactory510.{createRegulatedEntitiesJson, createRegulatedEntityJson} import code.atmattribute.AtmAttribute import code.bankconnectors.Connector import code.consent.Consents import code.loginattempts.LoginAttempt import code.metrics.APIMetrics +import code.model.AppType import code.model.dataAccess.MappedBankAccount +import code.regulatedentities.MappedRegulatedEntityProvider import code.transactionrequests.TransactionRequests.TransactionRequestTypes.{apply => _} import code.userlocks.UserLocksProvider import code.users.Users @@ -32,15 +38,14 @@ import code.views.Views import code.views.system.{AccountAccess, ViewDefinition} import com.github.dwickern.macros.NameOf.nameOf import com.openbankproject.commons.ExecutionContext.Implicits.global -import com.openbankproject.commons.dto.CustomerAndAttribute import com.openbankproject.commons.model.enums.{AtmAttributeType, UserAttributeType} -import com.openbankproject.commons.model.{AtmId, AtmT, BankId, Permission} +import com.openbankproject.commons.model._ import com.openbankproject.commons.util.{ApiVersion, ScannedApiVersion} -import net.liftweb.common.{Box, Full} -import net.liftweb.http.S +import net.liftweb.common.Full import net.liftweb.http.rest.RestHelper -import net.liftweb.json.parse +import net.liftweb.json.{compactRender, parse} import net.liftweb.mapper.By +import net.liftweb.util.Helpers import net.liftweb.util.Helpers.tryo import scala.collection.immutable.{List, Nil} @@ -120,6 +125,154 @@ trait APIMethods510 { (SuggestedSessionTimeoutV510(timeoutInSeconds.toString), HttpCode.`200`(cc.callContext)) } } + + + staticResourceDocs += ResourceDoc( + regulatedEntities, + implementedInApiVersion, + nameOf(regulatedEntities), + "GET", + "/regulated-entities", + "Get Regulated Entities", + """Returns information about: + | + |* Regulated Entities + """, + EmptyBody, + regulatedEntitiesJsonV510, + List(UnknownError), + apiTagDirectory :: apiTagApi :: Nil) + + lazy val regulatedEntities: OBPEndpoint = { + case "regulated-entities" :: Nil JsonGet _ => + cc => implicit val ec = EndpointContext(Some(cc)) + for { + (entities, callContext) <- getRegulatedEntitiesNewStyle(cc.callContext) + } yield { + (createRegulatedEntitiesJson(entities), HttpCode.`200`(callContext)) + } + } + + staticResourceDocs += ResourceDoc( + getRegulatedEntityById, + implementedInApiVersion, + nameOf(getRegulatedEntityById), + "GET", + "/regulated-entities/REGULATED_ENTITY_ID", + "Get Regulated Entity", + """Get Regulated Entity By REGULATED_ENTITY_ID + """, + EmptyBody, + regulatedEntitiesJsonV510, + List(UnknownError), + apiTagDirectory :: apiTagApi :: Nil) + + lazy val getRegulatedEntityById: OBPEndpoint = { + case "regulated-entities" :: regulatedEntityId :: Nil JsonGet _ => + cc => implicit val ec = EndpointContext(Some(cc)) + for { + (entity, callContext) <- getRegulatedEntityByEntityIdNewStyle(regulatedEntityId, cc.callContext) + } yield { + (createRegulatedEntityJson(entity), HttpCode.`200`(callContext)) + } + } + + + staticResourceDocs += ResourceDoc( + createRegulatedEntity, + implementedInApiVersion, + nameOf(createRegulatedEntity), + "POST", + "/regulated-entities", + "Create Regulated Entity", + s"""Create Regulated Entity + | + |${authenticationRequiredMessage(true)} + | + |""", + regulatedEntityPostJsonV510, + regulatedEntitiesJsonV510, + List( + $UserNotLoggedIn, + UserHasMissingRoles, + InvalidJsonFormat, + UnknownError + ), + List(apiTagDirectory, apiTagApi), + Some(List(canCreateRegulatedEntity)) + ) + + lazy val createRegulatedEntity: OBPEndpoint = { + case "regulated-entities" :: Nil JsonPost json -> _ => { + cc => + implicit val ec = EndpointContext(Some(cc)) + val failMsg = s"$InvalidJsonFormat The Json body should be the $RegulatedEntityPostJsonV510 " + for { + postedData <- NewStyle.function.tryons(failMsg, 400, cc.callContext) { + json.extract[RegulatedEntityPostJsonV510] + } + failMsg = s"$InvalidJsonFormat The `services` field is not valid JSON" + _ <- NewStyle.function.tryons(failMsg, 400, cc.callContext) { + parse(postedData.services) + } + (entity, callContext) <- createRegulatedEntityNewStyle( + certificateAuthorityCaOwnerId = Some(postedData.certificate_authority_ca_owner_id), + entityCertificatePublicKey = Some(postedData.entity_certificate_public_key), + entityName = Some(postedData.entity_name), + entityCode = Some(postedData.entity_code), + entityType = Some(postedData.entity_type), + entityAddress = Some(postedData.entity_address), + entityTownCity = Some(postedData.entity_town_city), + entityPostCode = Some(postedData.entity_post_code), + entityCountry = Some(postedData.entity_country), + entityWebSite = Some(postedData.entity_web_site), + services = Some(postedData.services), + cc.callContext + ) + } yield { + (createRegulatedEntityJson(entity), HttpCode.`201`(callContext)) + } + } + } + + resourceDocs += ResourceDoc( + deleteRegulatedEntity, + implementedInApiVersion, + nameOf(deleteRegulatedEntity), + "DELETE", + "/regulated-entities/REGULATED_ENTITY_ID", + "Delete Regulated Entity", + s"""Delete Regulated Entity specified by REGULATED_ENTITY_ID + | + |${authenticationRequiredMessage(true)} + |""".stripMargin, + EmptyBody, + EmptyBody, + List( + $UserNotLoggedIn, + UserHasMissingRoles, + InvalidConnectorResponse, + UnknownError + ), + List(apiTagDirectory, apiTagApi), + Some(List(canDeleteRegulatedEntity))) + + lazy val deleteRegulatedEntity: OBPEndpoint = { + case "regulated-entities" :: regulatedEntityId :: Nil JsonDelete _ => { + cc => + implicit val ec = EndpointContext(Some(cc)) + for { + (deleted, callContext) <- deleteRegulatedEntityNewStyle( + regulatedEntityId: String, + cc.callContext: Option[CallContext] + ) + } yield { + org.scalameta.logger.elem(deleted) + (Full(deleted), HttpCode.`200`(callContext)) + } + } + } + staticResourceDocs += ResourceDoc( waitingForGodot, @@ -1275,19 +1428,22 @@ trait APIMethods510 { "GET", "/management/metrics", "Get Metrics", - s"""Get the all metrics + s"""Get API metrics rows. These are records of each REST API call. | |require CanReadMetrics role | |Filters Part 1.*filtering* (no wilde cards etc.) parameters to GET /management/metrics | - |Should be able to filter on the following metrics fields + |You can filter by the following fields by applying url parameters | |eg: /management/metrics?from_date=$DateWithMsExampleString&to_date=$DateWithMsExampleString&limit=50&offset=2 | - |1 from_date (defaults to one week before current date): eg:from_date=$DateWithMsExampleString + |1 from_date e.g.:from_date=$DateWithMsExampleString Defaults to the Unix Epoch i.e. ${theEpochTime} | - |2 to_date (defaults to current date) eg:to_date=$DateWithMsExampleString + |2 to_date e.g.:to_date=$DateWithMsExampleString Defaults to a far future date i.e. ${APIUtil.ToDateInFuture} + | + |Note: it is recommended you send a valid from_date (e.g. 5 seconds ago) and to_date (now + 1 second) if you want to get the latest records + | Otherwise you may receive stale cached results. | |3 limit (for pagination: defaults to 50) eg:limit=200 | @@ -1621,6 +1777,149 @@ trait APIMethods510 { } } + + staticResourceDocs += ResourceDoc( + createConsumer, + implementedInApiVersion, + "createConsumer", + "POST", + "/dynamic-registration/consumers", + "Create a Consumer", + s"""Create a Consumer (mTLS access). + | + | JWT payload: + | - minimal + | { "description":"Description" } + | - full + | { + | "description": "Description", + | "app_name": "Tesobe GmbH", + | "app_type": "Sofit", + | "developer_email": "marko@tesobe.com", + | "redirect_url": "http://localhost:8082" + | } + | Please note that JWT must be signed with the counterpart private kew of the public key used to establish mTLS + | + |""", + ConsumerJwtPostJsonV510("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJkZXNjcmlwdGlvbiI6IkRlc2NyaXB0aW9uIn0.qDnzk1dGK8akdLFRl8fmJV_SeoDjRTDG_eMogCIzZ7M"), + consumerJsonV510, + List( + InvalidJsonFormat, + UnknownError + ), + List(apiTagDirectory, apiTagConsumer), + Some(Nil)) + + + lazy val createConsumer: OBPEndpoint = { + case "dynamic-registration" :: "consumers" :: Nil JsonPost json -> _ => { + cc => + implicit val ec = EndpointContext(Some(cc)) + for { + postedJwt <- NewStyle.function.tryons(InvalidJsonFormat, 400, cc.callContext) { + json.extract[ConsumerJwtPostJsonV510] + } + pem = APIUtil.`getPSD2-CERT`(cc.requestHeaders) + _ <- Helper.booleanToFuture(PostJsonIsNotSigned, 400, cc.callContext) { + verifyJwt(postedJwt.jwt, pem.getOrElse("")) + } + postedJson <- NewStyle.function.tryons(InvalidJsonFormat, 400, cc.callContext) { + parse(getSignedPayloadAsJson(postedJwt.jwt).getOrElse("{}")).extract[ConsumerPostJsonV510] + } + certificateInfo: CertificateInfoJsonV510 <- Future(X509.getCertificateInfo(pem)) map { + unboxFullOrFail(_, cc.callContext, X509GeneralError) + } + _ <- Helper.booleanToFuture(RegulatedEntityNotFoundByCertificate, 400, cc.callContext) { + MappedRegulatedEntityProvider.getRegulatedEntities() + .exists(_.entityCertificatePublicKey.replace("""\n""", "") == pem.getOrElse("").replace("""\n""", "")) + } + (consumer, callContext) <- createConsumerNewStyle( + key = Some(Helpers.randomString(40).toLowerCase), + secret = Some(Helpers.randomString(40).toLowerCase), + isActive = Some(true), + name = getCommonName(pem).or(postedJson.app_name) , + appType = postedJson.app_type.map(AppType.valueOf).orElse(Some(AppType.valueOf("Confidential"))), + description = Some(postedJson.description), + developerEmail = getEmailAddress(pem).or(postedJson.developer_email), + company = getOrganization(pem), + redirectURL = postedJson.redirect_url, + createdByUserId = None, + clientCertificate = pem, + cc.callContext + ) + } yield { + // Format the data as json + val json = JSONFactory510.createConsumerJSON(consumer, Some(certificateInfo)) + // Return + (json, HttpCode.`201`(callContext)) + } + } + } + + + private def consumerDisabledText() = { + if(APIUtil.getPropsAsBoolValue("consumers_enabled_by_default", false) == false) { + "Please note: Your consumer may be disabled as a result of this action." + } else { + "" + } + } + + staticResourceDocs += ResourceDoc( + updateConsumerRedirectUrl, + implementedInApiVersion, + "updateConsumerRedirectUrl", + "PUT", + "/management/consumers/CONSUMER_ID/consumer/redirect_url", + "Update Consumer RedirectUrl", + s"""Update an existing redirectUrl for a Consumer specified by CONSUMER_ID. + | + | ${consumerDisabledText()} + | + | CONSUMER_ID can be obtained after you register the application. + | + | Or use the endpoint 'Get Consumers' to get it + | + """.stripMargin, + consumerRedirectUrlJSON, + consumerJSON, + List( + UserNotLoggedIn, + UserHasMissingRoles, + UnknownError + ), + List(apiTagConsumer), + Some(List(canUpdateConsumerRedirectUrl)) + ) + + lazy val updateConsumerRedirectUrl: OBPEndpoint = { + case "management" :: "consumers" :: consumerId :: "consumer" :: "redirect_url" :: Nil JsonPut json -> _ => { + cc => + implicit val ec = EndpointContext(Some(cc)) + for { + (Full(u), callContext) <- authenticatedAccess(cc) + _ <- APIUtil.getPropsAsBoolValue("consumers_enabled_by_default", false) match { + case true => Future(Full(Unit)) + case false => NewStyle.function.hasEntitlement("", u.userId, ApiRole.canUpdateConsumerRedirectUrl, callContext) + } + postJson <- NewStyle.function.tryons(InvalidJsonFormat, 400, callContext) { + json.extract[ConsumerRedirectUrlJSON] + } + consumer <- NewStyle.function.getConsumerByConsumerId(consumerId, callContext) + //only the developer that created the Consumer should be able to edit it + _ <- Helper.booleanToFuture(UserNoPermissionUpdateConsumer, 400, callContext) { + consumer.createdByUserId.equals(u.userId) + } + //update the redirectURL and isactive (set to false when change redirectUrl) field in consumer table + updatedConsumer <- NewStyle.function.updateConsumer(consumer.id.get, None, None, Some(APIUtil.getPropsAsBoolValue("consumers_enabled_by_default", false)), None, None, None, None, Some(postJson.redirect_url), None, callContext) + } yield { + val json = JSONFactory510.createConsumerJSON(updatedConsumer) + (json, HttpCode.`200`(callContext)) + } + } + } + + } } diff --git a/obp-api/src/main/scala/code/api/v5_1_0/JSONFactory5.1.0.scala b/obp-api/src/main/scala/code/api/v5_1_0/JSONFactory5.1.0.scala index 80763b884..73dff2c05 100644 --- a/obp-api/src/main/scala/code/api/v5_1_0/JSONFactory5.1.0.scala +++ b/obp-api/src/main/scala/code/api/v5_1_0/JSONFactory5.1.0.scala @@ -30,21 +30,24 @@ import code.api.Constant import code.api.util.{APIUtil, ConsentJWT, CustomJsonFormats, JwtUtil, Role} import code.api.util.APIUtil.gitCommit import code.api.v1_4_0.JSONFactory1_4_0.{LocationJsonV140, MetaJsonV140, transformToLocationFromV140, transformToMetaFromV140} +import code.api.v2_1_0.ResourceUserJSON import code.api.v3_0_0.JSONFactory300.{createLocationJson, createMetaJson, transformToAddressFromV300} import code.api.v3_0_0.{AccountIdJson, AccountsIdsJsonV300, AddressJsonV300, OpeningTimesV300} import code.api.v4_0_0.{EnergySource400, HostedAt400, HostedBy400} import code.atmattribute.AtmAttribute import code.atms.Atms.Atm -import code.users.UserAttribute +import code.users.{UserAttribute, Users} import code.views.system.{AccountAccess, ViewDefinition} -import com.openbankproject.commons.model.{Address, AtmId, AtmT, BankId, BankIdAccountId, Customer, Location, Meta} +import com.openbankproject.commons.model.{Address, AtmId, AtmT, BankId, BankIdAccountId, Customer, Location, Meta, RegulatedEntityTrait} import com.openbankproject.commons.util.{ApiVersion, ScannedApiVersion} import java.util.Date import code.consent.MappedConsent import code.metrics.APIMetric -import net.liftweb.common.Box -import net.liftweb.json.parse +import code.model.Consumer +import net.liftweb.common.{Box, Full} +import net.liftweb.json +import net.liftweb.json.{JValue, parse} import scala.collection.immutable.List import scala.util.Try @@ -64,6 +67,36 @@ case class APIInfoJsonV510( energy_source : EnergySource400, resource_docs_requires_role: Boolean ) + +case class RegulatedEntityJsonV510( + entity_id: String, + certificate_authority_ca_owner_id: String, + entity_certificate_public_key: String, + entity_name: String, + entity_code: String, + entity_type: String, + entity_address: String, + entity_town_city: String, + entity_post_code: String, + entity_country: String, + entity_web_site: String, + services: JValue + ) +case class RegulatedEntityPostJsonV510( + certificate_authority_ca_owner_id: String, + entity_certificate_public_key: String, + entity_name: String, + entity_code: String, + entity_type: String, + entity_address: String, + entity_town_city: String, + entity_post_code: String, + entity_country: String, + entity_web_site: String, + services: String + ) +case class RegulatedEntitiesJsonV510(entities: List[RegulatedEntityJsonV510]) + case class WaitingForGodotJsonV510(sleep_in_milliseconds: Long) case class CertificateInfoJsonV510( @@ -250,6 +283,30 @@ case class MetricJsonV510( ) case class MetricsJsonV510(metrics: List[MetricJsonV510]) + +case class ConsumerJwtPostJsonV510(jwt: String) +case class ConsumerPostJsonV510(app_name: Option[String], + app_type: Option[String], + description: String, + developer_email: Option[String], + redirect_url: Option[String], + ) +case class ConsumerJsonV510(consumer_id: String, + consumer_key: String, + consumer_secret: String, + app_name: String, + app_type: String, + description: String, + developer_email: String, + company: String, + redirect_url: String, + certificate_pem: String, + certificate_info: Option[CertificateInfoJsonV510], + created_by_user: ResourceUserJSON, + enabled: Boolean, + created: Date + ) + object JSONFactory510 extends CustomJsonFormats { def createCustomersIds(customers : List[Customer]): CustomersIdsJsonV510 = @@ -534,6 +591,26 @@ object JSONFactory510 extends CustomJsonFormats { UserAttributesResponseJsonV510(userAttribute.map(createUserAttributeJson)) } + def createRegulatedEntityJson(entity: RegulatedEntityTrait): RegulatedEntityJsonV510 = { + RegulatedEntityJsonV510( + entity_id = entity.entityId, + certificate_authority_ca_owner_id = entity.certificateAuthorityCaOwnerId, + entity_certificate_public_key = entity.entityCertificatePublicKey, + entity_name = entity.entityName, + entity_code = entity.entityCode, + entity_type = entity.entityType, + entity_address = entity.entityAddress, + entity_town_city = entity.entityTownCity, + entity_post_code = entity.entityPostCode, + entity_country = entity.entityCountry, + entity_web_site = entity.entityWebSite, + services = json.parse(entity.services) + ) + } + def createRegulatedEntitiesJson(entities: List[RegulatedEntityTrait]): RegulatedEntitiesJsonV510 = { + RegulatedEntitiesJsonV510(entities.map(createRegulatedEntityJson)) + } + def createMetricJson(metric: APIMetric): MetricJsonV510 = { MetricJsonV510( user_id = metric.getUserId(), @@ -548,8 +625,8 @@ object JSONFactory510 extends CustomJsonFormats { implemented_by_partial_function = metric.getImplementedByPartialFunction(), correlation_id = metric.getCorrelationId(), duration = metric.getDuration(), - source_ip = metric.getTargetIp(), - target_ip = metric.getSourceIp(), + source_ip = metric.getSourceIp(), + target_ip = metric.getTargetIp(), response_body = metric.getResponseBody() ) } @@ -558,6 +635,37 @@ object JSONFactory510 extends CustomJsonFormats { MetricsJsonV510(metrics.map(createMetricJson)) } + def createConsumerJSON(c: Consumer, certificateInfo: Option[CertificateInfoJsonV510] = None): ConsumerJsonV510 = { + + val resourceUserJSON = Users.users.vend.getUserByUserId(c.createdByUserId.toString()) match { + case Full(resourceUser) => ResourceUserJSON( + user_id = resourceUser.userId, + email = resourceUser.emailAddress, + provider_id = resourceUser.idGivenByProvider, + provider = resourceUser.provider, + username = resourceUser.name + ) + case _ => null + } + + ConsumerJsonV510( + consumer_id = c.consumerId.get, + consumer_key = c.key.get, + consumer_secret = c.secret.get, + app_name = c.name.get, + app_type = c.appType.toString(), + description = c.description.get, + developer_email = c.developerEmail.get, + company = c.company.get, + redirect_url = c.redirectURL.get, + certificate_pem = c.clientCertificate.get, + certificate_info = certificateInfo, + created_by_user = resourceUserJSON, + enabled = c.isActive.get, + created = c.createdAt.get + ) + } + } diff --git a/obp-api/src/main/scala/code/consumer/ConsumerProvider.scala b/obp-api/src/main/scala/code/consumer/ConsumerProvider.scala index 351e4e6a0..25725e19f 100644 --- a/obp-api/src/main/scala/code/consumer/ConsumerProvider.scala +++ b/obp-api/src/main/scala/code/consumer/ConsumerProvider.scala @@ -1,6 +1,6 @@ package code.consumer -import code.api.util.APIUtil +import code.api.util.{APIUtil, CallContext, OBPQueryParam} import code.model.{AppType, Consumer, MappedConsumersProvider} import code.remotedata.RemotedataConsumers import com.openbankproject.commons.model.{BankIdAccountId, User, View} @@ -31,7 +31,7 @@ trait ConsumersProvider { def getConsumerByConsumerId(consumerId: String): Box[Consumer] def getConsumerByConsumerIdFuture(consumerId: String): Future[Box[Consumer]] def getConsumersByUserIdFuture(userId: String): Future[List[Consumer]] - def getConsumersFuture(): Future[List[Consumer]] + def getConsumersFuture(httpParams: List[OBPQueryParam], callContext: Option[CallContext]): Future[List[Consumer]] def createConsumer(key: Option[String], secret: Option[String], isActive: Option[Boolean], name: Option[String], appType: Option[AppType], description: Option[String], developerEmail: Option[String], redirectURL: Option[String], createdByUserId: Option[String], clientCertificate: Option[String] = None, company: Option[String] = None): Box[Consumer] def deleteConsumer(consumer: Consumer): Boolean def updateConsumer(id: Long, key: Option[String], secret: Option[String], isActive: Option[Boolean], name: Option[String], appType: Option[AppType], description: Option[String], developerEmail: Option[String], redirectURL: Option[String], createdByUserId: Option[String]): Box[Consumer] @@ -64,7 +64,7 @@ class RemotedataConsumersCaseClasses { case class getConsumerByConsumerId(consumerId: String) case class getConsumerByConsumerIdFuture(consumerId: String) case class getConsumersByUserIdFuture(userId: String) - case class getConsumersFuture() + case class getConsumersFuture(httpParams: List[OBPQueryParam], callContext: Option[CallContext]) case class createConsumer(key: Option[String], secret: Option[String], isActive: Option[Boolean], name: Option[String], appType: Option[AppType], description: Option[String], developerEmail: Option[String], redirectURL: Option[String], createdByUserId: Option[String], clientCertificate: Option[String], company: Option[String]) case class updateConsumer(id: Long, key: Option[String], secret: Option[String], isActive: Option[Boolean], name: Option[String], appType: Option[AppType], description: Option[String], developerEmail: Option[String], redirectURL: Option[String], createdByUserId: Option[String]) case class deleteConsumer(consumer: Consumer) diff --git a/obp-api/src/main/scala/code/metrics/MappedMetrics.scala b/obp-api/src/main/scala/code/metrics/MappedMetrics.scala index 0a7c7c747..9f04090db 100644 --- a/obp-api/src/main/scala/code/metrics/MappedMetrics.scala +++ b/obp-api/src/main/scala/code/metrics/MappedMetrics.scala @@ -360,7 +360,7 @@ object MappedMetrics extends APIMetrics with MdcLoggable{ } // TODO Cache this as long as fromDate and toDate are in the past (before now) - override def getTopApisFuture(queryParams: List[OBPQueryParam]): Future[Box[List[TopApi]]] = { + override def getTopApisFuture(queryParams: List[OBPQueryParam]): Future[Box[List[TopApi]]] = Future{ /** * Please note that "var cacheKey = (randomUUID().toString, randomUUID().toString, randomUU * is just a temporary value field with UUID values in order to prevent any ambiguity. @@ -369,7 +369,7 @@ object MappedMetrics extends APIMetrics with MdcLoggable{ */ var cacheKey = (randomUUID().toString, randomUUID().toString, randomUUID().toString) CacheKeyFromArguments.buildCacheKey {Caching.memoizeSyncWithProvider(Some(cacheKey.toString()))(cachedTopApis seconds){ - Future{ + { val fromDate = queryParams.collect { case OBPFromDate(value) => value }.headOption val toDate = queryParams.collect { case OBPToDate(value) => value }.headOption val consumerId = queryParams.collect { case OBPConsumerId(value) => value }.headOption.flatMap(consumerIdToPrimaryKey) @@ -440,7 +440,7 @@ object MappedMetrics extends APIMetrics with MdcLoggable{ }} // TODO Cache this as long as fromDate and toDate are in the past (before now) - override def getTopConsumersFuture(queryParams: List[OBPQueryParam]): Future[Box[List[TopConsumer]]] = { + override def getTopConsumersFuture(queryParams: List[OBPQueryParam]): Future[Box[List[TopConsumer]]] = Future { /** * Please note that "var cacheKey = (randomUUID().toString, randomUUID().toString, randomUU * is just a temporary value field with UUID values in order to prevent any ambiguity. @@ -449,7 +449,7 @@ object MappedMetrics extends APIMetrics with MdcLoggable{ */ var cacheKey = (randomUUID().toString, randomUUID().toString, randomUUID().toString) CacheKeyFromArguments.buildCacheKey {Caching.memoizeSyncWithProvider(Some(cacheKey.toString()))(cachedTopConsumers seconds){ - Future { + val fromDate = queryParams.collect { case OBPFromDate(value) => value }.headOption val toDate = queryParams.collect { case OBPToDate(value) => value }.headOption val consumerId = queryParams.collect { case OBPConsumerId(value) => value }.headOption.flatMap(consumerIdToPrimaryKey) @@ -519,7 +519,7 @@ object MappedMetrics extends APIMetrics with MdcLoggable{ } tryo(result) } - }}} + }} } diff --git a/obp-api/src/main/scala/code/model/OAuth.scala b/obp-api/src/main/scala/code/model/OAuth.scala index 7368ff9e9..14da03e21 100644 --- a/obp-api/src/main/scala/code/model/OAuth.scala +++ b/obp-api/src/main/scala/code/model/OAuth.scala @@ -27,7 +27,7 @@ TESOBE (http://www.tesobe.com/) package code.model import java.util.{Collections, Date} -import code.api.util.APIUtil +import code.api.util.{APIUtil, CallContext, OBPAscending, OBPDescending, OBPFromDate, OBPLimit, OBPOffset, OBPOrdering, OBPQueryParam, OBPToDate} import code.api.util.CommonFunctions.validUri import code.api.util.migration.Migration.DbFunction import code.consumer.{Consumers, ConsumersProvider} @@ -119,11 +119,26 @@ object MappedConsumersProvider extends ConsumersProvider with MdcLoggable { Future(getConsumersByUserId(userId)) } - def getConsumers(): List[Consumer] = { - Consumer.findAll() + def getConsumers(queryParams: List[OBPQueryParam], callContext: Option[CallContext]): List[Consumer] = { + val limit = queryParams.collect { case OBPLimit(value) => MaxRows[Consumer](value) }.headOption + val offset = queryParams.collect { case OBPOffset(value) => StartAt[Consumer](value) }.headOption + val fromDate = queryParams.collect { case OBPFromDate(date) => By_>=(Consumer.createdAt, date) }.headOption + val toDate = queryParams.collect { case OBPToDate(date) => By_<=(Consumer.createdAt, date) }.headOption + val ordering = queryParams.collect { + case OBPOrdering(_, direction) => + direction match { + case OBPAscending => OrderBy(Consumer.createdAt, Ascending) + case OBPDescending => OrderBy(Consumer.createdAt, Descending) + } + } + + val mapperParams: Seq[QueryParam[Consumer]] = Seq(limit.toSeq, offset.toSeq, fromDate.toSeq, toDate.toSeq, ordering.toSeq).flatten + + Consumer.findAll(mapperParams: _*) } - override def getConsumersFuture(): Future[List[Consumer]] = { - Future(getConsumers()) + + override def getConsumersFuture(httpParams: List[OBPQueryParam], callContext: Option[CallContext]): Future[List[Consumer]] = { + Future(getConsumers(httpParams: List[OBPQueryParam], callContext: Option[CallContext])) } override def createConsumer(key: Option[String], diff --git a/obp-api/src/main/scala/code/regulatedentities/MappedRegulatedEntitiyProvider.scala b/obp-api/src/main/scala/code/regulatedentities/MappedRegulatedEntitiyProvider.scala new file mode 100644 index 000000000..e84a6d042 --- /dev/null +++ b/obp-api/src/main/scala/code/regulatedentities/MappedRegulatedEntitiyProvider.scala @@ -0,0 +1,131 @@ +package code.regulatedentities + +import code.util.MappedUUID +import com.openbankproject.commons.model.RegulatedEntityTrait +import net.liftweb.common.Box +import net.liftweb.common.Box.tryo +import net.liftweb.mapper._ + +import scala.concurrent.Future + +object MappedRegulatedEntityProvider extends RegulatedEntityProvider { + def getRegulatedEntities(): List[RegulatedEntityTrait] = { + MappedRegulatedEntity.findAll() + } + + override def getRegulatedEntityByEntityId(entityId: String): Box[RegulatedEntityTrait] = { + MappedRegulatedEntity.find(By(MappedRegulatedEntity.EntityId, entityId)) + } + + override def createRegulatedEntity(certificateAuthorityCaOwnerId: Option[String], + entityCertificatePublicKey: Option[String], + entityName: Option[String], + entityCode: Option[String], + entityType: Option[String], + entityAddress: Option[String], + entityTownCity: Option[String], + entityPostCode: Option[String], + entityCountry: Option[String], + entityWebSite: Option[String], + services: Option[String] + ): Box[RegulatedEntityTrait] = { + tryo { + val entity = MappedRegulatedEntity.create + certificateAuthorityCaOwnerId match { + case Some(v) => entity.CertificateAuthorityCaOwnerId(v) + case None => + } + entityCertificatePublicKey match { + case Some(v) => entity.EntityCertificatePublicKey(v) + case None => + } + entityName match { + case Some(v) => entity.EntityName(v) + case None => + } + entityCode match { + case Some(v) => entity.EntityCode(v) + case None => + } + entityType match { + case Some(v) => entity.EntityType(v) + case None => + } + entityAddress match { + case Some(v) => entity.EntityAddress(v) + case None => + } + entityTownCity match { + case Some(v) => entity.EntityTownCity(v) + case None => + } + entityPostCode match { + case Some(v) => entity.EntityPostCode(v) + case None => + } + entityCountry match { + case Some(v) => entity.EntityCountry(v) + case None => + } + entityWebSite match { + case Some(v) => entity.EntityWebSite(v) + case None => + } + services match { + case Some(v) => entity.Services(v) + case None => + } + + if (entity.validate.isEmpty) { + entity.saveMe() + } else { + throw new Error(entity.validate.map(_.msg.toString()).mkString(";")) + } + } + } + + override def deleteRegulatedEntity(id: String): Box[Boolean] = { + tryo( + MappedRegulatedEntity.bulkDelete_!!(By(MappedRegulatedEntity.EntityId, id)) + ) + } + +} + +class MappedRegulatedEntity extends RegulatedEntityTrait with LongKeyedMapper[MappedRegulatedEntity] with IdPK { + override def getSingleton = MappedRegulatedEntity + object EntityId extends MappedUUID(this) + object CertificateAuthorityCaOwnerId extends MappedString(this, 256) + object EntityName extends MappedString(this, 256) + object EntityCode extends MappedString(this, 50) + object EntityCertificatePublicKey extends MappedText(this) + object EntityType extends MappedString(this, 50) + object EntityAddress extends MappedString(this, 256) + object EntityTownCity extends MappedString(this, 50) + object EntityPostCode extends MappedString(this, 50) + object EntityCountry extends MappedString(this, 50) + object EntityWebSite extends MappedString(this, 256) + object Services extends MappedText(this) + + + override def entityId: String = EntityId.get + override def certificateAuthorityCaOwnerId: String = CertificateAuthorityCaOwnerId.get + override def entityName: String = EntityName.get + override def entityCode: String = EntityCode.get + override def entityCertificatePublicKey: String = EntityCertificatePublicKey.get + override def entityType: String = EntityType.get + override def entityAddress: String = EntityAddress.get + override def entityTownCity: String = EntityTownCity.get + override def entityPostCode: String = EntityPostCode.get + override def entityCountry: String = EntityCountry.get + override def entityWebSite: String = EntityWebSite.get + override def services: String = Services.get + + +} + +object MappedRegulatedEntity extends MappedRegulatedEntity with LongKeyedMetaMapper[MappedRegulatedEntity] { + override def dbTableName = "RegulatedEntity" // define the DB table name + override def dbIndexes = Index(CertificateAuthorityCaOwnerId) :: super.dbIndexes +} + diff --git a/obp-api/src/main/scala/code/regulatedentities/RegulatedEntity.scala b/obp-api/src/main/scala/code/regulatedentities/RegulatedEntity.scala new file mode 100644 index 000000000..2a73a3e41 --- /dev/null +++ b/obp-api/src/main/scala/code/regulatedentities/RegulatedEntity.scala @@ -0,0 +1,36 @@ +package code.regulatedentities + + +import com.openbankproject.commons.model.RegulatedEntityTrait +import net.liftweb.common.{Box, Logger} +import net.liftweb.util.SimpleInjector + +object RegulatedEntityX extends SimpleInjector { + val regulatedEntityProvider = new Inject(buildOne _) {} + def buildOne: RegulatedEntityProvider = MappedRegulatedEntityProvider +} +/* For ProductFee */ +trait RegulatedEntityProvider { + + private val logger = Logger(classOf[RegulatedEntityProvider]) + + def getRegulatedEntities(): List[RegulatedEntityTrait] + + def getRegulatedEntityByEntityId(entityId: String): Box[RegulatedEntityTrait] + + def createRegulatedEntity(certificateAuthorityCaOwnerId: Option[String], + entityCertificatePublicKey: Option[String], + entityName: Option[String], + entityCode: Option[String], + entityType: Option[String], + entityAddress: Option[String], + entityTownCity: Option[String], + entityPostCode: Option[String], + entityCountry: Option[String], + entityWebSite: Option[String], + services: Option[String] + ): Box[RegulatedEntityTrait] + + def deleteRegulatedEntity(id: String): Box[Boolean] + +} \ No newline at end of file diff --git a/obp-api/src/main/scala/code/remotedata/RemotedataConsumers.scala b/obp-api/src/main/scala/code/remotedata/RemotedataConsumers.scala index 06f991656..714644782 100644 --- a/obp-api/src/main/scala/code/remotedata/RemotedataConsumers.scala +++ b/obp-api/src/main/scala/code/remotedata/RemotedataConsumers.scala @@ -2,6 +2,7 @@ package code.remotedata import akka.pattern.ask import code.actorsystem.ObpActorInit +import code.api.util.{CallContext, OBPQueryParam} import code.consumer.{ConsumersProvider, RemotedataConsumersCaseClasses} import code.model._ import net.liftweb.common._ @@ -35,8 +36,8 @@ object RemotedataConsumers extends ObpActorInit with ConsumersProvider { def getConsumersByUserIdFuture(id: String): Future[List[Consumer]] = (actor ? cc.getConsumersByUserIdFuture(id)).mapTo[List[Consumer]] - def getConsumersFuture(): Future[List[Consumer]] = - (actor ? cc.getConsumersFuture()).mapTo[List[Consumer]] + def getConsumersFuture(httpParams: List[OBPQueryParam], callContext: Option[CallContext]): Future[List[Consumer]] = + (actor ? cc.getConsumersFuture(httpParams: List[OBPQueryParam], callContext: Option[CallContext])).mapTo[List[Consumer]] def createConsumer(key: Option[String], secret: Option[String], isActive: Option[Boolean], name: Option[String], appType: Option[AppType], description: Option[String], developerEmail: Option[String], redirectURL: Option[String], createdByUserId: Option[String], clientCertificate: Option[String] = None, company: Option[String] = None): Box[Consumer] = getValueFromFuture( (actor ? cc.createConsumer(key, secret, isActive, name, appType, description, developerEmail, redirectURL, createdByUserId, clientCertificate, company)).mapTo[Box[Consumer]] diff --git a/obp-api/src/main/scala/code/remotedata/RemotedataConsumersActor.scala b/obp-api/src/main/scala/code/remotedata/RemotedataConsumersActor.scala index fe6db3f0a..84a7264da 100644 --- a/obp-api/src/main/scala/code/remotedata/RemotedataConsumersActor.scala +++ b/obp-api/src/main/scala/code/remotedata/RemotedataConsumersActor.scala @@ -2,6 +2,7 @@ package code.remotedata import akka.actor.Actor import code.actorsystem.ObpActorHelper +import code.api.util.{CallContext, OBPQueryParam} import code.consumer.RemotedataConsumersCaseClasses import code.model.{MappedConsumersProvider, _} import code.util.Helper.MdcLoggable @@ -41,9 +42,9 @@ class RemotedataConsumersActor extends Actor with ObpActorHelper with MdcLoggabl logger.debug(s"getConsumersByUserIdFuture($id)") sender ! (mapper.getConsumersByUserId(id)) - case cc.getConsumersFuture() => + case cc.getConsumersFuture(httpParams: List[OBPQueryParam], callContext: Option[CallContext]) => logger.debug(s"getConsumersFuture()") - sender ! (mapper.getConsumers()) + sender ! (mapper.getConsumers(httpParams: List[OBPQueryParam], callContext: Option[CallContext])) case cc.createConsumer(key: Option[String], secret: Option[String], isActive: Option[Boolean], name: Option[String], appType: Option[AppType], description: Option[String], developerEmail: Option[String], redirectURL: Option[String], createdByUserId: Option[String], clientCertificate: Option[String], company: Option[String]) => logger.debug(s"createConsumer(*****, *****, ${isActive.getOrElse("None")}, ${name.getOrElse("None")}, ${appType.getOrElse("None")}, ${description.getOrElse("None")}, ${developerEmail.getOrElse("None")}, ${redirectURL.getOrElse("None")}, ${createdByUserId.getOrElse("None")}, ${clientCertificate.getOrElse("None")}, ${company.getOrElse("None")})") diff --git a/obp-api/src/main/scala/code/snippet/OAuthWorkedThanks.scala b/obp-api/src/main/scala/code/snippet/OAuthWorkedThanks.scala index 982b4a4dc..8b315dbbf 100644 --- a/obp-api/src/main/scala/code/snippet/OAuthWorkedThanks.scala +++ b/obp-api/src/main/scala/code/snippet/OAuthWorkedThanks.scala @@ -48,8 +48,10 @@ class OAuthWorkedThanks extends MdcLoggable { val redirectUrl = ObpS.param("redirectUrl").map(urlDecode(_)) logger.debug(s"OAuthWorkedThanks.thanks.redirectUrl $redirectUrl") //extract the clean(omit the parameters) redirect url from request url - val requestedRedirectURL = Helper.extractCleanRedirectURL(redirectUrl.openOr("invalidRequestedRedirectURL")) openOr("invalidRequestedRedirectURL") - logger.debug(s"OAuthWorkedThanks.thanks.requestedRedirectURL $requestedRedirectURL") + val staticPortionOfRedirectUrl = Helper.getStaticPortionOfRedirectURL(redirectUrl.openOr("invalidRequestedRedirectURL")) openOr("invalidRequestedRedirectURL") + val hostOnlyOfRedirectUrlLegacy = Helper.getHostOnlyOfRedirectURL(staticPortionOfRedirectUrl) openOr("invalidRequestedRedirectURL") + logger.debug(s"OAuthWorkedThanks.thanks.staticPortionOfRedirectUrl $staticPortionOfRedirectUrl") + logger.debug(s"OAuthWorkedThanks.thanks.hostOnlyOfRedirectUrlLegacy $hostOnlyOfRedirectUrlLegacy") val requestedOauthToken = Helper.extractOauthToken(redirectUrl.openOr("No Oauth Token here")) openOr("No Oauth Token here") logger.debug(s"OAuthWorkedThanks.thanks.requestedOauthToken $requestedOauthToken") @@ -62,13 +64,10 @@ class OAuthWorkedThanks extends MdcLoggable { redirectUrl match { case Full(url) => - //this redirect url is checked by following, no open redirect issue. - // TODO maybe handle case of extra trailing / on the url ? + val incorrectRedirectUrlMessage = s"The validRedirectURL is $validRedirectURL but the staticPortionOfRedirectUrl was $staticPortionOfRedirectUrl" - val incorrectRedirectUrlMessage = s"The validRedirectURL is $validRedirectURL but the requestedRedirectURL was $requestedRedirectURL" - - - if(validRedirectURL.equals(requestedRedirectURL)) { + //hostOnlyOfRedirectUrlLegacy is deprecated now, we use the staticPortionOfRedirectUrl stead, + if(validRedirectURL.equals(staticPortionOfRedirectUrl)|| validRedirectURL.equals(hostOnlyOfRedirectUrlLegacy)) { "#redirect-link [href]" #> url & ".app-name"#> appName //there may be several places to be modified in html, so here use the class, not the id. }else{ diff --git a/obp-api/src/main/scala/code/util/Helper.scala b/obp-api/src/main/scala/code/util/Helper.scala index cb09e79c7..fa3cd2aa9 100644 --- a/obp-api/src/main/scala/code/util/Helper.scala +++ b/obp-api/src/main/scala/code/util/Helper.scala @@ -1,9 +1,8 @@ package code.util -import java.net.{Socket, SocketException} +import java.net.{Socket, SocketException, URL} import java.util.UUID.randomUUID import java.util.{Date, GregorianCalendar} - import code.api.util.{APIUtil, CallContext, CallContextLight, CustomJsonFormats} import code.api.{APIFailureNewStyle, Constant} import code.api.util.APIUtil.fullBoxOrException @@ -20,7 +19,9 @@ import com.openbankproject.commons.util.{ReflectUtils, RequiredFieldValidation, import com.tesobe.CacheKeyFromArguments import net.liftweb.http.S import net.liftweb.util.Helpers +import net.liftweb.util.Helpers.tryo import net.sf.cglib.proxy.{Enhancer, MethodInterceptor, MethodProxy} + import java.lang.reflect.Method import scala.concurrent.Future import scala.util.Random @@ -167,27 +168,30 @@ object Helper extends Loggable { prettyRender(decompose(input)) } + /** - * extract clean redirect url from input value, because input may have some parameters, such as the following examples
- * eg1: http://localhost:8082/oauthcallback?....--> http://localhost:8082
- * eg2: http://localhost:8016?oautallback?=3NLMGV ...--> http://localhost:8016 - * - * @param input a long url with parameters - * @return clean redirect url - */ - def extractCleanRedirectURL(input: String): Box[String] = { - /** - * pattern eg1: http://xxxxxx?oautxxxx -->http://xxxxxx - * pattern eg2: https://xxxxxx/oautxxxx -->http://xxxxxx - */ - //Note: the pattern should be : val pattern = "(https?):\\/\\/(.*)(?=((\\/)|(\\?))oauthcallback*)".r, but the OAuthTest is different, so add the following logic - val pattern = "([A-Za-z][A-Za-z0-9+.-]*):\\/\\/(.*)(?=((\\/)|(\\?))oauth*)".r - val validRedirectURL = pattern findFirstIn input - // Now for the OAuthTest, the redirect format is : http://localhost:8016?oauth_token=G5AEA2U1WG404EGHTIGBHKRR4YJZAPPHWKOMNEEV&oauth_verifier=53018 - // It is not the normal case: http://localhost:8082/oauthcallback?oauth_token=LUDKELGJXRDOC1AK1X1TOYIXM5W1AORFJT5KE43B&oauth_verifier=14062 - // So add the split function to select the first value; eg: Array(http://localhost:8082, thcallback) --> http://localhost:8082 - val extractCleanURL = validRedirectURL.getOrElse("").split("/oauth")(0) - Full(extractCleanURL) + * + * @param redirectUrl eg: http://localhost:8082/oauthcallback?oauth_token=G5AEA2U1WG404EGHTIGBHKRR4YJZAPPHWKOMNEEV&oauth_verifier=53018 + * @return http://localhost:8082/oauthcallback + */ + def getStaticPortionOfRedirectURL(redirectUrl: String): Box[String] = { + tryo(redirectUrl.split("\\?")(0)) //return everything before the "?" + } + + /** + * extract clean redirect url from input value, because input may have some parameters, such as the following examples
+ * eg1: http://localhost:8082/oauthcallback?....--> http://localhost:8082
+ * eg2: http://localhost:8016?oautallback?=3NLMGV ...--> http://localhost:8016 + * + * @param redirectUrl -> http://localhost:8082/oauthcallback?oauth_token=G5AEA2U1WG404EGHTIGBHKRR4YJZAPPHWKOMNEEV&oauth_verifier=53018 + * @return hostOnlyOfRedirectURL-> http://localhost:8082 + */ + @deprecated("We can not only use hostname as the redirectUrl, now add new method `getStaticPortionOfRedirectURL` ","05.12.2023") + def getHostOnlyOfRedirectURL(redirectUrl: String): Box[String] = { + val url = new URL(redirectUrl) //eg: http://localhost:8082/oauthcallback?oauth_token=G5AEA2U1WG404EGHTIGBHKRR4YJZAPPHWKOMNEEV&oauth_verifier=53018 + val protocol = url.getProtocol() // http + val authority = url.getAuthority()// localhost:8082, this will contain the port. + tryo(s"$protocol://$authority") // http://localhost:8082 } /** @@ -480,7 +484,7 @@ object Helper extends Loggable { }else if((args.length>0) && args.apply(0).toString.equalsIgnoreCase("consumer_key")){ result.asInstanceOf[Box[String]].filter(APIUtil.basicConsumerKeyValidation(_)==SILENCE_IS_GOLDEN) }else if((args.length>0) && args.apply(0).toString.equalsIgnoreCase("redirectUrl")){ - result.asInstanceOf[Box[String]].filter(APIUtil.basicUrlValidation(_)) + result.asInstanceOf[Box[String]].filter(APIUtil.basicUriAndQueryStringValidation(_)) } else{ result.asInstanceOf[Box[String]].filter(APIUtil.checkMediumString(_)==SILENCE_IS_GOLDEN) } diff --git a/obp-api/src/main/webapp/WEB-INF/web.xml b/obp-api/src/main/webapp/WEB-INF/web.xml index 16bd7588d..f421ec503 100644 --- a/obp-api/src/main/webapp/WEB-INF/web.xml +++ b/obp-api/src/main/webapp/WEB-INF/web.xml @@ -15,6 +15,13 @@ LiftFilter + + + + + + + /* diff --git a/obp-api/src/test/scala/code/api/v5_1_0/RegulatedEntityTest.scala b/obp-api/src/test/scala/code/api/v5_1_0/RegulatedEntityTest.scala new file mode 100644 index 000000000..5d7696209 --- /dev/null +++ b/obp-api/src/test/scala/code/api/v5_1_0/RegulatedEntityTest.scala @@ -0,0 +1,120 @@ +package code.api.v5_1_0 + +import code.api.ResourceDocs1_4_0.SwaggerDefinitionsJSON.regulatedEntityPostJsonV510 +import code.api.util.APIUtil.OAuth._ +import code.api.util.ApiRole.{CanCreateRegulatedEntity, CanDeleteRegulatedEntity, CanGetSystemIntegrity} +import code.api.util.ErrorMessages.{UserHasMissingRoles, UserNotLoggedIn} +import code.api.v5_1_0.OBPAPI5_1_0.Implementations5_1_0 +import code.entitlement.Entitlement +import com.github.dwickern.macros.NameOf.nameOf +import com.openbankproject.commons.model.ErrorMessage +import com.openbankproject.commons.util.ApiVersion +import net.liftweb.json.Serialization._ +import org.scalatest.Tag + +class RegulatedEntityTest extends V510ServerSetup { + /** + * Test tags + * Example: To run tests with tag "getPermissions": + * mvn test -D tagsToInclude + * + * This is made possible by the scalatest maven plugin + */ + object VersionOfApi extends Tag(ApiVersion.v5_1_0.toString) + object ApiEndpoint1 extends Tag(nameOf(Implementations5_1_0.createRegulatedEntity)) + object ApiEndpoint2 extends Tag(nameOf(Implementations5_1_0.getRegulatedEntityById)) + object ApiEndpoint3 extends Tag(nameOf(Implementations5_1_0.getRegulatedEntityById)) + object ApiEndpoint4 extends Tag(nameOf(Implementations5_1_0.deleteRegulatedEntity)) + + feature(s"test $ApiEndpoint1 version $VersionOfApi - Unauthorized access") { + scenario("We will call the endpoint without user credentials", ApiEndpoint1, VersionOfApi) { + When("We make a request v5.1.0") + val request510 = (v5_1_0_Request / "regulated-entities").POST + val response510 = makePostRequest(request510, write(regulatedEntityPostJsonV510)) + Then("We should get a 401") + response510.code should equal(401) + response510.body.extract[ErrorMessage].message should equal(UserNotLoggedIn) + } + } + + feature(s"test $ApiEndpoint1 version $VersionOfApi - Authorized access") { + scenario("We will call the endpoint with user credentials but without a proper entitlement", ApiEndpoint1, VersionOfApi) { + When("We make a request v5.1.0") + val request510 = (v5_1_0_Request / "regulated-entities").POST <@(user1) + val response510 = makePostRequest(request510, write(regulatedEntityPostJsonV510)) + Then("error should be " + UserHasMissingRoles + CanCreateRegulatedEntity) + response510.code should equal(403) + response510.body.extract[ErrorMessage].message should be (UserHasMissingRoles + CanCreateRegulatedEntity) + } + } + + feature(s"test $ApiEndpoint1 version $VersionOfApi - Authorized access") { + scenario("We will call the endpoint with user credentials and a proper entitlement", ApiEndpoint1, VersionOfApi) { + Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, CanCreateRegulatedEntity.toString) + When("We make a request v5.1.0") + val request510 = (v5_1_0_Request / "regulated-entities").POST <@ (user1) + val response510 = makePostRequest(request510, write(regulatedEntityPostJsonV510)) + Then("We get successful response") + response510.code should equal(201) + response510.body.extract[RegulatedEntityJsonV510] + } + } + + // ApiEndpoint4 - deleteRegulatedEntity + feature(s"test $ApiEndpoint4 version $VersionOfApi - Unauthorized access") { + scenario("We will call the endpoint without user credentials", ApiEndpoint1, VersionOfApi) { + When("We make a request v5.1.0") + val request510 = (v5_1_0_Request / "regulated-entities" / "some id").DELETE + val response510 = makeDeleteRequest(request510) + Then("We should get a 401") + response510.code should equal(401) + response510.body.extract[ErrorMessage].message should equal(UserNotLoggedIn) + } + } + feature(s"test $ApiEndpoint4 version $VersionOfApi - Authorized access") { + scenario("We will call the endpoint with user credentials but without a proper entitlement", ApiEndpoint1, VersionOfApi) { + When("We make a request v5.1.0") + val request510 = (v5_1_0_Request / "regulated-entities" / "some id").DELETE <@ (user1) + val response510 = makeDeleteRequest(request510) + Then("error should be " + UserHasMissingRoles + CanDeleteRegulatedEntity) + response510.code should equal(403) + response510.body.extract[ErrorMessage].message should be(UserHasMissingRoles + CanDeleteRegulatedEntity) + } + } + + + feature(s"test $ApiEndpoint1, $ApiEndpoint2, $ApiEndpoint3, $ApiEndpoint4 version $VersionOfApi - CRUD") { + scenario("We will call the endpoint with user credentials but without a proper entitlement", ApiEndpoint1, VersionOfApi) { + // Create a row + Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, CanCreateRegulatedEntity.toString) + val request510 = (v5_1_0_Request / "regulated-entities").POST <@ (user1) + val response510 = makePostRequest(request510, write(regulatedEntityPostJsonV510)) + Then("We get successful response") + response510.code should equal(201) + val createdRow: RegulatedEntityJsonV510 = response510.body.extract[RegulatedEntityJsonV510] + + // Get the row by id + val getRequest510 = (v5_1_0_Request / "regulated-entities" / createdRow.entity_id).GET + val getResponse510 = makeGetRequest(getRequest510) + getResponse510.code should equal(200) + val gottenRow: RegulatedEntityJsonV510 = getResponse510.body.extract[RegulatedEntityJsonV510] + + // TRy to match responses + createdRow should equal(gottenRow) + + // Delete the row + Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, CanDeleteRegulatedEntity.toString) + val deleteRequest510 = (v5_1_0_Request / "regulated-entities" / gottenRow.entity_id).DELETE <@ (user1) + val deleteResponse510 = makeDeleteRequest(deleteRequest510) + deleteResponse510.code should equal(200) + + + // Get all rows + val getAllRequest510 = (v5_1_0_Request / "regulated-entities").GET + val getAllResponse510 = makeGetRequest(getAllRequest510) + getAllResponse510.code should equal(200) + val allRows = getResponse510.body.extract[RegulatedEntitiesJsonV510] + allRows.entities.length should equal(0) + } + } +} diff --git a/obp-api/src/test/scala/code/bankconnectors/StoredProcedureConnector_vDec2019Test.scala b/obp-api/src/test/scala/code/bankconnectors/StoredProcedureConnector_vDec2019Test.scala deleted file mode 100644 index c60464054..000000000 --- a/obp-api/src/test/scala/code/bankconnectors/StoredProcedureConnector_vDec2019Test.scala +++ /dev/null @@ -1,101 +0,0 @@ -package code.bankconnectors.vMay2019 - -/* -Open Bank Project - API -Copyright (C) 2011-2019, TESOBE GmbH - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU Affero General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Affero General Public License for more details. - -You should have received a copy of the GNU Affero General Public License -along with this program. If not, see http://www.gnu.org/licenses/. - -Email: contact@tesobe.com -TESOBE GmbH -Osloerstrasse 16/17 -Berlin 13359, Germany -*/ - - -import code.actorsystem.ObpActorInit -import code.api.JSONFactoryGateway.PayloadOfJwtJSON -import code.api.util.{APIUtil, CallContext, CustomJsonFormats} -import code.bankconnectors.Connector -import code.bankconnectors.storedprocedure.StoredProcedureConnector_vDec2019 -import code.bankconnectors.vSept2018._ -import code.kafka.KafkaHelper -import code.setup.{DefaultUsers, KafkaSetup, ServerSetupWithTestData} -import com.openbankproject.commons.dto.InBoundGetBanks -import com.openbankproject.commons.model._ -import net.liftweb.common.{Box, Failure, Full} -import org.scalatest.Tag - -class StoredProcedureConnector_vDec2019Test extends ServerSetupWithTestData with DefaultUsers with ObpActorInit{ - - override implicit val formats = CustomJsonFormats.formats - - object StoredProcedureConnector_vDec2019Test extends Tag("StoredProcedureConnector_vDec2019") - - val callContext = Some( - CallContext( - gatewayLoginRequestPayload = Some(PayloadOfJwtJSON( - login_user_name = "", - is_first = false, - app_id = "", - app_name = "", - time_stamp = "", - cbs_token = Some(""), - cbs_id = "", - session_id = Some(""))), - user = Full(resourceUser1) - ) - ) - - val PropsConnectorVersion = APIUtil.getPropsValue("connector").openOrThrowException("connector props filed `connector` not set") - - - feature("Test all stored_procedure methods") { - if (PropsConnectorVersion == "stored_procedure_vDec2019") { -// scenario("test `checkBankAccountExists` method, there no need Adapter message for this method!", StoredProcedureConnector_vDec2019Test) { -// val checkBankAccountExists = StoredProcedureConnector_vDec2019.checkBankAccountExists(testBankId1,testAccountId1,callContext) -// getValueFromFuture(checkBankAccountExists)._1.isDefined equals (true) -// } -// -// scenario("test `getBankAccounts` method, there no need Adapter message for this method!", StoredProcedureConnector_vDec2019Test) { -// val checkBankAccountExists = StoredProcedureConnector_vDec2019.checkBankAccountExists(testBankId1,testAccountId1,callContext) -// getValueFromFuture(checkBankAccountExists)._1.isDefined equals (true) -// } -// scenario("test `getBank` method, there no need Adapter message for this method!", StoredProcedureConnector_vDec2019Test) { -// val transantionRequests210 = StoredProcedureConnector_vDec2019.getBank(testBankId1, callContext) -// getValueFromFuture(transantionRequests210).isDefined equals (true) -// } -// -// -// scenario("test `getBanks` method, there no need Adapter message for this method!", StoredProcedureConnector_vDec2019Test) { -// val transantionRequests210 = StoredProcedureConnector_vDec2019.getBanks(callContext) -// getValueFromFuture(transantionRequests210).isDefined equals (true) -// } -// -// scenario("test `getTransactionRequests210` method, there no need Adapter message for this method!", StoredProcedureConnector_vDec2019Test) { -// val transantionRequests210: Box[(List[TransactionRequest], Option[CallContext])] = StoredProcedureConnector_vDec2019.getTransactionRequests210(resourceUser1, null, callContext) -// transantionRequests210.isDefined equals(true) -// } - - scenario("test `getTransactions` method, there no need Adapter message for this method!", StoredProcedureConnector_vDec2019Test) { - val transactions = StoredProcedureConnector_vDec2019.getTransactions(testBankId1, testAccountId1, callContext, Nil) - val trans = getValueFromFuture(transactions)._1.openOrThrowException("Should not be empty!") - trans.head.description.isDefined equals (true) - } - - } else { - ignore("ignore test getObpConnectorLoopback, if it is mapped connector", StoredProcedureConnector_vDec2019Test) {} - } - } -} \ No newline at end of file diff --git a/obp-api/src/test/scala/code/bankconnectors/vMay2019/KafkaMappedConnector_vMay2019Test.scala b/obp-api/src/test/scala/code/bankconnectors/vMay2019/KafkaMappedConnector_vMay2019Test.scala deleted file mode 100644 index e3883ac1b..000000000 --- a/obp-api/src/test/scala/code/bankconnectors/vMay2019/KafkaMappedConnector_vMay2019Test.scala +++ /dev/null @@ -1,157 +0,0 @@ -package code.bankconnectors.vMay2019 - -/* -Open Bank Project - API -Copyright (C) 2011-2019, TESOBE GmbH - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU Affero General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Affero General Public License for more details. - -You should have received a copy of the GNU Affero General Public License -along with this program. If not, see http://www.gnu.org/licenses/. - -Email: contact@tesobe.com -TESOBE GmbH -Osloerstrasse 16/17 -Berlin 13359, Germany -*/ - - -import code.api.JSONFactoryGateway.PayloadOfJwtJSON -import code.api.util.{APIUtil, CallContext, CustomJsonFormats} -import code.bankconnectors.Connector -import code.bankconnectors.vSept2018._ -import code.kafka.KafkaHelper -import code.setup.{KafkaSetup, ServerSetupWithTestData} -import com.openbankproject.commons.dto.InBoundGetBanks -import com.openbankproject.commons.model._ -import net.liftweb.common.{Box, Failure, Full} -import org.scalatest.Tag - -class KafkaMappedConnector_vMay2019Test extends KafkaSetup with ServerSetupWithTestData { - - override implicit val formats = CustomJsonFormats.formats - - object kafkaTest extends Tag("kafkaTest") - - val callContext = Some( - CallContext( - gatewayLoginRequestPayload = Some(PayloadOfJwtJSON( - login_user_name = "", - is_first = false, - app_id = "", - app_name = "", - time_stamp = "", - cbs_token = Some(""), - cbs_id = "", - session_id = Some(""))), - user = Full(resourceUser1) - ) - ) - - val PropsConnectorVersion = APIUtil.getPropsValue("connector").openOrThrowException("connector props filed `connector` not set") - - - feature("Send and retrieve message") { - if (PropsConnectorVersion =="mapped" || PropsConnectorVersion =="star") { - ignore("ignore test getObpConnectorLoopback, if it is mapped connector", kafkaTest) {} - } else - scenario("1st test `getObpConnectorLoopback` method, there no need Adapter message for this method!", kafkaTest) { - //This method is only used for `kafka` connector, should first set `connector=kafka_vSept2018` in test.default.props. - //and also need to set up `api_instance_id` and `remotedata.timeout` field for it. - val propsApiInstanceId = code.api.Constant.ApiInstanceId - val propsRemotedataTimeout = APIUtil.getPropsValue("remotedata.timeout").openOrThrowException("connector props filed `remotedata.timeout` not set") - - PropsConnectorVersion contains ("kafka") should be(true) - propsApiInstanceId should be("1") - propsRemotedataTimeout should be("10") - - When("We call this method, and get the response. ") - val future = KafkaHelper.echoKafkaServer - val result = future.getContent - - Then("If it return value successfully, that mean api <--> kafka is working well. We only need check one filed of response.") - val connectorVersion = result.connectorVersion - connectorVersion should be(PropsConnectorVersion) - - Then("For KafkaMappedConnector_vSept2018 connector, we need to make these two methods work `getAuthInfoFirstCbsCall` and `getAuthInfo`") - - val firstAuthInfo: Box[AuthInfo] = for { - firstGetAuthInfo <- KafkaMappedConnector_vSept2018.getAuthInfoFirstCbsCall("","", callContext) - } yield { - (firstGetAuthInfo) - } - firstAuthInfo.openOrThrowException("firstAuthInfo Can not be empty here. ") - - val authInfo: Box[AuthInfo] = for { - getAuthInfo <- KafkaMappedConnector_vSept2018.getAuthInfo(callContext) - } yield { - getAuthInfo - } - authInfo.openOrThrowException("firstAuthInfo Can not be empty here. ") - - } - - if (PropsConnectorVersion =="mapped" || PropsConnectorVersion =="star") { - ignore("ignore test processRequest, if it is mapped connector", kafkaTest) {} - } else - scenario("Send and retrieve message directly to and from kafka", kafkaTest) { - val emptyStatusMessage = InboundStatusMessage("", "", "", "") - val inBound = InboundGetBanks(InboundAuthInfo("", ""), Status("", List(emptyStatusMessage)), List(InboundBank("1", "2", "3", "4"))) - When("send a OutboundGetBanks message") - - dispathResponse(inBound) - val req = OutboundGetBanks(AuthInfo()) - - val future = processRequest[InboundGetBanks](req) - val result: Box[InboundGetBanks] = future.getContent - - result should be(Full(inBound)) - } - - } - - feature("Test the getBank error cases") { - - if (PropsConnectorVersion =="mapped" || PropsConnectorVersion =="star") { - ignore("ignore test getBanks, if it is mapped connector", kafkaTest) {} - } else - scenario(s"test getBanksFuture -- status.hasError", kafkaTest) { - val inbound = Connector.connector.vend.messageDocs.filter(_.exampleInboundMessage.isInstanceOf[InBoundGetBanks]).map(_.exampleInboundMessage).head.asInstanceOf[InBoundGetBanks] - //This inBound.status.errorCode != "", so it will throw the error back. - val expectedValue = Failure("INTERNAL-"+ inbound.status.errorCode+". + CoreBank-Status:" + inbound.status.backendMessages) - dispathResponse(inbound) - val future = Connector.connector.vend.getBanks(callContext) - - dispathResponse(inbound) - val result = future.getContent - result should be(expectedValue) - } - - if (PropsConnectorVersion =="mapped" || PropsConnectorVersion =="star") { - ignore("ignore test getBanksFuture -- status.hasNoError, if it is mapped connector", kafkaTest) {} - } else - scenario(s"test getBanksFuture -- status.hasNoError", kafkaTest) { - val inbound = Connector.connector.vend.messageDocs - .filter(_.exampleInboundMessage.isInstanceOf[InBoundGetBanks]) - .map(_.exampleInboundMessage).head.asInstanceOf[InBoundGetBanks] - .copy(status = Status("", Nil)) // This will set errorCode to "", no it works - //This inBound.status.errorCode != "", so it will throw the error back. - val expectedValue = Full(inbound.data.head.bankId).toString - dispathResponse(inbound) - val future = Connector.connector.vend.getBanks(callContext) - - dispathResponse(inbound) - val result = future.getContent - result.map(_._1.head.bankId).toString should be(expectedValue) - } - - } -} \ No newline at end of file diff --git a/obp-api/src/test/scala/code/kafka/KafkaTest.scala b/obp-api/src/test/scala/code/kafka/KafkaTest.scala deleted file mode 100644 index 5f342f1ae..000000000 --- a/obp-api/src/test/scala/code/kafka/KafkaTest.scala +++ /dev/null @@ -1,401 +0,0 @@ -package code.kafka - -import java.util.{Date, UUID} - -import code.api.JSONFactoryGateway.PayloadOfJwtJSON -import code.api.util.{APIUtil, CallContext, CustomJsonFormats} -import code.api.v2_1_0.TransactionRequestBodyCommonJSON -import code.bankconnectors.Connector -import code.bankconnectors.vSept2018._ -import code.setup.{KafkaSetup, ServerSetupWithTestData} -import com.openbankproject.commons.dto.{InBoundGetKycChecks, InBoundGetKycMedias, InBoundGetKycStatuses} -import com.openbankproject.commons.model._ -import net.liftweb.common.{Box, Full} -import org.scalatest.Tag - -import scala.collection.immutable.List - -class KafkaTest extends KafkaSetup with ServerSetupWithTestData { - - override implicit val formats = CustomJsonFormats.formats - - object kafkaTest extends Tag("kafkaTest") - - val callContext = Some( - CallContext( - gatewayLoginRequestPayload = Some(PayloadOfJwtJSON( - login_user_name = "", - is_first = false, - app_id = "", - app_name = "", - time_stamp = "", - cbs_token = Some(""), - cbs_id = "", - session_id = Some(""))), - user = Full(resourceUser1) - ) - ) - - val PropsConnectorVersion = APIUtil.getPropsValue("connector").openOrThrowException("connector props filed `connector` not set") - - - feature("Send and retrieve message") { - if (PropsConnectorVersion =="mapped" || PropsConnectorVersion =="star"){ - ignore("ignore test getObpConnectorLoopback, if it is mapped connector", kafkaTest) {} - } else - scenario("1st test `getObpConnectorLoopback` method, there no need Adapter message for this method!", kafkaTest) { - //This method is only used for `kafka` connector, should first set `connector=kafka_vSept2018` in test.default.props. - //and also need to set up `api_instance_id` and `remotedata.timeout` field for it. - val propsApiInstanceId = code.api.Constant.ApiInstanceId - val propsRemotedataTimeout = APIUtil.getPropsValue("remotedata.timeout").openOrThrowException("connector props filed `remotedata.timeout` not set") - - PropsConnectorVersion contains ("kafka") should be (true) - propsApiInstanceId should be ("1") - propsRemotedataTimeout should be ("10") - - When("We call this method, and get the response. ") - val future = KafkaHelper.echoKafkaServer - val result = future.getContent - - Then("If it return value successfully, that mean api <--> kafka is working well. We only need check one filed of response.") - val connectorVersion= result.connectorVersion - connectorVersion should be (PropsConnectorVersion) - - Then("For KafkaMappedConnector_vSept2018 connector, we need to make these two methods work `getAuthInfoFirstCbsCall` and `getAuthInfo`") - - val firstAuthInfo: Box[AuthInfo] = for{ - firstGetAuthInfo <- KafkaMappedConnector_vSept2018.getAuthInfoFirstCbsCall("","", callContext) - } yield { - (firstGetAuthInfo) - } - firstAuthInfo.openOrThrowException("firstAuthInfo Can not be empty here. ") - - val authInfo: Box[AuthInfo] = for{ - getAuthInfo <- KafkaMappedConnector_vSept2018.getAuthInfo(callContext) - } yield { - getAuthInfo - } - authInfo.openOrThrowException("firstAuthInfo Can not be empty here. ") - - } - -// if (PropsConnectorVersion =="mapped" || PropsConnectorVersion =="star"){ -// ignore("ignore test processRequest, if it is mapped connector", kafkaTest) {} -// } else -// scenario("Send and retrieve message directly to and from kafka", kafkaTest) { -// val emptyStatusMessage = InboundStatusMessage("", "", "", "") -// val inBound = InboundGetBanks(InboundAuthInfo("", ""), Status("", List(emptyStatusMessage)), List(InboundBank("1", "2", "3", "4"))) -// When("send a OutboundGetBanks message") -// -// dispathResponse(inBound) -// val req = OutboundGetBanks(AuthInfo()) -// -// val future = processRequest[InboundGetBanks](req) -// val result: Box[InboundGetBanks] = future.getContent -// -// result should be (Full(inBound)) -// } - - if (PropsConnectorVersion =="mapped" || PropsConnectorVersion =="star"){ - ignore("ignore test getKycStatuses, if it is mapped connector", kafkaTest) {} - } else - scenario("test `getKycStatuses` method",kafkaTest) { - When("send a OutboundGetKycStatuses api message") - val emptyStatusMessage = InboundStatusMessage("", "", "", "") - val kycStatusCommons = KycStatusCommons(bankId = "hello_bank_id", customerId = "hello_customer_id", customerNumber = "hello_customer_number", ok = true, date = new Date()) - val singleInboundBank = List(kycStatusCommons) - val inboundAdapterCallContext = InboundAdapterCallContext(correlationId="some_correlationId") - val inBound = InBoundGetKycStatuses(inboundAdapterCallContext, Status("", List(emptyStatusMessage)), singleInboundBank) - - dispathResponse(inBound) - val future = Connector.connector.vend.getKycStatuses(kycStatusCommons.customerId, Some(CallContext())) - - val result: (Box[List[KycStatus]], Option[CallContext]) = future.getContent - val expectResult = Full(singleInboundBank) - result._1.toString should be (expectResult.toString) - } - - if (PropsConnectorVersion =="mapped" || PropsConnectorVersion =="star"){ - ignore("ignore test getKycChecks, if it is mapped connector", kafkaTest) {} - } else - scenario("test `getKycChecks` method", kafkaTest) { - When("send a OutboundGetKycChecks api message") - val inBound = Connector.connector.vend.messageDocs.filter(_.process =="obp.getKycChecks").map(_.exampleInboundMessage).head.asInstanceOf[InBoundGetKycChecks] - - dispathResponse(inBound) - - val future = Connector.connector.vend.getKycChecks(inBound.data.head.customerId, Some(CallContext())) - val result: (Box[List[KycCheck]], Option[CallContext]) = future.getContent - val expectResult = Full(inBound.data) - result._1.toString should be (expectResult.toString) - } - - if (PropsConnectorVersion =="mapped" || PropsConnectorVersion =="star"){ - ignore("ignore test getKycMedias, if it is mapped connector", kafkaTest) {} - } else - scenario("test `getKycMedias` method",kafkaTest) { - When("send a OutboundetKycMedias api message") - val inBound = Connector.connector.vend.messageDocs.filter(_.process =="obp.getKycMedias").map(_.exampleInboundMessage).head.asInstanceOf[InBoundGetKycMedias] - - dispathResponse(inBound) - val future = Connector.connector.vend.getKycMedias(inBound.data.head.customerId, Some(CallContext())) - - val result: (Box[List[KycMedia]], Option[CallContext]) = future.getContent - val expectResult = Full(inBound.data) - result._1.toString should be (expectResult.toString) - } - - if (PropsConnectorVersion =="mapped" || PropsConnectorVersion =="star"){ - ignore("ignore test getAdapterInfo, if it is mapped connector", kafkaTest) {} - } else - scenario(s"test getAdapterInfo method",kafkaTest) { - When("send a getAdapterInfo api message") - val inBound = Connector.connector.vend.messageDocs.filter(_.process.toString.contains("getAdapterInfo")).map(_.exampleInboundMessage).head.asInstanceOf[InboundAdapterInfo] - - dispathResponse(inBound) - val future = Connector.connector.vend.getAdapterInfo(None) - - val result: Box[(InboundAdapterInfoInternal, Option[CallContext])] = future.getContent - result.map(_._1) should be (Full(inBound.data)) - } - - if (PropsConnectorVersion =="mapped" || PropsConnectorVersion =="star"){ - ignore("ignore test getUser, if it is mapped connector", kafkaTest) {} - } else - scenario(s"test getUser method",kafkaTest) { - When("send a getUser api message") - val inBound = Connector.connector.vend.messageDocs.filter(_.process.toString.contains("getUser")).map(_.exampleInboundMessage).head.asInstanceOf[InboundGetUserByUsernamePassword] - - dispathResponse(inBound) - val box = Connector.connector.vend.getUser("username","password") - - box.map(_.displayName) should be (Full(inBound.data.displayName)) - } - - if (PropsConnectorVersion =="mapped" || PropsConnectorVersion =="star"){ - ignore("ignore test getBanksFuture, if it is mapped connector", kafkaTest) {} - } else - scenario(s"test getBanksFuture method", kafkaTest) { - val inBound = Connector.connector.vend.messageDocs.filter(_.process.toString.contains("getBanks")).map(_.exampleInboundMessage).head.asInstanceOf[InboundGetBanks] - - dispathResponse(inBound) - val future = Connector.connector.vend.getBanks(None) - - val result = future.getContent - result.map(_._1.head.bankId).toString should be (Full(inBound.data.head.bankId).toString) - - } - - if (PropsConnectorVersion =="mapped" || PropsConnectorVersion =="star"){ - ignore("ignore test getBanks, if it is mapped connector", kafkaTest) {} - } else - scenario(s"test getBanks method", kafkaTest) { - val inBound = Connector.connector.vend.messageDocs.filter(_.process.toString.contains("getBanks")).map(_.exampleInboundMessage).head.asInstanceOf[InboundGetBanks] - - dispathResponse(inBound) - val box = Connector.connector.vend.getBanksLegacy(None) - - box.map(_._1.head.bankId).toString should be (Full(inBound.data.head.bankId).toString) - } - - if (PropsConnectorVersion =="mapped" || PropsConnectorVersion =="star"){ - ignore("ignore test getBank, if it is mapped connector", kafkaTest) {} - } else - scenario(s"test getBank method", kafkaTest) { - val inBound = Connector.connector.vend.messageDocs.filter(_.exampleInboundMessage.isInstanceOf[InboundGetBank]).map(_.exampleInboundMessage).head.asInstanceOf[InboundGetBank] - - dispathResponse(inBound) - val box = Connector.connector.vend.getBankLegacy(BankId(""), None) - - box.map(_._1.bankId).toString should be (Full(inBound.data.bankId).toString) - } - - if (PropsConnectorVersion =="mapped" || PropsConnectorVersion =="star"){ - ignore("ignore test getBankFuture, if it is mapped connector", kafkaTest) {} - } else - scenario(s"test getBankFuture method",kafkaTest) { - val inBound = Connector.connector.vend.messageDocs.filter(_.exampleInboundMessage.isInstanceOf[InboundGetBank]).map(_.exampleInboundMessage).head.asInstanceOf[InboundGetBank] - - dispathResponse(inBound) - val future = Connector.connector.vend.getBank(BankId(""), None) - val result = future.getContent - - result.map(_._1.bankId).toString should be (Full(inBound.data.bankId).toString) - } - - if (PropsConnectorVersion =="mapped" || PropsConnectorVersion =="star"){ - ignore("ignore test getBankAccountsForUserFuture, if it is mapped connector", kafkaTest) {} - } else - scenario(s"test getBankAccountsForUserFuture method",kafkaTest) { - val inBound = Connector.connector.vend.messageDocs.filter(_.exampleInboundMessage.isInstanceOf[InboundGetAccounts]).map(_.exampleInboundMessage).head.asInstanceOf[InboundGetAccounts] - - dispathResponse(inBound) - val future = Connector.connector.vend.getBankAccountsForUser("", "", callContext) - val result = future.getContent - - result.map(_._1.head).toString should be (Full(inBound.data.head).toString) - } - - if (PropsConnectorVersion =="mapped" || PropsConnectorVersion =="star"){ - ignore("ignore test getBankAccountsForUser, if it is mapped connector", kafkaTest) {} - } else - scenario(s"test getBankAccountsForUser method",kafkaTest) { - val inBound = Connector.connector.vend.messageDocs.filter(_.exampleInboundMessage.isInstanceOf[InboundGetAccounts]).map(_.exampleInboundMessage).head.asInstanceOf[InboundGetAccounts] - dispathResponse(inBound) - val box = Connector.connector.vend.getBankAccountsForUserLegacy("","", callContext) - - box.map(_._1.head).toString should be (Full(inBound.data.head).toString) - } - - if (PropsConnectorVersion =="mapped" || PropsConnectorVersion =="star"){ - ignore("ignore test getBankAccount, if it is mapped connector", kafkaTest) {} - } else - scenario(s"test getBankAccount method",kafkaTest) { - val inBound = Connector.connector.vend.messageDocs.filter(_.exampleInboundMessage.isInstanceOf[InboundGetAccountbyAccountID]).map(_.exampleInboundMessage).head.asInstanceOf[InboundGetAccountbyAccountID] - dispathResponse(inBound) - val box = Connector.connector.vend.getBankAccountLegacy(BankId(""), AccountId(""), callContext) - - box.map(_._1.bankId).toString should be (Full(inBound.data.head.bankId).toString) - box.map(_._1.accountId).toString should be (Full(inBound.data.head.accountId).toString) - } - - if (PropsConnectorVersion =="mapped" || PropsConnectorVersion =="star"){ - ignore("ignore test getBankAccountFuture, if it is mapped connector", kafkaTest) {} - } else - scenario(s"test getBankAccountFuture method",kafkaTest) { - val inBound = Connector.connector.vend.messageDocs.filter(_.exampleInboundMessage.isInstanceOf[InboundGetAccountbyAccountID]).map(_.exampleInboundMessage).head.asInstanceOf[InboundGetAccountbyAccountID] - dispathResponse(inBound) - val future = Connector.connector.vend.checkBankAccountExists(BankId(""), AccountId(""), callContext) - - val result = future.getContent - - result._1.map(_.accountId.value).toString should be (Full(inBound.data.head.accountId).toString) - result._1.map(_.bankId.value).toString should be (Full(inBound.data.head.bankId).toString) - - } - - if (PropsConnectorVersion =="mapped" || PropsConnectorVersion =="star"){ - ignore("ignore test getChallengeThreshold, if it is mapped connector", kafkaTest) {} - } else - scenario(s"test getChallengeThreshold method",kafkaTest) { - val inBound = Connector.connector.vend.messageDocs.filter(_.exampleInboundMessage.isInstanceOf[InboundGetChallengeThreshold]).map(_.exampleInboundMessage).head.asInstanceOf[InboundGetChallengeThreshold] - dispathResponse(inBound) - val future = Connector.connector.vend.getChallengeThreshold("","","","","","","", callContext) - - val result = future.getContent - - result._1.map(_.amount).toString should be (Full(inBound.data.amount).toString) - result._1.map(_.currency).toString should be (Full(inBound.data.currency).toString) - - } - - if (PropsConnectorVersion =="mapped" || PropsConnectorVersion =="star"){ - ignore("ignore test makePaymentv210, if it is mapped connector", kafkaTest) {} - } else - scenario(s"test makePaymentv210 method",kafkaTest) { - val inBound = Connector.connector.vend.messageDocs.filter(_.exampleInboundMessage.isInstanceOf[InboundCreateTransactionId]).map(_.exampleInboundMessage).head.asInstanceOf[InboundCreateTransactionId] - dispathResponse(inBound) - - val fromAccount = BankAccountSept2018(KafkaMappedConnector_vSept2018.inboundAccountSept2018Example) - val toAccount = BankAccountSept2018(KafkaMappedConnector_vSept2018.inboundAccountSept2018Example) - val transactionRequestId = TransactionRequestId(UUID.randomUUID().toString) - val transactionRequestCommonBody = TransactionRequestBodyCommonJSON(AmountOfMoneyJsonV121("",""),"") - val future = Connector.connector.vend.makePaymentv210( - fromAccount, - toAccount, - transactionRequestId, - transactionRequestCommonBody, - 10, - "", - TransactionRequestType("SANDBOX_TAN"), - "", - callContext) - - val result = future.getContent - - result._1.map(_.value).toString should be (Full(inBound.data.id).toString) - - } - - if (PropsConnectorVersion =="mapped" || PropsConnectorVersion =="star"){ - ignore("ignore test createChallenge, if it is mapped connector", kafkaTest) {} - } else - scenario(s"test createChallenge method",kafkaTest) { - val inBound = Connector.connector.vend.messageDocs.filter(_.exampleInboundMessage.isInstanceOf[InboundCreateChallengeSept2018]).map(_.exampleInboundMessage).head.asInstanceOf[InboundCreateChallengeSept2018] - dispathResponse(inBound) - - val account = BankAccountSept2018(KafkaMappedConnector_vSept2018.inboundAccountSept2018Example) - val transactionRequestCommonBody = TransactionRequestBodyCommonJSON(AmountOfMoneyJsonV121("",""),"") - val future = Connector.connector.vend.createChallenge( - account.bankId, - account.accountId, - "", - TransactionRequestType("SANDBOX_TAN"), - "", - None, - callContext) - - val result = future.getContent - - result._1.toString should be (Full(inBound.data.answer).toString) - - } - - if (PropsConnectorVersion =="mapped" || PropsConnectorVersion =="star"){ - ignore("ignore test createCounterparty, if it is mapped connector", kafkaTest) {} - } else - scenario(s"test createCounterparty method",kafkaTest) { - val inBound = Connector.connector.vend.messageDocs.filter(_.exampleInboundMessage.isInstanceOf[InboundCreateCounterparty]).map(_.exampleInboundMessage).head.asInstanceOf[InboundCreateCounterparty] - val outBound = Connector.connector.vend.messageDocs.filter(_.exampleOutboundMessage.isInstanceOf[OutboundCreateCounterparty]).map(_.exampleOutboundMessage).head.asInstanceOf[OutboundCreateCounterparty] - dispathResponse(inBound) - - val account = BankAccountSept2018(KafkaMappedConnector_vSept2018.inboundAccountSept2018Example) - val transactionRequestCommonBody = TransactionRequestBodyCommonJSON(AmountOfMoneyJsonV121("",""),"") - val box = Connector.connector.vend.createCounterparty( - outBound.counterparty.name, - outBound.counterparty.description, - outBound.counterparty.currency, - outBound.counterparty.createdByUserId, - outBound.counterparty.thisBankId, - outBound.counterparty.thisAccountId, - outBound.counterparty.thisViewId, - outBound.counterparty.otherAccountRoutingScheme, - outBound.counterparty.otherAccountRoutingAddress, - outBound.counterparty.otherAccountSecondaryRoutingScheme, - outBound.counterparty.otherAccountSecondaryRoutingAddress, - outBound.counterparty.otherBankRoutingScheme, - outBound.counterparty.otherBankRoutingAddress, - outBound.counterparty.otherBranchRoutingScheme, - outBound.counterparty.otherBranchRoutingAddress, - outBound.counterparty.isBeneficiary, - outBound.counterparty.bespoke, - callContext) - - - box.map(_._1.counterpartyId) should be (Full(inBound.data.get.counterpartyId)) - box.map(_._1.createdByUserId) should be (Full(inBound.data.get.createdByUserId)) - - } - - if (PropsConnectorVersion =="mapped" || PropsConnectorVersion =="star"){ - ignore("ignore test getTransactionRequests210, if it is mapped connector", kafkaTest) {} - } else - scenario(s"test getTransactionRequests210 method",kafkaTest) { - - val inBound = Connector.connector.vend.messageDocs.filter(_.exampleInboundMessage.isInstanceOf[InboundGetTransactionRequests210]).map(_.exampleInboundMessage).head.asInstanceOf[InboundGetTransactionRequests210] - dispathResponse(inBound) - - val account = BankAccountSept2018(KafkaMappedConnector_vSept2018.inboundAccountSept2018Example) - val transactionRequestCommonBody = TransactionRequestBodyCommonJSON(AmountOfMoneyJsonV121("",""),"") - val box = Connector.connector.vend.getTransactionRequests210( - resourceUser1, - account, - callContext) - - box.map(_._1.head.body) should be (inBound.data.head.body) - - - } - - } -} diff --git a/obp-api/src/test/scala/code/setup/KafkaSetup.scala b/obp-api/src/test/scala/code/setup/KafkaSetup.scala deleted file mode 100644 index 4c881e9d6..000000000 --- a/obp-api/src/test/scala/code/setup/KafkaSetup.scala +++ /dev/null @@ -1,76 +0,0 @@ -package code.setup - -import code.actorsystem.ObpActorSystem -import code.api.util.CustomJsonFormats -import code.kafka._ -import code.util.Helper.MdcLoggable -import net.liftweb.json -import net.liftweb.json.Extraction -import net.manub.embeddedkafka.{EmbeddedKafka, EmbeddedKafkaConfig} -import org.apache.kafka.common.serialization.{StringDeserializer, StringSerializer} -import org.scalatest.{FeatureSpec, _} - -import com.openbankproject.commons.ExecutionContext.Implicits.global -import scala.concurrent.{Await, Future} -import scala.concurrent.duration.{Duration, _} - -trait KafkaSetup extends FeatureSpec with EmbeddedKafka with KafkaHelper - with GivenWhenThen with BeforeAndAfterAll - with Matchers with MdcLoggable { - - - - implicit val formats = CustomJsonFormats.formats - implicit val config = EmbeddedKafkaConfig(kafkaPort = 9092, zooKeeperPort = 2181) //TODO the port should read from test.default.props, but fail - implicit val stringSerializer = new StringSerializer - implicit val stringDeserializer = new StringDeserializer - - val requestMapResponseTopics:Map[String, String] = NorthSideConsumer.listOfTopics - .map(Topics.createTopicByClassName) - .map(pair => (pair.request, pair.response)) - .toMap - val requestTopics = requestMapResponseTopics.keySet - - override def beforeAll(): Unit = { - super.beforeAll() - - EmbeddedKafka.start() - - if(!OBPKafkaConsumer.primaryConsumer.started){ - val actorSystem = ObpActorSystem.startLocalActorSystem - KafkaHelperActors.startLocalKafkaHelperWorkers(actorSystem) - // Start North Side Consumer if it's not already started - OBPKafkaConsumer.primaryConsumer.start() - } - } - - override def afterAll(): Unit = { - super.afterAll() - OBPKafkaConsumer.primaryConsumer.complete() - EmbeddedKafka.stop() - } - - /** - * send an object to kafka as response - * - * @param inBound inBound object that will send to kafka as a response - * @tparam T Outbound type - */ - def dispathResponse(inBound: AnyRef): Unit = { - val inBoundStr = inBound match { - case str: String => str - case _ =>json.compactRender(Extraction.decompose(inBound)) - } - Future{ - val requestKeyValue = consumeNumberKeyedMessagesFromTopics(requestTopics, 1, true) - val (requestTopic, keyValueList) = requestKeyValue.find(_._2.nonEmpty).get - val (key, _) = keyValueList.head - val responseTopic = requestMapResponseTopics(requestTopic) - publishToKafka(responseTopic, key, inBoundStr) - } - } - - implicit class FutureExtract[T](future: Future[T]) { - def getContent: T = Await.result(future, 10 seconds) - } -} diff --git a/obp-api/src/test/scala/code/util/APIUtilTest.scala b/obp-api/src/test/scala/code/util/APIUtilTest.scala index 44b354495..450def8dc 100644 --- a/obp-api/src/test/scala/code/util/APIUtilTest.scala +++ b/obp-api/src/test/scala/code/util/APIUtilTest.scala @@ -698,12 +698,18 @@ class APIUtilTest extends FeatureSpec with Matchers with GivenWhenThen with Prop APIUtil.getObpFormatOperationId("xxx") should be ("xxx") } - feature("test APIUtil.basicUrlValidation method") { + feature("test APIUtil.basicUriAndQueryStringValidation method") { val testString1 = "https%3A%2F%2Fapisandbox.openbankproject.com%2Foauth%2Fauthorize%3Fnext%3D%2Fen%2Fusers%2Fmyuser%26oauth_token%3DWTOBT2YRCTMI1BCCF4XAIKRXPLLZDZPFAIL5K03Z%26oauth_verifier%3D45381" val testString2 = "http%3A%2F%2Flocalhost%3A8016%3Foauth_token%3DEBRZBMOPDXEUGGJP421FPFGK01IY2DGM5O3TLVSK%26oauth_verifier%3D63461" + val testString3 = "myapp://callback?oauth_token=%3DEBRZBMOPDXEUGGJP421FPFGK01IY2DGM5O3TLVSK%26oauth_verifier%3D63461" + val testString4 = "fb00000000:://callback?oauth_token=%3DEBRZBMOPDXEUGGJP421FPFGK01IY2DGM5O3TLVSK%26oauth_verifier%3D63461" + val testString5 = "http://127.0.0.1:8000/oauth/authorize?next=/en/metrics/api/&oauth_token=TN0124OCPRCL4KUJRF5LNLVMRNHTVZPJDBS2PNWU&oauth_verifier=10470" - APIUtil.basicUrlValidation(testString1) should be (true) - APIUtil.basicUrlValidation(testString2) should be (true) + APIUtil.basicUriAndQueryStringValidation(testString1) should be (true) + APIUtil.basicUriAndQueryStringValidation(testString2) should be (true) + APIUtil.basicUriAndQueryStringValidation(testString3) should be (true) + APIUtil.basicUriAndQueryStringValidation(testString4) should be (true) + APIUtil.basicUriAndQueryStringValidation(testString5) should be (true) } diff --git a/obp-api/src/test/scala/code/util/HelperTest.scala b/obp-api/src/test/scala/code/util/HelperTest.scala new file mode 100644 index 000000000..2e766e37d --- /dev/null +++ b/obp-api/src/test/scala/code/util/HelperTest.scala @@ -0,0 +1,66 @@ +/** + * Open Bank Project - API + * Copyright (C) 2011-2019, TESOBE GmbH. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * Email: contact@tesobe.com + * TESOBE GmbH. + * Osloer Strasse 16/17 + * Berlin 13359, Germany + * + * This product includes software developed at + * TESOBE (http://www.tesobe.com/) + * + */ + +package code.util + + +import code.api.util._ +import code.setup.PropsReset +import org.scalatest.{FeatureSpec, GivenWhenThen, Matchers} + + +class HelperTest extends FeatureSpec with Matchers with GivenWhenThen with PropsReset { + + feature("test APIUtil.getStaticPortionOfRedirectURL method") { + // The redirectURl is `http://localhost:8082/oauthcallback` + val testString1 = "http://localhost:8082/oauthcallback?oauth_token=G5AEA2U1WG404EGHTIGBHKRR4YJZAPPHWKOMNEEV&oauth_verifier=53018" + val testString2 = "http://localhost:8082?oauth_token=G5AEA2U1WG404EGHTIGBHKRR4YJZAPPHWKOMNEEV&oauth_verifier=53018" + val testString3 = "myapp://callback?oauth_token=%3DEBRZBMOPDXEUGGJP421FPFGK01IY2DGM5O3TLVSK%26oauth_verifier%3D63461" + val testString4 = "fb00000000:://callback?oauth_token=%3DEBRZBMOPDXEUGGJP421FPFGK01IY2DGM5O3TLVSK%26oauth_verifier%3D63461" + val testString5 = "http://127.0.0.1:8000/oauth/authorize?next=/en/metrics/api/&oauth_token=TN0124OCPRCL4KUJRF5LNLVMRNHTVZPJDBS2PNWU&oauth_verifier=10470" + + Helper.getStaticPortionOfRedirectURL(testString1).head should be("http://localhost:8082/oauthcallback") + Helper.getStaticPortionOfRedirectURL(testString2).head should be("http://localhost:8082") + Helper.getStaticPortionOfRedirectURL(testString3).head should be("myapp://callback") + Helper.getStaticPortionOfRedirectURL(testString4).head should be("fb00000000:://callback") + Helper.getStaticPortionOfRedirectURL(testString5).head should be("http://127.0.0.1:8000/oauth/authorize") + } + + feature("test APIUtil.getHostOnlyOfRedirectURL method") { + // The redirectURl is `http://localhost:8082/oauthcallback` + val testString1 = "http://localhost:8082/oauthcallback?oauth_token=G5AEA2U1WG404EGHTIGBHKRR4YJZAPPHWKOMNEEV&oauth_verifier=53018" + val testString2 = "http://localhost:8082/oauthcallback" + val testString3 = "http://localhost:8082?oauth_token=G5AEA2U1WG404EGHTIGBHKRR4YJZAPPHWKOMNEEV&oauth_verifier=53018" + val testString4 = "http://localhost:8082" + + Helper.getHostOnlyOfRedirectURL(testString1).head should be("http://localhost:8082") + Helper.getHostOnlyOfRedirectURL(testString2).head should be("http://localhost:8082") + Helper.getHostOnlyOfRedirectURL(testString3).head should be("http://localhost:8082") + Helper.getHostOnlyOfRedirectURL(testString4).head should be("http://localhost:8082") + } + +} \ No newline at end of file diff --git a/obp-api/src/test/scala/code/util/MappedClassNameTest.scala b/obp-api/src/test/scala/code/util/MappedClassNameTest.scala index 5cced4867..06dc3040f 100644 --- a/obp-api/src/test/scala/code/util/MappedClassNameTest.scala +++ b/obp-api/src/test/scala/code/util/MappedClassNameTest.scala @@ -115,6 +115,7 @@ class MappedClassNameTest extends FeatureSpec { "code.model.dataAccess.MappedBank", "code.UserRefreshes.MappedUserRefreshes", "code.DynamicEndpoint.DynamicEndpoint", + "code.regulatedentities.MappedRegulatedEntity", "code.CustomerDependants.MappedCustomerDependant") val newMappedTypes = ClassScanUtils.findTypes{ info => diff --git a/obp-commons/src/main/scala/com/openbankproject/commons/model/CommonModelTrait.scala b/obp-commons/src/main/scala/com/openbankproject/commons/model/CommonModelTrait.scala index cd0a28455..7707a607b 100644 --- a/obp-commons/src/main/scala/com/openbankproject/commons/model/CommonModelTrait.scala +++ b/obp-commons/src/main/scala/com/openbankproject/commons/model/CommonModelTrait.scala @@ -96,6 +96,22 @@ trait AccountApplication { def status: String } + +trait RegulatedEntityTrait { + def entityId: String + def certificateAuthorityCaOwnerId: String + def entityName: String + def entityCode: String + def entityCertificatePublicKey: String + def entityType: String + def entityAddress: String + def entityTownCity: String + def entityPostCode: String + def entityCountry: String + def entityWebSite: String + def services: String +} + trait UserAttributeTrait { def userAttributeId: String def userId: String diff --git a/pom.xml b/pom.xml index 2b8287314..b8f7e7923 100644 --- a/pom.xml +++ b/pom.xml @@ -18,7 +18,6 @@ 1.8.2 3.5.0 9.4.50.v20221201 - 2.17.1 2016.11-RC6-SNAPSHOT UTF-8