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 54bd78dd9..49c1f1947 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 @@ -5325,6 +5325,21 @@ object SwaggerDefinitionsJSON { attributes = Some(List(atmAttributeResponseJsonV510)) ) + + val userAttributeJsonV510 = UserAttributeJsonV510( + name = userAttributeNameExample.value, + `type` = userAttributeTypeExample.value, + value = userAttributeValueExample.value + ) + + val userAttributeResponseJsonV510 = UserAttributeResponseJsonV510( + user_attribute_id = userAttributeIdExample.value, + name = userAttributeNameExample.value, + `type` = userAttributeTypeExample.value, + value = userAttributeValueExample.value, + is_personal = userAttributeIsPersonalExample.value.toBoolean, + insert_date = new Date() + ) val postAtmJsonV510 = PostAtmJsonV510( id = Some(atmIdExample.value), diff --git a/obp-api/src/main/scala/code/api/berlin/group/v1_3/JSONFactory_BERLIN_GROUP_1_3.scala b/obp-api/src/main/scala/code/api/berlin/group/v1_3/JSONFactory_BERLIN_GROUP_1_3.scala index d8686484c..9c660b5bf 100644 --- a/obp-api/src/main/scala/code/api/berlin/group/v1_3/JSONFactory_BERLIN_GROUP_1_3.scala +++ b/obp-api/src/main/scala/code/api/berlin/group/v1_3/JSONFactory_BERLIN_GROUP_1_3.scala @@ -416,7 +416,7 @@ object JSONFactory_BERLIN_GROUP_1_3 extends CustomJsonFormats { } private def extractAccountData(scheme: String, address: String): (String, String, String, String, String) = { - val (iban: String, bban: String, pan: String, maskedPan: String, currency: String) = Connector.connector.vend.getBankAccountByRouting( + val (iban: String, bban: String, pan: String, maskedPan: String, currency: String) = Connector.connector.vend.getBankAccountByRoutingLegacy( None, scheme, address, 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 f9fabd6ad..439ec4c8a 100644 --- a/obp-api/src/main/scala/code/api/util/ApiRole.scala +++ b/obp-api/src/main/scala/code/api/util/ApiRole.scala @@ -403,6 +403,15 @@ object ApiRole { case class CanGetUsersWithAttributes (requiresBankId: Boolean = false) extends ApiRole lazy val canGetUsersWithAttributes = CanGetUsersWithAttributes() + + case class CanCreateNonPersonalUserAttribute (requiresBankId: Boolean = false) extends ApiRole + lazy val canCreateNonPersonalUserAttribute = CanCreateNonPersonalUserAttribute() + + case class CanGetNonPersonalUserAttributes (requiresBankId: Boolean = false) extends ApiRole + lazy val canGetNonPersonalUserAttributes = CanGetNonPersonalUserAttributes() + + case class CanDeleteNonPersonalUserAttribute (requiresBankId: Boolean = false) extends ApiRole + lazy val canDeleteNonPersonalUserAttribute = CanDeleteNonPersonalUserAttribute() case class CanReadUserLockedStatus(requiresBankId: Boolean = false) extends ApiRole lazy val canReadUserLockedStatus = CanReadUserLockedStatus() 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 94939194e..a00dcd329 100644 --- a/obp-api/src/main/scala/code/api/util/ErrorMessages.scala +++ b/obp-api/src/main/scala/code/api/util/ErrorMessages.scala @@ -553,9 +553,9 @@ object ErrorMessages { val InvalidChargePolicy = "OBP-40013: Invalid Charge Policy. Please specify a valid value for Charge_Policy: SHARED, SENDER or RECEIVER. " val AllowedAttemptsUsedUp = "OBP-40014: Sorry, you've used up your allowed attempts. " val InvalidChallengeType = "OBP-40015: Invalid Challenge Type. Please specify a valid value for CHALLENGE_TYPE, when you create the transaction request." - val InvalidChallengeAnswer = "OBP-40016: Invalid Challenge Answer. Please specify a valid value for answer in Json body. " + - "The challenge answer may be expired." + - "Or you've used up your allowed attempts (3 times)." + + val InvalidChallengeAnswer = s"OBP-40016: Invalid Challenge Answer. Please specify a valid value for answer in Json body. " + + s"The challenge answer may be expired." + + s"Or you've used up your allowed attempts." + "Or if connector = mapped and transactionRequestType_OTP_INSTRUCTION_TRANSPORT = DUMMY and suggested_default_sca_method=DUMMY, the answer must be `123`. " + "Or if connector = others, the challenge answer can be got by phone message or other security ways." val InvalidPhoneNumber = "OBP-40017: Invalid Phone Number. Please specify a valid value for PHONE_NUMBER. Eg:+9722398746 " diff --git a/obp-api/src/main/scala/code/api/util/ExampleValue.scala b/obp-api/src/main/scala/code/api/util/ExampleValue.scala index 519718ac7..026816edb 100644 --- a/obp-api/src/main/scala/code/api/util/ExampleValue.scala +++ b/obp-api/src/main/scala/code/api/util/ExampleValue.scala @@ -149,10 +149,13 @@ object ExampleValue { lazy val customerAttributeValueExample = ConnectorField("123456789", s"Customer attribute value.") glossaryItems += makeGlossaryItem("Customer.attributeValue", customerAttributeValueExample) - lazy val userAttributeValueExample = ConnectorField("90", s"Uset attribute value.") + lazy val userAttributeValueExample = ConnectorField("90", s"User attribute value.") glossaryItems += makeGlossaryItem("User.attributeValue", userAttributeValueExample) - lazy val labelExample = ConnectorField("My Account", s"A lable that describes the Account") + lazy val userAttributeIsPersonalExample = ConnectorField("false", s"User attribute is personal value.") + glossaryItems += makeGlossaryItem("User.isPersonal", userAttributeIsPersonalExample) + + lazy val labelExample = ConnectorField("My Account", s"A label that describes the Account") lazy val legalNameExample = ConnectorField("Eveline Tripman", s"The legal name of the Customer.") glossaryItems += makeGlossaryItem("Customer.legalName", legalNameExample) 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 17920b96a..f41002514 100644 --- a/obp-api/src/main/scala/code/api/util/NewStyle.scala +++ b/obp-api/src/main/scala/code/api/util/NewStyle.scala @@ -458,8 +458,8 @@ object NewStyle extends MdcLoggable{ } def getBankAccountByRouting(bankId: Option[BankId], scheme: String, address: String, callContext: Option[CallContext]) : OBPReturnType[BankAccount] = { - Future(Connector.connector.vend.getBankAccountByRouting(bankId: Option[BankId], scheme: String, address : String, callContext: Option[CallContext])) map { i => - unboxFullOrFail(i, callContext,s"$BankAccountNotFoundByAccountRouting Current scheme is $scheme, current address is $address, current bankId is $bankId", 404 ) + Connector.connector.vend.getBankAccountByRouting(bankId: Option[BankId], scheme: String, address : String, callContext: Option[CallContext]) map { i => + (unboxFullOrFail(i._1, callContext,s"$BankAccountNotFoundByAccountRouting Current scheme is $scheme, current address is $address, current bankId is $bankId", 404 ), i._2) } } @@ -1356,7 +1356,11 @@ object NewStyle extends MdcLoggable{ def validateChallengeAnswer(challengeId: String, hashOfSuppliedAnswer: String, callContext: Option[CallContext]): OBPReturnType[Boolean] = Connector.connector.vend.validateChallengeAnswer(challengeId: String, hashOfSuppliedAnswer: String, callContext: Option[CallContext]) map { i => - (unboxFullOrFail(i._1, callContext, s"$InvalidChallengeAnswer "), i._2) + (unboxFullOrFail(i._1, callContext, s"${ + InvalidChallengeAnswer + .replace("answer may be expired.", s"answer may be expired (${transactionRequestChallengeTtl} seconds).") + .replace("up your allowed attempts.", s"up your allowed attempts (${allowedAnswerTransactionRequestChallengeAttempts} times).") + }"), i._2) } def allChallengesSuccessfullyAnswered( @@ -1399,7 +1403,11 @@ object NewStyle extends MdcLoggable{ hashOfSuppliedAnswer: String, callContext: Option[CallContext] ) map { i => - (unboxFullOrFail(i._1, callContext, s"$InvalidChallengeAnswer "), i._2) + (unboxFullOrFail(i._1, callContext, s"${ + InvalidChallengeAnswer + .replace("answer may be expired.", s"answer may be expired (${transactionRequestChallengeTtl} seconds).") + .replace("up your allowed attempts.", s"up your allowed attempts (${allowedAnswerTransactionRequestChallengeAttempts} times).") + }"), i._2) } } } @@ -1888,6 +1896,23 @@ object NewStyle extends MdcLoggable{ } } + def getPersonalUserAttributes(userId: String, callContext: Option[CallContext]): OBPReturnType[List[UserAttribute]] = { + Connector.connector.vend.getPersonalUserAttributes( + userId: String, callContext: Option[CallContext] + ) map { + i => (connectorEmptyResponse(i._1, callContext), i._2) + } + } + + + def getNonPersonalUserAttributes(userId: String, callContext: Option[CallContext]): OBPReturnType[List[UserAttribute]] = { + Connector.connector.vend.getNonPersonalUserAttributes( + userId: String, callContext: Option[CallContext] + ) map { + i => (connectorEmptyResponse(i._1, callContext), i._2) + } + } + def getUserAttributesByUsers(userIds: List[String], callContext: Option[CallContext]): OBPReturnType[List[UserAttribute]] = { Connector.connector.vend.getUserAttributesByUsers( userIds, callContext: Option[CallContext] @@ -1901,6 +1926,7 @@ object NewStyle extends MdcLoggable{ name: String, attributeType: UserAttributeType.Value, value: String, + isPersonal: Boolean, callContext: Option[CallContext] ): OBPReturnType[UserAttribute] = { Connector.connector.vend.createOrUpdateUserAttribute( @@ -1909,6 +1935,7 @@ object NewStyle extends MdcLoggable{ name: String, attributeType: UserAttributeType.Value, value: String, + isPersonal: Boolean, callContext: Option[CallContext] ) map { i => (connectorEmptyResponse(i._1, callContext), i._2) diff --git a/obp-api/src/main/scala/code/api/util/migration/Migration.scala b/obp-api/src/main/scala/code/api/util/migration/Migration.scala index 1b7efd691..1038cd97c 100644 --- a/obp-api/src/main/scala/code/api/util/migration/Migration.scala +++ b/obp-api/src/main/scala/code/api/util/migration/Migration.scala @@ -95,6 +95,7 @@ object Migration extends MdcLoggable { dropConsentAuthContextDropIndex() alterMappedExpectedChallengeAnswerChallengeTypeLength() alterTransactionRequestChallengeChallengeTypeLength() + alterUserAttributeNameLength() alterMappedCustomerAttribute(startedBeforeSchemifier) dropMappedBadLoginAttemptIndex() } @@ -421,6 +422,13 @@ object Migration extends MdcLoggable { MigrationOfTransactionRequestChallengeChallengeTypeLength.alterColumnChallengeChallengeTypeLength(name) } } + + private def alterUserAttributeNameLength(): Boolean = { + val name = nameOf(alterUserAttributeNameLength) + runOnce(name) { + MigrationOfUserAttributeNameFieldLength.alterNameLength(name) + } + } private def alterMappedCustomerAttribute(startedBeforeSchemifier: Boolean): Boolean = { if(startedBeforeSchemifier == true) { logger.warn(s"Migration.database.alterMappedCustomerAttribute(true) cannot be run before Schemifier.") diff --git a/obp-api/src/main/scala/code/api/util/migration/MigrationOfUserAttributeNameFieldLength.scala b/obp-api/src/main/scala/code/api/util/migration/MigrationOfUserAttributeNameFieldLength.scala new file mode 100644 index 000000000..7a8f0e3bc --- /dev/null +++ b/obp-api/src/main/scala/code/api/util/migration/MigrationOfUserAttributeNameFieldLength.scala @@ -0,0 +1,62 @@ +package code.api.util.migration + +import code.api.util.APIUtil +import code.api.util.migration.Migration.{DbFunction, saveLog} +import code.users.UserAttribute +import net.liftweb.common.Full +import net.liftweb.mapper.{DB, Schemifier} +import net.liftweb.util.DefaultConnectionIdentifier + +import java.time.format.DateTimeFormatter +import java.time.{ZoneId, ZonedDateTime} + +object MigrationOfUserAttributeNameFieldLength { + + val oneDayAgo = ZonedDateTime.now(ZoneId.of("UTC")).minusDays(1) + val oneYearInFuture = ZonedDateTime.now(ZoneId.of("UTC")).plusYears(1) + val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm'Z'") + + def alterNameLength(name: String): Boolean = { + DbFunction.tableExists(UserAttribute, (DB.use(DefaultConnectionIdentifier){ conn => conn})) match { + case true => + val startDate = System.currentTimeMillis() + val commitId: String = APIUtil.gitCommit + var isSuccessful = false + + val executedSql = + DbFunction.maybeWrite(true, Schemifier.infoF _, DB.use(DefaultConnectionIdentifier){ conn => conn}) { + APIUtil.getPropsValue("db.driver") match { + case Full(value) if value.contains("com.microsoft.sqlserver.jdbc.SQLServerDriver") => + () => + """ + |ALTER TABLE UserAttribute ALTER COLUMN name varchar(255); + |""".stripMargin + case _ => + () => + """ + |ALTER TABLE UserAttribute ALTER COLUMN name type varchar(255); + |""".stripMargin + } + } + + val endDate = System.currentTimeMillis() + val comment: String = + s"""Executed SQL: + |$executedSql + |""".stripMargin + isSuccessful = true + saveLog(name, commitId, isSuccessful, startDate, endDate, comment) + isSuccessful + + case false => + val startDate = System.currentTimeMillis() + val commitId: String = APIUtil.gitCommit + val isSuccessful = false + val endDate = System.currentTimeMillis() + val comment: String = + s"""${UserAttribute._dbTableNameLC} table does not exist""".stripMargin + saveLog(name, commitId, isSuccessful, startDate, endDate, comment) + isSuccessful + } + } +} \ No newline at end of file diff --git a/obp-api/src/main/scala/code/api/v2_1_0/APIMethods210.scala b/obp-api/src/main/scala/code/api/v2_1_0/APIMethods210.scala index e5a60141a..ab25f70d9 100644 --- a/obp-api/src/main/scala/code/api/v2_1_0/APIMethods210.scala +++ b/obp-api/src/main/scala/code/api/v2_1_0/APIMethods210.scala @@ -644,7 +644,10 @@ trait APIMethods210 { (isChallengeAnswerValidated, callContext) <- NewStyle.function.validateChallengeAnswer(challengeAnswerJson.id, challengeAnswerJson.answer, callContext) - _ <- Helper.booleanToFuture(s"${InvalidChallengeAnswer} ", cc=callContext) { + _ <- Helper.booleanToFuture(s"${InvalidChallengeAnswer + .replace("answer may be expired.", s"answer may be expired (${transactionRequestChallengeTtl} seconds).") + .replace("up your allowed attempts.", s"up your allowed attempts (${allowedAnswerTransactionRequestChallengeAttempts} times).") + } ", cc = callContext) { (isChallengeAnswerValidated == true) } 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 54fec1767..962045194 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 @@ -1630,7 +1630,10 @@ trait APIMethods400 { (challengeAnswerIsValidated, callContext) <- NewStyle.function.validateChallengeAnswer(challengeAnswerJson.id, challengeAnswerJson.answer, callContext) - _ <- Helper.booleanToFuture(s"${InvalidChallengeAnswer.replace("answer may be expired.",s"answer may be expired, current expiration time is ${transactionRequestChallengeTtl} seconds .")} ", cc=callContext) { + _ <- Helper.booleanToFuture(s"${InvalidChallengeAnswer + .replace("answer may be expired.",s"answer may be expired (${transactionRequestChallengeTtl} seconds).") + .replace("up your allowed attempts.",s"up your allowed attempts (${allowedAnswerTransactionRequestChallengeAttempts} times).") + }", cc=callContext) { challengeAnswerIsValidated } @@ -8738,13 +8741,13 @@ trait APIMethods400 { } staticResourceDocs += ResourceDoc( - getCurrentUserAttributes, + getMyPersonalUserAttributes, implementedInApiVersion, - nameOf(getCurrentUserAttributes), + nameOf(getMyPersonalUserAttributes), "GET", "/my/user/attributes", - "Get User Attributes for current user", - s"""Get User Attributes for current user. + "Get My Personal User Attributes", + s"""Get My Personal User Attributes. | |${authenticationRequiredMessage(true)} |""".stripMargin, @@ -8757,11 +8760,11 @@ trait APIMethods400 { List(apiTagUser) ) - lazy val getCurrentUserAttributes: OBPEndpoint = { + lazy val getMyPersonalUserAttributes: OBPEndpoint = { case "my" :: "user" :: "attributes" :: Nil JsonGet _ => { cc => for { - (attributes, callContext) <- NewStyle.function.getUserAttributes(cc.userId, cc.callContext) + (attributes, callContext) <- NewStyle.function.getPersonalUserAttributes(cc.userId, cc.callContext) } yield { (JSONFactory400.createUserAttributesJson(attributes), HttpCode.`200`(callContext)) } @@ -8775,7 +8778,7 @@ trait APIMethods400 { nameOf(getUserWithAttributes), "GET", "/users/USER_ID/attributes", - "Get User Attributes for the user", + "Get User with Attributes by USER_ID", s"""Get User Attributes for the user defined via USER_ID. | |${authenticationRequiredMessage(true)} @@ -8795,7 +8798,7 @@ trait APIMethods400 { cc => for { (user, callContext) <- NewStyle.function.getUserByUserId(userId, cc.callContext) - (attributes, callContext) <- NewStyle.function.getUserAttributes(userId, callContext) + (attributes, callContext) <- NewStyle.function.getUserAttributes(user.userId, callContext) } yield { (JSONFactory400.createUserWithAttributesJson(user, attributes), HttpCode.`200`(callContext)) } @@ -8804,15 +8807,15 @@ trait APIMethods400 { staticResourceDocs += ResourceDoc( - createCurrentUserAttribute, + createMyPersonalUserAttribute, implementedInApiVersion, - nameOf(createCurrentUserAttribute), + nameOf(createMyPersonalUserAttribute), "POST", "/my/user/attributes", - "Create User Attribute for current user", - s""" Create User Attribute for current user + "Create My Personal User Attribute", + s""" Create My Personal User Attribute | - |The type field must be one of "STRING", "INTEGER", "DOUBLE" or DATE_WITH_DAY" + |The `type` field must be one of "STRING", "INTEGER", "DOUBLE" or DATE_WITH_DAY" | |${authenticationRequiredMessage(true)} | @@ -8827,18 +8830,17 @@ trait APIMethods400 { List(apiTagUser), Some(List())) - lazy val createCurrentUserAttribute : OBPEndpoint = { + lazy val createMyPersonalUserAttribute : OBPEndpoint = { case "my" :: "user" :: "attributes" :: Nil JsonPost json -> _=> { cc => - val failMsg = s"$InvalidJsonFormat The Json body should be the $TransactionAttributeJsonV400 " + val failMsg = s"$InvalidJsonFormat The Json body should be the $UserAttributeJsonV400 " for { - (attributes, callContext) <- NewStyle.function.getUserAttributes(cc.userId, cc.callContext) - postedData <- NewStyle.function.tryons(failMsg, 400, callContext) { - json.extract[TransactionAttributeJsonV400] + postedData <- NewStyle.function.tryons(failMsg, 400, cc.callContext) { + json.extract[UserAttributeJsonV400] } failMsg = s"$InvalidJsonFormat The `Type` field can only accept the following field: " + - s"${TransactionAttributeType.DOUBLE}(12.1234), ${TransactionAttributeType.STRING}(TAX_NUMBER), ${TransactionAttributeType.INTEGER} (123)and ${TransactionAttributeType.DATE_WITH_DAY}(2012-04-23)" - userAttributeType <- NewStyle.function.tryons(failMsg, 400, callContext) { + s"${TransactionAttributeType.DOUBLE}(12.1234), ${UserAttributeType.STRING}(TAX_NUMBER), ${UserAttributeType.INTEGER} (123)and ${UserAttributeType.DATE_WITH_DAY}(2012-04-23)" + userAttributeType <- NewStyle.function.tryons(failMsg, 400, cc.callContext) { UserAttributeType.withName(postedData.`type`) } (userAttribute, callContext) <- NewStyle.function.createOrUpdateUserAttribute( @@ -8847,7 +8849,8 @@ trait APIMethods400 { postedData.name, userAttributeType, postedData.value, - callContext + true, + cc.callContext ) } yield { (JSONFactory400.createUserAttributeJson(userAttribute), HttpCode.`201`(callContext)) @@ -8856,13 +8859,13 @@ trait APIMethods400 { } staticResourceDocs += ResourceDoc( - updateCurrentUserAttribute, + updateMyPersonalUserAttribute, implementedInApiVersion, - nameOf(updateCurrentUserAttribute), + nameOf(updateMyPersonalUserAttribute), "PUT", "/my/user/attributes/USER_ATTRIBUTE_ID", - "Update User Attribute for current user", - s"""Update User Attribute for current user by USER_ATTRIBUTE_ID + "Update My Personal User Attribute", + s"""Update My Personal User Attribute for current user by USER_ATTRIBUTE_ID | |The type field must be one of "STRING", "INTEGER", "DOUBLE" or DATE_WITH_DAY" | @@ -8879,21 +8882,20 @@ trait APIMethods400 { List(apiTagUser), Some(List())) - lazy val updateCurrentUserAttribute : OBPEndpoint = { + lazy val updateMyPersonalUserAttribute : OBPEndpoint = { case "my" :: "user" :: "attributes" :: userAttributeId :: Nil JsonPut json -> _=> { cc => - val failMsg = s"$InvalidJsonFormat The Json body should be the $TransactionAttributeJsonV400 " for { - (attributes, callContext) <- NewStyle.function.getUserAttributes(cc.userId, cc.callContext) + (attributes, callContext) <- NewStyle.function.getPersonalUserAttributes(cc.userId, cc.callContext) failMsg = s"$UserAttributeNotFound" _ <- NewStyle.function.tryons(failMsg, 400, callContext) { attributes.exists(_.userAttributeId == userAttributeId) } - postedData <- NewStyle.function.tryons(failMsg, 400, callContext) { - json.extract[TransactionAttributeJsonV400] + postedData <- NewStyle.function.tryons(s"$InvalidJsonFormat The Json body should be the $UserAttributeJsonV400 ", 400, callContext) { + json.extract[UserAttributeJsonV400] } failMsg = s"$InvalidJsonFormat The `Type` field can only accept the following field: " + - s"${TransactionAttributeType.DOUBLE}(12.1234), ${TransactionAttributeType.STRING}(TAX_NUMBER), ${TransactionAttributeType.INTEGER} (123)and ${TransactionAttributeType.DATE_WITH_DAY}(2012-04-23)" + s"${UserAttributeType.DOUBLE}(12.1234), ${UserAttributeType.STRING}(TAX_NUMBER), ${UserAttributeType.INTEGER} (123)and ${UserAttributeType.DATE_WITH_DAY}(2012-04-23)" userAttributeType <- NewStyle.function.tryons(failMsg, 400, callContext) { UserAttributeType.withName(postedData.`type`) } @@ -8903,6 +8905,7 @@ trait APIMethods400 { postedData.name, userAttributeType, postedData.value, + true, callContext ) } yield { 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 6b65053b7..ff8aea4b5 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 @@ -15,8 +15,9 @@ 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.v4_0_0.{JSONFactory400, PostApiCollectionJson400, UserAttributeJsonV400} import code.atmattribute.AtmAttribute +import code.bankconnectors.Connector import code.consent.Consents import code.loginattempts.LoginAttempt import code.metrics.APIMetrics @@ -29,7 +30,7 @@ import code.views.system.{AccountAccess, ViewDefinition} import com.github.dwickern.macros.NameOf.nameOf import com.openbankproject.commons.ExecutionContext.Implicits.global import com.openbankproject.commons.model.{AtmId, AtmT, BankId} -import com.openbankproject.commons.model.enums.AtmAttributeType +import com.openbankproject.commons.model.enums.{AtmAttributeType, UserAttributeType} import com.openbankproject.commons.util.{ApiVersion, ScannedApiVersion} import net.liftweb.common.Full import net.liftweb.http.S @@ -152,7 +153,139 @@ trait APIMethods510 { (JSONFactory400.createApiCollectionsJsonV400(apiCollections), HttpCode.`200`(callContext)) } } - } + } + staticResourceDocs += ResourceDoc( + createNonPersonalUserAttribute, + implementedInApiVersion, + nameOf(createNonPersonalUserAttribute), + "POST", + "/users/USER_ID/non-personal/attributes", + "Create Non Personal User Attribute", + s""" Create Non Personal User Attribute + | + |The type field must be one of "STRING", "INTEGER", "DOUBLE" or DATE_WITH_DAY" + | + |${authenticationRequiredMessage(true)} + | + |""", + userAttributeJsonV510, + userAttributeResponseJsonV510, + List( + $UserNotLoggedIn, + UserHasMissingRoles, + InvalidJsonFormat, + UnknownError + ), + List(apiTagUser, apiTagNewStyle), + Some(List(canCreateNonPersonalUserAttribute)) + ) + + lazy val createNonPersonalUserAttribute: OBPEndpoint = { + case "users" :: userId ::"non-personal":: "attributes" :: Nil JsonPost json -> _ => { + cc => + val failMsg = s"$InvalidJsonFormat The Json body should be the $UserAttributeJsonV510 " + for { + (user, callContext) <- NewStyle.function.getUserByUserId(userId, cc.callContext) + postedData <- NewStyle.function.tryons(failMsg, 400, callContext) { + json.extract[UserAttributeJsonV510] + } + failMsg = s"$InvalidJsonFormat The `Type` field can only accept the following field: " + + s"${UserAttributeType.DOUBLE}(12.1234), ${UserAttributeType.STRING}(TAX_NUMBER), ${UserAttributeType.INTEGER} (123)and ${UserAttributeType.DATE_WITH_DAY}(2012-04-23)" + userAttributeType <- NewStyle.function.tryons(failMsg, 400, callContext) { + UserAttributeType.withName(postedData.`type`) + } + (userAttribute, callContext) <- NewStyle.function.createOrUpdateUserAttribute( + user.userId, + None, + postedData.name, + userAttributeType, + postedData.value, + false, + callContext + ) + } yield { + (JSONFactory510.createUserAttributeJson(userAttribute), HttpCode.`201`(callContext)) + } + } + } + + resourceDocs += ResourceDoc( + deleteNonPersonalUserAttribute, + implementedInApiVersion, + nameOf(deleteNonPersonalUserAttribute), + "DELETE", + "/users/USER_ID/non-personal/attributes/USER_ATTRIBUTE_ID", + "Delete Non Personal User Attribute", + s"""Delete the Non Personal User Attribute specified by ENTITLEMENT_REQUEST_ID for a user specified by USER_ID + | + |${authenticationRequiredMessage(true)} + |""".stripMargin, + EmptyBody, + EmptyBody, + List( + UserNotLoggedIn, + UserHasMissingRoles, + InvalidConnectorResponse, + UnknownError + ), + List(apiTagUser, apiTagNewStyle), + Some(List(canDeleteNonPersonalUserAttribute))) + + lazy val deleteNonPersonalUserAttribute: OBPEndpoint = { + case "users" :: userId :: "non-personal" :: "attributes" :: userAttributeId :: Nil JsonDelete _ => { + cc => + for { + (_, callContext) <- authenticatedAccess(cc) + (_, callContext) <- NewStyle.function.getUserByUserId(userId, callContext) + (deleted,callContext) <- Connector.connector.vend.deleteUserAttribute( + userAttributeId: String, + callContext: Option[CallContext] + ) map { + i => (connectorEmptyResponse (i._1, callContext), i._2) + } + } yield { + (Full(deleted), HttpCode.`204`(callContext)) + } + } + } + + resourceDocs += ResourceDoc( + getNonPersonalUserAttributes, + implementedInApiVersion, + nameOf(getNonPersonalUserAttributes), + "GET", + "/users/USER_ID/non-personal/attributes", + "Get Non Personal User Attributes", + s"""Get Non Personal User Attribute for a user specified by USER_ID + | + |${authenticationRequiredMessage(true)} + |""".stripMargin, + EmptyBody, + EmptyBody, + List( + UserNotLoggedIn, + UserHasMissingRoles, + InvalidConnectorResponse, + UnknownError + ), + List(apiTagUser, apiTagNewStyle), + Some(List(canGetNonPersonalUserAttributes))) + + lazy val getNonPersonalUserAttributes: OBPEndpoint = { + case "users" :: userId :: "non-personal" ::"attributes" :: Nil JsonGet _ => { + cc => + for { + (_, callContext) <- authenticatedAccess(cc) + (user, callContext) <- NewStyle.function.getUserByUserId(userId, callContext) + (userAttributes,callContext) <- NewStyle.function.getNonPersonalUserAttributes( + user.userId, + callContext, + ) + } yield { + (JSONFactory510.createUserAttributesJson(userAttributes), HttpCode.`200`(callContext)) + } + } + } staticResourceDocs += ResourceDoc( 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 c34dd5afb..f92161f21 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 @@ -35,10 +35,12 @@ import code.api.v3_0_0.{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.views.system.{AccountAccess, ViewDefinition} import com.openbankproject.commons.model.{Address, AtmId, AtmT, BankId, Location, Meta} import com.openbankproject.commons.util.{ApiVersion, ScannedApiVersion} +import java.util.Date import scala.collection.immutable.List import scala.util.Try @@ -194,6 +196,26 @@ case class AtmAttributeResponseJsonV510( ) case class AtmAttributesResponseJsonV510(atm_attributes: List[AtmAttributeResponseJsonV510]) +case class UserAttributeResponseJsonV510( + user_attribute_id: String, + name: String, + `type`: String, + value: String, + is_personal: Boolean, + insert_date: Date +) + +case class UserAttributeJsonV510( + name: String, + `type`: String, + value: String +) + +case class UserAttributesResponseJsonV510( + user_attributes: List[UserAttributeResponseJsonV510] +) + + object JSONFactory510 { @@ -450,6 +472,19 @@ object JSONFactory510 { def createAtmAttributesJson(atmAttributes: List[AtmAttribute]): AtmAttributesResponseJsonV510 = AtmAttributesResponseJsonV510(atmAttributes.map(createAtmAttributeJson)) - + def createUserAttributeJson(userAttribute: UserAttribute): UserAttributeResponseJsonV510 = { + UserAttributeResponseJsonV510( + user_attribute_id = userAttribute.userAttributeId, + name = userAttribute.name, + `type` = userAttribute.attributeType.toString, + value = userAttribute.value, + insert_date = userAttribute.insertDate, + is_personal = userAttribute.isPersonal + ) + } + + def createUserAttributesJson(userAttribute: List[UserAttribute]): UserAttributesResponseJsonV510 = { + UserAttributesResponseJsonV510(userAttribute.map(createUserAttributeJson)) + } } diff --git a/obp-api/src/main/scala/code/bankconnectors/Connector.scala b/obp-api/src/main/scala/code/bankconnectors/Connector.scala index 0d9e85a3c..d38abc3d0 100644 --- a/obp-api/src/main/scala/code/bankconnectors/Connector.scala +++ b/obp-api/src/main/scala/code/bankconnectors/Connector.scala @@ -523,7 +523,8 @@ trait Connector extends MdcLoggable { def getBankAccountByAccountId(accountId : AccountId, callContext: Option[CallContext]) : OBPReturnType[Box[BankAccount]]= Future{(Failure(setUnimplementedError),callContext)} def getBankAccountByIban(iban : String, callContext: Option[CallContext]) : OBPReturnType[Box[BankAccount]]= Future{(Failure(setUnimplementedError),callContext)} - def getBankAccountByRouting(bankId: Option[BankId], scheme : String, address : String, callContext: Option[CallContext]) : Box[(BankAccount, Option[CallContext])]= Failure(setUnimplementedError) + def getBankAccountByRoutingLegacy(bankId: Option[BankId], scheme : String, address : String, callContext: Option[CallContext]) : Box[(BankAccount, Option[CallContext])]= Failure(setUnimplementedError) + def getBankAccountByRouting(bankId: Option[BankId], scheme : String, address : String, callContext: Option[CallContext]) : OBPReturnType[Box[BankAccount]]= Future{(Failure(setUnimplementedError), callContext)} def getAccountRoutingsByScheme(bankId: Option[BankId], scheme : String, callContext: Option[CallContext]): OBPReturnType[Box[List[BankAccountRouting]]] = Future{(Failure(setUnimplementedError),callContext)} def getAccountRouting(bankId: Option[BankId], scheme : String, address : String, callContext: Option[CallContext]) : Box[(BankAccountRouting, Option[CallContext])]= Failure(setUnimplementedError) @@ -2260,6 +2261,12 @@ trait Connector extends MdcLoggable { def getUserAttributes(userId: String, callContext: Option[CallContext]): OBPReturnType[Box[List[UserAttribute]]] = Future{(Failure(setUnimplementedError), callContext)} + def getPersonalUserAttributes(userId: String, callContext: Option[CallContext]): OBPReturnType[Box[List[UserAttribute]]] = + Future{(Failure(setUnimplementedError), callContext)} + + def getNonPersonalUserAttributes(userId: String, callContext: Option[CallContext]): OBPReturnType[Box[List[UserAttribute]]] = + Future{(Failure(setUnimplementedError), callContext)} + def getUserAttributesByUsers(userIds: List[String], callContext: Option[CallContext]): OBPReturnType[Box[List[UserAttribute]]] = Future{(Failure(setUnimplementedError), callContext)} @@ -2269,9 +2276,15 @@ trait Connector extends MdcLoggable { name: String, attributeType: UserAttributeType.Value, value: String, + isPersonal: Boolean, callContext: Option[CallContext] ): OBPReturnType[Box[UserAttribute]] = Future{(Failure(setUnimplementedError), callContext)} + def deleteUserAttribute( + userAttributeId: String, + callContext: Option[CallContext] + ): OBPReturnType[Box[Boolean]] = Future{(Failure(setUnimplementedError), callContext)} + def createOrUpdateTransactionAttribute( bankId: BankId, transactionId: TransactionId, diff --git a/obp-api/src/main/scala/code/bankconnectors/LocalMappedConnector.scala b/obp-api/src/main/scala/code/bankconnectors/LocalMappedConnector.scala index 537d5632a..9e9131fd5 100644 --- a/obp-api/src/main/scala/code/bankconnectors/LocalMappedConnector.scala +++ b/obp-api/src/main/scala/code/bankconnectors/LocalMappedConnector.scala @@ -219,6 +219,7 @@ object LocalMappedConnector extends Connector with MdcLoggable { val userAttributeName = s"TRANSACTION_REQUESTS_PAYMENT_LIMIT_${currency}_" + transactionRequestType.toUpperCase val userAttributes = UserAttribute.findAll( By(UserAttribute.UserId, userId), + By(UserAttribute.IsPersonal, false), OrderBy(UserAttribute.createdAt, Descending) ) val userAttributeValue = userAttributes.find(_.name == userAttributeName).map(_.value) @@ -759,10 +760,10 @@ object LocalMappedConnector extends Connector with MdcLoggable { } override def getBankAccountByIban(iban: String, callContext: Option[CallContext]): OBPReturnType[Box[BankAccount]] = Future { - getBankAccountByRouting(None, "IBAN", iban, callContext) + getBankAccountByRoutingLegacy(None, "IBAN", iban, callContext) } - override def getBankAccountByRouting(bankId: Option[BankId], scheme: String, address: String, callContext: Option[CallContext]): Box[(BankAccount, Option[CallContext])] = { + override def getBankAccountByRoutingLegacy(bankId: Option[BankId], scheme: String, address: String, callContext: Option[CallContext]): Box[(BankAccount, Option[CallContext])] = { bankId match { case Some(bankId) => BankAccountRouting @@ -775,6 +776,11 @@ object LocalMappedConnector extends Connector with MdcLoggable { } } + override def getBankAccountByRouting(bankId: Option[BankId], scheme: String, address: String, callContext: Option[CallContext]): OBPReturnType[Box[BankAccount]] = Future { + getBankAccountByRoutingLegacy(bankId: Option[BankId], scheme: String, address: String, callContext: Option[CallContext]) + } + + override def getAccountRoutingsByScheme(bankId: Option[BankId], scheme: String, callContext: Option[CallContext]): OBPReturnType[Box[List[BankAccountRouting]]] = { Future { Full(bankId match { @@ -1785,7 +1791,7 @@ object LocalMappedConnector extends Connector with MdcLoggable { for { toAccount <- - Connector.connector.vend.getBankAccountByRouting(None, toAccountRoutingScheme, toAccountRoutingAddress, None) match { + Connector.connector.vend.getBankAccountByRoutingLegacy(None, toAccountRoutingScheme, toAccountRoutingAddress, None) match { case Full(bankAccount) => Future.successful(bankAccount._1) case _: EmptyBox => NewStyle.function.getCounterpartyByIban(toAccountRoutingAddress, callContext).flatMap(counterparty => @@ -4077,15 +4083,26 @@ object LocalMappedConnector extends Connector with MdcLoggable { override def getUserAttributes(userId: String, callContext: Option[CallContext]): OBPReturnType[Box[List[UserAttribute]]] = { UserAttributeProvider.userAttributeProvider.vend.getUserAttributesByUser(userId: String) map {(_, callContext)} } + + override def getNonPersonalUserAttributes(userId: String, callContext: Option[CallContext]): OBPReturnType[Box[List[UserAttribute]]] = { + UserAttributeProvider.userAttributeProvider.vend.getNonPersonalUserAttributes(userId: String) map {(_, callContext)} + } + override def getPersonalUserAttributes(userId: String, callContext: Option[CallContext]): OBPReturnType[Box[List[UserAttribute]]] = { + UserAttributeProvider.userAttributeProvider.vend.getPersonalUserAttributes(userId: String) map {(_, callContext)} + } override def getUserAttributesByUsers(userIds: List[String], callContext: Option[CallContext]): OBPReturnType[Box[List[UserAttribute]]] = { UserAttributeProvider.userAttributeProvider.vend.getUserAttributesByUsers(userIds) map {(_, callContext)} } + override def deleteUserAttribute(userAttributeId: String, callContext: Option[CallContext]): OBPReturnType[Box[Boolean]] = { + UserAttributeProvider.userAttributeProvider.vend.deleteUserAttribute(userAttributeId) map {(_, callContext)} + } override def createOrUpdateUserAttribute( userId: String, userAttributeId: Option[String], name: String, attributeType: UserAttributeType.Value, value: String, + isPersonal: Boolean, callContext: Option[CallContext] ): OBPReturnType[Box[UserAttribute]] = { UserAttributeProvider.userAttributeProvider.vend.createOrUpdateUserAttribute( @@ -4093,7 +4110,8 @@ object LocalMappedConnector extends Connector with MdcLoggable { userAttributeId: Option[String], name: String, attributeType: UserAttributeType.Value, - value: String + value: String, + isPersonal: Boolean ) map { (_, callContext) } diff --git a/obp-api/src/main/scala/code/bankconnectors/akka/AkkaConnector_vDec2018.scala b/obp-api/src/main/scala/code/bankconnectors/akka/AkkaConnector_vDec2018.scala index f2afd131a..9e6ca7c2c 100644 --- a/obp-api/src/main/scala/code/bankconnectors/akka/AkkaConnector_vDec2018.scala +++ b/obp-api/src/main/scala/code/bankconnectors/akka/AkkaConnector_vDec2018.scala @@ -958,7 +958,7 @@ object AkkaConnector_vDec2018 extends Connector with AkkaConnectorActorInit { adapterImplementation = Some(AdapterImplementation("- Core", 1)) ) - override def getBankAccountByRouting(bankId: Option[BankId], scheme: String, address: String, callContext: Option[CallContext]): Box[(BankAccount, Option[CallContext])] = { + override def getBankAccountByRoutingLegacy(bankId: Option[BankId], scheme: String, address: String, callContext: Option[CallContext]): Box[(BankAccount, Option[CallContext])] = { import com.openbankproject.commons.dto.{InBoundGetBankAccountByRouting => InBound, OutBoundGetBankAccountByRouting => OutBound} val req = OutBound(callContext.map(_.toOutboundAdapterCallContext).orNull, bankId, scheme, address) val response: Future[Box[InBound]] = (southSideActor ? req).mapTo[InBound].recoverWith(recoverFunction).map(Box !! _) diff --git a/obp-api/src/main/scala/code/bankconnectors/rest/RestConnector_vMar2019.scala b/obp-api/src/main/scala/code/bankconnectors/rest/RestConnector_vMar2019.scala index adb8dd156..401f389cd 100644 --- a/obp-api/src/main/scala/code/bankconnectors/rest/RestConnector_vMar2019.scala +++ b/obp-api/src/main/scala/code/bankconnectors/rest/RestConnector_vMar2019.scala @@ -887,7 +887,7 @@ trait RestConnector_vMar2019 extends Connector with KafkaHelper with MdcLoggable adapterImplementation = Some(AdapterImplementation("- Core", 1)) ) - override def getBankAccountByRouting(bankId: Option[BankId], scheme: String, address: String, callContext: Option[CallContext]): Box[(BankAccount, Option[CallContext])] = { + override def getBankAccountByRoutingLegacy(bankId: Option[BankId], scheme: String, address: String, callContext: Option[CallContext]): Box[(BankAccount, Option[CallContext])] = { import com.openbankproject.commons.dto.{InBoundGetBankAccountByRouting => InBound, OutBoundGetBankAccountByRouting => OutBound} val req = OutBound(callContext.map(_.toOutboundAdapterCallContext).orNull, bankId, scheme, address) val response: Future[Box[InBound]] = sendRequest[InBound](getUrl(callContext, "getBankAccountByRouting"), HttpMethods.POST, req, callContext) diff --git a/obp-api/src/main/scala/code/bankconnectors/storedprocedure/StoredProcedureConnector_vDec2019.scala b/obp-api/src/main/scala/code/bankconnectors/storedprocedure/StoredProcedureConnector_vDec2019.scala index d673c2539..9b9d87b96 100644 --- a/obp-api/src/main/scala/code/bankconnectors/storedprocedure/StoredProcedureConnector_vDec2019.scala +++ b/obp-api/src/main/scala/code/bankconnectors/storedprocedure/StoredProcedureConnector_vDec2019.scala @@ -866,7 +866,7 @@ trait StoredProcedureConnector_vDec2019 extends Connector with MdcLoggable { adapterImplementation = Some(AdapterImplementation("- Core", 1)) ) - override def getBankAccountByRouting(bankId: Option[BankId], scheme: String, address: String, callContext: Option[CallContext]): Box[(BankAccount, Option[CallContext])] = { + override def getBankAccountByRoutingLegacy(bankId: Option[BankId], scheme: String, address: String, callContext: Option[CallContext]): Box[(BankAccount, Option[CallContext])] = { import com.openbankproject.commons.dto.{InBoundGetBankAccountByRouting => InBound, OutBoundGetBankAccountByRouting => OutBound} val req = OutBound(callContext.map(_.toOutboundAdapterCallContext).orNull, bankId, scheme, address) val response: Future[Box[InBound]] = sendRequest[InBound]("obp_get_bank_account_by_routing", req, callContext) diff --git a/obp-api/src/main/scala/code/remotedata/RemotedataUserAttribute.scala b/obp-api/src/main/scala/code/remotedata/RemotedataUserAttribute.scala index 7c81b537a..dc8d3902b 100644 --- a/obp-api/src/main/scala/code/remotedata/RemotedataUserAttribute.scala +++ b/obp-api/src/main/scala/code/remotedata/RemotedataUserAttribute.scala @@ -19,14 +19,24 @@ object RemotedataUserAttribute extends ObpActorInit with UserAttributeProvider { override def getUserAttributesByUser(userId: String): Future[Box[List[UserAttribute]]] = (actor ? cc.getUserAttributesByUser(userId)).mapTo[Box[List[UserAttribute]]] + override def getPersonalUserAttributes(userId: String): Future[Box[List[UserAttribute]]] = + (actor ? cc.getPersonalUserAttributes(userId)).mapTo[Box[List[UserAttribute]]] + + override def getNonPersonalUserAttributes(userId: String): Future[Box[List[UserAttribute]]] = + (actor ? cc.getNonPersonalUserAttributes(userId)).mapTo[Box[List[UserAttribute]]] + override def getUserAttributesByUsers(userIds: List[String]): Future[Box[List[UserAttribute]]] = (actor ? cc.getUserAttributesByUsers(userIds)).mapTo[Box[List[UserAttribute]]] + override def deleteUserAttribute(userAttributeId: String): Future[Box[Boolean]] = + (actor ? cc.deleteUserAttribute(userAttributeId: String)).mapTo[Box[Boolean]] + override def createOrUpdateUserAttribute(userId: String, userAttributeId: Option[String], name: String, attributeType: UserAttributeType.Value, - value: String): Future[Box[UserAttribute]] = - (actor ? cc.createOrUpdateUserAttribute(userId, userAttributeId, name, attributeType, value )).mapTo[Box[UserAttribute]] + value: String, + isPersonal: Boolean): Future[Box[UserAttribute]] = + (actor ? cc.createOrUpdateUserAttribute(userId, userAttributeId, name, attributeType, value, isPersonal)).mapTo[Box[UserAttribute]] } diff --git a/obp-api/src/main/scala/code/remotedata/RemotedataUserAttributeActor.scala b/obp-api/src/main/scala/code/remotedata/RemotedataUserAttributeActor.scala index 9faacc237..312902aae 100644 --- a/obp-api/src/main/scala/code/remotedata/RemotedataUserAttributeActor.scala +++ b/obp-api/src/main/scala/code/remotedata/RemotedataUserAttributeActor.scala @@ -23,9 +23,13 @@ class RemotedataUserAttributeActor extends Actor with ObpActorHelper with MdcLog logger.debug(s"getUserAttributesByUser(${userIds})") mapper.getUserAttributesByUsers(userIds) pipeTo sender - case cc.createOrUpdateUserAttribute(userId: String, userAttributeId: Option[String], name: String, attributeType: UserAttributeType.Value, value: String) => - logger.debug(s"createOrUpdateUserAttribute(${userId}, ${userAttributeId}, ${name}, ${attributeType}, ${value})") - mapper.createOrUpdateUserAttribute(userId, userAttributeId, name, attributeType, value) pipeTo sender + case cc.createOrUpdateUserAttribute( + userId: String, userAttributeId: Option[String], name: String, + attributeType: UserAttributeType.Value, value: String, + isPersonal: Boolean + ) => + logger.debug(s"createOrUpdateUserAttribute(${userId}, ${userAttributeId}, ${name}, ${attributeType}, ${value}, ${isPersonal})") + mapper.createOrUpdateUserAttribute(userId, userAttributeId, name, attributeType, value, isPersonal) pipeTo sender case message => logger.warn("[AKKA ACTOR ERROR - REQUEST NOT RECOGNIZED] " + message) } diff --git a/obp-api/src/main/scala/code/sandbox/OBPDataImport.scala b/obp-api/src/main/scala/code/sandbox/OBPDataImport.scala index 4c42a36c5..e9d55dcbc 100644 --- a/obp-api/src/main/scala/code/sandbox/OBPDataImport.scala +++ b/obp-api/src/main/scala/code/sandbox/OBPDataImport.scala @@ -325,7 +325,7 @@ trait OBPDataImport extends MdcLoggable { val ibans = data.accounts.map(_.IBAN) val duplicateIbans = ibans diff ibans.distinct val existingIbans = data.accounts.flatMap(acc => { - Connector.connector.vend.getBankAccountByRouting(Some(BankId(acc.bank)), AccountRoutingScheme.IBAN.toString, acc.IBAN, None).map(_._1) + Connector.connector.vend.getBankAccountByRoutingLegacy(Some(BankId(acc.bank)), AccountRoutingScheme.IBAN.toString, acc.IBAN, None).map(_._1) }) if(!banksNotSpecifiedInImport.isEmpty) { diff --git a/obp-api/src/main/scala/code/transactionChallenge/MappedChallengeProvider.scala b/obp-api/src/main/scala/code/transactionChallenge/MappedChallengeProvider.scala index 629cdb3a2..0006d6f5e 100644 --- a/obp-api/src/main/scala/code/transactionChallenge/MappedChallengeProvider.scala +++ b/obp-api/src/main/scala/code/transactionChallenge/MappedChallengeProvider.scala @@ -1,6 +1,7 @@ package code.transactionChallenge -import code.api.util.APIUtil.transactionRequestChallengeTtl +import code.api.util.APIUtil.{allowedAnswerTransactionRequestChallengeAttempts, transactionRequestChallengeTtl} +import code.api.util.ErrorMessages.InvalidChallengeAnswer import code.api.util.{APIUtil, ErrorMessages} import com.openbankproject.commons.model.{ChallengeTrait, ErrorMessage} import com.openbankproject.commons.model.enums.StrongCustomerAuthentication.SCA @@ -77,13 +78,23 @@ object MappedChallengeProvider extends ChallengeProvider { if(currentHashedAnswer==expectedHashedAnswer) { tryo{challenge.mSuccessful(true).mScaStatus(StrongCustomerAuthenticationStatus.finalised.toString).saveMe()} } else { - Failure(s"${ErrorMessages.InvalidChallengeAnswer}") + Failure(s"${ + s"${ + InvalidChallengeAnswer + .replace("answer may be expired.", s"answer may be expired (${transactionRequestChallengeTtl} seconds).") + .replace("up your allowed attempts.", s"up your allowed attempts (${allowedAnswerTransactionRequestChallengeAttempts} times).") + }"}") } case Some(id) => if(currentHashedAnswer==expectedHashedAnswer && id==challenge.expectedUserId) { tryo{challenge.mSuccessful(true).mScaStatus(StrongCustomerAuthenticationStatus.finalised.toString).saveMe()} } else { - Failure(s"${ErrorMessages.InvalidChallengeAnswer}") + Failure(s"${ + s"${ + InvalidChallengeAnswer + .replace("answer may be expired.", s"answer may be expired (${transactionRequestChallengeTtl} seconds).") + .replace("up your allowed attempts.", s"up your allowed attempts (${allowedAnswerTransactionRequestChallengeAttempts} times).") + }"}") } } }else{ diff --git a/obp-api/src/main/scala/code/users/MappedUserAttribute.scala b/obp-api/src/main/scala/code/users/MappedUserAttribute.scala index 6b281c10d..bbb2dbaa9 100644 --- a/obp-api/src/main/scala/code/users/MappedUserAttribute.scala +++ b/obp-api/src/main/scala/code/users/MappedUserAttribute.scala @@ -1,7 +1,8 @@ package code.users -import java.util.Date +import code.api.util.ErrorMessages +import java.util.Date import code.util.MappedUUID import com.openbankproject.commons.ExecutionContext.Implicits.global import com.openbankproject.commons.model.UserAttributeTrait @@ -19,18 +20,47 @@ object MappedUserAttributeProvider extends UserAttributeProvider { UserAttribute.findAll(By(UserAttribute.UserId, userId)) ) } + override def getPersonalUserAttributes(userId: String): Future[Box[List[UserAttribute]]] = Future { + tryo( + UserAttribute.findAll( + By(UserAttribute.UserId, userId), + By(UserAttribute.IsPersonal, true), + OrderBy(UserAttribute.createdAt, Descending) + ) + ) + } + override def getNonPersonalUserAttributes(userId: String): Future[Box[List[UserAttribute]]] = Future { + tryo( + UserAttribute.findAll( + By(UserAttribute.UserId, userId), + By(UserAttribute.IsPersonal, false), + OrderBy(UserAttribute.createdAt, Descending) + ) + ) + } override def getUserAttributesByUsers(userIds: List[String]): Future[Box[List[UserAttribute]]] = Future { tryo( UserAttribute.findAll(ByList(UserAttribute.UserId, userIds)) ) } + + override def deleteUserAttribute(userAttributeId: String): Future[Box[Boolean]] = { + Future { + UserAttribute.find(By(UserAttribute.UserAttributeId, userAttributeId)) match { + case Full(t) => Full(t.delete_!) + case Empty => Empty ?~! ErrorMessages.UserAttributeNotFound + case _ => Full(false) + } + } + } override def createOrUpdateUserAttribute(userId: String, userAttributeId: Option[String], name: String, attributeType: UserAttributeType.Value, - value: String): Future[Box[UserAttribute]] = { + value: String, + isPersonal: Boolean): Future[Box[UserAttribute]] = { userAttributeId match { case Some(id) => Future { UserAttribute.find(By(UserAttribute.UserAttributeId, id)) match { @@ -40,6 +70,7 @@ object MappedUserAttributeProvider extends UserAttributeProvider { .Name(name) .Type(attributeType.toString) .`Value`(value) +// .IsPersonal(isPersonal) //Can not update this field in update ne .saveMe() } case _ => Empty @@ -52,6 +83,7 @@ object MappedUserAttributeProvider extends UserAttributeProvider { .Name(name) .Type(attributeType.toString()) .`Value`(value) + .IsPersonal(isPersonal) .saveMe() } } @@ -65,9 +97,12 @@ class UserAttribute extends UserAttributeTrait with LongKeyedMapper[UserAttribut override def getSingleton = UserAttribute object UserAttributeId extends MappedUUID(this) object UserId extends MappedUUID(this) - object Name extends MappedString(this, 50) + object Name extends MappedString(this, 255) object Type extends MappedString(this, 50) object `Value` extends MappedString(this, 255) + object IsPersonal extends MappedBoolean(this) { + override def defaultValue = true + } override def userAttributeId: String = UserAttributeId.get override def userId: String = UserId.get @@ -75,6 +110,7 @@ class UserAttribute extends UserAttributeTrait with LongKeyedMapper[UserAttribut override def attributeType: UserAttributeType.Value = UserAttributeType.withName(Type.get) override def value: String = `Value`.get override def insertDate: Date = createdAt.get + override def isPersonal: Boolean = IsPersonal.get } object UserAttribute extends UserAttribute with LongKeyedMetaMapper[UserAttribute] { diff --git a/obp-api/src/main/scala/code/users/UserAttributeProvider.scala b/obp-api/src/main/scala/code/users/UserAttributeProvider.scala index e921ea5e9..09cfcb5f7 100644 --- a/obp-api/src/main/scala/code/users/UserAttributeProvider.scala +++ b/obp-api/src/main/scala/code/users/UserAttributeProvider.scala @@ -38,23 +38,31 @@ trait UserAttributeProvider { private val logger = Logger(classOf[UserAttributeProvider]) def getUserAttributesByUser(userId: String): Future[Box[List[UserAttribute]]] + def getPersonalUserAttributes(userId: String): Future[Box[List[UserAttribute]]] + def getNonPersonalUserAttributes(userId: String): Future[Box[List[UserAttribute]]] def getUserAttributesByUsers(userIds: List[String]): Future[Box[List[UserAttribute]]] + def deleteUserAttribute(userAttributeId: String): Future[Box[Boolean]] def createOrUpdateUserAttribute(userId: String, userAttributeId: Option[String], name: String, attributeType: UserAttributeType.Value, - value: String): Future[Box[UserAttribute]] + value: String, + isPersonal: Boolean): Future[Box[UserAttribute]] // End of Trait } class RemotedataUserAttributeCaseClasses { case class getUserAttributesByUser(userId: String) + case class getPersonalUserAttributes(userId: String) + case class getNonPersonalUserAttributes(userId: String) + case class deleteUserAttribute(userAttributeId: String) case class getUserAttributesByUsers(userIds: List[String]) case class createOrUpdateUserAttribute(userId: String, userAttributeId: Option[String], name: String, attributeType: UserAttributeType.Value, - value: String) + value: String, + isPersonal: Boolean) } object RemotedataUserAttributeCaseClasses extends RemotedataUserAttributeCaseClasses diff --git a/obp-api/src/test/resources/frozen_type_meta_data b/obp-api/src/test/resources/frozen_type_meta_data index e02f9500f..16e25bbbf 100644 Binary files a/obp-api/src/test/resources/frozen_type_meta_data and b/obp-api/src/test/resources/frozen_type_meta_data differ diff --git a/obp-api/src/test/scala/code/api/v4_0_0/UserAttributesTest.scala b/obp-api/src/test/scala/code/api/v4_0_0/UserAttributesTest.scala index 46f1067f2..97e3fbfc2 100644 --- a/obp-api/src/test/scala/code/api/v4_0_0/UserAttributesTest.scala +++ b/obp-api/src/test/scala/code/api/v4_0_0/UserAttributesTest.scala @@ -23,9 +23,9 @@ class UserAttributesTest extends V400ServerSetup { * This is made possible by the scalatest maven plugin */ object VersionOfApi extends Tag(ApiVersion.v4_0_0.toString) - object ApiEndpoint1 extends Tag(nameOf(Implementations4_0_0.createCurrentUserAttribute)) - object ApiEndpoint2 extends Tag(nameOf(Implementations4_0_0.getCurrentUserAttributes)) - object ApiEndpoint3 extends Tag(nameOf(Implementations4_0_0.updateCurrentUserAttribute)) + object ApiEndpoint1 extends Tag(nameOf(Implementations4_0_0.createMyPersonalUserAttribute)) + object ApiEndpoint2 extends Tag(nameOf(Implementations4_0_0.getMyPersonalUserAttributes)) + object ApiEndpoint3 extends Tag(nameOf(Implementations4_0_0.updateMyPersonalUserAttribute)) lazy val bankId = testBankId1.value diff --git a/obp-api/src/test/scala/code/api/v5_1_0/UserAttributesTest.scala b/obp-api/src/test/scala/code/api/v5_1_0/UserAttributesTest.scala new file mode 100644 index 000000000..b7c117582 --- /dev/null +++ b/obp-api/src/test/scala/code/api/v5_1_0/UserAttributesTest.scala @@ -0,0 +1,174 @@ +package code.api.v5_1_0 + +import code.api.ResourceDocs1_4_0.SwaggerDefinitionsJSON +import code.api.util.APIUtil.OAuth._ +import code.api.util.ApiRole +import code.api.util.ErrorMessages._ +import code.api.v4_0_0.UsersJsonV400 +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.write +import org.scalatest.Tag + + +class UserAttributesTest 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.createNonPersonalUserAttribute)) + object ApiEndpoint2 extends Tag(nameOf(Implementations5_1_0.deleteNonPersonalUserAttribute)) + object ApiEndpoint3 extends Tag(nameOf(Implementations5_1_0.getNonPersonalUserAttributes)) + + + lazy val bankId = testBankId1.value + lazy val accountId = testAccountId1.value + lazy val batteryLevel = "BATTERY_LEVEL" + lazy val postUserAttributeJsonV510 = SwaggerDefinitionsJSON.userAttributeJsonV510.copy(name = batteryLevel) + lazy val putUserAttributeJsonV510 = SwaggerDefinitionsJSON.userAttributeJsonV510.copy(name = "ROLE_2") + + feature(s"test $ApiEndpoint1 $ApiEndpoint2 $ApiEndpoint3 version $VersionOfApi - Unauthorized access") { + scenario(s"We will call the end $ApiEndpoint1 without user credentials", ApiEndpoint1, VersionOfApi) { + When("We make a request v5.1.0") + val request510 = (v5_1_0_Request / "users" /"testUserId"/ "non-personal" / "attributes").POST + val response510 = makePostRequest(request510, write(postUserAttributeJsonV510)) + Then("We should get a 401") + response510.code should equal(401) + response510.body.extract[ErrorMessage].message should equal(UserNotLoggedIn) + } + + scenario(s"We will call the $ApiEndpoint2 without user credentials", ApiEndpoint2, VersionOfApi) { + When("We make a request v5.1.0") + val request510 = (v5_1_0_Request / "users" /"testUserId" / "non-personal" /"attributes"/"testUserAttributeId").DELETE + val response510 = makeDeleteRequest(request510) + Then("We should get a 401") + response510.code should equal(401) + response510.body.extract[ErrorMessage].message should equal(UserNotLoggedIn) + } + + scenario(s"We will call the $ApiEndpoint3 without user credentials", ApiEndpoint3, VersionOfApi) { + When("We make a request v5.1.0") + val request510 = (v5_1_0_Request / "users" /"testUserId" / "non-personal" /"attributes").GET + val response510 = makeGetRequest(request510) + Then("We should get a 401") + response510.code should equal(401) + response510.body.extract[ErrorMessage].message should equal(UserNotLoggedIn) + } + } + + feature(s"test $ApiEndpoint1 $ApiEndpoint2 $ApiEndpoint3 version $VersionOfApi - authorized access") { + scenario(s"We will call the $ApiEndpoint1 $ApiEndpoint2 $ApiEndpoint3 with user credentials", ApiEndpoint1, + ApiEndpoint2, ApiEndpoint3,VersionOfApi) { + When("We make a request v5.1.0, we need to prepare the roles and users") + Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, ApiRole.CanGetAnyUser.toString) + Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, ApiRole.CanCreateNonPersonalUserAttribute.toString) + Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, ApiRole.CanDeleteNonPersonalUserAttribute.toString) + Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, ApiRole.CanGetNonPersonalUserAttributes.toString) + + val requestGetUsers = (v5_1_0_Request / "users").GET <@ (user1) + val responseGetUsers = makeGetRequest(requestGetUsers) + val userIds = responseGetUsers.body.extract[UsersJsonV400].users.map(_.user_id) + val userId = userIds(scala.util.Random.nextInt(userIds.size)) + + val request510 = (v5_1_0_Request / "users"/ userId / "non-personal" /"attributes").POST <@ (user1) + val response510 = makePostRequest(request510, write(postUserAttributeJsonV510)) + Then("We should get a 201") + response510.code should equal(201) + val jsonResponse = response510.body.extract[UserAttributeResponseJsonV510] + jsonResponse.is_personal shouldBe(false) + jsonResponse.name shouldBe(batteryLevel) + val userAttributeId = jsonResponse.user_attribute_id + + { + val request510 = (v5_1_0_Request / "users" / userId / "non-personal" /"attributes").GET <@ (user1) + val response510 = makeGetRequest(request510) + Then("We should get a 200") + response510.code should equal(200) + val jsonResponse = response510.body.extract[UserAttributesResponseJsonV510] + jsonResponse.user_attributes.length shouldBe (1) + jsonResponse.user_attributes.head.name shouldBe (batteryLevel) + jsonResponse.user_attributes.head.user_attribute_id shouldBe (userAttributeId) + } + val requestDeleteUserAttribute = (v5_1_0_Request / "users"/ userId/"non-personal"/"attributes"/userAttributeId).DELETE <@ (user1) + val responseDeleteUserAttribute = makeDeleteRequest(requestDeleteUserAttribute) + Then("We should get a 204") + responseDeleteUserAttribute.code should equal(204) + + Then("We delete it again, we should get the can not find error") + val responseDeleteUserAttributeAgain = makeDeleteRequest(requestDeleteUserAttribute) + Then("We should get a 400") + responseDeleteUserAttributeAgain.code should equal(400) + responseDeleteUserAttributeAgain.body.extract[ErrorMessage].message contains (UserAttributeNotFound) shouldBe( true) + + + { + val request510 = (v5_1_0_Request / "users" / userId / "non-personal" /"attributes").GET <@ (user1) + val response510 = makeGetRequest(request510) + Then("We should get a 200") + response510.code should equal(200) + val jsonResponse = response510.body.extract[UserAttributesResponseJsonV510] + jsonResponse.user_attributes.length shouldBe (0) + } + + } + + scenario(s"We will call the $ApiEndpoint1 with user credentials, but missing roles", ApiEndpoint1, ApiEndpoint2, VersionOfApi) { + When("We make a request v5.1.0, we need to prepare the roles and users") + Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, ApiRole.CanGetAnyUser.toString) + + val requestGetUsers = (v5_1_0_Request / "users").GET <@ (user1) + val responseGetUsers = makeGetRequest(requestGetUsers) + val userIds = responseGetUsers.body.extract[UsersJsonV400].users.map(_.user_id) + val userId = userIds(scala.util.Random.nextInt(userIds.size)) + + val request510 = (v5_1_0_Request / "users" / userId / "non-personal" /"attributes").POST <@ (user1) + val response510 = makePostRequest(request510, write(postUserAttributeJsonV510)) + Then("We should get a 403") + response510.code should equal(403) + response510.body.extract[ErrorMessage].message contains (UserHasMissingRoles) shouldBe (true) + response510.body.extract[ErrorMessage].message contains (ApiRole.CanCreateNonPersonalUserAttribute.toString()) shouldBe (true) + } + + scenario(s"We will call the $ApiEndpoint2 with user credentials, but missing roles", ApiEndpoint1, ApiEndpoint2, VersionOfApi) { + When("We make a request v5.1.0, we need to prepare the roles and users") + Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, ApiRole.CanGetAnyUser.toString) + + val requestGetUsers = (v5_1_0_Request / "users").GET <@ (user1) + val responseGetUsers = makeGetRequest(requestGetUsers) + val userIds = responseGetUsers.body.extract[UsersJsonV400].users.map(_.user_id) + val userId = userIds(scala.util.Random.nextInt(userIds.size)) + + val request510 = (v5_1_0_Request / "users" / userId / "non-personal" /"attributes" / "attributeId").DELETE <@ (user1) + val response510 = makeDeleteRequest(request510) + Then("We should get a 403") + response510.code should equal(403) + response510.body.extract[ErrorMessage].message contains (UserHasMissingRoles) shouldBe (true) + response510.body.extract[ErrorMessage].message contains (ApiRole.CanDeleteNonPersonalUserAttribute.toString()) shouldBe (true) + } + + scenario(s"We will call the $ApiEndpoint3 with user credentials, but missing roles", ApiEndpoint1, ApiEndpoint2, VersionOfApi) { + When("We make a request v5.1.0, we need to prepare the roles and users") + Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, ApiRole.CanGetAnyUser.toString) + + val requestGetUsers = (v5_1_0_Request / "users").GET <@ (user1) + val responseGetUsers = makeGetRequest(requestGetUsers) + val userIds = responseGetUsers.body.extract[UsersJsonV400].users.map(_.user_id) + val userId = userIds(scala.util.Random.nextInt(userIds.size)) + + val request510 = (v5_1_0_Request / "users" / userId / "non-personal" /"attributes" ).GET <@ (user1) + val response510 = makeGetRequest(request510) + Then("We should get a 403") + response510.code should equal(403) + response510.body.extract[ErrorMessage].message contains (UserHasMissingRoles) shouldBe (true) + response510.body.extract[ErrorMessage].message contains (ApiRole.CanGetNonPersonalUserAttributes.toString()) shouldBe (true) + } + } + +} 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 63da94958..b95706620 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 @@ -103,6 +103,7 @@ trait UserAttributeTrait { def attributeType: UserAttributeType.Value def value: String def insertDate: Date + def isPersonal: Boolean } trait AccountAttribute {