diff --git a/obp-api/src/main/scala/code/api/OAuth2.scala b/obp-api/src/main/scala/code/api/OAuth2.scala index 768637cd2..06273dbe0 100644 --- a/obp-api/src/main/scala/code/api/OAuth2.scala +++ b/obp-api/src/main/scala/code/api/OAuth2.scala @@ -166,11 +166,10 @@ object OAuth2Login extends RestHelper with MdcLoggable { } } - - val user = Users.users.vend.getUserByUserName(introspectOAuth2Token.getSub) + val user = Users.users.vend.getUserByUserName(hydraPublicUrl, introspectOAuth2Token.getSub) user match { case Full(u) => - LoginAttempt.userIsLocked(u.name) match { + LoginAttempt.userIsLocked(u.provider, u.name) match { case true => (Failure(UsernameHasBeenLocked), Some(cc.copy(consumer = consumer))) case false => (Full(u), Some(cc.copy(consumer = consumer))) } @@ -349,7 +348,7 @@ object OAuth2Login extends RestHelper with MdcLoggable { case Full(_) => val user = IdentityProviderCommon.getOrCreateResourceUser(value) val consumer = IdentityProviderCommon.getOrCreateConsumer(value, user.map(_.userId)) - LoginAttempt.userIsLocked(user.map(_.name).getOrElse("")) match { + LoginAttempt.userIsLocked(user.map(_.provider).getOrElse(""), user.map(_.name).getOrElse("")) match { case true => ((Failure(UsernameHasBeenLocked), Some(cc.copy(consumer = consumer)))) case false => (user, Some(cc.copy(consumer = consumer))) } @@ -368,7 +367,7 @@ object OAuth2Login extends RestHelper with MdcLoggable { user <- IdentityProviderCommon.getOrCreateResourceUserFuture(value) consumer <- Future{IdentityProviderCommon.getOrCreateConsumer(value, user.map(_.userId))} } yield { - LoginAttempt.userIsLocked(user.map(_.name).getOrElse("")) match { + LoginAttempt.userIsLocked(user.map(_.provider).getOrElse(""), user.map(_.name).getOrElse("")) match { case true => ((Failure(UsernameHasBeenLocked), Some(cc.copy(consumer = consumer)))) case false => (user, Some(cc.copy(consumer = consumer))) } diff --git a/obp-api/src/main/scala/code/api/OBPRestHelper.scala b/obp-api/src/main/scala/code/api/OBPRestHelper.scala index 226d544bd..317b5573b 100644 --- a/obp-api/src/main/scala/code/api/OBPRestHelper.scala +++ b/obp-api/src/main/scala/code/api/OBPRestHelper.scala @@ -352,7 +352,7 @@ trait OBPRestHelper extends RestHelper with MdcLoggable { if(u.isDeleted.getOrElse(false)) { Failure(UserIsDeleted) // The user is DELETED. } else { - LoginAttempt.userIsLocked(u.name) match { + LoginAttempt.userIsLocked(u.provider, u.name) match { case true => Failure(UsernameHasBeenLocked) // The user is LOCKED. case false => function(callContext) // All good } diff --git a/obp-api/src/main/scala/code/api/dauth.scala b/obp-api/src/main/scala/code/api/dauth.scala index e23240428..4b30dde93 100755 --- a/obp-api/src/main/scala/code/api/dauth.scala +++ b/obp-api/src/main/scala/code/api/dauth.scala @@ -139,7 +139,7 @@ object DAuth extends RestHelper with MdcLoggable { logger.debug("login_user_name: " + userName) for { tuple <- - UserX.getOrCreateDauthResourceUser(userName, provider) match { + UserX.getOrCreateDauthResourceUser(provider, userName) match { case Full(u) => Full((u,callContext)) // Return user case Empty => @@ -159,7 +159,7 @@ object DAuth extends RestHelper with MdcLoggable { logger.debug("login_user_name: " + username) for { - tuple <- Future { UserX.getOrCreateDauthResourceUser(username, provider)} map { + tuple <- Future { UserX.getOrCreateDauthResourceUser(provider, username)} map { case (Full(u)) => Full(u, callContext) // Return user case (Empty) => diff --git a/obp-api/src/main/scala/code/api/openidconnect.scala b/obp-api/src/main/scala/code/api/openidconnect.scala index 09aa4534d..9b6475448 100644 --- a/obp-api/src/main/scala/code/api/openidconnect.scala +++ b/obp-api/src/main/scala/code/api/openidconnect.scala @@ -123,7 +123,7 @@ object OpenIdConnect extends OBPRestHelper with MdcLoggable { JwtUtil.validateIdToken(idToken, OpenIdConnectConfig.get(identityProvider).jwks_uri) match { case Full(_) => getOrCreateResourceUser(idToken) match { - case Full(user) if LoginAttempt.userIsLocked(user.name) => // User is locked + case Full(user) if LoginAttempt.userIsLocked(user.provider, user.name) => // User is locked (401, ErrorMessages.UsernameHasBeenLocked, None) case Full(user) => // All good getOrCreateAuthUser(user) match { diff --git a/obp-api/src/main/scala/code/api/util/AfterApiAuth.scala b/obp-api/src/main/scala/code/api/util/AfterApiAuth.scala index 43b31ef96..06934a848 100644 --- a/obp-api/src/main/scala/code/api/util/AfterApiAuth.scala +++ b/obp-api/src/main/scala/code/api/util/AfterApiAuth.scala @@ -68,7 +68,7 @@ object AfterApiAuth extends MdcLoggable{ if (u.isDeleted.getOrElse(false)) { (Failure(UserIsDeleted), cc) // The user is DELETED. } else { - LoginAttempt.userIsLocked(u.name) match { + LoginAttempt.userIsLocked(u.provider, u.name) match { case true => (Failure(UsernameHasBeenLocked), cc) // The user is LOCKED. case false => (user, cc) // All good } diff --git a/obp-api/src/main/scala/code/api/util/ErrorMessages.scala b/obp-api/src/main/scala/code/api/util/ErrorMessages.scala index dd7dc501c..fb8bcda78 100644 --- a/obp-api/src/main/scala/code/api/util/ErrorMessages.scala +++ b/obp-api/src/main/scala/code/api/util/ErrorMessages.scala @@ -151,7 +151,7 @@ object ErrorMessages { val ScopeNotFound = "OBP-20025: Scope not found. Please specify a valid value for SCOPE_ID." val ConsumerDoesNotHaveScope = "OBP-20026: CONSUMER_ID does not have the SCOPE_ID " - val UserNotFoundByUsername = "OBP-20027: User not found by username." + val UserNotFoundByProviderAndUsername = "OBP-20027: User not found by provider and username." val GatewayLoginMissingParameters = "OBP-20028: These GatewayLogin parameters are missing:" val GatewayLoginUnknownError = "OBP-20029: Unknown Gateway login error." val GatewayLoginHostPropertyMissing = "OBP-20030: Property gateway.host is not defined." @@ -721,7 +721,7 @@ object ErrorMessages { UserNoPermissionAccessView -> 403, UserNotSuperAdminOrMissRole -> 403, ConsumerHasMissingRoles -> 403, - UserNotFoundByUsername -> 404, + UserNotFoundByProviderAndUsername -> 404, ApplicationNotIdentified -> 401, CouldNotExchangeAuthorizationCodeForTokens -> 401, CouldNotSaveOpenIDConnectUser -> 401, diff --git a/obp-api/src/main/scala/code/api/util/NewStyle.scala b/obp-api/src/main/scala/code/api/util/NewStyle.scala index 7cca51207..fbe1b865b 100644 --- a/obp-api/src/main/scala/code/api/util/NewStyle.scala +++ b/obp-api/src/main/scala/code/api/util/NewStyle.scala @@ -1134,8 +1134,8 @@ object NewStyle extends MdcLoggable{ } } - def getOrCreateResourceUser(username: String, provider: String, callContext: Option[CallContext]): OBPReturnType[User] = { - Future { UserX.getOrCreateDauthResourceUser(username, provider).map(user =>(user, callContext))} map { + def getOrCreateResourceUser(provider: String, username: String, callContext: Option[CallContext]): OBPReturnType[User] = { + Future { UserX.getOrCreateDauthResourceUser(provider, username).map(user =>(user, callContext))} map { unboxFullOrFail(_, callContext, s"$CannotGetOrCreateUser Current USERName($username) PROVIDER ($provider)", 404) } } diff --git a/obp-api/src/main/scala/code/api/util/migration/Migration.scala b/obp-api/src/main/scala/code/api/util/migration/Migration.scala index a38a8a1f8..1b7efd691 100644 --- a/obp-api/src/main/scala/code/api/util/migration/Migration.scala +++ b/obp-api/src/main/scala/code/api/util/migration/Migration.scala @@ -96,6 +96,7 @@ object Migration extends MdcLoggable { alterMappedExpectedChallengeAnswerChallengeTypeLength() alterTransactionRequestChallengeChallengeTypeLength() alterMappedCustomerAttribute(startedBeforeSchemifier) + dropMappedBadLoginAttemptIndex() } private def dummyScript(): Boolean = { @@ -431,6 +432,13 @@ object Migration extends MdcLoggable { } } } + + private def dropMappedBadLoginAttemptIndex(): Boolean = { + val name = nameOf(dropMappedBadLoginAttemptIndex) + runOnce(name) { + MigrationOfMappedBadLoginAttemptDropIndex.dropUniqueIndex(name) + } + } } /** diff --git a/obp-api/src/main/scala/code/api/util/migration/MigrationOfMappedBadLoginAttemptDropIndex.scala b/obp-api/src/main/scala/code/api/util/migration/MigrationOfMappedBadLoginAttemptDropIndex.scala new file mode 100644 index 000000000..c29e71e68 --- /dev/null +++ b/obp-api/src/main/scala/code/api/util/migration/MigrationOfMappedBadLoginAttemptDropIndex.scala @@ -0,0 +1,83 @@ +package code.api.util.migration + +import code.api.Constant +import code.api.util.APIUtil +import code.api.util.migration.Migration.{DbFunction, saveLog} +import code.loginattempts.MappedBadLoginAttempt +import net.liftweb.mapper.{DB, Schemifier} +import net.liftweb.util.DefaultConnectionIdentifier +import scalikejdbc.DB.CPContext +import scalikejdbc._ + +import java.time.format.DateTimeFormatter +import java.time.{ZoneId, ZonedDateTime} + +object MigrationOfMappedBadLoginAttemptDropIndex { + + val oneDayAgo = ZonedDateTime.now(ZoneId.of("UTC")).minusDays(1) + val oneYearInFuture = ZonedDateTime.now(ZoneId.of("UTC")).plusYears(1) + val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm'Z'") + + private lazy val getDbConnectionParameters: (String, String, String) = { + val dbUrl = APIUtil.getPropsValue("db.url") openOr Constant.h2DatabaseDefaultUrlValue + val username = dbUrl.split(";").filter(_.contains("user")).toList.headOption.map(_.split("=")(1)) + val password = dbUrl.split(";").filter(_.contains("password")).toList.headOption.map(_.split("=")(1)) + val dbUser = APIUtil.getPropsValue("db.user").orElse(username) + val dbPassword = APIUtil.getPropsValue("db.password").orElse(password) + (dbUrl, dbUser.getOrElse(""), dbPassword.getOrElse("")) + } + + /** + * this connection pool context corresponding db.url in default.props + */ + implicit lazy val context: CPContext = { + val settings = ConnectionPoolSettings( + initialSize = 5, + maxSize = 20, + connectionTimeoutMillis = 3000L, + validationQuery = "select 1", + connectionPoolFactoryName = "commons-dbcp2" + ) + val (dbUrl, user, password) = getDbConnectionParameters + val dbName = "DB_NAME" // corresponding props db.url DB + ConnectionPool.add(dbName, dbUrl, user, password, settings) + val connectionPool = ConnectionPool.get(dbName) + MultipleConnectionPoolContext(ConnectionPool.DEFAULT_NAME -> connectionPool) + } + + def dropUniqueIndex(name: String): Boolean = { + DbFunction.tableExists(MappedBadLoginAttempt, (DB.use(DefaultConnectionIdentifier){ conn => conn})) match { + case true => + val startDate = System.currentTimeMillis() + val commitId: String = APIUtil.gitCommit + var isSuccessful = false + + val executedSql = + DbFunction.maybeWrite(true, Schemifier.infoF _, DB.use(DefaultConnectionIdentifier){ conn => conn}) { + APIUtil.getPropsValue("db.driver") match { + case _ => + () => "DROP INDEX IF EXISTS mappedbadloginattempt_musername;" + } + } + + val endDate = System.currentTimeMillis() + val comment: String = + s"""Executed SQL: + |$executedSql + |""".stripMargin + isSuccessful = true + saveLog(name, commitId, isSuccessful, startDate, endDate, comment) + isSuccessful + + case false => + val startDate = System.currentTimeMillis() + val commitId: String = APIUtil.gitCommit + val isSuccessful = false + val endDate = System.currentTimeMillis() + val comment: String = + s"""${MappedBadLoginAttempt._dbTableNameLC} table does not exist""".stripMargin + saveLog(name, commitId, isSuccessful, startDate, endDate, comment) + isSuccessful + } + } +} diff --git a/obp-api/src/main/scala/code/api/v3_0_0/APIMethods300.scala b/obp-api/src/main/scala/code/api/v3_0_0/APIMethods300.scala index 36c6c1e27..7c2dbe2c0 100644 --- a/obp-api/src/main/scala/code/api/v3_0_0/APIMethods300.scala +++ b/obp-api/src/main/scala/code/api/v3_0_0/APIMethods300.scala @@ -3,7 +3,7 @@ package code.api.v3_0_0 import java.util.regex.Pattern import code.accountattribute.AccountAttributeX import code.accountholders.AccountHolders -import code.api.APIFailureNewStyle +import code.api.{APIFailureNewStyle, Constant} import code.api.Constant.{PARAM_LOCALE, PARAM_TIMESTAMP} import code.api.ResourceDocs1_4_0.SwaggerDefinitionsJSON import code.api.ResourceDocs1_4_0.SwaggerDefinitionsJSON.{bankJSON, banksJSON, branchJsonV300, _} @@ -975,7 +975,7 @@ trait APIMethods300 { """.stripMargin, EmptyBody, usersJsonV200, - List(UserNotLoggedIn, UserHasMissingRoles, UserNotFoundByUsername, UnknownError), + List(UserNotLoggedIn, UserHasMissingRoles, UserNotFoundByProviderAndUsername, UnknownError), List(apiTagUser, apiTagNewStyle), Some(List(canGetAnyUser))) @@ -986,8 +986,8 @@ trait APIMethods300 { for { (Full(u), callContext) <- authenticatedAccess(cc) _ <- NewStyle.function.hasEntitlement("", u.userId, ApiRole.canGetAnyUser, callContext) - user <- Users.users.vend.getUserByUserNameFuture(username) map { - x => unboxFullOrFail(x, callContext, UserNotFoundByUsername, 404) + user <- Users.users.vend.getUserByProviderAndUsernameFuture(Constant.localIdentityProvider, username) map { + x => unboxFullOrFail(x, callContext, UserNotFoundByProviderAndUsername, 404) } entitlements <- NewStyle.function.getEntitlementsByUserId(user.userId, callContext) } yield { diff --git a/obp-api/src/main/scala/code/api/v3_1_0/APIMethods310.scala b/obp-api/src/main/scala/code/api/v3_1_0/APIMethods310.scala index 2601cffde..e3e374577 100644 --- a/obp-api/src/main/scala/code/api/v3_1_0/APIMethods310.scala +++ b/obp-api/src/main/scala/code/api/v3_1_0/APIMethods310.scala @@ -1,5 +1,7 @@ package code.api.v3_1_0 +import code.api.Constant.localIdentityProvider + import java.text.SimpleDateFormat import java.util.UUID import java.util.regex.Pattern @@ -499,7 +501,7 @@ trait APIMethods310 { |""".stripMargin, EmptyBody, badLoginStatusJson, - List(UserNotLoggedIn, UserNotFoundByUsername, UserHasMissingRoles, UnknownError), + List(UserNotLoggedIn, UserNotFoundByProviderAndUsername, UserHasMissingRoles, UnknownError), List(apiTagUser, apiTagNewStyle), Some(List(canReadUserLockedStatus)) ) @@ -511,7 +513,7 @@ trait APIMethods310 { for { (Full(u), callContext) <- authenticatedAccess(cc) _ <- NewStyle.function.hasEntitlement("", u.userId, ApiRole.canReadUserLockedStatus, callContext) - badLoginStatus <- Future { LoginAttempt.getBadLoginStatus(username) } map { unboxFullOrFail(_, callContext, s"$UserNotFoundByUsername($username)", 404) } + badLoginStatus <- Future { LoginAttempt.getBadLoginStatus(localIdentityProvider, username) } map { unboxFullOrFail(_, callContext, s"$UserNotFoundByProviderAndUsername($username)", 404) } } yield { (createBadLoginStatusJson(badLoginStatus), HttpCode.`200`(callContext)) } @@ -535,7 +537,7 @@ trait APIMethods310 { |""".stripMargin, EmptyBody, badLoginStatusJson, - List(UserNotLoggedIn, UserNotFoundByUsername, UserHasMissingRoles, UnknownError), + List(UserNotLoggedIn, UserNotFoundByProviderAndUsername, UserHasMissingRoles, UnknownError), List(apiTagUser, apiTagNewStyle), Some(List(canUnlockUser))) @@ -546,9 +548,9 @@ trait APIMethods310 { for { (Full(u), callContext) <- authenticatedAccess(cc) _ <- NewStyle.function.hasEntitlement("", u.userId, ApiRole.canUnlockUser, callContext) - _ <- Future { LoginAttempt.resetBadLoginAttempts(username) } - _ <- Future { UserLocksProvider.unlockUser(username) } - badLoginStatus <- Future { LoginAttempt.getBadLoginStatus(username) } map { unboxFullOrFail(_, callContext, s"$UserNotFoundByUsername($username)", 404) } + _ <- Future { LoginAttempt.resetBadLoginAttempts(localIdentityProvider,username) } + _ <- Future { UserLocksProvider.unlockUser(localIdentityProvider,username) } + badLoginStatus <- Future { LoginAttempt.getBadLoginStatus(localIdentityProvider, username) } map { unboxFullOrFail(_, callContext, s"$UserNotFoundByProviderAndUsername($username)", 404) } } yield { (createBadLoginStatusJson(badLoginStatus), HttpCode.`200`(callContext)) } diff --git a/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala b/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala index e656f4fe5..ec74a2047 100644 --- a/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala +++ b/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala @@ -7,7 +7,7 @@ import java.util.{Calendar, Date} import code.DynamicData.{DynamicData, DynamicDataProvider} import code.DynamicEndpoint.DynamicEndpointSwagger import code.accountattribute.AccountAttributeX -import code.api.Constant.{PARAM_LOCALE, PARAM_TIMESTAMP} +import code.api.Constant.{PARAM_LOCALE, PARAM_TIMESTAMP, localIdentityProvider} import code.api.ResourceDocs1_4_0.SwaggerDefinitionsJSON import code.api.ResourceDocs1_4_0.SwaggerDefinitionsJSON.{jsonDynamicResourceDoc, _} import code.api.UKOpenBanking.v2_0_0.OBP_UKOpenBanking_200 @@ -2799,7 +2799,7 @@ trait APIMethods400 { |""".stripMargin, EmptyBody, userLockStatusJson, - List($UserNotLoggedIn, UserNotFoundByUsername, UserHasMissingRoles, UnknownError), + List($UserNotLoggedIn, UserNotFoundByProviderAndUsername, UserHasMissingRoles, UnknownError), List(apiTagUser, apiTagNewStyle), Some(List(canLockUser))) @@ -2808,8 +2808,8 @@ trait APIMethods400 { cc => for { (Full(u), callContext) <- SS.user - userLocks <- Future { UserLocksProvider.lockUser(username) } map { - unboxFullOrFail(_, callContext, s"$UserNotFoundByUsername($username)", 404) + userLocks <- Future { UserLocksProvider.lockUser(localIdentityProvider,username) } map { + unboxFullOrFail(_, callContext, s"$UserNotFoundByProviderAndUsername($username)", 404) } } yield { (JSONFactory400.createUserLockStatusJson(userLocks), HttpCode.`200`(callContext)) @@ -2889,7 +2889,7 @@ trait APIMethods400 { canCreateEntitlementAtAnyBankRole = Entitlement.entitlement.vend.getEntitlement("", loggedInUser.userId, canCreateEntitlementAtAnyBank.toString()) - (targetUser, callContext) <- NewStyle.function.getOrCreateResourceUser(postedData.username, postedData.provider, callContext) + (targetUser, callContext) <- NewStyle.function.getOrCreateResourceUser(postedData.provider, postedData.username, callContext) _ <- if (canCreateEntitlementAtAnyBankRole.isDefined) { //If the loggedIn User has `CanCreateEntitlementAtAnyBankRole` role, then we can grant all the requestRoles to the requestUser. @@ -3633,7 +3633,7 @@ trait APIMethods400 { acceptMarketingInfo <- NewStyle.function.getAgreementByUserId(user.userId, "accept_marketing_info", cc.callContext) termsAndConditions <- NewStyle.function.getAgreementByUserId(user.userId, "terms_and_conditions", cc.callContext) privacyConditions <- NewStyle.function.getAgreementByUserId(user.userId, "privacy_conditions", cc.callContext) - isLocked = LoginAttempt.userIsLocked(user.name) + isLocked = LoginAttempt.userIsLocked(user.provider, user.name) } yield { val agreements = acceptMarketingInfo.toList ::: termsAndConditions.toList ::: privacyConditions.toList (JSONFactory400.createUserInfoJSON(user, entitlements, Some(agreements), isLocked), HttpCode.`200`(cc.callContext)) @@ -3657,7 +3657,7 @@ trait APIMethods400 { """.stripMargin, EmptyBody, userJsonV400, - List($UserNotLoggedIn, UserHasMissingRoles, UserNotFoundByUsername, UnknownError), + List($UserNotLoggedIn, UserHasMissingRoles, UserNotFoundByProviderAndUsername, UnknownError), List(apiTagUser, apiTagNewStyle), Some(List(canGetAnyUser))) @@ -3666,11 +3666,11 @@ trait APIMethods400 { case "users" :: "username" :: username :: Nil JsonGet _ => { cc => for { - user <- Users.users.vend.getUserByUserNameFuture(username) map { - x => unboxFullOrFail(x, cc.callContext, UserNotFoundByUsername, 404) + user <- Users.users.vend.getUserByProviderAndUsernameFuture(Constant.localIdentityProvider, username) map { + x => unboxFullOrFail(x, cc.callContext, UserNotFoundByProviderAndUsername, 404) } entitlements <- NewStyle.function.getEntitlementsByUserId(user.userId, cc.callContext) - isLocked = LoginAttempt.userIsLocked(user.name) + isLocked = LoginAttempt.userIsLocked(user.provider, user.name) } yield { (JSONFactory400.createUserInfoJSON(user, entitlements, None, isLocked), HttpCode.`200`(cc.callContext)) } @@ -4405,7 +4405,7 @@ trait APIMethods400 { } _ <- NewStyle.function.canGrantAccessToView(bankId, accountId, cc.loggedInUser, cc.callContext) - (targetUser, callContext) <- NewStyle.function.getOrCreateResourceUser(postJson.username, postJson.provider, cc.callContext) + (targetUser, callContext) <- NewStyle.function.getOrCreateResourceUser(postJson.provider, postJson.username, cc.callContext) views <- getViews(bankId, accountId, postJson, callContext) addedView <- grantMultpleAccountAccessToUser(bankId, accountId, targetUser, views, callContext) } yield { diff --git a/obp-api/src/main/scala/code/api/v4_0_0/JSONFactory4.0.0.scala b/obp-api/src/main/scala/code/api/v4_0_0/JSONFactory4.0.0.scala index 5a99a7df8..634430445 100644 --- a/obp-api/src/main/scala/code/api/v4_0_0/JSONFactory4.0.0.scala +++ b/obp-api/src/main/scala/code/api/v4_0_0/JSONFactory4.0.0.scala @@ -1125,7 +1125,7 @@ object JSONFactory400 { t._1, t._2.getOrElse(Nil), t._3, - LoginAttempt.userIsLocked(t._1.name) + LoginAttempt.userIsLocked(t._1.provider, t._1.name) ) ) ) diff --git a/obp-api/src/main/scala/code/api/v5_1_0/APIMethods510.scala b/obp-api/src/main/scala/code/api/v5_1_0/APIMethods510.scala index f5336ca24..68eba9852 100644 --- a/obp-api/src/main/scala/code/api/v5_1_0/APIMethods510.scala +++ b/obp-api/src/main/scala/code/api/v5_1_0/APIMethods510.scala @@ -2,16 +2,21 @@ package code.api.v5_1_0 import code.api.ResourceDocs1_4_0.SwaggerDefinitionsJSON.{apiCollectionJson400, apiCollectionsJson400, apiInfoJson400, postApiCollectionJson400, revokedConsentJsonV310} +import code.api.ResourceDocs1_4_0.SwaggerDefinitionsJSON._ import code.api.util.APIUtil._ import code.api.util.ApiRole._ import code.api.util.ApiTag._ import code.api.util.ErrorMessages.{$UserNotLoggedIn, BankNotFound, ConsentNotFound, InvalidJsonFormat, UnknownError, UserNotFoundByUserId, UserNotLoggedIn, _} -import code.api.util.NewStyle +import code.api.util.{ApiRole, NewStyle} import code.api.util.NewStyle.HttpCode import code.api.v3_1_0.ConsentJsonV310 +import code.api.v3_1_0.JSONFactory310.createBadLoginStatusJson import code.api.v4_0_0.{JSONFactory400, PostApiCollectionJson400} import code.consent.Consents +import code.loginattempts.LoginAttempt import code.transactionrequests.TransactionRequests.TransactionRequestTypes.{apply => _} +import code.userlocks.UserLocksProvider +import code.users.Users import code.util.Helper import com.github.dwickern.macros.NameOf.nameOf import com.openbankproject.commons.ExecutionContext.Implicits.global @@ -199,7 +204,155 @@ trait APIMethods510 { } + staticResourceDocs += ResourceDoc( + getUserByProviderAndUsername, + implementedInApiVersion, + nameOf(getUserByProviderAndUsername), + "GET", + "/users/provider/PROVIDER/username/USERNAME", + "Get User by USERNAME", + s"""Get user by PROVIDER and USERNAME + | + |${authenticationRequiredMessage(true)} + | + |CanGetAnyUser entitlement is required, + | + """.stripMargin, + EmptyBody, + userJsonV400, + List($UserNotLoggedIn, UserHasMissingRoles, UserNotFoundByProviderAndUsername, UnknownError), + List(apiTagUser, apiTagNewStyle), + Some(List(canGetAnyUser)) + ) + + lazy val getUserByProviderAndUsername: OBPEndpoint = { + case "users" :: "provider" :: provider :: "username" :: username :: Nil JsonGet _ => { + cc => + for { + user <- Users.users.vend.getUserByProviderAndUsernameFuture(provider, username) map { + x => unboxFullOrFail(x, cc.callContext, UserNotFoundByProviderAndUsername, 404) + } + entitlements <- NewStyle.function.getEntitlementsByUserId(user.userId, cc.callContext) + isLocked = LoginAttempt.userIsLocked(user.provider, user.name) + } yield { + (JSONFactory400.createUserInfoJSON(user, entitlements, None, isLocked), HttpCode.`200`(cc.callContext)) + } + } + } + resourceDocs += ResourceDoc( + getUserLockStatus, + implementedInApiVersion, + nameOf(getUserLockStatus), + "GET", + "/users/PROVIDER/USERNAME/lock-status", + "Get User Lock Status", + s""" + |Get User Login Status. + |${authenticationRequiredMessage(true)} + | + |""".stripMargin, + EmptyBody, + badLoginStatusJson, + List(UserNotLoggedIn, UserNotFoundByProviderAndUsername, UserHasMissingRoles, UnknownError), + List(apiTagUser, apiTagNewStyle), + Some(List(canReadUserLockedStatus)) + ) + lazy val getUserLockStatus: OBPEndpoint = { + //get private accounts for all banks + case "users" ::provider :: username :: "lock-status" :: Nil JsonGet req => { + cc => + for { + (Full(u), callContext) <- SS.user + _ <- NewStyle.function.hasEntitlement("", u.userId, ApiRole.canReadUserLockedStatus, callContext) + badLoginStatus <- Future { + LoginAttempt.getBadLoginStatus(provider, username) + } map { + unboxFullOrFail(_, callContext, s"$UserNotFoundByProviderAndUsername provider($provider), username($username)", 404) + } + } yield { + (createBadLoginStatusJson(badLoginStatus), HttpCode.`200`(callContext)) + } + } + } + + resourceDocs += ResourceDoc( + unlockUserByProviderAndUsername, + implementedInApiVersion, + nameOf(unlockUserByProviderAndUsername), + "PUT", + "/users/PROVIDER/USERNAME/lock-status", + "Unlock the user", + s""" + |Unlock a User. + | + |(Perhaps the user was locked due to multiple failed login attempts) + | + |${authenticationRequiredMessage(true)} + | + |""".stripMargin, + EmptyBody, + badLoginStatusJson, + List(UserNotLoggedIn, UserNotFoundByProviderAndUsername, UserHasMissingRoles, UnknownError), + List(apiTagUser, apiTagNewStyle), + Some(List(canUnlockUser))) + lazy val unlockUserByProviderAndUsername: OBPEndpoint = { + //get private accounts for all banks + case "users" :: provider :: username :: "lock-status" :: Nil JsonPut req => { + cc => + for { + (Full(u), callContext) <- SS.user + _ <- NewStyle.function.hasEntitlement("", u.userId, ApiRole.canUnlockUser, callContext) + _ <- Future { + LoginAttempt.resetBadLoginAttempts(provider, username) + } + _ <- Future { + UserLocksProvider.unlockUser(provider, username) + } + badLoginStatus <- Future { + LoginAttempt.getBadLoginStatus(provider, username) + } map { + unboxFullOrFail(_, callContext, s"$UserNotFoundByProviderAndUsername provider($provider), username($username)", 404) + } + } yield { + (createBadLoginStatusJson(badLoginStatus), HttpCode.`200`(callContext)) + } + } + } + + staticResourceDocs += ResourceDoc( + lockUserByProviderAndUsername, + implementedInApiVersion, + nameOf(lockUserByProviderAndUsername), + "POST", + "/users/PROVIDER/USERNAME/locks", + "Lock the user", + s""" + |Lock a User. + | + |${authenticationRequiredMessage(true)} + | + |""".stripMargin, + EmptyBody, + userLockStatusJson, + List($UserNotLoggedIn, UserNotFoundByProviderAndUsername, UserHasMissingRoles, UnknownError), + List(apiTagUser, apiTagNewStyle), + Some(List(canLockUser))) + lazy val lockUserByProviderAndUsername: OBPEndpoint = { + case "users" :: provider :: username :: "locks" :: Nil JsonPost req => { + cc => + for { + (Full(u), callContext) <- SS.user + userLocks <- Future { + UserLocksProvider.lockUser(provider, username) + } map { + unboxFullOrFail(_, callContext, s"$UserNotFoundByProviderAndUsername provider($provider), username($username)", 404) + } + } yield { + (JSONFactory400.createUserLockStatusJson(userLocks), HttpCode.`200`(callContext)) + } + } + } } } diff --git a/obp-api/src/main/scala/code/api/v5_1_0/OBPAPI5_1_0.scala b/obp-api/src/main/scala/code/api/v5_1_0/OBPAPI5_1_0.scala index 30e3fcd90..388e435b4 100644 --- a/obp-api/src/main/scala/code/api/v5_1_0/OBPAPI5_1_0.scala +++ b/obp-api/src/main/scala/code/api/v5_1_0/OBPAPI5_1_0.scala @@ -40,6 +40,7 @@ import code.api.v3_1_0.APIMethods310 import code.api.v4_0_0.APIMethods400 import code.api.v5_0_0.{APIMethods500, OBPAPI5_0_0} import code.util.Helper.MdcLoggable +import com.github.dwickern.macros.NameOf.nameOf import com.openbankproject.commons.util.{ApiVersion, ApiVersionStatus} import net.liftweb.common.{Box, Full} import net.liftweb.http.{LiftResponse, PlainTextResponse} @@ -71,11 +72,18 @@ object OBPAPI5_1_0 extends OBPRestHelper // e.g getEndpoints(Implementations5_0_0) -- List(Implementations5_0_0.genericEndpoint, Implementations5_0_0.root) val endpointsOf5_1_0 = getEndpoints(Implementations5_1_0) + lazy val bugEndpoints = // these endpoints miss Provider parameter in the URL, we introduce new ones in V510. + nameOf(Implementations3_0_0.getUserByUsername) :: + nameOf(Implementations3_1_0.getBadLoginStatus) :: + nameOf(Implementations3_1_0.unlockUser) :: + nameOf(Implementations4_0_0.lockUser) :: + Nil + // if old version ResourceDoc objects have the same name endpoint with new version, omit old version ResourceDoc. def allResourceDocs = collectResourceDocs( OBPAPI5_0_0.allResourceDocs, Implementations5_1_0.resourceDocs - ) + ).filterNot(it => it.partialFunctionName.matches(bugEndpoints.mkString("|"))) // all endpoints private val endpoints: List[OBPEndpoint] = OBPAPI5_0_0.routes ++ endpointsOf5_1_0 diff --git a/obp-api/src/main/scala/code/bankconnectors/Connector.scala b/obp-api/src/main/scala/code/bankconnectors/Connector.scala index bbd793e8d..1792e2905 100644 --- a/obp-api/src/main/scala/code/bankconnectors/Connector.scala +++ b/obp-api/src/main/scala/code/bankconnectors/Connector.scala @@ -4,6 +4,7 @@ import java.util.Date import java.util.UUID.randomUUID import _root_.akka.http.scaladsl.model.HttpMethod import code.accountholders.{AccountHolders, MapperAccountHolders} +import code.api.Constant.localIdentityProvider import code.api.attributedefinition.AttributeDefinition import code.api.cache.Caching import code.api.util.APIUtil.{OBPReturnType, _} @@ -1558,7 +1559,7 @@ trait Connector extends MdcLoggable { @deprecated("we create new code.model.dataAccess.AuthUser.updateUserAccountViews for June2017 connector, try to use new instead of this","11 September 2018") def setAccountHolder(owner : String, bankId: BankId, accountId: AccountId, account_owners: List[String]) : Unit = { // if (account_owners.contains(owner)) { // No need for now, fix it later - val resourceUserOwner = Users.users.vend.getUserByUserName(owner) + val resourceUserOwner = Users.users.vend.getUserByUserName(localIdentityProvider, owner) resourceUserOwner match { case Full(owner) => { if ( ! accountOwnerExists(owner, bankId, accountId).openOrThrowException(attemptedToOpenAnEmptyBox)) { diff --git a/obp-api/src/main/scala/code/bankconnectors/LocalMappedConnector.scala b/obp-api/src/main/scala/code/bankconnectors/LocalMappedConnector.scala index fb2db7420..ae4f0b272 100644 --- a/obp-api/src/main/scala/code/bankconnectors/LocalMappedConnector.scala +++ b/obp-api/src/main/scala/code/bankconnectors/LocalMappedConnector.scala @@ -10,7 +10,7 @@ import code.accountattribute.AccountAttributeX import code.accountholders.{AccountHolders, MapperAccountHolders} import code.api.BerlinGroup.{AuthenticationType, ScaStatus} import code.api.Constant -import code.api.Constant.{INCOMING_SETTLEMENT_ACCOUNT_ID, OUTGOING_SETTLEMENT_ACCOUNT_ID} +import code.api.Constant.{INCOMING_SETTLEMENT_ACCOUNT_ID, OUTGOING_SETTLEMENT_ACCOUNT_ID, localIdentityProvider} import code.api.ResourceDocs1_4_0.SwaggerDefinitionsJSON import code.api.attributedefinition.{AttributeDefinition, AttributeDefinitionDI} import code.api.cache.Caching @@ -5512,7 +5512,7 @@ object LocalMappedConnector extends Connector with MdcLoggable { @deprecated("we create new code.model.dataAccess.AuthUser.updateUserAccountViews for June2017 connector, try to use new instead of this", "11 September 2018") override def setAccountHolder(owner: String, bankId: BankId, accountId: AccountId, account_owners: List[String]): Unit = { // if (account_owners.contains(owner)) { // No need for now, fix it later - val resourceUserOwner = Users.users.vend.getUserByUserName(owner) + val resourceUserOwner = Users.users.vend.getUserByUserName(localIdentityProvider, owner) resourceUserOwner match { case Full(owner) => { if (!accountOwnerExists(owner, bankId, accountId).openOrThrowException(attemptedToOpenAnEmptyBox)) { diff --git a/obp-api/src/main/scala/code/bankconnectors/vSept2018/KafkaMappedConnector_vSept2018.scala b/obp-api/src/main/scala/code/bankconnectors/vSept2018/KafkaMappedConnector_vSept2018.scala index 67855c15f..bcc8d8b7d 100644 --- a/obp-api/src/main/scala/code/bankconnectors/vSept2018/KafkaMappedConnector_vSept2018.scala +++ b/obp-api/src/main/scala/code/bankconnectors/vSept2018/KafkaMappedConnector_vSept2018.scala @@ -102,7 +102,7 @@ trait KafkaMappedConnector_vSept2018 extends Connector with KafkaHelper with Mdc basicUserAuthContexts <- cc.gatewayLoginRequestPayload match { case None => for{ - user <- Users.users.vend.getUserByUserName(username) ?~! "getAuthInfoFirstCbsCall: can not get user object here." + user <- Users.users.vend.getUserByUserName(provider,username) ?~! "getAuthInfoFirstCbsCall: can not get user object here." userAuthContexts<- UserAuthContextProvider.userAuthContextProvider.vend.getUserAuthContextsBox(user.userId)?~! "getAuthInfoFirstCbsCall: can not get userAuthContexts object here." basicUserAuthContexts = JsonFactory_vSept2018.createBasicUserAuthContextJson(userAuthContexts) } yield diff --git a/obp-api/src/main/scala/code/loginattempts/LoginAttempts.scala b/obp-api/src/main/scala/code/loginattempts/LoginAttempts.scala index a415bd2cb..d96fdf711 100644 --- a/obp-api/src/main/scala/code/loginattempts/LoginAttempts.scala +++ b/obp-api/src/main/scala/code/loginattempts/LoginAttempts.scala @@ -12,7 +12,7 @@ object LoginAttempt extends MdcLoggable { def maxBadLoginAttempts = APIUtil.getPropsValue("max.bad.login.attempts") openOr "5" - def incrementBadLoginAttempts(username: String, provider: String): Unit = { + def incrementBadLoginAttempts(provider: String, username: String): Unit = { username.isEmpty() match { case true => // Not a valid case. GitLab issue 389 logger.warn(s"Username is empty: incrementBadLoginAttempts(username=$username, provider=$provider") @@ -20,7 +20,10 @@ object LoginAttempt extends MdcLoggable { logger.debug(s"Hello from incrementBadLoginAttempts with $username") // Find badLoginAttempt record if one exists for a user - MappedBadLoginAttempt.find(By(MappedBadLoginAttempt.mUsername, username)) match { + MappedBadLoginAttempt.find( + By(MappedBadLoginAttempt.Provider, provider), + By(MappedBadLoginAttempt.mUsername, username) + ) match { // If it exits update the date and increment case Full(loginAttempt) => @@ -44,17 +47,23 @@ object LoginAttempt extends MdcLoggable { } } - def getBadLoginStatus(username: String): Box[BadLoginAttempt] = { - MappedBadLoginAttempt.find(By(MappedBadLoginAttempt.mUsername, username)) + def getBadLoginStatus(provider: String, username: String): Box[BadLoginAttempt] = { + MappedBadLoginAttempt.find( + By(MappedBadLoginAttempt.Provider, provider), + By(MappedBadLoginAttempt.mUsername, username) + ) } /** * check the bad login attempts, if it exceed the "max.bad.login.attempts"(in default.props), it return false. */ - def userIsLocked(username: String): Boolean = { + def userIsLocked(provider: String, username: String): Boolean = { - val result : Boolean = MappedBadLoginAttempt.find(By(MappedBadLoginAttempt.mUsername, username)) match { - case Empty => UserLocksProvider.isLocked(username) + val result : Boolean = MappedBadLoginAttempt.find( + By(MappedBadLoginAttempt.Provider, provider), + By(MappedBadLoginAttempt.mUsername, username) + ) match { + case Empty => UserLocksProvider.isLocked(provider, username) case Full(loginAttempt) => loginAttempt.badAttemptsSinceLastSuccessOrReset > maxBadLoginAttempts.toInt match { case true => true case false => false @@ -67,9 +76,12 @@ object LoginAttempt extends MdcLoggable { } - def resetBadLoginAttempts(username: String): Unit = { + def resetBadLoginAttempts(provider: String, username: String): Unit = { - MappedBadLoginAttempt.find(By(MappedBadLoginAttempt.mUsername, username)) match { + MappedBadLoginAttempt.find( + By(MappedBadLoginAttempt.Provider, provider), + By(MappedBadLoginAttempt.mUsername, username) + ) match { case Full(loginAttempt) => loginAttempt.mLastFailureDate(now).mBadAttemptsSinceLastSuccessOrReset(0).save case _ => diff --git a/obp-api/src/main/scala/code/loginattempts/MappedBadLoginAttempt.scala b/obp-api/src/main/scala/code/loginattempts/MappedBadLoginAttempt.scala index 356df7d54..757c5cb6b 100644 --- a/obp-api/src/main/scala/code/loginattempts/MappedBadLoginAttempt.scala +++ b/obp-api/src/main/scala/code/loginattempts/MappedBadLoginAttempt.scala @@ -21,7 +21,7 @@ class MappedBadLoginAttempt extends BadLoginAttempt with LongKeyedMapper[MappedB } object MappedBadLoginAttempt extends MappedBadLoginAttempt with LongKeyedMetaMapper[MappedBadLoginAttempt] { - override def dbIndexes = UniqueIndex(mUsername) :: super.dbIndexes + override def dbIndexes = UniqueIndex(Provider,mUsername) :: super.dbIndexes } trait BadLoginAttempt { diff --git a/obp-api/src/main/scala/code/model/BankingData.scala b/obp-api/src/main/scala/code/model/BankingData.scala index d57dad634..0325086ce 100644 --- a/obp-api/src/main/scala/code/model/BankingData.scala +++ b/obp-api/src/main/scala/code/model/BankingData.scala @@ -338,7 +338,7 @@ case class BankAccountExtended(val bankAccount: BankAccount) extends MdcLoggable final def revokeAllAccountAccess(user : User, otherUserProvider : String, otherUserIdGivenByProvider: String) : Box[Boolean] = { if(canRevokeAccessToViewCommon(bankId, accountId, user)) for{ - otherUser <- UserX.findByProviderId(otherUserProvider, otherUserIdGivenByProvider) ?~ UserNotFoundByUsername + otherUser <- UserX.findByProviderId(otherUserProvider, otherUserIdGivenByProvider) ?~ UserNotFoundByProviderAndUsername isRevoked <- Views.views.vend.revokeAllAccountAccess(bankId, accountId, otherUser) } yield isRevoked else diff --git a/obp-api/src/main/scala/code/model/User.scala b/obp-api/src/main/scala/code/model/User.scala index c788989f8..7f8f5b0aa 100644 --- a/obp-api/src/main/scala/code/model/User.scala +++ b/obp-api/src/main/scala/code/model/User.scala @@ -142,8 +142,8 @@ object UserX { usr } - def findByUserName(userName: String) = { - Users.users.vend.getUserByUserName(userName) + def findByUserName(provider: String, userName: String) = { + Users.users.vend.getUserByUserName(provider, userName) } def findByEmail(email: String) = { @@ -165,8 +165,8 @@ object UserX { Users.users.vend.saveResourceUser(ru) } - def getOrCreateDauthResourceUser(username: String, provider: String) = { - findByUserName(username).or( //first try to find the user by userId + def getOrCreateDauthResourceUser(provider: String, username: String) = { + findByUserName(provider, username).or( //first try to find the user by userId Users.users.vend.createResourceUser( // Otherwise create a new user provider = provider, providerId = Some(username), 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 5e51b9f79..03e178601 100644 --- a/obp-api/src/main/scala/code/model/dataAccess/AuthUser.scala +++ b/obp-api/src/main/scala/code/model/dataAccess/AuthUser.scala @@ -348,8 +348,8 @@ class AuthUser extends MegaProtoUser[AuthUser] with CreatedUpdated with MdcLogga } } - def getResourceUserByUsername(username: String) : Box[User] = { - Users.users.vend.getUserByUserName(username) + def getResourceUserByUsername(provider: String, username: String) : Box[User] = { + Users.users.vend.getUserByUserName(provider, username) } override def save(): Boolean = { @@ -469,10 +469,11 @@ import net.liftweb.util.Helpers._ val authorization: Box[String] = S.request.map(_.header("Authorization")).flatten val directLogin: Box[String] = S.request.map(_.header("DirectLogin")).flatten for { - resourceUser <- if (AuthUser.currentUser.isDefined) - //AuthUser.currentUser.get.user.foreign // this will be issue when the resource user is in remote side - Users.users.vend.getUserByUserName(AuthUser.currentUser.openOrThrowException(ErrorMessages.attemptedToOpenAnEmptyBox).username.get) - else if (directLogin.isDefined) // Direct Login + resourceUser <- if (AuthUser.currentUser.isDefined){ + //AuthUser.currentUser.get.user.foreign // this will be issue when the resource user is in remote side { + val user = AuthUser.currentUser.openOrThrowException(ErrorMessages.attemptedToOpenAnEmptyBox) + Users.users.vend.getUserByUserName(user.provider.get, user.username.get) + }else if (directLogin.isDefined) // Direct Login DirectLogin.getUser else if (hasDirectLoginHeader(authorization)) // Direct Login Deprecated DirectLogin.getUser @@ -619,7 +620,7 @@ import net.liftweb.util.Helpers._ } def grantDefaultEntitlementsToAuthUser(user: TheUserType) = { - tryo{getResourceUserByUsername(user.username.get).head.userId} match { + tryo{getResourceUserByUsername(user.getProvider(), user.username.get).head.userId} match { case Full(userId)=>APIUtil.grantDefaultEntitlementsToNewUser(userId) case _ => logger.error("Can not getResourceUserByUsername here, so it breaks the grantDefaultEntitlementsToNewUser process.") } @@ -810,39 +811,39 @@ import net.liftweb.util.Helpers._ if ( user.validated_? && // User is NOT locked AND the password is good - ! LoginAttempt.userIsLocked(username) && + ! LoginAttempt.userIsLocked(user.getProvider(), username) && user.testPassword(Full(password))) { // We logged in correctly, so reset badLoginAttempts counter (if it exists) - LoginAttempt.resetBadLoginAttempts(username) + LoginAttempt.resetBadLoginAttempts(user.getProvider(), username) Full(user.user.get) // Return the user. } // User is unlocked AND password is bad else if ( user.validated_? && - ! LoginAttempt.userIsLocked(username) && + ! LoginAttempt.userIsLocked(user.getProvider(), username) && ! user.testPassword(Full(password)) ) { - LoginAttempt.incrementBadLoginAttempts(username, user.getProvider()) + LoginAttempt.incrementBadLoginAttempts(user.getProvider(), username) Empty } // User is locked - else if (LoginAttempt.userIsLocked(username)) + else if (LoginAttempt.userIsLocked(user.getProvider(), username)) { - LoginAttempt.incrementBadLoginAttempts(username, user.getProvider()) + LoginAttempt.incrementBadLoginAttempts(user.getProvider(), username) logger.info(ErrorMessages.UsernameHasBeenLocked) //TODO need to fix, use Failure instead, it is used to show the error message to the GUI Full(usernameLockedStateCode) } else { // Nothing worked, so just increment bad login attempts - LoginAttempt.incrementBadLoginAttempts(username, user.getProvider()) + LoginAttempt.incrementBadLoginAttempts(user.getProvider(), username) Empty } // We have a user from an external provider. case Full(user) if (user.getProvider() != Constant.localIdentityProvider) => APIUtil.getPropsAsBoolValue("connector.user.authentication", false) match { - case true if !LoginAttempt.userIsLocked(username) => + case true if !LoginAttempt.userIsLocked(user.getProvider(), username) => val userId = for { authUser <- checkExternalUserViaConnector(username, password) @@ -850,22 +851,22 @@ import net.liftweb.util.Helpers._ authUser.user } } yield { - LoginAttempt.resetBadLoginAttempts(username) + LoginAttempt.resetBadLoginAttempts(user.getProvider(), username) resourceUser.get } userId match { case Full(l: Long) => Full(l) case _ => - LoginAttempt.incrementBadLoginAttempts(username, user.getProvider()) + LoginAttempt.incrementBadLoginAttempts(user.getProvider(), username) Empty } case false => - LoginAttempt.incrementBadLoginAttempts(username, user.getProvider()) + LoginAttempt.incrementBadLoginAttempts(user.getProvider(), username) Empty } // Everything else. case _ => - LoginAttempt.incrementBadLoginAttempts(username, user.foreign.map(_.provider).getOrElse(Constant.HostName)) + LoginAttempt.incrementBadLoginAttempts(user.foreign.map(_.provider).getOrElse(Constant.HostName), username) Empty } } @@ -1059,12 +1060,12 @@ def restoreSomeSessions(): Unit = { } def obpUserIsValidatedAndNotLocked(usernameFromGui: String, user: AuthUser) = { - user.validated_? && !LoginAttempt.userIsLocked(usernameFromGui) && + user.validated_? && !LoginAttempt.userIsLocked(user.getProvider(), usernameFromGui) && isObpProvider(user) } def externalUserIsValidatedAndNotLocked(usernameFromGui: String, user: AuthUser) = { - user.validated_? && !LoginAttempt.userIsLocked(usernameFromGui) && + user.validated_? && !LoginAttempt.userIsLocked(user.getProvider(), usernameFromGui) && !isObpProvider(user) } @@ -1090,7 +1091,7 @@ def restoreSomeSessions(): Unit = { case Full(user) if obpUserIsValidatedAndNotLocked(usernameFromGui, user) => if(user.testPassword(Full(passwordFromGui))) { // if User is NOT locked and password is good // Reset any bad attempt - LoginAttempt.resetBadLoginAttempts(usernameFromGui) + LoginAttempt.resetBadLoginAttempts(user.getProvider(), usernameFromGui) val preLoginState = capturePreLoginState() // User init actions AfterApiAuth.innerLoginUserInitAction(Full(user)) @@ -1098,13 +1099,13 @@ def restoreSomeSessions(): Unit = { val redirect = redirectUri() checkInternalRedirectAndLogUserIn(preLoginState, redirect, user) } else { // If user is NOT locked AND password is wrong => increment bad login attempt counter. - LoginAttempt.incrementBadLoginAttempts(usernameFromGui, user.getProvider()) + LoginAttempt.incrementBadLoginAttempts(user.getProvider(),usernameFromGui) S.error(Helper.i18n("invalid.login.credentials")) } // If user is locked, send the error to GUI - case Full(user) if LoginAttempt.userIsLocked(usernameFromGui) => - LoginAttempt.incrementBadLoginAttempts(usernameFromGui, user.getProvider()) + case Full(user) if LoginAttempt.userIsLocked(user.getProvider(), usernameFromGui) => + LoginAttempt.incrementBadLoginAttempts(user.getProvider(),usernameFromGui) S.error(S.?(ErrorMessages.UsernameHasBeenLocked)) // Check if user came from kafka/obpjvm/stored_procedure and @@ -1112,13 +1113,13 @@ def restoreSomeSessions(): Unit = { // from connector in case they changed on the south-side case Full(user) if externalUserIsValidatedAndNotLocked(usernameFromGui, user) && testExternalPassword(usernameFromGui, passwordFromGui) => // Reset any bad attempts - LoginAttempt.resetBadLoginAttempts(usernameFromGui) + LoginAttempt.resetBadLoginAttempts(user.getProvider(), usernameFromGui) val preLoginState = capturePreLoginState() logger.info("login redirect: " + loginRedirect.get) val redirect = redirectUri() //This method is used for connector = kafka* || obpjvm* //It will update the views and createAccountHolder .... - registeredUserHelper(user.username.get) + registeredUserHelper(user.getProvider(),user.username.get) // User init actions AfterApiAuth.innerLoginUserInitAction(Full(user)) checkInternalRedirectAndLogUserIn(preLoginState, redirect, user) @@ -1132,12 +1133,12 @@ def restoreSomeSessions(): Unit = { val redirect = redirectUri() externalUserHelper(usernameFromGui, passwordFromGui) match { case Full(user: AuthUser) => - LoginAttempt.resetBadLoginAttempts(usernameFromGui) + LoginAttempt.resetBadLoginAttempts(user.getProvider(), usernameFromGui) // User init actions AfterApiAuth.innerLoginUserInitAction(Full(user)) checkInternalRedirectAndLogUserIn(preLoginState, redirect, user) case _ => - LoginAttempt.incrementBadLoginAttempts(username.get, user.foreign.map(_.provider).getOrElse(Constant.HostName)) + LoginAttempt.incrementBadLoginAttempts(user.foreign.map(_.provider).getOrElse(Constant.HostName), username.get) Empty S.error(Helper.i18n("invalid.login.credentials")) } @@ -1146,7 +1147,7 @@ def restoreSomeSessions(): Unit = { case Empty => S.error(Helper.i18n("invalid.login.credentials")) case _ => - LoginAttempt.incrementBadLoginAttempts(usernameFromGui, user.foreign.map(_.provider).getOrElse(Constant.HostName)) + LoginAttempt.incrementBadLoginAttempts(user.foreign.map(_.provider).getOrElse(Constant.HostName), usernameFromGui) S.error(S.?(ErrorMessages.UnexpectedErrorDuringLogin)) // Note we hit this if user has not clicked email validation link } } @@ -1203,14 +1204,14 @@ def restoreSomeSessions(): Unit = { if (connector.startsWith("kafka") ) { for { user <- getUserFromConnector(name, password) - u <- Users.users.vend.getUserByUserName(name) + u <- Users.users.vend.getUserByUserName(user.getProvider(), name) } yield { user } } else { for { user <- checkExternalUserViaConnector(name, password) - u <- Users.users.vend.getUserByUserName(name) + u <- Users.users.vend.getUserByUserName(user.getProvider(), name) } yield { user } @@ -1221,10 +1222,10 @@ def restoreSomeSessions(): Unit = { /** * This method will update the views and createAccountHolder .... */ - def registeredUserHelper(username: String) = { + def registeredUserHelper(provider: String, username: String) = { if (connector.startsWith("kafka")) { for { - u <- Users.users.vend.getUserByUserName(username) + u <- Users.users.vend.getUserByUserName(provider, username) } yield { refreshUser(u, None) } diff --git a/obp-api/src/main/scala/code/remotedata/RemotedataUsers.scala b/obp-api/src/main/scala/code/remotedata/RemotedataUsers.scala index 3b28059fb..0979fa52b 100644 --- a/obp-api/src/main/scala/code/remotedata/RemotedataUsers.scala +++ b/obp-api/src/main/scala/code/remotedata/RemotedataUsers.scala @@ -50,12 +50,12 @@ object RemotedataUsers extends ObpActorInit with Users { def getUsersByUserIdsFuture(userIds : List[String]) : Future[List[User]] = (actor ? cc.getUsersByUserIdsFuture(userIds)).mapTo[List[User]] - def getUserByUserName(userName : String) : Box[ResourceUser] = getValueFromFuture( - (actor ? cc.getUserByUserName(userName)).mapTo[Box[ResourceUser]] + def getUserByUserName(provider : String, userName : String) : Box[ResourceUser] = getValueFromFuture( + (actor ? cc.getUserByUserName(provider, userName)).mapTo[Box[ResourceUser]] ) - def getUserByUserNameFuture(userName : String) : Future[Box[User]] = - (actor ? cc.getUserByUserNameFuture(userName)).mapTo[Box[User]] + def getUserByProviderAndUsernameFuture(provider: String, username: String): Future[Box[User]] = + (actor ? cc.getUserByUserNameFuture(provider, username)).mapTo[Box[User]] def getUserByEmail(email : String) : Box[List[ResourceUser]] = getValueFromFuture( (actor ? cc.getUserByEmail(email)).mapTo[Box[List[ResourceUser]]] diff --git a/obp-api/src/main/scala/code/remotedata/RemotedataUsersActor.scala b/obp-api/src/main/scala/code/remotedata/RemotedataUsersActor.scala index 5ed768852..537df1dda 100644 --- a/obp-api/src/main/scala/code/remotedata/RemotedataUsersActor.scala +++ b/obp-api/src/main/scala/code/remotedata/RemotedataUsersActor.scala @@ -57,13 +57,13 @@ class RemotedataUsersActor extends Actor with ObpActorHelper with MdcLoggable { logger.debug("getUsersByUserIdsFuture(" + userIds +")") sender ! (mapper.getUsersByUserIds(userIds)) - case cc.getUserByUserName(userName: String) => - logger.debug("getUserByUserName(" + userName +")") - sender ! (mapper.getUserByUserName(userName)) + case cc.getUserByUserName(provider: String, userName: String) => + logger.debug("getUserByUserName("+provider + userName +")") + sender ! (mapper.getUserByUserName(provider, userName)) - case cc.getUserByUserNameFuture(userName: String) => - logger.debug("getUserByUserNameFuture(" + userName +")") - sender ! (mapper.getUserByUserName(userName)) + case cc.getUserByUserNameFuture(provider: String, userName: String) => + logger.debug("getUserByUserNameFuture("+provider + userName +")") + sender ! (mapper.getUserByUserName(provider, userName)) case cc.getUserByEmail(email: String) => logger.debug("getUserByEmail(" + email +")") diff --git a/obp-api/src/main/scala/code/sandbox/OBPDataImport.scala b/obp-api/src/main/scala/code/sandbox/OBPDataImport.scala index 99031489d..4c42a36c5 100644 --- a/obp-api/src/main/scala/code/sandbox/OBPDataImport.scala +++ b/obp-api/src/main/scala/code/sandbox/OBPDataImport.scala @@ -2,9 +2,8 @@ package code.sandbox import java.text.SimpleDateFormat import java.util.UUID - import code.accountholders.AccountHolders -import code.api.Constant.{SYSTEM_ACCOUNTANT_VIEW_ID, SYSTEM_AUDITOR_VIEW_ID, SYSTEM_FIREHOSE_VIEW_ID, SYSTEM_OWNER_VIEW_ID} +import code.api.Constant.{SYSTEM_ACCOUNTANT_VIEW_ID, SYSTEM_AUDITOR_VIEW_ID, SYSTEM_FIREHOSE_VIEW_ID, SYSTEM_OWNER_VIEW_ID, localIdentityProvider} import code.api.util.APIUtil._ import code.api.util.{APIUtil, ApiPropsWithAlias, ErrorMessages} import code.bankconnectors.Connector @@ -124,7 +123,7 @@ trait OBPDataImport extends MdcLoggable { protected def createSaveableUser(u : SandboxUserImport) : Box[Saveable[ResourceUser]] protected def createUsers(toImport : List[SandboxUserImport]) : Box[List[Saveable[ResourceUser]]] = { - val existingResourceUsers = toImport.flatMap(u => Users.users.vend.getUserByUserName(u.user_name)) + val existingResourceUsers = toImport.flatMap(u => Users.users.vend.getUserByUserName(localIdentityProvider, u.user_name)) val allUsernames = toImport.map(_.user_name) val duplicateUsernames = allUsernames diff allUsernames.distinct diff --git a/obp-api/src/main/scala/code/snippet/UserInvitation.scala b/obp-api/src/main/scala/code/snippet/UserInvitation.scala index 99c354248..ad99efb68 100644 --- a/obp-api/src/main/scala/code/snippet/UserInvitation.scala +++ b/obp-api/src/main/scala/code/snippet/UserInvitation.scala @@ -28,8 +28,8 @@ package code.snippet import java.time.{Duration, ZoneId, ZoneOffset, ZonedDateTime} import java.util.Date - import code.api.Constant +import code.api.Constant.localIdentityProvider import code.api.util.{APIUtil, SecureRandomUtil} import code.model.dataAccess.{AuthUser, ResourceUser} import code.users @@ -99,7 +99,7 @@ class UserInvitation extends MdcLoggable { else if(userInvitation.map(_.status != "CREATED").getOrElse(false)) showErrorsForStatus() else if(timeDifference.abs.getSeconds > ttl) showErrorsForTtl() else if(AuthUser.currentUser.isDefined) showErrorYouMustBeLoggedOff() - else if(Users.users.vend.getUserByUserName(usernameVar.is).isDefined) showErrorsForUsername() + else if(Users.users.vend.getUserByUserName(localIdentityProvider, usernameVar.is).isDefined) showErrorsForUsername() else if(privacyCheckboxVar.is == false) showErrorsForPrivacyConditions() else if(termsCheckboxVar.is == false) showErrorsForTermsAndConditions() else if(personalDataCollectionConsentCountryWaiverList.exists(_.toLowerCase == countryVar.is.toLowerCase) == false && consentForCollectingCheckboxVar.is == false) showErrorsForConsentForCollectingPersonalData() diff --git a/obp-api/src/main/scala/code/userlocks/UserLocksProvider.scala b/obp-api/src/main/scala/code/userlocks/UserLocksProvider.scala index b01c4c78b..79148196f 100644 --- a/obp-api/src/main/scala/code/userlocks/UserLocksProvider.scala +++ b/obp-api/src/main/scala/code/userlocks/UserLocksProvider.scala @@ -7,8 +7,8 @@ import net.liftweb.mapper.By import net.liftweb.util.Helpers._ object UserLocksProvider extends MdcLoggable { - def isLocked(username: String): Boolean = { - Users.users.vend.getUserByUserName(username) match { + def isLocked(provider: String, username: String): Boolean = { + Users.users.vend.getUserByUserName(provider, username) match { case Full(user) => UserLocks.find(By(UserLocks.UserId, user.userId)) match { case Full(_) => true @@ -17,8 +17,8 @@ object UserLocksProvider extends MdcLoggable { case _ => false } } - def lockUser(username: String): Box[UserLocks] = { - Users.users.vend.getUserByUserName(username) match { + def lockUser(provider: String, username: String): Box[UserLocks] = { + Users.users.vend.getUserByUserName(provider, username) match { case Full(user) => UserLocks.find(By(UserLocks.UserId, user.userId)) match { case Full(userLocks) => @@ -40,8 +40,8 @@ object UserLocksProvider extends MdcLoggable { Empty } } - def unlockUser(username: String): Box[Boolean] = { - Users.users.vend.getUserByUserName(username) match { + def unlockUser(provider: String, username: String): Box[Boolean] = { + Users.users.vend.getUserByUserName(provider, username) match { case Full(user) => UserLocks.find(By(UserLocks.UserId, user.userId)) match { case Full(userLocks) => Some(userLocks.delete_!) diff --git a/obp-api/src/main/scala/code/users/LiftUsers.scala b/obp-api/src/main/scala/code/users/LiftUsers.scala index 04f48a3e8..2a88ff44b 100644 --- a/obp-api/src/main/scala/code/users/LiftUsers.scala +++ b/obp-api/src/main/scala/code/users/LiftUsers.scala @@ -92,13 +92,16 @@ object LiftUsers extends Users with MdcLoggable{ Future(getUsersByUserIds(userIds)) } - override def getUserByUserName(userName: String): Box[User] = { - ResourceUser.find(By(ResourceUser.name_, userName)) + override def getUserByUserName(provider : String, userName: String): Box[User] = { + ResourceUser.find( + By(ResourceUser.provider_, provider), + By(ResourceUser.name_, userName) + ) } - override def getUserByUserNameFuture(userName: String): Future[Box[User]] = { + override def getUserByProviderAndUsernameFuture(provider: String, username: String): Future[Box[User]] = { Future { - getUserByUserName(userName) + getUserByUserName(provider, username) } } diff --git a/obp-api/src/main/scala/code/users/Users.scala b/obp-api/src/main/scala/code/users/Users.scala index d6b446199..c9ce68ee6 100644 --- a/obp-api/src/main/scala/code/users/Users.scala +++ b/obp-api/src/main/scala/code/users/Users.scala @@ -43,8 +43,8 @@ trait Users { def getUsersByUserIdsFuture(userIds : List[String]) : Future[List[User]] // find ResourceUser by Resourceuser user name - def getUserByUserName(userName: String) : Box[User] - def getUserByUserNameFuture(userName: String) : Future[Box[User]] + def getUserByUserName(provider: String, userName: String) : Box[User] + def getUserByProviderAndUsernameFuture(provider: String, username: String): Future[Box[User]] def getUserByEmail(email: String) : Box[List[ResourceUser]] def getUserByEmailFuture(email: String) : Future[List[(ResourceUser, Box[List[Entitlement]])]] @@ -87,8 +87,8 @@ class RemotedataUsersCaseClasses { case class getUserByUserId(userId : String) case class getUserByUserIdFuture(userId : String) case class getUsersByUserIdsFuture(userId : List[String]) - case class getUserByUserName(userName : String) - case class getUserByUserNameFuture(userName : String) + case class getUserByUserName(provider : String, userName : String) + case class getUserByUserNameFuture(provider : String, userName : String) case class getUserByEmail(email : String) case class getUserByEmailFuture(email : String) case class getUsersByEmail(email : String) diff --git a/obp-api/src/test/scala/code/api/DirectLoginTest.scala b/obp-api/src/test/scala/code/api/DirectLoginTest.scala index ffbef54db..f49cf2cfa 100644 --- a/obp-api/src/test/scala/code/api/DirectLoginTest.scala +++ b/obp-api/src/test/scala/code/api/DirectLoginTest.scala @@ -1,5 +1,6 @@ package code.api +import code.api.Constant.localIdentityProvider import code.api.util.ErrorMessages import code.api.util.ErrorMessages._ import code.api.v2_0_0.OBPAPI2_0_0.Implementations2_0_0 @@ -184,7 +185,7 @@ class DirectLoginTest extends ServerSetup with BeforeAndAfter { assertResponse(response, ErrorMessages.UsernameHasBeenLocked) Then("We unlock the username") - LoginAttempt.resetBadLoginAttempts(USERNAME) + LoginAttempt.resetBadLoginAttempts(localIdentityProvider, USERNAME) } scenario("Consumer API key is disabled") { @@ -431,7 +432,7 @@ class DirectLoginTest extends ServerSetup with BeforeAndAfter { lazy val validHeadersWithToken = List(accessControlOriginHeader, headerWithToken) // Lock the user in order to test functionality - UserLocksProvider.lockUser(username) + UserLocksProvider.lockUser(localIdentityProvider, username) When("when we use the token to get current user and it should NOT work due to locked user - New Style") lazy val requestCurrentUserNewStyle = baseRequest / "obp" / "v3.0.0" / "users" / "current" diff --git a/obp-api/src/test/scala/code/api/oauthTest.scala b/obp-api/src/test/scala/code/api/oauthTest.scala index e3003f815..273c85cb7 100644 --- a/obp-api/src/test/scala/code/api/oauthTest.scala +++ b/obp-api/src/test/scala/code/api/oauthTest.scala @@ -346,7 +346,8 @@ class OAuthTest extends ServerSetup { verifier.asInstanceOf[Failure].msg.contains(ErrorMessages.UsernameHasBeenLocked) should equal (true) Then("We unlock the username") - LoginAttempt.resetBadLoginAttempts(user1.username.get) + LoginAttempt.resetBadLoginAttempts(user1.getProvider(), user1.username.get) + setPropsValues("max.bad.login.attempts"-> "5") } } diff --git a/obp-api/src/test/scala/code/api/v4_0_0/LockUserTest.scala b/obp-api/src/test/scala/code/api/v4_0_0/LockUserTest.scala index d4085f7ab..7f0001faa 100644 --- a/obp-api/src/test/scala/code/api/v4_0_0/LockUserTest.scala +++ b/obp-api/src/test/scala/code/api/v4_0_0/LockUserTest.scala @@ -2,7 +2,7 @@ package code.api.v4_0_0 import code.api.util.APIUtil.OAuth._ import code.api.util.ApiRole.CanLockUser -import code.api.util.ErrorMessages.{UserHasMissingRoles, UserNotFoundByUsername, UserNotLoggedIn} +import code.api.util.ErrorMessages.{UserHasMissingRoles, UserNotFoundByProviderAndUsername, UserNotLoggedIn} import code.api.v4_0_0.OBPAPI4_0_0.Implementations4_0_0 import code.entitlement.Entitlement import com.github.dwickern.macros.NameOf.nameOf @@ -51,7 +51,7 @@ class LockUserTest extends V400ServerSetup { val response400 = makePostRequest(request400, "") Then("We should get a 404") response400.code should equal(404) - response400.body.extract[ErrorMessage].message should be (s"$UserNotFoundByUsername($username)") + response400.body.extract[ErrorMessage].message should be (s"$UserNotFoundByProviderAndUsername($username)") } } diff --git a/obp-api/src/test/scala/code/api/v5_1_0/IndexPageTest.scala b/obp-api/src/test/scala/code/api/v5_1_0/IndexPageTest.scala index cc19b9a62..32558504b 100644 --- a/obp-api/src/test/scala/code/api/v5_1_0/IndexPageTest.scala +++ b/obp-api/src/test/scala/code/api/v5_1_0/IndexPageTest.scala @@ -30,7 +30,7 @@ import okhttp3.{OkHttpClient, Request} class IndexPageTest extends V510ServerSetup { - /** + /**d * Test tags * Example: To run tests with tag "getPermissions": * mvn test -D tagsToInclude diff --git a/obp-api/src/test/scala/code/api/v5_1_0/LockUserTest.scala b/obp-api/src/test/scala/code/api/v5_1_0/LockUserTest.scala new file mode 100644 index 000000000..25c20c230 --- /dev/null +++ b/obp-api/src/test/scala/code/api/v5_1_0/LockUserTest.scala @@ -0,0 +1,186 @@ +package code.api.v5_1_0 + +import code.api.Constant.localIdentityProvider +import code.api.util.APIUtil.OAuth +import code.api.util.APIUtil.OAuth._ +import code.api.util.ApiRole.{CanLockUser, CanReadUserLockedStatus, CanUnlockUser} +import code.api.util.ErrorMessages.{UserHasMissingRoles, UserNotFoundByProviderAndUsername, UserNotLoggedIn, UsernameHasBeenLocked} +import code.api.v3_0_0.UserJsonV300 +import code.api.v3_1_0.BadLoginStatusJson +import code.api.v5_1_0.OBPAPI5_1_0.Implementations5_1_0 +import code.entitlement.Entitlement +import code.loginattempts.LoginAttempt +import com.github.dwickern.macros.NameOf.nameOf +import com.openbankproject.commons.model.ErrorMessage +import com.openbankproject.commons.util.ApiVersion +import org.scalatest.Tag + +class LockUserTest extends V510ServerSetup { + /** + * Test tags + * Example: To run tests with tag "getPermissions": + * mvn test -D tagsToInclude + * + * This is made possible by the scalatest maven plugin + */ + object VersionOfApi extends Tag(ApiVersion.v5_1_0.toString) + object ApiEndpoint1 extends Tag(nameOf(Implementations5_1_0.lockUserByProviderAndUsername)) + object ApiEndpoint2 extends Tag(nameOf(Implementations5_1_0.getUserLockStatus)) + object ApiEndpoint3 extends Tag(nameOf(Implementations5_1_0.unlockUserByProviderAndUsername)) + + + feature(s"test $ApiEndpoint1,$ApiEndpoint2, $ApiEndpoint3, version $VersionOfApi - Unauthorized access") { + scenario(s"We will call the $ApiEndpoint1 without user credentials", ApiEndpoint1, VersionOfApi) { + When("We make a request v5.1.0") + val request = (v5_1_0_Request / "users"/"PROVIDER" / "USERNAME" / "locks").POST + val response = makePostRequest(request, "") + Then("We should get a 401") + response.code should equal(401) + response.body.extract[ErrorMessage].message should equal(UserNotLoggedIn) + } + scenario(s"We will call the $ApiEndpoint2 without user credentials", ApiEndpoint2, VersionOfApi) { + When("We make a request v5.1.0") + val request = (v5_1_0_Request / "users" / "PROVIDER" / "USERNAME" / "lock-status").GET + val response = makeGetRequest(request) + Then("We should get a 401") + response.code should equal(401) + response.body.extract[ErrorMessage].message should equal(UserNotLoggedIn) + } + scenario(s"We will call the $ApiEndpoint3 without user credentials", ApiEndpoint3, VersionOfApi) { + When("We make a request v5.1.0") + val request = (v5_1_0_Request / "users" / "PROVIDER" / "USERNAME" / "lock-status").PUT + val response = makePutRequest(request, "") + Then("We should get a 401") + response.code should equal(401) + response.body.extract[ErrorMessage].message should equal(UserNotLoggedIn) + } + } + + feature(s"test $ApiEndpoint1,$ApiEndpoint2, $ApiEndpoint3, version $VersionOfApi - Missing roles") { + scenario(s"We will call the $ApiEndpoint1 without user credentials", ApiEndpoint1, VersionOfApi) { + When("We make a request v5.1.0") + val request = (v5_1_0_Request /"users" /"PROVIDER" / "USERNAME" / "locks").POST <@(user1) + val response = makePostRequest(request, "") + Then("error should be " + UserHasMissingRoles + CanLockUser) + response.code should equal(403) + response.body.extract[ErrorMessage].message should be (UserHasMissingRoles + CanLockUser) + } + scenario(s"We will call the $ApiEndpoint2 without user credentials", ApiEndpoint2, VersionOfApi) { + When("We make a request v5.1.0") + val request = (v5_1_0_Request / "users" /"PROVIDER" / "USERNAME" / "lock-status").GET <@(user1) + val response = makeGetRequest(request) + Then("error should be " + UserHasMissingRoles + CanReadUserLockedStatus) + response.code should equal(403) + response.body.extract[ErrorMessage].message should be (UserHasMissingRoles + CanReadUserLockedStatus) + } + scenario(s"We will call the $ApiEndpoint3 without user credentials", ApiEndpoint3, VersionOfApi) { + When("We make a request v5.1.0") + val request = (v5_1_0_Request / "users" /"PROVIDER" / "USERNAME" / "lock-status").PUT <@(user1) + val response = makePutRequest(request, "") + Then("error should be " + UserHasMissingRoles + CanUnlockUser) + response.code should equal(403) + response.body.extract[ErrorMessage].message should be (UserHasMissingRoles + CanUnlockUser) + } + } + + feature(s"test $ApiEndpoint1,$ApiEndpoint2, $ApiEndpoint3, version $VersionOfApi - Wrong username") { + scenario(s"We will call the $ApiEndpoint1 without user credentials", ApiEndpoint1, VersionOfApi) { + val username = "USERNAME" + Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, CanLockUser.toString) + When("We make a request v5.1.0") + val request = (v5_1_0_Request /"users" / localIdentityProvider / username / "locks").POST <@(user1) + val response = makePostRequest(request, "") + Then("We should get a 404") + response.code should equal(404) + response.body.extract[ErrorMessage].message contains s"$UserNotFoundByProviderAndUsername" shouldBe(true) + } + scenario(s"We will call the $ApiEndpoint2 without user credentials", ApiEndpoint2, VersionOfApi) { + val username = "USERNAME" + Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, CanReadUserLockedStatus.toString) + When("We make a request v5.1.0") + val request = (v5_1_0_Request /"users" / localIdentityProvider / username / "lock-status").GET <@(user1) + val response = makeGetRequest(request) + Then("We should get a 404") + response.code should equal(404) + response.body.extract[ErrorMessage].message contains s"$UserNotFoundByProviderAndUsername" shouldBe(true) + } + scenario(s"We will call the $ApiEndpoint3 without user credentials", ApiEndpoint3, VersionOfApi) { + val username = "USERNAME" + Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, CanUnlockUser.toString) + When("We make a request v5.1.0") + val request = (v5_1_0_Request /"users" / localIdentityProvider / username / "lock-status").PUT <@(user1) + val response = makePutRequest(request, "") + Then("We should get a 404") + response.code should equal(404) + response.body.extract[ErrorMessage].message contains s"$UserNotFoundByProviderAndUsername" shouldBe(true) + } + } + + feature(s"test $ApiEndpoint1,$ApiEndpoint2, $ApiEndpoint3, version $VersionOfApi - Proper values") { + + val resource2Username = resourceUser2.name + val resource2Provider = resourceUser2.provider + scenario(s"We will call the $ApiEndpoint1 without user credentials", ApiEndpoint1, VersionOfApi) { + Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, CanLockUser.toString) + When("We make a request v5.1.0") + val request = (v5_1_0_Request /"users" / resource2Provider / resource2Username / "locks").POST <@(user1) + val response = makePostRequest(request, "") + Then("We should get a 200") + response.code should equal(200) + + { + Then("We try endpoint with user2") + val request = (v5_1_0_Request / "users" / "current").GET <@ (user2) + val response = makeGetRequest(request) + Then("We should get a 401") + response.body.extract[ErrorMessage].message contains s"$UsernameHasBeenLocked" shouldBe (true) + response.code should equal (401) + response + } + } + scenario(s"we fake failed login 10 times, cause lock the user, and check login status and unlock it ", ApiEndpoint1, ApiEndpoint2, ApiEndpoint3, VersionOfApi) { + Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, CanReadUserLockedStatus.toString) + Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, CanUnlockUser.toString) + Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, CanLockUser.toString) + + // Lock the user in order to test functionality + for (i <- 1 to 10) LoginAttempt.incrementBadLoginAttempts(resource2Provider, resource2Username) + + When("We make a request v5.1.0") + val request = (v5_1_0_Request / "users" / "current").GET <@ (user2) + val response = makeGetRequest(request) + Then("We should get a 401") + response.code should equal(401) + response.body.extract[ErrorMessage].message contains s"$UsernameHasBeenLocked" shouldBe(true) + + { + Then("We make check the lock status") + val request = (v5_1_0_Request / "users" / resource2Provider / resource2Username / "lock-status").GET <@ (user1) + val response = makeGetRequest(request) + Then("We should get a 200") + response.code should equal(200) + response.body.extract[BadLoginStatusJson].bad_attempts_since_last_success_or_reset shouldBe (10) + } + + { + Then("We unlock the user") + val request = (v5_1_0_Request / "users" / resource2Provider / resource2Username / "lock-status").PUT <@ (user1) + val response = makePutRequest(request, "") + Then("We should get a 200") + response.code should equal(200) + } + + { + Then("We try endpoint with user2") + val request = (v5_1_0_Request / "users" / "current").GET <@ (user2) + val response = makeGetRequest(request) + Then("We should get a 200") + response.code should equal(200) + response.body.extract[UserJsonV300].username shouldBe(resource2Username) + } + + } + + } + +} \ No newline at end of file diff --git a/obp-api/src/test/scala/code/api/v5_1_0/UserTest.scala b/obp-api/src/test/scala/code/api/v5_1_0/UserTest.scala new file mode 100644 index 000000000..6a9d1c702 --- /dev/null +++ b/obp-api/src/test/scala/code/api/v5_1_0/UserTest.scala @@ -0,0 +1,66 @@ +package code.api.v5_1_0 + +import code.api.util.APIUtil.OAuth._ +import code.api.util.ApiRole.CanGetAnyUser +import code.api.util.ErrorMessages.{UserHasMissingRoles, UserNotLoggedIn, attemptedToOpenAnEmptyBox} +import code.api.v4_0_0.OBPAPI4_0_0.Implementations4_0_0 +import code.api.v4_0_0.{UserIdJsonV400, UserJsonV400} +import code.api.v5_1_0.OBPAPI5_1_0.Implementations5_1_0 +import code.entitlement.Entitlement +import code.model.UserX +import code.users.Users +import com.github.dwickern.macros.NameOf.nameOf +import com.openbankproject.commons.model.ErrorMessage +import com.openbankproject.commons.util.ApiVersion +import org.scalatest.Tag + +import java.util.UUID + +class UserTest extends V510ServerSetup { + /** + * Test tags + * Example: To run tests with tag "getPermissions": + * mvn test -D tagsToInclude + * + * This is made possible by the scalatest maven plugin + */ + object VersionOfApi extends Tag(ApiVersion.v5_1_0.toString) + object ApiEndpoint1 extends Tag(nameOf(Implementations5_1_0.getUserByProviderAndUsername)) + + feature(s"test $ApiEndpoint1 version $VersionOfApi - Unauthorized access") { + scenario("We will call the endpoint without user credentials", ApiEndpoint1, VersionOfApi) { + When("We make a request v5.1.0") + val request400 = (v5_1_0_Request / "users" / "provider"/"x" / "username" / "USERNAME").GET + val response400 = makeGetRequest(request400) + Then("We should get a 401") + response400.code should equal(401) + response400.body.extract[ErrorMessage].message should equal(UserNotLoggedIn) + } + } + + feature(s"test $ApiEndpoint1 version $VersionOfApi - Authorized access") { + scenario("We will call the endpoint with user credentials but without a proper entitlement", ApiEndpoint1, VersionOfApi) { + When("We make a request v5.1.0") + val request400 = (v5_1_0_Request / "users" / "provider"/defaultProvider / "username" / "USERNAME").GET <@(user1) + val response400 = makeGetRequest(request400) + Then("error should be " + UserHasMissingRoles + CanGetAnyUser) + response400.code should equal(403) + response400.body.extract[ErrorMessage].message should be (UserHasMissingRoles + CanGetAnyUser) + } + } + + feature(s"test $ApiEndpoint1 version $VersionOfApi - Authorized access") { + scenario("We will call the endpoint with user credentials and a proper entitlement", ApiEndpoint1, VersionOfApi) { + Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, CanGetAnyUser.toString) + val user = UserX.createResourceUser(defaultProvider, Some("user.name.1"), None, Some("user.name.1"), None, Some(UUID.randomUUID.toString), None).openOrThrowException(attemptedToOpenAnEmptyBox) + When("We make a request v5.1.0") + val request400 = (v5_1_0_Request / "users" / "provider"/user.provider / "username" / user.name ).GET <@(user1) + val response400 = makeGetRequest(request400) + Then("We get successful response") + response400.code should equal(200) + response400.body.extract[UserJsonV400] + Users.users.vend.deleteResourceUser(user.id.get) + } + } + +}