diff --git a/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/ResourceDocsAPIMethods.scala b/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/ResourceDocsAPIMethods.scala index fc2935f7d..ff0d92a9a 100644 --- a/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/ResourceDocsAPIMethods.scala +++ b/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/ResourceDocsAPIMethods.scala @@ -519,7 +519,7 @@ trait ResourceDocsAPIMethods extends MdcLoggable with APIMethods220 with APIMeth case Some(STATIC) => { val cacheValueFromRedis = Caching.getStaticResourceDocCache(cacheKey) - val dynamicDocs: Box[JValue] = + val staticDocs: Box[JValue] = if (cacheValueFromRedis.isDefined) { Full(json.parse(cacheValueFromRedis.get)) } else { @@ -530,12 +530,12 @@ trait ResourceDocsAPIMethods extends MdcLoggable with APIMethods220 with APIMeth Full(resourceDocJsonJValue) } - Future(dynamicDocs.map(successJsonResponse(_))) + Future(staticDocs.map(successJsonResponse(_))) } case _ => { val cacheValueFromRedis = Caching.getAllResourceDocCache(cacheKey) - val dynamicDocs: Box[JValue] = + val bothStaticAndDyamicDocs: Box[JValue] = if (cacheValueFromRedis.isDefined) { Full(json.parse(cacheValueFromRedis.get)) } else { @@ -546,7 +546,7 @@ trait ResourceDocsAPIMethods extends MdcLoggable with APIMethods220 with APIMeth Full(resourceDocJsonJValue) } - Future(dynamicDocs.map(successJsonResponse(_))) + Future(bothStaticAndDyamicDocs.map(successJsonResponse(_))) } } } @@ -715,13 +715,11 @@ trait ResourceDocsAPIMethods extends MdcLoggable with APIMethods220 with APIMeth swaggerJValue <- NewStyle.function.tryons(s"$UnknownError Can not convert internal swagger file.", 400, cc.callContext) { val cacheValueFromRedis = Caching.getStaticSwaggerDocCache(cacheKey) - val dynamicDocs: JValue = - if (cacheValueFromRedis.isDefined) { - json.parse(cacheValueFromRedis.get) - } else { - convertResourceDocsToSwaggerJvalueAndSetCache(cacheKey, requestedApiVersionString, resourceDocsJsonFiltered) - } - dynamicDocs + if (cacheValueFromRedis.isDefined) { + json.parse(cacheValueFromRedis.get) + } else { + convertResourceDocsToSwaggerJvalueAndSetCache(cacheKey, requestedApiVersionString, resourceDocsJsonFiltered) + } } } yield { (swaggerJValue, HttpCode.`200`(cc.callContext)) 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 121cc6bd7..745417dd9 100644 --- a/obp-api/src/main/scala/code/api/util/APIUtil.scala +++ b/obp-api/src/main/scala/code/api/util/APIUtil.scala @@ -784,16 +784,24 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{ * (?=.*\d) //should contain at least one digit * (?=.*[a-z]) //should contain at least one lower case * (?=.*[A-Z]) //should contain at least one upper case - * (?=.*[!"#$%&'\(\)*+,-./:;<=>?@\\[\\\\]^_\\`{|}~]) //should contain at least one special character - * ([A-Za-z0-9!"#$%&'\(\)*+,-./:;<=>?@\\[\\\\]^_\\`{|}~]{10,16}) //should contain 10 to 16 valid characters + * (?=.*[!\"#$%&'\(\)*+,-./:;<=>?@\\[\\\\]^_\\`{|}~\[\]]) //should contain at least one special character + * ([A-Za-z0-9!\"#$%&'\(\)*+,-./:;<=>?@\\[\\\\]^_\\`{|}~\[\]]{10,16}) //should contain 10 to 16 valid characters **/ val regex = - """^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[!"#$%&'\(\)*+,-./:;<=>?@\\[\\\\]^_\\`{|}~])([A-Za-z0-9!"#$%&'\(\)*+,-./:;<=>?@\\[\\\\]^_\\`{|}~]{10,16})$""".r - password match { - case password if(password.length > 16 && password.length <= 512 && basicPasswordValidation(password) ==SILENCE_IS_GOLDEN) => true - case regex(password) if(basicPasswordValidation(password) ==SILENCE_IS_GOLDEN) => true - case _ => false + """^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[!\"#$%&'\(\)*+,-./:;<=>?@\\[\\\\]^_\\`{|}~\[\]])([A-Za-z0-9!\"#$%&'\(\)*+,-./:;<=>?@\\[\\\\]^_\\`{|}~\[\]]{10,16})$""".r + + // first check `basicPasswordValidation` + if (basicPasswordValidation(password) != SILENCE_IS_GOLDEN) { + return false } + + // 2nd: check the password length between 10 and 512 + if (password.length > 16 && password.length <= 512) { + return true + } + + // 3rd: check the regular expression + regex.pattern.matcher(password).matches() } /** only A-Z, a-z, 0-9,-,_,. =, & and max length <= 2048 */ @@ -847,12 +855,17 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{ /** also support space now */ def basicPasswordValidation(value:String): String ={ val valueLength = value.length - val regex = """^([A-Za-z0-9!"#$%&'\(\)*+,-./:;<=>?@\\[\\\\]^_\\`{|}~ ]+)$""".r - value match { - case regex(e) if(valueLength <= 512) => SILENCE_IS_GOLDEN - case regex(e) if(valueLength > 512) => ErrorMessages.InvalidValueLength - case _ => ErrorMessages.InvalidValueCharacters + val regex = """^([A-Za-z0-9!\"#$%&'\(\)*+,-./:;<=>?@\\[\\\\]^_\\`{|}~ \[\]]+)$""".r + + if (!regex.pattern.matcher(value).matches()) { + return ErrorMessages.InvalidValueCharacters } + + if (valueLength > 512) { + return ErrorMessages.InvalidValueLength + } + + SILENCE_IS_GOLDEN } /** only A-Z, a-z, 0-9, -, _, ., @, and max length <= 512 */ diff --git a/obp-api/src/test/scala/code/util/APIUtilTest.scala b/obp-api/src/test/scala/code/util/APIUtilTest.scala index 450def8dc..3ee21c4cb 100644 --- a/obp-api/src/test/scala/code/util/APIUtilTest.scala +++ b/obp-api/src/test/scala/code/util/APIUtilTest.scala @@ -37,6 +37,7 @@ import code.api.util._ import code.setup.PropsReset import code.util.Helper.SILENCE_IS_GOLDEN import com.openbankproject.commons.model.UserAuthContextCommons +import com.github.dwickern.macros.NameOf.nameOf import net.liftweb.common.{Box, Empty, Full} import net.liftweb.http.provider.HTTPParam import net.liftweb.json.{JValue, parse} @@ -838,6 +839,65 @@ class APIUtilTest extends FeatureSpec with Matchers with GivenWhenThen with Prop actualValue6 contains (InvalidValueLength) shouldBe (true) } + + scenario(s"Test the ${nameOf(APIUtil.basicPasswordValidation _)} method") { + val firefoxStrongPasswordProposal = "9YF]gZnXzAENM+]" + + basicPasswordValidation(firefoxStrongPasswordProposal) shouldBe (SILENCE_IS_GOLDEN) // ✅ SILENCE_IS_GOLDEN + basicPasswordValidation("Abc!123 xyz") shouldBe (SILENCE_IS_GOLDEN) // ✅ SILENCE_IS_GOLDEN + basicPasswordValidation("SuperStrong#123") shouldBe (SILENCE_IS_GOLDEN) // ✅ SILENCE_IS_GOLDEN + basicPasswordValidation("Hello World!") shouldBe (SILENCE_IS_GOLDEN) // ✅ SILENCE_IS_GOLDEN + basicPasswordValidation(" ") shouldBe (SILENCE_IS_GOLDEN) // ✅ SILENCE_IS_GOLDEN allow space so far + + basicPasswordValidation("short💥") shouldBe (InvalidValueCharacters) // ❌ ErrorMessages.InvalidValueCharacters + basicPasswordValidation("a" * 513) shouldBe (InvalidValueLength) // ❌ ErrorMessages.InvalidValueLength + + } + + scenario(s"Test the ${nameOf(APIUtil.fullPasswordValidation _)} method") { + val firefoxStrongPasswordProposal = "9YF]gZnXzAENM+]" + + fullPasswordValidation(firefoxStrongPasswordProposal) // ✅ true + fullPasswordValidation("Abc!123xyz") // ✅ true + fullPasswordValidation("SuperStrong#123") // ✅ true + fullPasswordValidation("Abcdefg!1") // ✅ true + fullPasswordValidation("short1!") // ❌ false(too short) + fullPasswordValidation("alllowercase123!") // ❌ false(no capital letter) + fullPasswordValidation("ALLUPPERCASE123!") // ❌ false(no smaller case letter) + fullPasswordValidation("NoSpecialChar123") // ❌ false(not special character) + + } + + } + + feature(s"test ${nameOf(APIUtil.basicPasswordValidation _)} and ${nameOf(APIUtil.fullPasswordValidation _)}") { + + scenario(s"Test the ${nameOf(APIUtil.basicPasswordValidation _)} method") { + val firefoxStrongPasswordProposal = "9YF]gZnXzAENM+]" + + basicPasswordValidation(firefoxStrongPasswordProposal) shouldBe (SILENCE_IS_GOLDEN) // ✅ SILENCE_IS_GOLDEN + basicPasswordValidation("Abc!123 xyz") shouldBe (SILENCE_IS_GOLDEN) // ✅ SILENCE_IS_GOLDEN + basicPasswordValidation("SuperStrong#123") shouldBe (SILENCE_IS_GOLDEN) // ✅ SILENCE_IS_GOLDEN + basicPasswordValidation("Hello World!") shouldBe (SILENCE_IS_GOLDEN) // ✅ SILENCE_IS_GOLDEN + basicPasswordValidation(" ") shouldBe (SILENCE_IS_GOLDEN) // ✅ SILENCE_IS_GOLDEN allow space so far + + basicPasswordValidation("short💥") shouldBe (InvalidValueCharacters) // ❌ ErrorMessages.InvalidValueCharacters + basicPasswordValidation("a" * 513) shouldBe (InvalidValueLength) // ❌ ErrorMessages.InvalidValueLength + + } + + scenario(s"Test the ${nameOf(APIUtil.fullPasswordValidation _)} method") { + val firefoxStrongPasswordProposal = "9YF]gZnXzAENM+]" + + fullPasswordValidation(firefoxStrongPasswordProposal) // ✅ true + fullPasswordValidation("Abc!123xyz") // ✅ true + fullPasswordValidation("SuperStrong#123") // ✅ true + fullPasswordValidation("Abcdefg!1") // ✅ true + fullPasswordValidation("short1!") // ❌ false(too short) + fullPasswordValidation("alllowercase123!") // ❌ false(no capital letter) + fullPasswordValidation("ALLUPPERCASE123!") // ❌ false(no smaller case letter) + fullPasswordValidation("NoSpecialChar123") // ❌ false(not special character) + } }