From 8cae093fec72991a7d40d02101cfafbb6ff5683a Mon Sep 17 00:00:00 2001 From: Florian M Date: Tue, 17 Jan 2023 09:52:01 +0100 Subject: [PATCH] Purge last usages of SqlToken.raw with user input (#6745) * Purge last usages of SqlToken.raw with user input * Update SqlInterpolation.scala * fix tests, add test for enum array * Update app/utils/sql/SqlInterpolation.scala Co-authored-by: Norman Rzepka --- app/models/binary/DataSet.scala | 3 +-- app/models/user/MultiUser.scala | 18 ++++++++++----- app/oxalis/security/Token.scala | 12 ++++++++-- app/utils/sql/SqlInterpolation.scala | 23 +++----------------- test/backend/SqlInterpolationTestSuite.scala | 16 +++++++++----- 5 files changed, 38 insertions(+), 34 deletions(-) diff --git a/app/models/binary/DataSet.scala b/app/models/binary/DataSet.scala index 88dae4855e2..d434b5ba939 100755 --- a/app/models/binary/DataSet.scala +++ b/app/models/binary/DataSet.scala @@ -189,8 +189,7 @@ class DataSetDAO @Inject()(sqlClient: SqlClient, case None => q"${true}" case Some(searchQuery) => val queryTokens = searchQuery.toLowerCase.trim.split(" +") - SqlToken.raw( - queryTokens.map(queryToken => s"POSITION(${escapeLiteral(queryToken)} IN LOWER(name)) > 0").mkString(" AND ")) + SqlToken.joinBySeparator(queryTokens.map(queryToken => q"POSITION($queryToken IN LOWER(name)) > 0"), " AND ") } def countByFolder(folderId: ObjectId): Fox[Int] = diff --git a/app/models/user/MultiUser.scala b/app/models/user/MultiUser.scala index c1db717178c..94353b8b2dc 100644 --- a/app/models/user/MultiUser.scala +++ b/app/models/user/MultiUser.scala @@ -2,18 +2,18 @@ package models.user import com.mohiva.play.silhouette.api.util.PasswordInfo import com.scalableminds.util.accesscontext.DBAccessContext +import com.scalableminds.util.enumeration.ExtendedEnumeration import com.scalableminds.util.time.Instant import com.scalableminds.util.tools.{Fox, JsonHelper} - -import javax.inject.Inject import com.scalableminds.webknossos.schema.Tables._ import models.user.Theme.Theme import play.api.libs.json.Format.GenericFormat import play.api.libs.json.{JsObject, Json} import slick.lifted.Rep -import utils.sql.{SQLDAO, SqlClient, SqlToken} import utils.ObjectId +import utils.sql.{SQLDAO, SqlClient} +import javax.inject.Inject import scala.concurrent.ExecutionContext case class MultiUser( @@ -28,6 +28,12 @@ case class MultiUser( isDeleted: Boolean = false ) +object PasswordHasherType extends ExtendedEnumeration { + type PasswordHasher = Value + + val SCrypt, Empty = Value +} + class MultiUserDAO @Inject()(sqlClient: SqlClient)(implicit ec: ExecutionContext) extends SQLDAO[MultiUser, MultiusersRow, Multiusers](sqlClient) { protected val collection = Multiusers @@ -55,11 +61,12 @@ class MultiUserDAO @Inject()(sqlClient: SqlClient)(implicit ec: ExecutionContext def insertOne(u: MultiUser): Fox[Unit] = for { + passwordInfoHasher <- PasswordHasherType.fromString(u.passwordInfo.hasher).toFox _ <- run(q"""insert into webknossos.multiusers(_id, email, passwordInfo_hasher, passwordInfo_password, isSuperUser, novelUserExperienceInfos, selectedTheme, created, isDeleted) - values(${u._id}, ${u.email}, ${SqlToken.raw(escapeLiteral(u.passwordInfo.hasher))}, + values(${u._id}, ${u.email}, $passwordInfoHasher, ${u.passwordInfo.password}, ${u.isSuperUser}, ${u.novelUserExperienceInfos}, ${u.selectedTheme}, ${u.created}, ${u.isDeleted})""".asUpdate) @@ -68,8 +75,9 @@ class MultiUserDAO @Inject()(sqlClient: SqlClient)(implicit ec: ExecutionContext def updatePasswordInfo(multiUserId: ObjectId, passwordInfo: PasswordInfo)(implicit ctx: DBAccessContext): Fox[Unit] = for { _ <- assertUpdateAccess(multiUserId) + passwordInfoHasher <- PasswordHasherType.fromString(passwordInfo.hasher).toFox _ <- run(q"""update webknossos.multiusers set - passwordInfo_hasher = ${SqlToken.raw(escapeLiteral(passwordInfo.hasher))}, + passwordInfo_hasher = $passwordInfoHasher, passwordInfo_password = ${passwordInfo.password} where _id = $multiUserId""".asUpdate) } yield () diff --git a/app/oxalis/security/Token.scala b/app/oxalis/security/Token.scala index 13fd559ac8b..ebff8c098ee 100644 --- a/app/oxalis/security/Token.scala +++ b/app/oxalis/security/Token.scala @@ -3,14 +3,15 @@ package oxalis.security import com.mohiva.play.silhouette.api.LoginInfo import com.mohiva.play.silhouette.impl.authenticators.BearerTokenAuthenticator import com.scalableminds.util.accesscontext.DBAccessContext +import com.scalableminds.util.enumeration.ExtendedEnumeration import com.scalableminds.util.time.Instant import com.scalableminds.util.tools.Fox import com.scalableminds.webknossos.schema.Tables._ import oxalis.security.TokenType.TokenType import slick.jdbc.PostgresProfile.api._ import slick.lifted.Rep -import utils.sql.{SQLDAO, SqlClient, SqlToken} import utils.ObjectId +import utils.sql.{SQLDAO, SqlClient} import javax.inject.Inject import scala.concurrent.ExecutionContext @@ -37,6 +38,12 @@ case class Token(_id: ObjectId, )) } +object LoginInfoProvider extends ExtendedEnumeration { + type PasswordHasher = Value + + val credentials: LoginInfoProvider.Value = Value +} + object Token { def fromBearerTokenAuthenticator(b: BearerTokenAuthenticator, tokenType: TokenType)( implicit ec: ExecutionContext): Fox[Token] = @@ -97,9 +104,10 @@ class TokenDAO @Inject()(sqlClient: SqlClient)(implicit ec: ExecutionContext) def insertOne(t: Token): Fox[Unit] = for { + loginInfoProvider <- LoginInfoProvider.fromString(t.loginInfo.providerID).toFox _ <- run( q"""insert into webknossos.tokens(_id, value, loginInfo_providerID, loginInfo_providerKey, lastUsedDateTime, expirationDateTime, idleTimeout, tokenType, created, isDeleted) - values(${t._id}, ${t.value}, ${SqlToken.raw(escapeLiteral(t.loginInfo.providerID))}, + values(${t._id}, ${t.value}, $loginInfoProvider, ${t.loginInfo.providerKey}, ${t.lastUsedDateTime}, ${t.expirationDateTime}, ${t.idleTimeout.map(_.toMillis)}, ${t.tokenType}, ${t.created}, ${t.isDeleted})""".asUpdate) diff --git a/app/utils/sql/SqlInterpolation.scala b/app/utils/sql/SqlInterpolation.scala index 0107ed9db5b..b12e6c7f2d7 100644 --- a/app/utils/sql/SqlInterpolation.scala +++ b/app/utils/sql/SqlInterpolation.scala @@ -74,26 +74,6 @@ case class SqlToken(sql: String, values: List[SqlValue] = List()) { } object SqlToken { - def join(values: List[Either[SqlValue, SqlToken]], sep: String): SqlToken = { - val outputSql = mutable.StringBuilder.newBuilder - val outputValues = ListBuffer[SqlValue]() - for (i <- values.indices) { - val value = values(i) - value match { - case Left(x) => - outputSql ++= x.placeholder - outputValues += x - case Right(x) => - outputSql ++= x.sql - outputValues ++= x.values - } - if (i < values.length - 1) { - outputSql ++= sep - } - } - SqlToken(sql = outputSql.toString, values = outputValues.toList) - } - def tupleFromList(values: List[SqlValue]): SqlToken = SqlToken(sql = s"(${values.map(_.placeholder).mkString(", ")})", values = values) @@ -106,6 +86,9 @@ object SqlToken { def raw(s: String): SqlToken = SqlToken(s) + def joinBySeparator(tokens: Iterable[SqlToken], separator: String): SqlToken = + SqlToken(sql = tokens.map(_.sql).mkString(separator), values = tokens.flatMap(_.values).toList) + def empty: SqlToken = raw("") def identifier(id: String): SqlToken = raw('"' + id + '"') diff --git a/test/backend/SqlInterpolationTestSuite.scala b/test/backend/SqlInterpolationTestSuite.scala index 62b091e4383..ee15669bdf4 100644 --- a/test/backend/SqlInterpolationTestSuite.scala +++ b/test/backend/SqlInterpolationTestSuite.scala @@ -150,6 +150,14 @@ class SqlInterpolationTestSuite extends PlaySpec with SqlTypeImplicits { assert(sql == SqlToken("SELECT * FROM test WHERE state = ?", List(EnumerationValue(enumVal)))) assert(sql.debugInfo == "SELECT * FROM test WHERE state = 'PENDING'") } + "construct an SQLToken with Enumeration Array" in { + val enumVals = List(JobState.PENDING, JobState.STARTED) + val sql = q"""SELECT * FROM test WHERE state = ${EnumerationArrayValue(enumVals, "webknossos.JOB_STATE")}""" + assert( + sql == SqlToken("SELECT * FROM test WHERE state = ?::webknossos.JOB_STATE[]", + List(EnumerationArrayValue(enumVals, "webknossos.JOB_STATE")))) + assert(sql.debugInfo == "SELECT * FROM test WHERE state = {PENDING,STARTED}::webknossos.JOB_STATE[]") + } "construct an SQLToken with String Array" in { val stringList = List("First String", "Second String") val sql = q"""SELECT * FROM test WHERE tags = $stringList""" @@ -163,14 +171,12 @@ class SqlInterpolationTestSuite extends PlaySpec with SqlTypeImplicits { assert(sql.debugInfo == "SELECT * FROM test WHERE bounding_box = '(1.0,2.0,3.0,50.0,60.0,70.0)'") } "construct an SQLToken with nested-joined SQL" in { - val fields = List("name", "age") + val fields = List(q"name", q"age") val values = List("Bob".toSqlValue, 5.toSqlValue) val sql = - q"""INSERT INTO test(${SqlToken.join(fields.map(x => Right(SqlToken.identifier(x))), ", ")}) VALUES ${SqlToken - .tupleList(List(values))}""" + q"""INSERT INTO test(${SqlToken.joinBySeparator(fields, ", ")}) VALUES ${SqlToken.tupleList(List(values))}""" - assert( - sql == SqlToken("""INSERT INTO test("name", "age") VALUES (?, ?)""", List(StringValue("Bob"), IntValue(5)))) + assert(sql == SqlToken("""INSERT INTO test(name, age) VALUES (?, ?)""", List(StringValue("Bob"), IntValue(5)))) } "create debugInfo from SQLToken" in { val sql = q"""SELECT * FROM test WHERE age = ${3} AND name = ${"Amy"}"""