CVE-2021-25935: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting') (CWE-79)
value: In OpenNMS Horizon, versions opennms-17.0.0-1 through opennms-27.1.0-1; OpenNMS Meridian, versions meridian-foundation-2015.1.0-1 through meridian-foundation-2019.1.18-1; meridian-foundation-2020.1.0-1 through meridian-foundation-2020.1.7-1 are vulnerable to Stored Cross-Site Scripting, since the function `add()` performs improper validation checks on the input sent to the `foreign-source` parameter. Due to this flaw an attacker could bypass the existing regex validation and inject an arbitrary script which will be stored in the database.
CWE description: The product does not neutralize or incorrectly neutralizes user-controllable input before it is placed in output that is used as a web page that is served to other users.
CVSS v3 base score: 5.4
Published: 2021-05-25T19:15Z
Repository: https://github.com/OpenNMS/opennms
Language: Other
Files changed: core/web-assets/src/main/assets/js/apps/onms-requisitions/lib/scripts/controllers/ForeignSource.js, core/web-assets/src/main/assets/js/apps/onms-requisitions/lib/scripts/controllers/Node.js, core/web-assets/src/main/assets/js/apps/onms-requisitions/lib/scripts/controllers/QuickAddNode.js, core/web-assets/src/main/assets/js/apps/onms-requisitions/lib/scripts/controllers/Requisition.js, core/web-assets/src/main/assets/js/apps/onms-requisitions/lib/scripts/controllers/Requisitions.js, core/web-assets/src/main/assets/js/apps/onms-requisitions/lib/scripts/services/Synchronize.js, core/web-assets/src/test/javascript/ng-requisitions/controllers/ForeignSource.test.js, core/web-assets/src/test/javascript/ng-requisitions/controllers/Node.test.js, core/web-assets/src/test/javascript/ng-requisitions/controllers/Requisition.test.js, core/web-assets/src/test/javascript/ng-requisitions/controllers/Requisitions.test.js, opennms-webapp/src/main/java/org/opennms/web/admin/users/AddNewUserServlet.java, opennms-webapp/src/main/java/org/opennms/web/admin/users/RenameUserServlet.java, opennms-webapp/src/main/java/org/opennms/web/controller/admin/group/GroupController.java, opennms-webapp/src/main/webapp/WEB-INF/jsp/admin/notification/noticeWizard/eventNotices.jsp, opennms-webapp/src/main/webapp/WEB-INF/jsp/admin/userGroupView/groups/groupDetail.jsp, opennms-webapp/src/main/webapp/WEB-INF/jsp/admin/userGroupView/groups/list.jsp, opennms-webapp/src/main/webapp/WEB-INF/jsp/admin/userGroupView/groups/modifyGroup.jsp, opennms-webapp/src/main/webapp/WEB-INF/jsp/admin/userGroupView/groups/newGroup.jsp, opennms-webapp/src/main/webapp/WEB-INF/jsp/alarm/detail.jsp, opennms-webapp/src/main/webapp/WEB-INF/jsp/event/detail.jsp, opennms-webapp/src/main/webapp/WEB-INF/jsp/outage/detail.jsp, opennms-webapp/src/main/webapp/WEB-INF/tags/element/nodelist.tag, opennms-webapp/src/main/webapp/admin/notification/noticeWizard/choosePath.jsp, opennms-webapp/src/main/webapp/admin/userGroupView/users/list.jsp, opennms-webapp/src/main/webapp/admin/userGroupView/users/modifyUser.jsp, opennms-webapp/src/main/webapp/admin/userGroupView/users/newUser.jsp, opennms-webapp/src/main/webapp/admin/userGroupView/users/userDetail.jsp, opennms-webapp/src/main/webapp/element/node.jsp, opennms-webapp/src/main/webapp/notification/detail.jsp, smoke-test/src/test/java/org/opennms/smoketest/NotificationsPageIT.java, smoke-test/src/test/java/org/opennms/smoketest/UserIT.java
Commit date: 2021-04-20 14:25:03 +0200
Commit message: NMS-13231: Backport Security Issues from Last Month


Diff: diff --git a/core/web-assets/src/main/assets/js/apps/onms-requisitions/lib/scripts/controllers/ForeignSource.js b/core/web-assets/src/main/assets/js/apps/onms-requisitions/lib/scripts/controllers/ForeignSource.js
index 2fdde6ab96ad..9a72a07667ff 100644
--- a/core/web-assets/src/main/assets/js/apps/onms-requisitions/lib/scripts/controllers/ForeignSource.js
+++ b/core/web-assets/src/main/assets/js/apps/onms-requisitions/lib/scripts/controllers/ForeignSource.js
@@ -452,11 +452,11 @@ require('../services/Requisitions');
     $scope.save = function() {
       var form = this.fsForm;
       RequisitionsService.startTiming();
       RequisitionsService.saveForeignSourceDefinition($scope.foreignSourceDef).then(
         function() { // success
-          growl.success('The definition for the requisition ' + $scope.foreignSource + ' has been saved.');
+          growl.success('The definition for the requisition ' + _.escape($scope.foreignSource) + ' has been saved.');
           form.$dirty = false;
         },
         $scope.errorHandler
       );
     };
@@ -472,11 +472,11 @@ require('../services/Requisitions');
       bootbox.confirm('Are you sure you want to reset the foreign source definition to the default ?', function(ok) {
         if (ok) {
           RequisitionsService.startTiming();
           RequisitionsService.deleteForeignSourceDefinition($scope.foreignSource).then(
             function() { // success
-              growl.success('The foreign source definition for ' + $scope.foreignSource + 'has been reseted.');
+              growl.success('The foreign source definition for ' + _.escape($scope.foreignSource) + 'has been reseted.');
               $scope.initialize();
             },
             $scope.errorHandler
           );
         }
@@ -515,11 +515,11 @@ require('../services/Requisitions');
     * @name ForeignSourceController:initialize
     * @ngdoc method
     * @methodOf ForeignSourceController
     */
     $scope.initialize = function() {
-      growl.success('Retrieving definition for requisition ' + $scope.foreignSource + '...');
+      growl.success('Retrieving definition for requisition ' + _.escape($scope.foreignSource) + '...');
       RequisitionsService.getForeignSourceDefinition($scope.foreignSource).then(
         function(foreignSourceDef) { // success
           $scope.foreignSourceDef = foreignSourceDef;
           // Updating pagination variables for detectors.
           $scope.filteredDetectors = $scope.foreignSourceDef.detectors;
diff --git a/core/web-assets/src/main/assets/js/apps/onms-requisitions/lib/scripts/controllers/Node.js b/core/web-assets/src/main/assets/js/apps/onms-requisitions/lib/scripts/controllers/Node.js
index 5be105157152..40fc6ba7d9fb 100644
--- a/core/web-assets/src/main/assets/js/apps/onms-requisitions/lib/scripts/controllers/Node.js
+++ b/core/web-assets/src/main/assets/js/apps/onms-requisitions/lib/scripts/controllers/Node.js
@@ -474,11 +474,11 @@ const RequisitionMetaDataEntry = require('../model/RequisitionMetaDataEntry');
     $scope.save = function() {
       var form = this.nodeForm;
       RequisitionsService.startTiming();
       RequisitionsService.saveNode($scope.node).then(
         function() { // success
-          growl.success('The node ' + $scope.node.nodeLabel + ' has been saved.');
+          growl.success('The node ' + _.escape($scope.node.nodeLabel) + ' has been saved.');
           $scope.foreignId = $scope.node.foreignId;
           form.$dirty = false;
         },
         $scope.errorHandler
       );
@@ -490,11 +490,11 @@ const RequisitionMetaDataEntry = require('../model/RequisitionMetaDataEntry');
     * @name NodeController:refresh
     * @ngdoc method
     * @methodOf NodeController
     */
     $scope.refresh = function() {
-      growl.success('Retrieving node ' + $scope.foreignId + ' from requisition ' + $scope.foreignSource + '...');
+      growl.success('Retrieving node ' + _.escape($scope.foreignId) + ' from requisition ' + _.escape($scope.foreignSource) + '...');
       RequisitionsService.getNode($scope.foreignSource, $scope.foreignId).then(
         function(node) { // success
           $scope.node = node;
         },
         $scope.errorHandler
diff --git a/core/web-assets/src/main/assets/js/apps/onms-requisitions/lib/scripts/controllers/QuickAddNode.js b/core/web-assets/src/main/assets/js/apps/onms-requisitions/lib/scripts/controllers/QuickAddNode.js
index 4cada09ae0a6..18f7fa069487 100644
--- a/core/web-assets/src/main/assets/js/apps/onms-requisitions/lib/scripts/controllers/QuickAddNode.js
+++ b/core/web-assets/src/main/assets/js/apps/onms-requisitions/lib/scripts/controllers/QuickAddNode.js
@@ -109,12 +109,12 @@ const QuickNode = require('../model/QuickNode');
     * @ngdoc method
     * @methodOf QuickAddNodeController
     */
     $scope.provision = function() {
       $scope.isSaving = true;
-      growl.info($sanitize('The node ' + $scope.node.nodeLabel + ' is being added to requisition ' + $scope.node.foreignSource + '. Please wait...'));
-      var successMessage = $sanitize('The node ' + $scope.node.nodeLabel + ' has been added to requisition ' + $scope.node.foreignSource);
+      growl.info('The node ' + _.escape($scope.node.nodeLabel) + ' is being added to requisition ' + _.escape($scope.node.foreignSource) + '. Please wait...');
+      var successMessage = 'The node ' + _.escape($scope.node.nodeLabel) + ' has been added to requisition ' + _.escape($scope.node.foreignSource);
       RequisitionsService.quickAddNode($scope.node).then(
         function() { // success
           $scope.reset();
           bootbox.dialog({
             message: successMessage,
@@ -236,11 +236,11 @@ const QuickNode = require('../model/QuickNode');
         if (foreignSource) {
           RequisitionsService.addRequisition(foreignSource).then(
             function() { // success
               RequisitionsService.synchronizeRequisition(foreignSource, false).then(
                 function() {
-                  growl.success('The requisition ' + foreignSource + ' has been created and synchronized.');
+                  growl.success('The requisition ' + _.escape(foreignSource) + ' has been created and synchronized.');
                   $scope.foreignSources.push(foreignSource);
                 },
                 $scope.errorHandler
               );
             },
diff --git a/core/web-assets/src/main/assets/js/apps/onms-requisitions/lib/scripts/controllers/Requisition.js b/core/web-assets/src/main/assets/js/apps/onms-requisitions/lib/scripts/controllers/Requisition.js
index d7c84193a325..cc5aa1a662ac 100644
--- a/core/web-assets/src/main/assets/js/apps/onms-requisitions/lib/scripts/controllers/Requisition.js
+++ b/core/web-assets/src/main/assets/js/apps/onms-requisitions/lib/scripts/controllers/Requisition.js
@@ -198,11 +198,11 @@ require('../services/Synchronize');
     * @ngdoc method
     * @methodOf RequisitionController
     * @param {object} The node's object to delete
     */
     $scope.deleteNode = function(node) {
-      bootbox.confirm('Are you sure you want to remove the node ' + node.nodeLabel + '?', function(ok) {
+      bootbox.confirm('Are you sure you want to remove the node ' + _.escape(node.nodeLabel) + '?', function(ok) {
         if (ok) {
           RequisitionsService.startTiming();
           RequisitionsService.deleteNode(node).then(
             function() { // success
               var index = -1;
@@ -212,11 +212,11 @@ require('../services/Synchronize');
                 }
               }
               if (index > -1) {
                 $scope.filteredNodes.splice(index,1);
               }
-              growl.success('The node ' + node.nodeLabel + ' has been deleted.');
+              growl.success('The node ' + _.escape(node.nodeLabel) + ' has been deleted.');
             },
             $scope.errorHandler
           );
         }
       });
@@ -293,11 +293,11 @@ require('../services/Synchronize');
     $scope.initialize = function(customHandler) {
       var value = $cookies.get('requisitions_page_size');
       if (value) {
         $scope.pageSize = value;
       }
-      growl.success('Retrieving requisition ' + $scope.foreignSource + '...');
+      growl.success('Retrieving requisition ' + _.escape($scope.foreignSource) + '...');
       RequisitionsService.getRequisition($scope.foreignSource).then(
         function(requisition) { // success
           $scope.requisition = requisition;
           $scope.filteredNodes = requisition.nodes;
           $scope.updateFilteredNodes();
diff --git a/core/web-assets/src/main/assets/js/apps/onms-requisitions/lib/scripts/controllers/Requisitions.js b/core/web-assets/src/main/assets/js/apps/onms-requisitions/lib/scripts/controllers/Requisitions.js
index eb5cd1cf6186..47673f96a0c1 100644
--- a/core/web-assets/src/main/assets/js/apps/onms-requisitions/lib/scripts/controllers/Requisitions.js
+++ b/core/web-assets/src/main/assets/js/apps/onms-requisitions/lib/scripts/controllers/Requisitions.js
@@ -170,18 +170,18 @@ require('../services/Synchronize');
           foreignSource: function() { return foreignSource; },
           availableForeignSources: function() { return availableForeignSources; }
         }
       });
       modalInstance.result.then(function(targetForeignSource) {
-        bootbox.confirm('This action will override the existing foreign source definition for the requisition named ' + targetForeignSource + ', using ' + foreignSource + ' as a template. Are you sure you want to continue ? This cannot be undone.', function(ok) {
+        bootbox.confirm('This action will override the existing foreign source definition for the requisition named ' + _.escape(targetForeignSource) + ', using ' + _.escape(foreignSource) + ' as a template. Are you sure you want to continue ? This cannot be undone.', function(ok) {
           if (!ok) {
             return;
           }
           RequisitionsService.startTiming();
           RequisitionsService.cloneForeignSourceDefinition(foreignSource, targetForeignSource).then(
             function() { // success
-              growl.success('The foreign source definition for ' + foreignSource + ' has been cloned to ' + targetForeignSource);
+              growl.success('The foreign source definition for ' + _.escape(foreignSource) + ' has been cloned to ' + _.escape(targetForeignSource));
             },
             $scope.errorHandler
           );
         });
       });
@@ -199,22 +199,22 @@ require('../services/Synchronize');
     $scope.add = function() {
       bootbox.prompt('Please enter the name for the new requisition', function(foreignSource) {
         if (foreignSource) {
           // Validate Requisition
           if (foreignSource.match(/[/\\?:&*'"]/)) {
-            bootbox.alert('Cannot add the requisition ' + foreignSource + ' because the following characters are invalid:<br/>:, /, \\, ?, &, *, \', "');
+            bootbox.alert('Cannot add the requisition ' + _.escape(foreignSource) + ' because the following characters are invalid:<br/>:, /, \\, ?, &, *, \', "');
             return;
           }
           var r = $scope.requisitionsData.getRequisition(foreignSource);
           if (r) {
-            bootbox.alert('Cannot add the requisition ' + foreignSource+ ' because there is already a requisition with that name');
+            bootbox.alert('Cannot add the requisition ' + _.escape(foreignSource) + ' because there is already a requisition with that name');
             return;
           }
           // Create Requisition
           RequisitionsService.addRequisition(foreignSource).then(
             function(r) { // success
-              growl.success('The requisition ' + r.foreignSource + ' has been created.');
+              growl.success('The requisition ' + _.escape(r.foreignSource) + ' has been created.');
             },
             $scope.errorHandler
           );
         }
       });
@@ -269,11 +269,11 @@ require('../services/Synchronize');
     */
     $scope.refresh = function(requisition) {
       RequisitionsService.startTiming();
       RequisitionsService.updateDeployedStatsForRequisition(requisition).then(
         function() { // success
-          growl.success('The deployed statistics for ' + requisition.foreignSource + ' has been updated.');
+          growl.success('The deployed statistics for ' + _.escape(requisition.foreignSource) + ' has been updated.');
         },
         $scope.errorHandler
       );
     };
 
@@ -284,16 +284,16 @@ require('../services/Synchronize');
     * @ngdoc method
     * @methodOf RequisitionsController
     * @param {string} foreignSource The name of the requisition
     */
     $scope.removeAllNodes = function(foreignSource) {
-      bootbox.confirm('Are you sure you want to remove all the nodes from ' + foreignSource + '?', function(ok) {
+      bootbox.confirm('Are you sure you want to remove all the nodes from ' + _.escape(foreignSource) + '?', function(ok) {
         if (ok) {
           RequisitionsService.startTiming();
           RequisitionsService.removeAllNodesFromRequisition(foreignSource).then(
             function() { // success
-              growl.success('All the nodes from ' + foreignSource + ' have been removed, and the requisition has been synchronized.');
+              growl.success('All the nodes from ' + _.escape(foreignSource) + ' have been removed, and the requisition has been synchronized.');
               var req = $scope.requisitionsData.getRequisition(foreignSource);
               req.reset();
             },
             $scope.errorHandler
           );
@@ -308,16 +308,16 @@ require('../services/Synchronize');
     * @ngdoc method
     * @methodOf RequisitionsController
     * @param {string} foreignSource The name of the requisition
     */
     $scope.delete = function(foreignSource) {
-      bootbox.confirm('Are you sure you want to remove the requisition ' + foreignSource + '?', function(ok) {
+      bootbox.confirm('Are you sure you want to remove the requisition ' + _.escape(foreignSource) + '?', function(ok) {
         if (ok) {
           RequisitionsService.startTiming();
           RequisitionsService.deleteRequisition(foreignSource).then(
             function() { // success
-              growl.success('The requisition ' + foreignSource + ' has been deleted.');
+              growl.success('The requisition ' + _.escape(foreignSource) + ' has been deleted.');
             },
             $scope.errorHandler
           );
         }
       });
diff --git a/core/web-assets/src/main/assets/js/apps/onms-requisitions/lib/scripts/services/Synchronize.js b/core/web-assets/src/main/assets/js/apps/onms-requisitions/lib/scripts/services/Synchronize.js
index 04cc54e88cb1..af27a5f51a1a 100644
--- a/core/web-assets/src/main/assets/js/apps/onms-requisitions/lib/scripts/services/Synchronize.js
+++ b/core/web-assets/src/main/assets/js/apps/onms-requisitions/lib/scripts/services/Synchronize.js
@@ -44,11 +44,11 @@ require('./Requisitions');
         */
         var doSynchronize = function(requisition, rescanExisting) {
           RequisitionsService.startTiming();
           RequisitionsService.synchronizeRequisition(requisition.foreignSource, rescanExisting).then(
             function() { // success
-              growl.success('The import operation has been started for ' + requisition.foreignSource + ' (rescanExisting? ' + rescanExisting + ')<br/>Use <b>refresh</b> to update the deployed statistics');
+              growl.success('The import operation has been started for ' + _.escape(requisition.foreignSource) + ' (rescanExisting? ' + rescanExisting + ')<br/>Use <b>refresh</b> to update the deployed statistics');
               requisition.setDeployed(true);
             },
             errorHandler
           );
         };
@@ -56,11 +56,11 @@ require('./Requisitions');
           message: 'Do you want to rescan existing nodes ?<br/><hr/>' +
                    'Choose <b>yes</b> to synchronize all the nodes with the database executing the scan phase.<br/>' +
                    'Choose <b>no</b> to synchronize only the new and deleted nodes with the database executing the scan phase only for new nodes.<br/>' +
                    'Choose <b>dbonly</b> to synchronize all the nodes with the database skipping the scan phase.<br/>' +
                    'Choose <b>cancel</b> to abort the request.',
-          title: 'Synchronize Requisition ' + requisition.foreignSource,
+          title: 'Synchronize Requisition ' + _.escape(requisition.foreignSource),
           buttons: {
             fullSync: {
               label: 'Yes',
               className: 'btn-primary',
               callback: function() {
diff --git a/core/web-assets/src/test/javascript/ng-requisitions/controllers/ForeignSource.test.js b/core/web-assets/src/test/javascript/ng-requisitions/controllers/ForeignSource.test.js
index bc699d0da35e..25e6c72982cc 100644
--- a/core/web-assets/src/test/javascript/ng-requisitions/controllers/ForeignSource.test.js
+++ b/core/web-assets/src/test/javascript/ng-requisitions/controllers/ForeignSource.test.js
@@ -4,10 +4,11 @@
 */
 
 'use strict';
 
 const angular = require('angular-js');
+const _ = require('underscore-js');
 require('angular-mocks');
 require('../../../../../src/main/assets/js/apps/onms-requisitions/requisitions');
 
 var controllerFactory, scope, $q, mockModal = {}, mockGrowl = {}, mockRequisitionsService = {}, foreignSource = 'test-requisition';
 
diff --git a/core/web-assets/src/test/javascript/ng-requisitions/controllers/Node.test.js b/core/web-assets/src/test/javascript/ng-requisitions/controllers/Node.test.js
index 0a8f41b491c6..78251bf2a11e 100644
--- a/core/web-assets/src/test/javascript/ng-requisitions/controllers/Node.test.js
+++ b/core/web-assets/src/test/javascript/ng-requisitions/controllers/Node.test.js
@@ -6,10 +6,11 @@
 */
 
 'use strict';
 
 const angular = require('angular-js');
+const _ = require('underscore-js');
 require('angular-mocks');
 require('../../../../../src/main/assets/js/apps/onms-requisitions/requisitions');
 
 const RequisitionNode = require('../../../../../src/main/assets/js/apps/onms-requisitions/lib/scripts/model/RequisitionNode');
 
diff --git a/core/web-assets/src/test/javascript/ng-requisitions/controllers/Requisition.test.js b/core/web-assets/src/test/javascript/ng-requisitions/controllers/Requisition.test.js
index 11a2b85eadda..bad0e61d6e3f 100644
--- a/core/web-assets/src/test/javascript/ng-requisitions/controllers/Requisition.test.js
+++ b/core/web-assets/src/test/javascript/ng-requisitions/controllers/Requisition.test.js
@@ -6,10 +6,11 @@
 */
 
 'use strict';
 
 const angular = require('angular-js');
+const _ = require('underscore-js');
 require('angular-mocks');
 require('../../../../../src/main/assets/js/apps/onms-requisitions/requisitions');
 
 const Requisition = require('../../../../../src/main/assets/js/apps/onms-requisitions/lib/scripts/model/Requisition');
 
diff --git a/core/web-assets/src/test/javascript/ng-requisitions/controllers/Requisitions.test.js b/core/web-assets/src/test/javascript/ng-requisitions/controllers/Requisitions.test.js
index 8f9651c7b221..9ba817ac284b 100644
--- a/core/web-assets/src/test/javascript/ng-requisitions/controllers/Requisitions.test.js
+++ b/core/web-assets/src/test/javascript/ng-requisitions/controllers/Requisitions.test.js
@@ -6,10 +6,11 @@
 */
 
 'use strict';
 
 const angular = require('angular-js');
+const _ = require('underscore-js');
 require('angular-mocks');
 require('../../../../../src/main/assets/js/apps/onms-requisitions/requisitions');
 
 const OnmsDateFormatter = require('../../../../../src/main/assets/js/apps/onms-date-formatter');
 const RequisitionsData = require('../../../../../src/main/assets/js/apps/onms-requisitions/lib/scripts/model/RequisitionsData');
diff --git a/opennms-webapp/src/main/java/org/opennms/web/admin/users/AddNewUserServlet.java b/opennms-webapp/src/main/java/org/opennms/web/admin/users/AddNewUserServlet.java
index 4867e09a3fcb..9d546038fcee 100644
--- a/opennms-webapp/src/main/java/org/opennms/web/admin/users/AddNewUserServlet.java
+++ b/opennms-webapp/src/main/java/org/opennms/web/admin/users/AddNewUserServlet.java
@@ -67,10 +67,15 @@ public class AddNewUserServlet extends HttpServlet {
             throw new ServletException("AddNewUserServlet: Error initialising user factory." + e);
         }
         UserManager userFactory = UserFactory.getInstance();
 
         String userID = request.getParameter("userID");
+
+        if (userID != null && userID.matches(".*[&<>\"`']+.*")) {
+            throw new ServletException("User ID must not contain any HTML markup.");
+        }
+
         String password = request.getParameter("pass1");
 
         boolean hasUser = false;
         try {
             hasUser = userFactory.hasUser(userID);
diff --git a/opennms-webapp/src/main/java/org/opennms/web/admin/users/RenameUserServlet.java b/opennms-webapp/src/main/java/org/opennms/web/admin/users/RenameUserServlet.java
index bf4e5efb59ac..22860222f339 100644
--- a/opennms-webapp/src/main/java/org/opennms/web/admin/users/RenameUserServlet.java
+++ b/opennms-webapp/src/main/java/org/opennms/web/admin/users/RenameUserServlet.java
@@ -58,10 +58,14 @@ public class RenameUserServlet extends HttpServlet {
     @Override
     public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
         String userID = request.getParameter("userID");
         String newID = request.getParameter("newID");
 
+        if (newID != null && newID.matches(".*[&<>\"`']+.*")) {
+            throw new ServletException("User ID must not contain any HTML markup.");
+        }
+
         // now save to the xml file
         try {
             UserManager userFactory = UserFactory.getInstance();
             userFactory.renameUser(userID, newID);
         } catch (Throwable e) {
diff --git a/opennms-webapp/src/main/java/org/opennms/web/controller/admin/group/GroupController.java b/opennms-webapp/src/main/java/org/opennms/web/controller/admin/group/GroupController.java
index fa7cceac5579..f589d026c2ef 100644
--- a/opennms-webapp/src/main/java/org/opennms/web/controller/admin/group/GroupController.java
+++ b/opennms-webapp/src/main/java/org/opennms/web/controller/admin/group/GroupController.java
@@ -149,11 +149,15 @@ public class GroupController extends AbstractController implements InitializingB
 
     private ModelAndView renameGroup(HttpServletRequest request, HttpServletResponse response) throws Exception {
         
         String oldName = request.getParameter("groupName");
         String newName = request.getParameter("newName");
-        
+
+        if (newName != null && newName.matches(".*[&<>\"`']+.*")) {
+            throw new ServletException("Group ID must not contain any HTML markup.");
+        }
+
         if (StringUtils.hasText(oldName) && StringUtils.hasText(newName)) {
             m_groupRepository.renameGroup(oldName, newName);
         }
         
         return listGroups(request, response);
@@ -310,10 +314,18 @@ public class GroupController extends AbstractController implements InitializingB
         String groupComment = request.getParameter("groupComment");
         if (groupComment == null) {
             groupComment = "";
         }
 
+        if (groupName != null && groupName.matches(".*[&<>\"`']+.*")) {
+            throw new ServletException("Group ID must not contain any HTML markup.");
+        }
+
+        if (groupComment != null && groupComment.matches(".*[&<>\"`']+.*")) {
+            throw new ServletException("Group comment must not contain any HTML markup.");
+        }
+
         boolean hasGroup = false;
         try {
             hasGroup = m_groupRepository.groupExists(groupName);
         } catch (Throwable e) {
             throw new ServletException("Can't determine if group " + groupName + " already exists in groups.xml.", e);
diff --git a/opennms-webapp/src/main/webapp/WEB-INF/jsp/admin/notification/noticeWizard/eventNotices.jsp b/opennms-webapp/src/main/webapp/WEB-INF/jsp/admin/notification/noticeWizard/eventNotices.jsp
index fdcdcc338698..472d1a1b2390 100644
--- a/opennms-webapp/src/main/webapp/WEB-INF/jsp/admin/notification/noticeWizard/eventNotices.jsp
+++ b/opennms-webapp/src/main/webapp/WEB-INF/jsp/admin/notification/noticeWizard/eventNotices.jsp
@@ -34,10 +34,11 @@
 	session="true"
 	import="org.opennms.web.admin.notification.noticeWizard.*"
 %>
 <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
 <%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn"%>
+<%@ taglib uri="https://www.owasp.org/index.php/OWASP_Java_Encoder_Project" prefix="e"%>
 
 <jsp:include page="/includes/bootstrap.jsp" flush="false" >
   <jsp:param name="title" value="Event Notifications" />
   <jsp:param name="headTitle" value="Event Notifications" />
   <jsp:param name="headTitle" value="Admin" />
@@ -107,35 +108,35 @@
             </th>
           </tr>
           <c:forEach items="${notifications}" var="notification">
           <tr>
             <td>
-              <input type="button" class="btn btn-secondary" value="Edit" onclick="javascript:editNotice('${notification.escapedName}')"/>
+              <input type="button" class="btn btn-secondary" value="Edit" onclick="javascript:editNotice('${e:forJavaScript(notification.escapedName)}')"/>
             </td>
             <td>
-              <input type="button" class="btn btn-secondary" value="Delete"  onclick="javascript:deleteNotice('${notification.escapedName}')"/>
+              <input type="button" class="btn btn-secondary" value="Delete"  onclick="javascript:deleteNotice('${e:forJavaScript(notification.escapedName)}')"/>
             </td>
             <td>
             <c:choose>
               <c:when test="${notification.isOn}">
-                <input type="radio" value="Off" onclick="javascript:setStatus('${notification.escapedName}','off')"/>Off
-                <input type="radio" value="On" CHECKED onclick="javascript:setStatus('${notification.escapedName}','on')"/>On
+                <input type="radio" value="Off" onclick="javascript:setStatus('${e:forJavaScript(notification.escapedName)}','off')"/>Off
+                <input type="radio" value="On" CHECKED onclick="javascript:setStatus('${e:forJavaScript(notification.escapedName)}','on')"/>On
               </c:when>
               <c:otherwise>
-                <input type="radio" value="Off" CHECKED onclick="javascript:setStatus('${notification.escapedName}','off')"/>Off
-                <input type="radio" value="On" onclick="javascript:setStatus('${notification.escapedName}','on')"/>On
+                <input type="radio" value="Off" CHECKED onclick="javascript:setStatus('${e:forJavaScript(notification.escapedName)}','off')"/>Off
+                <input type="radio" value="On" onclick="javascript:setStatus('${e:forJavaScript(notification.escapedName)}','on')"/>On
               </c:otherwise>
             </c:choose>
             </td>
             <td>
-              ${notification.name}
+            ${fn:escapeXml(notification.name)}
             </td>
             <td>
-              ${notification.eventLabel}
+              ${fn:escapeXml(notification.eventLabel)}
             </td>
             <td>
-              ${notification.displayUei}
+              ${fn:escapeXml(notification.displayUei)}
             </td>
           </tr>
           </c:forEach>
         </table>
   </div>
diff --git a/opennms-webapp/src/main/webapp/WEB-INF/jsp/admin/userGroupView/groups/groupDetail.jsp b/opennms-webapp/src/main/webapp/WEB-INF/jsp/admin/userGroupView/groups/groupDetail.jsp
index 623c9bc5c76b..de4c0c8efe8c 100644
--- a/opennms-webapp/src/main/webapp/WEB-INF/jsp/admin/userGroupView/groups/groupDetail.jsp
+++ b/opennms-webapp/src/main/webapp/WEB-INF/jsp/admin/userGroupView/groups/groupDetail.jsp
@@ -38,10 +38,11 @@
 		org.opennms.netmgt.config.users.DutySchedule
 	"
 %>
 
 <%@page import="org.opennms.web.group.WebGroup"%>
+<%@ page import="org.opennms.core.utils.WebSecurityUtils" %>
 
 <%
     WebGroup group = (WebGroup)request.getAttribute("group");
 %>
 
@@ -58,17 +59,17 @@
 
 <div class="row">
   <div class="col-md-6">
     <div class="card">
       <div class="card-header">
-        <span>Details for Group: <%=group.getName()%></span>
+        <span>Details for Group: <%=WebSecurityUtils.sanitizeString(group.getName())%></span>
       </div>
       <table class="table table-sm">
         <tr>
           <th>Comments:</th>
           <td width="75%">
-            <%=group.getComments()%>
+            <%=WebSecurityUtils.sanitizeString(group.getComments())%>
           </td>
         </tr>
         <tr>
           <th>Assigned Users:</th>
           <td width="75%">
@@ -77,11 +78,11 @@
             { %>
               No users belong to this group.
             <% } else { %>
               <ul class="list-unstyled">
               <% for (String user : users) { %>
-               <li> <%=user%> </li>
+               <li> <%=WebSecurityUtils.sanitizeString(user)%> </li>
               <% } %>
               </ul>
             <% } %>
           </td>
         </tr>
diff --git a/opennms-webapp/src/main/webapp/WEB-INF/jsp/admin/userGroupView/groups/list.jsp b/opennms-webapp/src/main/webapp/WEB-INF/jsp/admin/userGroupView/groups/list.jsp
index f82e15920e30..03c093acaa8c 100644
--- a/opennms-webapp/src/main/webapp/WEB-INF/jsp/admin/userGroupView/groups/list.jsp
+++ b/opennms-webapp/src/main/webapp/WEB-INF/jsp/admin/userGroupView/groups/list.jsp
@@ -28,10 +28,11 @@
  *******************************************************************************/
 
 --%>
 
 <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
+<%@taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %>
 
 <jsp:include page="/includes/bootstrap.jsp" flush="false" >
   <jsp:param name="title" value="Group Configuration" />
   <jsp:param name="headTitle" value="List" />
   <jsp:param name="headTitle" value="Groups" />
@@ -75,12 +76,15 @@
 
     function renameGroup(groupName)
     {
         var newName = prompt("Enter new name for group.", groupName);
 
-        if (newName != null && newName != "")
-        {
+        if (newName != null && newName != "") {
+          if (/.*[&<>"`']+.*/.test(newName)) {
+            alert("The group ID must not contain any HTML markup.");
+            return;
+          }
           document.allGroups.newName.value = newName;
           document.allGroups.groupName.value=groupName;
           document.allGroups.operation.value="rename";
           document.allGroups.action="admin/userGroupView/groups/modifyGroup";
           document.allGroups.submit();
@@ -111,41 +115,41 @@
           <th>Rename</th>
           <th>Group Name</th>
           <th>Comments</th>
         </tr>
          <c:forEach var="group" varStatus="groupStatus" items="${groups}">
-         <tr class="divider ${groupStatus.index % 2 == 0 ?  'even' : 'odd'}" id="group-${group.name}">
+         <tr class="divider ${groupStatus.index % 2 == 0 ?  'even' : 'odd'}" id="group-${fn:escapeXml(group.name)}">
           <td width="5%" class="text-center">
             <c:choose>
-              <c:when test='${group.name != "Admin"}'>
-                <a id="${group.name}.doDelete" href="javascript:deleteGroup('${group.name}')" onclick="return confirm('Are you sure you want to delete the group ${group.name}?')"><i class="fa fa-trash-o fa-2x"></i></a>              
+              <c:when test='${fn:escapeXml(group.name) != "Admin"}'>
+                <a id="${group.name}.doDelete" href="javascript:deleteGroup('${fn:escapeXml(group.name)}')" onclick="return confirm('Are you sure you want to delete the group ${fn:escapeXml(group.name)}?')"><i class="fa fa-trash-o fa-2x"></i></a>
               </c:when>
               <c:otherwise>
-                <i class="fa fa-trash-o fa-2x" onclick="alert('Sorry, the ${group.name} group cannot be deleted.')"></i>
+                <i class="fa fa-trash-o fa-2x" onclick="alert('Sorry, the ${fn:escapeXml(group.name)} group cannot be deleted.')"></i>
               </c:otherwise>
             </c:choose>
           </td>
           <td width="5%" class="text-center">
-            <a id="${group.name}.doModify" href="javascript:modifyGroup('${group.name}')"><i class="fa fa-edit fa-2x"></i></a>
+            <a id="${fn:escapeXml(group.name)}.doModify" href="javascript:modifyGroup('${fn:escapeXml(group.name)}')"><i class="fa fa-edit fa-2x"></i></a>
           </td>
           <td width="5%" class="text-center">
             <c:choose>
               <c:when test='${group.name != "Admin"}'>
-                <button id="${group.name}.doRename" type="button" class="btn btn-secondary" name="rename" onclick="renameGroup('${group.name}')">Rename</button>
+                <button id="${fn:escapeXml(group.name)}.doRename" type="button" class="btn btn-secondary" name="rename" onclick="renameGroup('${fn:escapeXml(group.name)}')">Rename</button>
               </c:when>
               <c:otherwise>
-                <button id="${group.name}.doRename" type="button" class="btn btn-secondary" name="rename" onclick="alert('Sorry, the Admin group cannot be renamed.')">Rename</button>
+                <button id="${fn:escapeXml(group.name)}.doRename" type="button" class="btn btn-secondary" name="rename" onclick="alert('Sorry, the Admin group cannot be renamed.')">Rename</button>
               </c:otherwise>
             </c:choose>
           </td>
           <td>
-            <a href="javascript:detailGroup('${group.name}')">${group.name}</a>
+            <a href="javascript:detailGroup('${fn:escapeXml(group.name)}')">${fn:escapeXml(group.name)}</a>
           </td>
             <td>
               <c:choose>
                 <c:when test="${group.comments.isPresent()}">
-                  ${group.comments.get()}
+                  ${fn:escapeXml(group.comments.get())}
                 </c:when>
                 
                 <c:otherwise>
                   No Comments
                 </c:otherwise>
diff --git a/opennms-webapp/src/main/webapp/WEB-INF/jsp/admin/userGroupView/groups/modifyGroup.jsp b/opennms-webapp/src/main/webapp/WEB-INF/jsp/admin/userGroupView/groups/modifyGroup.jsp
index c70e61e3487f..b4fc9a574cd4 100644
--- a/opennms-webapp/src/main/webapp/WEB-INF/jsp/admin/userGroupView/groups/modifyGroup.jsp
+++ b/opennms-webapp/src/main/webapp/WEB-INF/jsp/admin/userGroupView/groups/modifyGroup.jsp
@@ -37,10 +37,11 @@
 		java.text.*,
 		org.opennms.netmgt.config.users.*
 	"
 %>
 <%@page import="org.opennms.web.group.WebGroup"%>
+<%@ page import="org.opennms.core.utils.WebSecurityUtils" %>
 
 <%
   	WebGroup group = (WebGroup)session.getAttribute("group.modifyGroup.jsp");
     if (group == null) {
         throw new ServletException("Could not get session attribute group");
@@ -461,11 +462,11 @@
 
 <%!
     private String createSelectList(String name, String[] categories) {
         StringBuffer buffer = new StringBuffer("<select class=\"form-control custom-select\" multiple=\"multiple\" name=\""+name+"\" size=\"10\">");
         for(String category : categories){
-            buffer.append("<option>" + category + "</option>");
+            buffer.append("<option>" + WebSecurityUtils.sanitizeString(category) + "</option>");
         }
         buffer.append("</select>");
         
         return buffer.toString();
     }
diff --git a/opennms-webapp/src/main/webapp/WEB-INF/jsp/admin/userGroupView/groups/newGroup.jsp b/opennms-webapp/src/main/webapp/WEB-INF/jsp/admin/userGroupView/groups/newGroup.jsp
index 7f6d06db02ca..e8c2a840cf36 100644
--- a/opennms-webapp/src/main/webapp/WEB-INF/jsp/admin/userGroupView/groups/newGroup.jsp
+++ b/opennms-webapp/src/main/webapp/WEB-INF/jsp/admin/userGroupView/groups/newGroup.jsp
@@ -41,19 +41,30 @@
 	<jsp:param name="breadcrumb" value="<a href='admin/userGroupView/groups/list.htm'>Group List</a>" />
 	<jsp:param name="breadcrumb" value="New Group" />
 </jsp:include>
 
 <script type="text/javascript">
-  function validateFormInput() 
+  function validateFormInput()
   {
     var id = new String(document.newGroupForm.groupName.value);
     if (id.toLowerCase()=="admin")
     {
         alert("The group ID '" + document.newGroupForm.groupName.value + "' cannot be used. It may be confused with the administration group ID 'Admin'.");
         return false;
     }
-    
+
+    if (/.*[&<>"`']+.*/.test(id)) {
+        alert("The group ID must not contain any HTML markup.");
+        return false;
+    }
+
+    var comment = new String(document.newGroupForm.groupComment.value);
+    if (/.*[&<>"`']+.*/.test(comment)) {
+        alert("The group comment must not contain any HTML markup.");
+        return false;
+    }
+
     document.newGroupForm.action="admin/userGroupView/groups/modifyGroup";
     document.newGroupForm.operation.value="addGroup";
     return true;
   }    
   function cancelGroup()
diff --git a/opennms-webapp/src/main/webapp/WEB-INF/jsp/alarm/detail.jsp b/opennms-webapp/src/main/webapp/WEB-INF/jsp/alarm/detail.jsp
index cb6d70aa2cfc..523863ed912d 100644
--- a/opennms-webapp/src/main/webapp/WEB-INF/jsp/alarm/detail.jsp
+++ b/opennms-webapp/src/main/webapp/WEB-INF/jsp/alarm/detail.jsp
@@ -447,11 +447,11 @@
         <th>Acknowledged&nbsp;Type</th>
         <th>Time&nbsp;Acknowledged</th>
     </tr>
     <% for (OnmsAcknowledgment ack : acks) {%>
     <tr class="severity-<%=alarm.getSeverity().getLabel().toLowerCase()%>">
-        <td><%=ack.getAckUser()%></td>
+        <td><%=WebSecurityUtils.sanitizeString(ack.getAckUser())%></td>
         <td><%=ack.getAckAction()%></td>
         <td><onms:datetime date="<%=ack.getAckTime()%>" /></td>
     </tr>
     <% }%>
 </table>
diff --git a/opennms-webapp/src/main/webapp/WEB-INF/jsp/event/detail.jsp b/opennms-webapp/src/main/webapp/WEB-INF/jsp/event/detail.jsp
index 79caff231d4f..726ad12e22ec 100644
--- a/opennms-webapp/src/main/webapp/WEB-INF/jsp/event/detail.jsp
+++ b/opennms-webapp/src/main/webapp/WEB-INF/jsp/event/detail.jsp
@@ -105,11 +105,11 @@
           <th class="col-1">Severity</th>
           <td class="col-3 bright"><%= event.getSeverity().getLabel() %></td>
           <th class="col-1">Node</th>
           <td ${acknowledgeEvent ? '' : 'colspan="3"'} class="${acknowledgeEvent ? 'col-3' : 'col-7'}">
             <% if( event.getNodeId() > 0 ) { %>
-              <a href="element/node.jsp?node=<%=event.getNodeId()%>"><%=event.getNodeLabel()%></a>
+              <a href="element/node.jsp?node=<%=event.getNodeId()%>"><%=WebSecurityUtils.sanitizeString(event.getNodeLabel())%></a>
             <% } else {%>
               &nbsp;
             <% } %>
           </td>
           <c:if test="${acknowledgeEvent}">
diff --git a/opennms-webapp/src/main/webapp/WEB-INF/jsp/outage/detail.jsp b/opennms-webapp/src/main/webapp/WEB-INF/jsp/outage/detail.jsp
index 76d73362cbd5..2c38e1287d8b 100644
--- a/opennms-webapp/src/main/webapp/WEB-INF/jsp/outage/detail.jsp
+++ b/opennms-webapp/src/main/webapp/WEB-INF/jsp/outage/detail.jsp
@@ -70,11 +70,11 @@
   <table class="table table-sm severity">
         <tr class="d-flex">
           <th class="col-2">Node</th>
           <td class="col-2">
             <% if( outage.getNodeId() > 0 ) { %>
-              <a href="element/node.jsp?node=<%=outage.getNodeId()%>"><%=outage.getNodeLabel()%></a>
+              <a href="element/node.jsp?node=<%=outage.getNodeId()%>"><%=WebSecurityUtils.sanitizeString(outage.getNodeLabel())%></a>
             <% } else {%>
               &nbsp;
             <% } %>
           </td>
           
diff --git a/opennms-webapp/src/main/webapp/WEB-INF/tags/element/nodelist.tag b/opennms-webapp/src/main/webapp/WEB-INF/tags/element/nodelist.tag
index 954904deee33..347e9a8d9c4b 100644
--- a/opennms-webapp/src/main/webapp/WEB-INF/tags/element/nodelist.tag
+++ b/opennms-webapp/src/main/webapp/WEB-INF/tags/element/nodelist.tag
@@ -1,20 +1,21 @@
 <%@ attribute name="nodes" type="java.util.List" rtexprvalue="true" required="true" %>
 <%@ attribute name="snmpParm" type="java.lang.String" rtexprvalue="true" required="true" %>
 <%@ attribute name="isMaclikeSearch"  type="java.lang.Boolean" rtexprvalue="true" required="true" %>
 
 <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
+<%@taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %>
 
 <ul class="list-unstyled mb-0">
   <c:forEach var="nodeModel" items="${nodes}">
     <c:url var="nodeLink" value="element/node.jsp">
       <c:param name="node" value="${nodeModel.node.id}"/>
     </c:url>
     <li>
       <c:choose>
         <c:when test="${!empty nodeModel.node.foreignSource}">
-          <div class="NLnode"><a href="${nodeLink}">${nodeModel.node.label}</a>&nbsp;&nbsp;<span class="NLdbid label label-default" title="Database ID: ${nodeModel.node.id}"><i class="fa fa-database"></i>&nbsp;${nodeModel.node.id}</span>&nbsp;<span class="NLfs label label-default" title="Requisition: ${nodeModel.node.foreignSource}"><i class="fa fa-list-alt"></i>&nbsp;${nodeModel.node.foreignSource}</span>&nbsp;<span class="NLfid label label-default" title="Foreign ID: ${nodeModel.node.foreignId}"><i class="fa fa-qrcode"></i>&nbsp;${nodeModel.node.foreignId}</span>&nbsp;<span class="NLloc label label-default" title="Location: ${nodeModel.node.location.locationName}"><i class="fa fa-map-marker"></i>&nbsp;${nodeModel.node.location.locationName}</span> <c:if test="${nodeModel.node.hasFlows}"><span class="NLflows label label-default" title="Flows: flow data available"><i class="fa fa-exchange"></i>&nbsp;flow data</span></c:if></div>
+          <div class="NLnode"><a href="${nodeLink}">${nodeModel.node.label}</a>&nbsp;&nbsp;<span class="NLdbid label label-default" title="Database ID: ${nodeModel.node.id}"><i class="fa fa-database"></i>&nbsp;${nodeModel.node.id}</span>&nbsp;<span class="NLfs label label-default" title="Requisition: ${fn:escapeXml(nodeModel.node.foreignSource)}"><i class="fa fa-list-alt"></i>&nbsp;${fn:escapeXml(nodeModel.node.foreignSource)}</span>&nbsp;<span class="NLfid label label-default" title="Foreign ID: ${nodeModel.node.foreignId}"><i class="fa fa-qrcode"></i>&nbsp;${nodeModel.node.foreignId}</span>&nbsp;<span class="NLloc label label-default" title="Location: ${nodeModel.node.location.locationName}"><i class="fa fa-map-marker"></i>&nbsp;${nodeModel.node.location.locationName}</span> <c:if test="${nodeModel.node.hasFlows}"><span class="NLflows label label-default" title="Flows: flow data available"><i class="fa fa-exchange"></i>&nbsp;flow data</span></c:if></div>
         </c:when>
         <c:otherwise>
           <div class="NLnode"><a href="${nodeLink}">${nodeModel.node.label}</a>&nbsp;&nbsp;<span class="NLdbid label label-default" title="Database ID: ${nodeModel.node.id}"><i class="fa fa-database"></i>&nbsp;${nodeModel.node.id}</span>&nbsp;<span class="NLloc label label-default" title="Location: ${nodeModel.node.location.locationName}"><i class="fa fa-map-marker"></i>&nbsp;${nodeModel.node.location.locationName}</span> <c:if test="${nodeModel.node.hasFlows}"><span class="NLflows label label-default" title="Flows: flow data available"><i class="fa fa-exchange"></i>&nbsp;flow data</span></c:if></div>
         </c:otherwise>
       </c:choose>
diff --git a/opennms-webapp/src/main/webapp/admin/notification/noticeWizard/choosePath.jsp b/opennms-webapp/src/main/webapp/admin/notification/noticeWizard/choosePath.jsp
index 4c575c664cb0..77068a986cbf 100644
--- a/opennms-webapp/src/main/webapp/admin/notification/noticeWizard/choosePath.jsp
+++ b/opennms-webapp/src/main/webapp/admin/notification/noticeWizard/choosePath.jsp
@@ -37,10 +37,14 @@
 		org.opennms.netmgt.config.notifications.*,
         org.opennms.netmgt.config.destinationPaths.*,
 		org.opennms.netmgt.config.*
 	"
 %>
+<%@ page import="org.opennms.core.utils.WebSecurityUtils" %>
+
+<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn"%>
+
 
 <%!
     public void init() throws ServletException {
         try {
             DestinationPathFactory.init();
@@ -114,11 +118,11 @@
         }
     }
   
 </script>
 
-<h2><%=(newNotice.getName()!=null ? "Editing notice: " + newNotice.getName() + "<br/>" : "")%></h2>
+<h2><%=(newNotice.getName()!=null ? "Editing notice: " + WebSecurityUtils.sanitizeString(newNotice.getName()) + "<br/>" : "")%></h2>
 
 <form method="post" name="info"
       action="admin/notification/noticeWizard/notificationWizard">
       <input type="hidden" name="userAction" value=""/>
       <input type="hidden" name="sourcePage" value="<%=NotificationWizardServlet.SOURCE_PAGE_PATH%>"/>
@@ -133,32 +137,32 @@
         <tr>
           <td width="10%" valign="top" align="left">
             <label>Name:</label>
           </td>
           <td valign="top" align="left">
-            <input type="text" class="form-control" name="name" value='<%=(newNotice.getName()!=null ? newNotice.getName() : "")%>'/>
+            <input type="text" class="form-control" name="name" value='<%=(newNotice.getName()!=null ? WebSecurityUtils.sanitizeString(newNotice.getName()) : "")%>'/>
           </td>
         </tr>
         <tr>
           <td width="10%" valign="top" align="left">
             <label>Description:</label>
           </td>
           <td valign="top" align="left">
-            <input type="text" class="form-control" name="description" value='<%=newNotice.getDescription().orElse("")%>'/>
+            <input type="text" class="form-control" name="description" value='<%=WebSecurityUtils.sanitizeString(newNotice.getDescription().orElse(""))%>'/>
           </td>
         </tr>
         <tr>
           <td width="10%" valign="top" align="left">
             <label>Parameter:</label>
           </td>
           <td valign="top" align="left">
             <div class="row">
               <div class="col-md-6">
-                <label>Name:</label> <input type="text" class="form-control" size="30" name="varbindName" value='<%=varbindName%>'/>
+                <label>Name:</label> <input type="text" class="form-control" size="30" name="varbindName" value='<%=WebSecurityUtils.sanitizeString(varbindName)%>'/>
               </div>
               <div class="col-md-6">
-                <label>Value:</label> <input class="form-control" type="text" size="30" name="varbindValue" value='<%=varbindValue%>'/>
+                <label>Value:</label> <input class="form-control" type="text" size="30" name="varbindValue" value='<%=WebSecurityUtils.sanitizeString(varbindValue)%>'/>
               </div>
             </div>
           </td>
         </tr>
         <tr>
@@ -172,27 +176,27 @@
          <tr>
           <td width="10%" valign="top" align="left">
             <label>Text Message:</label>
           </td>
           <td valign="top" align="left">
-            <textarea rows="3" class="form-control" name="textMsg"><%=(newNotice.getTextMessage()!=null ? newNotice.getTextMessage() : "")%></textarea>
+            <textarea rows="3" class="form-control" name="textMsg"><%=(newNotice.getTextMessage()!=null ? WebSecurityUtils.sanitizeString(newNotice.getTextMessage()) : "")%></textarea>
           </td>
          </tr>
          <tr>
           <td width="10%" valign="top" align="left">
             <label>Short Message:</label>
           </td>
           <td valign="top" align="left">
-            <textarea rows="1" class="form-control" name="numMsg"><%=newNotice.getNumericMessage().orElse("")%></textarea>
+            <textarea rows="1" class="form-control" name="numMsg"><%=WebSecurityUtils.sanitizeString(newNotice.getNumericMessage().orElse(""))%></textarea>
           </td>
          </tr>
          <tr>
           <td width="10%" valign="top" align="left">
             <label>Email Subject:</label>
           </td>
           <td valign="top" align="left">
-            <input type="text" class="form-control" name="subject" value='<%=newNotice.getSubject().orElse("")%>'/>
+            <input type="text" class="form-control" name="subject" value='<%=WebSecurityUtils.sanitizeString(newNotice.getSubject().orElse(""))%>'/>
           </td>
          </tr>
          <tr>
           <td width="10%" valign="top" align="left">
             <label>Special Values:</label>
diff --git a/opennms-webapp/src/main/webapp/admin/userGroupView/users/list.jsp b/opennms-webapp/src/main/webapp/admin/userGroupView/users/list.jsp
index 1250fa5c1839..c0d164c4e509 100644
--- a/opennms-webapp/src/main/webapp/admin/userGroupView/users/list.jsp
+++ b/opennms-webapp/src/main/webapp/admin/userGroupView/users/list.jsp
@@ -34,10 +34,14 @@
 	session="true"
 %>
 <%@page import="java.util.*" %>
 <%@page import="org.opennms.netmgt.config.*" %>
 <%@page import="org.opennms.netmgt.config.users.*" %>
+<%@ page import="org.opennms.core.utils.WebSecurityUtils" %>
+
+<%@taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %>
+
 <%
 	UserManager userFactory;
   	Map<String,User> users = null;
 	
 	try
@@ -93,13 +97,17 @@
     
     function renameUser(userID)
     {
         document.allUsers.userID.value=userID;
         var newID = prompt("Enter new name for user.", userID);
-        
-        if (newID != null && newID != "")
-        {
+
+        if (newID != null && newID != "") {
+          if (/.*[&<>"`']+.*/.test(newID)) {
+            alert("The user ID must not contain any HTML markup.");
+            return;
+          }
+
           document.allUsers.newID.value = newID;
           document.allUsers.action="admin/userGroupView/users/renameUser";
           document.allUsers.submit();
         }
     }
@@ -148,58 +156,59 @@
 	      String xmppAddress = userFactory.getXMPPAddress(userid);
 	      String numericService = userFactory.getNumericPage(userid);
 	      String textService = userFactory.getTextPage(userid);
 	      String numericPin = userFactory.getNumericPin(userid);
 	      String textPin = userFactory.getTextPin(userid);
+	      String sanitizedUserId = WebSecurityUtils.sanitizeString(curUser.getUserId());
          %>
          <tr id="user-<%= userid %>">
           <% if (!curUser.getUserId().equals("admin") && !curUser.getUserId().equals("rtc")) { %>
           <td rowspan="2" class="text-center"> 
-            <a id="<%= "users("+curUser.getUserId()+").doDelete" %>" href="javascript:deleteUser('<%=curUser.getUserId()%>')" onclick="return confirm('Are you sure you want to delete the user <%=curUser.getUserId()%>?')"><i class="fa fa-trash-o fa-2x"></i></a> 
+            <a id="<%= "users("+sanitizedUserId+").doDelete" %>" href="javascript:deleteUser('<%=sanitizedUserId%>')" onclick="return confirm('Are you sure you want to delete the user <%=sanitizedUserId%>?')"><i class="fa fa-trash-o fa-2x"></i></a>
           </td>
           <% } else { %>
           <td rowspan="2" class="text-center">
             <i class="fa fa-trash-o fa-2x" onclick="alert('Sorry, the admin user cannot be deleted.')"></i>
           </td>
           <% } %>
           <td rowspan="2" class="text-center">
-            <a id="<%= "users("+curUser.getUserId()+").doModify" %>" href="javascript:modifyUser('<%=curUser.getUserId()%>')"><i class="fa fa-edit fa-2x"></i></a>
+            <a id="<%= "users("+sanitizedUserId+").doModify" %>" href="javascript:modifyUser('<%=sanitizedUserId%>')"><i class="fa fa-edit fa-2x"></i></a>
           </td>
           <td rowspan="2" class="text-center">
             <% if ( !curUser.getUserId().equals("admin")) { %>
-                <button id="<%= "users("+curUser.getUserId()+").doRename" %>" class="btn btn-secondary"  name="rename" onclick="renameUser('<%=curUser.getUserId()%>')">Rename</button>
+                <button id="<%= "users("+sanitizedUserId+").doRename" %>" class="btn btn-secondary"  name="rename" onclick="renameUser('<%=sanitizedUserId%>')">Rename</button>
               <% } else { %>
-                <button id="<%= "users("+curUser.getUserId()+").doRename" %>" class="btn btn-secondary"  name="rename" onclick="alert('Sorry, the admin user cannot be renamed.')">Rename</button>
+                <button id="<%= "users("+sanitizedUserId+").doRename" %>" class="btn btn-secondary"  name="rename" onclick="alert('Sorry, the admin user cannot be renamed.')">Rename</button>
               <% } %>
           </td>
           <td>
-            <a id="<%= "users("+curUser.getUserId()+").doDetails" %>" href="javascript:detailUser('<%=curUser.getUserId()%>')"><%=curUser.getUserId()%></a>
+            <a id="<%= "users("+sanitizedUserId+").doDetails" %>" href="javascript:detailUser('<%=sanitizedUserId%>')"><%=sanitizedUserId%></a>
           </td>
           <td>
-           <div id="<%= "users("+curUser.getUserId()+").fullName" %>">
+           <div id="<%= "users("+sanitizedUserId+").fullName" %>">
 		    <%= (curUser.getFullName().orElse("")) %>
 	      </div>
           </td>
           <td>
-            <div id="<%= "users("+curUser.getUserId()+").email" %>">
+            <div id="<%= "users("+sanitizedUserId+").email" %>">
             <%= ((email == null || email.equals("")) ? "&nbsp;" : email) %>
             </div>
           </td>
           <td>
-           <div id="<%= "users("+curUser.getUserId()+").pagerEmail" %>">
+           <div id="<%= "users("+sanitizedUserId+").pagerEmail" %>">
             <%= ((pagerEmail == null || pagerEmail.equals("")) ? "&nbsp;" : pagerEmail) %>
             </div>
           </td>
           <td>
-           <div id="<%= "users("+curUser.getUserId()+").xmppAddress" %>">
+           <div id="<%= "users("+sanitizedUserId+").xmppAddress" %>">
             <%= ((xmppAddress == null || xmppAddress.equals("")) ? "&nbsp;" : xmppAddress) %>
            </div>
           </td>
           </tr>
           <tr>
             <td colspan="5">
-             <div id="<%= "users("+curUser.getUserId()+").userComments" %>">
+             <div id="<%= "users("+sanitizedUserId+").userComments" %>">
              <%= (curUser.getUserComments().orElse("No Comments")) %>
 	        </div>
             </td>
           </tr>
          <% row++;
diff --git a/opennms-webapp/src/main/webapp/admin/userGroupView/users/modifyUser.jsp b/opennms-webapp/src/main/webapp/admin/userGroupView/users/modifyUser.jsp
index 2c02c352a6a4..04814d2ab995 100644
--- a/opennms-webapp/src/main/webapp/admin/userGroupView/users/modifyUser.jsp
+++ b/opennms-webapp/src/main/webapp/admin/userGroupView/users/modifyUser.jsp
@@ -38,10 +38,11 @@
 <%@page import="java.time.ZoneId"%>
 <%@page import="org.opennms.netmgt.config.*"%>
 <%@page import="org.opennms.netmgt.config.users.*"%>
 <%@page import="org.opennms.web.api.Util"%>
 <%@page import="org.opennms.web.api.Authentication"%>
+<%@ page import="org.opennms.core.utils.WebSecurityUtils" %>
 <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
 
 <%!
     private String createSelectList(String name, List<String> elements) {
         StringBuffer buffer = new StringBuffer("<select class=\"form-control\" multiple=\"multiple\" name=\""+name+"\" size=\"10\">");
@@ -251,19 +252,19 @@
         document.modifyUser.submit();
     }
 </script>
 
 <form role="form" class="form-horizontal" id="modifyUser" method="post" name="modifyUser">
-<input id="userID" type="hidden" name="userID" value="<%=user.getUserId()%>"/>
+<input id="userID" type="hidden" name="userID" value="<%=WebSecurityUtils.sanitizeString(user.getUserId())%>"/>
 <input id="password" type="hidden" name="password"/>
 <input id="redirect" type="hidden" name="redirect"/>
 
 <div class="row">
   <div class="col-md-6">
     <div class="card">
       <div class="card-header">
-        <span>Modify User: <%=userid%></span>
+        <span>Modify User: <%=WebSecurityUtils.sanitizeString(userid)%></span>
       </div>
       <div class="card-body">
         <h3>User Password</h3>
           <div class="form-group">
             <button type="button" class="btn btn-secondary" onClick="resetPassword()">Reset Password</button>
diff --git a/opennms-webapp/src/main/webapp/admin/userGroupView/users/newUser.jsp b/opennms-webapp/src/main/webapp/admin/userGroupView/users/newUser.jsp
index b07ef034cc7e..ebca703b2890 100644
--- a/opennms-webapp/src/main/webapp/admin/userGroupView/users/newUser.jsp
+++ b/opennms-webapp/src/main/webapp/admin/userGroupView/users/newUser.jsp
@@ -49,11 +49,16 @@
     if (id.toLowerCase()=="admin")
     {
         alert("The user ID '" + document.newUserForm.userID.value + "' cannot be used. It may be confused with the administration user ID 'admin'.");
         return false;
     }
-    
+
+    if (/.*[&<>"`']+.*/.test(id)) {
+        alert("The user ID must not contain any HTML markup.");
+        return false;
+    }
+
     if (document.newUserForm.pass1.value == document.newUserForm.pass2.value) 
     {
       document.newUserForm.action="admin/userGroupView/users/addNewUser";
       return true;
     } 
diff --git a/opennms-webapp/src/main/webapp/admin/userGroupView/users/userDetail.jsp b/opennms-webapp/src/main/webapp/admin/userGroupView/users/userDetail.jsp
index a9412e05cd4c..7b0ce9c2f548 100644
--- a/opennms-webapp/src/main/webapp/admin/userGroupView/users/userDetail.jsp
+++ b/opennms-webapp/src/main/webapp/admin/userGroupView/users/userDetail.jsp
@@ -37,10 +37,12 @@
 		java.text.*,
 		org.opennms.netmgt.config.users.*,
 		org.opennms.web.servlet.MissingParameterException
 	"
 %>
+<%@ page import="org.opennms.core.utils.WebSecurityUtils" %>
+<%@ page import="java.util.stream.Collectors" %>
 
 <%
 	User user = null;
 	UserManager userFactory = UserFactory.getInstance();
   	String userID = request.getParameter("userID");
@@ -70,11 +72,11 @@
 
 <div class="row">
   <div class="col-md-6">
     <div class="card">
       <div class="card-header">
-        <span>Details for User: <%=user.getUserId()%></span>
+        <span>Details for User: <%=WebSecurityUtils.sanitizeString(user.getUserId())%></span>
       </div>
       <table class="table table-sm">
         <tr>
           <th>
             Full Name:
diff --git a/opennms-webapp/src/main/webapp/element/node.jsp b/opennms-webapp/src/main/webapp/element/node.jsp
index 8410f3174634..df4bb06566e1 100644
--- a/opennms-webapp/src/main/webapp/element/node.jsp
+++ b/opennms-webapp/src/main/webapp/element/node.jsp
@@ -308,11 +308,11 @@ function confirmAssetEdit() {
 }
 </script>
 
 <h5>
   <c:if test="${model.foreignSource != null}">
-    <div class="NPnode">Node: <strong>${model.label}</strong>&nbsp;&nbsp;&nbsp;<span class="NPdbid badge badge-secondary " title="Database ID: ${model.id}"><i class="fa fa-database"></i>&nbsp;${model.id}</span>&nbsp;<span class="NPfs badge badge-secondary " title="Requisition: ${model.foreignSource}"><i class="fa fa-list-alt"></i>&nbsp;${model.foreignSource}</span>&nbsp;<span class="NPfid badge badge-secondary " title="Foreign ID: ${model.foreignId}"><i class="fa fa-qrcode"></i>&nbsp;${model.foreignId}</span>&nbsp;<span class="NPloc badge badge-secondary " title="Location: ${model.location}"><i class="fa fa-map-marker"></i>&nbsp;${model.location}</span> <c:if test="${model.node.hasFlows}"><span class="NPflows badge badge-secondary " title="Flows: flow data available"><i class="fa fa-exchange"></i> flow data</span></c:if></div>
+    <div class="NPnode">Node: <strong>${model.label}</strong>&nbsp;&nbsp;&nbsp;<span class="NPdbid badge badge-secondary " title="Database ID: ${model.id}"><i class="fa fa-database"></i>&nbsp;${model.id}</span>&nbsp;<span class="NPfs badge badge-secondary " title="Requisition: ${fn:escapeXml(model.foreignSource)}"><i class="fa fa-list-alt"></i>&nbsp;${fn:escapeXml(model.foreignSource)}</span>&nbsp;<span class="NPfid badge badge-secondary " title="Foreign ID: ${model.foreignId}"><i class="fa fa-qrcode"></i>&nbsp;${model.foreignId}</span>&nbsp;<span class="NPloc badge badge-secondary " title="Location: ${model.location}"><i class="fa fa-map-marker"></i>&nbsp;${model.location}</span> <c:if test="${model.node.hasFlows}"><span class="NPflows badge badge-secondary " title="Flows: flow data available"><i class="fa fa-exchange"></i> flow data</span></c:if></div>
   </c:if>
   <c:if test="${model.foreignSource == null}">
     <div class="NPnode">Node: <strong>${model.label}</strong>&nbsp;&nbsp;&nbsp;<span class="NPdbid badge badge-secondary " title="Database ID: ${model.id}"><i class="fa fa-database"></i>&nbsp;${model.id}</span>&nbsp;<span class="NPloc badge badge-secondary " title="Location: ${model.location}"><i class="fa fa-map-marker"></i>&nbsp;${model.location}</span> <c:if test="${model.node.hasFlows}"><span class="NPflows badge badge-secondary " title="Flows: flow data available"><i class="fa fa-exchange"></i> flow data</span></c:if></div>
   </c:if>
 </h5>
diff --git a/opennms-webapp/src/main/webapp/notification/detail.jsp b/opennms-webapp/src/main/webapp/notification/detail.jsp
index 539311768a1a..445cfd0943a6 100644
--- a/opennms-webapp/src/main/webapp/notification/detail.jsp
+++ b/opennms-webapp/src/main/webapp/notification/detail.jsp
@@ -112,11 +112,11 @@
           &nbsp;
         </c:otherwise>
       </c:choose>
     </td>
     <th class="col-md-1">Responder</th>
-    <td class="col-md-2"><%=notice.getResponder()!=null ? notice.getResponder() : "&nbsp;"%></td>
+    <td class="col-md-2"><%=notice.getResponder()!=null ? WebSecurityUtils.sanitizeString(notice.getResponder()) : "&nbsp;"%></td>
     <th class="col-md-1">Location</th>
     <td class="col-md-2">
       <c:choose>
         <c:when test="<%= eventLocation != null %>">
           <a href="event/detail.jsp?id=<%=notice.getEventId()%>"><%= eventLocation %></a>
@@ -235,11 +235,11 @@
     </tr>
   
   <%  for (NoticeSentTo sentTo : notice.getSentTo()) { %>
 
     <tr class="severity-<%=eventSeverity.toLowerCase()%>">
-      <td><%=sentTo.getUserId()%></td>
+      <td><%=WebSecurityUtils.sanitizeString(sentTo.getUserId())%></td>
 
       <td>
         <c:choose>
           <c:when test="<%=sentTo.getTime() != null && sentTo.getTime().getTime() > 0%>">
             <onms:datetime date="<%=sentTo.getTime()%>" />
diff --git a/smoke-test/src/test/java/org/opennms/smoketest/NotificationsPageIT.java b/smoke-test/src/test/java/org/opennms/smoketest/NotificationsPageIT.java
index 3c2e90e9b05d..eb5cf5f69caf 100644
--- a/smoke-test/src/test/java/org/opennms/smoketest/NotificationsPageIT.java
+++ b/smoke-test/src/test/java/org/opennms/smoketest/NotificationsPageIT.java
@@ -36,10 +36,11 @@ import org.junit.Test;
 import org.junit.runners.MethodSorters;
 import org.openqa.selenium.By;
 
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
 public class NotificationsPageIT extends OpenNMSSeleniumIT {
+
     @Before
     public void setUp() throws Exception {
         notificationsPage();
     }
 
@@ -84,7 +85,6 @@ public class NotificationsPageIT extends OpenNMSSeleniumIT {
         findElementByXpath("//p//strong[text()='acknowledged']");
         findElementByLink("[Show outstanding]");
         findElementByLink("Respond Time");
         assertElementDoesNotHaveText(By.xpath("//span[@class='label label-default']"), "admin was notified [-]");
     }
-
 }
diff --git a/smoke-test/src/test/java/org/opennms/smoketest/UserIT.java b/smoke-test/src/test/java/org/opennms/smoketest/UserIT.java
index 226b884da69c..f9e5a22ab644 100644
--- a/smoke-test/src/test/java/org/opennms/smoketest/UserIT.java
+++ b/smoke-test/src/test/java/org/opennms/smoketest/UserIT.java
@@ -27,11 +27,14 @@
  *******************************************************************************/
 
 package org.opennms.smoketest;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
 
+import java.time.Duration;
+import java.time.temporal.ChronoUnit;
 import java.util.List;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.openqa.selenium.Alert;
@@ -119,6 +122,88 @@ public class UserIT extends OpenNMSSeleniumIT {
         findElementById("users(" + USER_NAME + ").doDelete").click();
         handleAlert("Are you sure you want to delete the user " + USER_NAME + "?");
         assertElementDoesNotExist(By.id(USER_NAME));
     }
 
+    @Test
+    public void testInvalidUserIds() {
+        testInvalidUserId("John<b>Doe</b>",true);
+        testInvalidUserId("Jane'Doe'",true);
+        testInvalidUserId("John&Doe",true);
+        testInvalidUserId("Jane\"\"Doe",true);
+    }
+
+    @Test
+    public void testValidUserIds() {
+        testInvalidUserId("John-Doe",false);
+        testInvalidUserId("Jane/Doe",false);
+        testInvalidUserId("John.Doe",false);
+        testInvalidUserId("Jane#Doe", false);
+        testInvalidUserId("John@Döe.com", false);
+        testInvalidUserId("JohnDoé", false);
+    }
+
+    @Test
+    public void testInvalidGroupIds() {
+        testInvalidGroupId("John<b>Doe</b>",true);
+        testInvalidGroupId("Jane'Doe'",true);
+        testInvalidGroupId("John&Doe",true);
+        testInvalidGroupId("Jane\"\"Doe",true);
+    }
+
+    @Test
+    public void testValidGroupIds() {
+        testInvalidGroupId("John-Doe",false);
+        testInvalidGroupId("Jane/Doe",false);
+        testInvalidGroupId("John.Doe",false);
+        testInvalidGroupId("Jane#Doe", false);
+        testInvalidGroupId("John@Döe.com", false);
+        testInvalidGroupId("JohnDoé", false);
+    }
+
+    public void testInvalidUserId(final String userId, final boolean mustFail) {
+        adminPage();
+        findElementByLink("Configure Users, Groups and On-Call Roles").click();
+        findElementByLink("Configure Users").click();
+        findElementByLink("Add new user").click();
+
+        enterText(By.id("userID"), userId);
+        enterText(By.id("pass1"), "SmokeTestPassword");
+        enterText(By.id("pass2"), "SmokeTestPassword");
+        findElementByXpath("//button[@type='submit' and text()='OK']").click();
+
+        if (mustFail) {
+            try {
+                final Alert alert = wait.withTimeout(Duration.of(5, ChronoUnit.SECONDS)).until(ExpectedConditions.alertIsPresent());
+                alert.dismiss();
+            } catch (final Exception e) {
+                LOG.debug("Got an exception waiting for a 'invalid user ID' alert.", e);
+                throw e;
+            }
+        } else {
+            wait.until(ExpectedConditions.elementToBeClickable(By.name("finish")));
+        }
+    }
+
+    public void testInvalidGroupId(final String groupId, final boolean mustFail) {
+        adminPage();
+        findElementByLink("Configure Users, Groups and On-Call Roles").click();
+        findElementByLink("Configure Groups").click();
+        findElementByLink("Add new group").click();
+
+        enterText(By.id("groupName"), groupId);
+        enterText(By.id("groupComment"), "SmokeTestComment");
+        findElementByXpath("//button[@type='submit' and text()='OK']").click();
+
+        if (mustFail) {
+            try {
+                final Alert alert = wait.withTimeout(Duration.of(5, ChronoUnit.SECONDS)).until(ExpectedConditions.alertIsPresent());
+                alert.dismiss();
+            } catch (final Exception e) {
+                LOG.debug("Got an exception waiting for a 'invalid group ID' alert.", e);
+                throw e;
+            }
+        } else {
+            wait.until(ExpectedConditions.elementToBeClickable(By.name("finish")));
+        }
+    }
 }

Vulnerable code:           growl.success('The definition for the requisition ' + $scope.foreignSource + ' has been saved.');
              growl.success('The foreign source definition for ' + $scope.foreignSource + 'has been reseted.');
      growl.success('Retrieving definition for requisition ' + $scope.foreignSource + '...');
          growl.success('The node ' + $scope.node.nodeLabel + ' has been saved.');
      growl.success('Retrieving node ' + $scope.foreignId + ' from requisition ' + $scope.foreignSource + '...');
      growl.info($sanitize('The node ' + $scope.node.nodeLabel + ' is being added to requisition ' + $scope.node.foreignSource + '. Please wait...'));
      var successMessage = $sanitize('The node ' + $scope.node.nodeLabel + ' has been added to requisition ' + $scope.node.foreignSource);
                  growl.success('The requisition ' + foreignSource + ' has been created and synchronized.');
      bootbox.confirm('Are you sure you want to remove the node ' + node.nodeLabel + '?', function(ok) {
              growl.success('The node ' + node.nodeLabel + ' has been deleted.');
      growl.success('Retrieving requisition ' + $scope.foreignSource + '...');
        bootbox.confirm('This action will override the existing foreign source definition for the requisition named ' + targetForeignSource + ', using ' + foreignSource + ' as a template. Are you sure you want to continue ? This cannot be undone.', function(ok) {
              growl.success('The foreign source definition for ' + foreignSource + ' has been cloned to ' + targetForeignSource);
            bootbox.alert('Cannot add the requisition ' + foreignSource + ' because the following characters are invalid:<br/>:, /, \\, ?, &, *, \', "');
            bootbox.alert('Cannot add the requisition ' + foreignSource+ ' because there is already a requisition with that name');
              growl.success('The requisition ' + r.foreignSource + ' has been created.');
          growl.success('The deployed statistics for ' + requisition.foreignSource + ' has been updated.');
      bootbox.confirm('Are you sure you want to remove all the nodes from ' + foreignSource + '?', function(ok) {
              growl.success('All the nodes from ' + foreignSource + ' have been removed, and the requisition has been synchronized.');
      bootbox.confirm('Are you sure you want to remove the requisition ' + foreignSource + '?', function(ok) {
              growl.success('The requisition ' + foreignSource + ' has been deleted.');
              growl.success('The import operation has been started for ' + requisition.foreignSource + ' (rescanExisting? ' + rescanExisting + ')<br/>Use <b>refresh</b> to update the deployed statistics');
          title: 'Synchronize Requisition ' + requisition.foreignSource,
        
              <input type="button" class="btn btn-secondary" value="Edit" onclick="javascript:editNotice('${notification.escapedName}')"/>
              <input type="button" class="btn btn-secondary" value="Delete"  onclick="javascript:deleteNotice('${notification.escapedName}')"/>
                <input type="radio" value="Off" onclick="javascript:setStatus('${notification.escapedName}','off')"/>Off
                <input type="radio" value="On" CHECKED onclick="javascript:setStatus('${notification.escapedName}','on')"/>On
                <input type="radio" value="Off" CHECKED onclick="javascript:setStatus('${notification.escapedName}','off')"/>Off
                <input type="radio" value="On" onclick="javascript:setStatus('${notification.escapedName}','on')"/>On
              ${notification.name}
              ${notification.eventLabel}
              ${notification.displayUei}
        <span>Details for Group: <%=group.getName()%></span>
            <%=group.getComments()%>
               <li> <%=user%> </li>
        if (newName != null && newName != "")
        {
         <tr class="divider ${groupStatus.index % 2 == 0 ?  'even' : 'odd'}" id="group-${group.name}">
              <c:when test='${group.name != "Admin"}'>
                <a id="${group.name}.doDelete" href="javascript:deleteGroup('${group.name}')" onclick="return confirm('Are you sure you want to delete the group ${group.name}?')"><i class="fa fa-trash-o fa-2x"></i></a>              
                <i class="fa fa-trash-o fa-2x" onclick="alert('Sorry, the ${group.name} group cannot be deleted.')"></i>
            <a id="${group.name}.doModify" href="javascript:modifyGroup('${group.name}')"><i class="fa fa-edit fa-2x"></i></a>
                <button id="${group.name}.doRename" type="button" class="btn btn-secondary" name="rename" onclick="renameGroup('${group.name}')">Rename</button>
                <button id="${group.name}.doRename" type="button" class="btn btn-secondary" name="rename" onclick="alert('Sorry, the Admin group cannot be renamed.')">Rename</button>
            <a href="javascript:detailGroup('${group.name}')">${group.name}</a>
                  ${group.comments.get()}
            buffer.append("<option>" + category + "</option>");
  function validateFormInput() 
    
        <td><%=ack.getAckUser()%></td>
              <a href="element/node.jsp?node=<%=event.getNodeId()%>"><%=event.getNodeLabel()%></a>
              <a href="element/node.jsp?node=<%=outage.getNodeId()%>"><%=outage.getNodeLabel()%></a>
          <div class="NLnode"><a href="${nodeLink}">${nodeModel.node.label}</a>&nbsp;&nbsp;<span class="NLdbid label label-default" title="Database ID: ${nodeModel.node.id}"><i class="fa fa-database"></i>&nbsp;${nodeModel.node.id}</span>&nbsp;<span class="NLfs label label-default" title="Requisition: ${nodeModel.node.foreignSource}"><i class="fa fa-list-alt"></i>&nbsp;${nodeModel.node.foreignSource}</span>&nbsp;<span class="NLfid label label-default" title="Foreign ID: ${nodeModel.node.foreignId}"><i class="fa fa-qrcode"></i>&nbsp;${nodeModel.node.foreignId}</span>&nbsp;<span class="NLloc label label-default" title="Location: ${nodeModel.node.location.locationName}"><i class="fa fa-map-marker"></i>&nbsp;${nodeModel.node.location.locationName}</span> <c:if test="${nodeModel.node.hasFlows}"><span class="NLflows label label-default" title="Flows: flow data available"><i class="fa fa-exchange"></i>&nbsp;flow data</span></c:if></div>
<h2><%=(newNotice.getName()!=null ? "Editing notice: " + newNotice.getName() + "<br/>" : "")%></h2>
            <input type="text" class="form-control" name="name" value='<%=(newNotice.getName()!=null ? newNotice.getName() : "")%>'/>
            <input type="text" class="form-control" name="description" value='<%=newNotice.getDescription().orElse("")%>'/>
                <label>Name:</label> <input type="text" class="form-control" size="30" name="varbindName" value='<%=varbindName%>'/>
                <label>Value:</label> <input class="form-control" type="text" size="30" name="varbindValue" value='<%=varbindValue%>'/>
            <textarea rows="3" class="form-control" name="textMsg"><%=(newNotice.getTextMessage()!=null ? newNotice.getTextMessage() : "")%></textarea>
            <textarea rows="1" class="form-control" name="numMsg"><%=newNotice.getNumericMessage().orElse("")%></textarea>
            <input type="text" class="form-control" name="subject" value='<%=newNotice.getSubject().orElse("")%>'/>
        
        if (newID != null && newID != "")
        {
            <a id="<%= "users("+curUser.getUserId()+").doDelete" %>" href="javascript:deleteUser('<%=curUser.getUserId()%>')" onclick="return confirm('Are you sure you want to delete the user <%=curUser.getUserId()%>?')"><i class="fa fa-trash-o fa-2x"></i></a> 
            <a id="<%= "users("+curUser.getUserId()+").doModify" %>" href="javascript:modifyUser('<%=curUser.getUserId()%>')"><i class="fa fa-edit fa-2x"></i></a>
                <button id="<%= "users("+curUser.getUserId()+").doRename" %>" class="btn btn-secondary"  name="rename" onclick="renameUser('<%=curUser.getUserId()%>')">Rename</button>
                <button id="<%= "users("+curUser.getUserId()+").doRename" %>" class="btn btn-secondary"  name="rename" onclick="alert('Sorry, the admin user cannot be renamed.')">Rename</button>
            <a id="<%= "users("+curUser.getUserId()+").doDetails" %>" href="javascript:detailUser('<%=curUser.getUserId()%>')"><%=curUser.getUserId()%></a>
           <div id="<%= "users("+curUser.getUserId()+").fullName" %>">
            <div id="<%= "users("+curUser.getUserId()+").email" %>">
           <div id="<%= "users("+curUser.getUserId()+").pagerEmail" %>">
           <div id="<%= "users("+curUser.getUserId()+").xmppAddress" %>">
             <div id="<%= "users("+curUser.getUserId()+").userComments" %>">
<input id="userID" type="hidden" name="userID" value="<%=user.getUserId()%>"/>
        <span>Modify User: <%=userid%></span>
    
        <span>Details for User: <%=user.getUserId()%></span>
    <div class="NPnode">Node: <strong>${model.label}</strong>&nbsp;&nbsp;&nbsp;<span class="NPdbid badge badge-secondary " title="Database ID: ${model.id}"><i class="fa fa-database"></i>&nbsp;${model.id}</span>&nbsp;<span class="NPfs badge badge-secondary " title="Requisition: ${model.foreignSource}"><i class="fa fa-list-alt"></i>&nbsp;${model.foreignSource}</span>&nbsp;<span class="NPfid badge badge-secondary " title="Foreign ID: ${model.foreignId}"><i class="fa fa-qrcode"></i>&nbsp;${model.foreignId}</span>&nbsp;<span class="NPloc badge badge-secondary " title="Location: ${model.location}"><i class="fa fa-map-marker"></i>&nbsp;${model.location}</span> <c:if test="${model.node.hasFlows}"><span class="NPflows badge badge-secondary " title="Flows: flow data available"><i class="fa fa-exchange"></i> flow data</span></c:if></div>
    <td class="col-md-2"><%=notice.getResponder()!=null ? notice.getResponder() : "&nbsp;"%></td>
      <td><%=sentTo.getUserId()%></td>

Fixed code:           growl.success('The definition for the requisition ' + _.escape($scope.foreignSource) + ' has been saved.');
              growl.success('The foreign source definition for ' + _.escape($scope.foreignSource) + 'has been reseted.');
      growl.success('Retrieving definition for requisition ' + _.escape($scope.foreignSource) + '...');
          growl.success('The node ' + _.escape($scope.node.nodeLabel) + ' has been saved.');
      growl.success('Retrieving node ' + _.escape($scope.foreignId) + ' from requisition ' + _.escape($scope.foreignSource) + '...');
      growl.info('The node ' + _.escape($scope.node.nodeLabel) + ' is being added to requisition ' + _.escape($scope.node.foreignSource) + '. Please wait...');
      var successMessage = 'The node ' + _.escape($scope.node.nodeLabel) + ' has been added to requisition ' + _.escape($scope.node.foreignSource);
                  growl.success('The requisition ' + _.escape(foreignSource) + ' has been created and synchronized.');
      bootbox.confirm('Are you sure you want to remove the node ' + _.escape(node.nodeLabel) + '?', function(ok) {
              growl.success('The node ' + _.escape(node.nodeLabel) + ' has been deleted.');
      growl.success('Retrieving requisition ' + _.escape($scope.foreignSource) + '...');
        bootbox.confirm('This action will override the existing foreign source definition for the requisition named ' + _.escape(targetForeignSource) + ', using ' + _.escape(foreignSource) + ' as a template. Are you sure you want to continue ? This cannot be undone.', function(ok) {
              growl.success('The foreign source definition for ' + _.escape(foreignSource) + ' has been cloned to ' + _.escape(targetForeignSource));
            bootbox.alert('Cannot add the requisition ' + _.escape(foreignSource) + ' because the following characters are invalid:<br/>:, /, \\, ?, &, *, \', "');
            bootbox.alert('Cannot add the requisition ' + _.escape(foreignSource) + ' because there is already a requisition with that name');
              growl.success('The requisition ' + _.escape(r.foreignSource) + ' has been created.');
          growl.success('The deployed statistics for ' + _.escape(requisition.foreignSource) + ' has been updated.');
      bootbox.confirm('Are you sure you want to remove all the nodes from ' + _.escape(foreignSource) + '?', function(ok) {
              growl.success('All the nodes from ' + _.escape(foreignSource) + ' have been removed, and the requisition has been synchronized.');
      bootbox.confirm('Are you sure you want to remove the requisition ' + _.escape(foreignSource) + '?', function(ok) {
              growl.success('The requisition ' + _.escape(foreignSource) + ' has been deleted.');
              growl.success('The import operation has been started for ' + _.escape(requisition.foreignSource) + ' (rescanExisting? ' + rescanExisting + ')<br/>Use <b>refresh</b> to update the deployed statistics');
          title: 'Synchronize Requisition ' + _.escape(requisition.foreignSource),
const _ = require('underscore-js');
const _ = require('underscore-js');
const _ = require('underscore-js');
const _ = require('underscore-js');

        if (userID != null && userID.matches(".*[&<>\"`']+.*")) {
            throw new ServletException("User ID must not contain any HTML markup.");
        }

        if (newID != null && newID.matches(".*[&<>\"`']+.*")) {
            throw new ServletException("User ID must not contain any HTML markup.");
        }


        if (newName != null && newName.matches(".*[&<>\"`']+.*")) {
            throw new ServletException("Group ID must not contain any HTML markup.");
        }

        if (groupName != null && groupName.matches(".*[&<>\"`']+.*")) {
            throw new ServletException("Group ID must not contain any HTML markup.");
        }

        if (groupComment != null && groupComment.matches(".*[&<>\"`']+.*")) {
            throw new ServletException("Group comment must not contain any HTML markup.");
        }

<%@ taglib uri="https://www.owasp.org/index.php/OWASP_Java_Encoder_Project" prefix="e"%>
              <input type="button" class="btn btn-secondary" value="Edit" onclick="javascript:editNotice('${e:forJavaScript(notification.escapedName)}')"/>
              <input type="button" class="btn btn-secondary" value="Delete"  onclick="javascript:deleteNotice('${e:forJavaScript(notification.escapedName)}')"/>
                <input type="radio" value="Off" onclick="javascript:setStatus('${e:forJavaScript(notification.escapedName)}','off')"/>Off
                <input type="radio" value="On" CHECKED onclick="javascript:setStatus('${e:forJavaScript(notification.escapedName)}','on')"/>On
                <input type="radio" value="Off" CHECKED onclick="javascript:setStatus('${e:forJavaScript(notification.escapedName)}','off')"/>Off
                <input type="radio" value="On" onclick="javascript:setStatus('${e:forJavaScript(notification.escapedName)}','on')"/>On
            ${fn:escapeXml(notification.name)}
              ${fn:escapeXml(notification.eventLabel)}
              ${fn:escapeXml(notification.displayUei)}
<%@ page import="org.opennms.core.utils.WebSecurityUtils" %>
        <span>Details for Group: <%=WebSecurityUtils.sanitizeString(group.getName())%></span>
            <%=WebSecurityUtils.sanitizeString(group.getComments())%>
               <li> <%=WebSecurityUtils.sanitizeString(user)%> </li>
<%@taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %>
        if (newName != null && newName != "") {
          if (/.*[&<>"`']+.*/.test(newName)) {
            alert("The group ID must not contain any HTML markup.");
            return;
          }
         <tr class="divider ${groupStatus.index % 2 == 0 ?  'even' : 'odd'}" id="group-${fn:escapeXml(group.name)}">
              <c:when test='${fn:escapeXml(group.name) != "Admin"}'>
                <a id="${group.name}.doDelete" href="javascript:deleteGroup('${fn:escapeXml(group.name)}')" onclick="return confirm('Are you sure you want to delete the group ${fn:escapeXml(group.name)}?')"><i class="fa fa-trash-o fa-2x"></i></a>
                <i class="fa fa-trash-o fa-2x" onclick="alert('Sorry, the ${fn:escapeXml(group.name)} group cannot be deleted.')"></i>
            <a id="${fn:escapeXml(group.name)}.doModify" href="javascript:modifyGroup('${fn:escapeXml(group.name)}')"><i class="fa fa-edit fa-2x"></i></a>
                <button id="${fn:escapeXml(group.name)}.doRename" type="button" class="btn btn-secondary" name="rename" onclick="renameGroup('${fn:escapeXml(group.name)}')">Rename</button>
                <button id="${fn:escapeXml(group.name)}.doRename" type="button" class="btn btn-secondary" name="rename" onclick="alert('Sorry, the Admin group cannot be renamed.')">Rename</button>
            <a href="javascript:detailGroup('${fn:escapeXml(group.name)}')">${fn:escapeXml(group.name)}</a>
                  ${fn:escapeXml(group.comments.get())}
<%@ page import="org.opennms.core.utils.WebSecurityUtils" %>
            buffer.append("<option>" + WebSecurityUtils.sanitizeString(category) + "</option>");
  function validateFormInput()

    if (/.*[&<>"`']+.*/.test(id)) {
        alert("The group ID must not contain any HTML markup.");
        return false;
    }

    var comment = new String(document.newGroupForm.groupComment.value);
    if (/.*[&<>"`']+.*/.test(comment)) {
        alert("The group comment must not contain any HTML markup.");
        return false;
    }

        <td><%=WebSecurityUtils.sanitizeString(ack.getAckUser())%></td>
              <a href="element/node.jsp?node=<%=event.getNodeId()%>"><%=WebSecurityUtils.sanitizeString(event.getNodeLabel())%></a>
              <a href="element/node.jsp?node=<%=outage.getNodeId()%>"><%=WebSecurityUtils.sanitizeString(outage.getNodeLabel())%></a>
<%@taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %>
          <div class="NLnode"><a href="${nodeLink}">${nodeModel.node.label}</a>&nbsp;&nbsp;<span class="NLdbid label label-default" title="Database ID: ${nodeModel.node.id}"><i class="fa fa-database"></i>&nbsp;${nodeModel.node.id}</span>&nbsp;<span class="NLfs label label-default" title="Requisition: ${fn:escapeXml(nodeModel.node.foreignSource)}"><i class="fa fa-list-alt"></i>&nbsp;${fn:escapeXml(nodeModel.node.foreignSource)}</span>&nbsp;<span class="NLfid label label-default" title="Foreign ID: ${nodeModel.node.foreignId}"><i class="fa fa-qrcode"></i>&nbsp;${nodeModel.node.foreignId}</span>&nbsp;<span class="NLloc label label-default" title="Location: ${nodeModel.node.location.locationName}"><i class="fa fa-map-marker"></i>&nbsp;${nodeModel.node.location.locationName}</span> <c:if test="${nodeModel.node.hasFlows}"><span class="NLflows label label-default" title="Flows: flow data available"><i class="fa fa-exchange"></i>&nbsp;flow data</span></c:if></div>
<%@ page import="org.opennms.core.utils.WebSecurityUtils" %>

<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn"%>

<h2><%=(newNotice.getName()!=null ? "Editing notice: " + WebSecurityUtils.sanitizeString(newNotice.getName()) + "<br/>" : "")%></h2>
            <input type="text" class="form-control" name="name" value='<%=(newNotice.getName()!=null ? WebSecurityUtils.sanitizeString(newNotice.getName()) : "")%>'/>
            <input type="text" class="form-control" name="description" value='<%=WebSecurityUtils.sanitizeString(newNotice.getDescription().orElse(""))%>'/>
                <label>Name:</label> <input type="text" class="form-control" size="30" name="varbindName" value='<%=WebSecurityUtils.sanitizeString(varbindName)%>'/>
                <label>Value:</label> <input class="form-control" type="text" size="30" name="varbindValue" value='<%=WebSecurityUtils.sanitizeString(varbindValue)%>'/>
            <textarea rows="3" class="form-control" name="textMsg"><%=(newNotice.getTextMessage()!=null ? WebSecurityUtils.sanitizeString(newNotice.getTextMessage()) : "")%></textarea>
            <textarea rows="1" class="form-control" name="numMsg"><%=WebSecurityUtils.sanitizeString(newNotice.getNumericMessage().orElse(""))%></textarea>
            <input type="text" class="form-control" name="subject" value='<%=WebSecurityUtils.sanitizeString(newNotice.getSubject().orElse(""))%>'/>
<%@ page import="org.opennms.core.utils.WebSecurityUtils" %>

<%@taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %>


        if (newID != null && newID != "") {
          if (/.*[&<>"`']+.*/.test(newID)) {
            alert("The user ID must not contain any HTML markup.");
            return;
          }

	      String sanitizedUserId = WebSecurityUtils.sanitizeString(curUser.getUserId());
            <a id="<%= "users("+sanitizedUserId+").doDelete" %>" href="javascript:deleteUser('<%=sanitizedUserId%>')" onclick="return confirm('Are you sure you want to delete the user <%=sanitizedUserId%>?')"><i class="fa fa-trash-o fa-2x"></i></a>
            <a id="<%= "users("+sanitizedUserId+").doModify" %>" href="javascript:modifyUser('<%=sanitizedUserId%>')"><i class="fa fa-edit fa-2x"></i></a>
                <button id="<%= "users("+sanitizedUserId+").doRename" %>" class="btn btn-secondary"  name="rename" onclick="renameUser('<%=sanitizedUserId%>')">Rename</button>
                <button id="<%= "users("+sanitizedUserId+").doRename" %>" class="btn btn-secondary"  name="rename" onclick="alert('Sorry, the admin user cannot be renamed.')">Rename</button>
            <a id="<%= "users("+sanitizedUserId+").doDetails" %>" href="javascript:detailUser('<%=sanitizedUserId%>')"><%=sanitizedUserId%></a>
           <div id="<%= "users("+sanitizedUserId+").fullName" %>">
            <div id="<%= "users("+sanitizedUserId+").email" %>">
           <div id="<%= "users("+sanitizedUserId+").pagerEmail" %>">
           <div id="<%= "users("+sanitizedUserId+").xmppAddress" %>">
             <div id="<%= "users("+sanitizedUserId+").userComments" %>">
<%@ page import="org.opennms.core.utils.WebSecurityUtils" %>
<input id="userID" type="hidden" name="userID" value="<%=WebSecurityUtils.sanitizeString(user.getUserId())%>"/>
        <span>Modify User: <%=WebSecurityUtils.sanitizeString(userid)%></span>

    if (/.*[&<>"`']+.*/.test(id)) {
        alert("The user ID must not contain any HTML markup.");
        return false;
    }

<%@ page import="org.opennms.core.utils.WebSecurityUtils" %>
<%@ page import="java.util.stream.Collectors" %>
        <span>Details for User: <%=WebSecurityUtils.sanitizeString(user.getUserId())%></span>
    <div class="NPnode">Node: <strong>${model.label}</strong>&nbsp;&nbsp;&nbsp;<span class="NPdbid badge badge-secondary " title="Database ID: ${model.id}"><i class="fa fa-database"></i>&nbsp;${model.id}</span>&nbsp;<span class="NPfs badge badge-secondary " title="Requisition: ${fn:escapeXml(model.foreignSource)}"><i class="fa fa-list-alt"></i>&nbsp;${fn:escapeXml(model.foreignSource)}</span>&nbsp;<span class="NPfid badge badge-secondary " title="Foreign ID: ${model.foreignId}"><i class="fa fa-qrcode"></i>&nbsp;${model.foreignId}</span>&nbsp;<span class="NPloc badge badge-secondary " title="Location: ${model.location}"><i class="fa fa-map-marker"></i>&nbsp;${model.location}</span> <c:if test="${model.node.hasFlows}"><span class="NPflows badge badge-secondary " title="Flows: flow data available"><i class="fa fa-exchange"></i> flow data</span></c:if></div>
    <td class="col-md-2"><%=notice.getResponder()!=null ? WebSecurityUtils.sanitizeString(notice.getResponder()) : "&nbsp;"%></td>
      <td><%=WebSecurityUtils.sanitizeString(sentTo.getUserId())%></td>

import static org.junit.Assert.assertTrue;
import java.time.Duration;
import java.time.temporal.ChronoUnit;
    @Test
    public void testInvalidUserIds() {
        testInvalidUserId("John<b>Doe</b>",true);
        testInvalidUserId("Jane'Doe'",true);
        testInvalidUserId("John&Doe",true);
        testInvalidUserId("Jane\"\"Doe",true);
    }

    @Test
    public void testValidUserIds() {
        testInvalidUserId("John-Doe",false);
        testInvalidUserId("Jane/Doe",false);
        testInvalidUserId("John.Doe",false);
        testInvalidUserId("Jane#Doe", false);
        testInvalidUserId("John@Döe.com", false);
        testInvalidUserId("JohnDoé", false);
    }

    @Test
    public void testInvalidGroupIds() {
        testInvalidGroupId("John<b>Doe</b>",true);
        testInvalidGroupId("Jane'Doe'",true);
        testInvalidGroupId("John&Doe",true);
        testInvalidGroupId("Jane\"\"Doe",true);
    }

    @Test
    public void testValidGroupIds() {
        testInvalidGroupId("John-Doe",false);
        testInvalidGroupId("Jane/Doe",false);
        testInvalidGroupId("John.Doe",false);
        testInvalidGroupId("Jane#Doe", false);
        testInvalidGroupId("John@Döe.com", false);
        testInvalidGroupId("JohnDoé", false);
    }

    public void testInvalidUserId(final String userId, final boolean mustFail) {
        adminPage();
        findElementByLink("Configure Users, Groups and On-Call Roles").click();
        findElementByLink("Configure Users").click();
        findElementByLink("Add new user").click();

        enterText(By.id("userID"), userId);
        enterText(By.id("pass1"), "SmokeTestPassword");
        enterText(By.id("pass2"), "SmokeTestPassword");
        findElementByXpath("//button[@type='submit' and text()='OK']").click();

        if (mustFail) {
            try {
                final Alert alert = wait.withTimeout(Duration.of(5, ChronoUnit.SECONDS)).until(ExpectedConditions.alertIsPresent());
                alert.dismiss();
            } catch (final Exception e) {
                LOG.debug("Got an exception waiting for a 'invalid user ID' alert.", e);
                throw e;
            }
        } else {
            wait.until(ExpectedConditions.elementToBeClickable(By.name("finish")));
        }
    }

    public void testInvalidGroupId(final String groupId, final boolean mustFail) {
        adminPage();
        findElementByLink("Configure Users, Groups and On-Call Roles").click();
        findElementByLink("Configure Groups").click();
        findElementByLink("Add new group").click();

        enterText(By.id("groupName"), groupId);
        enterText(By.id("groupComment"), "SmokeTestComment");
        findElementByXpath("//button[@type='submit' and text()='OK']").click();

        if (mustFail) {
            try {
                final Alert alert = wait.withTimeout(Duration.of(5, ChronoUnit.SECONDS)).until(ExpectedConditions.alertIsPresent());
                alert.dismiss();
            } catch (final Exception e) {
                LOG.debug("Got an exception waiting for a 'invalid group ID' alert.", e);
                throw e;
            }
        } else {
            wait.until(ExpectedConditions.elementToBeClickable(By.name("finish")));
        }
    }