internetee-registry/app/models/csync_record.rb
2021-06-17 12:25:53 +05:00

141 lines
4.1 KiB
Ruby

# frozen_string_literal: true
class CsyncRecord < ApplicationRecord
include CsyncRecord::Diggable
belongs_to :domain, optional: false
validates :domain, uniqueness: true
validates :cdnskey, :action, :last_scan, presence: true
validate :validate_unique_pub_key
validate :validate_csync_action
validate :validate_cdnskey_format
after_save :process_new_dnskey, if: proc { pushable? && !disable_requested? }
after_save :remove_dnskeys, if: proc { pushable? && disable_requested? }
SCAN_CYCLES = 3
def record_new_scan(result)
assign_scanner_data!(result)
prefix = "CsyncRecord: #{domain.name}:"
if save
log.info "#{prefix} Cycle done."
else
log.info "#{prefix}: not processing. Reason: #{errors.full_messages.join(' .')}"
CsyncRecord.where(domain: domain).delete_all
end
end
def assign_scanner_data!(result)
state = result[:type]
self.last_scan = Time.zone.now
self.times_scanned = (persisted? && cdnskey != result[:cdnskey] ? 1 : times_scanned + 1)
self.cdnskey = result[:cdnskey]
self.action = initializes_dnssec?(state) ? 'initialized' : determine_csync_intention(state)
end
def dnskey
key = Dnskey.new_from_csync(domain: domain, cdnskey: cdnskey)
log.info "DNSKEY not valid. #{key.errors.full_messages.join('. ')}." unless key.valid?
key
end
def destroy_all_but_last_one
domain.dnskeys.order(id: :desc).offset(1).destroy_all
end
def process_new_dnskey
return unless dnssec_validates?
if dnskey.save
destroy_all_but_last_one
finalize_and_notify
else
log.info "Failed to save DNSKEY. Errors: #{dnskey.errors.full_messages.join('. ')}"
end
end
def finalize_and_notify
CsyncMailer.dnssec_updated(domain: domain).deliver_now
notify_registrar_about_csync
CsyncRecord.where(domain: domain).destroy_all
log.info "CsyncRecord: #{domain.name}: DNSKEYs updated."
end
def pushable?
return true if domain.dnskeys.any? || times_scanned >= SCAN_CYCLES
false
end
def disable_requested?
['0 3 0 AA==', '0 3 0 0'].include? cdnskey
end
def remove_dnskeys
log.info "CsyncJob: Removing DNSKEYs for domain '#{domain.name}'"
domain.dnskeys.destroy_all
CsyncMailer.dnssec_deleted(domain: domain).deliver_now
notify_registrar_about_csync
destroy
end
def notify_registrar_about_csync
domain.update_whois_record
domain.registrar.notifications.create!(text: I18n.t('notifications.texts.csync',
domain: domain.name, action: action))
end
def validate_unique_pub_key
return false unless domain
return true if disable_requested?
return true unless dnskey_already_present?
errors.add(:public_key, 'already tied to this domain')
end
# since dnskeys stored in DB may include whitespace chars, we could not find them by
# 'where' clause using dnskey.public_key being stripped of whitespaces by csync generator
def dnskey_already_present?
domain.dnskeys.pluck(:public_key).map { |key| key.gsub(/\s+/, '') }.include? dnskey.public_key
end
def self.by_domain_name(domain_name)
domain = Domain.find_by(name: domain_name) || Domain.find_by(name_puny: domain_name)
log.info "CsyncRecord: '#{domain_name}' not in zone. Not initializing record." unless domain
CsyncRecord.find_or_initialize_by(domain: domain) if domain
end
def determine_csync_intention(scan_state)
return unless domain.dnskeys.any? && scan_state == 'secure'
disable_requested? ? 'deactivate' : 'rollover'
end
def initializes_dnssec?(scan_state)
true if domain.dnskeys.empty? && !disable_requested? && scan_state == 'insecure'
end
def log
self.class.log
end
def self.log
Rails.env.test? ? logger : Logger.new(STDOUT)
end
def validate_csync_action
return true if %w[initialized rollover].include? action
return true if action == 'deactivate' && disable_requested?
errors.add(:action, :invalid)
end
def validate_cdnskey_format
return true if disable_requested?
return true if dnskey.valid?
errors.add(:cdnskey, :invalid)
end
end