# HG changeset patch # User Mike Becker # Date 1627990892 -7200 # Node ID c9c6abf167c7fe5ff65ac31f37bc8571cba57a36 # Parent 785820da6485ddf057ee336399bc7218d2f24f6b #21 adds input validation mechanism Also upgrades to Kotlin 1.5.21 diff -r 785820da6485 -r c9c6abf167c7 build.gradle.kts --- a/build.gradle.kts Tue Aug 03 12:22:10 2021 +0200 +++ b/build.gradle.kts Tue Aug 03 13:41:32 2021 +0200 @@ -1,7 +1,7 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile plugins { - kotlin("jvm") version "1.4.10" + kotlin("jvm") version "1.5.21" war } group = "de.uapcore" diff -r 785820da6485 -r c9c6abf167c7 src/main/kotlin/de/uapcore/lightpit/RequestMapping.kt --- a/src/main/kotlin/de/uapcore/lightpit/RequestMapping.kt Tue Aug 03 12:22:10 2021 +0200 +++ b/src/main/kotlin/de/uapcore/lightpit/RequestMapping.kt Tue Aug 03 13:41:32 2021 +0200 @@ -37,6 +37,10 @@ typealias MappingMethod = (HttpRequest, DataAccessObject) -> Unit typealias PathParameters = Map +sealed interface ValidationResult +class ValidationError(val message: String): ValidationResult +class ValidatedValue(val result: T): ValidationResult + class HttpRequest( val request: HttpServletRequest, val response: HttpServletResponse, @@ -155,6 +159,18 @@ fun param(name: String): String? = request.getParameter(name) fun paramArray(name: String): Array = request.getParameterValues(name) ?: emptyArray() + fun param(name: String, validator: (String?) -> (ValidationResult), errorMessages: MutableList): T? { + return when (val result = validator(param(name))) { + is ValidationError -> { + errorMessages.add(i18n(result.message)) + null + } + is ValidatedValue -> { + result.result + } + } + } + private fun forward(jsp: String) { request.getRequestDispatcher(jspPath(jsp)).forward(request, response) } diff -r 785820da6485 -r c9c6abf167c7 src/main/kotlin/de/uapcore/lightpit/servlet/UsersServlet.kt --- a/src/main/kotlin/de/uapcore/lightpit/servlet/UsersServlet.kt Tue Aug 03 12:22:10 2021 +0200 +++ b/src/main/kotlin/de/uapcore/lightpit/servlet/UsersServlet.kt Tue Aug 03 13:41:32 2021 +0200 @@ -25,12 +25,9 @@ package de.uapcore.lightpit.servlet -import de.uapcore.lightpit.AbstractServlet -import de.uapcore.lightpit.HttpRequest -import de.uapcore.lightpit.LoggingTrait +import de.uapcore.lightpit.* import de.uapcore.lightpit.dao.DataAccessObject import de.uapcore.lightpit.entities.User -import de.uapcore.lightpit.logger import de.uapcore.lightpit.viewmodel.UserEditView import de.uapcore.lightpit.viewmodel.UsersView import javax.servlet.annotation.WebServlet @@ -48,21 +45,21 @@ private val list = "users" private val form = "user-form" - fun index(http: HttpRequest, dao: DataAccessObject) { + private fun index(http: HttpRequest, dao: DataAccessObject) { with(http) { view = UsersView(dao.listUsers()) render(list) } } - fun create(http: HttpRequest, dao: DataAccessObject) { + private fun create(http: HttpRequest, dao: DataAccessObject) { with(http) { view = UserEditView(User(-1)) render(form) } } - fun edit(http: HttpRequest, dao: DataAccessObject) { + private fun edit(http: HttpRequest, dao: DataAccessObject) { val id = http.pathParams["userid"]?.toIntOrNull() if (id == null) { http.response.sendError(404) @@ -79,7 +76,7 @@ } } - fun commit(http: HttpRequest, dao: DataAccessObject) { + private fun commit(http: HttpRequest, dao: DataAccessObject) { val id = http.param("userid")?.toIntOrNull() if (id == null) { http.response.sendError(400) @@ -88,25 +85,32 @@ val user = User(id) with(user) { - username = http.param("username") ?: "" givenname = http.param("givenname") lastname = http.param("lastname") mail = http.param("mail") } - if (dao.findUserByName(user.username) != null) { - with(http) { - view = UserEditView(user).apply { errorText = "validation.username.unique" } + if (user.id > 0) { + logger().info("Update user with id ${user.id}.") + dao.updateUser(user) + http.renderCommit("users/") + } else { + val errorMessages = mutableListOf() + val username = http.param("username", { + if (it == null) ValidationError("validation.username.null") + else if (dao.findUserByName(it) != null) ValidationError("validation.username.unique") + else ValidatedValue(it) + }, errorMessages) + + if (username != null) { + logger().info("Insert user ${username}.") + user.username = username + dao.insertUser(user) + http.renderCommit("users/") + } else { + http.view = UserEditView(user).apply { this.errorMessages = errorMessages } + http.render(form) } } - - if (user.id > 0) { - logger().info("Update user ${user.username} with id ${user.id}.") - dao.updateUser(user) - } else { - logger().info("Insert user ${user.username}.") - dao.insertUser(user) - } - http.renderCommit("users/") } } \ No newline at end of file diff -r 785820da6485 -r c9c6abf167c7 src/main/kotlin/de/uapcore/lightpit/types/WebColor.kt --- a/src/main/kotlin/de/uapcore/lightpit/types/WebColor.kt Tue Aug 03 12:22:10 2021 +0200 +++ b/src/main/kotlin/de/uapcore/lightpit/types/WebColor.kt Tue Aug 03 13:41:32 2021 +0200 @@ -35,7 +35,7 @@ /** * The color representation with the leading hash symbol. */ - val color: String = (if (arg.startsWith("#")) arg else "#$arg").toUpperCase() + val color: String = (if (arg.startsWith("#")) arg else "#$arg").uppercase() /** * The hex representation without the leading hash symbol. diff -r 785820da6485 -r c9c6abf167c7 src/main/kotlin/de/uapcore/lightpit/viewmodel/View.kt --- a/src/main/kotlin/de/uapcore/lightpit/viewmodel/View.kt Tue Aug 03 12:22:10 2021 +0200 +++ b/src/main/kotlin/de/uapcore/lightpit/viewmodel/View.kt Tue Aug 03 13:41:32 2021 +0200 @@ -27,5 +27,5 @@ abstract class View abstract class EditView : View() { - var errorText: String? = null + var errorMessages: List = emptyList() } diff -r 785820da6485 -r c9c6abf167c7 src/main/resources/localization/strings.properties --- a/src/main/resources/localization/strings.properties Tue Aug 03 12:22:10 2021 +0200 +++ b/src/main/resources/localization/strings.properties Tue Aug 03 13:41:32 2021 +0200 @@ -128,6 +128,7 @@ user.lastname=Last Name user.mail=E-Mail username=User Name +validation.username.null=Username is mandatory. validation.username.unique=Username is already taken. version.latest=Latest Version version.next=Next Version diff -r 785820da6485 -r c9c6abf167c7 src/main/resources/localization/strings_de.properties --- a/src/main/resources/localization/strings_de.properties Tue Aug 03 12:22:10 2021 +0200 +++ b/src/main/resources/localization/strings_de.properties Tue Aug 03 13:41:32 2021 +0200 @@ -127,6 +127,7 @@ user.lastname=Nachname user.mail=E-Mail username=Benutzername +validation.username.null=Benutzername ist ein Pflichtfeld. validation.username.unique=Der Benutzername wird bereits verwendet. version.latest=Neuste Version version.next=N\u00e4chste Version diff -r 785820da6485 -r c9c6abf167c7 src/main/webapp/WEB-INF/jsp/user-form.jsp --- a/src/main/webapp/WEB-INF/jsp/user-form.jsp Tue Aug 03 12:22:10 2021 +0200 +++ b/src/main/webapp/WEB-INF/jsp/user-form.jsp Tue Aug 03 13:41:32 2021 +0200 @@ -38,34 +38,30 @@ + + + <%@include file="../jspf/error-messages.jspf" %> + + - " + " readonly /> - " /> + " /> - " /> + " /> - " /> + " /> - - - -
- -
- - -
diff -r 785820da6485 -r c9c6abf167c7 src/main/webapp/WEB-INF/jspf/error-messages.jspf --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/main/webapp/WEB-INF/jspf/error-messages.jspf Tue Aug 03 13:41:32 2021 +0200 @@ -0,0 +1,34 @@ +<%-- + ~ Copyright 2021 Mike Becker. All rights reserved. + ~ + ~ Redistribution and use in source and binary forms, with or without + ~ modification, are permitted provided that the following conditions are met: + ~ + ~ 1. Redistributions of source code must retain the above copyright + ~ notice, this list of conditions and the following disclaimer. + ~ + ~ 2. Redistributions in binary form must reproduce the above copyright + ~ notice, this list of conditions and the following disclaimer in the + ~ documentation and/or other materials provided with the distribution. + ~ + ~ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + ~ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + ~ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + ~ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + ~ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + ~ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + ~ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + ~ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + ~ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + ~ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + --%> +<%@page pageEncoding="UTF-8" %> +<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> + +
+ +
+ +
+
+
diff -r 785820da6485 -r c9c6abf167c7 src/main/webapp/lightpit.css --- a/src/main/webapp/lightpit.css Tue Aug 03 12:22:10 2021 +0200 +++ b/src/main/webapp/lightpit.css Tue Aug 03 13:41:32 2021 +0200 @@ -198,6 +198,7 @@ table.formtable tbody td > * { width: 100%; + margin: 0; box-sizing: border-box; } @@ -238,7 +239,7 @@ } .info-box, .error-box, .warn-box { - margin: 2em; + margin: 1.5em; border-style: dashed; border-width: thin; border-color: deepskyblue; @@ -246,11 +247,15 @@ } .error-box { + border-style: outset; border-color: red; + background: lightcoral; } .warn-box { + border-style: outset; border-color: gold; + background: lightgoldenrodyellow; } .table {