diff --git a/app/models/domain.rb b/app/models/domain.rb index deafa86..0fd518f 100644 --- a/app/models/domain.rb +++ b/app/models/domain.rb @@ -1,110 +1,124 @@ class Domain < ActiveRecord::Base + class NotAChild < StandardError; end self.inheritance_column = :nx # List all supported domain types. def self.domain_types [ 'NATIVE', 'MASTER', 'SLAVE', ] end # List domain types that can be created. def self.allowed_domain_types domain_types - WebDNS.settings[:prohibit_domain_types] end belongs_to :group has_many :records # BUG in bump_serial_trigger has_one :soa, -> { unscope(where: :type) }, class_name: SOA validates :group_id, presence: true validates :name, uniqueness: true, presence: true validates :type, presence: true, inclusion: { in: domain_types } validates :master, presence: true, ipv4: true, if: :slave? after_create :generate_soa after_create :generate_ns attr_writer :serial_strategy # Get the zone's serial strategy. # # Returns one of the supported serial strategies. def serial_strategy @serial_strategy ||= WebDNS.settings[:serial_strategy] end # Returns true if this a reverse zone. def reverse? name.end_with?('.in-addr.arpa') || name.end_with?('.ip6.arpa') end # Returns true if this a ENUM zone. def enum? name.end_with?('.e164.arpa') end # Returns true if this is a slave zone. def slave? type == 'SLAVE' end # Compute subnet for reverse records def subnet return if not reverse? if name.end_with?('.in-addr.arpa') subnet_v4 elsif name.end_with?('.ip6.arpa') subnet_v6 end end + def self.replace_ds(parent, child, records) + parent = find_by_name!(parent) + fail NotAChild if not child.end_with?(parent.name) + + existing = parent.records.where(name: child, type: 'DS') + recs = records.map { |rec| DS.new(domain: parent, name: child, content: rec) } + + ActiveRecord::Base.transaction do + existing.destroy_all + recs.map(&:save!) + end + end + private def subnet_v4 # get ip octets (remove .in-addr.arpa) octets = name.split('.')[0...-2].reverse return if octets.any? { |_| false } mask = 8 * octets.size octets += [0, 0, 0, 0] ip = IPAddr.new octets[0, 4].join('.') [ip, mask].join('/') end def subnet_v6 nibbles = name.split('.')[0...-2].reverse return if nibbles.any? { |_| false } mask = 4 * nibbles.size nibbles += [0] * 32 ip = IPAddr.new nibbles[0, 32].in_groups_of(4).map(&:join).join(':') [ip, mask].join('/') end # Hooks def generate_soa soa_record = SOA.new(domain: self) soa_record.save! end def generate_ns return if slave? return if WebDNS.settings[:default_ns].empty? WebDNS.settings[:default_ns].each { |ns| Record.find_or_create_by!(domain: self, type: 'NS', name: '', content: ns) } end end diff --git a/test/models/domain_test.rb b/test/models/domain_test.rb index 44852e2..6479a74 100644 --- a/test/models/domain_test.rb +++ b/test/models/domain_test.rb @@ -1,87 +1,116 @@ require 'test_helper' class DomainTest < ActiveSupport::TestCase def setup @domain = build(:domain) end test 'automatic SOA creation' do @domain.save! @domain.reload assert_not_nil @domain.soa end test 'increment serial on new record' do @domain.save! soa = @domain.soa assert_serial_update soa do www = A.new(name: 'www', domain: @domain, content: '1.2.3.4') www.save! end end test 'increment serial on record update' do @domain.save! www = A.new(name: 'www', domain: @domain, content: '1.2.3.4') www.save! soa = @domain.soa.reload assert_serial_update soa do www.content = '1.2.3.5' www.save! end end test 'automatic NS creation' do @domain.save! @domain.reload assert_equal WebDNS.settings[:default_ns].sort, @domain.records.where(type: 'NS').pluck(:content).sort end test 'increment serial on record destroy' do @domain.save! www = A.new(name: 'www', domain: @domain, content: '1.2.3.4') www.save! soa = @domain.soa.reload assert_serial_update soa do www.destroy! end end class SlaveDomainTest < ActiveSupport::TestCase def setup @domain = build(:slave) end test 'saves' do @domain.save assert_empty @domain.errors end test 'automatic SOA creation' do @domain.save! @domain.reload assert_not_nil @domain.soa assert_equal 1, @domain.soa.serial end test 'validates master' do @domain.master = 'not-an-ip' @domain.save assert_not_empty @domain.errors['master'] end test 'no records are allowed for users' do @domain.save! rec = build(:a, domain_id: @domain.id) assert_not rec.valid? assert_not_empty rec.errors[:type] end end + + class DsDomainTest < ActiveSupport::TestCase + def setup + @domain = create(:domain) + @ds = [ + '31406 8 1 189968811e6eba862dd6c209f75623d8d9ed9142', + '31406 8 2 f78cf3344f72137235098ecbbd08947c2c9001c7f6a085a17f518b5d8f6b916d', + ] + @child = "dnssec.#{@domain.name}" + @extra = DS.create(domain: @domain, name: @child, content: 'other') + end + + test 'add ds records' do + Domain.replace_ds(@domain.name, @child, @ds) + @extra.save! # Should be deleted + + assert_equal @ds.size, DS.where(name: "dnssec.#{@domain.name}").count + @ds.each { |ds| + assert_equal 1, DS.where(name: "dnssec.#{@domain.name}", content: ds).count + } + end + + test 'check if child is a valid subdomain' do + assert_raise Domain::NotAChild do + Domain.replace_ds(@domain.name, 'dnssec.example.net', @ds) + end + end + + end end