#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
     1.1 --- a/build.gradle.kts	Tue Aug 03 12:22:10 2021 +0200
     1.2 +++ b/build.gradle.kts	Tue Aug 03 13:41:32 2021 +0200
     1.3 @@ -1,7 +1,7 @@
     1.4  import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
     1.5  
     1.6  plugins {
     1.7 -    kotlin("jvm") version "1.4.10"
     1.8 +    kotlin("jvm") version "1.5.21"
     1.9      war
    1.10  }
    1.11  group = "de.uapcore"
     2.1 --- a/src/main/kotlin/de/uapcore/lightpit/RequestMapping.kt	Tue Aug 03 12:22:10 2021 +0200
     2.2 +++ b/src/main/kotlin/de/uapcore/lightpit/RequestMapping.kt	Tue Aug 03 13:41:32 2021 +0200
     2.3 @@ -37,6 +37,10 @@
     2.4  typealias MappingMethod = (HttpRequest, DataAccessObject) -> Unit
     2.5  typealias PathParameters = Map<String, String>
     2.6  
     2.7 +sealed interface ValidationResult<T>
     2.8 +class ValidationError<T>(val message: String): ValidationResult<T>
     2.9 +class ValidatedValue<T>(val result: T): ValidationResult<T>
    2.10 +
    2.11  class HttpRequest(
    2.12      val request: HttpServletRequest,
    2.13      val response: HttpServletResponse,
    2.14 @@ -155,6 +159,18 @@
    2.15      fun param(name: String): String? = request.getParameter(name)
    2.16      fun paramArray(name: String): Array<String> = request.getParameterValues(name) ?: emptyArray()
    2.17  
    2.18 +    fun <T> param(name: String, validator: (String?) -> (ValidationResult<T>), errorMessages: MutableList<String>): T? {
    2.19 +        return when (val result = validator(param(name))) {
    2.20 +            is ValidationError -> {
    2.21 +                errorMessages.add(i18n(result.message))
    2.22 +                null
    2.23 +            }
    2.24 +            is ValidatedValue -> {
    2.25 +                result.result
    2.26 +            }
    2.27 +        }
    2.28 +    }
    2.29 +
    2.30      private fun forward(jsp: String) {
    2.31          request.getRequestDispatcher(jspPath(jsp)).forward(request, response)
    2.32      }
     3.1 --- a/src/main/kotlin/de/uapcore/lightpit/servlet/UsersServlet.kt	Tue Aug 03 12:22:10 2021 +0200
     3.2 +++ b/src/main/kotlin/de/uapcore/lightpit/servlet/UsersServlet.kt	Tue Aug 03 13:41:32 2021 +0200
     3.3 @@ -25,12 +25,9 @@
     3.4  
     3.5  package de.uapcore.lightpit.servlet
     3.6  
     3.7 -import de.uapcore.lightpit.AbstractServlet
     3.8 -import de.uapcore.lightpit.HttpRequest
     3.9 -import de.uapcore.lightpit.LoggingTrait
    3.10 +import de.uapcore.lightpit.*
    3.11  import de.uapcore.lightpit.dao.DataAccessObject
    3.12  import de.uapcore.lightpit.entities.User
    3.13 -import de.uapcore.lightpit.logger
    3.14  import de.uapcore.lightpit.viewmodel.UserEditView
    3.15  import de.uapcore.lightpit.viewmodel.UsersView
    3.16  import javax.servlet.annotation.WebServlet
    3.17 @@ -48,21 +45,21 @@
    3.18      private val list = "users"
    3.19      private val form = "user-form"
    3.20  
    3.21 -    fun index(http: HttpRequest, dao: DataAccessObject) {
    3.22 +    private fun index(http: HttpRequest, dao: DataAccessObject) {
    3.23          with(http) {
    3.24              view = UsersView(dao.listUsers())
    3.25              render(list)
    3.26          }
    3.27      }
    3.28  
    3.29 -    fun create(http: HttpRequest, dao: DataAccessObject) {
    3.30 +    private fun create(http: HttpRequest, dao: DataAccessObject) {
    3.31          with(http) {
    3.32              view = UserEditView(User(-1))
    3.33              render(form)
    3.34          }
    3.35      }
    3.36  
    3.37 -    fun edit(http: HttpRequest, dao: DataAccessObject) {
    3.38 +    private fun edit(http: HttpRequest, dao: DataAccessObject) {
    3.39          val id = http.pathParams["userid"]?.toIntOrNull()
    3.40          if (id == null) {
    3.41              http.response.sendError(404)
    3.42 @@ -79,7 +76,7 @@
    3.43          }
    3.44      }
    3.45  
    3.46 -    fun commit(http: HttpRequest, dao: DataAccessObject) {
    3.47 +    private fun commit(http: HttpRequest, dao: DataAccessObject) {
    3.48          val id = http.param("userid")?.toIntOrNull()
    3.49          if (id == null) {
    3.50              http.response.sendError(400)
    3.51 @@ -88,25 +85,32 @@
    3.52  
    3.53          val user = User(id)
    3.54          with(user) {
    3.55 -            username = http.param("username") ?: ""
    3.56              givenname = http.param("givenname")
    3.57              lastname = http.param("lastname")
    3.58              mail = http.param("mail")
    3.59          }
    3.60  
    3.61 -        if (dao.findUserByName(user.username) != null) {
    3.62 -            with(http) {
    3.63 -                view = UserEditView(user).apply { errorText = "validation.username.unique" }
    3.64 +        if (user.id > 0) {
    3.65 +            logger().info("Update user with id ${user.id}.")
    3.66 +            dao.updateUser(user)
    3.67 +            http.renderCommit("users/")
    3.68 +        } else {
    3.69 +            val errorMessages = mutableListOf<String>()
    3.70 +            val username = http.param("username", {
    3.71 +                if (it == null) ValidationError("validation.username.null")
    3.72 +                else if (dao.findUserByName(it) != null) ValidationError("validation.username.unique")
    3.73 +                else ValidatedValue(it)
    3.74 +            }, errorMessages)
    3.75 +
    3.76 +            if (username != null) {
    3.77 +                logger().info("Insert user ${username}.")
    3.78 +                user.username = username
    3.79 +                dao.insertUser(user)
    3.80 +                http.renderCommit("users/")
    3.81 +            } else {
    3.82 +                http.view = UserEditView(user).apply { this.errorMessages = errorMessages }
    3.83 +                http.render(form)
    3.84              }
    3.85          }
    3.86 -
    3.87 -        if (user.id > 0) {
    3.88 -            logger().info("Update user ${user.username} with id ${user.id}.")
    3.89 -            dao.updateUser(user)
    3.90 -        } else {
    3.91 -            logger().info("Insert user ${user.username}.")
    3.92 -            dao.insertUser(user)
    3.93 -        }
    3.94 -        http.renderCommit("users/")
    3.95      }
    3.96  }
    3.97 \ No newline at end of file
     4.1 --- a/src/main/kotlin/de/uapcore/lightpit/types/WebColor.kt	Tue Aug 03 12:22:10 2021 +0200
     4.2 +++ b/src/main/kotlin/de/uapcore/lightpit/types/WebColor.kt	Tue Aug 03 13:41:32 2021 +0200
     4.3 @@ -35,7 +35,7 @@
     4.4      /**
     4.5       * The color representation with the leading hash symbol.
     4.6       */
     4.7 -    val color: String = (if (arg.startsWith("#")) arg else "#$arg").toUpperCase()
     4.8 +    val color: String = (if (arg.startsWith("#")) arg else "#$arg").uppercase()
     4.9  
    4.10      /**
    4.11       * The hex representation without the leading hash symbol.
     5.1 --- a/src/main/kotlin/de/uapcore/lightpit/viewmodel/View.kt	Tue Aug 03 12:22:10 2021 +0200
     5.2 +++ b/src/main/kotlin/de/uapcore/lightpit/viewmodel/View.kt	Tue Aug 03 13:41:32 2021 +0200
     5.3 @@ -27,5 +27,5 @@
     5.4  
     5.5  abstract class View
     5.6  abstract class EditView : View() {
     5.7 -    var errorText: String? = null
     5.8 +    var errorMessages: List<String> = emptyList()
     5.9  }
     6.1 --- a/src/main/resources/localization/strings.properties	Tue Aug 03 12:22:10 2021 +0200
     6.2 +++ b/src/main/resources/localization/strings.properties	Tue Aug 03 13:41:32 2021 +0200
     6.3 @@ -128,6 +128,7 @@
     6.4  user.lastname=Last Name
     6.5  user.mail=E-Mail
     6.6  username=User Name
     6.7 +validation.username.null=Username is mandatory.
     6.8  validation.username.unique=Username is already taken.
     6.9  version.latest=Latest Version
    6.10  version.next=Next Version
     7.1 --- a/src/main/resources/localization/strings_de.properties	Tue Aug 03 12:22:10 2021 +0200
     7.2 +++ b/src/main/resources/localization/strings_de.properties	Tue Aug 03 13:41:32 2021 +0200
     7.3 @@ -127,6 +127,7 @@
     7.4  user.lastname=Nachname
     7.5  user.mail=E-Mail
     7.6  username=Benutzername
     7.7 +validation.username.null=Benutzername ist ein Pflichtfeld.
     7.8  validation.username.unique=Der Benutzername wird bereits verwendet.
     7.9  version.latest=Neuste Version
    7.10  version.next=N\u00e4chste Version
     8.1 --- a/src/main/webapp/WEB-INF/jsp/user-form.jsp	Tue Aug 03 12:22:10 2021 +0200
     8.2 +++ b/src/main/webapp/WEB-INF/jsp/user-form.jsp	Tue Aug 03 13:41:32 2021 +0200
     8.3 @@ -38,34 +38,30 @@
     8.4              <col style="width: 50ch">
     8.5          </colgroup>
     8.6          <tbody>
     8.7 +        <c:if test="${not empty viewmodel.errorMessages}">
     8.8 +        <tr>
     8.9 +            <td colspan="2"><%@include file="../jspf/error-messages.jspf" %></td>
    8.10 +        </tr>
    8.11 +        </c:if>
    8.12          <tr>
    8.13              <th><fmt:message key="username"/></th>
    8.14 -            <td><input name="username" type="text" maxlength="50" required value="<c:out value="${user.username}"/>"
    8.15 +            <td><input name="username" type="text" maxlength="200" required value="<c:out value="${user.username}"/>"
    8.16                         <c:if test="${user.id ge 0}">readonly</c:if> /></td>
    8.17          </tr>
    8.18          <tr>
    8.19              <th><fmt:message key="user.givenname"/></th>
    8.20 -            <td><input name="givenname" type="text" maxlength="50" value="<c:out value="${user.givenname}"/>" /></td>
    8.21 +            <td><input name="givenname" type="text" maxlength="200" value="<c:out value="${user.givenname}"/>" /></td>
    8.22          </tr>
    8.23          <tr>
    8.24              <th><fmt:message key="user.lastname"/></th>
    8.25 -            <td><input name="lastname" type="text" maxlength="50" value="<c:out value="${user.lastname}"/>" /></td>
    8.26 +            <td><input name="lastname" type="text" maxlength="200" value="<c:out value="${user.lastname}"/>" /></td>
    8.27          </tr>
    8.28          <tr>
    8.29              <th><fmt:message key="user.mail"/></th>
    8.30 -            <td><input name="mail" type="email" maxlength="50" value="<c:out value="${user.mail}"/>" /></td>
    8.31 +            <td><input name="mail" type="email" maxlength="200" value="<c:out value="${user.mail}"/>" /></td>
    8.32          </tr>
    8.33          </tbody>
    8.34          <tfoot>
    8.35 -        <c:if test="${not empty viewmodel.errorText}">
    8.36 -        <tr>
    8.37 -            <td colspan="2">
    8.38 -                <div class="error-box">
    8.39 -                    <fmt:message key="${viewmodel.errorText}"/>
    8.40 -                </div>
    8.41 -            </td>
    8.42 -        </tr>
    8.43 -        </c:if>
    8.44          <tr>
    8.45              <td colspan="2">
    8.46                  <input type="hidden" name="userid" value="${user.id}"/>
     9.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     9.2 +++ b/src/main/webapp/WEB-INF/jspf/error-messages.jspf	Tue Aug 03 13:41:32 2021 +0200
     9.3 @@ -0,0 +1,34 @@
     9.4 +<%--
     9.5 +  ~ Copyright 2021 Mike Becker. All rights reserved.
     9.6 +  ~
     9.7 +  ~ Redistribution and use in source and binary forms, with or without
     9.8 +  ~ modification, are permitted provided that the following conditions are met:
     9.9 +  ~
    9.10 +  ~ 1. Redistributions of source code must retain the above copyright
    9.11 +  ~ notice, this list of conditions and the following disclaimer.
    9.12 +  ~
    9.13 +  ~ 2. Redistributions in binary form must reproduce the above copyright
    9.14 +  ~ notice, this list of conditions and the following disclaimer in the
    9.15 +  ~ documentation and/or other materials provided with the distribution.
    9.16 +  ~
    9.17 +  ~ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
    9.18 +  ~ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
    9.19 +  ~ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
    9.20 +  ~ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
    9.21 +  ~ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
    9.22 +  ~ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
    9.23 +  ~ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
    9.24 +  ~ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
    9.25 +  ~ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
    9.26 +  ~ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    9.27 +  --%>
    9.28 +<%@page pageEncoding="UTF-8" %>
    9.29 +<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
    9.30 +
    9.31 +<div class="error-box hcenter">
    9.32 +    <c:forEach var="errorMessage" items="${viewmodel.errorMessages}">
    9.33 +        <div>
    9.34 +            <c:out value="${errorMessage}"/>
    9.35 +        </div>
    9.36 +    </c:forEach>
    9.37 +</div>
    10.1 --- a/src/main/webapp/lightpit.css	Tue Aug 03 12:22:10 2021 +0200
    10.2 +++ b/src/main/webapp/lightpit.css	Tue Aug 03 13:41:32 2021 +0200
    10.3 @@ -198,6 +198,7 @@
    10.4  
    10.5  table.formtable tbody td > * {
    10.6      width: 100%;
    10.7 +    margin: 0;
    10.8      box-sizing: border-box;
    10.9  }
   10.10  
   10.11 @@ -238,7 +239,7 @@
   10.12  }
   10.13  
   10.14  .info-box, .error-box, .warn-box {
   10.15 -    margin: 2em;
   10.16 +    margin: 1.5em;
   10.17      border-style: dashed;
   10.18      border-width: thin;
   10.19      border-color: deepskyblue;
   10.20 @@ -246,11 +247,15 @@
   10.21  }
   10.22  
   10.23  .error-box {
   10.24 +    border-style: outset;
   10.25      border-color: red;
   10.26 +    background: lightcoral;
   10.27  }
   10.28  
   10.29  .warn-box {
   10.30 +    border-style: outset;
   10.31      border-color: gold;
   10.32 +    background: lightgoldenrodyellow;
   10.33  }
   10.34  
   10.35  .table {

mercurial