CVE-2021-25933: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting') (CWE-79)
value: In OpenNMS Horizon, versions opennms-1-0-stable 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.6-1 are vulnerable to Stored Cross-Site Scripting, since the function `validateFormInput()` performs improper validation checks on the input sent to the `groupName` and `groupComment` parameters. Due to this flaw, an authenticated attacker could inject arbitrary script and trick other admin users into downloading malicious files which can cause severe damage to the organization using opennms.
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: 4.8
Published: 2021-05-20T15: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")));
        }
    }