diff --git a/app/helpers/epp/domains_helper.rb b/app/helpers/epp/domains_helper.rb index 2abf3aa27..566f5ebd2 100644 --- a/app/helpers/epp/domains_helper.rb +++ b/app/helpers/epp/domains_helper.rb @@ -38,6 +38,9 @@ module Epp::DomainsHelper @domain = find_domain handle_errors(@domain) and return unless @domain + handle_errors(@domain) and return unless @domain.attach_objects(@ph, parsed_frame.css('add')) + handle_errors(@domain) and return unless @domain.detach_objects(@ph, parsed_frame.css('rem')) + handle_errors(@domain) and return unless @domain.save render '/epp/domains/success' end diff --git a/app/models/concerns/epp_errors.rb b/app/models/concerns/epp_errors.rb index e8c56918e..cb7f53dd2 100644 --- a/app/models/concerns/epp_errors.rb +++ b/app/models/concerns/epp_errors.rb @@ -51,8 +51,12 @@ module EppErrors end def find_epp_code(msg) - self.class::EPP_CODE_MAP.each do |code, values| - return code if values.include?(msg) + epp_code_map.each do |code, values| + values.each do |x| + t = errors.generate_message(*x) if x.is_a?(Array) + t = x if x.is_a?(String) + return code if t == msg + end end nil end diff --git a/app/models/contact.rb b/app/models/contact.rb index 81938499b..d114f90a8 100644 --- a/app/models/contact.rb +++ b/app/models/contact.rb @@ -4,13 +4,6 @@ class Contact < ActiveRecord::Base include EppErrors - EPP_CODE_MAP = { - '2302' => ['Contact id already exists'], - '2303' => [:not_found, :epp_obj_does_not_exist], - '2305' => ['Object association prohibits operation'], - '2005' => ['Phone nr is invalid', 'Email is invalid'] - } - EPP_ATTR_MAP = {} has_one :address @@ -88,7 +81,7 @@ class Contact < ActiveRecord::Base relation = get_relation(model) return true unless relation.nil? || relation.blank? false - end + end #should use only in transaction def destroy_and_clean @@ -101,27 +94,35 @@ class Contact < ActiveRecord::Base destroy end + def epp_code_map + { + '2302' => [[:code, :epp_id_taken]], + '2303' => [:not_found, :epp_obj_does_not_exist], + '2005' => ['Phone nr is invalid', 'Email is invalid'] + } + end + class << self def extract_attributes ph, type=:create - + contact_hash = { phone: ph[:voice], ident: ph[:ident], email: ph[:email] } - + contact_hash = contact_hash.merge({ name: ph[:postalInfo][:name], org_name: ph[:postalInfo][:org] }) if ph[:postalInfo].is_a? Hash - + contact_hash[:code] = ph[:id] if type == :create - + contact_hash.delete_if { |k, v| v.nil? } end - + def check_availability(codes) codes = [codes] if codes.is_a?(String) diff --git a/app/models/domain.rb b/app/models/domain.rb index a3775a244..1f52863ed 100644 --- a/app/models/domain.rb +++ b/app/models/domain.rb @@ -4,14 +4,6 @@ class Domain < ActiveRecord::Base include EppErrors - EPP_CODE_MAP = { - '2302' => ['Domain name already exists', 'Domain name is reserved or restricted'], # Object exists - '2306' => ['Registrant is missing', 'Admin contact is missing', 'Given and current expire dates do not match'], # Parameter policy error - '2004' => ['Nameservers count must be between 1-13', 'Period must add up to 1, 2 or 3 years'], # Parameter value range error - '2303' => ['Registrant not found', 'Contact was not found'], # Object does not exist - '2200' => ['Authentication error'] - } - EPP_ATTR_MAP = { owner_contact: 'registrant', name_dirty: 'name', @@ -33,6 +25,10 @@ class Domain < ActiveRecord::Base has_and_belongs_to_many :nameservers + has_many :domain_statuses, -> { + joins(:setting).where(settings: {setting_group_id: SettingGroup.domain_statuses.id}) + } + delegate :code, to: :owner_contact, prefix: true delegate :name, to: :registrar, prefix: true @@ -53,16 +49,21 @@ class Domain < ActiveRecord::Base write_attribute(:name_dirty, value) end - ### CREATE ### + ### CREATE & UPDATE ### def attach_objects(ph, parsed_frame) - attach_owner_contact(ph[:registrant]) + attach_owner_contact(ph[:registrant]) if ph[:registrant] 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)) errors.empty? end + def detach_objects(ph, parsed_frame) + detach_nameservers(self.class.parse_nameservers_from_frame(parsed_frame)) + end + def attach_owner_contact(code) self.owner_contact = Contact.find_by(code: code) @@ -109,6 +110,25 @@ class Domain < ActiveRecord::Base end end + def attach_statuses(status_list) + status_list.each do |x| + setting = SettingGroup.domain_statuses.settings.find_by(value: x[:value]) + self.domain_statuses.build( + setting: setting, + description: x[:description] + ) + end + end + + def detach_nameservers(ns_list) + to_delete = [] + ns_list.each do |ns_attrs| + to_delete << self.nameservers.where(ns_attrs) + end + + self.nameservers.delete(to_delete) + end + ### RENEW ### def renew(cur_exp_date, period, unit='y') @@ -127,8 +147,9 @@ class Domain < ActiveRecord::Base ### VALIDATIONS ### def validate_nameservers_count - sg = SettingGroup.find_by(code: SettingGroup::DOMAIN_VALIDATION_CODE) - min, max = sg.get(:ns_min_count).to_i, sg.get(:ns_max_count).to_i + sg = SettingGroup.domain_validation + min, max = sg.setting(:ns_min_count).value.to_i, sg.setting(:ns_max_count).value.to_i + unless nameservers.length.between?(min, max) errors.add(:nameservers, :out_of_range, {min: min, max: max}) end @@ -159,6 +180,33 @@ class Domain < ActiveRecord::Base }) if cur_exp_date.to_date != valid_to end + def epp_code_map + domain_validation_sg = SettingGroup.domain_validation + + { + '2302' => [ # Object exists + [:name_dirty, :taken], + [:name_dirty, :reserved] + ], + '2306' => [ # Parameter policy error + [:owner_contact, :blank], + [:admin_contacts, :blank], + [:valid_to, :epp_exp_dates_do_not_match] + ], + '2004' => [ # Parameter value range error + [:nameservers, :out_of_range, {min: domain_validation_sg.setting(:ns_min_count).value, max: domain_validation_sg.setting(:ns_max_count).value}], + [:period, :out_of_range] + ], + '2303' => [ # Object does not exist + [:owner_contact, :epp_registrant_not_found], + [:domain_contacts, :not_found] + ], + '2200' => [ + [:auth_info, :wrong_pw] + ] + } + end + ## SHARED # For domain transfer @@ -211,6 +259,18 @@ class Domain < ActiveRecord::Base p[:unit] end + def parse_statuses_from_frame(parsed_frame) + res = [] + + parsed_frame.css('status').each do |x| + res << { + value: x['s'], + description: x.text + } + end + res + end + def check_availability(domains) domains = [domains] if domains.is_a?(String) @@ -227,7 +287,7 @@ class Domain < ActiveRecord::Base end if Domain.find_by(name: x) - res << {name: x, avail: 0, reason: 'in use'} #confirm reason with current API + res << {name: x, avail: 0, reason: 'in use'} else res << {name: x, avail: 1} end diff --git a/app/models/domain_status.rb b/app/models/domain_status.rb new file mode 100644 index 000000000..80c898161 --- /dev/null +++ b/app/models/domain_status.rb @@ -0,0 +1,21 @@ +class DomainStatus < ActiveRecord::Base + # Domain statuses are stored as settings + include EppErrors + + EPP_ATTR_MAP = { + setting: 'status' + } + + belongs_to :domain + belongs_to :setting + + delegate :value, :code, to: :setting + + validates :setting, uniqueness: { scope: :domain_id } + + def epp_code_map + { + '2302' => [[:setting, :taken]] + } + end +end diff --git a/app/models/nameserver.rb b/app/models/nameserver.rb index e033d29b4..e3cf257c8 100644 --- a/app/models/nameserver.rb +++ b/app/models/nameserver.rb @@ -1,10 +1,6 @@ class Nameserver < ActiveRecord::Base include EppErrors - EPP_CODE_MAP = { - '2005' => ['Hostname is invalid', 'IPv4 is invalid', 'IPv6 is invalid'] - } - EPP_ATTR_MAP = { hostname: 'hostName' } @@ -15,4 +11,14 @@ class Nameserver < ActiveRecord::Base validates :hostname, format: { with: /\A(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])\z/ } validates :ipv4, format: { with: /\A(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\z/, allow_nil: true } validates :ipv6, format: { with: /(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]).){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]).){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))/, allow_nil: true } + + def epp_code_map + { + '2005' => [ + [:hostname, :invalid], + [:ipv4, :invalid], + [:ipv6, :invalid] + ] + } + end end diff --git a/app/models/setting.rb b/app/models/setting.rb index 3bdda2a0c..eb4dbe7f5 100644 --- a/app/models/setting.rb +++ b/app/models/setting.rb @@ -1,4 +1,6 @@ class Setting < ActiveRecord::Base belongs_to :setting_group + has_many :domain_statuses + has_many :domains, through: :domain_statuses validates :code, uniqueness: { scope: :setting_group_id } end diff --git a/app/models/setting_group.rb b/app/models/setting_group.rb index 0435677f8..3fea54616 100644 --- a/app/models/setting_group.rb +++ b/app/models/setting_group.rb @@ -3,10 +3,19 @@ class SettingGroup < ActiveRecord::Base accepts_nested_attributes_for :settings - DOMAIN_VALIDATION_CODE = 'domain_validation' + validates :code, uniqueness: true - def get(key) - s = settings.find_by(code: key.to_s) - s.try(:value) + def setting(key) + settings.find_by(code: key.to_s) + end + + class << self + def domain_validation + find_by(code: 'domain_validation') + end + + def domain_statuses + find_by(code: 'domain_statuses') + end end end diff --git a/app/views/setting_groups/index.haml b/app/views/setting_groups/index.haml index 492d76d54..07bbdcafd 100644 --- a/app/views/setting_groups/index.haml +++ b/app/views/setting_groups/index.haml @@ -1,14 +1,14 @@ %h2= t('shared.setting_groups') %hr -- @setting_groups.each do |x| - .row - .col-md-12 - %table.table.table-striped.table-bordered - %tr - %th{class: 'col-xs-9'} - = t('.setting_group') - %th{class: 'col-xs-2'} - = t('shared.action') +.row + .col-md-12 + %table.table.table-striped.table-bordered + %tr + %th{class: 'col-xs-9'} + = t('.setting_group') + %th{class: 'col-xs-2'} + = t('shared.action') + - @setting_groups.each do |x| %tr %td= t("setting_groups.codes.#{x.code}") %td= link_to(t('.edit_settings'), setting_group_path(x), class: 'btn btn-primary btn-xs') diff --git a/config/locales/en.yml b/config/locales/en.yml index f12ea140c..1214bbd9c 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -50,8 +50,6 @@ en: blank: 'Admin contact is missing' nameservers: out_of_range: 'Nameservers count must be between %{min}-%{max}' - hostname_invalid: 'Hostname is invalid' - ip_invalid: 'IPv4 is invalid' period: out_of_range: 'Period must add up to 1, 2 or 3 years' auth_info: @@ -68,6 +66,12 @@ en: attributes: code: taken: 'Code already exists' + domain_status: + attributes: + setting: + taken: 'Status already exists on this domain' + value: + taken: 'Status already exists on this domain' attributes: domain: name: 'Domain name' diff --git a/db/migrate/20140819095802_create_domains_statuses.rb b/db/migrate/20140819095802_create_domains_statuses.rb new file mode 100644 index 000000000..bd7323982 --- /dev/null +++ b/db/migrate/20140819095802_create_domains_statuses.rb @@ -0,0 +1,9 @@ +class CreateDomainsStatuses < ActiveRecord::Migration + def change + create_table :domain_statuses do |t| + t.integer :domain_id + t.integer :setting_id + t.string :description + end + end +end diff --git a/db/migrate/20140819103517_populate_domain_statuses.rb b/db/migrate/20140819103517_populate_domain_statuses.rb new file mode 100644 index 000000000..51695125f --- /dev/null +++ b/db/migrate/20140819103517_populate_domain_statuses.rb @@ -0,0 +1,25 @@ +class PopulateDomainStatuses < ActiveRecord::Migration + def change + sg = SettingGroup.create(code: 'domain_statuses') + sg.settings = [ + Setting.create(code: 'clientDeleteProhibited'.underscore, value: 'clientDeleteProhibited'), + Setting.create(code: 'serverDeleteProhibited'.underscore, value: 'serverDeleteProhibited'), + Setting.create(code: 'clientHold'.underscore, value: 'clientHold'), + Setting.create(code: 'serverHold'.underscore, value: 'serverHold'), + Setting.create(code: 'clientRenewProhibited'.underscore, value: 'clientRenewProhibited'), + Setting.create(code: 'serverRenewProhibited'.underscore, value: 'serverRenewProhibited'), + Setting.create(code: 'clientTransferProhibited'.underscore, value: 'clientTransferProhibited'), + Setting.create(code: 'serverTransferProhibited'.underscore, value: 'serverTransferProhibited'), + Setting.create(code: 'clientUpdateProhibited'.underscore, value: 'clientUpdateProhibited'), + Setting.create(code: 'serverUpdateProhibited'.underscore, value: 'serverUpdateProhibited'), + Setting.create(code: 'inactive', value: 'inactive'), + Setting.create(code: 'ok', value: 'ok'), + Setting.create(code: 'pendingCreate'.underscore, value: 'pendingCreate'), + Setting.create(code: 'pendingDelete'.underscore, value: 'pendingDelete'), + Setting.create(code: 'pendingRenew'.underscore, value: 'pendingRenew'), + Setting.create(code: 'pendingTransfer'.underscore, value: 'pendingTransfer'), + Setting.create(code: 'pendingUpdate'.underscore, value: 'pendingUpdate') + ] + sg.save + end +end diff --git a/db/schema.rb b/db/schema.rb index 71db15ed3..71d2b04e3 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: 20140815114000) do +ActiveRecord::Schema.define(version: 20140819103517) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -61,6 +61,12 @@ ActiveRecord::Schema.define(version: 20140815114000) do t.datetime "updated_at" end + create_table "domain_statuses", force: true do |t| + t.integer "domain_id" + t.integer "setting_id" + t.string "description" + end + create_table "domains", force: true do |t| t.string "name" t.integer "registrar_id" diff --git a/spec/epp/domain_spec.rb b/spec/epp/domain_spec.rb index 87d5a5f60..0ef3e6671 100644 --- a/spec/epp/domain_spec.rb +++ b/spec/epp/domain_spec.rb @@ -7,6 +7,7 @@ describe 'EPP Domain', epp: true do before(:each) do Fabricate(:epp_user) Fabricate(:domain_validation_setting_group) + Fabricate(:domain_statuses_setting_group) end it 'returns error if contact does not exists' do @@ -244,13 +245,51 @@ describe 'EPP Domain', epp: true do expect(response[:results][0][:msg]).to eq('Domain not found') end - it 'updates domain', pending: true do - response = epp_request('domains/update.xml') + it 'updates domain and adds objects' do + response = epp_request('domains/update_add_objects.xml') + expect(response[:results][0][:result_code]).to eq('2303') + expect(response[:results][0][:msg]).to eq('Contact was not found') + + Fabricate(:contact, code: 'mak21') + + response = epp_request('domains/update_add_objects.xml') expect(response[:results][0][:result_code]).to eq('1000') d = Domain.first - new_ns = d.nameservers.find_by(hostname: 'ns2.example.com') + + new_ns_count = d.nameservers.where(hostname: ['ns1.example.com', 'ns2.example.com']).count + expect(new_ns_count).to eq(2) + + new_contact = d.tech_contacts.find_by(code: 'mak21') + expect(new_contact).to be_truthy + + expect(d.domain_statuses.count).to eq(2) + expect(d.domain_statuses.first.description).to eq('Payment overdue.') + expect(d.domain_statuses.first.value).to eq('clientHold') + expect(d.domain_statuses.first.code).to eq('client_hold') + + expect(d.domain_statuses.last.value).to eq('clientUpdateProhibited') + + response = epp_request('domains/update_add_objects.xml') + + expect(response[:results][0][:result_code]).to eq('2302') + expect(response[:results][0][:msg]).to eq('Status already exists on this domain') + expect(d.domain_statuses.count).to eq(2) + end + + it 'updates a domain and removes objects' do + Fabricate(:contact, code: 'mak21') + epp_request('domains/update_add_objects.xml') + + d = Domain.last + + new_ns = d.nameservers.find_by(hostname: 'ns1.example.com') expect(new_ns).to be_truthy + + response = epp_request('domains/update_remove_objects.xml') + + rem_ns = d.nameservers.find_by(hostname: 'ns1.example.com') + expect(rem_ns).to be_falsey end end @@ -272,7 +311,7 @@ describe 'EPP Domain', epp: true do expect(name.text).to eq('example.ee') expect(name[:avail]).to eq('0') - expect(reason.text).to eq('in use') #confirm this with current API + expect(reason.text).to eq('in use') end it 'checks multiple domains' do diff --git a/spec/epp/requests/domains/update.xml b/spec/epp/requests/domains/update.xml index 0c4bd59dd..d3a5b9a6c 100644 --- a/spec/epp/requests/domains/update.xml +++ b/spec/epp/requests/domains/update.xml @@ -7,6 +7,7 @@ example.ee + ns1.example.com ns2.example.com mak21 diff --git a/spec/epp/requests/domains/update_add_objects.xml b/spec/epp/requests/domains/update_add_objects.xml new file mode 100644 index 000000000..8cfd17616 --- /dev/null +++ b/spec/epp/requests/domains/update_add_objects.xml @@ -0,0 +1,22 @@ + + + + + + example.ee + + + ns1.example.com + ns2.example.com + + mak21 + Payment overdue. + + + + + ABC-12345 + + diff --git a/spec/epp/requests/domains/update_remove_objects.xml b/spec/epp/requests/domains/update_remove_objects.xml new file mode 100644 index 000000000..fd79586c3 --- /dev/null +++ b/spec/epp/requests/domains/update_remove_objects.xml @@ -0,0 +1,19 @@ + + + + + + example.ee + + + ns1.example.com + + sh8013 + + + + + ABC-12345 + + diff --git a/spec/fabricators/setting_group_fabricator.rb b/spec/fabricators/setting_group_fabricator.rb index 0a05b7125..f4bb09221 100644 --- a/spec/fabricators/setting_group_fabricator.rb +++ b/spec/fabricators/setting_group_fabricator.rb @@ -13,3 +13,11 @@ Fabricator(:domain_validation_setting_group, from: :setting_group) do Fabricate(:setting, code: 'ns_max_count', value: 13) ]} end + +Fabricator(:domain_statuses_setting_group, from: :setting_group) do + code 'domain_statuses' + settings { [ + Fabricate(:setting, code: 'client_hold', value: 'clientHold'), + Fabricate(:setting, code: 'client_update_prohibited', value: 'clientUpdateProhibited') + ]} +end diff --git a/spec/models/domain_spec.rb b/spec/models/domain_spec.rb index 244fe75c1..2f674de5e 100644 --- a/spec/models/domain_spec.rb +++ b/spec/models/domain_spec.rb @@ -48,6 +48,19 @@ describe Domain do admin_contacts: ["Admin contact is missing"], nameservers: ["Nameservers count must be between 1-13"] }) + + sg = SettingGroup.domain_validation + min = sg.setting(:ns_min_count) + max = sg.setting(:ns_max_count) + + min.value = 2 + min.save + + max.value = 7 + max.save + + expect(d.valid?).to be false + expect(d.errors.messages[:nameservers]).to eq(['Nameservers count must be between 2-7']) end it 'does not create a reserved domain' do diff --git a/spec/models/setting_spec.rb b/spec/models/setting_spec.rb index 5aaa13b50..3a8e60330 100644 --- a/spec/models/setting_spec.rb +++ b/spec/models/setting_spec.rb @@ -13,7 +13,7 @@ describe Setting do err = sg.settings.last.errors[:code].first expect(err).to eq('Code already exists') - sg_2 = Fabricate(:setting_group) + sg_2 = Fabricate(:setting_group, code: 'domain_statuses') sg_2.settings.build(code: 'this_is_code') expect(sg_2.save).to be true