mirror of
https://github.com/OpenBankProject/OBP-API.git
synced 2026-02-06 12:56:51 +00:00
commit
795d70ee86
@ -594,7 +594,7 @@ class Boot extends MdcLoggable {
|
||||
LiftRules.explicitlyParsedSuffixes = Helpers.knownSuffixes &~ (Set("com"))
|
||||
|
||||
val locale = I18NUtil.getDefaultLocale()
|
||||
Locale.setDefault(locale)
|
||||
// Locale.setDefault(locale) // TODO Explain why this line of code introduce weird side effects
|
||||
logger.info("Default Project Locale is :" + locale)
|
||||
|
||||
// Cookie name
|
||||
|
||||
@ -964,7 +964,7 @@ object ResourceDocsAPIMethodsUtil extends MdcLoggable{
|
||||
Some(resourceDocTags)
|
||||
}
|
||||
}
|
||||
logger.info(s"tagsOption is $tags")
|
||||
logger.debug(s"tagsOption is $tags")
|
||||
|
||||
// So we can produce a reduced list of resource docs to prevent manual editing of swagger files.
|
||||
val rawPartialFunctionNames = S.param("functions")
|
||||
@ -987,31 +987,31 @@ object ResourceDocsAPIMethodsUtil extends MdcLoggable{
|
||||
Some(pfns)
|
||||
}
|
||||
}
|
||||
logger.info(s"partialFunctionNames is $partialFunctionNames")
|
||||
logger.debug(s"partialFunctionNames is $partialFunctionNames")
|
||||
|
||||
// So we can produce a reduced list of resource docs to prevent manual editing of swagger files.
|
||||
val languageParam = for {
|
||||
x <- S.param("language").or(S.param(PARAM_LOCALE))
|
||||
y <- stringToLanguageParam(x)
|
||||
} yield y
|
||||
logger.info(s"languageParam is $languageParam")
|
||||
logger.debug(s"languageParam is $languageParam")
|
||||
|
||||
// So we can produce a reduced list of resource docs to prevent manual editing of swagger files.
|
||||
val contentParam = for {
|
||||
x <- S.param("content")
|
||||
y <- stringToContentParam(x)
|
||||
} yield y
|
||||
logger.info(s"content is $contentParam")
|
||||
logger.debug(s"content is $contentParam")
|
||||
|
||||
val apiCollectionIdParam = for {
|
||||
x <- S.param("api-collection-id")
|
||||
} yield x
|
||||
logger.info(s"apiCollectionIdParam is $apiCollectionIdParam")
|
||||
logger.debug(s"apiCollectionIdParam is $apiCollectionIdParam")
|
||||
|
||||
val cacheModifierParam = for {
|
||||
x <- S.param("cache-modifier")
|
||||
} yield x
|
||||
logger.info(s"cacheModifierParam is $cacheModifierParam")
|
||||
logger.debug(s"cacheModifierParam is $cacheModifierParam")
|
||||
|
||||
(tags, partialFunctionNames, languageParam, contentParam, apiCollectionIdParam, cacheModifierParam)
|
||||
}
|
||||
@ -1080,7 +1080,7 @@ so the caller must specify any required filtering by catalog explicitly.
|
||||
|
||||
|
||||
if (filteredResources4.length > 0 && resourcesToUse.length == 0) {
|
||||
logger.info("tags filter reduced the list of resource docs to zero")
|
||||
logger.debug("tags filter reduced the list of resource docs to zero")
|
||||
}
|
||||
|
||||
resourcesToUse
|
||||
|
||||
@ -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.{ApiTrigger, ExampleValue}
|
||||
import code.api.util.{APIUtil, ApiTrigger, 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
|
||||
@ -36,6 +36,7 @@ import com.openbankproject.commons.util.{ApiVersion, FieldNameApiVersions, Refle
|
||||
import net.liftweb.json
|
||||
import java.net.URLEncoder
|
||||
|
||||
import code.api.v5_1_0.CertificateInfoJsonV510
|
||||
import code.endpointMapping.EndpointMappingCommons
|
||||
|
||||
import scala.collection.immutable.List
|
||||
@ -4154,6 +4155,15 @@ object SwaggerDefinitionsJSON {
|
||||
val oAuth2ServerJWKURIJson = OAuth2ServerJWKURIJson("https://www.googleapis.com/oauth2/v3/certs")
|
||||
|
||||
val oAuth2ServerJwksUrisJson = OAuth2ServerJwksUrisJson(List(oAuth2ServerJWKURIJson))
|
||||
|
||||
val certificateInfoJsonV510 = CertificateInfoJsonV510(
|
||||
subject_domain_name = "OID.2.5.4.41=VPN, EMAILADDRESS=admin@tesobe.com, CN=TESOBE CA, OU=TESOBE Operations, O=TESOBE, L=Berlin, ST=Berlin, C=DE",
|
||||
issuer_domain_name = "CN=localhost, O=TESOBE GmbH, ST=Berlin, C=DE",
|
||||
not_before = "2022-04-01T10:13:00.000Z",
|
||||
not_after = "2032-04-01T10:13:00.000Z",
|
||||
roles = None,
|
||||
roles_info = Some("PEM Encoded Certificate does not contain PSD2 roles.")
|
||||
)
|
||||
|
||||
val updateAccountRequestJsonV310 = UpdateAccountRequestJsonV310(
|
||||
label = "Label",
|
||||
|
||||
@ -244,6 +244,17 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Purpose of this helper function is to get the Consent-JWT value from a Request Headers.
|
||||
* @return the Consent-JWT value from a Request Header as a String
|
||||
*/
|
||||
def getConsentIdRequestHeaderValue(requestHeaders: List[HTTPParam]): Option[String] = {
|
||||
requestHeaders.toSet.filter(_.name == RequestHeader.`Consent-Id`).toList match {
|
||||
case x :: Nil => Some(x.values.mkString(", "))
|
||||
case _ => None
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Purpose of this helper function is to get the PSD2-CERT value from a Request Headers.
|
||||
* @return the PSD2-CERT value from a Request Header as a String
|
||||
@ -3648,12 +3659,11 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
|
||||
`getPSD2-CERT`(cc.map(_.requestHeaders).getOrElse(Nil)) match {
|
||||
case Some(pem) =>
|
||||
logger.debug("PSD2-CERT pem: " + pem)
|
||||
val decodedPem = URLDecoder.decode(pem,"UTF-8")
|
||||
val validatedPem = X509.validate(decodedPem)
|
||||
val validatedPem = X509.validate(pem)
|
||||
logger.debug("validatedPem: " + validatedPem)
|
||||
validatedPem match {
|
||||
case Full(true) =>
|
||||
val hasServiceProvider = X509.extractPsd2Roles(decodedPem).map(_.exists(_ == serviceProvider))
|
||||
val hasServiceProvider = X509.extractPsd2Roles(pem).map(_.exists(_ == serviceProvider))
|
||||
logger.debug("hasServiceProvider: " + hasServiceProvider)
|
||||
hasServiceProvider match {
|
||||
case Full(true) => Full(true)
|
||||
|
||||
@ -5,6 +5,8 @@ import java.security.PublicKey
|
||||
import java.security.cert.{CertificateExpiredException, CertificateNotYetValidException, X509Certificate}
|
||||
import java.security.interfaces.{ECPublicKey, RSAPublicKey}
|
||||
|
||||
import code.api.v5_1_0.CertificateInfoJsonV510
|
||||
import code.util.Helper.MdcLoggable
|
||||
import com.github.dwickern.macros.NameOf
|
||||
import com.nimbusds.jose.jwk.RSAKey
|
||||
import com.nimbusds.jose.util.X509CertUtils
|
||||
@ -13,7 +15,7 @@ import org.bouncycastle.asn1._
|
||||
import org.bouncycastle.asn1.x509.Extension
|
||||
import org.bouncycastle.asn1.x509.qualified.QCStatement
|
||||
|
||||
object X509 {
|
||||
object X509 extends MdcLoggable {
|
||||
|
||||
object OID {
|
||||
lazy val role = "2.5.4.72"
|
||||
@ -76,7 +78,7 @@ object X509 {
|
||||
psd2Roles += (psd2RoleSequence.getObjectAt(1).toASN1Primitive.toString)
|
||||
}
|
||||
}
|
||||
org.scalameta.logger.elem(psd2Roles.toList)
|
||||
logger.debug(psd2Roles.toList)
|
||||
psd2Roles.toList
|
||||
}
|
||||
|
||||
@ -201,5 +203,48 @@ object X509 {
|
||||
val rsaJWK = RSAKey.parse(cert)
|
||||
rsaJWK
|
||||
}
|
||||
|
||||
|
||||
private def extractCertificateInfo(pem: String): Box[CertificateInfoJsonV510] = {
|
||||
// Parse X.509 certificate
|
||||
val cert: X509Certificate = X509CertUtils.parse(pem)
|
||||
if (cert == null) {
|
||||
// Parsing failed
|
||||
Failure(ErrorMessages.X509ParsingFailed)
|
||||
} else {
|
||||
val subjectDN = cert.getSubjectDN().getName()
|
||||
val issuerDN = cert.getIssuerDN().getName()
|
||||
val notBefore = cert.getNotBefore()
|
||||
val notAfter = cert.getNotAfter()
|
||||
var roles: Option[List[String]] = None
|
||||
var rolesInfo: Option[String] = None
|
||||
try {
|
||||
val qcstatements = extractQcStatements(cert)
|
||||
val asn1encodable = extractPsd2QcStatements(qcstatements)
|
||||
roles = Some(getPsd2Roles(asn1encodable: Array[ASN1Encodable]))
|
||||
}
|
||||
catch {
|
||||
case _: Throwable =>
|
||||
Failure(ErrorMessages.X509ThereAreNoPsd2Roles)
|
||||
rolesInfo = Some("PEM Encoded Certificate does not contain PSD2 roles.")
|
||||
}
|
||||
val result = CertificateInfoJsonV510(
|
||||
subject_domain_name = subjectDN,
|
||||
issuer_domain_name = issuerDN,
|
||||
not_before = APIUtil.formatDate(notBefore),
|
||||
not_after = APIUtil.formatDate(notAfter),
|
||||
roles = roles,
|
||||
roles_info = rolesInfo
|
||||
)
|
||||
Full(result)
|
||||
}
|
||||
}
|
||||
|
||||
def getCertificateInfo(pem: Option[String]): Box[CertificateInfoJsonV510] = {
|
||||
pem match {
|
||||
case Some(value) => extractCertificateInfo(value)
|
||||
case None => Failure(ErrorMessages.X509CannotGetCertificate)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -7,7 +7,7 @@ import code.api.util.APIUtil._
|
||||
import code.api.util.ApiRole._
|
||||
import code.api.util.ApiTag._
|
||||
import code.api.util.ErrorMessages.{$UserNotLoggedIn, BankNotFound, ConsentNotFound, InvalidJsonFormat, UnknownError, UserNotFoundByUserId, UserNotLoggedIn, _}
|
||||
import code.api.util.{ApiRole, NewStyle}
|
||||
import code.api.util.{APIUtil, ApiRole, NewStyle, X509}
|
||||
import code.api.util.NewStyle.HttpCode
|
||||
import code.api.v3_0_0.JSONFactory300.createAggregateMetricJson
|
||||
import code.api.v3_1_0.ConsentJsonV310
|
||||
@ -112,8 +112,8 @@ trait APIMethods510 {
|
||||
revokeConsentAtBank,
|
||||
implementedInApiVersion,
|
||||
nameOf(revokeConsentAtBank),
|
||||
"GET",
|
||||
"/banks/BANK_ID/consents/CONSENT_ID/revoke",
|
||||
"DELETE",
|
||||
"/banks/BANK_ID/consents/CONSENT_ID",
|
||||
"Revoke Consent at Bank",
|
||||
s"""
|
||||
|Revoke Consent specified by CONSENT_ID
|
||||
@ -141,7 +141,7 @@ trait APIMethods510 {
|
||||
)
|
||||
|
||||
lazy val revokeConsentAtBank: OBPEndpoint = {
|
||||
case "banks" :: BankId(bankId) :: "consents" :: consentId :: "revoke" :: Nil JsonGet _ => {
|
||||
case "banks" :: BankId(bankId) :: "consents" :: consentId :: Nil JsonDelete _ => {
|
||||
cc =>
|
||||
for {
|
||||
(Full(user), callContext) <- authenticatedAccess(cc)
|
||||
@ -160,6 +160,91 @@ trait APIMethods510 {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
staticResourceDocs += ResourceDoc(
|
||||
selfRevokeConsent,
|
||||
implementedInApiVersion,
|
||||
nameOf(selfRevokeConsent),
|
||||
"DELETE",
|
||||
"/my/consent/current",
|
||||
"Revoke Consent used in the Current Call",
|
||||
s"""
|
||||
|Revoke Consent specified by Consent-Id at Request Header
|
||||
|
|
||||
|There are a few reasons you might need to revoke an application’s access to a user’s account:
|
||||
| - The user explicitly wishes to revoke the application’s access
|
||||
| - You as the service provider have determined an application is compromised or malicious, and want to disable it
|
||||
| - etc.
|
||||
||
|
||||
|OBP as a resource server stores access tokens in a database, then it is relatively easy to revoke some token that belongs to a particular user.
|
||||
|The status of the token is changed to "REVOKED" so the next time the revoked client makes a request, their token will fail to validate.
|
||||
|
|
||||
|${authenticationRequiredMessage(true)}
|
||||
|
|
||||
""".stripMargin,
|
||||
EmptyBody,
|
||||
revokedConsentJsonV310,
|
||||
List(
|
||||
UserNotLoggedIn,
|
||||
BankNotFound,
|
||||
UnknownError
|
||||
),
|
||||
List(apiTagConsent, apiTagPSD2AIS, apiTagPsd2, apiTagNewStyle)
|
||||
)
|
||||
lazy val selfRevokeConsent: OBPEndpoint = {
|
||||
case "my" :: "consent" :: "current" :: Nil JsonDelete _ => {
|
||||
cc =>
|
||||
for {
|
||||
(Full(user), callContext) <- authenticatedAccess(cc)
|
||||
consentId = getConsentIdRequestHeaderValue(cc.requestHeaders).getOrElse("")
|
||||
_ <- Future(Consents.consentProvider.vend.getConsentByConsentId(consentId)) map {
|
||||
unboxFullOrFail(_, callContext, ConsentNotFound)
|
||||
}
|
||||
consent <- Future(Consents.consentProvider.vend.revoke(consentId)) map {
|
||||
i => connectorEmptyResponse(i, callContext)
|
||||
}
|
||||
} yield {
|
||||
(ConsentJsonV310(consent.consentId, consent.jsonWebToken, consent.status), HttpCode.`200`(callContext))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
staticResourceDocs += ResourceDoc(
|
||||
mtlsClientCertificateInfo,
|
||||
implementedInApiVersion,
|
||||
nameOf(mtlsClientCertificateInfo),
|
||||
"GET",
|
||||
"/my/mtls/certificate/current",
|
||||
"Provide client's certificate info of a current call",
|
||||
s"""
|
||||
|Provide client's certificate info of a current call specified by PSD2-CERT value at Request Header
|
||||
|
|
||||
|${authenticationRequiredMessage(true)}
|
||||
|
|
||||
""".stripMargin,
|
||||
EmptyBody,
|
||||
certificateInfoJsonV510,
|
||||
List(
|
||||
UserNotLoggedIn,
|
||||
BankNotFound,
|
||||
UnknownError
|
||||
),
|
||||
List(apiTagConsent, apiTagPSD2AIS, apiTagPsd2, apiTagNewStyle)
|
||||
)
|
||||
lazy val mtlsClientCertificateInfo: OBPEndpoint = {
|
||||
case "my" :: "mtls" :: "certificate" :: "current" :: Nil JsonGet _ => {
|
||||
cc =>
|
||||
for {
|
||||
(Full(_), callContext) <- authenticatedAccess(cc)
|
||||
info <- Future(X509.getCertificateInfo(APIUtil.`getPSD2-CERT`(cc.requestHeaders))) map {
|
||||
unboxFullOrFail(_, callContext, X509GeneralError)
|
||||
}
|
||||
} yield {
|
||||
(info, HttpCode.`200`(callContext))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
staticResourceDocs += ResourceDoc(
|
||||
|
||||
@ -47,6 +47,15 @@ case class APIInfoJsonV510(
|
||||
resource_docs_requires_role: Boolean
|
||||
)
|
||||
|
||||
case class CertificateInfoJsonV510(
|
||||
subject_domain_name: String,
|
||||
issuer_domain_name: String,
|
||||
not_before: String,
|
||||
not_after: String,
|
||||
roles: Option[List[String]],
|
||||
roles_info: Option[String] = None
|
||||
)
|
||||
|
||||
object JSONFactory510 {
|
||||
def getApiInfoJSON(apiVersion : ApiVersion, apiVersionStatus: String) = {
|
||||
val organisation = APIUtil.getPropsValue("hosted_by.organisation", "TESOBE")
|
||||
|
||||
@ -21,11 +21,13 @@ object MetricsArchiveScheduler extends MdcLoggable {
|
||||
private val oneDayInMillis: Long = 86400000
|
||||
|
||||
def start(intervalInSeconds: Long): Unit = {
|
||||
logger.info("Hello from MetricsArchiveScheduler.start")
|
||||
scheduler.schedule(
|
||||
initialDelay = Duration(getMillisTillMidnight(), TimeUnit.MILLISECONDS),
|
||||
interval = Duration(intervalInSeconds, TimeUnit.SECONDS),
|
||||
runnable = new Runnable {
|
||||
def run(): Unit = {
|
||||
logger.info("Hello from MetricsArchiveScheduler.start.run")
|
||||
conditionalDeleteMetricsRow()
|
||||
deleteOutdatedRowsFromMetricsArchive()
|
||||
}
|
||||
@ -34,6 +36,7 @@ object MetricsArchiveScheduler extends MdcLoggable {
|
||||
}
|
||||
|
||||
def deleteOutdatedRowsFromMetricsArchive() = {
|
||||
logger.info("Hello from MetricsArchiveScheduler.deleteOutdatedRowsFromMetricsArchive")
|
||||
val currentTime = new Date()
|
||||
val defaultValue : Int = 365 * 3
|
||||
val days = APIUtil.getPropsAsLongValue("retain_archive_metrics_days", defaultValue) match {
|
||||
@ -43,9 +46,11 @@ object MetricsArchiveScheduler extends MdcLoggable {
|
||||
val someYearsAgo: Date = new Date(currentTime.getTime - (oneDayInMillis * days))
|
||||
// Delete the outdated rows from the table "MetricsArchive"
|
||||
MetricArchive.bulkDelete_!!(By_<=(MetricArchive.date, someYearsAgo))
|
||||
logger.info("Bye from MetricsArchiveScheduler.deleteOutdatedRowsFromMetricsArchive")
|
||||
}
|
||||
|
||||
def conditionalDeleteMetricsRow() = {
|
||||
logger.info("Hello from MetricsArchiveScheduler.conditionalDeleteMetricsRow")
|
||||
val currentTime = new Date()
|
||||
val days = APIUtil.getPropsAsLongValue("retain_metrics_days", 367) match {
|
||||
case days if days > 59 => days
|
||||
@ -53,11 +58,16 @@ object MetricsArchiveScheduler extends MdcLoggable {
|
||||
}
|
||||
val someDaysAgo: Date = new Date(currentTime.getTime - (oneDayInMillis * days))
|
||||
// Get the data from the table "Metric" older than specified by retain_metrics_days
|
||||
logger.info("MetricsArchiveScheduler.conditionalDeleteMetricsRow says before candidateMetricRowsToMove val")
|
||||
val candidateMetricRowsToMove = APIMetrics.apiMetrics.vend.getAllMetrics(List(OBPToDate(someDaysAgo)))
|
||||
logger.info("MetricsArchiveScheduler.conditionalDeleteMetricsRow says after candidateMetricRowsToMove val")
|
||||
logger.info(s"Number of rows: ${candidateMetricRowsToMove.length}")
|
||||
candidateMetricRowsToMove map { i =>
|
||||
// and copy it to the table "MetricsArchive"
|
||||
// and copy it to the table "MetricArchive"
|
||||
copyRowToMetricsArchive(i)
|
||||
}
|
||||
logger.info("MetricsArchiveScheduler.conditionalDeleteMetricsRow says after coping all rows")
|
||||
logger.info("MetricsArchiveScheduler.conditionalDeleteMetricsRow says before maybeDeletedRows val")
|
||||
val maybeDeletedRows: List[(Boolean, Long)] = candidateMetricRowsToMove map { i =>
|
||||
// and delete it after successful coping
|
||||
MetricArchive.find(By(MetricArchive.metricId, i.getMetricId())) match {
|
||||
@ -65,6 +75,7 @@ object MetricsArchiveScheduler extends MdcLoggable {
|
||||
case _ => (false, i.getMetricId())
|
||||
}
|
||||
}
|
||||
logger.info("MetricsArchiveScheduler.conditionalDeleteMetricsRow says after maybeDeletedRows val")
|
||||
maybeDeletedRows.filter(_._1 == false).map { i =>
|
||||
logger.warn(s"Row with primary key ${i._2} of the table Metric is not successfully copied.")
|
||||
}
|
||||
|
||||
@ -80,12 +80,12 @@ class ConsentsTest extends V510ServerSetup with PropsReset{
|
||||
def getConsentRequestUrl(requestId:String) = (v5_1_0_Request / "consumer"/ "consent-requests"/requestId).GET<@(user1)
|
||||
def createConsentByConsentRequestIdEmail(requestId:String) = (v5_1_0_Request / "consumer"/ "consent-requests"/requestId/"EMAIL"/"consents").POST<@(user1)
|
||||
def getConsentByRequestIdUrl(requestId:String) = (v5_1_0_Request / "consumer"/ "consent-requests"/requestId/"consents").GET<@(user1)
|
||||
def revokeConsentUrl(consentId: String) = v5_1_0_Request / "banks" / bankId / "consents" / consentId / "revoke"
|
||||
def revokeConsentUrl(consentId: String) = (v5_1_0_Request / "banks" / bankId / "consents" / consentId).DELETE
|
||||
|
||||
feature(s"test $ApiEndpoint6 version $VersionOfApi - Unauthorized access") {
|
||||
scenario("We will call the endpoint without user credentials", ApiEndpoint6, VersionOfApi) {
|
||||
When(s"We make a request $ApiEndpoint6")
|
||||
val response510 = makeGetRequest(revokeConsentUrl("whatever"))
|
||||
val response510 = makeDeleteRequest(revokeConsentUrl("whatever"))
|
||||
Then("We should get a 401")
|
||||
response510.code should equal(401)
|
||||
response510.body.extract[ErrorMessage].message should equal(UserNotLoggedIn)
|
||||
@ -94,7 +94,7 @@ class ConsentsTest extends V510ServerSetup with PropsReset{
|
||||
feature(s"test $ApiEndpoint6 version $VersionOfApi - Authorized access") {
|
||||
scenario("We will call the endpoint without user credentials", ApiEndpoint6, VersionOfApi) {
|
||||
When(s"We make a request $ApiEndpoint1")
|
||||
val response510 = makeGetRequest(revokeConsentUrl("whatever")<@(user1))
|
||||
val response510 = makeDeleteRequest(revokeConsentUrl("whatever")<@(user1))
|
||||
Then("We should get a 403")
|
||||
response510.code should equal(403)
|
||||
response510.body.extract[ErrorMessage].message contains (UserHasMissingRoles + CanRevokeConsentAtBank) should be (true)
|
||||
@ -177,7 +177,7 @@ class ConsentsTest extends V510ServerSetup with PropsReset{
|
||||
|
||||
// Revoke consent
|
||||
Entitlement.entitlement.vend.addEntitlement(bankId, resourceUser1.userId, CanRevokeConsentAtBank.toString)
|
||||
val response510 = makeGetRequest(revokeConsentUrl(getConsentByRequestResponseJson.consent_id)<@(user1))
|
||||
val response510 = makeDeleteRequest(revokeConsentUrl(getConsentByRequestResponseJson.consent_id)<@(user1))
|
||||
Then("We should get a 200")
|
||||
response510.code should equal(200)
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user