diff --git a/app/helpers/epp/domains_helper.rb b/app/helpers/epp/domains_helper.rb index c76fda2d0..1f6b68b4f 100644 --- a/app/helpers/epp/domains_helper.rb +++ b/app/helpers/epp/domains_helper.rb @@ -4,6 +4,7 @@ module Epp::DomainsHelper @domain = Epp::EppDomain.new(domain_create_params) @domain.parse_and_attach_domain_dependencies(parsed_frame) + @domain.parse_and_attach_ds_data(parsed_frame.css('extension create')) if @domain.errors.any? handle_errors(@domain) @@ -50,6 +51,7 @@ module Epp::DomainsHelper handle_errors(@domain) and return unless @domain @domain.parse_and_attach_domain_dependencies(parsed_frame.css('add')) + @domain.parse_and_attach_ds_data(parsed_frame.css('extension add')) @domain.parse_and_detach_domain_dependencies(parsed_frame.css('rem')) @domain.parse_and_update_domain_dependencies(parsed_frame.css('chg')) diff --git a/app/models/concerns/epp_errors.rb b/app/models/concerns/epp_errors.rb index 1c1c2c5b0..fcc31a7dd 100644 --- a/app/models/concerns/epp_errors.rb +++ b/app/models/concerns/epp_errors.rb @@ -5,7 +5,12 @@ module EppErrors epp_errors = [] errors.messages.each do |key, values| key = key.to_s.split('.')[0].to_sym - if self.class.reflect_on_association(key) + + macro = self.class.reflect_on_association(key).try(:macro) + multi = [:has_and_belongs_to_many, :has_many] + single = [:belongs_to, :has_one] + + if macro && multi.include?(macro) send(key).each do |x| epp_errors << x.generate_epp_errors end diff --git a/app/models/delegation_signer.rb b/app/models/delegation_signer.rb index 63a87b507..b5a5c195e 100644 --- a/app/models/delegation_signer.rb +++ b/app/models/delegation_signer.rb @@ -1,6 +1,6 @@ class DelegationSigner < ActiveRecord::Base include EppErrors - has_many :dnskeys + has_one :dnskeys validate :validate_dnskeys_uniqueness validate :validate_dnskeys_count diff --git a/app/models/dnskey.rb b/app/models/dnskey.rb index f990804d8..2f249c0fd 100644 --- a/app/models/dnskey.rb +++ b/app/models/dnskey.rb @@ -2,7 +2,6 @@ class Dnskey < ActiveRecord::Base include EppErrors belongs_to :domain - belongs_to :delegation_signer validates :alg, :protocol, :flags, :public_key, presence: true validate :validate_algorithm diff --git a/app/models/domain.rb b/app/models/domain.rb index 101f95833..6650a91f3 100644 --- a/app/models/domain.rb +++ b/app/models/domain.rb @@ -27,7 +27,7 @@ class Domain < ActiveRecord::Base has_many :domain_transfers, dependent: :delete_all - has_many :delegation_signers, dependent: :delete_all + has_many :dnskeys, dependent: :delete_all # accepts_nested_attributes_for :delegation_signers, allow_destroy: true, # reject_if: proc { |attrs| attrs[:public_key].blank? } @@ -49,12 +49,12 @@ class Domain < ActiveRecord::Base validate :validate_period validate :validate_nameservers_count validate :validate_admin_contacts_count - #validate :validate_dnskeys_count + validate :validate_dnskeys_count validate :validate_nameservers_uniqueness validate :validate_tech_contacts_uniqueness validate :validate_admin_contacts_uniqueness validate :validate_domain_statuses_uniqueness - # validate :validate_dnskeys_uniqueness + validate :validate_dnskeys_uniqueness validate :validate_nameserver_ips attr_accessor :owner_contact_typeahead diff --git a/app/models/epp/epp_domain.rb b/app/models/epp/epp_domain.rb index 20d2a7b5b..527f5dca2 100644 --- a/app/models/epp/epp_domain.rb +++ b/app/models/epp/epp_domain.rb @@ -29,7 +29,13 @@ class Epp::EppDomain < Domain max: domain_validation_sg.setting(:ns_max_count).value } ], - [:period, :out_of_range, { value: { obj: 'period', val: period } }] + [:period, :out_of_range, { value: { obj: 'period', val: period } }], + [:dnskeys, :out_of_range, + { + min: domain_validation_sg.setting(Setting::DNSKEYS_MIN_COUNT).value, + max: domain_validation_sg.setting(Setting::DNSKEYS_MAX_COUNT).value + } + ] ], '2200' => [ [:auth_info, :wrong_pw] @@ -42,7 +48,6 @@ class Epp::EppDomain < Domain attach_contacts(self.class.parse_contacts_from_frame(parsed_frame)) attach_nameservers(self.class.parse_nameservers_from_frame(parsed_frame)) attach_statuses(self.class.parse_statuses_from_frame(parsed_frame)) - attach_dnskeys(self.class.parse_dnskeys_from_frame(parsed_frame)) errors.empty? end @@ -51,7 +56,12 @@ class Epp::EppDomain < Domain detach_contacts(self.class.parse_contacts_from_frame(parsed_frame)) detach_nameservers(self.class.parse_nameservers_from_frame(parsed_frame)) detach_statuses(self.class.parse_statuses_from_frame(parsed_frame)) - detach_dnskeys(self.class.parse_dnskeys_from_frame(parsed_frame)) + + errors.empty? + end + + def parse_and_attach_ds_data(parsed_frame) + attach_dnskeys(self.class.parse_dnskeys_from_frame(parsed_frame)) errors.empty? end @@ -184,7 +194,7 @@ class Epp::EppDomain < Domain errors.add(:base, :ds_data_with_keys_not_allowed) next else - attach_ds(ds_data) + dnskeys.build(ds_data) end end @@ -193,30 +203,35 @@ class Epp::EppDomain < Domain return end - attach_ds({ - key_tag: SecureRandom.hex(5), - alg: 3, - digest_type: sg.setting(Setting::DS_ALGORITHM).value, - key_data: dnssec_data[:key_data] - }) - end - - def attach_ds(ds_data) - key_data = ds_data.delete(:key_data) - ds = delegation_signers.build(ds_data) - key_data.each do |x| - ds.dnskeys.build(x) + dnssec_data[:key_data].each do |x| + dnskeys.build({ + ds_key_tag: SecureRandom.hex(5), + ds_alg: 3, + ds_digest_type: sg.setting(Setting::DS_ALGORITHM).value + }.merge(x)) end + + + errors.any? end - def detach_dnskeys(dnskey_list) + def detach_dnskeys(dnssec_data) + sg = SettingGroup.dnskeys + ds_data_allowed = sg.setting(Setting::ALLOW_DS_DATA).value == '0' ? false : true + key_data_allowed = sg.setting(Setting::ALLOW_KEY_DATA).value == '0' ? false : true + + if dnssec_data[:ds_data].any? && !ds_data_allowed + errors.add(:base, :ds_data_not_allowed) + return + end + to_delete = [] - dnskey_list.each do |x| - dnskey = dnskeys.where(public_key: x[:public_key]) - if dnskey.blank? - add_epp_error('2303', 'pubKey', x[:public_key], [:dnskeys, :not_found]) + dnssec_data[:ds_data].each do |x| + ds = dnskeys.where(ds_key_tag: x[:ds_key_tag]) + if ds.blank? + add_epp_error('2303', 'keyTag', x[:key_tag], [:dnskeys, :not_found]) else - to_delete << dnskey + to_delete << ds end end @@ -392,26 +407,20 @@ class Epp::EppDomain < Domain res[:max_sig_life] = parsed_frame.css('maxSigLife').first.try(:text) parsed_frame.css('dsData').each do |x| - keys = [] - x.css('keyData').each do |kd| - keys << { - flags: kd.css('flags').first.try(:text), - protocol: kd.css('protocol').first.try(:text), - alg: kd.css('alg').first.try(:text), - public_key: kd.css('pubKey').first.try(:text) - } - end - + kd = x.css('keyData').first res[:ds_data] << { - key_tag: x.css('keyTag').first.try(:text), - alg: x.css('alg').first.try(:text), - digest_type: x.css('digestType').first.try(:text), - digest: x.css('digest').first.try(:text), - key_data: keys + ds_key_tag: x.css('keyTag').first.try(:text), + ds_alg: x.css('alg').first.try(:text), + ds_digest_type: x.css('digestType').first.try(:text), + ds_digest: x.css('digest').first.try(:text), + flags: kd.css('flags').first.try(:text), + protocol: kd.css('protocol').first.try(:text), + alg: kd.css('alg').first.try(:text), + public_key: kd.css('pubKey').first.try(:text) } end - parsed_frame.css('create > keyData').each do |x| + parsed_frame.css('* > keyData').each do |x| res[:key_data] << { flags: x.css('flags').first.try(:text), protocol: x.css('protocol').first.try(:text), diff --git a/app/views/epp/domains/info.xml.builder b/app/views/epp/domains/info.xml.builder index d8fe5e3e5..0ee779080 100644 --- a/app/views/epp/domains/info.xml.builder +++ b/app/views/epp/domains/info.xml.builder @@ -63,24 +63,22 @@ xml.epp_head do xml.extension do xml.tag!('secDNS:infData', 'xmlns:secDNS' => 'urn:ietf:params:xml:ns:secDNS-1.1') do - @domain.delegation_signers.each do |x| + @domain.dnskeys.each do |x| xml.tag!('secDNS:dsData') do - xml.tag!('secDNS:keyTag', x.key_tag) - xml.tag!('secDNS:alg', x.alg) - xml.tag!('secDNS:digestType', x.digest_type) - xml.tag!('secDNS:digest', x.digest) - x.dnskeys.each do |key| - xml.tag!('secDNS:keyData') do - xml.tag!('secDNS:flags', key.flags) - xml.tag!('secDNS:protocol', key.protocol) - xml.tag!('secDNS:alg', key.alg) - xml.tag!('secDNS:pubKey', key.public_key) - end - end if x.dnskeys.any? + xml.tag!('secDNS:keyTag', x.ds_key_tag) + xml.tag!('secDNS:alg', x.ds_alg) + xml.tag!('secDNS:digestType', x.ds_digest_type) + xml.tag!('secDNS:digest', x.ds_digest) + xml.tag!('secDNS:keyData') do + xml.tag!('secDNS:flags', x.flags) + xml.tag!('secDNS:protocol', x.protocol) + xml.tag!('secDNS:alg', x.alg) + xml.tag!('secDNS:pubKey', x.public_key) + end end end end - end if @domain.delegation_signers.any? + end if @domain.dnskeys.any? xml << render('/epp/shared/trID') end diff --git a/config/locales/en.yml b/config/locales/en.yml index ca648aa06..21c7fc71a 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -110,8 +110,9 @@ en: registrar: blank: 'Registrar is missing' dnskeys: + not_found: 'DS was not found' invalid: 'DNS keys are invalid' - not_found: 'Dnskey was not found' + out_of_range: 'DNS keys count must be between %{min}-%{max}' domain: <<: *epp_domain_ar_attributes diff --git a/db/migrate/20141010130412_add_ds_filelds_to_dnskey.rb b/db/migrate/20141010130412_add_ds_filelds_to_dnskey.rb new file mode 100644 index 000000000..6b0c7e32c --- /dev/null +++ b/db/migrate/20141010130412_add_ds_filelds_to_dnskey.rb @@ -0,0 +1,9 @@ +class AddDsFileldsToDnskey < ActiveRecord::Migration + def change + add_column :dnskeys, :ds_key_tag, :string + add_column :dnskeys, :ds_alg, :integer + add_column :dnskeys, :ds_digest_type, :integer + add_column :dnskeys, :ds_digest, :string + end +end + diff --git a/db/schema.rb b/db/schema.rb index 73e293b2d..a5819e711 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20141009101337) do +ActiveRecord::Schema.define(version: 20141010130412) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -106,6 +106,10 @@ ActiveRecord::Schema.define(version: 20141009101337) do t.integer "alg" t.string "public_key" t.integer "delegation_signer_id" + t.string "ds_key_tag" + t.integer "ds_alg" + t.integer "ds_digest_type" + t.string "ds_digest" end create_table "domain_contacts", force: true do |t| diff --git a/spec/epp/domain_spec.rb b/spec/epp/domain_spec.rb index a5d204622..05838c283 100644 --- a/spec/epp/domain_spec.rb +++ b/spec/epp/domain_spec.rb @@ -203,13 +203,14 @@ describe 'EPP Domain', epp: true do expect(d.nameservers.count).to eq(2) expect(d.auth_info).not_to be_empty - expect(d.delegation_signers.count).to eq(1) - ds = d.delegation_signers.first + expect(d.dnskeys.count).to eq(1) - expect(ds.dnskeys.count).to eq(1) - - key = ds.dnskeys.first + key = d.dnskeys.first + expect(key.ds_alg).to eq(3) + expect(key.ds_key_tag).to_not be_blank + sg = SettingGroup.dnskeys + expect(key.ds_digest_type).to eq(sg.setting(Setting::DS_ALGORITHM).value.to_i) expect(key.flags).to eq(257) expect(key.protocol).to eq(3) expect(key.alg).to eq(5) @@ -359,16 +360,17 @@ describe 'EPP Domain', epp: true do epp_request(xml, :xml) d = Domain.first - ds = d.delegation_signers.first + expect(d.dnskeys.count).to eq(3) - expect(ds.key_tag).to_not be_blank - expect(ds.alg).to eq(3) - expect(ds.digest_type).to eq(SettingGroup.dnskeys.setting(Setting::DS_ALGORITHM).value.to_i) + key_1 = d.dnskeys[0] + expect(key_1.ds_key_tag).to_not be_blank + expect(key_1.ds_alg).to eq(3) + expect(key_1.ds_digest_type).to eq(SettingGroup.dnskeys.setting(Setting::DS_ALGORITHM).value.to_i) - expect(ds.dnskeys.pluck(:flags)).to match_array([257, 0, 256]) - expect(ds.dnskeys.pluck(:protocol)).to match_array([3, 3, 3]) - expect(ds.dnskeys.pluck(:alg)).to match_array([3, 5, 254]) - expect(ds.dnskeys.pluck(:public_key)).to match_array(%w( + expect(d.dnskeys.pluck(:flags)).to match_array([257, 0, 256]) + expect(d.dnskeys.pluck(:protocol)).to match_array([3, 3, 3]) + expect(d.dnskeys.pluck(:alg)).to match_array([3, 5, 254]) + expect(d.dnskeys.pluck(:public_key)).to match_array(%w( AwEAAddt2AkLfYGKgiEZB5SmIF8EvrjxNMH6HtxWEA4RJ9Ao6LCWheg8 700b97b591ed27ec2590d19f06f88bba700b97b591ed27ec2590d19f 841936717ae427ace63c28d04918569a841936717ae427ace63c28d0 @@ -571,10 +573,28 @@ describe 'EPP Domain', epp: true do d.domain_statuses.build(value: DomainStatus::CLIENT_HOLD, description: 'Payment overdue.') d.nameservers.build(hostname: 'ns1.example.com', ipv4: '192.168.1.1', ipv6: '1080:0:0:0:8:800:200C:417A') - ds = d.delegation_signers.build(key_tag: '123', alg: 3, digest_type: 1, digest: 'abc') + d.dnskeys.build( + ds_key_tag: '123', + ds_alg: 3, + ds_digest_type: 1, + ds_digest: 'abc', + flags: 257, + protocol: 3, + alg: 3, + public_key: 'AwEAAddt2AkLfYGKgiEZB5SmIF8EvrjxNMH6HtxWEA4RJ9Ao6LCWheg8' + ) + + d.dnskeys.build( + ds_key_tag: '123', + ds_alg: 3, + ds_digest_type: 1, + ds_digest: 'abc', + flags: 0, + protocol: 3, + alg: 5, + public_key: '700b97b591ed27ec2590d19f06f88bba700b97b591ed27ec2590d19f' + ) - ds.dnskeys.build(flags: 257, protocol: 3, alg: 3, public_key: 'AwEAAddt2AkLfYGKgiEZB5SmIF8EvrjxNMH6HtxWEA4RJ9Ao6LCWheg8') - ds.dnskeys.build(flags: 0, protocol: 3, alg: 5, public_key: '700b97b591ed27ec2590d19f06f88bba700b97b591ed27ec2590d19f') d.save response = epp_request(domain_info_xml, :xml) @@ -604,20 +624,22 @@ describe 'EPP Domain', epp: true do expect(inf_data.css('exDate').text).to eq(d.valid_to.to_time.utc.to_s) expect(inf_data.css('pw').text).to eq(d.auth_info) - ds_data = response[:parsed].css('dsData')[0] + ds_data_1 = response[:parsed].css('dsData')[0] - expect(ds_data.css('keyTag').first.text).to eq('123') - expect(ds_data.css('alg').first.text).to eq('3') - expect(ds_data.css('digestType').first.text).to eq('1') - expect(ds_data.css('digest').first.text).to eq('abc') + expect(ds_data_1.css('keyTag').first.text).to eq('123') + expect(ds_data_1.css('alg').first.text).to eq('3') + expect(ds_data_1.css('digestType').first.text).to eq('1') + expect(ds_data_1.css('digest').first.text).to eq('abc') - dnskey_1 = ds_data.css('keyData')[0] + dnskey_1 = ds_data_1.css('keyData')[0] expect(dnskey_1.css('flags').first.text).to eq('257') expect(dnskey_1.css('protocol').first.text).to eq('3') expect(dnskey_1.css('alg').first.text).to eq('3') expect(dnskey_1.css('pubKey').first.text).to eq('AwEAAddt2AkLfYGKgiEZB5SmIF8EvrjxNMH6HtxWEA4RJ9Ao6LCWheg8') - dnskey_2 = ds_data.css('keyData')[1] + ds_data_2 = response[:parsed].css('dsData')[1] + + dnskey_2 = ds_data_2.css('keyData')[0] expect(dnskey_2.css('flags').first.text).to eq('0') expect(dnskey_2.css('protocol').first.text).to eq('3') expect(dnskey_2.css('alg').first.text).to eq('5') @@ -646,30 +668,30 @@ describe 'EPP Domain', epp: true do { hostObj: { value: 'ns2.example.com' } } ] }, - dnssec: [ - { - dnskey: { - flags: { value: '0' }, - protocol: { value: '3' }, - alg: { value: '5' }, - pubKey: { value: '700b97b591ed27ec2590d19f06f88bba700b97b591ed27ec2590d19f' } - } - }, - { - dnskey: { - flags: { value: '256' }, - protocol: { value: '3' }, - alg: { value: '254' }, - pubKey: { value: '841936717ae427ace63c28d04918569a841936717ae427ace63c28d0' } - } - } - ], _other: [ { contact: { value: 'mak21', attrs: { type: 'tech' } } }, { status: { value: 'Payment overdue.', attrs: { s: 'clientHold', lang: 'en' } } }, { status: { value: '', attrs: { s: 'clientUpdateProhibited' } } } ] ] + }, { + add: [ + { keyData: { + flags: { value: '0' }, + protocol: { value: '3' }, + alg: { value: '5' }, + pubKey: { value: '700b97b591ed27ec2590d19f06f88bba700b97b591ed27ec2590d19f' } + } + }, + { + keyData: { + flags: { value: '256' }, + protocol: { value: '3' }, + alg: { value: '254' }, + pubKey: { value: '841936717ae427ace63c28d04918569a841936717ae427ace63c28d0' } + } + } + ] }) response = epp_request(xml, :xml) @@ -694,11 +716,12 @@ describe 'EPP Domain', epp: true do expect(d.domain_statuses.first.value).to eq('clientHold') expect(d.domain_statuses.last.value).to eq('clientUpdateProhibited') + expect(d.delegation_signers.count).to eq(1) - expect(d.dnskeys.count).to eq(2) + ds = d.delegation_signers.first + expect(ds.dnskeys.count).to eq(2) response = epp_request(xml, :xml) - expect(response[:results][0][:result_code]).to eq('2302') expect(response[:results][0][:msg]).to eq('Nameserver already exists on this domain') expect(response[:results][0][:value]).to eq('ns1.example.com') @@ -730,30 +753,30 @@ describe 'EPP Domain', epp: true do { hostObj: { value: 'ns2.example.com' } } ] }, - dnssec: [ - { - dnskey: { - flags: { value: '0' }, - protocol: { value: '3' }, - alg: { value: '5' }, - pubKey: { value: '700b97b591ed27ec2590d19f06f88bba700b97b591ed27ec2590d19f' } - } - }, - { - dnskey: { - flags: { value: '256' }, - protocol: { value: '3' }, - alg: { value: '254' }, - pubKey: { value: '841936717ae427ace63c28d04918569a841936717ae427ace63c28d0' } - } - } - ], _other: [ { contact: { value: 'mak21', attrs: { type: 'tech' } } }, { status: { value: 'Payment overdue.', attrs: { s: 'clientHold', lang: 'en' } } }, { status: { value: '', attrs: { s: 'clientUpdateProhibited' } } } ] ] + }, { + add: [ + { keyData: { + flags: { value: '0' }, + protocol: { value: '3' }, + alg: { value: '5' }, + pubKey: { value: '700b97b591ed27ec2590d19f06f88bba700b97b591ed27ec2590d19f' } + } + }, + { + keyData: { + flags: { value: '256' }, + protocol: { value: '3' }, + alg: { value: '254' }, + pubKey: { value: '841936717ae427ace63c28d04918569a841936717ae427ace63c28d0' } + } + } + ] }) epp_request(xml, :xml) @@ -777,13 +800,21 @@ describe 'EPP Domain', epp: true do { status: { value: '', attrs: { s: 'clientHold' } } } ] ] + }, { + rem: [ + { keyData: { + pubKey: { value: '700b97b591ed27ec2590d19f06f88bba700b97b591ed27ec2590d19f' } + } + } + ] }) d = Domain.last - expect(d.dnskeys.count).to eq(2) + expect(d.delegation_signers.count).to eq(1) response = epp_request(xml, :xml) - expect(d.dnskeys.count).to eq(1) + ds = d.delegation_signers.first + expect(ds.dnskeys.count).to eq(1) expect(d.domain_statuses.count).to eq(1) expect(d.domain_statuses.first.value).to eq('clientUpdateProhibited') diff --git a/spec/support/epp.rb b/spec/support/epp.rb index 48e1ffeb9..0e86ee795 100644 --- a/spec/support/epp.rb +++ b/spec/support/epp.rb @@ -169,7 +169,7 @@ module Epp end end - def domain_update_xml(xml_params = {}) + def domain_update_xml(xml_params = {}, dnssec_params = false) defaults = { name: { value: 'example.ee' } } @@ -186,6 +186,12 @@ module Epp generate_xml_from_hash(xml_params, xml, 'domain') end end + + xml.extension do + xml.tag!('secDNS:create', 'xmlns:secDNS' => 'urn:ietf:params:xml:ns:secDNS-1.1') do + generate_xml_from_hash(dnssec_params, xml, 'secDNS') + end + end if dnssec_params != false xml.clTRID 'ABC-12345' end end