From 0b08199b734ea4d70ab149b33b52b761bc3f1bcd Mon Sep 17 00:00:00 2001 From: hongwei Date: Tue, 29 Jun 2021 14:38:50 +0200 Subject: [PATCH] feature/added the emailToSpaceMapping and grantEntitlementsToUseDynamicEndpointsAtOneBank methods --- .../src/main/scala/code/api/directlogin.scala | 31 ++++++++++----- .../main/scala/code/api/util/APIUtil.scala | 16 ++++++++ .../dynamic/DynamicEndpointHelper.scala | 7 ++++ .../scala/code/entitlement/Entilement.scala | 5 ++- .../code/entitlement/MappedEntitlements.scala | 6 ++- .../code/model/dataAccess/AuthUser.scala | 39 +++++++++++++++++++ .../remotedata/RemotedataEntitlements.scala | 4 +- .../RemotedataEntitlementsActor.scala | 6 +-- .../code/snippet/ConsumerRegistration.scala | 2 +- 9 files changed, 98 insertions(+), 18 deletions(-) diff --git a/obp-api/src/main/scala/code/api/directlogin.scala b/obp-api/src/main/scala/code/api/directlogin.scala index 6d3118e24..51924caad 100644 --- a/obp-api/src/main/scala/code/api/directlogin.scala +++ b/obp-api/src/main/scala/code/api/directlogin.scala @@ -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 { diff --git a/obp-api/src/main/scala/code/api/util/APIUtil.scala b/obp-api/src/main/scala/code/api/util/APIUtil.scala index 6113ba081..00a5fc16d 100644 --- a/obp-api/src/main/scala/code/api/util/APIUtil.scala +++ b/obp-api/src/main/scala/code/api/util/APIUtil.scala @@ -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) + } } diff --git a/obp-api/src/main/scala/code/api/v4_0_0/dynamic/DynamicEndpointHelper.scala b/obp-api/src/main/scala/code/api/v4_0_0/dynamic/DynamicEndpointHelper.scala index 818c388e7..e53ec3278 100644 --- a/obp-api/src/main/scala/code/api/v4_0_0/dynamic/DynamicEndpointHelper.scala +++ b/obp-api/src/main/scala/code/api/v4_0_0/dynamic/DynamicEndpointHelper.scala @@ -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 diff --git a/obp-api/src/main/scala/code/entitlement/Entilement.scala b/obp-api/src/main/scala/code/entitlement/Entilement.scala index 608971472..3c1489767 100644 --- a/obp-api/src/main/scala/code/entitlement/Entilement.scala +++ b/obp-api/src/main/scala/code/entitlement/Entilement.scala @@ -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]) } diff --git a/obp-api/src/main/scala/code/entitlement/MappedEntitlements.scala b/obp-api/src/main/scala/code/entitlement/MappedEntitlements.scala index 133bf203a..7b39f1a70 100644 --- a/obp-api/src/main/scala/code/entitlement/MappedEntitlements.scala +++ b/obp-api/src/main/scala/code/entitlement/MappedEntitlements.scala @@ -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 } diff --git a/obp-api/src/main/scala/code/model/dataAccess/AuthUser.scala b/obp-api/src/main/scala/code/model/dataAccess/AuthUser.scala index 573647bd9..dadeba934 100644 --- a/obp-api/src/main/scala/code/model/dataAccess/AuthUser.scala +++ b/obp-api/src/main/scala/code/model/dataAccess/AuthUser.scala @@ -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 diff --git a/obp-api/src/main/scala/code/remotedata/RemotedataEntitlements.scala b/obp-api/src/main/scala/code/remotedata/RemotedataEntitlements.scala index 3a1cf3414..ed68b4d5f 100644 --- a/obp-api/src/main/scala/code/remotedata/RemotedataEntitlements.scala +++ b/obp-api/src/main/scala/code/remotedata/RemotedataEntitlements.scala @@ -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( diff --git a/obp-api/src/main/scala/code/remotedata/RemotedataEntitlementsActor.scala b/obp-api/src/main/scala/code/remotedata/RemotedataEntitlementsActor.scala index 1081c2d5b..6869b7d7c 100644 --- a/obp-api/src/main/scala/code/remotedata/RemotedataEntitlementsActor.scala +++ b/obp-api/src/main/scala/code/remotedata/RemotedataEntitlementsActor.scala @@ -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)") diff --git a/obp-api/src/main/scala/code/snippet/ConsumerRegistration.scala b/obp-api/src/main/scala/code/snippet/ConsumerRegistration.scala index fdaece6eb..bcda3e9f7 100644 --- a/obp-api/src/main/scala/code/snippet/ConsumerRegistration.scala +++ b/obp-api/src/main/scala/code/snippet/ConsumerRegistration.scala @@ -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"