#21 adds input validation mechanism

Tue, 03 Aug 2021 13:41:32 +0200

author
Mike Becker <universe@uap-core.de>
date
Tue, 03 Aug 2021 13:41:32 +0200
changeset 209
c9c6abf167c7
parent 208
785820da6485
child 210
37fbdcb422b7

#21 adds input validation mechanism

Also upgrades to Kotlin 1.5.21

build.gradle.kts file | annotate | diff | comparison | revisions
src/main/kotlin/de/uapcore/lightpit/RequestMapping.kt file | annotate | diff | comparison | revisions
src/main/kotlin/de/uapcore/lightpit/servlet/UsersServlet.kt file | annotate | diff | comparison | revisions
src/main/kotlin/de/uapcore/lightpit/types/WebColor.kt file | annotate | diff | comparison | revisions
src/main/kotlin/de/uapcore/lightpit/viewmodel/View.kt file | annotate | diff | comparison | revisions
src/main/resources/localization/strings.properties file | annotate | diff | comparison | revisions
src/main/resources/localization/strings_de.properties file | annotate | diff | comparison | revisions
src/main/webapp/WEB-INF/jsp/user-form.jsp file | annotate | diff | comparison | revisions
src/main/webapp/WEB-INF/jspf/error-messages.jspf file | annotate | diff | comparison | revisions
src/main/webapp/lightpit.css file | annotate | diff | comparison | revisions
--- 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"
--- 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<String, String>
 
+sealed interface ValidationResult<T>
+class ValidationError<T>(val message: String): ValidationResult<T>
+class ValidatedValue<T>(val result: T): ValidationResult<T>
+
 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<String> = request.getParameterValues(name) ?: emptyArray()
 
+    fun <T> param(name: String, validator: (String?) -> (ValidationResult<T>), errorMessages: MutableList<String>): 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)
     }
--- 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<String>()
+            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
--- 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.
--- 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<String> = emptyList()
 }
--- 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
--- 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
--- 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 @@
             <col style="width: 50ch">
         </colgroup>
         <tbody>
+        <c:if test="${not empty viewmodel.errorMessages}">
+        <tr>
+            <td colspan="2"><%@include file="../jspf/error-messages.jspf" %></td>
+        </tr>
+        </c:if>
         <tr>
             <th><fmt:message key="username"/></th>
-            <td><input name="username" type="text" maxlength="50" required value="<c:out value="${user.username}"/>"
+            <td><input name="username" type="text" maxlength="200" required value="<c:out value="${user.username}"/>"
                        <c:if test="${user.id ge 0}">readonly</c:if> /></td>
         </tr>
         <tr>
             <th><fmt:message key="user.givenname"/></th>
-            <td><input name="givenname" type="text" maxlength="50" value="<c:out value="${user.givenname}"/>" /></td>
+            <td><input name="givenname" type="text" maxlength="200" value="<c:out value="${user.givenname}"/>" /></td>
         </tr>
         <tr>
             <th><fmt:message key="user.lastname"/></th>
-            <td><input name="lastname" type="text" maxlength="50" value="<c:out value="${user.lastname}"/>" /></td>
+            <td><input name="lastname" type="text" maxlength="200" value="<c:out value="${user.lastname}"/>" /></td>
         </tr>
         <tr>
             <th><fmt:message key="user.mail"/></th>
-            <td><input name="mail" type="email" maxlength="50" value="<c:out value="${user.mail}"/>" /></td>
+            <td><input name="mail" type="email" maxlength="200" value="<c:out value="${user.mail}"/>" /></td>
         </tr>
         </tbody>
         <tfoot>
-        <c:if test="${not empty viewmodel.errorText}">
-        <tr>
-            <td colspan="2">
-                <div class="error-box">
-                    <fmt:message key="${viewmodel.errorText}"/>
-                </div>
-            </td>
-        </tr>
-        </c:if>
         <tr>
             <td colspan="2">
                 <input type="hidden" name="userid" value="${user.id}"/>
--- /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" %>
+
+<div class="error-box hcenter">
+    <c:forEach var="errorMessage" items="${viewmodel.errorMessages}">
+        <div>
+            <c:out value="${errorMessage}"/>
+        </div>
+    </c:forEach>
+</div>
--- 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 {

mercurial