diff --git a/otoroshi/app/auth/basic.scala b/otoroshi/app/auth/basic.scala index ab08ae37c..fed1eb9e4 100644 --- a/otoroshi/app/auth/basic.scala +++ b/otoroshi/app/auth/basic.scala @@ -1,7 +1,7 @@ package otoroshi.auth import java.security.SecureRandom -import java.util.Optional +import java.util.{Base64, Optional} import akka.http.scaladsl.model.Uri import akka.http.scaladsl.util.FastFuture import com.fasterxml.jackson.annotation.JsonInclude.Include @@ -10,7 +10,7 @@ import com.fasterxml.jackson.datatype.jdk8.Jdk8Module import com.google.common.base.Charsets import com.yubico.webauthn._ import com.yubico.webauthn.data._ -import otoroshi.controllers.{routes, LocalCredentialRepository} +import otoroshi.controllers.{LocalCredentialRepository, routes} import otoroshi.env.Env import otoroshi.models._ import org.joda.time.DateTime @@ -23,6 +23,7 @@ import play.api.mvc._ import otoroshi.security.{IdGenerator, OtoroshiClaim} import otoroshi.utils.{JsonPathValidator, JsonValidator} +import java.nio.charset.StandardCharsets import scala.concurrent.{ExecutionContext, Future} import scala.util.{Failure, Success, Try} @@ -306,6 +307,8 @@ case class BasicAuthModule(authConfig: BasicAuthModuleConfig) extends AuthModule ): Future[Result] = { implicit val req = request val redirect = request.getQueryString("redirect") + .filter(redirect => request.getQueryString("hash").contains(env.sign(s"desc=${descriptor.id}&redirect=${redirect}"))) + .map(redirectBase64Encoded => new String(Base64.getUrlDecoder.decode(redirectBase64Encoded), StandardCharsets.UTF_8)) val hash = env.sign(s"${authConfig.id}:::${descriptor.id}") env.datastores.authConfigsDataStore.generateLoginToken().flatMap { token => if (authConfig.basicAuth) { diff --git a/otoroshi/app/auth/ldap.scala b/otoroshi/app/auth/ldap.scala index ff1f21e3c..0f0ead4d7 100644 --- a/otoroshi/app/auth/ldap.scala +++ b/otoroshi/app/auth/ldap.scala @@ -19,6 +19,8 @@ import otoroshi.security.{IdGenerator, OtoroshiClaim} import otoroshi.utils.{JsonPathValidator, JsonValidator, RegexPool} import otoroshi.utils.syntax.implicits._ +import java.nio.charset.StandardCharsets +import java.util.Base64 import javax.naming.ldap.{Control, InitialLdapContext} import scala.annotation.tailrec import scala.concurrent.{ExecutionContext, Future} @@ -768,6 +770,8 @@ case class LdapAuthModule(authConfig: LdapAuthModuleConfig) extends AuthModule { ): Future[Result] = { implicit val req = request val redirect = request.getQueryString("redirect") + .filter(redirect => request.getQueryString("hash").contains(env.sign(s"desc=${descriptor.id}&redirect=${redirect}"))) + .map(redirectBase64Encoded => new String(Base64.getUrlDecoder.decode(redirectBase64Encoded), StandardCharsets.UTF_8)) val hash = env.sign(s"${authConfig.id}:::${descriptor.id}") env.datastores.authConfigsDataStore.generateLoginToken().flatMap { token => if (authConfig.basicAuth) { diff --git a/otoroshi/app/auth/oauth.scala b/otoroshi/app/auth/oauth.scala index 14942c2cd..719068d41 100644 --- a/otoroshi/app/auth/oauth.scala +++ b/otoroshi/app/auth/oauth.scala @@ -281,6 +281,8 @@ case class GenericOauth2Module(authConfig: OAuth2ModuleConfig) extends AuthModul implicit val req = request val redirect = request.getQueryString("redirect") + .filter(redirect => request.getQueryString("hash").contains(env.sign(s"desc=${descriptor.id}&redirect=${redirect}"))) + .map(redirectBase64Encoded => new String(Base64.getUrlDecoder.decode(redirectBase64Encoded), StandardCharsets.UTF_8)) val clientId = authConfig.clientId val responseType = "code" val scope = authConfig.scope // "openid profile email name" diff --git a/otoroshi/app/auth/oauth1.scala b/otoroshi/app/auth/oauth1.scala index cb9e0bfc3..8bc2d0daf 100644 --- a/otoroshi/app/auth/oauth1.scala +++ b/otoroshi/app/auth/oauth1.scala @@ -16,6 +16,7 @@ import play.api.mvc.Results.{Ok, Redirect} import play.api.mvc.{AnyContent, Request, RequestHeader, Result} import java.net.URLEncoder +import java.nio.charset.StandardCharsets import java.util.Base64 import scala.concurrent.{ExecutionContext, Future} import scala.util.Try @@ -331,6 +332,8 @@ case class Oauth1AuthModule(authConfig: Oauth1ModuleConfig) extends AuthModule { if (parameters("oauth_callback_confirmed") == "true") { val redirect = request.getQueryString("redirect") + .filter(redirect => request.getQueryString("hash").contains(env.sign(s"desc=${descriptor.id}&redirect=${redirect}"))) + .map(redirectBase64Encoded => new String(Base64.getUrlDecoder.decode(redirectBase64Encoded), StandardCharsets.UTF_8)) val hash = env.sign(s"${authConfig.id}:::${descriptor.id}") val oauth_token = parameters("oauth_token") Redirect(s"${authConfig.authorizeURL}?oauth_token=$oauth_token&perms=read") diff --git a/otoroshi/app/auth/saml/SAMLClient.scala b/otoroshi/app/auth/saml/SAMLClient.scala index 9e70666ab..b8e8d7ac8 100644 --- a/otoroshi/app/auth/saml/SAMLClient.scala +++ b/otoroshi/app/auth/saml/SAMLClient.scala @@ -80,6 +80,8 @@ case class SAMLModule(authConfig: SamlAuthModuleConfig) extends AuthModule { implicit val req: RequestHeader = request val redirect = request.getQueryString("redirect") + .filter(redirect => request.getQueryString("hash").contains(env.sign(s"desc=${descriptor.id}&redirect=${redirect}"))) + .map(redirectBase64Encoded => new String(Base64.getUrlDecoder.decode(redirectBase64Encoded), StandardCharsets.UTF_8)) val redirectTo = redirect.getOrElse( routes.PrivateAppsController.home.absoluteURL(env.exposedRootSchemeIsHttps) ) diff --git a/otoroshi/app/controllers/Auth0Controller.scala b/otoroshi/app/controllers/Auth0Controller.scala index 648ee19d6..f7602fe98 100644 --- a/otoroshi/app/controllers/Auth0Controller.scala +++ b/otoroshi/app/controllers/Auth0Controller.scala @@ -20,6 +20,8 @@ import play.api.libs.json._ import play.api.mvc._ import java.net.URLEncoder +import java.nio.charset.StandardCharsets +import java.util.Base64 import java.util.concurrent.TimeUnit import javax.crypto.Cipher import javax.crypto.spec.SecretKeySpec @@ -245,7 +247,10 @@ class AuthController( ) ) } - f(auths, route, ctx.request.getQueryString("redirect")) + val redirect = ctx.request.getQueryString("redirect") + .filter(redirect => ctx.request.getQueryString("hash").contains(env.sign(s"desc=${routeId}&redirect=${redirect}"))) + .map(redirectBase64Encoded => new String(Base64.getUrlDecoder.decode(redirectBase64Encoded), StandardCharsets.UTF_8)) + f(auths, route, redirect) case JsError(errors) => logger.error(s"Failed to parse multi auth configuration, $errors") NotFound(otoroshi.views.html.oto.error("Private apps are not configured", env)).vfuture @@ -287,6 +292,8 @@ class AuthController( val secStr = if (auth.clientSideSessionEnabled) s"&sec=${sec}" else "" req .getQueryString("redirect") + .filter(redirect => req.getQueryString("hash").contains(env.sign(s"desc=${route.id}&redirect=${redirect}"))) + .map(redirectBase64Encoded => new String(Base64.getUrlDecoder.decode(redirectBase64Encoded), StandardCharsets.UTF_8)) .getOrElse(s"${req.theProtocol}://${req.theHost}${req.relativeUri}") match { case "urn:ietf:wg:oauth:2.0:oob" => { val redirection = @@ -317,13 +324,14 @@ class AuthController( } case redirectTo => { // TODO - check if ref is needed + val encodedRedirectTo = Base64.getUrlEncoder.encodeToString(redirectTo.getBytes(StandardCharsets.UTF_8)) val url = new java.net.URL(s"${req.theProtocol}://${req.theHost}${req.relativeUri}") val host = url.getHost val scheme = url.getProtocol val setCookiesRedirect = url.getPort match { case -1 => val redirection = - s"$scheme://$host/.well-known/otoroshi/login?route=true&sessionId=${user.randomId}&redirectTo=$redirectTo&host=$host&cp=${auth + s"$scheme://$host/.well-known/otoroshi/login?route=true&sessionId=${user.randomId}&redirectTo=$encodedRedirectTo&host=$host&cp=${auth .routeCookieSuffix(route)}&ma=${auth.sessionMaxAge}&httpOnly=${auth.sessionCookieValues.httpOnly}&secure=${auth.sessionCookieValues.secure}${secStr}" val hash = env.sign(redirection) if (otoroshi.controllers.AuthController.logger.isDebugEnabled) { @@ -334,7 +342,7 @@ class AuthController( s"$redirection&hash=$hash" case port => val redirection = - s"$scheme://$host:$port/.well-known/otoroshi/login?route=true&sessionId=${user.randomId}&redirectTo=$redirectTo&host=$host&cp=${auth + s"$scheme://$host:$port/.well-known/otoroshi/login?route=true&sessionId=${user.randomId}&redirectTo=$encodedRedirectTo&host=$host&cp=${auth .routeCookieSuffix(route)}&ma=${auth.sessionMaxAge}&httpOnly=${auth.sessionCookieValues.httpOnly}&secure=${auth.sessionCookieValues.secure}${secStr}" val hash = env.sign(redirection) if (otoroshi.controllers.AuthController.logger.isDebugEnabled) { @@ -394,6 +402,8 @@ class AuthController( val secStr = if (auth.clientSideSessionEnabled) s"&sec=${sec}" else "" req .getQueryString("redirect") + .filter(redirect => req.getQueryString("hash").contains(env.sign(s"desc=${serviceId}&redirect=${redirect}"))) + .map(redirectBase64Encoded => new String(Base64.getUrlDecoder.decode(redirectBase64Encoded), StandardCharsets.UTF_8)) .getOrElse(s"${req.theProtocol}://${req.theHost}${req.relativeUri}") match { case "urn:ietf:wg:oauth:2.0:oob" => { val redirection = @@ -423,13 +433,14 @@ class AuthController( ) } case redirectTo => { + val encodedRedirectTo = Base64.getUrlEncoder.encodeToString(redirectTo.getBytes(StandardCharsets.UTF_8)) val url = new java.net.URL(s"${req.theProtocol}://${req.theHost}${req.relativeUri}") val host = url.getHost val scheme = url.getProtocol val setCookiesRedirect = url.getPort match { case -1 => val redirection = - s"$scheme://$host/.well-known/otoroshi/login?sessionId=${user.randomId}&redirectTo=$redirectTo&host=$host&cp=${auth + s"$scheme://$host/.well-known/otoroshi/login?sessionId=${user.randomId}&redirectTo=$encodedRedirectTo&host=$host&cp=${auth .cookieSuffix(descriptor)}&ma=${auth.sessionMaxAge}&httpOnly=${auth.sessionCookieValues.httpOnly}&secure=${auth.sessionCookieValues.secure}${secStr}" val hash = env.sign(redirection) if (otoroshi.controllers.AuthController.logger.isDebugEnabled) { @@ -440,7 +451,7 @@ class AuthController( s"$redirection&hash=$hash" case port => val redirection = - s"$scheme://$host:$port/.well-known/otoroshi/login?sessionId=${user.randomId}&redirectTo=$redirectTo&host=$host&cp=${auth + s"$scheme://$host:$port/.well-known/otoroshi/login?sessionId=${user.randomId}&redirectTo=$encodedRedirectTo&host=$host&cp=${auth .cookieSuffix(descriptor)}&ma=${auth.sessionMaxAge}&httpOnly=${auth.sessionCookieValues.httpOnly}&secure=${auth.sessionCookieValues.secure}${secStr}" val hash = env.sign(redirection) if (otoroshi.controllers.AuthController.logger.isDebugEnabled) { @@ -578,14 +589,15 @@ class AuthController( env.createPrivateSessionCookies(req.theHost, user.randomId, descriptor, auth, user.some): _* ) case redirectTo => + val encodedRedirectTo = Base64.getUrlEncoder.encodeToString(redirectTo.getBytes(StandardCharsets.UTF_8)) val url = new java.net.URL(redirectTo) val host = url.getHost val scheme = url.getProtocol val setCookiesRedirect = url.getPort match { case -1 => val redirection = - s"$scheme://$host/.well-known/otoroshi/login?sessionId=${paUser.randomId}&redirectTo=$redirectTo&host=$host&cp=${auth - .cookieSuffix(descriptor)}&ma=${auth.sessionMaxAge}&httpOnly=${auth.sessionCookieValues.httpOnly}&secure=${auth.sessionCookieValues.secure}${secStr}" + s"$scheme://$host/.well-known/otoroshi/login?sessionId=${paUser.randomId}&redirectTo=$encodedRedirectTo&host=$host&cp=${auth + .cookieSuffix(descriptor)}&ma=${auth.sessionMaxAge}&httpOnly=${auth.sessionCookieValues.httpOnly}&secure=${auth.sessionCookieValues.secure}${}" val hash = env.sign(redirection) if (otoroshi.controllers.AuthController.logger.isDebugEnabled) { otoroshi.controllers.AuthController.logger @@ -594,7 +606,7 @@ class AuthController( s"$redirection&hash=$hash" case port => val redirection = - s"$scheme://$host:$port/.well-known/otoroshi/login?sessionId=${paUser.randomId}&redirectTo=$redirectTo&host=$host&cp=${auth + s"$scheme://$host:$port/.well-known/otoroshi/login?sessionId=${paUser.randomId}&redirectTo=$encodedRedirectTo&host=$host&cp=${auth .cookieSuffix(descriptor)}&ma=${auth.sessionMaxAge}&httpOnly=${auth.sessionCookieValues.httpOnly}&secure=${auth.sessionCookieValues.secure}${secStr}" val hash = env.sign(redirection) if (otoroshi.controllers.AuthController.logger.isDebugEnabled) { diff --git a/otoroshi/app/gateway/handlers.scala b/otoroshi/app/gateway/handlers.scala index 2371c5ee8..fc2a8f123 100644 --- a/otoroshi/app/gateway/handlers.scala +++ b/otoroshi/app/gateway/handlers.scala @@ -38,6 +38,8 @@ import otoroshi.utils.http.RequestImplicits._ import otoroshi.utils.syntax.implicits._ import java.io.File +import java.nio.charset.StandardCharsets +import java.util.Base64 import scala.concurrent.{ExecutionContext, Future} import scala.util.Try import scala.util.control.NoStackTrace @@ -740,7 +742,7 @@ class GatewayRequestHandler( def setPrivateAppsCookies() = actionBuilder.async { req => - val redirectToOpt: Option[String] = req.queryString.get("redirectTo").map(_.last) + val redirectToOpt: Option[String] = req.queryString.get("redirectTo").map(_.last).map(v => new String(Base64.getUrlDecoder.decode(v), StandardCharsets.UTF_8)) val sessionIdOpt: Option[String] = req.queryString.get("sessionId").map(_.last) val hostOpt: Option[String] = req.queryString.get("host").map(_.last) val cookiePrefOpt: Option[String] = req.queryString.get("cp").map(_.last) diff --git a/otoroshi/app/models/privateappsuser.scala b/otoroshi/app/models/privateappsuser.scala index bdb7d7e05..5c9a5febd 100644 --- a/otoroshi/app/models/privateappsuser.scala +++ b/otoroshi/app/models/privateappsuser.scala @@ -19,6 +19,8 @@ import otoroshi.next.plugins.{MultiAuthModule, NgMultiAuthModuleConfig} import otoroshi.utils.TypedMap import otoroshi.utils.syntax.implicits.BetterSyntax +import java.nio.charset.StandardCharsets +import java.util.Base64 import scala.concurrent.duration._ import scala.concurrent.duration.Duration import scala.concurrent.{ExecutionContext, Future} @@ -297,13 +299,17 @@ object PrivateAppsUserHelper { case Some(paUsr) => callDownstream(config, None, Some(paUsr)) case None => { - val redirect = req - .getQueryString("redirect") - .getOrElse(s"${req.theProtocol}://${req.theHost}${req.relativeUri}") + // val redirect = req + // .getQueryString("redirect") + // .getOrElse(s"${req.theProtocol}://${req.theHost}${req.relativeUri}") + val redirect = s"${req.theProtocol}://${req.theHost}${req.relativeUri}" + val encodedRedirect = Base64.getUrlEncoder.encodeToString(redirect.getBytes(StandardCharsets.UTF_8)) + val descriptorId = descriptor.id + val hash = env.sign(s"desc=${descriptorId}&redirect=${encodedRedirect}") val redirectTo = env.rootScheme + env.privateAppsHost + env.privateAppsPort + otoroshi.controllers.routes.AuthController .confidentialAppLoginPage() - .url + s"?desc=${descriptor.id}&redirect=${redirect}" + .url + s"?desc=${descriptorId}&redirect=${encodedRedirect}&hash=${hash}" if (logger.isTraceEnabled) logger.trace("should redirect to " + redirectTo) descriptor.authConfigRef match { case None => diff --git a/otoroshi/app/next/plugins/auth.scala b/otoroshi/app/next/plugins/auth.scala index c59aa594e..610430297 100644 --- a/otoroshi/app/next/plugins/auth.scala +++ b/otoroshi/app/next/plugins/auth.scala @@ -1,5 +1,6 @@ package otoroshi.next.plugins +import akka.http.scaladsl.model.Uri import akka.stream.Materializer import akka.util.ByteString import otoroshi.env.Env @@ -13,6 +14,9 @@ import play.api.libs.json._ import play.api.mvc.Results.BadRequest import play.api.mvc.{Result, Results} +import java.net.URLEncoder +import java.nio.charset.StandardCharsets +import java.util.Base64 import scala.concurrent.{ExecutionContext, Future} import scala.util.{Failure, Success, Try} @@ -275,13 +279,18 @@ class MultiAuthModule extends NgAccessValidator { ctx.attrs.put(otoroshi.plugins.Keys.UserKey -> paUsr) NgAccess.NgAllowed.vfuture case None => { - val redirect = ctx.request - .getQueryString("redirect") - .getOrElse(s"${ctx.request.theProtocol}://${ctx.request.theHost}${ctx.request.relativeUri}") + // val redirect = ctx.request + // .getQueryString("redirect") + // .getOrElse(s"${ctx.request.theProtocol}://${ctx.request.theHost}${ctx.request.relativeUri}") + val req = ctx.request + val redirect = s"${req.theProtocol}://${req.theHost}${req.relativeUri}" + val encodedRedirect = Base64.getUrlEncoder.encodeToString(redirect.getBytes(StandardCharsets.UTF_8)) + val descriptorId = ctx.route.legacy.id + val hash = env.sign(s"desc=${descriptorId}&redirect=${encodedRedirect}") val redirectTo = env.rootScheme + env.privateAppsHost + env.privateAppsPort + otoroshi.controllers.routes.AuthController .confidentialAppLoginPage() - .url + s"?desc=${ctx.route.legacy.id}&redirect=${redirect}" + .url + s"?desc=${descriptorId}&redirect=${encodedRedirect}&hash=${hash}" if (logger.isTraceEnabled) logger.trace("should redirect to " + redirectTo) NgAccess .NgDenied( @@ -367,13 +376,17 @@ class AuthModule extends NgAccessValidator { ctx.attrs.put(otoroshi.plugins.Keys.UserKey -> paUsr) NgAccess.NgAllowed.vfuture case None => { - val redirect = req - .getQueryString("redirect") - .getOrElse(s"${req.theProtocol}://${req.theHost}${req.relativeUri}") + // val redirect = req + // .getQueryString("redirect") + // .getOrElse(s"${req.theProtocol}://${req.theHost}${req.relativeUri}") + val redirect = s"${req.theProtocol}://${req.theHost}${req.relativeUri}" + val encodedRedirect = Base64.getUrlEncoder.encodeToString(redirect.getBytes(StandardCharsets.UTF_8)) + val descriptorId = descriptor.id + val hash = env.sign(s"desc=${descriptorId}&redirect=${encodedRedirect}") val redirectTo = env.rootScheme + env.privateAppsHost + env.privateAppsPort + otoroshi.controllers.routes.AuthController .confidentialAppLoginPage() - .url + s"?desc=${descriptor.id}&redirect=${redirect}" + .url + s"?desc=${descriptorId}&redirect=${encodedRedirect}&hash=${hash}" if (logger.isTraceEnabled) logger.trace("should redirect to " + redirectTo) NgAccess .NgDenied(