Example #1
0
        public async Task <DomainTlsEvaluatorResults> GetDomainTlsEvaluatorResults(string domain)
        {
            MxEntityState mxState = await _mxApiDao.GetMxEntityState(domain);

            if (mxState == null)
            {
                _log.LogInformation($"Domain {domain} not found - publishing DomainMissing");
                await _messagePublisher.Publish(new DomainMissing(domain), _config.MicroserviceOutputSnsTopicArn);

                return(null);
            }

            if (mxState.HostMxRecords == null)
            {
                return(_domainTlsEvaluatorResultsFactory.CreatePending(domain));
            }

            List <string> hostNames = mxState.HostMxRecords.Select(x => x.Id).ToList();

            Dictionary <string, TlsEntityState> tlsEntityStates = await _mxApiDao.GetTlsEntityStates(hostNames);

            DomainTlsEvaluatorResults result = _domainTlsEvaluatorResultsFactory.Create(mxState, tlsEntityStates);

            return(result);
        }
Example #2
0
        public async Task <MxEntityState> Get(string domain)
        {
            MxEntityState result = null;

            using (var connection = await CreateAndOpenConnection())
                using (DbDataReader reader = await MySqlHelper.ExecuteReaderAsync(connection,
                                                                                  MxStateDaoResources.GetMxRecord, new MySqlParameter("domain", ReverseUrl(domain))))
                {
                    while (await reader.ReadAsync())
                    {
                        result = result ?? new MxEntityState(domain)
                        {
                            Error         = JsonConvert.DeserializeObject <Message>(reader.GetString("error")),
                            LastUpdated   = reader.GetDateTimeNullable("lastUpdated"),
                            MxState       = (MxState)reader.GetInt32("mxState"),
                            HostMxRecords = new List <HostMxRecord>()
                        };

                        string hostMxRecord = reader.GetString("hostMxRecord");
                        if (!string.IsNullOrEmpty(hostMxRecord))
                        {
                            result.HostMxRecords.Add(JsonConvert.DeserializeObject <HostMxRecord>(hostMxRecord));
                        }
                    }
                }

            return(result);
        }
Example #3
0
        public async Task ShouldHandleChangeSaveAndDispatchWhenMxRecordsPolledReceived()
        {
            string snsTopicArn = "SnsTopicArn";

            A.CallTo(() => _mxEntityConfig.SnsTopicArn).Returns(snsTopicArn);

            string        domainName  = "testDomainName";
            MxEntityState stateFromDb = new MxEntityState(domainName.ToLower())
            {
                MxState = MxState.Created
            };

            A.CallTo(() => _dao.Get(domainName.ToLower())).Returns(Task.FromResult(stateFromDb));


            A.CallTo(() => _mxEntityConfig.NextScheduledInSeconds).Returns(33);
            A.CallTo(() => _clock.GetDateTimeUtc()).Returns(DateTime.MinValue);

            MxRecordsPolled message = new MxRecordsPolled(domainName, new List <HostMxRecord>(), null)
            {
                Timestamp = DateTime.UnixEpoch
            };

            await _mxEntity.Handle(message);

            Assert.AreEqual(stateFromDb.MxState, MxState.Evaluated);
            Assert.AreEqual(stateFromDb.HostMxRecords, message.Records);
            Assert.AreEqual(stateFromDb.LastUpdated, message.Timestamp);

            A.CallTo(() => _changeNotifiersComposite.Handle(stateFromDb, message)).MustHaveHappenedOnceExactly();
            A.CallTo(() => _dao.Save(stateFromDb)).MustHaveHappenedOnceExactly();
            A.CallTo(() => _dispatcher.Dispatch(A <MxRecordsUpdated> .That.Matches(a => a.Id == message.Id.ToLower() && a.Records.Count == 0), snsTopicArn)).MustHaveHappenedOnceExactly();
            A.CallTo(() => _dispatcher.Dispatch(A <CreateScheduledReminder> .That.Matches(a => a.ResourceId == domainName.ToLower() && a.Service == "Mx" && a.ScheduledTime.Second == 33), snsTopicArn)).MustHaveHappenedOnceExactly();
        }
        public void DoesNotNotifyWhenNoChanges()
        {
            string testDomain   = "domain";
            string testHostName = "hostname";

            MxEntityState state  = new MxEntityState(testDomain);
            HostMxRecord  record = new HostMxRecord(testHostName, 5, new List <string> {
                "192.168.0.1", "192.168.0.2"
            });

            state.HostMxRecords = new List <HostMxRecord> {
                new HostMxRecord(testHostName, 5, new List <string> {
                    "192.168.0.1"
                })
            };
            List <HostMxRecord> hostMxRecords = new List <HostMxRecord> {
                record
            };
            MxRecordsPolled mxRecordsPolled = new MxRecordsPolled(testDomain, hostMxRecords, TimeSpan.MinValue);

            _recordChangedNotifier.Handle(state, mxRecordsPolled);

            A.CallTo(() => _dispatcher.Dispatch(A <MxRecordAdded> ._, A <string> ._)).MustNotHaveHappened();
            A.CallTo(() => _dispatcher.Dispatch(A <MxRecordRemoved> ._, A <string> ._)).MustNotHaveHappened();
        }
        public void DoesNotNotifyWhenNoChangesWithDifferentCaseType()
        {
            string testDomain = "domain";

            MxEntityState state = new MxEntityState(testDomain)
            {
                HostMxRecords = new List <HostMxRecord>
                {
                    new HostMxRecord("HOSTNAME", 5, new List <string> {
                        "192.168.0.1"
                    })
                }
            };

            List <HostMxRecord> hostMxRecords = new List <HostMxRecord>
            {
                new HostMxRecord("hostname", 5, new List <string> {
                    "192.168.0.1"
                })
            };

            MxRecordsPolled mxRecordsPolled = new MxRecordsPolled(testDomain, hostMxRecords, TimeSpan.MinValue);

            _recordChangedNotifier.Handle(state, mxRecordsPolled);

            A.CallTo(() => _dispatcher.Dispatch(A <MxRecordAdded> ._, A <string> ._)).MustNotHaveHappened();
            A.CallTo(() => _dispatcher.Dispatch(A <MxRecordRemoved> ._, A <string> ._)).MustNotHaveHappened();
        }
 public void Handle(MxEntityState state, Common.Messaging.Abstractions.Message message)
 {
     foreach (IChangeNotifier changeNotifier in _notifiers)
     {
         changeNotifier.Handle(state, message);
     }
 }
        public async Task Handle(DomainDeleted message)
        {
            string domainName = message.Id.ToLower();

            MxEntityState state = await LoadState(domainName, nameof(message));

            if (state.HostMxRecords != null && state.HostMxRecords.Count > 0)
            {
                List <string> uniqueHosts = await _dao.GetHostsUniqueToDomain(domainName);

                if (uniqueHosts.Count > 0)
                {
                    await _dao.DeleteHosts(uniqueHosts);

                    foreach (string host in uniqueHosts)
                    {
                        _dispatcher.Dispatch(new MxHostDeleted(host), _mxEntityConfig.SnsTopicArn);
                        _log.LogInformation($"An MxHostDeleted message for Host: {host} has been dispatched to the SnsTopic: {_mxEntityConfig.SnsTopicArn}");
                    }
                }
            }

            await _dao.Delete(domainName);

            _log.LogInformation($"Deleted MX entity with id: {message.Id}.");
        }
        public async Task Handle(DomainCreated message)
        {
            string domainName = message.Id.ToLower();

            MxEntityState state = await _dao.Get(domainName);

            if (state != null)
            {
                _log.LogInformation($"Ignoring {nameof(DomainCreated)} as MxEntity already exists for {domainName}.");
                return;
            }

            await _dao.Save(new MxEntityState(domainName));

            _log.LogInformation($"Created MxEntity for {domainName}.");

            MxEntityCreated mxEntityCreated = new MxEntityCreated(domainName);

            _dispatcher.Dispatch(mxEntityCreated, _mxEntityConfig.SnsTopicArn);
            _log.LogInformation(
                $"An MxEntityCreated message for Domain: {domainName} has been dispatched to SnsTopic: {_mxEntityConfig.SnsTopicArn}");

            Message createScheduledReminder = new CreateScheduledReminder(Guid.NewGuid().ToString(), "Mx", domainName, _clock.GetDateTimeUtc());

            _dispatcher.Dispatch(createScheduledReminder, _mxEntityConfig.SnsTopicArn);
            _log.LogInformation(
                $"A CreateScheduledReminder message for Domain: {domainName} has been dispatched to SnsTopic: {_mxEntityConfig.SnsTopicArn}");
        }
Example #9
0
        public async Task ShouldDispatchMultipleMxHostDeletedMessagesWhenDomainDeletedReceivedAndMultipleHosts()
        {
            string domainName = "test.gov.uk";
            string hostName1  = "test-host-inbound1.com";
            string hostName2  = "test-host-inbound2.com";


            DomainDeleted message = new DomainDeleted(domainName);

            MxEntityState stateFromDb = new MxEntityState(domainName.ToLower())
            {
                MxState = MxState.Created, HostMxRecords = new List <HostMxRecord>
                {
                    new HostMxRecord(hostName1, 0, new List <string>()),
                    new HostMxRecord(hostName2, 0, new List <string>())
                }
            };

            List <string> uniqueHosts = new List <string> {
                hostName1, hostName2
            };

            A.CallTo(() => _dao.Get(domainName.ToLower())).Returns(Task.FromResult(stateFromDb));
            A.CallTo(() => _dao.GetHostsUniqueToDomain(domainName.ToLower())).Returns(Task.FromResult(uniqueHosts));

            await _mxEntity.Handle(message);

            A.CallTo(() => _dao.DeleteHosts(uniqueHosts)).MustHaveHappenedOnceExactly();
            A.CallTo(() => _dispatcher.Dispatch(A <MxHostDeleted> .That.Matches(a => a.Id == stateFromDb.HostMxRecords[0].Id), A <string> ._)).MustHaveHappenedOnceExactly();
            A.CallTo(() => _dispatcher.Dispatch(A <MxHostDeleted> .That.Matches(a => a.Id == stateFromDb.HostMxRecords[1].Id), A <string> ._)).MustHaveHappenedOnceExactly();
            A.CallTo(() => _dao.Delete(domainName.ToLower())).MustHaveHappenedOnceExactly();
        }
        public DomainTlsEvaluatorResults Create(MxEntityState mxState,
                                                Dictionary <string, TlsEntityState> tlsEntityStates)
        {
            List <MxTlsEvaluatorResults>            mxTlsEvaluatorResults            = new List <MxTlsEvaluatorResults>();
            List <MxTlsCertificateEvaluatorResults> mxTlsCertificateEvaluatorResults =
                new List <MxTlsCertificateEvaluatorResults>();

            foreach (HostMxRecord hostMxRecord in mxState.HostMxRecords)
            {
                // tlsEntityStates keys are always lowercase due to ReverseUrl() in MxApiDao.GetTlsEntityStates
                TlsEntityState tlsEntityState = tlsEntityStates[hostMxRecord.Id.ToLower()];

                List <TlsRecord> records = new List <TlsRecord>();

                TlsRecords tlsRecords = tlsEntityState.TlsRecords;

                if (tlsRecords != null)
                {
                    records.Add(tlsRecords.Tls12AvailableWithBestCipherSuiteSelected);
                    records.Add(tlsRecords.Tls12AvailableWithBestCipherSuiteSelectedFromReverseList);
                    records.Add(tlsRecords.Tls12AvailableWithSha2HashFunctionSelected);
                    records.Add(tlsRecords.Tls12AvailableWithWeakCipherSuiteNotSelected);
                    records.Add(tlsRecords.Tls11AvailableWithBestCipherSuiteSelected);
                    records.Add(tlsRecords.Tls11AvailableWithWeakCipherSuiteNotSelected);
                    records.Add(tlsRecords.Tls10AvailableWithBestCipherSuiteSelected);
                    records.Add(tlsRecords.Tls10AvailableWithWeakCipherSuiteNotSelected);
                    records.Add(tlsRecords.Ssl3FailsWithBadCipherSuite);
                    records.Add(tlsRecords.TlsSecureEllipticCurveSelected);
                    records.Add(tlsRecords.TlsSecureDiffieHellmanGroupSelected);
                    records.Add(tlsRecords.TlsWeakCipherSuitesRejected);
                    records.Add(tlsRecords.Tls12Available);
                    records.Add(tlsRecords.Tls11Available);
                    records.Add(tlsRecords.Tls10Available);
                }

                List <string> warnings = records.Where(_ => _.TlsEvaluatedResult.Result == EvaluatorResult.WARNING)
                                         .Select(_ => _.TlsEvaluatedResult.Description).ToList();
                List <string> failures = records.Where(_ => _.TlsEvaluatedResult.Result == EvaluatorResult.FAIL)
                                         .Select(_ => _.TlsEvaluatedResult.Description).ToList();
                List <string> informational = records
                                              .Where(_ => _.TlsEvaluatedResult.Result == EvaluatorResult.INFORMATIONAL ||
                                                     _.TlsEvaluatedResult.Result == EvaluatorResult.INCONCLUSIVE)
                                              .Select(_ => _.TlsEvaluatedResult.Description).ToList();

                mxTlsEvaluatorResults.Add(
                    new MxTlsEvaluatorResults(
                        hostMxRecord.Id,
                        hostMxRecord.Preference ?? 0,
                        mxState.LastUpdated ?? DateTime.MinValue,
                        warnings,
                        failures,
                        informational));

                mxTlsCertificateEvaluatorResults.Add(CreateMxTlsCertificateEvaluatorResults(hostMxRecord.Id,
                                                                                            hostMxRecord.Preference ?? 0, mxState.LastUpdated ?? DateTime.MinValue, tlsEntityState.CertificateResults));
            }

            return(new DomainTlsEvaluatorResults(mxState.Id, mxState.MxState == MxState.PollPending,
                                                 mxTlsEvaluatorResults, mxTlsCertificateEvaluatorResults));
        }
        public async Task Handle(MxRecordsPolled message)
        {
            string domainName = message.Id.ToLower();

            MxEntityState state = await LoadState(domainName, nameof(message));

            MxState oldState       = state.MxState;
            int     oldRecordCount = state.HostMxRecords?.Count ?? 0;
            int     newRecordCount = message.Records?.Count ?? 0;

            _changeNotifiersComposite.Handle(state, message);

            List <HostMxRecord> validHostRecords = new List <HostMxRecord>();

            if (message.Records != null)
            {
                foreach (HostMxRecord hostRecord in message.Records)
                {
                    if (Uri.CheckHostName(hostRecord.Id) != UriHostNameType.Unknown)
                    {
                        validHostRecords.Add(hostRecord);
                    }
                    else
                    {
                        _log.LogInformation($"Erroneous host: {hostRecord.Id} found for domain: {domainName}");
                    }
                }
            }

            if (message.Error == null)
            {
                state.HostMxRecords = validHostRecords;
            }

            state.LastUpdated = message.Timestamp;
            state.Error       = message.Error;
            state.MxState     = MxState.Evaluated;

            await _dao.Save(state);

            _log.LogInformation($"Updated MxEntity from {oldState} to {MxState.Evaluated} and MX records before: {oldRecordCount} after: {newRecordCount} for {domainName}.");

            Message mxRecordsUpdated = new MxRecordsUpdated(domainName, validHostRecords);

            _dispatcher.Dispatch(mxRecordsUpdated, _mxEntityConfig.SnsTopicArn);
            _log.LogInformation($"An MxRecordsUpdated message for Domain: {domainName} has been dispatched to SnsTopic: {_mxEntityConfig.SnsTopicArn}");

            // Should probably change this so it only happens for a new host
            validHostRecords?.ForEach(mxRecord => {
                _dispatcher.Dispatch(new MxHostTestPending(mxRecord.Id), _mxEntityConfig.SnsTopicArn);
                _log.LogInformation($"An MxHostTestPending message for Host: {mxRecord.Id} has been dispatched to SnsTopic: {_mxEntityConfig.SnsTopicArn}");
            });

            Message createScheduledReminder = new CreateScheduledReminder(Guid.NewGuid().ToString(), "Mx", domainName, _clock.GetDateTimeUtc().AddSeconds(_mxEntityConfig.NextScheduledInSeconds));

            _dispatcher.Dispatch(createScheduledReminder, _mxEntityConfig.SnsTopicArn);
            _log.LogInformation($"A CreateScheduledReminder message for Domain: {domainName} has been dispatched to SnsTopic: {_mxEntityConfig.SnsTopicArn}");
        }
        private async Task <MxEntityState> LoadState(string domainName, string messageType)
        {
            MxEntityState state = await _dao.Get(domainName);

            if (state == null)
            {
                _log.LogError("Ignoring {EventName} as MX Entity does not exist for {Id}.", messageType, domainName);
                throw new MailCheckException(
                          $"Cannot handle event {messageType} as MX Entity doesnt exists for {domainName}.");
            }

            return(state);
        }
Example #13
0
        public async Task MissingHostMxRecordsReturnPending()
        {
            MxEntityState             mxStateFromDao = new MxEntityState("");
            DomainTlsEvaluatorResults pendingEvaluatorResultFromFactory = new DomainTlsEvaluatorResults("", true);

            A.CallTo(() => _mxApiDao.GetMxEntityState("testDomain")).Returns(mxStateFromDao);
            A.CallTo(() => _domainTlsEvaluatorResultsFactory.CreatePending("testDomain")).Returns(pendingEvaluatorResultFromFactory);

            DomainTlsEvaluatorResults result = await _mxService.GetDomainTlsEvaluatorResults("testDomain");

            A.CallTo(() => _messagePublisher.Publish(A <DomainMissing> ._, A <string> ._)).MustNotHaveHappened();
            Assert.AreSame(pendingEvaluatorResultFromFactory, result);
        }
        public async Task Handle(MxScheduledReminder message)
        {
            string        domainName = message.ResourceId.ToLower();
            MxEntityState state      = await LoadState(domainName, nameof(message));

            await _dao.UpdateState(domainName, MxState.PollPending);

            _log.LogInformation($"Updated MxEntity.MxState from {state.MxState} to {MxState.PollPending} for {domainName}.");

            Message mxPollPending = new MxPollPending(domainName);

            _dispatcher.Dispatch(mxPollPending, _mxEntityConfig.SnsTopicArn);
            _log.LogInformation(
                $"An MxPollPending message for Domain: {domainName} has been dispatched to SnsTopic: {_mxEntityConfig.SnsTopicArn}");
        }
        public async Task MxLookupFailed_EnsureSqlAndParametersBuiltCorrectly()
        {
            var fakeConnectionInfo = A.Fake <IConnectionInfoAsync>();

            string capturedSql = null;
            IDictionary <string, object> capturedParameters = null;

            var fakeLogger        = A.Fake <ILogger <MxEntityDao> >();
            var fakeSaveOperation = A.Fake <Func <string, IDictionary <string, object>, Task <int> > >();

            A.CallTo(() => fakeSaveOperation.Invoke(A <string> .Ignored, A <IDictionary <string, object> > .Ignored))
            .Invokes((string sql, IDictionary <string, object> parameters) =>
            {
                capturedSql        = sql;
                capturedParameters = parameters;
            })
            .Returns(Task.FromResult(10));

            var dao = new MxEntityDao(
                fakeConnectionInfo,
                fakeLogger,
                fakeSaveOperation
                );

            var mxEntityState = new MxEntityState("google.com")
            {
                MxState       = MxState.Created,
                LastUpdated   = new DateTime(2020, 3, 4, 5, 6, 7),
                Error         = new Message(Guid.Empty, "DNS", MessageType.error, "DNS lookup failed", "DNS lookup failed"),
                HostMxRecords = null
            };

            var expectedParameters = new Dictionary <string, object>
            {
                ["domain"]      = "com.google",
                ["mxState"]     = MxState.Created,
                ["error"]       = @"{""Id"":""00000000-0000-0000-0000-000000000000"",""Source"":""DNS"",""MessageType"":2,""Text"":""DNS lookup failed"",""MarkDown"":""DNS lookup failed"",""MessageDisplay"":0}",
                ["lastUpdated"] = new DateTime(2020, 3, 4, 5, 6, 7)
            };

            await dao.Save(mxEntityState);

            A.CallTo(() => fakeSaveOperation(A <string> .Ignored, A <IDictionary <string, object> > .Ignored))
            .MustHaveHappenedOnceExactly();

            Assert.That(capturedSql, Is.EqualTo(ExpectedDnsErrorSql));
            Assert.That(capturedParameters, Is.EquivalentTo(expectedParameters));
        }
        public async Task NoMxRecords_EnsureSqlAndParametersBuiltCorrectly()
        {
            var fakeConnectionInfo = A.Fake <IConnectionInfoAsync>();

            string capturedSql = null;
            IDictionary <string, object> capturedParameters = null;

            var fakeLogger        = A.Fake <ILogger <MxEntityDao> >();
            var fakeSaveOperation = A.Fake <Func <string, IDictionary <string, object>, Task <int> > >();

            A.CallTo(() => fakeSaveOperation.Invoke(A <string> .Ignored, A <IDictionary <string, object> > .Ignored))
            .Invokes((string sql, IDictionary <string, object> parameters) =>
            {
                capturedSql        = sql;
                capturedParameters = parameters;
            })
            .Returns(Task.FromResult(10));

            var dao = new MxEntityDao(
                fakeConnectionInfo,
                fakeLogger,
                fakeSaveOperation
                );

            var mxEntityState = new MxEntityState("google.com")
            {
                MxState       = MxState.Created,
                LastUpdated   = new DateTime(2020, 3, 4, 5, 6, 7),
                HostMxRecords = new List <Contracts.Poller.HostMxRecord>()
            };

            var expectedParameters = new Dictionary <string, object>
            {
                ["domain"]      = "com.google",
                ["mxState"]     = MxState.Created,
                ["error"]       = "null",
                ["lastUpdated"] = new DateTime(2020, 3, 4, 5, 6, 7)
            };

            await dao.Save(mxEntityState);

            A.CallTo(() => fakeSaveOperation(A <string> .Ignored, A <IDictionary <string, object> > .Ignored))
            .MustHaveHappenedOnceExactly();

            Assert.That(capturedSql, Is.EqualTo(ExpectedNoMxSql));
            Assert.That(capturedParameters, Is.EquivalentTo(expectedParameters));
        }
Example #17
0
        public async Task ShouldOmitErroneousHostsFound()
        {
            string domainName = "test.gov.uk";
            string hostName1  = "test-host-inbound1 .com";
            string hostName2  = "test-host-inbound2.com";


            string snsTopicArn = "SnsTopicArn";

            A.CallTo(() => _mxEntityConfig.SnsTopicArn).Returns(snsTopicArn);

            List <HostMxRecord> hostMxRecords = new List <HostMxRecord>
            {
                new HostMxRecord(hostName1, 0, new List <string>()),
                new HostMxRecord(hostName2, 0, new List <string>()),
            };

            MxEntityState stateFromDb = new MxEntityState(domainName.ToLower())
            {
                MxState       = MxState.Created,
                HostMxRecords = hostMxRecords,
            };

            A.CallTo(() => _dao.Get(domainName.ToLower())).Returns(Task.FromResult(stateFromDb));


            A.CallTo(() => _mxEntityConfig.NextScheduledInSeconds).Returns(33);
            A.CallTo(() => _clock.GetDateTimeUtc()).Returns(DateTime.MinValue);

            MxRecordsPolled message = new MxRecordsPolled(domainName, hostMxRecords, null)
            {
                Timestamp = DateTime.UnixEpoch
            };
            List <HostMxRecord> validRecords = new List <HostMxRecord> {
                new HostMxRecord(hostName2, 0, new List <string>())
            };
            await _mxEntity.Handle(message);

            Assert.AreEqual(stateFromDb.MxState, MxState.Evaluated);
            Assert.AreEqual(stateFromDb.HostMxRecords[0], message.Records[1]); // Make sure erroneous host is ommitted
            Assert.AreEqual(stateFromDb.LastUpdated, message.Timestamp);

            A.CallTo(() => _changeNotifiersComposite.Handle(stateFromDb, message)).MustHaveHappenedOnceExactly();
            A.CallTo(() => _dao.Save(stateFromDb)).MustHaveHappenedOnceExactly();
            A.CallTo(() => _dispatcher.Dispatch(A <MxHostTestPending> .That.Matches(a => a.Id == hostName1.ToLower()), snsTopicArn)).MustNotHaveHappened();
            A.CallTo(() => _dispatcher.Dispatch(A <MxHostTestPending> .That.Matches(a => a.Id == hostName2.ToLower()), snsTopicArn)).MustHaveHappenedOnceExactly();
        }
Example #18
0
        public async Task Save(MxEntityState mxEntityState)
        {
            string domain = ReverseUrl(mxEntityState.Id);

            var parameters = new Dictionary <string, object>();

            parameters.Add("domain", domain);
            parameters.Add("mxState", mxEntityState.MxState);
            parameters.Add("error", JsonConvert.SerializeObject(mxEntityState.Error));
            parameters.Add("lastUpdated", mxEntityState.LastUpdated);

            var    builder        = new SqlBuilder();
            string updateMxTables = String.Empty;

            if (mxEntityState.HostMxRecords != null)
            {
                updateMxTables = MxStateDaoResources.DeleteMxRecord;

                if (mxEntityState.HostMxRecords.Count > 0)
                {
                    updateMxTables += MxStateDaoResources.UpsertMxHost + MxStateDaoResources.UpsertMxRecord;

                    builder.SetToken("MxHostValues", MxHostFields.ToValuesParameterListSql(mxEntityState.HostMxRecords.Count));
                    builder.SetToken("MxRecordValues", MxRecordFields.ToValuesParameterListSql(mxEntityState.HostMxRecords.Count));

                    mxEntityState.HostMxRecords.Select((hostMxRecord, index) =>
                    {
                        string host = ReverseUrl(hostMxRecord.Id);

                        parameters.Add($"domain_{index}", domain);
                        parameters.Add($"hostname_{index}", host);
                        parameters.Add($"hostMxRecord_{index}", JsonConvert.SerializeObject(hostMxRecord));
                        parameters.Add($"lastUpdated_{index}", mxEntityState.LastUpdated);
                        parameters.Add($"preference_{index}", hostMxRecord.Preference);

                        return(index);
                    }).ToArray();
                }
            }

            var commandText = builder.Build($"{MxStateDaoResources.UpsertDomain}{updateMxTables}");

            await _saveOperation(commandText, parameters);
        }
        public void Handle(MxEntityState state, Message message)
        {
            string domainName = message.Id;

            if (message is MxRecordsPolled mxRecordsPolled)
            {
                List <HostMxRecord> recordsInMessage = mxRecordsPolled.Records ?? new List <HostMxRecord>();
                List <HostMxRecord> recordsInState   = state.HostMxRecords ?? new List <HostMxRecord>();

                List <HostMxRecord> added   = recordsInMessage.Except(recordsInState, _comparer).ToList();
                List <HostMxRecord> removed = recordsInState.Except(recordsInMessage, _comparer).ToList();

                bool hasAddedRecords   = added.Any();
                bool hasRemovedRecords = removed.Any();

                if (hasAddedRecords)
                {
                    MxRecordAdded mxRecordAdded = new MxRecordAdded(domainName, added.ToList());
                    _dispatcher.Dispatch(mxRecordAdded, _mxEntityConfig.SnsTopicArn);
                }

                if (hasRemovedRecords)
                {
                    MxRecordRemoved mxRecordRemoved = new MxRecordRemoved(domainName, removed.ToList());
                    _dispatcher.Dispatch(mxRecordRemoved, _mxEntityConfig.SnsTopicArn);
                }

                if (hasAddedRecords || hasRemovedRecords)
                {
                    _logger.LogInformation($"recordsInMessage: {JsonConvert.SerializeObject(recordsInMessage)} for domain: {domainName}");
                    _logger.LogInformation($"recordsInState: {JsonConvert.SerializeObject(recordsInState)} for domain: {domainName}");

                    if (hasAddedRecords)
                    {
                        _logger.LogInformation($"added records: {JsonConvert.SerializeObject(added)} for domain: {domainName}");
                    }

                    if (hasRemovedRecords)
                    {
                        _logger.LogInformation($"removed records: {JsonConvert.SerializeObject(removed)} for domain: {domainName}");
                    }
                }
            }
        }
Example #20
0
        public async Task ShouldDeleteWhenDomainDeletedReceived()
        {
            string domainName = "testDomainName";
            string hostName   = "testHostName";

            DomainDeleted message = new DomainDeleted(domainName);

            MxEntityState stateFromDb = new MxEntityState(domainName.ToLower())
            {
                MxState = MxState.Created, HostMxRecords = new List <HostMxRecord>
                {
                    new HostMxRecord(hostName, 0, new List <string>())
                }
            };

            A.CallTo(() => _dao.Get(domainName.ToLower())).Returns(Task.FromResult(stateFromDb));
            await _mxEntity.Handle(message);

            A.CallTo(() => _dao.Delete(domainName.ToLower())).MustHaveHappenedOnceExactly();
        }
Example #21
0
        public async Task ShouldDispatchPollPendingWhenScheduledReminderReceived()
        {
            string        domainName  = "testDomainName";
            string        snsTopicArn = "SnsTopicArn";
            MxEntityState stateFromDb = new MxEntityState(domainName.ToLower())
            {
                MxState = MxState.Created
            };

            string expectedDomainName = "testdomainname";

            A.CallTo(() => _dao.Get(expectedDomainName)).Returns(stateFromDb);
            A.CallTo(() => _mxEntityConfig.SnsTopicArn).Returns(snsTopicArn);

            await _mxEntity.Handle(new MxScheduledReminder(Guid.NewGuid().ToString(), domainName));

            A.CallTo(() => _dao.UpdateState(expectedDomainName, MxState.PollPending)).MustHaveHappenedOnceExactly();
            A.CallTo(() => _dispatcher.Dispatch(A <MxPollPending> .That.Matches(entity => entity.Id == domainName.ToLower()),
                                                snsTopicArn)).MustHaveHappenedOnceExactly();
        }
Example #22
0
        public async Task EvaluatorResultsAreReturned()
        {
            MxEntityState mxStateFromDao = new MxEntityState("")
            {
                HostMxRecords = new List <HostMxRecord> {
                    new HostMxRecord("testHost1", 0, null)
                }
            };

            Dictionary <string, TlsEntityState> tlsEntityStatesFromDao     = new Dictionary <string, TlsEntityState>();
            DomainTlsEvaluatorResults           evaluatorResultFromFactory = new DomainTlsEvaluatorResults("", false);

            A.CallTo(() => _mxApiDao.GetMxEntityState("testDomain")).Returns(mxStateFromDao);
            A.CallTo(() => _mxApiDao.GetTlsEntityStates(A <List <string> > ._)).Returns(tlsEntityStatesFromDao);
            A.CallTo(() => _domainTlsEvaluatorResultsFactory.Create(mxStateFromDao, tlsEntityStatesFromDao)).Returns(evaluatorResultFromFactory);

            DomainTlsEvaluatorResults result = await _mxService.GetDomainTlsEvaluatorResults("testDomain");

            A.CallTo(() => _messagePublisher.Publish(A <DomainMissing> ._, A <string> ._)).MustNotHaveHappened();
            Assert.AreSame(evaluatorResultFromFactory, result);
        }
        public void NotifiesWhenRecordAddedAndRemoved()
        {
            string testDomain    = "domain";
            string testHostName1 = "hostname1";
            string testHostName2 = "hostname2";


            MxEntityState state   = new MxEntityState(testDomain);
            HostMxRecord  record1 = new HostMxRecord(testHostName1, 5, new List <string> {
                "192.168.0.1"
            });
            HostMxRecord record2 = new HostMxRecord(testHostName2, 5, new List <string> {
                "192.168.0.1"
            });

            state.HostMxRecords = new List <HostMxRecord> {
                record1
            };
            List <HostMxRecord> hostMxRecords = new List <HostMxRecord> {
                record2
            };
            MxRecordsPolled mxRecordsPolled = new MxRecordsPolled(testDomain, hostMxRecords, TimeSpan.MinValue);

            _recordChangedNotifier.Handle(state, mxRecordsPolled);

            A.CallTo(() => _dispatcher.Dispatch(A <MxRecordRemoved> ._, A <string> ._))
            .MustHaveHappenedOnceExactly();
            A.CallTo(() =>
                     _dispatcher.Dispatch(
                         A <MxRecordRemoved> .That.Matches(x => x.Id == testDomain && x.Records[0].Id == testHostName1),
                         A <string> ._)).MustHaveHappenedOnceExactly();

            A.CallTo(() => _dispatcher.Dispatch(A <MxRecordAdded> ._, A <string> ._))
            .MustHaveHappenedOnceExactly();
            A.CallTo(() =>
                     _dispatcher.Dispatch(
                         A <MxRecordAdded> .That.Matches(x => x.Id == testDomain && x.Records[0].Id == testHostName2),
                         A <string> ._)).MustHaveHappenedOnceExactly();
        }
        public void NotifiesWhenMxPreferenceChanges()
        {
            string testDomain   = "domain";
            string testHostName = "hostname";

            MxEntityState state = new MxEntityState(testDomain);

            state.HostMxRecords = new List <HostMxRecord>
            {
                new HostMxRecord(testHostName, 5, new List <string> {
                    "192.168.0.1"
                })
            };

            List <HostMxRecord> hostMxRecords = new List <HostMxRecord>
            {
                new HostMxRecord(testHostName, 10, new List <string> {
                    "192.168.0.1"
                }),
            };

            MxRecordsPolled mxRecordsPolled = new MxRecordsPolled(testDomain, hostMxRecords, TimeSpan.MinValue);

            _recordChangedNotifier.Handle(state, mxRecordsPolled);

            A.CallTo(() => _dispatcher.Dispatch(A <MxRecordAdded> ._, A <string> ._))
            .MustHaveHappenedOnceExactly();
            A.CallTo(() =>
                     _dispatcher.Dispatch(A <MxRecordAdded> .That.Matches(x => x.Id == testDomain), A <string> ._))
            .MustHaveHappenedOnceExactly();

            A.CallTo(() => _dispatcher.Dispatch(A <MxRecordRemoved> ._, A <string> ._))
            .MustHaveHappenedOnceExactly();
            A.CallTo(() =>
                     _dispatcher.Dispatch(A <MxRecordRemoved> .That.Matches(x => x.Id == testDomain), A <string> ._))
            .MustHaveHappenedOnceExactly();
        }
        public void CaseMismatchDoesNotBreakResultsConstruction()
        {
            _domainTlsEvaluatorResultsFactory = new DomainTlsEvaluatorResultsFactory();

            MxEntityState mxState = new MxEntityState("exampledomain.co.uk");

            mxState.HostMxRecords = new List <HostMxRecord>()
            {
                new HostMxRecord("MAILHOST.GOOGLE.COM.", 1, new List <string> {
                    ""
                })
            };

            Dictionary <string, TlsEntityState> tlsEntityStates = new Dictionary <string, TlsEntityState>
            {
                ["mailhost.google.com."] = new TlsEntityState("Mailhost.google.com")
            };

            DomainTlsEvaluatorResults domainTlsEvaluatorResults = _domainTlsEvaluatorResultsFactory.Create(mxState, tlsEntityStates);

            Assert.AreEqual("exampledomain.co.uk", domainTlsEvaluatorResults.Id);
            Assert.AreEqual(1, domainTlsEvaluatorResults.MxTlsEvaluatorResults.Count);
            Assert.AreEqual("mailhost.google.com.", domainTlsEvaluatorResults.MxTlsEvaluatorResults[0].Hostname.ToLower());
        }
        public async Task SomeMxRecords_EnsureSqlAndParametersBuiltCorrectly()
        {
            var fakeConnectionInfo = A.Fake <IConnectionInfoAsync>();

            string capturedSql = null;
            IDictionary <string, object> capturedParameters = null;

            var fakeLogger        = A.Fake <ILogger <MxEntityDao> >();
            var fakeSaveOperation = A.Fake <Func <string, IDictionary <string, object>, Task <int> > >();

            A.CallTo(() => fakeSaveOperation.Invoke(A <string> .Ignored, A <IDictionary <string, object> > .Ignored))
            .Invokes((string sql, IDictionary <string, object> parameters) =>
            {
                capturedSql        = sql;
                capturedParameters = parameters;
            })
            .Returns(Task.FromResult(10));

            var dao = new MxEntityDao(
                fakeConnectionInfo,
                fakeLogger,
                fakeSaveOperation
                );

            var mxEntityState = new MxEntityState("google.com")
            {
                MxState       = MxState.Created,
                LastUpdated   = new DateTime(2020, 3, 4, 5, 6, 7),
                HostMxRecords = new List <Contracts.Poller.HostMxRecord>
                {
                    new Contracts.Poller.HostMxRecord("mx1.com.", 1, new List <string> {
                        "1.1.1.1"
                    }),
                    new Contracts.Poller.HostMxRecord("mx2.com.", 2, new List <string> {
                        "1.1.2.1", "1.1.2.2"
                    }),
                    new Contracts.Poller.HostMxRecord("mx3.com.", 3, new List <string> {
                        "1.1.3.1", "1.1.3.2", "1.1.3.3"
                    })
                }
            };

            var expectedParameters = new Dictionary <string, object>
            {
                ["domain"]         = "com.google",
                ["mxState"]        = MxState.Created,
                ["error"]          = "null",
                ["lastUpdated"]    = new DateTime(2020, 3, 4, 5, 6, 7),
                ["domain_0"]       = "com.google",
                ["hostname_0"]     = ".com.mx1",
                ["hostMxRecord_0"] = @"{""Preference"":1,""IpAddresses"":[""1.1.1.1""],""Id"":""mx1.com."",""CorrelationId"":null,""CausationId"":null,""Type"":null,""MessageId"":null,""Timestamp"":""0001-01-01T00:00:00""}",
                ["lastUpdated_0"]  = new DateTime(2020, 3, 4, 5, 6, 7),
                ["preference_0"]   = 1,
                ["domain_1"]       = "com.google",
                ["hostname_1"]     = ".com.mx2",
                ["hostMxRecord_1"] = @"{""Preference"":2,""IpAddresses"":[""1.1.2.1"",""1.1.2.2""],""Id"":""mx2.com."",""CorrelationId"":null,""CausationId"":null,""Type"":null,""MessageId"":null,""Timestamp"":""0001-01-01T00:00:00""}",
                ["lastUpdated_1"]  = new DateTime(2020, 3, 4, 5, 6, 7),
                ["preference_1"]   = 2,
                ["domain_2"]       = "com.google",
                ["hostname_2"]     = ".com.mx3",
                ["hostMxRecord_2"] = @"{""Preference"":3,""IpAddresses"":[""1.1.3.1"",""1.1.3.2"",""1.1.3.3""],""Id"":""mx3.com."",""CorrelationId"":null,""CausationId"":null,""Type"":null,""MessageId"":null,""Timestamp"":""0001-01-01T00:00:00""}",
                ["lastUpdated_2"]  = new DateTime(2020, 3, 4, 5, 6, 7),
                ["preference_2"]   = 3
            };

            await dao.Save(mxEntityState);

            A.CallTo(() => fakeSaveOperation(A <string> .Ignored, A <IDictionary <string, object> > .Ignored))
            .MustHaveHappenedOnceExactly();

            Assert.That(capturedSql, Is.EqualTo(ExpectedMxPresentSql));
            Assert.That(capturedParameters, Is.EquivalentTo(expectedParameters));
        }