mirror of
https://github.com/OpenBankProject/OBP-API.git
synced 2026-02-06 15:27:01 +00:00
Add User lockout mechanism #276
This commit is contained in:
parent
199214a265
commit
050a598d64
@ -440,5 +440,7 @@ object ToSchemify {
|
||||
MappedEntitlement,
|
||||
MappedPhysicalCard,
|
||||
PinReset,
|
||||
MappedCounterparty)
|
||||
MappedCounterparty,
|
||||
MappedBadLoginAttempts
|
||||
)
|
||||
}
|
||||
|
||||
@ -99,6 +99,9 @@ object DirectLogin extends RestHelper with Loggable {
|
||||
if (userId == 0) {
|
||||
message = ErrorMessages.InvalidLoginCredentials
|
||||
httpCode = 401
|
||||
} else if (userId == OBPUser.usernameLockedStateCode) {
|
||||
message = ErrorMessages.LockedLoginUsername
|
||||
httpCode = 401
|
||||
} else {
|
||||
val claims = Map("" -> "")
|
||||
val (token:String, secret:String) = generateTokenAndSecret(claims)
|
||||
|
||||
@ -88,6 +88,8 @@ object ErrorMessages {
|
||||
|
||||
val InvalidDirectLoginParameters = "OBP-20012: Invalid direct login parameters"
|
||||
|
||||
val LockedLoginUsername = "OBP-20013: The account has been locked, please contact administrator !"
|
||||
|
||||
// Resource related messages
|
||||
val BankNotFound = "OBP-30001: Bank not found. Please specify a valid value for BANK_ID."
|
||||
val CustomerNotFound = "OBP-30002: Customer not found. Please specify a valid value for CUSTOMER_NUMBER."
|
||||
|
||||
@ -760,5 +760,10 @@ trait Connector {
|
||||
List(ownerView, publicView, accountantsView, auditorsView).flatten
|
||||
}
|
||||
|
||||
def incrementBadLoginAttempts(username:String):Unit
|
||||
|
||||
def userIsLocked(username:String):Boolean
|
||||
|
||||
def resetBadLoginAttempts(username:String):Unit
|
||||
|
||||
}
|
||||
|
||||
@ -1190,6 +1190,11 @@ object KafkaMappedConnector extends Connector with Loggable {
|
||||
return json.parse("""{"error":"could not send message to kafka"}""")
|
||||
}
|
||||
|
||||
override def incrementBadLoginAttempts(username: String): Unit = Empty
|
||||
|
||||
override def userIsLocked(username: String): Boolean = false
|
||||
|
||||
override def resetBadLoginAttempts(username: String): Unit = Empty
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -618,4 +618,10 @@ private object LocalConnector extends Connector with Loggable {
|
||||
override def createOrUpdateBranch(branch: BranchJsonPost ): Box[Branch] = Empty
|
||||
|
||||
override def getBranch(bankId : BankId, branchId: BranchId) : Box[MappedBranch]= Empty
|
||||
|
||||
override def incrementBadLoginAttempts(username: String): Unit = Empty
|
||||
|
||||
override def userIsLocked(username: String): Boolean = false
|
||||
|
||||
override def resetBadLoginAttempts(username: String): Unit = Empty
|
||||
}
|
||||
|
||||
@ -37,6 +37,7 @@ import scala.concurrent.ExecutionContext.Implicits.global
|
||||
object LocalMappedConnector extends Connector with Loggable {
|
||||
|
||||
type AccountType = MappedBankAccount
|
||||
val maxBadLoginAttempts = Props.get("max.bad.login.attempts") openOr "10"
|
||||
|
||||
// Gets current challenge level for transaction request
|
||||
override def getChallengeThreshold(userId: String, accountId: String, transactionRequestType: String, currency: String): (BigDecimal, String) = {
|
||||
@ -845,4 +846,35 @@ Store one or more transactions
|
||||
)
|
||||
}
|
||||
|
||||
override def incrementBadLoginAttempts(username: String): Unit ={
|
||||
|
||||
MappedBadLoginAttempts.find(By(MappedBadLoginAttempts.mUsername, username)) match {
|
||||
case Full(loginAttempt) =>
|
||||
loginAttempt.mLastFailureDate(now).mBadAttemptsSinceLastSuccess(loginAttempt.mBadAttemptsSinceLastSuccess+1).save
|
||||
case _ =>
|
||||
MappedBadLoginAttempts.create.mUsername(username).mBadAttemptsSinceLastSuccess(0).save()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* check the bad login attempts,if it exceed the "max.bad.login.attempts"(in default.props), it return false.
|
||||
*/
|
||||
override def userIsLocked(username: String): Boolean = {
|
||||
|
||||
MappedBadLoginAttempts.find(By(MappedBadLoginAttempts.mUsername, username)) match {
|
||||
case Empty => true //When the username first login in. No records, so it is empty
|
||||
case loginAttempt if(loginAttempt.get.mBadAttemptsSinceLastSuccess < (maxBadLoginAttempts.toInt-1)) => true
|
||||
case _ => false
|
||||
}
|
||||
}
|
||||
|
||||
override def resetBadLoginAttempts(username: String): Unit = {
|
||||
|
||||
MappedBadLoginAttempts.find(By(MappedBadLoginAttempts.mUsername, username)) match {
|
||||
case Full(loginAttempt) =>
|
||||
loginAttempt.mLastFailureDate(now).mBadAttemptsSinceLastSuccess(0).save
|
||||
case _ =>
|
||||
MappedBadLoginAttempts.create.mUsername(username).mBadAttemptsSinceLastSuccess(0).save()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1209,5 +1209,11 @@ private def saveTransaction(fromAccount: AccountType, toAccount: AccountType, am
|
||||
override def createOrUpdateBranch(branch: BranchJsonPost ): Box[Branch] = Empty
|
||||
|
||||
override def getBranch(bankId : BankId, branchId: BranchId) : Box[MappedBranch]= Empty
|
||||
|
||||
override def incrementBadLoginAttempts(username: String): Unit = Empty
|
||||
|
||||
override def userIsLocked(username: String): Boolean = false
|
||||
|
||||
override def resetBadLoginAttempts(username: String): Unit = Empty
|
||||
}
|
||||
|
||||
|
||||
@ -0,0 +1,29 @@
|
||||
package code.model.dataAccess
|
||||
|
||||
import java.util.Date
|
||||
|
||||
import net.liftweb.mapper._
|
||||
|
||||
class MappedBadLoginAttempts extends LoginAttempt with LongKeyedMapper[MappedBadLoginAttempts] with IdPK {
|
||||
def getSingleton = MappedBadLoginAttempts
|
||||
|
||||
object mUsername extends MappedString(this, 255)
|
||||
object mBadAttemptsSinceLastSuccess extends MappedInt(this)
|
||||
object mLastFailureDate extends MappedDateTime(this)
|
||||
|
||||
override def username: String = mUsername.get
|
||||
|
||||
override def badAttemptsSinceLastSuccess: Int = mBadAttemptsSinceLastSuccess.get
|
||||
|
||||
override def lastFailureDate: Date = mLastFailureDate.get
|
||||
}
|
||||
|
||||
object MappedBadLoginAttempts extends MappedBadLoginAttempts with LongKeyedMetaMapper[MappedBadLoginAttempts] {
|
||||
override def dbIndexes = UniqueIndex(mUsername) :: super.dbIndexes
|
||||
}
|
||||
|
||||
trait LoginAttempt {
|
||||
def username: String
|
||||
def badAttemptsSinceLastSuccess : Int
|
||||
def lastFailureDate : Date
|
||||
}
|
||||
@ -33,7 +33,7 @@ package code.model.dataAccess
|
||||
|
||||
import java.util.UUID
|
||||
|
||||
import code.api.util.APIUtil
|
||||
import code.api.util.{APIUtil, ErrorMessages}
|
||||
import code.api.{DirectLogin, OAuthHandshake}
|
||||
import code.bankconnectors.Connector
|
||||
import net.liftweb.common._
|
||||
@ -159,6 +159,9 @@ class OBPUser extends MegaProtoUser[OBPUser] with Logger {
|
||||
object OBPUser extends OBPUser with MetaMegaProtoUser[OBPUser]{
|
||||
import net.liftweb.util.Helpers._
|
||||
|
||||
/**Marking the locked state to show different error message */
|
||||
val usernameLockedStateCode = 999999999
|
||||
|
||||
val connector = Props.get("connector").openOrThrowException("no connector set")
|
||||
|
||||
override def emailFrom = Props.get("mail.users.userinfo.sender.address", "sender-not-set")
|
||||
@ -357,11 +360,30 @@ import net.liftweb.util.Helpers._
|
||||
findUserByUsername(name) match {
|
||||
case Full(user) =>
|
||||
if (user.validated_? &&
|
||||
// Check whether user is locked or not
|
||||
Connector.connector.vend.userIsLocked(name) &&
|
||||
user.getProvider() == Props.get("hostname","") &&
|
||||
user.testPassword(Full(password)))
|
||||
{
|
||||
// if login in correctly, reset or set the bad login attemps to 0.
|
||||
Connector.connector.vend.resetBadLoginAttempts(name)
|
||||
Full(user.user)
|
||||
}
|
||||
//recording the login faild times when password is wrong
|
||||
else if (user.validated_? &&
|
||||
// Check whether user is locked or not
|
||||
Connector.connector.vend.userIsLocked(name) &&
|
||||
!user.testPassword(Full(password))
|
||||
) {
|
||||
Connector.connector.vend.incrementBadLoginAttempts(name)
|
||||
Empty
|
||||
}
|
||||
else if (!Connector.connector.vend.userIsLocked(name)
|
||||
) {
|
||||
info(ErrorMessages.LockedLoginUsername)
|
||||
S.error(S.?(ErrorMessages.LockedLoginUsername))
|
||||
Full(usernameLockedStateCode)
|
||||
}
|
||||
else {
|
||||
connector match {
|
||||
case "kafka" =>
|
||||
@ -440,12 +462,17 @@ import net.liftweb.util.Helpers._
|
||||
override def login = {
|
||||
def loginAction = {
|
||||
if (S.post_?) {
|
||||
val usernameFromGui = S.param("username").getOrElse("")
|
||||
S.param("username").
|
||||
flatMap(name => findUserByUsername(name)) match {
|
||||
case Full(user) if user.validated_? &&
|
||||
// Check if user came from localhost
|
||||
user.getProvider() == Props.get("hostname","") &&
|
||||
// Check whether user is locked or not
|
||||
Connector.connector.vend.userIsLocked(usernameFromGui) &&
|
||||
user.testPassword(S.param("password")) => {
|
||||
// if login in correctly, reset or set the bad login attemps to 0.
|
||||
Connector.connector.vend.resetBadLoginAttempts(usernameFromGui)
|
||||
val preLoginState = capturePreLoginState()
|
||||
info("login redir: " + loginRedirect.get)
|
||||
val redir = loginRedirect.get match {
|
||||
@ -463,6 +490,19 @@ import net.liftweb.util.Helpers._
|
||||
})
|
||||
}
|
||||
|
||||
// This case is to record the login faild times when password is wrong
|
||||
case Full(user) if user.validated_? &&
|
||||
// Check whether user is locked or not
|
||||
Connector.connector.vend.userIsLocked(usernameFromGui) &&
|
||||
!user.testPassword(S.param("password")) =>{
|
||||
Connector.connector.vend.incrementBadLoginAttempts(usernameFromGui)
|
||||
S.error(S.?("passwords.do.not.match"))
|
||||
}
|
||||
|
||||
// This case is to send the error to GUI, when the username is locked
|
||||
case Full(user) if !Connector.connector.vend.userIsLocked(usernameFromGui) =>
|
||||
S.error(S.?(ErrorMessages.LockedLoginUsername))
|
||||
|
||||
case Full(user) if !user.validated_? =>
|
||||
S.error(S.?("account.validation.error"))
|
||||
|
||||
|
||||
@ -179,6 +179,12 @@ class PhysicalCardsTest extends ServerSetup with DefaultUsers with DefaultConne
|
||||
override def getBranch(bankId: BankId, branchId: BranchId): Box[MappedBranch]= Empty
|
||||
|
||||
override def getCounterpartyByCounterpartyId(counterpartyId: CounterpartyId): Box[CounterpartyTrait] = ???
|
||||
|
||||
override def incrementBadLoginAttempts(username: String): Unit = Empty
|
||||
|
||||
override def userIsLocked(username: String): Boolean = false
|
||||
|
||||
override def resetBadLoginAttempts(username: String): Unit = Empty
|
||||
}
|
||||
|
||||
override def beforeAll() {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user