diff --git a/app/models/cname.rb b/app/models/cname.rb index 50b8b1e..b2525fb 100644 --- a/app/models/cname.rb +++ b/app/models/cname.rb @@ -1,4 +1,6 @@ class CNAME < Record validates :content, presence: true, hostname: true + + before_validation :remove_terminating_dot end diff --git a/app/models/ns.rb b/app/models/ns.rb index f620e5b..f12b135 100644 --- a/app/models/ns.rb +++ b/app/models/ns.rb @@ -1,4 +1,6 @@ class NS < Record validates :content, presence: true, hostname: true + + before_validation :remove_terminating_dot end diff --git a/app/models/ptr.rb b/app/models/ptr.rb index daa4351..a61dda3 100644 --- a/app/models/ptr.rb +++ b/app/models/ptr.rb @@ -1,4 +1,6 @@ class PTR < Record validates :content, presence: true, hostname: true + + before_validation :remove_terminating_dot end diff --git a/app/models/record.rb b/app/models/record.rb index 033711f..dd35396 100644 --- a/app/models/record.rb +++ b/app/models/record.rb @@ -1,115 +1,119 @@ require 'ipaddr' require_dependency 'drop_privileges_validator' class Record < ActiveRecord::Base belongs_to :domain def self.record_types [ 'A', 'AAAA', 'CNAME', 'MX', 'TXT', 'SPF', 'SRV', 'SSHFP', 'SOA', 'NS', 'PTR', ] end def self.forward_records record_types - ['SOA', 'PTR'] end def self.reverse_records ['PTR', 'CNAME', 'TXT', 'NS'] end def self.allowed_record_types record_types - WebDNS.settings[:prohibit_records_types] end validates :name, presence: true validates :type, inclusion: { in: record_types } # http://mark.lindsey.name/2009/03/never-use-dns-ttl-of-zero-0.html validates_numericality_of :ttl, allow_nil: true, # Default pdns TTL only_integer: true, greater_than: 0, less_than_or_equal_to: 2_147_483_647 # Don't allow the following actions on drop privileges mode validates_drop_privileges :type, message: 'You cannot touch that record!', unless: -> { Record.allowed_record_types.include?(type) } validates_drop_privileges :name, message: 'You cannot touch top level NS records!', if: -> { type == 'NS' && domain_record? } before_validation :guess_reverse_name before_validation :set_name after_save :update_zone_serial after_destroy :update_zone_serial def short return '' if name == domain.name return '' if name.blank? File.basename(name, ".#{domain.name}") end def domain_record? name.blank? || name == domain.name end # Editable by a non-admin user def editable? return false unless Record.allowed_record_types.include?(type) return false if type == 'NS' && domain_record? true end def supports_prio? false end # Create record specific urls for all record types # # Overrides default rails STI def self.model_name return super if self == Record Record.model_name end def to_dns [name, ttl, 'IN', type, supports_prio? ? prio : nil, content].compact.join(' ') end private # Hooks def guess_reverse_name return if not type == 'PTR' return if not domain.reverse? return if name.blank? reverse = IPAddr.new(name).reverse self.name = reverse if reverse.end_with?(domain.name) rescue IPAddr::InvalidAddressError # rubycop:disable HandleExceptions end # Powerdns expects full domain names def set_name self.name = domain.name if name.blank? self.name = "#{name}.#{domain.name}" if not name.end_with?(domain.name) end + def remove_terminating_dot + self.content = content.gsub(/\.+\Z/, '') + end + def update_zone_serial # SOA records handle serial themselves return true if type == 'SOA' domain.soa.bump_serial! end end diff --git a/test/models/cname_test.rb b/test/models/cname_test.rb index 4d3a6fc..bb34089 100644 --- a/test/models/cname_test.rb +++ b/test/models/cname_test.rb @@ -1,14 +1,22 @@ require 'test_helper' class CnameTest < ActiveSupport::TestCase setup do @record = build(:cname) end test 'save' do @record.save assert_empty @record.errors end + test 'chop terminating dot' do + @record.content = 'with-dot.example.com.' + @record.save! + @record.reload + + assert_equal 'with-dot.example.com', @record.content + end + end diff --git a/test/models/ns_test.rb b/test/models/ns_test.rb index 6d81be5..dab9721 100644 --- a/test/models/ns_test.rb +++ b/test/models/ns_test.rb @@ -1,29 +1,37 @@ require 'test_helper' class NSTest < ActiveSupport::TestCase setup do @record = build(:ns) end test 'save' do @record.save assert_empty @record.errors end + test 'chop terminating dot' do + @record.content = 'with-dot.example.com.' + @record.save! + @record.reload + + assert_equal 'with-dot.example.com', @record.content + end + test 'drop privileges on zone NS records' do @record.drop_privileges = true @record.save assert_not_empty @record.errors[:name] end test 'doesnt drop privileges on non zone NS records' do @record.name = 'other' @record.drop_privileges = true @record.save assert_empty @record.errors[:name] end end diff --git a/test/models/ptr_test.rb b/test/models/ptr_test.rb index 7ba80e9..6468f15 100644 --- a/test/models/ptr_test.rb +++ b/test/models/ptr_test.rb @@ -1,42 +1,50 @@ require 'test_helper' class PTRTest < ActiveSupport::TestCase class V4PTRTest < ActiveSupport::TestCase setup do @record = build(:v4_ptr) end test 'save' do @record.save assert_empty @record.errors end test 'guess record' do @record.name = '192.0.2.1' assert @record.save assert_equal '1.2.0.192.in-addr.arpa', @record.name end + + test 'chop terminating dot' do + @record.content = 'with-dot.example.com.' + @record.save! + @record.reload + + assert_equal 'with-dot.example.com', @record.content + end end class V6PTRTest < ActiveSupport::TestCase setup do @record = build(:v6_ptr) end test 'save' do @record.save assert_empty @record.errors end test 'guess record' do @record.name = '2001:db8::2' assert @record.save assert_equal '2.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa', @record.name end end end