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 9eb1463ab..72033abd7 100644 --- a/obp-api/src/main/scala/code/api/util/ApiRole.scala +++ b/obp-api/src/main/scala/code/api/util/ApiRole.scala @@ -406,6 +406,9 @@ object ApiRole { case class CanCreateUserAttribute (requiresBankId: Boolean = false) extends ApiRole lazy val canCreateUserAttribute = CanCreateUserAttribute() + + case class CanDeleteUserAttribute (requiresBankId: Boolean = false) extends ApiRole + lazy val canDeleteUserAttribute = CanDeleteUserAttribute() case class CanReadUserLockedStatus(requiresBankId: Boolean = false) extends ApiRole lazy val canReadUserLockedStatus = CanReadUserLockedStatus() 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 1e9485579..3da4fc675 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,6 +15,7 @@ import code.api.v3_1_0.ConsentJsonV310 import code.api.v3_1_0.JSONFactory310.createBadLoginStatusJson 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 @@ -132,6 +133,7 @@ trait APIMethods510 { userAttributeResponseJson, List( $UserNotLoggedIn, + UserHasMissingRoles, InvalidJsonFormat, UnknownError ), @@ -144,7 +146,7 @@ trait APIMethods510 { cc => val failMsg = s"$InvalidJsonFormat The Json body should be the $UserAttributeJsonV400 " for { - (attributes, callContext) <- NewStyle.function.getUserAttributes(userId, cc.callContext) + (user, callContext) <- NewStyle.function.getUserByUserId(userId, cc.callContext) postedData <- NewStyle.function.tryons(failMsg, 400, callContext) { json.extract[UserAttributeJsonV400] } @@ -154,7 +156,7 @@ trait APIMethods510 { UserAttributeType.withName(postedData.`type`) } (userAttribute, callContext) <- NewStyle.function.createOrUpdateUserAttribute( - userId, + user.userId, None, postedData.name, userAttributeType, @@ -165,7 +167,48 @@ trait APIMethods510 { } } } + + resourceDocs += ResourceDoc( + deleteUserAttribute, + implementedInApiVersion, + nameOf(deleteUserAttribute), + "DELETE", + "/users/USER_ID/attributes/USER_ATTRIBUTE_ID", + "Delete User Attribute", + s"""Delete the 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(canDeleteUserAttribute))) + lazy val deleteUserAttribute: OBPEndpoint = { + case "users" :: userId :: "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)) + } + } + } + + staticResourceDocs += ResourceDoc( customViewNamesCheck, implementedInApiVersion, diff --git a/obp-api/src/main/scala/code/bankconnectors/Connector.scala b/obp-api/src/main/scala/code/bankconnectors/Connector.scala index 0d9e85a3c..2a2e8ec69 100644 --- a/obp-api/src/main/scala/code/bankconnectors/Connector.scala +++ b/obp-api/src/main/scala/code/bankconnectors/Connector.scala @@ -2272,6 +2272,11 @@ trait Connector extends MdcLoggable { 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..ba3f9edd0 100644 --- a/obp-api/src/main/scala/code/bankconnectors/LocalMappedConnector.scala +++ b/obp-api/src/main/scala/code/bankconnectors/LocalMappedConnector.scala @@ -4080,6 +4080,9 @@ object LocalMappedConnector extends Connector with MdcLoggable { 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], diff --git a/obp-api/src/main/scala/code/remotedata/RemotedataUserAttribute.scala b/obp-api/src/main/scala/code/remotedata/RemotedataUserAttribute.scala index 7c81b537a..5707f9688 100644 --- a/obp-api/src/main/scala/code/remotedata/RemotedataUserAttribute.scala +++ b/obp-api/src/main/scala/code/remotedata/RemotedataUserAttribute.scala @@ -22,6 +22,9 @@ object RemotedataUserAttribute extends ObpActorInit with UserAttributeProvider { 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, diff --git a/obp-api/src/main/scala/code/users/MappedUserAttribute.scala b/obp-api/src/main/scala/code/users/MappedUserAttribute.scala index 427fd4d29..2c336d973 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 @@ -25,6 +26,16 @@ object MappedUserAttributeProvider extends UserAttributeProvider { 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], diff --git a/obp-api/src/main/scala/code/users/UserAttributeProvider.scala b/obp-api/src/main/scala/code/users/UserAttributeProvider.scala index e921ea5e9..1040a071b 100644 --- a/obp-api/src/main/scala/code/users/UserAttributeProvider.scala +++ b/obp-api/src/main/scala/code/users/UserAttributeProvider.scala @@ -39,6 +39,7 @@ trait UserAttributeProvider { def getUserAttributesByUser(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, @@ -49,6 +50,7 @@ trait UserAttributeProvider { class RemotedataUserAttributeCaseClasses { case class getUserAttributesByUser(userId: String) + case class deleteUserAttribute(userAttributeId: String) case class getUserAttributesByUsers(userIds: List[String]) case class createOrUpdateUserAttribute(userId: String, userAttributeId: Option[String], 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 index c8d661eac..ad87e894f 100644 --- 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 @@ -11,7 +11,6 @@ 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.common.Box import net.liftweb.json.Serialization.write import org.scalatest.Tag @@ -26,6 +25,7 @@ class UserAttributesTest extends V510ServerSetup { */ object VersionOfApi extends Tag(ApiVersion.v5_1_0.toString) object ApiEndpoint1 extends Tag(nameOf(Implementations5_1_0.createUserAttribute)) + object ApiEndpoint2 extends Tag(nameOf(Implementations5_1_0.deleteUserAttribute)) lazy val bankId = testBankId1.value @@ -36,24 +36,32 @@ class UserAttributesTest extends V510ServerSetup { - feature(s"test $ApiEndpoint1 version $VersionOfApi - Unauthorized access") { - scenario("We will call the endpoint without user credentials", ApiEndpoint1, VersionOfApi) { + feature(s"test $ApiEndpoint1 $ApiEndpoint2 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"/ "attributes").POST + val request510 = (v5_1_0_Request / "users" /"testUserId"/ "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", ApiEndpoint1, VersionOfApi) { + When("We make a request v5.1.0") + val request510 = (v5_1_0_Request / "users" /"testUserId"/ "attributes"/"testUserAttributeId").POST + val response510 = makeDeleteRequest(request510) + Then("We should get a 401") + response510.code should equal(401) + response510.body.extract[ErrorMessage].message should equal(UserNotLoggedIn) + } } - feature(s"test $ApiEndpoint1 version $VersionOfApi - authorized access") { - scenario("We will call the endpoint with user credentials, but missing role", ApiEndpoint1, VersionOfApi) { + feature(s"test $ApiEndpoint1 $ApiEndpoint2 version $VersionOfApi - authorized access") { + scenario(s"We will call the $ApiEndpoint1 $ApiEndpoint2 with user credentials", 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) Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, ApiRole.CanCreateUserAttribute.toString) - - + Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, ApiRole.CanDeleteUserAttribute.toString) val requestGetUsers = (v5_1_0_Request / "users").GET <@ (user1) val responseGetUsers = makeGetRequest(requestGetUsers) @@ -65,10 +73,23 @@ class UserAttributesTest extends V510ServerSetup { Then("We should get a 201") response510.code should equal(201) val jsonResponse = response510.body.extract[UserAttributeResponseJsonV400] - jsonResponse.name should be(batteryLevel) + val userAttributeId = jsonResponse.user_attribute_id + + + val requestDeleteUserAttribute = (v5_1_0_Request / "users"/ userId/"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) + } - scenario("We will call the endpoint with user credentials, but missing roles", ApiEndpoint1, VersionOfApi) { + 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) @@ -84,6 +105,23 @@ class UserAttributesTest extends V510ServerSetup { response510.body.extract[ErrorMessage].message contains (UserHasMissingRoles) shouldBe (true) response510.body.extract[ErrorMessage].message contains (ApiRole.CanCreateUserAttribute.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 / "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.CanDeleteUserAttribute.toString()) shouldBe (true) + } } }