diff --git a/app/interactions/domains/force_delete/base.rb b/app/interactions/domains/force_delete/base.rb index d4ad2b8202..712d0c08e5 100644 --- a/app/interactions/domains/force_delete/base.rb +++ b/app/interactions/domains/force_delete/base.rb @@ -13,6 +13,9 @@ class Base < ActiveInteraction::Base string :reason, default: nil, description: 'Which mail template to use explicitly' + string :email, + default: nil, + description: 'Possible invalid email to notify on' validates :type, inclusion: { in: %i[fast_track soft] } end diff --git a/app/interactions/domains/force_delete/notify_registrar.rb b/app/interactions/domains/force_delete/notify_registrar.rb index 522502640b..e4aa48976a 100644 --- a/app/interactions/domains/force_delete/notify_registrar.rb +++ b/app/interactions/domains/force_delete/notify_registrar.rb @@ -2,11 +2,23 @@ module Domains module ForceDelete class NotifyRegistrar < Base def execute + email.present? ? notify_with_email : notify_without_email + end + + def notify_without_email domain.registrar.notifications.create!(text: I18n.t('force_delete_set_on_domain', domain_name: domain.name, outzone_date: domain.outzone_date, purge_date: domain.purge_date)) end + + def notify_with_email + domain.registrar.notifications.create!(text: I18n.t('force_delete_auto_email', + domain_name: domain.name, + outzone_date: domain.outzone_date, + purge_date: domain.purge_date, + email: email)) + end end end end diff --git a/app/interactions/domains/force_delete_email/base.rb b/app/interactions/domains/force_delete_email/base.rb new file mode 100644 index 0000000000..40339691b8 --- /dev/null +++ b/app/interactions/domains/force_delete_email/base.rb @@ -0,0 +1,27 @@ +module Domains + module ForceDeleteEmail + class Base < ActiveInteraction::Base + string :email, + description: 'Bounced email to set ForceDelete from' + + def execute + domain_contacts = Contact.where(email: email).map(&:domain_contacts).flatten + registrant_ids = Registrant.where(email: email).pluck(:id) + + domains = domain_contacts.map(&:domain).flatten + + Domain.where(registrant_id: registrant_ids) + + domains.each { |domain| process_force_delete(domain) unless domain.force_delete_scheduled? } + end + + private + + def process_force_delete(domain) + domain.schedule_force_delete(type: :soft, + notify_by_email: true, + reason: 'invalid_email', + email: email) + end + end + end +end diff --git a/app/mailers/domain_expire_mailer.rb b/app/mailers/domain_expire_mailer.rb index 229120825d..958e8c1c16 100644 --- a/app/mailers/domain_expire_mailer.rb +++ b/app/mailers/domain_expire_mailer.rb @@ -43,8 +43,11 @@ def registrar_presenter(registrar:) # Needed because there are invalid emails in the database, which have been imported from legacy app def filter_invalid_emails(emails:, domain:) - emails.select do |email| - valid = EmailValidator.new(email).valid? + old_validation_type = Truemail.configure.default_validation_type + Truemail.configure.default_validation_type = :regex + + results = emails.select do |email| + valid = Truemail.valid?(email) unless valid logger.info("Unable to send DomainExpireMailer#expired email for domain #{domain.name} (##{domain.id})" \ @@ -53,5 +56,7 @@ def filter_invalid_emails(emails:, domain:) valid end + Truemail.configure.default_validation_type = old_validation_type + results end end diff --git a/app/models/bounced_mail_address.rb b/app/models/bounced_mail_address.rb index db44138295..376e16d350 100644 --- a/app/models/bounced_mail_address.rb +++ b/app/models/bounced_mail_address.rb @@ -1,6 +1,7 @@ class BouncedMailAddress < ApplicationRecord validates :email, :message_id, :bounce_type, :bounce_subtype, :action, :status, presence: true after_destroy :destroy_aws_suppression + after_create :force_delete_from_bounce def bounce_reason "#{action} (#{status} #{diagnostic})" @@ -42,4 +43,8 @@ def self.ses_configured? rescue Aws::Errors::MissingRegionError false end + + def force_delete_from_bounce + Domains::ForceDeleteEmail::Base.run(email: email) + end end diff --git a/app/models/concerns/domain/force_delete.rb b/app/models/concerns/domain/force_delete.rb index ca13eb5d12..bc89022d0a 100644 --- a/app/models/concerns/domain/force_delete.rb +++ b/app/models/concerns/domain/force_delete.rb @@ -45,9 +45,9 @@ def force_delete_scheduled? statuses.include?(DomainStatus::FORCE_DELETE) end - def schedule_force_delete(type: :fast_track, notify_by_email: false, reason: nil) + def schedule_force_delete(type: :fast_track, notify_by_email: false, reason: nil, email: nil) Domains::ForceDelete::SetForceDelete.run(domain: self, type: type, reason: reason, - notify_by_email: notify_by_email) + notify_by_email: notify_by_email, email: email) end def cancel_force_delete diff --git a/app/models/email_address_verification.rb b/app/models/email_address_verification.rb index 2fe7c0dbe6..c54cd42240 100644 --- a/app/models/email_address_verification.rb +++ b/app/models/email_address_verification.rb @@ -1,5 +1,6 @@ class EmailAddressVerification < ApplicationRecord RECENTLY_VERIFIED_PERIOD = 1.month + after_save :check_force_delete scope :not_verified_recently, lambda { where('verified_at IS NULL or verified_at < ?', verification_period) @@ -40,6 +41,12 @@ def verified? success end + def check_force_delete + return unless failed? + + Domains::ForceDeleteEmail::Base.run(email: email) + end + def verify validation_request = Truemail.validate(email) diff --git a/app/presenters/domain_presenter.rb b/app/presenters/domain_presenter.rb index bcbd5d6001..6ded9a30b8 100644 --- a/app/presenters/domain_presenter.rb +++ b/app/presenters/domain_presenter.rb @@ -1,5 +1,5 @@ class DomainPresenter - delegate :name, :transfer_code, :registrant, :registrant_id, to: :domain + delegate :name, :transfer_code, :registrant, :registrant_id, :id, to: :domain def initialize(domain:, view:) @domain = domain diff --git a/config/locales/en.yml b/config/locales/en.yml index 22f29a6e32..97c968996e 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -626,6 +626,7 @@ en: created_at_until: 'Created at until' is_registrant: 'Is registrant' force_delete_set_on_domain: 'Force delete set on domain %{domain_name}. Outzone date: %{outzone_date}. Purge date: %{purge_date}' + force_delete_auto_email: 'Force delete set on domain %{domain_name}. Outzone date: %{outzone_date}. Purge date: %{purge_date}. Invalid email: %{email}' grace_period_started_domain: 'For domain %{domain_name} started 45-days redemption grace period, ForceDelete will be in effect from %{date}' force_delete_cancelled: 'Force delete is cancelled on domain %{domain_name}' contact_is_not_valid: 'Contact %{value} is not valid, please fix the invalid contact' diff --git a/lib/tasks/email_bounce_test.rake b/lib/tasks/email_bounce_test.rake new file mode 100644 index 0000000000..e02e260fa5 --- /dev/null +++ b/lib/tasks/email_bounce_test.rake @@ -0,0 +1,14 @@ +namespace :email_bounce do + desc 'Creates a dummy email bounce by email address' + task :create_test, [:email] => [:environment] do |_t, args| + bounced_mail = BouncedMailAddress.new + bounced_mail.email = args[:email] + bounced_mail.message_id = '010f0174a0c7d348-ea6e2fc1-0854-4073-b71f-5cecf9b0d0b2-000000' + bounced_mail.bounce_type = 'Permanent' + bounced_mail.bounce_subtype = 'General' + bounced_mail.action = 'failed' + bounced_mail.status = '5.1.1' + bounced_mail.diagnostic = 'smtp; 550 5.1.1 user unknown' + bounced_mail.save! + end +end diff --git a/lib/validators/email_validator.rb b/lib/validators/email_validator.rb deleted file mode 100644 index 60b1b3ce16..0000000000 --- a/lib/validators/email_validator.rb +++ /dev/null @@ -1,15 +0,0 @@ -class EmailValidator - def self.regexp - Devise::email_regexp - end - - def initialize(email) - @email = email - end - - def valid? - email =~ self.class.regexp - end - - attr_reader :email -end diff --git a/test/mailers/domain_expire_mailer_test.rb b/test/mailers/domain_expire_mailer_test.rb index 84e520b787..c36c3d80ed 100644 --- a/test/mailers/domain_expire_mailer_test.rb +++ b/test/mailers/domain_expire_mailer_test.rb @@ -22,4 +22,29 @@ def test_delivers_domain_expiration_soft_email assert_equal I18n.t("domain_expire_mailer.expired_soft.subject", domain_name: domain.name), email.subject end + + def test_delivers_domain_expiration_soft_email_if_auto_fd + domain = domains(:shop) + assert_not domain.force_delete_scheduled? + travel_to Time.zone.parse('2010-07-05') + email = 'some@strangesentence@internet.ee' + + Truemail.configure.default_validation_type = :regex + + contact = domain.admin_contacts.first + contact.update_attribute(:email, email) + contact.email_verification.verify + + assert contact.email_verification_failed? + + domain.reload + + assert domain.force_delete_scheduled? + + email = DomainExpireMailer.expired_soft(domain: domain, registrar: domain.registrar).deliver_now + + assert_emails 1 + assert_equal I18n.t("domain_expire_mailer.expired_soft.subject", domain_name: domain.name), + email.subject + end end diff --git a/test/models/bounced_mail_address_test.rb b/test/models/bounced_mail_address_test.rb index 4af4017119..a7288cd01a 100644 --- a/test/models/bounced_mail_address_test.rb +++ b/test/models/bounced_mail_address_test.rb @@ -12,6 +12,52 @@ def setup @bounced_mail.action = 'failed' @bounced_mail.status = '5.1.1' @bounced_mail.diagnostic = 'smtp; 550 5.1.1 user unknown' + + @contact_email = "john@inbox.test" + end + + def test_soft_force_delete_related_domains + domain_contacts = Contact.where(email: @contact_email).map(&:domain_contacts).flatten + + domain_contacts.each do |domain_contact| + domain_contact.domain.update(valid_to: Time.zone.now + 5.years) + assert_not domain_contact.domain.statuses.include? DomainStatus::FORCE_DELETE + assert_not domain_contact.domain.statuses.include? DomainStatus::SERVER_RENEW_PROHIBITED + assert_not domain_contact.domain.statuses.include? DomainStatus::SERVER_TRANSFER_PROHIBITED + end + + @bounced_mail.email = @contact_email + @bounced_mail.save + + domain_contacts.each do |domain_contact| + domain_contact.reload + assert_equal 'soft', domain_contact.domain.force_delete_type + assert domain_contact.domain.force_delete_scheduled? + assert domain_contact.domain.statuses.include? DomainStatus::FORCE_DELETE + assert domain_contact.domain.statuses.include? DomainStatus::SERVER_RENEW_PROHIBITED + assert domain_contact.domain.statuses.include? DomainStatus::SERVER_TRANSFER_PROHIBITED + end + end + + def test_soft_force_delete_if_domain_has_force_delete_status + domain_contacts = Contact.where(email: @contact_email).map(&:domain_contacts).flatten + perform_enqueued_jobs do + domain_contacts.each do |domain_contact| + domain_contact.domain.update(valid_to: Time.zone.now + 5.years) + domain_contact.domain.schedule_force_delete(type: :soft, notify_by_email: false, reason: 'test') + end + end + force_delete_date = domain_contacts.map(&:domain).each.pluck(:force_delete_date).sample + assert_not_nil force_delete_date + + @bounced_mail.email = @contact_email + @bounced_mail.save + + domain_contacts.all? do |domain_contact| + assert_equal force_delete_date, domain_contact.domain.force_delete_date + assert_equal 'soft', domain_contact.domain.force_delete_type + assert domain_contact.domain.force_delete_scheduled? + end end def test_email_is_required diff --git a/test/models/domain/force_delete_test.rb b/test/models/domain/force_delete_test.rb index 19338f495a..837ad3adca 100644 --- a/test/models/domain/force_delete_test.rb +++ b/test/models/domain/force_delete_test.rb @@ -5,6 +5,11 @@ class ForceDeleteTest < ActionMailer::TestCase @domain = domains(:shop) Setting.redemption_grace_period = 30 ActionMailer::Base.deliveries.clear + @old_validation_type = Truemail.configure.default_validation_type + end + + teardown do + Truemail.configure.default_validation_type = @old_validation_type end def test_schedules_force_delete_fast_track @@ -315,4 +320,79 @@ def test_force_delete_does_not_affect_registrant_update_confirmable assert @domain.force_delete_scheduled? assert @domain.registrant_update_confirmable?(@domain.registrant_verification_token) end + + def test_schedules_force_delete_after_bounce + @domain.update(valid_to: Time.zone.parse('2012-08-05')) + assert_not @domain.force_delete_scheduled? + travel_to Time.zone.parse('2010-07-05') + email = @domain.admin_contacts.first.email + asserted_text = "Invalid email: #{email}" + + prepare_bounced_email_address(email) + + @domain.reload + + assert @domain.force_delete_scheduled? + assert_equal 'invalid_email', @domain.template_name + assert_equal Date.parse('2010-09-19'), @domain.force_delete_date.to_date + assert_equal Date.parse('2010-08-05'), @domain.force_delete_start.to_date + notification = @domain.registrar.notifications.last + assert notification.text.include? asserted_text + end + + def test_schedules_force_delete_after_registrant_bounce + @domain.update(valid_to: Time.zone.parse('2012-08-05')) + assert_not @domain.force_delete_scheduled? + travel_to Time.zone.parse('2010-07-05') + email = @domain.registrant.email + asserted_text = "Invalid email: #{email}" + + prepare_bounced_email_address(email) + + @domain.reload + + assert @domain.force_delete_scheduled? + assert_equal 'invalid_email', @domain.template_name + assert_equal Date.parse('2010-09-19'), @domain.force_delete_date.to_date + assert_equal Date.parse('2010-08-05'), @domain.force_delete_start.to_date + notification = @domain.registrar.notifications.last + assert notification.text.include? asserted_text + end + + def test_schedules_force_delete_invalid_contact + @domain.update(valid_to: Time.zone.parse('2012-08-05')) + assert_not @domain.force_delete_scheduled? + travel_to Time.zone.parse('2010-07-05') + email = 'some@strangesentence@internet.ee' + asserted_text = "Invalid email: #{email}" + + Truemail.configure.default_validation_type = :regex + + contact = @domain.admin_contacts.first + contact.update_attribute(:email, email) + contact.email_verification.verify + + assert contact.email_verification_failed? + + @domain.reload + + assert @domain.force_delete_scheduled? + assert_equal 'invalid_email', @domain.template_name + assert_equal Date.parse('2010-09-19'), @domain.force_delete_date.to_date + assert_equal Date.parse('2010-08-05'), @domain.force_delete_start.to_date + notification = @domain.registrar.notifications.last + assert notification.text.include? asserted_text + end + + def prepare_bounced_email_address(email) + @bounced_mail = BouncedMailAddress.new + @bounced_mail.email = email + @bounced_mail.message_id = '010f0174a0c7d348-ea6e2fc1-0854-4073-b71f-5cecf9b0d0b2-000000' + @bounced_mail.bounce_type = 'Permanent' + @bounced_mail.bounce_subtype = 'General' + @bounced_mail.action = 'failed' + @bounced_mail.status = '5.1.1' + @bounced_mail.diagnostic = 'smtp; 550 5.1.1 user unknown' + @bounced_mail.save! + end end