Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions obp-api/src/main/scala/code/api/directlogin.scala
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,9 @@ object DirectLogin extends RestHelper with MdcLoggable {
} else if (userId == AuthUser.usernameLockedStateCode) {
message = ErrorMessages.UsernameHasBeenLocked
httpCode = 401
} else if (userId == AuthUser.userEmailNotValidatedStateCode) {
message = ErrorMessages.UserEmailNotValidated
httpCode = 401
} else {
val jwtPayloadAsJson =
"""{
Expand Down
2 changes: 2 additions & 0 deletions obp-api/src/main/scala/code/api/util/ErrorMessages.scala
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ object ErrorMessages {
val InvalidDirectLoginParameters = "OBP-20012: Invalid direct login parameters"

val UsernameHasBeenLocked = "OBP-20013: The account has been locked, please contact an administrator!"
val UserEmailNotValidated = "OBP-20073: The user email has not been validated. Please validate your email address first."

val InvalidConsumerId = "OBP-20014: Invalid Consumer ID. Please specify a valid value for CONSUMER_ID."

Expand Down Expand Up @@ -883,6 +884,7 @@ object ErrorMessages {
InvalidConsumerKey -> 401,
// InvalidConsumerCredentials -> 401, // or 400
UsernameHasBeenLocked -> 401,
UserEmailNotValidated -> 401,
UserNoPermissionAccessView -> 403,
UserLacksPermissionCanGrantAccessToViewForTargetAccount -> 403,
UserLacksPermissionCanRevokeAccessToViewForTargetAccount -> 403,
Expand Down
16 changes: 10 additions & 6 deletions obp-api/src/main/scala/code/api/v6_0_0/APIMethods600.scala
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ import code.api.util.ExampleValue
import code.api.util.ExampleValue.dynamicEntityResponseBodyExample
import net.liftweb.common.Box

import java.net.URLDecoder
import java.nio.charset.StandardCharsets
import java.text.SimpleDateFormat
import java.util.UUID.randomUUID
import scala.collection.immutable.{List, Nil}
Expand Down Expand Up @@ -8754,10 +8756,12 @@ trait APIMethods600 {
postedData <- NewStyle.function.tryons(s"$InvalidJsonFormat The Json body should be the PostVerifyUserCredentialsJsonV600", 400, callContext) {
json.extract[PostVerifyUserCredentialsJsonV600]
}
// Decode the provider in case it's URL-encoded (e.g., "http%3A%2F%2Fexample.com" -> "http://example.com")
decodedProvider = URLDecoder.decode(postedData.provider, StandardCharsets.UTF_8)
// Validate credentials using the existing AuthUser mechanism

resourceUserIdBox =
if (postedData.provider == Constant.localIdentityProvider || postedData.provider.isEmpty) {
if (decodedProvider == Constant.localIdentityProvider || decodedProvider.isEmpty) {
// Local provider: only check local credentials. No external fallback.
val result = code.model.dataAccess.AuthUser.getResourceUserId(
postedData.username, postedData.password, Constant.localIdentityProvider
Expand All @@ -8767,8 +8771,8 @@ trait APIMethods600 {
} else {
// External provider: validate via connector. Local DB stores a random UUID
// as password for external users, so getResourceUserId would always fail.
if (LoginAttempt.userIsLocked(postedData.provider, postedData.username)) {
logger.info(s"verifyUserCredentials says: external user is locked, provider: ${postedData.provider}, username: ${postedData.username}")
if (LoginAttempt.userIsLocked(decodedProvider, postedData.username)) {
logger.info(s"verifyUserCredentials says: external user is locked, provider: ${decodedProvider}, username: ${postedData.username}")
Full(code.model.dataAccess.AuthUser.usernameLockedStateCode)
} else {
val connectorResult = code.model.dataAccess.AuthUser.externalUserHelper(
Expand All @@ -8777,10 +8781,10 @@ trait APIMethods600 {
logger.info(s"verifyUserCredentials says: externalUserHelper result: $connectorResult")
connectorResult match {
case Full(_) =>
LoginAttempt.resetBadLoginAttempts(postedData.provider, postedData.username)
LoginAttempt.resetBadLoginAttempts(decodedProvider, postedData.username)
connectorResult
case _ =>
LoginAttempt.incrementBadLoginAttempts(postedData.provider, postedData.username)
LoginAttempt.incrementBadLoginAttempts(decodedProvider, postedData.username)
connectorResult
}
}
Expand All @@ -8803,7 +8807,7 @@ trait APIMethods600 {
}
// Verify provider matches if specified and not empty
_ <- Helper.booleanToFuture(s"$InvalidLoginCredentials Authentication provider mismatch.", 401, callContext) {
postedData.provider.isEmpty || user.provider == postedData.provider
decodedProvider.isEmpty || user.provider == decodedProvider
}
} yield {
(JSONFactory200.createUserJSON(user), HttpCode.`200`(callContext))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ import code.kycstatuses.KycStatuses
import code.meetings.Meetings
import code.metadata.counterparties.Counterparties
import code.model._
import code.model.dataAccess.AuthUser.findAuthUserByUsernameLocallyLegacy
import code.model.dataAccess._
import code.productAttributeattribute.MappedProductAttribute
import code.productattribute.ProductAttributeX
Expand Down Expand Up @@ -5356,7 +5355,7 @@ object LocalMappedConnector extends Connector with MdcLoggable {
//NOTE: this method is not for mapped connector, we put it here for the star default implementation.
// : we call that method only when we set external authentication and provider is not OBP-API
override def checkExternalUserExists(username: String, callContext: Option[CallContext]): Box[InboundExternalUser] = {
findAuthUserByUsernameLocallyLegacy(username).map(user =>
AuthUser.findAuthUserByUsernameAndProvider(username, Constant.localIdentityProvider).map(user =>
InboundExternalUser(aud = "",
exp = "",
iat = "",
Expand Down
Loading