feature/Add endpoint SyncExternalUser v5.1.0

This commit is contained in:
Marko Milić 2024-12-12 13:24:48 +01:00
parent 415afeeb9f
commit 349e01c745
4 changed files with 75 additions and 3 deletions

View File

@ -490,6 +490,9 @@ object ApiRole extends MdcLoggable{
case class CanRefreshUser(requiresBankId: Boolean = false) extends ApiRole
lazy val canRefreshUser = CanRefreshUser()
case class CanSyncUser(requiresBankId: Boolean = false) extends ApiRole
lazy val canSyncUser = CanSyncUser()
case class CanGetAccountApplications(requiresBankId: Boolean = false) extends ApiRole
lazy val canGetAccountApplications = CanGetAccountApplications()

View File

@ -21,7 +21,7 @@ import code.api.v2_1_0.ConsumerRedirectUrlJSON
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.v3_1_0.JSONFactory310.{createBadLoginStatusJson, createRefreshUserJson}
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
@ -32,8 +32,9 @@ import code.consent.{ConsentRequests, Consents}
import code.consumer.Consumers
import code.loginattempts.LoginAttempt
import code.metrics.APIMetrics
import code.metrics.MappedMetric.userId
import code.model.AppType
import code.model.dataAccess.MappedBankAccount
import code.model.dataAccess.{AuthUser, MappedBankAccount}
import code.regulatedentities.MappedRegulatedEntityProvider
import code.userlocks.UserLocksProvider
import code.users.Users
@ -626,6 +627,47 @@ trait APIMethods510 {
}
staticResourceDocs += ResourceDoc(
syncExternalUser,
implementedInApiVersion,
nameOf(syncExternalUser),
"POST",
"/users/PROVIDER/PROVIDER_ID/sync",
"Sync User",
s"""The endpoint is used to create or sync an OBP User with User from an external identity provider.
|PROVIDER is the host of the provider e.g. a Keycloak Host.
|PROVIDER_ID is the unique identifier for the User at the PROVIDER.
|At the end of the process, a User will exist in OBP with the Account Access records defined by the CBS.
|
|${authenticationRequiredMessage(true)}
|
|""",
EmptyBody,
refresUserJson,
List(
$UserNotLoggedIn,
UserHasMissingRoles,
UnknownError
),
List(apiTagUser),
Some(List(canSyncUser))
)
lazy val syncExternalUser : OBPEndpoint = {
case "users" :: provider :: providerId :: "sync" :: Nil JsonPost _ => {
cc => implicit val ec = EndpointContext(Some(cc))
for {
(user: User, callContext) <- NewStyle.function.getOrCreateResourceUser(provider, providerId, cc.callContext)
_ <- AuthUser.refreshUser(user, callContext)
} yield {
(JSONFactory510.getSyncedUser(user), HttpCode.`201`(callContext))
}
}
}
staticResourceDocs += ResourceDoc(
getAccountsHeldByUserAtBank,
implementedInApiVersion,

View File

@ -555,6 +555,8 @@ case class ConsumerLogoUrlJson(
logo_url: String
)
case class SyncExternalUserJson(user_id: String)
object JSONFactory510 extends CustomJsonFormats {
def createViewJson(view: View): CustomViewJsonV510 = {
@ -893,6 +895,10 @@ object JSONFactory510 extends CustomJsonFormats {
)
}
def getSyncedUser(user : User): SyncExternalUserJson = {
SyncExternalUserJson(user.userId)
}
def createUserAttributesJson(userAttribute: List[UserAttribute]): UserAttributesResponseJsonV510 = {
UserAttributesResponseJsonV510(userAttribute.map(createUserAttributeJson))
}

View File

@ -1,12 +1,13 @@
package code.api.v5_1_0
import code.api.util.APIUtil.OAuth._
import code.api.util.ApiRole.{CanGetAccountsHeldAtAnyBank, CanGetAccountsHeldAtOneBank}
import code.api.util.ApiRole.{CanGetAccountsHeldAtAnyBank, CanGetAccountsHeldAtOneBank, CanSyncUser}
import code.api.util.ErrorMessages.{UserHasMissingRoles, UserNotLoggedIn}
import code.api.v5_1_0.OBPAPI5_1_0.Implementations5_1_0
import com.github.dwickern.macros.NameOf.nameOf
import com.openbankproject.commons.model.ErrorMessage
import com.openbankproject.commons.util.ApiVersion
import net.liftweb.json.Serialization.write
import org.scalatest.Tag
class AccountTest extends V510ServerSetup {
@ -21,6 +22,7 @@ class AccountTest extends V510ServerSetup {
object GetCoreAccountByIdThroughView extends Tag(nameOf(Implementations5_1_0.getCoreAccountByIdThroughView))
object getAccountsHeldByUserAtBank extends Tag(nameOf(Implementations5_1_0.getAccountsHeldByUserAtBank))
object GetAccountsHeldByUser extends Tag(nameOf(Implementations5_1_0.getAccountsHeldByUser))
object SyncExternalUser extends Tag(nameOf(Implementations5_1_0.syncExternalUser))
lazy val bankId = randomBankId
@ -74,5 +76,24 @@ class AccountTest extends V510ServerSetup {
response.body.extract[ErrorMessage].message contains errorMessage should be(true)
}
}
feature(s"test ${SyncExternalUser.name}") {
scenario(s"We will test ${SyncExternalUser.name}", SyncExternalUser, VersionOfApi) {
val request = (v5_1_0_Request / "users" / resourceUser2.provider / resourceUser2.idGivenByProvider / "sync").GET
// Anonymous call fails
val response = makePostRequest(request, write(""))
response.code should equal(401)
response.body.extract[ErrorMessage].message should equal(UserNotLoggedIn)
}
scenario("We will call the endpoint with user credentials", SyncExternalUser, VersionOfApi) {
When(s"We make a request $SyncExternalUser")
val requestGet = (v5_1_0_Request / "users" / resourceUser2.provider / resourceUser2.idGivenByProvider / "sync").GET <@(user1)
val response = makePostRequest(requestGet, write(""))
Then("We should get a 403")
response.code should equal(403)
val errorMessage = UserHasMissingRoles + s"$CanSyncUser"
response.body.extract[ErrorMessage].message contains errorMessage should be(true)
}
}
}