Merge pull request #2459 from constantine2nd/develop

PSU Authentication methods props psu_authentication_method
This commit is contained in:
Simon Redfern 2024-12-10 13:56:51 +01:00 committed by GitHub
commit e0ef3fb7f0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 90 additions and 13 deletions

View File

@ -771,6 +771,28 @@ display_internal_errors=false
# oauth2.keycloak.source-of-truth = false
# ------------------------------------------------------------------------------ OAuth 2 ------
# -- PSU Authentication methods --------------------------------------------------------------
# The EBA notes that there would appear to currently be three main ways or methods
# of carrying out the authentication procedure of the PSU through a dedicated interface,
# and APIs in particular, namely:
# - redirection,
# - embedded approaches and
# - decoupled approaches (or a combination thereof).
# In the cases of redirection and decoupled approaches,
# PSUs authentication data is exchanged directly between PSUs and ASPSPs,
# as opposed to embedded approaches, in which PSUs authentication data
# is exchanged between TPPs and ASPSPs through the interface.
####
# psu_authentication_method = redirection_with_dedicated_start_of_authorization
# Possible values:
# - redirection
# - redirection_with_dedicated_start_of_authorization
# - embedded
# - decoupled
# In case that "psu_authentication_method = redirection" you must define
# psu_authentication_method_sca_redirect_url = redirect_url_value
# -------------------------------------------------------------- Authentication methods --
## This property is used for documenting at Resource Doc. It may include the port also (but not /obp)
## (this needs to be a URL)
documented_server_url=https://apisandbox.openbankproject.com

View File

@ -323,7 +323,7 @@ object OAuth2Login extends RestHelper with MdcLoggable {
}
def resolveProvider(idToken: String) = {
isIssuer(jwtToken = idToken, identityProvider = hydraPublicUrl) match {
HydraUtil.integrateWithHydra && isIssuer(jwtToken = idToken, identityProvider = hydraPublicUrl) match {
case true if HydraUtil.hydraUsesObpUserCredentials => // Case that source of the truth of Hydra user management is the OBP-API mapper DB
// In case that ORY Hydra login url is "hostname/user_mgt/login" we MUST override hydraPublicUrl as provider
// in order to avoid creation of a new user

View File

@ -130,7 +130,7 @@ As a last option, an ASPSP might in addition accept a command with access rights
PostConsentResponseJson(
consentId = "1234-wertiq-983",
consentStatus = "received",
_links = ConsentLinksV13("/v1.3/consents/1234-wertiq-983/authorisations")
_links = ConsentLinksV13(Some(Href("/v1.3/consents/1234-wertiq-983/authorisations")))
),
List(UserNotLoggedIn, UnknownError),
ApiTag("Account Information Service (AIS)") :: apiTagBerlinGroupM :: Nil

View File

@ -2,9 +2,9 @@ package code.api.berlin.group.v1_3
import java.text.SimpleDateFormat
import java.util.Date
import code.api.berlin.group.v1_3.model._
import code.api.util.APIUtil._
import code.api.util.ErrorMessages.MissingPropsValueAtThisInstance
import code.api.util.{APIUtil, ConsentJWT, CustomJsonFormats, JwtUtil}
import code.bankconnectors.Connector
import code.consent.ConsentTrait
@ -15,6 +15,7 @@ import net.liftweb.common.Box.tryo
import net.liftweb.common.{Box, Full}
import net.liftweb.json
import net.liftweb.json.{JValue, parse}
import scala.collection.immutable.List
case class JvalueCaseClass(jvalueToCaseclass: JValue)
@ -231,7 +232,12 @@ object JSONFactory_BERLIN_GROUP_1_3 extends CustomJsonFormats {
combinedServiceIndicator: Boolean
)
case class ConsentLinksV13(
startAuthorisation: String
startAuthorisation: Option[Href] = None,
scaRedirect: Option[Href] = None,
status: Option[Href] = None,
scaStatus: Option[Href] = None,
startAuthorisationWithPsuIdentification: Option[Href] = None,
startAuthorisationWithPsuAuthentication: Option[Href] = None,
)
case class PostConsentResponseJson(
@ -239,6 +245,7 @@ object JSONFactory_BERLIN_GROUP_1_3 extends CustomJsonFormats {
consentStatus: String,
_links: ConsentLinksV13
)
case class Href(href: String)
case class PutConsentResponseJson(
scaStatus: String,
@ -246,6 +253,8 @@ object JSONFactory_BERLIN_GROUP_1_3 extends CustomJsonFormats {
)
case class GetConsentResponseJson(
access: ConsentAccessJson,
recurringIndicator: Boolean,
@ -508,11 +517,51 @@ object JSONFactory_BERLIN_GROUP_1_3 extends CustomJsonFormats {
}
def createPostConsentResponseJson(consent: ConsentTrait) : PostConsentResponseJson = {
PostConsentResponseJson(
consentId = consent.consentId,
consentStatus = consent.status.toLowerCase(),
_links= ConsentLinksV13(s"/v1.3/consents/${consent.consentId}/authorisations")
)
def redirectionWithDedicatedStartOfAuthorization = {
PostConsentResponseJson(
consentId = consent.consentId,
consentStatus = consent.status.toLowerCase(),
_links = ConsentLinksV13(
startAuthorisation = Some(Href(s"/v1.3/consents/${consent.consentId}/authorisations"))
)
)
}
getPropsValue("psu_authentication_method") match {
case Full("redirection") =>
val scaRedirectUrl = getPropsValue("psu_authentication_method_sca_redirect_url")
.openOr(MissingPropsValueAtThisInstance + "psu_authentication_method_sca_redirect_url")
PostConsentResponseJson(
consentId = consent.consentId,
consentStatus = consent.status.toLowerCase(),
_links = ConsentLinksV13(
scaRedirect = Some(Href(s"$scaRedirectUrl/${consent.consentId}")),
status = Some(Href(s"/v1.3/consents/${consent.consentId}/status")),
scaStatus = Some(Href(s"/v1.3/consents/${consent.consentId}/authorisations/AUTHORISATIONID")),
)
)
case Full("redirection_with_dedicated_start_of_authorization") =>
redirectionWithDedicatedStartOfAuthorization
case Full("embedded") =>
PostConsentResponseJson(
consentId = consent.consentId,
consentStatus = consent.status.toLowerCase(),
_links = ConsentLinksV13(
startAuthorisationWithPsuAuthentication = Some(Href(s"/v1.3/consents/${consent.consentId}/authorisations"))
)
)
case Full("decoupled") =>
PostConsentResponseJson(
consentId = consent.consentId,
consentStatus = consent.status.toLowerCase(),
_links = ConsentLinksV13(
startAuthorisationWithPsuIdentification = Some(Href(s"/v1.3/consents/${consent.consentId}/authorisations"))
)
)
case _ =>
redirectionWithDedicatedStartOfAuthorization
}
}
def createPutConsentResponseJson(consent: ConsentTrait) : ScaStatusResponse = {
ScaStatusResponse(

View File

@ -1092,6 +1092,7 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
} yield deleted
case "status" => Full(OBPStatus(values.head))
case "consumer_id" => Full(OBPConsumerId(values.head))
case "consent_id" => Full(OBPConsentId(values.head))
case "user_id" => Full(OBPUserId(values.head))
case "bank_id" => Full(OBPBankId(values.head))
case "account_id" => Full(OBPAccountId(values.head))
@ -1139,6 +1140,7 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
anon <- getHttpParamValuesByName(httpParams,"anon")
deletedStatus <- getHttpParamValuesByName(httpParams,"is_deleted")
consumerId <- getHttpParamValuesByName(httpParams,"consumer_id")
consentId <- getHttpParamValuesByName(httpParams,"consent_id")
userId <- getHttpParamValuesByName(httpParams, "user_id")
bankId <- getHttpParamValuesByName(httpParams, "bank_id")
accountId <- getHttpParamValuesByName(httpParams, "account_id")
@ -1176,7 +1178,7 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
val ordering = OBPOrdering(sortBy, sortDirection)
//This guarantee the order
List(limit, offset, ordering, fromDate, toDate,
anon, status, consumerId, userId, url, appName, implementedByPartialFunction, implementedInVersion,
anon, status, consumerId, consentId, userId, url, appName, implementedByPartialFunction, implementedInVersion,
verb, correlationId, duration, excludeAppNames, excludeUrlPattern, excludeImplementedByPartialfunctions,
includeAppNames, includeUrlPattern, includeImplementedByPartialfunctions,
connectorName,functionName, bankId, accountId, customerId, lockedStatus, deletedStatus
@ -1214,6 +1216,7 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
val status = getHttpRequestUrlParam(httpRequestUrl,"status")
val isDeleted = getHttpRequestUrlParam(httpRequestUrl, "is_deleted")
val consumerId = getHttpRequestUrlParam(httpRequestUrl,"consumer_id")
val consentId = getHttpRequestUrlParam(httpRequestUrl,"consent_id")
val userId = getHttpRequestUrlParam(httpRequestUrl, "user_id")
val bankId = getHttpRequestUrlParam(httpRequestUrl, "bank_id")
val accountId = getHttpRequestUrlParam(httpRequestUrl, "account_id")
@ -1244,7 +1247,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("status", status), 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("consent_id", consentId), 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

@ -26,6 +26,7 @@ case class OBPFromDate(value: Date) extends OBPQueryParam
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 OBPConsentId(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

View File

@ -1,7 +1,7 @@
package code.consent
import java.util.Date
import code.api.util.{APIUtil, Consent, ErrorMessages, OBPStatus, OBPOffset, OBPQueryParam, OBPUserId, OBPLimit, OBPConsumerId, SecureRandomUtil}
import code.api.util.{APIUtil, Consent, ErrorMessages, OBPConsentId, OBPConsumerId, OBPLimit, OBPOffset, OBPQueryParam, OBPStatus, OBPUserId, SecureRandomUtil}
import code.consent.ConsentStatus.ConsentStatus
import code.model.Consumer
import code.util.MappedUUID
@ -67,8 +67,9 @@ object MappedConsentProvider extends ConsentProvider {
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:
// The optional variables:
val consumerId = queryParams.collect { case OBPConsumerId(value) => By(MappedConsent.mConsumerId, value)}.headOption
val consentId = queryParams.collect { case OBPConsentId(value) => By(MappedConsent.mConsentId, 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
@ -77,6 +78,7 @@ object MappedConsentProvider extends ConsentProvider {
limit.toSeq,
status.toSeq,
userId.toSeq,
consentId.toSeq,
consumerId.toSeq
).flatten
}