feature/added the emailToSpaceMapping and grantEntitlementsToUseDynamicEndpointsAtOneBank methods

This commit is contained in:
hongwei 2021-06-29 14:38:50 +02:00
parent d6a44e9ef4
commit 0b08199b73
9 changed files with 98 additions and 18 deletions

View File

@ -29,11 +29,12 @@ package code.api
import java.util.Date
import code.api.util.APIUtil._
import code.api.util.ErrorMessages.InvalidDirectLoginParameters
import code.api.util.NewStyle.HttpCode
import code.api.util._
import code.consumer.Consumers._
import code.model.dataAccess.AuthUser
import code.model.{Consumer, Token, TokenType}
import code.model.{Consumer, Token, TokenType, UserX}
import code.token.Tokens
import code.util.Helper.{MdcLoggable, SILENCE_IS_GOLDEN}
import com.nimbusds.jwt.JWTClaimsSet
@ -43,6 +44,7 @@ import net.liftweb.common._
import net.liftweb.http._
import net.liftweb.http.rest.RestHelper
import net.liftweb.util.Helpers
import net.liftweb.util.Helpers.tryo
import scala.compat.Platform
import scala.concurrent.Future
@ -93,9 +95,21 @@ object DirectLogin extends RestHelper with MdcLoggable {
{
//Handling get request for a token
case Req("my" :: "logins" :: "direct" :: Nil,_ , PostRequest) => {
for(
(httpCode: Int, message: String) <- createTokenFuture(getAllParameters)
) yield {
for{
(httpCode: Int, message: String, userId:Long) <- createTokenFuture(getAllParameters)
_ <- if(!emailToSpaceMapping.isEmpty){
for{
resourceUser <- Future {UserX.findByResourceUserId(userId).openOrThrowException(s"$InvalidDirectLoginParameters can not find the resourceUser!")}
authUser <- Future { AuthUser.findUserByUsernameLocally(resourceUser.name).openOrThrowException(s"$InvalidDirectLoginParameters can not find the auth user!")}
_ <- Future {tryo{AuthUser.grantEntitlementsToUseDynamicEndpointsAtOneBank(authUser, None)}
.openOr(logger.error(s"$authUser.directLogin.grantEntitlementsToUseDynamicEndpointsAtOneBank throw exception!"))}
} yield{
""
}
} else{
Future.successful("")
}
} yield {
if (httpCode == 200) {
(JSONFactory.createTokenJSON(message), HttpCode.`201`(CallContext()))
} else {
@ -110,7 +124,7 @@ object DirectLogin extends RestHelper with MdcLoggable {
* @param allParameters map {"username": "some_username", "password": "some_password", "consumer_key": "some_consumer_key"}
* @return httpCode and token value
*/
def createTokenFuture(allParameters: Map[String, String]): Future[(Int, String)] = {
def createTokenFuture(allParameters: Map[String, String]): Future[(Int, String, Long)] = {
val httpMethod = S.request match {
case Full(r) => r.request.method
case _ => "GET"
@ -134,12 +148,11 @@ object DirectLogin extends RestHelper with MdcLoggable {
createTokenCommonPart(httpCode, message, directLoginParameters)
}
def createTokenCommonPart(code: Int, msg: String, directLoginParameters: Map[String, String]): (Int, String) = {
def createTokenCommonPart(code: Int, msg: String, directLoginParameters: Map[String, String]): (Int, String, Long) = {
var message = msg
var httpCode = code
val userId: Long = (for {id <- getUserId(directLoginParameters)} yield id).getOrElse(0)
if (httpCode == 200) {
val userId: Long = (for {id <- getUserId(directLoginParameters)} yield id).getOrElse(0)
if (userId == 0) {
message = ErrorMessages.InvalidLoginCredentials
httpCode = 401
@ -164,7 +177,7 @@ object DirectLogin extends RestHelper with MdcLoggable {
}
}
}
(httpCode, message)
(httpCode, message, userId)
}
def getHttpMethod = S.request match {

View File

@ -523,6 +523,11 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
def getHeaders() = getHeadersCommonPart() ::: getGatewayResponseHeader()
case class CustomResponseHeaders(list: List[(String, String)])
//This is used for get the value from props `email_to_space_mapping`
case class EmailToSpaceMapping(
domain: String,
bank_ids: List[String]
)
//Note: changed noContent--> defaultSuccess, because of the Swagger format. (Not support empty in DataType, maybe fix it latter.)
def noContentJsonResponse(implicit headers: CustomResponseHeaders = CustomResponseHeaders(Nil)) : JsonResponse =
@ -2836,6 +2841,10 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
)
} map {
x =>
//TODO due to performance issue, first comment this out,
// val authUser = AuthUser.findUserByUsernameLocally(x._1.head.name).openOrThrowException("")
// tryo{AuthUser.grantEntitlementsToUseDynamicEndpointsAtOneBank(authUser, x._2)}.openOr(logger.error(s"${x._1} authenticatedAccess.grantEntitlementsToUseDynamicEndpointsAtOneBank throw exception! "))
// make sure, if `refreshUserIfRequired` throw exception, do not break the `authenticatedAccess`,
// TODO better move `refreshUserIfRequired` to other place.
tryo{refreshUserIfRequired(x._1,x._2)}.openOr(logger.error(s"${x._1} authenticatedAccess.refreshUserIfRequired throw exception! "))
@ -3983,4 +3992,11 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
val berlinGroupV13AliasPath = APIUtil.getPropsValue("berlin_group_v1.3_alias.path","").split("/").toList.map(_.trim)
val getAtmsIsPublic = APIUtil.getPropsAsBoolValue("apiOptions.getAtmsIsPublic", true)
def emailToSpaceMapping = {
//TODO, error handling,
val emailToSpaceMapping = APIUtil.getPropsValue("email_to_space_mapping")
val emailToSpaceMappingJson = emailToSpaceMapping.map(json.parse(_))
emailToSpaceMappingJson.map(_.extractOrElse[List[EmailToSpaceMapping]](Nil)).getOrElse(Nil)
}
}

View File

@ -85,6 +85,13 @@ object DynamicEndpointHelper extends RestHelper {
roles
}
def listOfRolesToUseAllDynamicEndpointsAOneBank(bankId: Option[String]): List[ApiRole] = {
val foundInfos: List[DynamicEndpointInfo] = DynamicEndpointProvider.connectorMethodProvider.vend.getAll(bankId)
.map(dynamicEndpoint => buildDynamicEndpointInfo(dynamicEndpoint.swaggerString, dynamicEndpoint.dynamicEndpointId.get, dynamicEndpoint.bankId))
foundInfos.map(getRoles(_)).flatten.toSet.toList
}
def getRoles(dynamicEndpointInfo: DynamicEndpointInfo): List[ApiRole] =
for {
resourceDoc <- dynamicEndpointInfo.resourceDocs.toList

View File

@ -30,7 +30,7 @@ trait EntitlementProvider {
def getEntitlementsByRole(roleName: String): Box[List[Entitlement]]
def getEntitlementsFuture() : Future[Box[List[Entitlement]]]
def getEntitlementsByRoleFuture(roleName: String) : Future[Box[List[Entitlement]]]
def addEntitlement(bankId: String, userId: String, roleName: String) : Box[Entitlement]
def addEntitlement(bankId: String, userId: String, roleName: String, createdByProcess: String="manual") : Box[Entitlement]
def deleteDynamicEntityEntitlement(entityName: String, bankId:Option[String]) : Box[Boolean]
def deleteEntitlements(entityNames: List[String]) : Box[Boolean]
}
@ -40,6 +40,7 @@ trait Entitlement {
def bankId : String
def userId : String
def roleName : String
def createdByProcess : String
}
class RemotedataEntitlementsCaseClasses {
@ -53,7 +54,7 @@ class RemotedataEntitlementsCaseClasses {
case class getEntitlementsByRole(roleName: String)
case class getEntitlementsFuture()
case class getEntitlementsByRoleFuture(roleName: String)
case class addEntitlement(bankId: String, userId: String, roleName: String)
case class addEntitlement(bankId: String, userId: String, roleName: String, createdByProcess: String="manual")
case class deleteDynamicEntityEntitlement(entityName: String, bankId:Option[String])
case class deleteEntitlements(entityNames: List[String])
}

View File

@ -102,12 +102,13 @@ object MappedEntitlementsProvider extends EntitlementProvider {
}
}
override def addEntitlement(bankId: String, userId: String, roleName: String): Box[Entitlement] = {
override def addEntitlement(bankId: String, userId: String, roleName: String, createdByProcess: String ="manual"): Box[Entitlement] = {
// Return a Box so we can handle errors later.
val addEntitlement = MappedEntitlement.create
.mBankId(bankId)
.mUserId(userId)
.mRoleName(roleName)
.mCreatedByProcess(createdByProcess)
.saveMe()
Some(addEntitlement)
}
@ -122,11 +123,14 @@ class MappedEntitlement extends Entitlement
object mBankId extends UUIDString(this)
object mUserId extends UUIDString(this)
object mRoleName extends MappedString(this, 64)
object mCreatedByProcess extends MappedString(this, 255)
override def entitlementId: String = mEntitlementId.get.toString
override def bankId: String = mBankId.get
override def userId: String = mUserId.get
override def roleName: String = mRoleName.get
override def createdByProcess: String =
if(mCreatedByProcess.get == null || mCreatedByProcess.get.isEmpty) "manual" else mCreatedByProcess.get
}

View File

@ -31,9 +31,11 @@ import code.accountholders.AccountHolders
import code.api.util.APIUtil.{hasAnOAuthHeader, isValidStrongPassword, logger, _}
import code.api.util.ErrorMessages._
import code.api.util._
import code.api.v4_0_0.dynamic.DynamicEndpointHelper
import code.api.{APIFailure, DirectLogin, GatewayLogin, OAuthHandshake}
import code.bankconnectors.Connector
import code.context.UserAuthContextProvider
import code.entitlement.Entitlement
import code.loginattempts.LoginAttempt
import code.users.Users
import code.util.Helper
@ -926,6 +928,10 @@ def restoreSomeSessions(): Unit = {
logUserIn(user, () => {
S.notice(S.?("logged.in"))
preLoginState()
if(!emailToSpaceMapping.isEmpty){
tryo{AuthUser.grantEntitlementsToUseDynamicEndpointsAtOneBank(user, None)}
.openOr(logger.error(s"${user} authenticatedAccess.grantEntitlementsToUseDynamicEndpointsAtOneBank throw exception! "))
}
S.redirectTo(redirect)
})
} else {
@ -1104,6 +1110,39 @@ def restoreSomeSessions(): Unit = {
} yield v
}
}
/**
* Spaces is the obp BankIds, each bank can create many dynamice endpoints, all of them are belong to one Bank.(Space)
* @param emailToSpaceMappings
* @return
*/
def mySpaces(user: AuthUser, emailToSpaceMappings: List[EmailToSpaceMapping]): List[BankId] ={
//1st: first check the user is validated
if (user.validated_?) {
//userEmail = robert.uk.29@example.com
// 2st get the email domain - `example.com`
val emailDomain = user.email.get.split("@").last
//3 return the bankIds
emailToSpaceMappings.find(_.domain==emailDomain).map(_.bank_ids).getOrElse(Nil).map(BankId(_))
} else {
Nil
}
}
def grantEntitlementsToUseDynamicEndpointsAtOneBank(user: AuthUser, callContext: Option[CallContext]) = {
val userId = user.user.obj.map(_.userId).getOrElse("")
//call mySpaces --> get BankIds --> listOfRolesToUseAllDynamicEndpointsAOneBank (at each bank)--> Grant roles (for each role)
for{
bankId <- mySpaces(user: AuthUser, emailToSpaceMapping)
role <- DynamicEndpointHelper.listOfRolesToUseAllDynamicEndpointsAOneBank(Some(bankId.value))
}yield{
//TODO, later we can add a diff here: we need create new roles, and remove the out of date ones.
if (!hasEntitlement(bankId.value, userId, role)) {
Entitlement.entitlement.vend.addEntitlement(bankId.value, userId, role.toString,"grantEntitlementsToUseDynamicEndpointsAtOneBank")
}
}
}
/**
* This is a helper method

View File

@ -48,8 +48,8 @@ object RemotedataEntitlements extends ObpActorInit with EntitlementProvider {
def getEntitlementsByRoleFuture(roleName: String) : Future[Box[List[Entitlement]]] =
(actor ? cc.getEntitlementsByRoleFuture(roleName)).mapTo[Box[List[Entitlement]]]
def addEntitlement(bankId: String, userId: String, roleName: String) : Box[Entitlement] = getValueFromFuture(
(actor ? cc.addEntitlement(bankId, userId, roleName)).mapTo[Box[Entitlement]]
def addEntitlement(bankId: String, userId: String, roleName: String, createdByProcess: String="manual") : Box[Entitlement] = getValueFromFuture(
(actor ? cc.addEntitlement(bankId, userId, roleName, createdByProcess: String)).mapTo[Box[Entitlement]]
)
override def deleteDynamicEntityEntitlement(entityName: String, bankId:Option[String]): Box[Boolean] = getValueFromFuture(

View File

@ -55,9 +55,9 @@ class RemotedataEntitlementsActor extends Actor with ObpActorHelper with MdcLogg
logger.debug(s"getEntitlementsByRole($role)")
sender ! (mapper.getEntitlementsByRole(role))
case cc.addEntitlement(bankId: String, userId: String, roleName: String) =>
logger.debug(s"addEntitlement($bankId, $userId, $roleName)")
sender ! (mapper.addEntitlement(bankId, userId, roleName))
case cc.addEntitlement(bankId: String, userId: String, roleName: String, createdByProcess: String) =>
logger.debug(s"addEntitlement($bankId, $userId, $roleName, $createdByProcess)")
sender ! (mapper.addEntitlement(bankId, userId, roleName, createdByProcess: String))
case cc.deleteDynamicEntityEntitlement(entityName: String, bankId:Option[String]) =>
logger.debug(s"deleteDynamicEntityEntitlement($entityName) bankId($bankId)")

View File

@ -474,7 +474,7 @@ class ConsumerRegistration extends MdcLoggable {
while(matcher.find()) {
val userName = matcher.group(1)
val password = matcher.group(2)
val (code, token) = DirectLogin.createToken(Map(("username", userName), ("password", password), ("consumer_key", consumerKey)))
val (code, token, userId) = DirectLogin.createToken(Map(("username", userName), ("password", password), ("consumer_key", consumerKey)))
val authHeader = code match {
case 200 => (userName, password) -> s"""Authorization: DirectLogin token="$token""""
case _ => (userName, password) -> "username or password is invalid, generate token fail"