Merge pull request #2225 from hongwei1/develop

Feature/added isPersonal field to user attribute
This commit is contained in:
Simon Redfern 2023-05-31 16:16:24 +02:00 committed by GitHub
commit caee653943
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 630 additions and 72 deletions

View File

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

View File

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

View File

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

View File

@ -552,9 +552,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 "

View File

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

View File

@ -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,14 @@ 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 getUserAttributesByUsers(userIds: List[String], callContext: Option[CallContext]): OBPReturnType[List[UserAttribute]] = {
Connector.connector.vend.getUserAttributesByUsers(
userIds, callContext: Option[CallContext]
@ -1901,6 +1917,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 +1926,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)

View File

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

View File

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

View File

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

View File

@ -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, apiTagNewStyle)
)
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, apiTagNewStyle),
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, apiTagNewStyle),
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 {

View File

@ -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.getPersonalUserAttributes(
user.userId,
callContext,
)
} yield {
(JSONFactory510.createUserAttributesJson(userAttributes), HttpCode.`200`(callContext))
}
}
}
staticResourceDocs += ResourceDoc(

View File

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

View File

@ -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,9 @@ 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 getUserAttributesByUsers(userIds: List[String], callContext: Option[CallContext]): OBPReturnType[Box[List[UserAttribute]]] =
Future{(Failure(setUnimplementedError), callContext)}
@ -2269,9 +2273,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,

View File

@ -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,23 @@ 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 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 +4107,8 @@ object LocalMappedConnector extends Connector with MdcLoggable {
userAttributeId: Option[String],
name: String,
attributeType: UserAttributeType.Value,
value: String
value: String,
isPersonal: Boolean
) map {
(_, callContext)
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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/"attributes"/"non-personal"/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)
}
}
}

View File

@ -103,6 +103,7 @@ trait UserAttributeTrait {
def attributeType: UserAttributeType.Value
def value: String
def insertDate: Date
def isPersonal: Boolean
}
trait AccountAttribute {