diff --git a/obp-api/src/main/resources/props/sample.props.template b/obp-api/src/main/resources/props/sample.props.template index 0498f5ad7d..8a1860ef49 100644 --- a/obp-api/src/main/resources/props/sample.props.template +++ b/obp-api/src/main/resources/props/sample.props.template @@ -881,7 +881,11 @@ database_messages_scheduler_interval=3600 # -- Consents --------------------------------------------- # In case isn't defined default value is "false" # consents.allowed=true -# consumer_validation_method_for_consent=CONSUMER_KEY_VALUE +# +# In order to pin a consent to a consumer we can use the property consumer_validation_method_for_consent +# Possibile values are: CONSUMER_CERTIFICATE, CONSUMER_KEY_VALUE, NONE +# consumer_validation_method_for_consent=CONSUMER_CERTIFICATE +# # consents.max_time_to_live=3600 # In case isn't defined default value is "false" # consents.sca.enabled=true diff --git a/obp-api/src/main/scala/code/api/util/APIUtil.scala b/obp-api/src/main/scala/code/api/util/APIUtil.scala index 73079dc6de..48c6f66a12 100644 --- a/obp-api/src/main/scala/code/api/util/APIUtil.scala +++ b/obp-api/src/main/scala/code/api/util/APIUtil.scala @@ -2971,8 +2971,14 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{ } else if (APIUtil.hasConsentJWT(reqHeaders)) { // Open Bank Project's Consent val consentValue = APIUtil.getConsentJWT(reqHeaders) Consent.getConsentJwtValueByConsentId(consentValue.getOrElse("")) match { - case Some(jwt) => // JWT value obtained via "Consent-Id" request header - Consent.applyRules(Some(jwt), cc) + case Some(consent) => // JWT value obtained via "Consent-Id" request header + Consent.applyRules( + Some(consent.jsonWebToken), + // Note: At this point we are getting the Consumer from the Consumer in the Consent. + // This may later be cross checked via the value in consumer_validation_method_for_consent. + // TODO: Get the source of truth for Consumer (e.g. CONSUMER_CERTIFICATE) as early as possible. + cc.copy(consumer = Consumers.consumers.vend.getConsumerByConsumerId(consent.consumerId)) + ) case _ => JwtUtil.checkIfStringIsJWTValue(consentValue.getOrElse("")).isDefined match { case true => // It's JWT obtained via "Consent-JWT" request header diff --git a/obp-api/src/main/scala/code/api/util/ConsentUtil.scala b/obp-api/src/main/scala/code/api/util/ConsentUtil.scala index 86949f8dd4..e7b1d46f2b 100644 --- a/obp-api/src/main/scala/code/api/util/ConsentUtil.scala +++ b/obp-api/src/main/scala/code/api/util/ConsentUtil.scala @@ -15,6 +15,7 @@ import code.context.{ConsentAuthContextProvider, UserAuthContextProvider} import code.entitlement.Entitlement import code.model.Consumer import code.users.Users +import code.util.Helper.MdcLoggable import code.util.HydraUtil import code.views.Views import com.nimbusds.jwt.JWTClaimsSet @@ -25,7 +26,7 @@ import net.liftweb.http.provider.HTTPParam import net.liftweb.json.JsonParser.ParseException import net.liftweb.json.{Extraction, MappingException, compactRender, parse} import net.liftweb.mapper.By -import net.liftweb.util.ControlHelpers +import net.liftweb.util.{ControlHelpers, Props} import sh.ory.hydra.model.OAuth2TokenIntrospection import scala.collection.immutable.{List, Nil} @@ -104,7 +105,7 @@ case class Consent(createdByUserId: String, } } -object Consent { +object Consent extends MdcLoggable { final lazy val challengeAnswerAtTestEnvironment = "123" @@ -126,7 +127,11 @@ object Consent { private def checkConsumerIsActiveAndMatched(consent: ConsentJWT, callContext: CallContext): Box[Boolean] = { Consumers.consumers.vend.getConsumerByConsumerId(consent.aud) match { case Full(consumerFromConsent) if consumerFromConsent.isActive.get == true => // Consumer is active - APIUtil.getPropsValue(nameOfProperty = "consumer_validation_method_for_consent", defaultValue = "CONSUMER_KEY_VALUE") match { + val validationMetod = APIUtil.getPropsValue(nameOfProperty = "consumer_validation_method_for_consent", defaultValue = "CONSUMER_CERTIFICATE") + if(validationMetod != "CONSUMER_CERTIFICATE" && Props.mode == Props.RunModes.Production) { + logger.warn(s"consumer_validation_method_for_consent is not set to CONSUMER_CERTIFICATE! The current value is: ${validationMetod}") + } + validationMetod match { case "CONSUMER_KEY_VALUE" => val requestHeaderConsumerKey = getConsumerKey(callContext.requestHeaders) requestHeaderConsumerKey match { @@ -272,7 +277,7 @@ object Consent { if (result.forall(_ == "Added")) Full(user) else Failure("Cannot add permissions to the user with id: " + user.userId) } - private def hasConsentInternalOldStyle(consentIdAsJwt: String, calContext: CallContext): Box[User] = { + private def applyConsentRulesCommonOldStyle(consentIdAsJwt: String, calContext: CallContext): Box[User] = { implicit val dateFormats = CustomJsonFormats.formats def applyConsentRules(consent: ConsentJWT): Box[User] = { @@ -316,7 +321,7 @@ object Consent { } } - private def hasConsentCommon(consentAsJwt: String, callContext: CallContext): Future[(Box[User], Option[CallContext])] = { + private def applyConsentRulesCommon(consentAsJwt: String, callContext: CallContext): Future[(Box[User], Option[CallContext])] = { implicit val dateFormats = CustomJsonFormats.formats def applyConsentRules(consent: ConsentJWT): Future[(Box[User], Option[CallContext])] = { @@ -351,13 +356,16 @@ object Consent { case Full(jsonAsString) => try { val consent = net.liftweb.json.parse(jsonAsString).extract[ConsentJWT] - checkConsent(consent, consentAsJwt, callContext) match { // Check is it Consent-JWT expired + // Set Consumer into Call Context + val consumer = Consumers.consumers.vend.getConsumerByConsumerId(consent.aud) + val updatedCallContext = callContext.copy(consumer = consumer) + checkConsent(consent, consentAsJwt, updatedCallContext) match { // Check is it Consent-JWT expired case (Full(true)) => // OK applyConsentRules(consent) case failure@Failure(_, _, _) => // Handled errors - Future(failure, Some(callContext)) + Future(failure, Some(updatedCallContext)) case _ => // Unexpected errors - Future(Failure(ErrorMessages.ConsentCheckExpiredIssue), Some(callContext)) + Future(Failure(ErrorMessages.ConsentCheckExpiredIssue), Some(updatedCallContext)) } } catch { // Possible exceptions case e: ParseException => Future(Failure("ParseException: " + e.getMessage), Some(callContext)) @@ -371,27 +379,20 @@ object Consent { } } - private def hasConsentOldStyle(consentIdAsJwt: String, callContext: CallContext): (Box[User], CallContext) = { - (hasConsentInternalOldStyle(consentIdAsJwt, callContext), callContext) - } - private def hasConsent(consentAsJwt: String, callContext: CallContext): Future[(Box[User], Option[CallContext])] = { - hasConsentCommon(consentAsJwt, callContext) - } - def applyRules(consentJwt: Option[String], callContext: CallContext): Future[(Box[User], Option[CallContext])] = { val allowed = APIUtil.getPropsAsBoolValue(nameOfProperty="consents.allowed", defaultValue=false) (consentJwt, allowed) match { - case (Some(consentId), true) => hasConsent(consentId, callContext) + case (Some(jwt), true) => applyConsentRulesCommon(jwt, callContext) case (_, false) => Future((Failure(ErrorMessages.ConsentDisabled), Some(callContext))) case (None, _) => Future((Failure(ErrorMessages.ConsentHeaderNotFound), Some(callContext))) } } - def getConsentJwtValueByConsentId(consentId: String): Option[String] = { + def getConsentJwtValueByConsentId(consentId: String): Option[MappedConsent] = { APIUtil.checkIfStringIsUUIDVersion1(consentId) match { case true => // String is a UUID Consents.consentProvider.vend.getConsentByConsentId(consentId) match { - case Full(consent) => Some(consent.jsonWebToken) + case Full(consent) => Some(consent) case _ => None // It's not valid UUID value } case false => None // It's not UUID at all @@ -407,7 +408,7 @@ object Consent { Full(Nil) } } - private def hasBerlinGroupConsentInternal(consentId: String, callContext: CallContext): Future[(Box[User], Option[CallContext])] = { + private def applyBerlinGroupConsentRulesCommon(consentId: String, callContext: CallContext): Future[(Box[User], Option[CallContext])] = { implicit val dateFormats = CustomJsonFormats.formats def applyConsentRules(consent: ConsentJWT): Future[(Box[User], Option[CallContext])] = { @@ -464,6 +465,9 @@ object Consent { // 1st we need to find a Consent via the field MappedConsent.consentId Consents.consentProvider.vend.getConsentByConsentId(consentId) match { case Full(storedConsent) => + // Set Consumer into Call Context + val consumer = Consumers.consumers.vend.getConsumerByConsumerId(storedConsent.consumerId) + val updatedCallContext = callContext.copy(consumer = consumer) // This function MUST be called only once per call. I.e. it's date dependent val (canBeUsed, currentCounterState) = checkFrequencyPerDay(storedConsent) if(canBeUsed) { @@ -471,28 +475,28 @@ object Consent { case Full(jsonAsString) => try { val consent = net.liftweb.json.parse(jsonAsString).extract[ConsentJWT] - checkConsent(consent, storedConsent.jsonWebToken, callContext) match { // Check is it Consent-JWT expired + checkConsent(consent, storedConsent.jsonWebToken, updatedCallContext) match { // Check is it Consent-JWT expired case (Full(true)) => // OK // Update MappedConsent.usesSoFarTodayCounter field Consents.consentProvider.vend.updateBerlinGroupConsent(consentId, currentCounterState + 1) applyConsentRules(consent) case failure@Failure(_, _, _) => // Handled errors - Future(failure, Some(callContext)) + Future(failure, Some(updatedCallContext)) case _ => // Unexpected errors - Future(Failure(ErrorMessages.ConsentCheckExpiredIssue), Some(callContext)) + Future(Failure(ErrorMessages.ConsentCheckExpiredIssue), Some(updatedCallContext)) } } catch { // Possible exceptions - case e: ParseException => Future(Failure("ParseException: " + e.getMessage), Some(callContext)) - case e: MappingException => Future(Failure("MappingException: " + e.getMessage), Some(callContext)) - case e: Exception => Future(Failure("parsing failed: " + e.getMessage), Some(callContext)) + case e: ParseException => Future(Failure("ParseException: " + e.getMessage), Some(updatedCallContext)) + case e: MappingException => Future(Failure("MappingException: " + e.getMessage), Some(updatedCallContext)) + case e: Exception => Future(Failure("parsing failed: " + e.getMessage), Some(updatedCallContext)) } case failure@Failure(_, _, _) => - Future(failure, Some(callContext)) + Future(failure, Some(updatedCallContext)) case _ => - Future(Failure("Cannot extract data from: " + consentId), Some(callContext)) + Future(Failure("Cannot extract data from: " + consentId), Some(updatedCallContext)) } } else { - Future(Failure(ErrorMessages.TooManyRequests + s" ${RequestHeader.`Consent-ID`}: $consentId"), Some(callContext)) + Future(Failure(ErrorMessages.TooManyRequests + s" ${RequestHeader.`Consent-ID`}: $consentId"), Some(updatedCallContext)) } case failure@Failure(_, _, _) => Future(failure, Some(callContext)) @@ -500,13 +504,10 @@ object Consent { Future(Failure(ErrorMessages.ConsentNotFound + s" ($consentId)"), Some(callContext)) } } - private def hasBerlinGroupConsent(consentId: String, callContext: CallContext): Future[(Box[User], Option[CallContext])] = { - hasBerlinGroupConsentInternal(consentId, callContext) - } def applyBerlinGroupRules(consentId: Option[String], callContext: CallContext): Future[(Box[User], Option[CallContext])] = { val allowed = APIUtil.getPropsAsBoolValue(nameOfProperty="consents.allowed", defaultValue=false) (consentId, allowed) match { - case (Some(consentId), true) => hasBerlinGroupConsent(consentId, callContext) + case (Some(consentId), true) => applyBerlinGroupConsentRulesCommon(consentId, callContext) case (_, false) => Future((Failure(ErrorMessages.ConsentDisabled), Some(callContext))) case (None, _) => Future((Failure(ErrorMessages.ConsentHeaderNotFound), Some(callContext))) } @@ -514,7 +515,7 @@ object Consent { def applyRulesOldStyle(consentId: Option[String], callContext: CallContext): (Box[User], CallContext) = { val allowed = APIUtil.getPropsAsBoolValue(nameOfProperty="consents.allowed", defaultValue=false) (consentId, allowed) match { - case (Some(consentId), true) => hasConsentOldStyle(consentId, callContext) + case (Some(consentId), true) => (applyConsentRulesCommonOldStyle(consentId, callContext), callContext) case (_, false) => (Failure(ErrorMessages.ConsentDisabled), callContext) case (None, _) => (Failure(ErrorMessages.ConsentHeaderNotFound), callContext) } diff --git a/obp-api/src/main/scala/code/api/v3_0_0/APIMethods300.scala b/obp-api/src/main/scala/code/api/v3_0_0/APIMethods300.scala index 992c76ed3f..a939fd8558 100644 --- a/obp-api/src/main/scala/code/api/v3_0_0/APIMethods300.scala +++ b/obp-api/src/main/scala/code/api/v3_0_0/APIMethods300.scala @@ -500,7 +500,7 @@ trait APIMethods300 { availablePrivateAccounts <- Views.views.vend.getPrivateBankAccountsFuture(u) (coreAccounts, callContext) <- getFilteredCoreAccounts(availablePrivateAccounts, req, callContext) } yield { - (JSONFactory300.createCoreAccountsByCoreAccountsJSON(coreAccounts), HttpCode.`200`(callContext)) + (JSONFactory300.createCoreAccountsByCoreAccountsJSON(coreAccounts, u), HttpCode.`200`(callContext)) } } } @@ -1710,7 +1710,7 @@ trait APIMethods300 { availablePrivateAccounts <- Views.views.vend.getPrivateBankAccountsFuture(u, bankId) (accounts, callContext) <- getFilteredCoreAccounts(availablePrivateAccounts, req, callContext) } yield { - (JSONFactory300.createCoreAccountsByCoreAccountsJSON(accounts), HttpCode.`200`(callContext)) + (JSONFactory300.createCoreAccountsByCoreAccountsJSON(accounts, u), HttpCode.`200`(callContext)) } } } diff --git a/obp-api/src/main/scala/code/api/v3_0_0/JSONFactory3.0.0.scala b/obp-api/src/main/scala/code/api/v3_0_0/JSONFactory3.0.0.scala index 20cefeceb8..3effc4857c 100644 --- a/obp-api/src/main/scala/code/api/v3_0_0/JSONFactory3.0.0.scala +++ b/obp-api/src/main/scala/code/api/v3_0_0/JSONFactory3.0.0.scala @@ -846,7 +846,7 @@ object JSONFactory300{ ) ) - def createCoreAccountsByCoreAccountsJSON(coreAccounts : List[CoreAccount]): CoreAccountsJsonV300 = + def createCoreAccountsByCoreAccountsJSON(coreAccounts : List[CoreAccount], user: User): CoreAccountsJsonV300 = CoreAccountsJsonV300(coreAccounts.map(coreAccount => CoreAccountJson( coreAccount.id, coreAccount.label, @@ -854,7 +854,7 @@ object JSONFactory300{ coreAccount.accountType, coreAccount.accountRoutings.map(accountRounting =>AccountRoutingJsonV121(accountRounting.scheme, accountRounting.address)), views = Views.views.vend - .assignedViewsForAccount(BankIdAccountId(BankId(coreAccount.bankId), AccountId(coreAccount.id))).filter(_.isPrivate) + .privateViewsUserCanAccessForAccount(user, BankIdAccountId(BankId(coreAccount.bankId), AccountId(coreAccount.id))).filter(_.isPrivate) .map(mappedView => ViewBasicV300( mappedView.viewId.value, 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 221925173d..88fefb88a4 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 @@ -45,6 +45,7 @@ import java.util.concurrent.ThreadLocalRandom import code.accountattribute.AccountAttributeX import code.api.Constant.SYSTEM_OWNER_VIEW_ID import code.api.util.FutureUtil.EndpointContext +import code.consumer.Consumers import code.util.Helper.booleanToFuture import code.views.system.{AccountAccess, ViewDefinition} @@ -991,7 +992,15 @@ trait APIMethods500 { case Props.RunModes.Test => Consent.challengeAnswerAtTestEnvironment case _ => SecureRandomUtil.numeric() } - createdConsent <- Future(Consents.consentProvider.vend.createObpConsent(user, challengeAnswer, Some(consentRequestId))) map { + consumer = Consumers.consumers.vend.getConsumerByConsumerId(calculatedConsumerId.getOrElse("None")) + createdConsent <- Future( + Consents.consentProvider.vend.createObpConsent( + user, + challengeAnswer, + Some(consentRequestId), + consumer + ) + ) map { i => connectorEmptyResponse(i, callContext) } 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 ec6f86e00e..b047863421 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 @@ -20,7 +20,7 @@ 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.{createAccountBalancesJson, createBalancesJson} +import code.api.v4_0_0.JSONFactory400.{createAccountBalancesJson, createBalancesJson, createNewCoreBankAccountJson} import code.api.v4_0_0.{JSONFactory400, PostAccountAccessJsonV400, PostApiCollectionJson400, RevokedJsonV400} import code.api.v5_1_0.JSONFactory510.{createRegulatedEntitiesJson, createRegulatedEntityJson} import code.atmattribute.AtmAttribute @@ -2155,6 +2155,37 @@ trait APIMethods510 { } } + + + staticResourceDocs += ResourceDoc( + getCoreAccountByIdThroughView, + implementedInApiVersion, + nameOf(getCoreAccountByIdThroughView), + "GET", + "/banks/BANK_ID/accounts/ACCOUNT_ID/views/VIEW_ID", + "Get Account by Id (Core) through the VIEW_ID", + s"""Information returned about the account through VIEW_ID : + |""".stripMargin, + EmptyBody, + moderatedCoreAccountJsonV400, + List($UserNotLoggedIn, $BankAccountNotFound,UnknownError), + apiTagAccount :: apiTagPSD2AIS :: apiTagPsd2 :: Nil + ) + lazy val getCoreAccountByIdThroughView : OBPEndpoint = { + case "banks" :: BankId(bankId) :: "accounts" :: AccountId(accountId) :: "views" :: ViewId(viewId) :: Nil JsonGet req => { + cc => implicit val ec = EndpointContext(Some(cc)) + for { + (user @Full(u), account, callContext) <- SS.userAccount + bankIdAccountId = BankIdAccountId(account.bankId, account.accountId) + view <- NewStyle.function.checkViewAccessAndReturnView(viewId , bankIdAccountId, user, callContext) + moderatedAccount <- NewStyle.function.moderatedBankAccountCore(account, view, user, callContext) + } yield { + val availableViews: List[View] = Views.views.vend.privateViewsUserCanAccessForAccount(u, BankIdAccountId(account.bankId, account.accountId)) + (createNewCoreBankAccountJson(moderatedAccount, availableViews), HttpCode.`200`(callContext)) + } + } + } + staticResourceDocs += ResourceDoc( getBankAccountBalances, implementedInApiVersion, diff --git a/obp-api/src/main/scala/code/consent/ConsentProvider.scala b/obp-api/src/main/scala/code/consent/ConsentProvider.scala index 1641adfa4e..2a3fdf52e6 100644 --- a/obp-api/src/main/scala/code/consent/ConsentProvider.scala +++ b/obp-api/src/main/scala/code/consent/ConsentProvider.scala @@ -21,7 +21,7 @@ trait ConsentProvider { def updateConsentStatus(consentId: String, status: ConsentStatus): Box[MappedConsent] def updateConsentUser(consentId: String, user: User): Box[MappedConsent] def getConsentsByUser(userId: String): List[MappedConsent] - def createObpConsent(user: User, challengeAnswer: String, consentRequestId:Option[String]): Box[MappedConsent] + def createObpConsent(user: User, challengeAnswer: String, consentRequestId:Option[String], consumer: Option[Consumer] = None): Box[MappedConsent] def setJsonWebToken(consentId: String, jwt: String): Box[MappedConsent] def revoke(consentId: String): Box[MappedConsent] def checkAnswer(consentId: String, challenge: String): Box[MappedConsent] diff --git a/obp-api/src/main/scala/code/consent/MappedConsent.scala b/obp-api/src/main/scala/code/consent/MappedConsent.scala index 6fe27b370f..37734773da 100644 --- a/obp-api/src/main/scala/code/consent/MappedConsent.scala +++ b/obp-api/src/main/scala/code/consent/MappedConsent.scala @@ -62,13 +62,14 @@ object MappedConsentProvider extends ConsentProvider { override def getConsentsByUser(userId: String): List[MappedConsent] = { MappedConsent.findAll(By(MappedConsent.mUserId, userId)) } - override def createObpConsent(user: User, challengeAnswer: String, consentRequestId:Option[String]): Box[MappedConsent] = { + override def createObpConsent(user: User, challengeAnswer: String, consentRequestId:Option[String], consumer: Option[Consumer]): Box[MappedConsent] = { tryo { val salt = BCrypt.gensalt() val challengeAnswerHashed = BCrypt.hashpw(challengeAnswer, salt).substring(0, 44) MappedConsent .create .mUserId(user.userId) + .mConsumerId(consumer.map(_.consumerId.get).getOrElse(null)) .mConsentRequestId(consentRequestId.getOrElse(null)) .mChallenge(challengeAnswerHashed) .mSalt(salt) diff --git a/obp-api/src/test/resources/cert/client.pfx b/obp-api/src/test/resources/cert/client.pfx deleted file mode 100644 index cb1159850c..0000000000 Binary files a/obp-api/src/test/resources/cert/client.pfx and /dev/null differ diff --git a/obp-api/src/test/resources/cert/localhost_SAN_dns_ip.pfx b/obp-api/src/test/resources/cert/localhost_SAN_dns_ip.pfx deleted file mode 100644 index 548fa6e824..0000000000 Binary files a/obp-api/src/test/resources/cert/localhost_SAN_dns_ip.pfx and /dev/null differ diff --git a/obp-api/src/test/resources/cert/localhost_san_dns_ip.pfx b/obp-api/src/test/resources/cert/localhost_san_dns_ip.pfx new file mode 100644 index 0000000000..4f1cf33444 Binary files /dev/null and b/obp-api/src/test/resources/cert/localhost_san_dns_ip.pfx differ diff --git a/obp-api/src/test/scala/code/api/v5_1_0/AccountAccessTest.scala b/obp-api/src/test/scala/code/api/v5_1_0/AccountAccessTest.scala index 98924bb889..f12d30b093 100644 --- a/obp-api/src/test/scala/code/api/v5_1_0/AccountAccessTest.scala +++ b/obp-api/src/test/scala/code/api/v5_1_0/AccountAccessTest.scala @@ -5,10 +5,11 @@ import code.api.ResourceDocs1_4_0.SwaggerDefinitionsJSON import code.api.ResourceDocs1_4_0.SwaggerDefinitionsJSON.createViewJsonV300 import code.api.util.APIUtil.OAuth._ import code.api.util.ApiRole -import code.api.util.ErrorMessages.{UserLacksPermissionCanGrantAccessToCustomViewForTargetAccount, UserLacksPermissionCanGrantAccessToSystemViewForTargetAccount, UserLacksPermissionCanGrantAccessToViewForTargetAccount, UserLacksPermissionCanRevokeAccessToCustomViewForTargetAccount, UserLacksPermissionCanRevokeAccessToSystemViewForTargetAccount, UserLacksPermissionCanRevokeAccessToViewForTargetAccount, UserNotLoggedIn} +import code.api.util.ApiRole.CanSeeAccountAccessForAnyUser +import code.api.util.ErrorMessages._ import code.api.v3_0_0.ViewJsonV300 import code.api.v3_1_0.CreateAccountResponseJsonV310 -import code.api.v4_0_0.RevokedJsonV400 +import code.api.v4_0_0.{AccountsMinimalJson400, RevokedJsonV400} import code.api.v5_1_0.OBPAPI5_1_0.Implementations5_1_0 import code.entitlement.Entitlement import com.github.dwickern.macros.NameOf.nameOf @@ -30,6 +31,7 @@ class AccountAccessTest extends V510ServerSetup { object ApiEndpoint1 extends Tag(nameOf(Implementations5_1_0.grantUserAccessToViewById)) object ApiEndpoint2 extends Tag(nameOf(Implementations5_1_0.revokeUserAccessToViewById)) object ApiEndpoint3 extends Tag(nameOf(Implementations5_1_0.createUserWithAccountAccessById)) + object GetAccountAccessByUserId extends Tag(nameOf(Implementations5_1_0.getAccountAccessByUserId)) lazy val bankId = randomBankId @@ -54,6 +56,34 @@ class AccountAccessTest extends V510ServerSetup { createViewViaEndpoint(bankId, accountId, postBodyViewJson, user1) } + + + feature(s"test ${GetAccountAccessByUserId.name}") { + scenario(s"We will test ${GetAccountAccessByUserId.name}", GetAccountAccessByUserId, VersionOfApi) { + + val requestGet = (v5_1_0_Request / "users" / resourceUser2.userId / "account-access").GET + + // Anonymous call fails + val anonymousResponseGet = makeGetRequest(requestGet) + anonymousResponseGet.code should equal(401) + anonymousResponseGet.body.extract[ErrorMessage].message should equal(UserNotLoggedIn) + + // Call endpoint without the entitlement + val badResponseGet = makeGetRequest(requestGet <@ user1) + badResponseGet.code should equal(403) + val errorMessage = badResponseGet.body.extract[ErrorMessage].message + errorMessage contains UserHasMissingRoles should be (true) + errorMessage contains CanSeeAccountAccessForAnyUser.toString() should be (true) + + // All good + Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, CanSeeAccountAccessForAnyUser.toString()) + val goodResponseGet = makeGetRequest(requestGet <@ user1) + goodResponseGet.code should equal(200) + goodResponseGet.body.extract[AccountsMinimalJson400] + + } + } + feature(s"test $ApiEndpoint1 Authorized access") { scenario("We will call the endpoint without user credentials", ApiEndpoint1, VersionOfApi) { diff --git a/obp-api/src/test/scala/code/api/v5_1_0/AccountTest.scala b/obp-api/src/test/scala/code/api/v5_1_0/AccountTest.scala index 131f61650b..59cef9533a 100644 --- a/obp-api/src/test/scala/code/api/v5_1_0/AccountTest.scala +++ b/obp-api/src/test/scala/code/api/v5_1_0/AccountTest.scala @@ -20,31 +20,18 @@ class AccountTest extends V510ServerSetup { * This is made possible by the scalatest maven plugin */ object VersionOfApi extends Tag(ApiVersion.v5_1_0.toString) - object GetAccountAccessByUserId extends Tag(nameOf(Implementations5_1_0.getAccountAccessByUserId)) + object GetCoreAccountByIdThroughView extends Tag(nameOf(Implementations5_1_0.getCoreAccountByIdThroughView)) - feature(s"test ${GetAccountAccessByUserId.name}") { - scenario(s"We will test ${GetAccountAccessByUserId.name}", GetAccountAccessByUserId, VersionOfApi) { + feature(s"test ${GetCoreAccountByIdThroughView.name}") { + scenario(s"We will test ${GetCoreAccountByIdThroughView.name}", GetCoreAccountByIdThroughView, VersionOfApi) { - val requestGet = (v5_1_0_Request / "users" / resourceUser2.userId / "account-access").GET + val requestGet = (v5_1_0_Request / "banks" / "BANK_ID" / "accounts" / "ACCOUNT_ID"/ "views" / "VIEW_ID").GET // Anonymous call fails val anonymousResponseGet = makeGetRequest(requestGet) anonymousResponseGet.code should equal(401) anonymousResponseGet.body.extract[ErrorMessage].message should equal(UserNotLoggedIn) - // Call endpoint without the entitlement - val badResponseGet = makeGetRequest(requestGet <@ user1) - badResponseGet.code should equal(403) - val errorMessage = badResponseGet.body.extract[ErrorMessage].message - errorMessage contains UserHasMissingRoles should be (true) - errorMessage contains CanSeeAccountAccessForAnyUser.toString() should be (true) - - // All good - Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, CanSeeAccountAccessForAnyUser.toString()) - val goodResponseGet = makeGetRequest(requestGet <@ user1) - goodResponseGet.code should equal(200) - goodResponseGet.body.extract[AccountsMinimalJson400] - } }