Skip to content

Commit

Permalink
Merge pull request #2434 from constantine2nd/develop
Browse files Browse the repository at this point in the history
OIDC; Get My Consents
  • Loading branch information
constantine2nd authored Nov 5, 2024
2 parents b3a0844 + 8527d80 commit d201ab9
Show file tree
Hide file tree
Showing 5 changed files with 115 additions and 10 deletions.
19 changes: 14 additions & 5 deletions obp-api/src/main/scala/code/api/openidconnect.scala
Original file line number Diff line number Diff line change
Expand Up @@ -160,12 +160,16 @@ object OpenIdConnect extends OBPRestHelper with MdcLoggable {
logger.debug("Error at getOrCreateResourceUser: " + everythingElse)
(401, ErrorMessages.CouldNotSaveOpenIDConnectUser, None)
}
case badObj@Failure(_, _, _) => chainErrorMessage(badObj, ErrorMessages.CouldNotValidateIDToken)
case badObj@Failure(_, _, _) =>
logger.debug("Error at JwtUtil.validateIdToken: " + badObj)
chainErrorMessage(badObj, ErrorMessages.CouldNotValidateIDToken)
case everythingElse =>
logger.debug("Error at JwtUtil.validateIdToken: " + everythingElse)
(401, ErrorMessages.CouldNotValidateIDToken, None)
}
case badObj@Failure(_, _, _) => chainErrorMessage(badObj, ErrorMessages.CouldNotExchangeAuthorizationCodeForTokens)
case badObj@Failure(_, _, _) =>
logger.debug("Error at exchangeAuthorizationCodeForTokens: " + badObj)
chainErrorMessage(badObj, ErrorMessages.CouldNotExchangeAuthorizationCodeForTokens)
case everythingElse =>
logger.debug("Error at exchangeAuthorizationCodeForTokens: " + everythingElse)
(401, ErrorMessages.CouldNotExchangeAuthorizationCodeForTokens, None)
Expand Down Expand Up @@ -197,7 +201,7 @@ object OpenIdConnect extends OBPRestHelper with MdcLoggable {
private def extractParams(s: S): (String, String, String) = {
// TODO Figure out why ObpS does not contain response parameter code
val code = s.param("code")
val state = ObpS.param("state")
val state = s.param("state")
val sessionState = OpenIDConnectSessionState.get
(code.getOrElse(""), state.getOrElse("0"), sessionState.map(_.toString).getOrElse("1"))
}
Expand Down Expand Up @@ -271,10 +275,15 @@ object OpenIdConnect extends OBPRestHelper with MdcLoggable {
refreshToken <- tryo{(tokenResponse \ "refresh_token").extractOrElse[String]("")}
scope <- tryo{(tokenResponse \ "scope").extractOrElse[String]("")}
} yield {
logger.debug(s"(idToken: $idToken, accessToken: $accessToken, tokenType: $tokenType, expiresIn.toLong: ${expiresIn.toLong}, refreshToken: $refreshToken, scope: $scope)")
(idToken, accessToken, tokenType, expiresIn.toLong, refreshToken, scope)
}
case badObject@Failure(_, _, _) => badObject
case _ => Failure(ErrorMessages.InternalServerError + " - exchangeAuthorizationCodeForTokens")
case badObject@Failure(_, _, _) =>
logger.debug("Error at exchangeAuthorizationCodeForTokens: " + badObject)
badObject
case everythingElse =>
logger.debug("Error at exchangeAuthorizationCodeForTokens: " + everythingElse)
Failure(ErrorMessages.InternalServerError + " - exchangeAuthorizationCodeForTokens")
}
}

Expand Down
2 changes: 1 addition & 1 deletion obp-api/src/main/scala/code/api/util/APIUtil.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3418,7 +3418,7 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
val sysEnvironmentPropertyValue: Box[String] = sys.env.get(sysEnvironmentPropertyName)
val directPropsValue = sysEnvironmentPropertyValue match {
case Full(_) =>
logger.debug("System environment property value found for: " + sysEnvironmentPropertyName)
logger.debug(s"System environment property value found for $sysEnvironmentPropertyName : $sysEnvironmentPropertyValue")
sysEnvironmentPropertyValue
case _ =>
(Props.get(brandSpecificPropertyName), Props.get(brandSpecificPropertyName + ".is_encrypted"), Props.get(brandSpecificPropertyName + ".is_obfuscated")) match {
Expand Down
42 changes: 41 additions & 1 deletion obp-api/src/main/scala/code/api/v5_1_0/APIMethods510.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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.{createRegulatedEntitiesJson, createRegulatedEntityJson}
import code.api.v5_1_0.JSONFactory510.{createConsentsInfoJsonV510, createRegulatedEntitiesJson, createRegulatedEntityJson}
import code.atmattribute.AtmAttribute
import code.bankconnectors.Connector
import code.consent.{ConsentRequests, Consents}
Expand Down Expand Up @@ -974,6 +974,46 @@ trait APIMethods510 {
}


staticResourceDocs += ResourceDoc(
getMyConsents,
implementedInApiVersion,
nameOf(getMyConsents),
"GET",
"/banks/BANK_ID/my/consents",
"Get My Consents",
s"""
|
|This endpoint gets the Consents created by a current User.
|
|${authenticationRequiredMessage(true)}
|
""".stripMargin,
EmptyBody,
consentsJsonV400,
List(
$UserNotLoggedIn,
$BankNotFound,
UnknownError
),
List(apiTagConsent, apiTagPSD2AIS, apiTagPsd2))

lazy val getMyConsents: OBPEndpoint = {
case "banks" :: BankId(bankId) :: "my" :: "consents" :: Nil JsonGet _ => {
cc =>
implicit val ec = EndpointContext(Some(cc))
for {
consents <- Future {
Consents.consentProvider.vend.getConsentsByUser(cc.userId)
.sortBy(i => (i.creationDateTime, i.apiStandard)).reverse
}
} yield {
val consentsOfBank = Consent.filterByBankId(consents, bankId)
(createConsentsInfoJsonV510(consentsOfBank), HttpCode.`200`(cc))
}
}
}


staticResourceDocs += ResourceDoc(
getConsentByConsentId,
implementedInApiVersion,
Expand Down
38 changes: 37 additions & 1 deletion obp-api/src/main/scala/code/api/v5_1_0/JSONFactory5.1.0.scala
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ package code.api.v5_1_0

import code.api.Constant
import code.api.util.{APIUtil, ConsentJWT, CustomJsonFormats, JwtUtil, Role}
import code.api.util.APIUtil.{gitCommit, stringOrNull}
import code.api.util.APIUtil.{DateWithDay, DateWithSeconds, gitCommit, stringOrNull}
import code.api.v1_2_1.BankRoutingJsonV121
import code.api.v1_4_0.JSONFactory1_4_0.{LocationJsonV140, MetaJsonV140, transformToLocationFromV140, transformToMetaFromV140}
import code.api.v2_1_0.ResourceUserJSON
Expand All @@ -52,6 +52,7 @@ import net.liftweb.common.{Box, Full}
import net.liftweb.json
import net.liftweb.json.{JString, JValue, parse, parseOpt}

import java.text.SimpleDateFormat
import scala.collection.immutable.List
import scala.util.Try

Expand Down Expand Up @@ -122,6 +123,21 @@ case class ConsentJsonV510(consent_id: String,
consent_request_id: Option[String],
scopes: Option[List[Role]])


case class ConsentInfoJsonV510(consent_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 ConsentsInfoJsonV510(consents: List[ConsentInfoJsonV510])


case class CurrencyJsonV510(alphanumeric_code: String)
case class CurrenciesJsonV510(currencies: List[CurrencyJsonV510])

Expand Down Expand Up @@ -676,6 +692,26 @@ object JSONFactory510 extends CustomJsonFormats {
)
}

def createConsentsInfoJsonV510(consents: List[MappedConsent]): ConsentsInfoJsonV510 = {
ConsentsInfoJsonV510(
consents.map { c =>
val jwtPayload: Box[ConsentJWT] = JwtUtil.getSignedPayloadAsJson(c.jsonWebToken).map(parse(_).extract[ConsentJWT])
ConsentInfoJsonV510(
consent_id = c.consentId,
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")
val email = APIUtil.getPropsValue("hosted_by.email", "[email protected]")
Expand Down
24 changes: 22 additions & 2 deletions obp-api/src/test/scala/code/api/v5_1_0/ConsentsTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,8 @@ class ConsentsTest extends V510ServerSetup with PropsReset{
object ApiEndpoint5 extends Tag(nameOf(Implementations4_0_0.getUsers))
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))

lazy val entitlements = List(PostConsentEntitlementJsonV310("", CanGetAnyUser.toString()))
lazy val bankId = testBankId1.value
lazy val accountAccess = List(AccountAccessV500(
Expand All @@ -85,6 +86,7 @@ class ConsentsTest extends V510ServerSetup with PropsReset{
def getConsentByRequestIdUrl(requestId:String) = (v5_1_0_Request / "consumer"/ "consent-requests"/requestId/"consents").GET<@(user1)
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

feature(s"test $ApiEndpoint6 version $VersionOfApi - Unauthorized access") {
scenario("We will call the endpoint without user credentials", ApiEndpoint6, VersionOfApi) {
Expand All @@ -96,14 +98,32 @@ 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) {
scenario("We will call the endpoint with user credentials", ApiEndpoint6, VersionOfApi) {
When(s"We make a request $ApiEndpoint1")
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)
}
}

feature(s"test $ApiEndpoint8 version $VersionOfApi - Unautenticated access") {
scenario("We will call the endpoint without user credentials", ApiEndpoint8, VersionOfApi) {
When(s"We make a request $ApiEndpoint8")
val response510 = makeGetRequest(getMyConsents("whatever"))
Then("We should get a 401")
response510.code should equal(401)
response510.body.extract[ErrorMessage].message should equal(UserNotLoggedIn)
}
}
feature(s"test $ApiEndpoint8 version $VersionOfApi - Autenticated access") {
scenario("We will call the endpoint with user credentials", ApiEndpoint8, VersionOfApi) {
When(s"We make a request $ApiEndpoint1")
val response510 = makeGetRequest(getMyConsents("whatever")<@(user1))
Then("We should get a 200")
response510.code should equal(200)
}
}

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) {
Expand Down

0 comments on commit d201ab9

Please sign in to comment.