Ensuring proper validation of data uniqueness
import play.api.mvc._
import play.api.libs.json._
import scala.concurrent.ExecutionContext.Implicits.global
class UserController @Inject() (cc: ControllerComponents) extends AbstractController(cc) {
def createUser = Action.async(parse.json) { request =>
val userResult = request.body.validate[User]
userResult.fold(
errors => {
Future.successful(BadRequest(Json.obj("status" ->"KO", "message" -> JsError.toJson(errors))))
},
user => {
UserRepository.create(user).map { _ =>
Ok(Json.obj("status" ->"OK", "message" -> ("User '"+user.name+"' saved.") ))
}
}
)
}
}
The vulnerable part of the code is in the
createUser
method. When a POST request is made to this endpoint with a JSON body containing user information, the application attempts to parse the JSON into a
User
object and save it to the UserRepository.
However, there is no check to ensure that a user with the same information doesn't already exist in the repository. This could lead to duplicate user data in the repository, which is a violation of data uniqueness.
An attacker could take advantage of this vulnerability by repeatedly sending POST requests with the same user information, causing multiple identical users to be created in the UserRepository.
This vulnerability could be exploited to perform actions as the same user multiple times, or to cause confusion and errors in the application by creating multiple users with the same information.
import play.api.mvc._
import play.api.libs.json._
import scala.concurrent.ExecutionContext.Implicits.global
class UserController @Inject() (cc: ControllerComponents) extends AbstractController(cc) {
def createUser = Action.async(parse.json) { request =>
val userResult = request.body.validate[User]
userResult.fold(
errors => {
Future.successful(BadRequest(Json.obj("status" ->"KO", "message" -> JsError.toJson(errors))))
},
user => {
UserRepository.findByUsername(user.username).flatMap {
case Some(_) => Future.successful(BadRequest(Json.obj("status" ->"KO", "message" -> "User already exists")))
case None => UserRepository.create(user).map { _ =>
Ok(Json.obj("status" ->"OK", "message" -> ("User '"+user.name+"' saved.") ))
}
}
}
)
}
}
The updated code now includes a check for user uniqueness before creating a new user. This is done by calling
UserRepository.findByUsername(user.username)
. If a user with the same username already exists in the database, the application will return a
BadRequest
with a message indicating that the user already exists.
This check is performed atomically to avoid race conditions. If two requests to create a user with the same username are received at the same time, one of them will fail the uniqueness check and receive an error response.
This solution also handles any exceptions or errors that may occur during the uniqueness validation process. If an error occurs while checking for user uniqueness, the application will return a
BadRequest
with an appropriate error message.
In addition to this application-level check, consider using database constraints or unique indexes to enforce data uniqueness at the database level. This provides an additional layer of protection against data duplication.