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 a9525630b..8a11054f1 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 @@ -2735,6 +2735,99 @@ object SwaggerDefinitionsJSON { views = List(viewJSONV220) ) + + val viewJsonV500 = ViewJsonV500( + id = "1234", + short_name = "short_name", + description = "description", + metadata_view = SYSTEM_OWNER_VIEW_ID, + is_public = true, + is_system = true, + alias = "No", + hide_metadata_if_alias_used = true, + can_add_comment = true, + can_add_corporate_location = true, + can_add_image = true, + can_add_image_url = true, + can_add_more_info = true, + can_add_open_corporates_url = true, + can_add_physical_location = true, + can_add_private_alias = true, + can_add_public_alias = true, + can_add_tag = true, + can_add_url = true, + can_add_where_tag = true, + can_delete_comment = true, + can_add_counterparty = true, + can_delete_corporate_location = true, + can_delete_image = true, + can_delete_physical_location = true, + can_delete_tag = true, + can_delete_where_tag = true, + can_edit_owner_comment = true, + can_see_bank_account_balance = true, + can_query_available_funds = true, + can_see_bank_account_bank_name = true, + can_see_bank_account_currency = true, + can_see_bank_account_iban = true, + can_see_bank_account_label = true, + can_see_bank_account_national_identifier = true, + can_see_bank_account_number = true, + can_see_bank_account_owners = true, + can_see_bank_account_swift_bic = true, + can_see_bank_account_type = true, + can_see_comments = true, + can_see_corporate_location = true, + can_see_image_url = true, + can_see_images = true, + can_see_more_info = true, + can_see_open_corporates_url = true, + can_see_other_account_bank_name = true, + can_see_other_account_iban = true, + can_see_other_account_kind = true, + can_see_other_account_metadata = true, + can_see_other_account_national_identifier = true, + can_see_other_account_number = true, + can_see_other_account_swift_bic = true, + can_see_owner_comment = true, + can_see_physical_location = true, + can_see_private_alias = true, + can_see_public_alias = true, + can_see_tags = true, + can_see_transaction_amount = true, + can_see_transaction_balance = true, + can_see_transaction_currency = true, + can_see_transaction_description = true, + can_see_transaction_finish_date = true, + can_see_transaction_metadata = true, + can_see_transaction_other_bank_account = true, + can_see_transaction_start_date = true, + can_see_transaction_this_bank_account = true, + can_see_transaction_type = true, + can_see_url = true, + can_see_where_tag = true, + //V300 new + can_see_bank_routing_scheme = true, + can_see_bank_routing_address = true, + can_see_bank_account_routing_scheme = true, + can_see_bank_account_routing_address = true, + can_see_other_bank_routing_scheme = true, + can_see_other_bank_routing_address = true, + can_see_other_account_routing_scheme = true, + can_see_other_account_routing_address = true, + can_add_transaction_request_to_own_account = true, //added following two for payments + can_add_transaction_request_to_any_account = true, + can_see_bank_account_credit_limit = true, + can_create_direct_debit = true, + can_create_standing_order = true, + can_grant_access_to_views = List("Owner"), + can_revoke_access_to_views = List("Owner") + ) + + val viewsJsonV500 = ViewsJsonV500( + views = List(viewJsonV500) + ) + val fXRateJSON = FXRateJsonV220( bank_id = bankIdExample.value, from_currency_code = "EUR", diff --git a/obp-api/src/main/scala/code/api/v5_0_0/APIMethods500.scala b/obp-api/src/main/scala/code/api/v5_0_0/APIMethods500.scala index 2b775c003..cb7551caa 100644 --- a/obp-api/src/main/scala/code/api/v5_0_0/APIMethods500.scala +++ b/obp-api/src/main/scala/code/api/v5_0_0/APIMethods500.scala @@ -1,7 +1,8 @@ package code.api.v5_0_0 -import java.util.Date +import java.util.concurrent.ThreadLocalRandom +import code.accountattribute.AccountAttributeX import code.api.ResourceDocs1_4_0.SwaggerDefinitionsJSON._ import code.api.util.APIUtil._ import code.api.util.ApiRole._ @@ -15,36 +16,30 @@ import code.api.v3_0_0.JSONFactory300 import code.api.v3_1_0._ import code.api.v4_0_0.JSONFactory400.createCustomersMinimalJson import code.api.v4_0_0.{JSONFactory400, PutProductJsonV400} -import code.api.v5_0_0.JSONFactory500.createPhysicalCardJson +import code.api.v5_0_0.JSONFactory500.{createPhysicalCardJson, createViewsJsonV500, createViewJsonV500} import code.bankconnectors.Connector import code.consent.{ConsentRequests, Consents} -import code.model._ import code.entitlement.Entitlement +import code.model._ import code.model.dataAccess.BankAccountCreation import code.transactionrequests.TransactionRequests.TransactionRequestTypes.{apply => _} import code.util.Helper +import code.util.Helper.booleanToFuture import code.views.Views import com.github.dwickern.macros.NameOf.nameOf import com.openbankproject.commons.ExecutionContext.Implicits.global import com.openbankproject.commons.model.enums.StrongCustomerAuthentication -import com.openbankproject.commons.model.{AccountAttribute, AccountId, AccountRouting, BankAccount, BankId, BankIdAccountId, CardAction, CardAttributeCommons, CardCollectionInfo, CardPostedInfo, CardReplacementInfo, CardReplacementReason, CreditLimit, CreditRating, CustomerFaceImage, CustomerId, PinResetInfo, PinResetReason, ProductCode, TransactionRequestType, UserAuthContextUpdateStatus, View, ViewId} +import com.openbankproject.commons.model._ import com.openbankproject.commons.util.ApiVersion -import net.liftweb.common.{Box, Empty, Full} +import net.liftweb.common.{Empty, Full} import net.liftweb.http.Req import net.liftweb.http.rest.RestHelper import net.liftweb.json import net.liftweb.json.{Extraction, compactRender, prettyRender} -import net.liftweb.util.Helpers.tryo import net.liftweb.util.Props -import java.util.concurrent.ThreadLocalRandom - -import code.accountattribute.AccountAttributeX -import code.api.v5_0_0.JSONFactory500.createViewsJsonV500 -import code.util.Helper.booleanToFuture import scala.collection.immutable.{List, Nil} import scala.collection.mutable.ArrayBuffer -import scala.concurrent import scala.concurrent.Future import scala.util.Random @@ -1447,7 +1442,7 @@ trait APIMethods500 { } - resourceDocs += ResourceDoc( + staticResourceDocs += ResourceDoc( getViewsForBankAccount, implementedInApiVersion, nameOf(getViewsForBankAccount), @@ -1479,7 +1474,7 @@ trait APIMethods500 { | |${authenticationRequiredMessage(true)} and the user needs to have access to the owner view.""", emptyObjectJson, - viewsJsonV300, + viewsJsonV500, List( $UserNotLoggedIn, $BankAccountNotFound, @@ -1507,6 +1502,41 @@ trait APIMethods500 { } } + + staticResourceDocs += ResourceDoc( + getSystemView, + implementedInApiVersion, + "getSystemView", + "GET", + "/system-views/VIEW_ID", + "Get System View", + s"""Get System View + | + |${authenticationRequiredMessage(true)} + | + """.stripMargin, + emptyObjectJson, + viewJsonV500, + List( + $UserNotLoggedIn, + $BankNotFound, + UnknownError + ), + List(apiTagSystemView, apiTagNewStyle), + Some(List(canGetSystemView)) + ) + + lazy val getSystemView: OBPEndpoint = { + case "system-views" :: viewId :: Nil JsonGet _ => { + cc => + for { + view <- NewStyle.function.systemView(ViewId(viewId), cc.callContext) + } yield { + (createViewJsonV500(view), HttpCode.`200`(cc.callContext)) + } + } + } + staticResourceDocs += ResourceDoc( createCustomerAccountLink, implementedInApiVersion, diff --git a/obp-api/src/test/scala/code/api/v5_0_0/SystemViewsTests.scala b/obp-api/src/test/scala/code/api/v5_0_0/SystemViewsTests.scala new file mode 100644 index 000000000..b85ca2f45 --- /dev/null +++ b/obp-api/src/test/scala/code/api/v5_0_0/SystemViewsTests.scala @@ -0,0 +1,273 @@ +/** +Open Bank Project - API +Copyright (C) 2011-2019, TESOBE GmbH. + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . + +Email: contact@tesobe.com +TESOBE GmbH. +Osloer Strasse 16/17 +Berlin 13359, Germany + +This product includes software developed at +TESOBE (http://www.tesobe.com/) + + */ +package code.api.v5_0_0 + +import _root_.net.liftweb.json.Serialization.write +import code.api.Constant._ +import code.api.ResourceDocs1_4_0.SwaggerDefinitionsJSON._ +import code.api.util.APIUtil +import code.api.util.APIUtil.OAuth._ +import code.api.util.ApiRole.{CanCreateSystemView, CanDeleteSystemView, CanGetSystemView, CanUpdateSystemView} +import code.api.util.ErrorMessages.{UserHasMissingRoles, UserNotLoggedIn} +import code.api.v3_0_0.ViewJsonV300 +import code.api.v3_1_0.APIMethods310.Implementations3_1_0 +import code.api.v5_0_0.APIMethods500.Implementations5_0_0 +import code.entitlement.Entitlement +import code.setup.APIResponse +import code.views.MapperViews +import code.views.system.AccountAccess +import com.github.dwickern.macros.NameOf.nameOf +import com.openbankproject.commons.model.{CreateViewJson, ErrorMessage, UpdateViewJSON} +import com.openbankproject.commons.util.ApiVersion +import net.liftweb.mapper.By +import org.scalatest.Tag + +class SystemViewsTests extends V500ServerSetup { + override def beforeAll(): Unit = { + super.beforeAll() + } + + override def afterAll(): Unit = { + super.afterAll() + } + + /** + * 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_0_0.toString) + object ApiEndpoint1 extends Tag(nameOf(Implementations5_0_0.getSystemView)) + object ApiEndpoint2 extends Tag(nameOf(Implementations3_1_0.createSystemView)) + object ApiEndpoint3 extends Tag(nameOf(Implementations3_1_0.updateSystemView)) + object ApiEndpoint4 extends Tag(nameOf(Implementations3_1_0.deleteSystemView)) + + // Custom view, name starts from `_` + // System view, owner + val randomSystemViewId = APIUtil.generateUUID() + val postBodySystemViewJson = createSystemViewJson.copy(name=randomSystemViewId).copy(metadata_view = randomSystemViewId) + val systemViewId = MapperViews.getNewViewPermalink(postBodySystemViewJson.name) + + def getSystemView(viewId : String, consumerAndToken: Option[(Consumer, Token)]): APIResponse = { + val request = v5_0_0_Request / "system-views" / viewId <@(consumerAndToken) + makeGetRequest(request) + } + def postSystemView(view: CreateViewJson, consumerAndToken: Option[(Consumer, Token)]): APIResponse = { + val request = (v5_0_0_Request / "system-views").POST <@(consumerAndToken) + makePostRequest(request, write(view)) + } + def putSystemView(viewId : String, view: UpdateViewJSON, consumerAndToken: Option[(Consumer, Token)]): APIResponse = { + val request = (v5_0_0_Request / "system-views" / viewId).PUT <@(consumerAndToken) + makePutRequest(request, write(view)) + } + def deleteSystemView(viewId : String, consumerAndToken: Option[(Consumer, Token)]): APIResponse = { + val request = (v5_0_0_Request / "system-views" / viewId).DELETE <@(consumerAndToken) + makeDeleteRequest(request) + } + def createSystemView(viewId: String): Boolean = { + Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, CanCreateSystemView.toString) + val postBody = postBodySystemViewJson.copy(name=viewId).copy(metadata_view = viewId) + val response400 = postSystemView(postBody, user1) + response400.code == 201 + } + + + + feature(s"test $ApiEndpoint2 version $VersionOfApi - Unauthorized access") { + scenario("We will call the endpoint without user credentials", ApiEndpoint1, VersionOfApi) { + When(s"We make a request $ApiEndpoint2") + val response400 = postSystemView(postBodySystemViewJson, None) + Then("We should get a 401") + response400.code should equal(401) + response400.body.extract[ErrorMessage].message should equal(UserNotLoggedIn) + } + } + feature(s"test $ApiEndpoint2 version $VersionOfApi - Authorized access") { + scenario("We will call the endpoint without user credentials", ApiEndpoint1, VersionOfApi) { + When(s"We make a request $ApiEndpoint2") + val response400 = postSystemView(postBodySystemViewJson, user1) + Then("We should get a 403") + response400.code should equal(403) + response400.body.extract[ErrorMessage].message should equal(UserHasMissingRoles + CanCreateSystemView) + } + } + feature(s"test $ApiEndpoint2 version $VersionOfApi - Authorized access with proper Role") { + scenario("We will call the endpoint without user credentials", ApiEndpoint1, VersionOfApi) { + When(s"We make a request $ApiEndpoint2") + Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, CanCreateSystemView.toString) + val response400 = postSystemView(postBodySystemViewJson, user1) + Then("We should get a 201") + response400.code should equal(201) + response400.body.extract[ViewJsonV300] + } + } + + + feature(s"test $ApiEndpoint1 version $VersionOfApi - Unauthorized access") { + scenario("We will call the endpoint without user credentials", ApiEndpoint1, VersionOfApi) { + When(s"We make a request $ApiEndpoint1") + val response400 = getSystemView("", None) + Then("We should get a 401") + response400.code should equal(401) + response400.body.extract[ErrorMessage].message should equal(UserNotLoggedIn) + } + } + feature(s"test $ApiEndpoint1 version $VersionOfApi - Authorized access") { + scenario("We will call the endpoint without user credentials", ApiEndpoint1, VersionOfApi) { + When(s"We make a request $ApiEndpoint1") + val response400 = getSystemView("", user1) + Then("We should get a 403") + response400.code should equal(403) + response400.body.extract[ErrorMessage].message should equal(UserHasMissingRoles + CanGetSystemView) + } + } + feature(s"test $ApiEndpoint1 version $VersionOfApi - Authorized access with proper Role") { + scenario("We will call the endpoint without user credentials", ApiEndpoint1, VersionOfApi) { + val viewId = APIUtil.generateUUID() + createSystemView(viewId) + When(s"We make a request $ApiEndpoint1") + Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, CanGetSystemView.toString) + val response400 = getSystemView(viewId, user1) + Then("We should get a 200") + response400.code should equal(200) + response400.body.extract[ViewJsonV500] + } + } + + + feature(s"test $ApiEndpoint3 version $VersionOfApi - Unauthorized access") { + scenario("We will call the endpoint without user credentials", ApiEndpoint3, VersionOfApi) { + When(s"We make a request $ApiEndpoint3") + val response400 = getSystemView("", None) + Then("We should get a 401") + response400.code should equal(401) + response400.body.extract[ErrorMessage].message should equal(UserNotLoggedIn) + } + } + feature(s"test $ApiEndpoint3 version $VersionOfApi - Authorized access") { + scenario("We will call the endpoint without user credentials", ApiEndpoint3, VersionOfApi) { + When(s"We make a request $ApiEndpoint3") + val response400 = getSystemView("", user1) + Then("We should get a 403") + response400.code should equal(403) + response400.body.extract[ErrorMessage].message should equal(UserHasMissingRoles + CanGetSystemView) + } + } + feature(s"test $ApiEndpoint3 version $VersionOfApi - Authorized access with proper Role") { + scenario("we will update a view on a bank account", ApiEndpoint3, VersionOfApi) { + val updatedViewDescription = "aloha" + val updatedAliasToUse = "public" + val allowedActions = List("can_see_images", "can_delete_comment") + + def viewUpdateJson(originalView : ViewJsonV300) = { + //it's not perfect, assumes too much about originalView (i.e. randomView(true, "")) + UpdateViewJSON( + description = updatedViewDescription, + metadata_view = originalView.metadata_view, + is_public = originalView.is_public, + is_firehose = Some(true), + which_alias_to_use = updatedAliasToUse, + hide_metadata_if_alias_used = !originalView.hide_metadata_if_alias_used, + allowed_actions = allowedActions + ) + } + Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, CanCreateSystemView.toString) + Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, CanUpdateSystemView.toString) + + Given("A view exists") + val creationReply = postSystemView(postBodySystemViewJson, user1) + creationReply.code should equal (201) + val createdView : ViewJsonV300 = creationReply.body.extract[ViewJsonV300] + createdView.id should not startWith("_") + createdView.can_see_images should equal(true) + createdView.can_delete_comment should equal(true) + createdView.can_delete_physical_location should equal(true) + createdView.can_edit_owner_comment should equal(true) + createdView.description should not equal(updatedViewDescription) + createdView.hide_metadata_if_alias_used should equal(false) + + When("We use a valid access token and valid put json") + val reply = putSystemView(createdView.id, viewUpdateJson(createdView), user1) + Then("We should get back the updated view") + reply.code should equal (200) + val updatedView = reply.body.extract[ViewJsonV300] + updatedView.can_see_images should equal(true) + updatedView.can_delete_comment should equal(true) + updatedView.can_delete_physical_location should equal(false) + updatedView.can_edit_owner_comment should equal(false) + updatedView.description should equal(updatedViewDescription) + updatedView.hide_metadata_if_alias_used should equal(true) + updatedView.is_firehose should equal(Some(true)) + } + } + + + feature(s"test $ApiEndpoint4 version $VersionOfApi - Unauthorized access") { + scenario("We will call the endpoint without user credentials", ApiEndpoint4, VersionOfApi) { + When(s"We make a request $ApiEndpoint4") + val response400 = deleteSystemView("", None) + Then("We should get a 401") + response400.code should equal(401) + response400.body.extract[ErrorMessage].message should equal(UserNotLoggedIn) + } + } + feature(s"test $ApiEndpoint4 version $VersionOfApi - Authorized access") { + scenario("We will call the endpoint without user credentials", ApiEndpoint4, VersionOfApi) { + When(s"We make a request $ApiEndpoint4") + val response400 = deleteSystemView("", user1) + Then("We should get a 403") + response400.code should equal(403) + response400.body.extract[ErrorMessage].message should equal(UserHasMissingRoles + CanDeleteSystemView) + } + } + feature(s"test $ApiEndpoint4 version $VersionOfApi - Authorized access with proper Role") { + scenario("We will call the endpoint without user credentials", ApiEndpoint4, VersionOfApi) { + val viewId = APIUtil.generateUUID() + createSystemView(viewId) + Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, CanDeleteSystemView.toString) + When(s"We make a request $ApiEndpoint4") + val response400 = deleteSystemView(viewId, user1) + Then("We should get a 200") + response400.code should equal(200) + } + } + feature(s"test $ApiEndpoint4 version $VersionOfApi - Authorized access with proper Role in order to delete owner view") { + scenario("We will call the endpoint without user credentials", ApiEndpoint4, VersionOfApi) { + Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, CanDeleteSystemView.toString) + When(s"We make a request $ApiEndpoint4") + AccountAccess.findAll( + By(AccountAccess.view_id, SYSTEM_OWNER_VIEW_ID), + By(AccountAccess.user_fk, resourceUser1.id.get) + ).forall(_.delete_!) // Remove all rows assigned to the system owner view in order to delete it + val response400 = deleteSystemView(SYSTEM_OWNER_VIEW_ID, user1) + Then("We should get a 200") + response400.code should equal(200) + } + } +}