Merge pull request #2440 from constantine2nd/develop

getConsentsAtBank v5.1.0
This commit is contained in:
Simon Redfern 2024-11-07 13:30:15 +01:00 committed by GitHub
commit bc4ad2c773
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 190 additions and 10 deletions

View File

@ -11,7 +11,7 @@ import code.api.dynamic.endpoint.helper.practise.PractiseEndpoint
import code.api.util.APIUtil.{defaultJValue, _}
import code.api.util.ApiRole._
import code.api.util.ExampleValue._
import code.api.util.{APIUtil, ApiRole, ApiTrigger, ExampleValue}
import code.api.util.{APIUtil, ApiRole, ApiTrigger, ConsentJWT, CustomJsonFormats, ExampleValue}
import code.api.v2_2_0.JSONFactory220.{AdapterImplementationJson, MessageDocJson, MessageDocsJson}
import code.api.v3_0_0.JSONFactory300.createBranchJsonV300
import code.api.v3_0_0.custom.JSONFactoryCustom300
@ -39,7 +39,7 @@ import java.net.URLEncoder
import code.api.v5_1_0.{AtmsJsonV510, CustomViewJsonV510, _}
import code.endpointMapping.EndpointMappingCommons
import net.liftweb.json.Extraction
import net.liftweb.json.{Extraction, parse}
import scala.collection.immutable.List
@ -55,6 +55,8 @@ object SwaggerDefinitionsJSON {
implicit def convertStringToBoolean(value:String) = value.toBoolean
implicit val formats = CustomJsonFormats.formats
lazy val regulatedEntitiesJsonV510: RegulatedEntitiesJsonV510 = RegulatedEntitiesJsonV510(List(regulatedEntityJsonV510))
lazy val regulatedEntityJsonV510: RegulatedEntityJsonV510 = RegulatedEntityJsonV510(
entity_id = "0af807d7-3c39-43ef-9712-82bcfde1b9ca",
@ -4200,7 +4202,44 @@ object SwaggerDefinitionsJSON {
status = ConsentStatus.INITIATED.toString,
api_standard = "Berlin Group",
api_version = "v1.3"
)
)
val allConsentJsonV510 = AllConsentJsonV510(
consent_reference_id = "9d429899-24f5-42c8-8565-943ffa6a7945",
consumer_id = consumerIdExample.value,
created_by_user_id = userIdExample.value,
last_action_date = dateExample.value,
last_usage_date = dateTimeExample.value,
status = ConsentStatus.INITIATED.toString,
api_standard = "Berlin Group",
api_version = "v1.3",
jwt = "eyJhbGciOiJIUzI1NiJ9.eyJlbnRpdGxlbWVudHMiOltdLCJjcmVhdGVkQnlVc2VySWQiOiIiLCJzdWIiOiJmY2YzNDZkMi0xNTNiLTQ0MzAtOWE4Zi1mMzU3Njg1MzM5ODciLCJhdWQiOiIyNjY0NjUwYy04MDkwLTQ4MWUtOGJkOC0wM2E5MmY5Yzg3ZWEiLCJuYmYiOjE3MzAzNzMyNzEsImFjY2VzcyI6eyJhY2NvdW50cyI6W3siaWJhbiI6IlJTMzUyNjAwMDU2MDEwMDE2MTEzNzkifV19LCJpc3MiOiJodHRwczovLzEyNy4wLjAuMTo4MDgwIiwiZXhwIjoxNzMwOTM3NjAwLCJpYXQiOjE3MzAzNzMyNzEsImp0aSI6ImQzM2Y3NDYzLWVlNDktNGU4YS04YTkyLTYxMzhkYzE4M2QxNiIsInZpZXdzIjpbeyJiYW5rX2lkIjoibmxia2IiLCJhY2NvdW50X2lkIjoiOTUzODkyOTctNDVjNC00MGViLTllZmQtMzMxYmExOTQzZGE0Iiwidmlld19pZCI6IlJlYWRBY2NvdW50c0Jlcmxpbkdyb3VwIn1dfQ.SXE4W34596lrSXqZrA8cvQs_fvhjWYilU8VDpXZ3C3Y",
jwt_payload = parse("""{
"createdByUserId": "",
"sub": "fcf346d2-153b-4430-9a8f-f35768533987",
"iss": "https://127.0.0.1:8080",
"aud": "2664650c-8090-481e-8bd8-03a92f9c87ea",
"jti": "d33f7463-ee49-4e8a-8a92-6138dc183d16",
"iat": 1730373271,
"nbf": 1730373271,
"exp": 1730937600,
"entitlements": [],
"views": [ {
"bank_id": "nlbkb",
"account_id": "95389297-45c4-40eb-9efd-331ba1943da4",
"view_id": "ReadAccountsBerlinGroup"
}
],
"access": {
"accounts": [ {
"iban": "RS35260005601001611379"
}
]
}
}""").extractOpt[ConsentJWT],
)
val consentsJsonV510 = ConsentsJsonV510(List(allConsentJsonV510))
val revokedConsentJsonV310 = ConsentJsonV310(
consent_id = "9d429899-24f5-42c8-8565-943ffa6a7945",
jwt = "eyJhbGciOiJIUzI1NiJ9.eyJlbnRpdGxlbWVudHMiOltdLCJjcmVhdGVkQnlVc2VySWQiOiJhYjY1MzlhOS1iMTA1LTQ0ODktYTg4My0wYWQ4ZDZjNjE2NTciLCJzdWIiOiIyMWUxYzhjYy1mOTE4LTRlYWMtYjhlMy01ZTVlZWM2YjNiNGIiLCJhdWQiOiJlanpuazUwNWQxMzJyeW9tbmhieDFxbXRvaHVyYnNiYjBraWphanNrIiwibmJmIjoxNTUzNTU0ODk5LCJpc3MiOiJodHRwczpcL1wvd3d3Lm9wZW5iYW5rcHJvamVjdC5jb20iLCJleHAiOjE1NTM1NTg0OTksImlhdCI6MTU1MzU1NDg5OSwianRpIjoiMDlmODhkNWYtZWNlNi00Mzk4LThlOTktNjYxMWZhMWNkYmQ1Iiwidmlld3MiOlt7ImFjY291bnRfaWQiOiJtYXJrb19wcml2aXRlXzAxIiwiYmFua19pZCI6ImdoLjI5LnVrLngiLCJ2aWV3X2lkIjoib3duZXIifSx7ImFjY291bnRfaWQiOiJtYXJrb19wcml2aXRlXzAyIiwiYmFua19pZCI6ImdoLjI5LnVrLngiLCJ2aWV3X2lkIjoib3duZXIifV19.8cc7cBEf2NyQvJoukBCmDLT7LXYcuzTcSYLqSpbxLp4",
@ -4240,9 +4279,9 @@ object SwaggerDefinitionsJSON {
val consentsJsonV310 = ConsentsJsonV310(List(consentJsonV310))
val consentsJsonV400 = ConsentsJsonV400(List(consentJsonV400))
val consentInfosJsonV400 = ConsentInfosJsonV400(List(consentInfoJsonV400))
val oAuth2ServerJWKURIJson = OAuth2ServerJWKURIJson("https://www.googleapis.com/oauth2/v3/certs")
val oAuth2ServerJwksUrisJson = OAuth2ServerJwksUrisJson(List(oAuth2ServerJWKURIJson))

View File

@ -1090,6 +1090,7 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
value <- tryo(values.head.toBoolean) ?~! FilterIsDeletedFormatError
deleted = OBPIsDeleted(value)
} yield deleted
case "status" => Full(OBPStatus(values.head))
case "consumer_id" => Full(OBPConsumerId(values.head))
case "user_id" => Full(OBPUserId(values.head))
case "bank_id" => Full(OBPBankId(values.head))
@ -1134,6 +1135,7 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
limit <- getLimit(httpParams)
offset <- getOffset(httpParams)
//all optional fields
status <- getHttpParamValuesByName(httpParams,"status")
anon <- getHttpParamValuesByName(httpParams,"anon")
deletedStatus <- getHttpParamValuesByName(httpParams,"is_deleted")
consumerId <- getHttpParamValuesByName(httpParams,"consumer_id")
@ -1174,7 +1176,7 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
val ordering = OBPOrdering(sortBy, sortDirection)
//This guarantee the order
List(limit, offset, ordering, fromDate, toDate,
anon, consumerId, userId, url, appName, implementedByPartialFunction, implementedInVersion,
anon, status, consumerId, userId, url, appName, implementedByPartialFunction, implementedInVersion,
verb, correlationId, duration, excludeAppNames, excludeUrlPattern, excludeImplementedByPartialfunctions,
includeAppNames, includeUrlPattern, includeImplementedByPartialfunctions,
connectorName,functionName, bankId, accountId, customerId, lockedStatus, deletedStatus
@ -1209,6 +1211,7 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
val limit = getHttpRequestUrlParam(httpRequestUrl,"limit")
val offset = getHttpRequestUrlParam(httpRequestUrl,"offset")
val anon = getHttpRequestUrlParam(httpRequestUrl,"anon")
val status = getHttpRequestUrlParam(httpRequestUrl,"status")
val isDeleted = getHttpRequestUrlParam(httpRequestUrl, "is_deleted")
val consumerId = getHttpRequestUrlParam(httpRequestUrl,"consumer_id")
val userId = getHttpRequestUrlParam(httpRequestUrl, "user_id")
@ -1241,7 +1244,7 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
Full(List(
HTTPParam("sort_direction",sortDirection), HTTPParam("from_date",fromDate), HTTPParam("to_date", toDate), HTTPParam("limit",limit), HTTPParam("offset",offset),
HTTPParam("anon", anon), HTTPParam("consumer_id", consumerId), HTTPParam("user_id", userId), HTTPParam("url", url), HTTPParam("app_name", appName),
HTTPParam("anon", anon), HTTPParam("status", status), HTTPParam("consumer_id", consumerId), HTTPParam("user_id", userId), HTTPParam("url", url), HTTPParam("app_name", appName),
HTTPParam("implemented_by_partial_function",implementedByPartialFunction), HTTPParam("implemented_in_version",implementedInVersion), HTTPParam("verb", verb),
HTTPParam("correlation_id", correlationId), HTTPParam("duration", duration), HTTPParam("exclude_app_names", excludeAppNames),
HTTPParam("exclude_url_patterns", excludeUrlPattern),HTTPParam("exclude_implemented_by_partial_functions", excludeImplementedByPartialfunctions),

View File

@ -973,6 +973,10 @@ object ApiRole extends MdcLoggable{
case class CanRevokeConsentAtBank(requiresBankId: Boolean = true) extends ApiRole
lazy val canRevokeConsentAtBank = CanRevokeConsentAtBank()
case class CanGetConsentsAtOneBank(requiresBankId: Boolean = true) extends ApiRole
lazy val canGetConsentsAtOneBank = CanGetConsentsAtOneBank()
case class CanGetConsentsAtAnyBank(requiresBankId: Boolean = false) extends ApiRole
lazy val canGetConsentsAtAnyBank = CanGetConsentsAtAnyBank()
case class CanSeeAccountAccessForAnyUser(requiresBankId: Boolean = false) extends ApiRole
lazy val canSeeAccountAccessForAnyUser = CanSeeAccountAccessForAnyUser()

View File

@ -27,6 +27,7 @@ case class OBPToDate(value: Date) extends OBPQueryParam
case class OBPOrdering(field: Option[String], order: OBPOrder) extends OBPQueryParam
case class OBPConsumerId(value: String) extends OBPQueryParam
case class OBPUserId(value: String) extends OBPQueryParam
case class OBPStatus(value: String) extends OBPQueryParam
case class OBPBankId(value: String) extends OBPQueryParam
case class OBPAccountId(value: String) extends OBPQueryParam
case class OBPUrl(value: String) extends OBPQueryParam

View File

@ -26,7 +26,7 @@ import code.api.v3_1_0.JSONFactory310.createBadLoginStatusJson
import code.api.v4_0_0.JSONFactory400.{createAccountBalancesJson, createBalancesJson, createNewCoreBankAccountJson}
import code.api.v4_0_0.{JSONFactory400, PostAccountAccessJsonV400, PostApiCollectionJson400, RevokedJsonV400}
import code.api.v5_0_0.{JSONFactory500, PostConsentRequestJsonV500}
import code.api.v5_1_0.JSONFactory510.{createConsentsInfoJsonV510, createRegulatedEntitiesJson, createRegulatedEntityJson}
import code.api.v5_1_0.JSONFactory510.{createConsentsInfoJsonV510, createConsentsJsonV510, createRegulatedEntitiesJson, createRegulatedEntityJson}
import code.atmattribute.AtmAttribute
import code.bankconnectors.Connector
import code.consent.{ConsentRequests, Consents}
@ -1014,6 +1014,61 @@ trait APIMethods510 {
}
staticResourceDocs += ResourceDoc(
getConsentsAtBank,
implementedInApiVersion,
nameOf(getConsentsAtBank),
"GET",
"/management/consents/banks/BANK_ID",
"Get Consents at Bank",
s"""
|
|This endpoint gets the Consents at Bank by BANK_ID.
|
|${authenticationRequiredMessage(true)}
|
|1 limit (for pagination: defaults to 50) eg:limit=200
|
|2 offset (for pagination: zero index, defaults to 0) eg: offset=10
|
|3 consumer_id (ignore if omitted)
|
|4 user_id (ignore if omitted)
|
|5 status (ignore if omitted)
|
|eg: /management/consents/banks/BANK_ID?&consumer_id=78&limit=10&offset=10
|
""".stripMargin,
EmptyBody,
consentsJsonV510,
List(
$UserNotLoggedIn,
$BankNotFound,
UnknownError
),
List(apiTagConsent, apiTagPSD2AIS, apiTagPsd2),
Some(List(canGetConsentsAtOneBank, canGetConsentsAtAnyBank)),
)
lazy val getConsentsAtBank: OBPEndpoint = {
case "management" :: "consents" :: "banks" :: BankId(bankId) :: Nil JsonGet _ => {
cc =>
implicit val ec = EndpointContext(Some(cc))
for {
httpParams <- NewStyle.function.extractHttpParamsFromUrl(cc.url)
(obpQueryParams, callContext) <- createQueriesByHttpParamsFuture(httpParams, cc.callContext)
consents <- Future {
Consents.consentProvider.vend.getConsents(obpQueryParams)
}
} yield {
val consentsOfBank = Consent.filterByBankId(consents, bankId)
(createConsentsJsonV510(consentsOfBank), HttpCode.`200`(callContext))
}
}
}
staticResourceDocs += ResourceDoc(
getConsentByConsentId,
implementedInApiVersion,

View File

@ -137,6 +137,19 @@ case class ConsentInfoJsonV510(consent_id: String,
)
case class ConsentsInfoJsonV510(consents: List[ConsentInfoJsonV510])
case class AllConsentJsonV510(consent_reference_id: String,
consumer_id: String,
created_by_user_id: String,
status: String,
last_action_date: String,
last_usage_date: String,
jwt: String,
jwt_payload: Box[ConsentJWT],
api_standard: String,
api_version: String,
)
case class ConsentsJsonV510(consents: List[AllConsentJsonV510])
case class CurrencyJsonV510(alphanumeric_code: String)
case class CurrenciesJsonV510(currencies: List[CurrencyJsonV510])
@ -711,6 +724,25 @@ object JSONFactory510 extends CustomJsonFormats {
}
)
}
def createConsentsJsonV510(consents: List[MappedConsent]): ConsentsJsonV510 = {
ConsentsJsonV510(
consents.map { c =>
val jwtPayload: Box[ConsentJWT] = JwtUtil.getSignedPayloadAsJson(c.jsonWebToken).map(parse(_).extract[ConsentJWT])
AllConsentJsonV510(
consent_reference_id = c.id.get.toString,
consumer_id = c.consumerId,
created_by_user_id = c.userId,
status = c.status,
last_action_date = if (c.lastActionDate != null) new SimpleDateFormat(DateWithDay).format(c.lastActionDate) else null,
last_usage_date = if (c.usesSoFarTodayCounterUpdatedAt != null) new SimpleDateFormat(DateWithSeconds).format(c.usesSoFarTodayCounterUpdatedAt) else null,
jwt = c.jsonWebToken,
jwt_payload = jwtPayload,
api_standard = c.apiStandard,
api_version = c.apiVersion
)
}
)
}
def getApiInfoJSON(apiVersion : ApiVersion, apiVersionStatus: String) = {
val organisation = APIUtil.getPropsValue("hosted_by.organisation", "TESOBE")

View File

@ -1,5 +1,6 @@
package code.consent
import code.api.util.OBPQueryParam
import com.openbankproject.commons.model.User
import net.liftweb.common.Box
import net.liftweb.util.SimpleInjector
@ -16,6 +17,7 @@ object Consents extends SimpleInjector {
}
trait ConsentProvider {
def getConsents(queryParams: List[OBPQueryParam]): List[MappedConsent]
def getConsentByConsentId(consentId: String): Box[MappedConsent]
def getConsentByConsentRequestId(consentRequestId: String): Box[MappedConsent]
def updateConsentStatus(consentId: String, status: ConsentStatus): Box[MappedConsent]

View File

@ -1,8 +1,7 @@
package code.consent
import java.util.Date
import code.api.util.{APIUtil, Consent, ErrorMessages, SecureRandomUtil}
import code.api.util.{APIUtil, Consent, ErrorMessages, OBPStatus, OBPOffset, OBPQueryParam, OBPUserId, OBPLimit, OBPConsumerId, SecureRandomUtil}
import code.consent.ConsentStatus.ConsentStatus
import code.model.Consumer
import code.util.MappedUUID
@ -63,6 +62,29 @@ object MappedConsentProvider extends ConsentProvider {
override def getConsentsByUser(userId: String): List[MappedConsent] = {
MappedConsent.findAll(By(MappedConsent.mUserId, userId))
}
private def getQueryParams(queryParams: List[OBPQueryParam]) = {
val limit = queryParams.collect { case OBPLimit(value) => MaxRows[MappedConsent](value) }.headOption
val offset = queryParams.collect { case OBPOffset(value) => StartAt[MappedConsent](value) }.headOption
// he optional variables:
val consumerId = queryParams.collect { case OBPConsumerId(value) => By(MappedConsent.mConsumerId, value)}.headOption
val userId = queryParams.collect { case OBPUserId(value) => By(MappedConsent.mUserId, value)}.headOption
val status = queryParams.collect { case OBPStatus(value) => By(MappedConsent.mStatus, value.toUpperCase())}.headOption
Seq(
offset.toSeq,
limit.toSeq,
status.toSeq,
userId.toSeq,
consumerId.toSeq
).flatten
}
override def getConsents(queryParams: List[OBPQueryParam]): List[MappedConsent] = {
val optionalParams = getQueryParams(queryParams)
MappedConsent.findAll(optionalParams: _*)
}
override def createObpConsent(user: User, challengeAnswer: String, consentRequestId:Option[String], consumer: Option[Consumer]): Box[MappedConsent] = {
tryo {
val salt = BCrypt.gensalt()

View File

@ -66,6 +66,7 @@ class ConsentsTest extends V510ServerSetup with PropsReset{
object ApiEndpoint6 extends Tag(nameOf(Implementations5_1_0.revokeConsentAtBank))
object ApiEndpoint7 extends Tag(nameOf(Implementations5_1_0.getConsentByConsentId))
object ApiEndpoint8 extends Tag(nameOf(Implementations5_1_0.getMyConsents))
object ApiEndpoint9 extends Tag(nameOf(Implementations5_1_0.getConsentsAtBank))
lazy val entitlements = List(PostConsentEntitlementJsonV310("", CanGetAnyUser.toString()))
lazy val bankId = testBankId1.value
@ -87,6 +88,7 @@ class ConsentsTest extends V510ServerSetup with PropsReset{
def getConsentByIdUrl(requestId:String) = (v5_1_0_Request / "consumer" / "current" / "consents" / requestId ).GET<@(user1)
def revokeConsentUrl(consentId: String) = (v5_1_0_Request / "banks" / bankId / "consents" / consentId).DELETE
def getMyConsents(consentId: String) = (v5_1_0_Request / "banks" / bankId / "my" / "consents").GET
def getConsentsAtBAnk(consentId: String) = (v5_1_0_Request / "management"/ "consents" / "banks" / bankId).GET
feature(s"test $ApiEndpoint6 version $VersionOfApi - Unauthorized access") {
scenario("We will call the endpoint without user credentials", ApiEndpoint6, VersionOfApi) {
@ -124,6 +126,26 @@ class ConsentsTest extends V510ServerSetup with PropsReset{
response510.code should equal(200)
}
}
feature(s"test $ApiEndpoint9 version $VersionOfApi - Unautenticated access") {
scenario("We will call the endpoint without user credentials", ApiEndpoint9, VersionOfApi) {
When(s"We make a request $ApiEndpoint9")
val response510 = makeGetRequest(getConsentsAtBAnk("whatever"))
Then("We should get a 401")
response510.code should equal(401)
response510.body.extract[ErrorMessage].message should equal(UserNotLoggedIn)
}
}
feature(s"test $ApiEndpoint9 version $VersionOfApi - Autenticated access") {
scenario("We will call the endpoint with user credentials", ApiEndpoint9, VersionOfApi) {
When(s"We make a request $ApiEndpoint1")
val response510 = makeGetRequest(getConsentsAtBAnk("whatever") <@ (user1))
Then("We should get a 403")
response510.code should equal(403)
response510.body.extract[ErrorMessage].message contains (UserHasMissingRoles + s"$CanGetConsentsAtOneBank or $CanGetConsentsAtAnyBank") should be(true)
}
}
feature(s"Create/Use/Revoke Consent $VersionOfApi") {
scenario("We will call the Create, Get and Delete endpoints with user credentials ", ApiEndpoint1, ApiEndpoint2, ApiEndpoint3, ApiEndpoint4, ApiEndpoint5, ApiEndpoint6, ApiEndpoint7, VersionOfApi) {