diff --git a/src/ServiceControl.Config.Tests/Validation/AddAuditInstanceValidationTests.cs b/src/ServiceControl.Config.Tests/Validation/AddAuditInstanceValidationTests.cs index d76f3400fc..800976d87a 100644 --- a/src/ServiceControl.Config.Tests/Validation/AddAuditInstanceValidationTests.cs +++ b/src/ServiceControl.Config.Tests/Validation/AddAuditInstanceValidationTests.cs @@ -321,6 +321,8 @@ public void Audit_hostname_can_not_be_null_when_adding_audit_instance() [TestCase("192.168.1.1")] [TestCase("256.0.0.0")] + [TestCase("::1")] + [TestCase("2001:0db8:85a3:0000:0000:8a2e:0370:7334")] public void Audit_hostname_can_be_an_ip_address_when_adding_audit_instance(string ipAddress) { var viewModel = new ServiceControlAddViewModel @@ -337,6 +339,33 @@ public void Audit_hostname_can_be_an_ip_address_when_adding_audit_instance(strin Assert.That(notifyErrorInfo.GetErrors(nameof(viewModel.AuditHostName)), Is.Empty); } + [TestCase("hostname with spaces")] + [TestCase("bad@hostname")] + [TestCase("bad#hostname")] + [TestCase("badhostname...")] + [TestCase("badhostname[/")] + public void Audit_hostname_cannot_contain_invalid_characters_when_adding_audit_instance(string invalidHostname) + { + var viewModel = new ServiceControlAddViewModel + { + InstallAuditInstance = true, + SubmitAttempted = true, + AuditHostName = invalidHostname + }; + + viewModel.NotifyOfPropertyChange(nameof(viewModel.AuditHostName)); + + var notifyErrorInfo = GetNotifyErrorInfo(viewModel); + var errors = notifyErrorInfo.GetErrors(nameof(viewModel.AuditHostName)); + + Assert.Multiple(() => + { + Assert.That(errors, Is.Not.Empty, "Hostname validation should exist and trigger for invalid hostnames"); + Assert.That(errors.Cast().Any(error => error.Contains("Hostname is not valid")), Is.True, + "Hostname validation should display the exact error message 'Hostname is not valid'"); + }); + } + #endregion #region Portnumber diff --git a/src/ServiceControl.Config.Tests/Validation/AddErrorInstanceValidationTests.cs b/src/ServiceControl.Config.Tests/Validation/AddErrorInstanceValidationTests.cs index 534ba7f705..12150cab65 100644 --- a/src/ServiceControl.Config.Tests/Validation/AddErrorInstanceValidationTests.cs +++ b/src/ServiceControl.Config.Tests/Validation/AddErrorInstanceValidationTests.cs @@ -320,6 +320,8 @@ public void Error_hostname_can_not_be_null_when_adding_error_instance() [TestCase("192.168.1.1")] [TestCase("256.0.0.0")] + [TestCase("::1")] + [TestCase("2001:0db8:85a3:0000:0000:8a2e:0370:7334")] public void Error_hostname_can_be_an_ip_address_when_adding_error_instance(string ipAddress) { var viewModel = new ServiceControlAddViewModel @@ -336,6 +338,33 @@ public void Error_hostname_can_be_an_ip_address_when_adding_error_instance(strin Assert.That(notifyErrorInfo.GetErrors(nameof(viewModel.ErrorHostName)), Is.Empty); } + [TestCase("hostname with spaces")] + [TestCase("bad@hostname")] + [TestCase("bad#hostname")] + [TestCase("badhostname...")] + [TestCase("badhostname[/")] + public void Error_hostname_cannot_contain_invalid_characters_when_adding_error_instance(string invalidHostname) + { + var viewModel = new ServiceControlAddViewModel + { + InstallErrorInstance = true, + SubmitAttempted = true, + ErrorHostName = invalidHostname + }; + + viewModel.NotifyOfPropertyChange(nameof(viewModel.ErrorHostName)); + + var notifyErrorInfo = GetNotifyErrorInfo(viewModel); + var errors = notifyErrorInfo.GetErrors(nameof(viewModel.ErrorHostName)); + + Assert.Multiple(() => + { + Assert.That(errors, Is.Not.Empty, "Hostname validation should exist and trigger for invalid hostnames"); + Assert.That(errors.Cast().Any(error => error.Contains("Hostname is not valid")), Is.True, + "Hostname validation should display the exact error message 'Hostname is not valid'"); + }); + } + #endregion #region Portnumber diff --git a/src/ServiceControl.Config.Tests/Validation/AddMonitoringInstanceValidationTests.cs b/src/ServiceControl.Config.Tests/Validation/AddMonitoringInstanceValidationTests.cs index 42ccf0e6dd..5a089ddee9 100644 --- a/src/ServiceControl.Config.Tests/Validation/AddMonitoringInstanceValidationTests.cs +++ b/src/ServiceControl.Config.Tests/Validation/AddMonitoringInstanceValidationTests.cs @@ -127,6 +127,8 @@ public void Monitoring_hostname_cannot_be_null_when_adding_monitoring_instance() [TestCase("192.168.1.1")] [TestCase("256.0.0.0")] + [TestCase("::1")] + [TestCase("2001:0db8:85a3:0000:0000:8a2e:0370:7334")] public void Monitoring_hostname_can_be_an_ip_address_when_adding_monitoring_instance(string ipAddress) { var viewModel = new MonitoringAddViewModel @@ -142,6 +144,32 @@ public void Monitoring_hostname_can_be_an_ip_address_when_adding_monitoring_inst Assert.That(notifyErrorInfo.GetErrors(nameof(viewModel.HostName)), Is.Empty); } + [TestCase("hostname with spaces")] + [TestCase("bad@hostname")] + [TestCase("bad#hostname")] + [TestCase("badhostname...")] + [TestCase("badhostname[/")] + public void Monitoring_hostname_cannot_contain_invalid_characters_when_adding_monitoring_instance(string invalidHostname) + { + var viewModel = new MonitoringAddViewModel + { + SubmitAttempted = true, + HostName = invalidHostname + }; + + viewModel.NotifyOfPropertyChange(nameof(viewModel.HostName)); + + var notifyErrorInfo = GetNotifyErrorInfo(viewModel); + var errors = notifyErrorInfo.GetErrors(nameof(viewModel.HostName)); + + Assert.Multiple(() => + { + Assert.That(errors, Is.Not.Empty, "Hostname validation should exist and trigger for invalid hostnames"); + Assert.That(errors.Cast().Any(error => error.Contains("Hostname is not valid")), Is.True, + "Hostname validation should display the exact error message 'Hostname is not valid'"); + }); + } + #endregion #region Portnumber diff --git a/src/ServiceControl.Config.Tests/Validation/EditAuditInstanceValidationTests.cs b/src/ServiceControl.Config.Tests/Validation/EditAuditInstanceValidationTests.cs index 73abc4d6d2..977bc00b9e 100644 --- a/src/ServiceControl.Config.Tests/Validation/EditAuditInstanceValidationTests.cs +++ b/src/ServiceControl.Config.Tests/Validation/EditAuditInstanceValidationTests.cs @@ -1,6 +1,7 @@ namespace ServiceControl.Config.Tests.Validation { using System.ComponentModel; + using System.Linq; using NUnit.Framework; using ServiceControlInstaller.Engine.Instances; using UI.InstanceAdd; @@ -127,7 +128,9 @@ public void Audit_hostname_can_not_be_null_when_editing_audit_instance() [TestCase("192.168.1.1")] [TestCase("256.0.0.0")] - public void Audi_hostname_can_be_an_ip_address_when_editing_an_audit_instance(string ipAddress) + [TestCase("::1")] + [TestCase("2001:0db8:85a3:0000:0000:8a2e:0370:7334")] + public void Audit_hostname_can_be_an_ip_address_when_editing_an_audit_instance(string ipAddress) { var viewModel = new ServiceControlAuditEditViewModel { @@ -142,6 +145,32 @@ public void Audi_hostname_can_be_an_ip_address_when_editing_an_audit_instance(st Assert.That(notifyErrorInfo.GetErrors(nameof(viewModel.HostName)), Is.Empty); } + [TestCase("hostname with spaces")] + [TestCase("bad@hostname")] + [TestCase("bad#hostname")] + [TestCase("badhostname...")] + [TestCase("badhostname[/")] + public void Audit_hostname_cannot_contain_invalid_characters_when_editing_audit_instance(string invalidHostname) + { + var viewModel = new ServiceControlAuditEditViewModel + { + SubmitAttempted = true, + HostName = invalidHostname + }; + + viewModel.NotifyOfPropertyChange(nameof(viewModel.HostName)); + + var notifyErrorInfo = GetNotifyErrorInfo(viewModel); + var errors = notifyErrorInfo.GetErrors(nameof(viewModel.HostName)); + + Assert.Multiple(() => + { + Assert.That(errors, Is.Not.Empty, "Hostname validation should exist and trigger for invalid hostnames"); + Assert.That(errors.Cast().Any(error => error.Contains("Hostname is not valid")), Is.True, + "Hostname validation should display the exact error message 'Hostname is not valid'"); + }); + } + #endregion #region Portnumber diff --git a/src/ServiceControl.Config.Tests/Validation/EditErrorInstanceValidationTests.cs b/src/ServiceControl.Config.Tests/Validation/EditErrorInstanceValidationTests.cs index a1e744191a..230bf2bbad 100644 --- a/src/ServiceControl.Config.Tests/Validation/EditErrorInstanceValidationTests.cs +++ b/src/ServiceControl.Config.Tests/Validation/EditErrorInstanceValidationTests.cs @@ -1,6 +1,7 @@ namespace ServiceControl.Config.Tests.Validation { using System.ComponentModel; + using System.Linq; using NUnit.Framework; using ServiceControlInstaller.Engine.Instances; using UI.InstanceAdd; @@ -120,6 +121,8 @@ public void Error_hostname_can_not_be_null_when_editing_error_instance() [TestCase("192.168.1.1")] [TestCase("256.0.0.0")] + [TestCase("::1")] + [TestCase("2001:0db8:85a3:0000:0000:8a2e:0370:7334")] public void Error_hostname_can_be_an_ip_address_when_editing_an_error_instance(string ipAddress) { var viewModel = new ServiceControlEditViewModel @@ -135,6 +138,32 @@ public void Error_hostname_can_be_an_ip_address_when_editing_an_error_instance(s Assert.That(notifyErrorInfo.GetErrors(nameof(viewModel.HostName)), Is.Empty); } + [TestCase("hostname with spaces")] + [TestCase("bad@hostname")] + [TestCase("bad#hostname")] + [TestCase("badhostname...")] + [TestCase("badhostname[/")] + public void Error_hostname_cannot_contain_invalid_characters_when_editing_error_instance(string invalidHostname) + { + var viewModel = new ServiceControlEditViewModel + { + SubmitAttempted = true, + HostName = invalidHostname + }; + + viewModel.NotifyOfPropertyChange(nameof(viewModel.HostName)); + + var notifyErrorInfo = GetNotifyErrorInfo(viewModel); + var errors = notifyErrorInfo.GetErrors(nameof(viewModel.HostName)); + + Assert.Multiple(() => + { + Assert.That(errors, Is.Not.Empty, "Hostname validation should exist and trigger for invalid hostnames"); + Assert.That(errors.Cast().Any(error => error.Contains("Hostname is not valid")), Is.True, + "Hostname validation should display the exact error message 'Hostname is not valid'"); + }); + } + #endregion #region Portnumber diff --git a/src/ServiceControl.Config.Tests/Validation/EditMonitoringInstanceValidationTests.cs b/src/ServiceControl.Config.Tests/Validation/EditMonitoringInstanceValidationTests.cs index 18815e2ea7..2754b0dd87 100644 --- a/src/ServiceControl.Config.Tests/Validation/EditMonitoringInstanceValidationTests.cs +++ b/src/ServiceControl.Config.Tests/Validation/EditMonitoringInstanceValidationTests.cs @@ -1,6 +1,7 @@ namespace ServiceControl.Config.Tests.Validation { using System.ComponentModel; + using System.Linq; using NUnit.Framework; using UI.InstanceEdit; @@ -42,6 +43,8 @@ public void Monitoring_hostname_cannot_be_null_when_editing_monitoring_instance( [TestCase("192.168.1.1")] [TestCase("256.0.0.0")] + [TestCase("::1")] + [TestCase("2001:0db8:85a3:0000:0000:8a2e:0370:7334")] public void Monitoring_hostname_can_be_an_ip_address_when_editing_a_monitoring_instance(string ipAddress) { var viewModel = new MonitoringEditViewModel @@ -56,6 +59,32 @@ public void Monitoring_hostname_can_be_an_ip_address_when_editing_a_monitoring_i Assert.That(notifyErrorInfo.GetErrors(nameof(viewModel.HostName)), Is.Empty); } + + [TestCase("hostname with spaces")] + [TestCase("bad@hostname")] + [TestCase("bad#hostname")] + [TestCase("badhostname...")] + [TestCase("badhostname[/")] + public void Monitoring_hostname_cannot_contain_invalid_characters_when_editing_monitoring_instance(string invalidHostname) + { + var viewModel = new MonitoringEditViewModel + { + SubmitAttempted = true, + HostName = invalidHostname + }; + + viewModel.NotifyOfPropertyChange(nameof(viewModel.HostName)); + + var notifyErrorInfo = GetNotifyErrorInfo(viewModel); + var errors = notifyErrorInfo.GetErrors(nameof(viewModel.HostName)); + + Assert.Multiple(() => + { + Assert.That(errors, Is.Not.Empty, "Hostname validation should exist and trigger for invalid hostnames"); + Assert.That(errors.Cast().Any(error => error.Contains("Hostname is not valid")), Is.True, + "Hostname validation should display the exact error message 'Hostname is not valid'"); + }); + } #endregion #region Portnumber diff --git a/src/ServiceControl.Config/UI/InstanceAdd/ServiceControlAddViewModelValidator.cs b/src/ServiceControl.Config/UI/InstanceAdd/ServiceControlAddViewModelValidator.cs index 223d5505dd..7a627d954a 100644 --- a/src/ServiceControl.Config/UI/InstanceAdd/ServiceControlAddViewModelValidator.cs +++ b/src/ServiceControl.Config/UI/InstanceAdd/ServiceControlAddViewModelValidator.cs @@ -52,6 +52,7 @@ public ServiceControlAddViewModelValidator() RuleFor(viewModel => viewModel.ErrorHostName) .NotEmpty() + .ValidHostname() .When(viewModel => viewModel.InstallErrorInstance); RuleFor(x => x.ErrorPortNumber) @@ -155,6 +156,7 @@ public ServiceControlAddViewModelValidator() RuleFor(viewModel => viewModel.AuditHostName) .NotEmpty() + .ValidHostname() .When(viewModel => viewModel.InstallAuditInstance); RuleFor(x => x.AuditPortNumber) diff --git a/src/ServiceControl.Config/UI/InstanceEdit/MonitoringEditViewModelValidator.cs b/src/ServiceControl.Config/UI/InstanceEdit/MonitoringEditViewModelValidator.cs index d9c762282c..46aad9154d 100644 --- a/src/ServiceControl.Config/UI/InstanceEdit/MonitoringEditViewModelValidator.cs +++ b/src/ServiceControl.Config/UI/InstanceEdit/MonitoringEditViewModelValidator.cs @@ -12,10 +12,6 @@ public MonitoringEditViewModelValidator() .NotEmpty() .When(x => x.SubmitAttempted); - RuleFor(x => x.HostName) - .NotEmpty() - .When(x => x.SubmitAttempted); - RuleFor(x => x.PortNumber) .NotEmpty() .ValidPort() diff --git a/src/ServiceControl.Config/UI/InstanceEdit/ServiceControlAuditEditViewModelValidator.cs b/src/ServiceControl.Config/UI/InstanceEdit/ServiceControlAuditEditViewModelValidator.cs index b4dd2524e0..9741d57238 100644 --- a/src/ServiceControl.Config/UI/InstanceEdit/ServiceControlAuditEditViewModelValidator.cs +++ b/src/ServiceControl.Config/UI/InstanceEdit/ServiceControlAuditEditViewModelValidator.cs @@ -14,6 +14,7 @@ public ServiceControlAuditEditViewModelValidator() RuleFor(x => x.HostName) .NotEmpty() + .ValidHostname() .When(x => x.SubmitAttempted); RuleFor(x => x.PortNumber) diff --git a/src/ServiceControl.Config/UI/InstanceEdit/ServiceControlEditViewModelValidator.cs b/src/ServiceControl.Config/UI/InstanceEdit/ServiceControlEditViewModelValidator.cs index d91d0f78da..0e9f6bfe8f 100644 --- a/src/ServiceControl.Config/UI/InstanceEdit/ServiceControlEditViewModelValidator.cs +++ b/src/ServiceControl.Config/UI/InstanceEdit/ServiceControlEditViewModelValidator.cs @@ -14,6 +14,7 @@ public ServiceControlEditViewModelValidator() RuleFor(x => x.HostName) .NotEmpty() + .ValidHostname() .When(x => x.SubmitAttempted); RuleFor(x => x.PortNumber) diff --git a/src/ServiceControl.Config/UI/SharedInstanceEditor/SharedMonitoringEditorViewModelValidator.cs b/src/ServiceControl.Config/UI/SharedInstanceEditor/SharedMonitoringEditorViewModelValidator.cs index 54d21952ff..d226baa652 100644 --- a/src/ServiceControl.Config/UI/SharedInstanceEditor/SharedMonitoringEditorViewModelValidator.cs +++ b/src/ServiceControl.Config/UI/SharedInstanceEditor/SharedMonitoringEditorViewModelValidator.cs @@ -9,7 +9,7 @@ protected SharedMonitoringEditorViewModelValidator() { RuleFor(x => x.HostName) .NotEmpty() - .When(x => x.SubmitAttempted); + .ValidHostname(); RuleFor(x => x.LogPath) .NotEmpty() diff --git a/src/ServiceControl.Config/UI/Upgrades/AddNewAuditInstanceViewModelValidator.cs b/src/ServiceControl.Config/UI/Upgrades/AddNewAuditInstanceViewModelValidator.cs index cd38ec6059..26b4cdc499 100644 --- a/src/ServiceControl.Config/UI/Upgrades/AddNewAuditInstanceViewModelValidator.cs +++ b/src/ServiceControl.Config/UI/Upgrades/AddNewAuditInstanceViewModelValidator.cs @@ -60,6 +60,11 @@ public AddNewAuditInstanceViewModelValidator() .NotEmpty() .NotEqual(x => x.ServiceControlAudit.AuditQueueName).WithMessage(string.Format(Validation.Validations.MSG_UNIQUEQUEUENAME, "Audit")) .When(x => x.SubmitAttempted && (x.ServiceControlAudit.AuditForwarding?.Value ?? false)); + + RuleFor(x => x.ServiceControlAudit.HostName) + .NotEmpty() + .ValidHostname() + .When(x => x.SubmitAttempted); } } } \ No newline at end of file diff --git a/src/ServiceControl.Config/Validation/Validations.cs b/src/ServiceControl.Config/Validation/Validations.cs index b0562eee77..7fb7ba8373 100644 --- a/src/ServiceControl.Config/Validation/Validations.cs +++ b/src/ServiceControl.Config/Validation/Validations.cs @@ -202,6 +202,21 @@ public static IRuleBuilderOptions MustBeUniqueWindowsServiceName(t }); } + public static IRuleBuilderOptions ValidHostname(this IRuleBuilder ruleBuilder) + { + return ruleBuilder.Must((t, hostname) => + { + if (string.IsNullOrWhiteSpace(hostname)) + { + return false; + } + + var hostNameType = Uri.CheckHostName(hostname); + return hostNameType is UriHostNameType.Dns or UriHostNameType.IPv4 or UriHostNameType.IPv6; + }) + .WithMessage(MSG_INVALID_HOSTNAME); + } + public const string MSG_EMAIL_NOT_VALID = "Not Valid."; public const string MSG_THIS_TRANSPORT_REQUIRES_A_CONNECTION_STRING = "This transport requires a connection string."; @@ -234,6 +249,8 @@ public static IRuleBuilderOptions MustBeUniqueWindowsServiceName(t public const string MSG_ILLEGAL_PATH_CHAR = "Paths cannot contain characters {0}"; + public const string MSG_INVALID_HOSTNAME = "Hostname is not valid."; + static char[] ILLEGAL_PATH_CHARS = { '*',