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 4bf30c681..239e2fb3a 100644 --- a/obp-api/src/main/scala/code/api/util/APIUtil.scala +++ b/obp-api/src/main/scala/code/api/util/APIUtil.scala @@ -777,20 +777,6 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{ } } - def basicUrlValidation(urlString: String): Boolean = { - //in scala test - org.scalatest.FeatureSpecLike.scenario: - // redirectUrl = http%3A%2F%2Flocalhost%3A8016%3Foauth_token%3DEBRZBMOPDXEUGGJP421FPFGK01IY2DGM5O3TLVSK%26oauth_verifier%3D63461 - // URLDecoder.decode(urlString,"UTF-8")-->http://localhost:8016?oauth_token=EBRZBMOPDXEUGGJP421FPFGK01IY2DGM5O3TLVSK&oauth_verifier=63461 - val regex = - """((([A-Za-z]{3,9}:(?:\/\/)?)(?:[-;:&=\+\$,\w]+@)?[A-Za-z0-9.-]+(:[0-9]+)?|(?:www.|[-;:&=\+\$,\w]+@)[A-Za-z0-9.-]+)((?:\/[\+~%\/.\w-_]*)?\??(?:[-\+=&;%@.\w_\/]*)#?(?:[\w]*))?)""".r - val decodeUrlValue = URLDecoder.decode(urlString, "UTF-8").trim() - decodeUrlValue match { - case regex(_*) if (decodeUrlValue.length <= 2048) => true - case _ => false - } - } - - /** only A-Z, a-z, 0-9,-,_,. =, & and max length <= 2048 */ def basicUriAndQueryStringValidation(urlString: String): Boolean = { val regex = diff --git a/obp-api/src/main/scala/code/snippet/OAuthWorkedThanks.scala b/obp-api/src/main/scala/code/snippet/OAuthWorkedThanks.scala index 982b4a4dc..8b315dbbf 100644 --- a/obp-api/src/main/scala/code/snippet/OAuthWorkedThanks.scala +++ b/obp-api/src/main/scala/code/snippet/OAuthWorkedThanks.scala @@ -48,8 +48,10 @@ class OAuthWorkedThanks extends MdcLoggable { val redirectUrl = ObpS.param("redirectUrl").map(urlDecode(_)) logger.debug(s"OAuthWorkedThanks.thanks.redirectUrl $redirectUrl") //extract the clean(omit the parameters) redirect url from request url - val requestedRedirectURL = Helper.extractCleanRedirectURL(redirectUrl.openOr("invalidRequestedRedirectURL")) openOr("invalidRequestedRedirectURL") - logger.debug(s"OAuthWorkedThanks.thanks.requestedRedirectURL $requestedRedirectURL") + val staticPortionOfRedirectUrl = Helper.getStaticPortionOfRedirectURL(redirectUrl.openOr("invalidRequestedRedirectURL")) openOr("invalidRequestedRedirectURL") + val hostOnlyOfRedirectUrlLegacy = Helper.getHostOnlyOfRedirectURL(staticPortionOfRedirectUrl) openOr("invalidRequestedRedirectURL") + logger.debug(s"OAuthWorkedThanks.thanks.staticPortionOfRedirectUrl $staticPortionOfRedirectUrl") + logger.debug(s"OAuthWorkedThanks.thanks.hostOnlyOfRedirectUrlLegacy $hostOnlyOfRedirectUrlLegacy") val requestedOauthToken = Helper.extractOauthToken(redirectUrl.openOr("No Oauth Token here")) openOr("No Oauth Token here") logger.debug(s"OAuthWorkedThanks.thanks.requestedOauthToken $requestedOauthToken") @@ -62,13 +64,10 @@ class OAuthWorkedThanks extends MdcLoggable { redirectUrl match { case Full(url) => - //this redirect url is checked by following, no open redirect issue. - // TODO maybe handle case of extra trailing / on the url ? + val incorrectRedirectUrlMessage = s"The validRedirectURL is $validRedirectURL but the staticPortionOfRedirectUrl was $staticPortionOfRedirectUrl" - val incorrectRedirectUrlMessage = s"The validRedirectURL is $validRedirectURL but the requestedRedirectURL was $requestedRedirectURL" - - - if(validRedirectURL.equals(requestedRedirectURL)) { + //hostOnlyOfRedirectUrlLegacy is deprecated now, we use the staticPortionOfRedirectUrl stead, + if(validRedirectURL.equals(staticPortionOfRedirectUrl)|| validRedirectURL.equals(hostOnlyOfRedirectUrlLegacy)) { "#redirect-link [href]" #> url & ".app-name"#> appName //there may be several places to be modified in html, so here use the class, not the id. }else{ diff --git a/obp-api/src/main/scala/code/util/Helper.scala b/obp-api/src/main/scala/code/util/Helper.scala index 15c12c894..fa3cd2aa9 100644 --- a/obp-api/src/main/scala/code/util/Helper.scala +++ b/obp-api/src/main/scala/code/util/Helper.scala @@ -1,9 +1,8 @@ package code.util -import java.net.{Socket, SocketException} +import java.net.{Socket, SocketException, URL} import java.util.UUID.randomUUID import java.util.{Date, GregorianCalendar} - import code.api.util.{APIUtil, CallContext, CallContextLight, CustomJsonFormats} import code.api.{APIFailureNewStyle, Constant} import code.api.util.APIUtil.fullBoxOrException @@ -20,7 +19,9 @@ import com.openbankproject.commons.util.{ReflectUtils, RequiredFieldValidation, import com.tesobe.CacheKeyFromArguments import net.liftweb.http.S import net.liftweb.util.Helpers +import net.liftweb.util.Helpers.tryo import net.sf.cglib.proxy.{Enhancer, MethodInterceptor, MethodProxy} + import java.lang.reflect.Method import scala.concurrent.Future import scala.util.Random @@ -167,8 +168,30 @@ object Helper extends Loggable { prettyRender(decompose(input)) } - def extractCleanRedirectURL(input: String): Box[String] = { - Full(input.split("\\?oauth_token=")(0)) + + /** + * + * @param redirectUrl eg: http://localhost:8082/oauthcallback?oauth_token=G5AEA2U1WG404EGHTIGBHKRR4YJZAPPHWKOMNEEV&oauth_verifier=53018 + * @return http://localhost:8082/oauthcallback + */ + def getStaticPortionOfRedirectURL(redirectUrl: String): Box[String] = { + tryo(redirectUrl.split("\\?")(0)) //return everything before the "?" + } + + /** + * extract clean redirect url from input value, because input may have some parameters, such as the following examples
+ * eg1: http://localhost:8082/oauthcallback?....--> http://localhost:8082
+ * eg2: http://localhost:8016?oautallback?=3NLMGV ...--> http://localhost:8016 + * + * @param redirectUrl -> http://localhost:8082/oauthcallback?oauth_token=G5AEA2U1WG404EGHTIGBHKRR4YJZAPPHWKOMNEEV&oauth_verifier=53018 + * @return hostOnlyOfRedirectURL-> http://localhost:8082 + */ + @deprecated("We can not only use hostname as the redirectUrl, now add new method `getStaticPortionOfRedirectURL` ","05.12.2023") + def getHostOnlyOfRedirectURL(redirectUrl: String): Box[String] = { + val url = new URL(redirectUrl) //eg: http://localhost:8082/oauthcallback?oauth_token=G5AEA2U1WG404EGHTIGBHKRR4YJZAPPHWKOMNEEV&oauth_verifier=53018 + val protocol = url.getProtocol() // http + val authority = url.getAuthority()// localhost:8082, this will contain the port. + tryo(s"$protocol://$authority") // http://localhost:8082 } /** @@ -461,7 +484,7 @@ object Helper extends Loggable { }else if((args.length>0) && args.apply(0).toString.equalsIgnoreCase("consumer_key")){ result.asInstanceOf[Box[String]].filter(APIUtil.basicConsumerKeyValidation(_)==SILENCE_IS_GOLDEN) }else if((args.length>0) && args.apply(0).toString.equalsIgnoreCase("redirectUrl")){ - result.asInstanceOf[Box[String]].filter(APIUtil.basicUrlValidation(_)) + result.asInstanceOf[Box[String]].filter(APIUtil.basicUriAndQueryStringValidation(_)) } else{ result.asInstanceOf[Box[String]].filter(APIUtil.checkMediumString(_)==SILENCE_IS_GOLDEN) } diff --git a/obp-api/src/test/scala/code/util/APIUtilTest.scala b/obp-api/src/test/scala/code/util/APIUtilTest.scala index 44b354495..450def8dc 100644 --- a/obp-api/src/test/scala/code/util/APIUtilTest.scala +++ b/obp-api/src/test/scala/code/util/APIUtilTest.scala @@ -698,12 +698,18 @@ class APIUtilTest extends FeatureSpec with Matchers with GivenWhenThen with Prop APIUtil.getObpFormatOperationId("xxx") should be ("xxx") } - feature("test APIUtil.basicUrlValidation method") { + feature("test APIUtil.basicUriAndQueryStringValidation method") { val testString1 = "https%3A%2F%2Fapisandbox.openbankproject.com%2Foauth%2Fauthorize%3Fnext%3D%2Fen%2Fusers%2Fmyuser%26oauth_token%3DWTOBT2YRCTMI1BCCF4XAIKRXPLLZDZPFAIL5K03Z%26oauth_verifier%3D45381" val testString2 = "http%3A%2F%2Flocalhost%3A8016%3Foauth_token%3DEBRZBMOPDXEUGGJP421FPFGK01IY2DGM5O3TLVSK%26oauth_verifier%3D63461" + val testString3 = "myapp://callback?oauth_token=%3DEBRZBMOPDXEUGGJP421FPFGK01IY2DGM5O3TLVSK%26oauth_verifier%3D63461" + val testString4 = "fb00000000:://callback?oauth_token=%3DEBRZBMOPDXEUGGJP421FPFGK01IY2DGM5O3TLVSK%26oauth_verifier%3D63461" + val testString5 = "http://127.0.0.1:8000/oauth/authorize?next=/en/metrics/api/&oauth_token=TN0124OCPRCL4KUJRF5LNLVMRNHTVZPJDBS2PNWU&oauth_verifier=10470" - APIUtil.basicUrlValidation(testString1) should be (true) - APIUtil.basicUrlValidation(testString2) should be (true) + APIUtil.basicUriAndQueryStringValidation(testString1) should be (true) + APIUtil.basicUriAndQueryStringValidation(testString2) should be (true) + APIUtil.basicUriAndQueryStringValidation(testString3) should be (true) + APIUtil.basicUriAndQueryStringValidation(testString4) should be (true) + APIUtil.basicUriAndQueryStringValidation(testString5) should be (true) } diff --git a/obp-api/src/test/scala/code/util/HelperTest.scala b/obp-api/src/test/scala/code/util/HelperTest.scala index eaad59c97..2e766e37d 100644 --- a/obp-api/src/test/scala/code/util/HelperTest.scala +++ b/obp-api/src/test/scala/code/util/HelperTest.scala @@ -35,13 +35,32 @@ import org.scalatest.{FeatureSpec, GivenWhenThen, Matchers} class HelperTest extends FeatureSpec with Matchers with GivenWhenThen with PropsReset { - feature("test APIUtil.basicUrlValidation method") { + feature("test APIUtil.getStaticPortionOfRedirectURL method") { + // The redirectURl is `http://localhost:8082/oauthcallback` val testString1 = "http://localhost:8082/oauthcallback?oauth_token=G5AEA2U1WG404EGHTIGBHKRR4YJZAPPHWKOMNEEV&oauth_verifier=53018" val testString2 = "http://localhost:8082?oauth_token=G5AEA2U1WG404EGHTIGBHKRR4YJZAPPHWKOMNEEV&oauth_verifier=53018" + val testString3 = "myapp://callback?oauth_token=%3DEBRZBMOPDXEUGGJP421FPFGK01IY2DGM5O3TLVSK%26oauth_verifier%3D63461" + val testString4 = "fb00000000:://callback?oauth_token=%3DEBRZBMOPDXEUGGJP421FPFGK01IY2DGM5O3TLVSK%26oauth_verifier%3D63461" + val testString5 = "http://127.0.0.1:8000/oauth/authorize?next=/en/metrics/api/&oauth_token=TN0124OCPRCL4KUJRF5LNLVMRNHTVZPJDBS2PNWU&oauth_verifier=10470" - Helper.extractCleanRedirectURL(testString1).head should be("http://localhost:8082/oauthcallback") - Helper.extractCleanRedirectURL(testString2).head should be("http://localhost:8082") + Helper.getStaticPortionOfRedirectURL(testString1).head should be("http://localhost:8082/oauthcallback") + Helper.getStaticPortionOfRedirectURL(testString2).head should be("http://localhost:8082") + Helper.getStaticPortionOfRedirectURL(testString3).head should be("myapp://callback") + Helper.getStaticPortionOfRedirectURL(testString4).head should be("fb00000000:://callback") + Helper.getStaticPortionOfRedirectURL(testString5).head should be("http://127.0.0.1:8000/oauth/authorize") + } + + feature("test APIUtil.getHostOnlyOfRedirectURL method") { + // The redirectURl is `http://localhost:8082/oauthcallback` + val testString1 = "http://localhost:8082/oauthcallback?oauth_token=G5AEA2U1WG404EGHTIGBHKRR4YJZAPPHWKOMNEEV&oauth_verifier=53018" + val testString2 = "http://localhost:8082/oauthcallback" + val testString3 = "http://localhost:8082?oauth_token=G5AEA2U1WG404EGHTIGBHKRR4YJZAPPHWKOMNEEV&oauth_verifier=53018" + val testString4 = "http://localhost:8082" + Helper.getHostOnlyOfRedirectURL(testString1).head should be("http://localhost:8082") + Helper.getHostOnlyOfRedirectURL(testString2).head should be("http://localhost:8082") + Helper.getHostOnlyOfRedirectURL(testString3).head should be("http://localhost:8082") + Helper.getHostOnlyOfRedirectURL(testString4).head should be("http://localhost:8082") } } \ No newline at end of file