From 3e983e60829a7ab83d47b8573f05576a2829c57f Mon Sep 17 00:00:00 2001 From: Martin Lensment Date: Thu, 11 Sep 2014 11:31:46 +0300 Subject: [PATCH] Refactor EPP code to EppDomain --- app/helpers/epp/domains_helper.rb | 18 +- app/models/domain.rb | 319 ----------------------------- app/models/epp_domain.rb | 324 ++++++++++++++++++++++++++++++ config/locales/en.yml | 4 +- 4 files changed, 335 insertions(+), 330 deletions(-) create mode 100644 app/models/epp_domain.rb diff --git a/app/helpers/epp/domains_helper.rb b/app/helpers/epp/domains_helper.rb index bc72785f7..463bf5539 100644 --- a/app/helpers/epp/domains_helper.rb +++ b/app/helpers/epp/domains_helper.rb @@ -1,7 +1,7 @@ module Epp::DomainsHelper def create_domain - Domain.transaction do - @domain = Domain.new(domain_create_params) + EppDomain.transaction do + @domain = EppDomain.new(domain_create_params) @domain.attach_owner_contact(@ph[:registrant]) if @ph[:registrant] @@ -29,7 +29,7 @@ module Epp::DomainsHelper def check_domain ph = params_hash['epp']['command']['check']['check'] - @domains = Domain.check_availability(ph[:name]) + @domains = EppDomain.check_availability(ph[:name]) render '/epp/domains/check' end @@ -52,7 +52,7 @@ module Epp::DomainsHelper end def update_domain - Domain.transaction do + EppDomain.transaction do @domain = find_domain handle_errors(@domain) and return unless @domain @@ -106,15 +106,15 @@ module Epp::DomainsHelper def domain_create_params period = (@ph[:period].to_i == 0) ? 1 : @ph[:period].to_i - period_unit = Domain.parse_period_unit_from_frame(parsed_frame) || 'y' - valid_to = Date.today + Domain.convert_period_to_time(period, period_unit) + period_unit = EppDomain.parse_period_unit_from_frame(parsed_frame) || 'y' + valid_to = Date.today + EppDomain.convert_period_to_time(period, period_unit) { name: @ph[:name], registrar_id: current_epp_user.registrar.try(:id), registered_at: Time.now, period: (@ph[:period].to_i == 0) ? 1 : @ph[:period].to_i, - period_unit: Domain.parse_period_unit_from_frame(parsed_frame) || 'y', + period_unit: EppDomain.parse_period_unit_from_frame(parsed_frame) || 'y', valid_from: Date.today, valid_to: valid_to } @@ -160,8 +160,8 @@ module Epp::DomainsHelper ## SHARED def find_domain(secure = { secure: true }) - domain = Domain.find_by(name: @ph[:name], registrar: current_epp_user.registrar) if secure[:secure] == true - domain = Domain.find_by(name: @ph[:name]) if secure[:secure] == false + domain = EppDomain.find_by(name: @ph[:name], registrar: current_epp_user.registrar) if secure[:secure] == true + domain = EppDomain.find_by(name: @ph[:name]) if secure[:secure] == false unless domain epp_errors << { code: '2303', msg: I18n.t('errors.messages.epp_domain_not_found'), value: { obj: 'name', val: @ph[:name] } } diff --git a/app/models/domain.rb b/app/models/domain.rb index 4a8791cb2..79b1a493e 100644 --- a/app/models/domain.rb +++ b/app/models/domain.rb @@ -1,14 +1,6 @@ class Domain < ActiveRecord::Base # TODO whois requests ip whitelist for full info for own domains and partial info for other domains # TODO most inputs should be trimmed before validatation, probably some global logic? - - include EppErrors - EPP_ATTR_MAP = { - owner_contact: 'registrant', - name_dirty: 'name', - period: 'period' - } - belongs_to :registrar belongs_to :owner_contact, class_name: 'Contact' @@ -52,205 +44,6 @@ class Domain < ActiveRecord::Base write_attribute(:name_dirty, value) end - ### CREATE & UPDATE ### - - def parse_and_attach_domain_dependencies(parsed_frame) - 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 parse_and_detach_domain_dependencies(parsed_frame) - 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)) - - errors.empty? - end - - def parse_and_update_domain_dependencies(parsed_frame) - owner_contact_code = parsed_frame.css('registrant').try(:text) - attach_owner_contact(owner_contact_code) if owner_contact_code.present? - - errors.empty? - end - - # TODO: Find out if there are any attributes that can be changed - # if not, delete this method - def parse_and_update_domain_attributes(parsed_frame) - #assign_attributes(self.class.parse_update_params_from_frame(parsed_frame)) - - errors.empty? - end - - def attach_owner_contact(code) - self.owner_contact = Contact.find_by(code: code) - - return if owner_contact - - add_epp_error('2303', 'registrant', code, [:owner_contact, :not_found]) - end - - def attach_contacts(contacts) - contacts.each do |k, v| - v.each do |x| - contact = Contact.find_by(code: x[:contact]) - if contact - attach_contact(k, contact) - else - # Detailed error message with value to display in EPP response - add_epp_error('2303', 'contact', x[:contact], [:domain_contacts, :not_found]) - end - end - end - - return unless owner_contact - - attach_contact(DomainContact::TECH, owner_contact) if tech_contacts_count.zero? - attach_contact(DomainContact::ADMIN, owner_contact) if admin_contacts_count.zero? && owner_contact.citizen? - end - - def attach_contact(type, contact) - domain_contacts.build(contact: contact, contact_type: DomainContact::TECH) if type.to_sym == :tech - domain_contacts.build(contact: contact, contact_type: DomainContact::ADMIN) if type.to_sym == :admin - end - - def attach_nameservers(ns_list) - ns_list.each do |ns_attrs| - nameservers.build(ns_attrs) - end - end - - def attach_statuses(status_list) - status_list.each do |x| - unless DomainStatus::STATUSES.include?(x[:value]) - add_epp_error('2303', 'status', x[:value], [:domain_statuses, :not_found]) - next - end - - domain_statuses.build( - value: x[:value], - description: x[:description] - ) - end - end - - def detach_contacts(contact_list) - to_delete = [] - contact_list.each do |k, v| - v.each do |x| - contact = domain_contacts.joins(:contact).where(contacts: { code: x[:contact] }, contact_type: k.to_s) - if contact.blank? - add_epp_error('2303', 'contact', x[:contact], [:domain_contacts, :not_found]) - else - to_delete << contact - end - end - end - - domain_contacts.delete(to_delete) - end - - def detach_nameservers(ns_list) - to_delete = [] - ns_list.each do |ns_attrs| - nameserver = nameservers.where(ns_attrs) - if nameserver.blank? - add_epp_error('2303', 'hostObj', ns_attrs[:hostname], [:nameservers, :not_found]) - else - to_delete << nameserver - end - end - - nameservers.delete(to_delete) - end - - def detach_statuses(status_list) - to_delete = [] - status_list.each do |x| - status = domain_statuses.find_by(value: x[:value]) - if status.blank? - add_epp_error('2303', 'status', x[:value], [:domain_statuses, :not_found]) - else - to_delete << status - end - end - - domain_statuses.delete(to_delete) - end - - ### RENEW ### - - def renew(cur_exp_date, period, unit = 'y') - # TODO: Check how much time before domain exp date can it be renewed - validate_exp_dates(cur_exp_date) - return false if errors.any? - - p = self.class.convert_period_to_time(period, unit) - - self.valid_to = valid_to + p - self.period = period - self.period_unit = unit - save - end - - ### TRANSFER ### - - def transfer(params) - return false unless authenticate(params[:pw]) - - pt = pending_transfer - if pt && params[:action] == 'approve' - return approve_pending_transfer(params[:current_user]) - end - - return true if pt - - wait_time = SettingGroup.domain_general.setting(:transfer_wait_time).value.to_i - - if wait_time > 0 - domain_transfers.create( - status: DomainTransfer::PENDING, - transfer_requested_at: Time.zone.now, - transfer_to: params[:current_user].registrar, - transfer_from: registrar - ) - else - domain_transfers.create( - status: DomainTransfer::SERVER_APPROVED, - transfer_requested_at: Time.zone.now, - transferred_at: Time.zone.now, - transfer_to: params[:current_user].registrar, - transfer_from: registrar - ) - - generate_auth_info - - self.registrar = params[:current_user].registrar - save - end - end - - def approve_pending_transfer(current_user) - pt = pending_transfer - if current_user.registrar != pt.transfer_from - add_epp_error('2304', nil, nil, I18n.t('shared.transfer_can_be_approved_only_by_current_registrar')) - return false - end - - pt.update( - status: DomainTransfer::CLIENT_APPROVED, - transferred_at: Time.zone.now - ) - - generate_auth_info - - self.registrar = pt.transfer_to - save - end - def pending_transfer domain_transfers.find_by(status: DomainTransfer::PENDING) end @@ -310,11 +103,6 @@ class Domain < ActiveRecord::Base errors.add(:period, :out_of_range) unless valid_values.include?(period.to_s) end - def validate_exp_dates(cur_exp_date) - return if cur_exp_date.to_date == valid_to - add_epp_error('2306', 'curExpDate', cur_exp_date, I18n.t('errors.messages.epp_exp_dates_do_not_match')) - end - def all_dependencies_valid? validate_nameservers_count validate_admin_contacts_count @@ -322,33 +110,6 @@ class Domain < ActiveRecord::Base errors.empty? end - def epp_code_map # rubocop:disable Metrics/MethodLength - domain_validation_sg = SettingGroup.domain_validation - - { - '2302' => [ # Object exists - [:name_dirty, :taken], - [:name_dirty, :reserved] - ], - '2306' => [ # Parameter policy error - [:owner_contact, :blank], - [:admin_contacts, :out_of_range] - ], - '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] - ], - '2200' => [ - [:auth_info, :wrong_pw] - ] - } - end - ## SHARED def to_s @@ -361,12 +122,6 @@ class Domain < ActiveRecord::Base end while self.class.exists?(auth_info: auth_info) end - # For domain transfer - def authenticate(pw) - errors.add(:auth_info, { msg: errors.generate_message(:auth_info, :wrong_pw) }) if pw != auth_info - errors.empty? - end - def tech_contacts_count domain_contacts.select { |x| x.contact_type == DomainContact::TECH }.count end @@ -381,79 +136,5 @@ class Domain < ActiveRecord::Base return period.to_i.months if unit == 'm' return period.to_i.years if unit == 'y' end - - def parse_contacts_from_frame(parsed_frame) - res = {} - DomainContact::TYPES.each do |ct| - res[ct.to_sym] ||= [] - parsed_frame.css("contact[type='#{ct}']").each do |x| - res[ct.to_sym] << Hash.from_xml(x.to_s).with_indifferent_access - end - end - - res - end - - def parse_nameservers_from_frame(parsed_frame) - res = [] - parsed_frame.css('hostAttr').each do |x| - res << { - hostname: x.css('hostName').first.try(:text), - ipv4: x.css('hostAddr[ip="v4"]').first.try(:text), - ipv6: x.css('hostAddr[ip="v6"]').first.try(:text) - } - end - - parsed_frame.css('hostObj').each do |x| - res << { - hostname: x.text - } - end - - res - end - - def parse_period_unit_from_frame(parsed_frame) - p = parsed_frame.css('period').first - return nil unless p - 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) - - res = [] - domains.each do |x| - unless DomainNameValidator.validate_format(x) - res << { name: x, avail: 0, reason: 'invalid format' } - next - end - - unless DomainNameValidator.validate_reservation(x) - res << { name: x, avail: 0, reason: I18n.t('errors.messages.epp_domain_reserved') } - next - end - - if Domain.find_by(name: x) - res << { name: x, avail: 0, reason: 'in use' } - else - res << { name: x, avail: 1 } - end - end - - res - end end end diff --git a/app/models/epp_domain.rb b/app/models/epp_domain.rb new file mode 100644 index 000000000..93c8dec9d --- /dev/null +++ b/app/models/epp_domain.rb @@ -0,0 +1,324 @@ +class EppDomain < Domain + include EppErrors + + EPP_ATTR_MAP = { + owner_contact: 'registrant', + name_dirty: 'name', + period: 'period' + } + + def parse_and_attach_domain_dependencies(parsed_frame) + 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 parse_and_detach_domain_dependencies(parsed_frame) + 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)) + + errors.empty? + end + + def parse_and_update_domain_dependencies(parsed_frame) + owner_contact_code = parsed_frame.css('registrant').try(:text) + attach_owner_contact(owner_contact_code) if owner_contact_code.present? + + errors.empty? + end + + # TODO: Find out if there are any attributes that can be changed + # if not, delete this method + def parse_and_update_domain_attributes(parsed_frame) + #assign_attributes(self.class.parse_update_params_from_frame(parsed_frame)) + + errors.empty? + end + + def attach_owner_contact(code) + self.owner_contact = Contact.find_by(code: code) + + return if owner_contact + + add_epp_error('2303', 'registrant', code, [:owner_contact, :not_found]) + end + + def attach_contacts(contacts) + contacts.each do |k, v| + v.each do |x| + contact = Contact.find_by(code: x[:contact]) + if contact + attach_contact(k, contact) + else + # Detailed error message with value to display in EPP response + add_epp_error('2303', 'contact', x[:contact], [:domain_contacts, :not_found]) + end + end + end + + return unless owner_contact + + attach_contact(DomainContact::TECH, owner_contact) if tech_contacts_count.zero? + attach_contact(DomainContact::ADMIN, owner_contact) if admin_contacts_count.zero? && owner_contact.citizen? + end + + def attach_contact(type, contact) + domain_contacts.build(contact: contact, contact_type: DomainContact::TECH) if type.to_sym == :tech + domain_contacts.build(contact: contact, contact_type: DomainContact::ADMIN) if type.to_sym == :admin + end + + def attach_nameservers(ns_list) + ns_list.each do |ns_attrs| + nameservers.build(ns_attrs) + end + end + + def attach_statuses(status_list) + status_list.each do |x| + unless DomainStatus::STATUSES.include?(x[:value]) + add_epp_error('2303', 'status', x[:value], [:domain_statuses, :not_found]) + next + end + + domain_statuses.build( + value: x[:value], + description: x[:description] + ) + end + end + + def detach_contacts(contact_list) + to_delete = [] + contact_list.each do |k, v| + v.each do |x| + contact = domain_contacts.joins(:contact).where(contacts: { code: x[:contact] }, contact_type: k.to_s) + if contact.blank? + add_epp_error('2303', 'contact', x[:contact], [:domain_contacts, :not_found]) + else + to_delete << contact + end + end + end + + domain_contacts.delete(to_delete) + end + + def detach_nameservers(ns_list) + to_delete = [] + ns_list.each do |ns_attrs| + nameserver = nameservers.where(ns_attrs) + if nameserver.blank? + add_epp_error('2303', 'hostObj', ns_attrs[:hostname], [:nameservers, :not_found]) + else + to_delete << nameserver + end + end + + nameservers.delete(to_delete) + end + + def detach_statuses(status_list) + to_delete = [] + status_list.each do |x| + status = domain_statuses.find_by(value: x[:value]) + if status.blank? + add_epp_error('2303', 'status', x[:value], [:domain_statuses, :not_found]) + else + to_delete << status + end + end + + domain_statuses.delete(to_delete) + end + + ### RENEW ### + + def renew(cur_exp_date, period, unit = 'y') + # TODO: Check how much time before domain exp date can it be renewed + validate_exp_dates(cur_exp_date) + return false if errors.any? + + p = self.class.convert_period_to_time(period, unit) + + self.valid_to = valid_to + p + self.period = period + self.period_unit = unit + save + end + + ### TRANSFER ### + + def transfer(params) + return false unless authenticate(params[:pw]) + + pt = pending_transfer + if pt && params[:action] == 'approve' + return approve_pending_transfer(params[:current_user]) + end + + return true if pt + + wait_time = SettingGroup.domain_general.setting(:transfer_wait_time).value.to_i + + if wait_time > 0 + domain_transfers.create( + status: DomainTransfer::PENDING, + transfer_requested_at: Time.zone.now, + transfer_to: params[:current_user].registrar, + transfer_from: registrar + ) + else + domain_transfers.create( + status: DomainTransfer::SERVER_APPROVED, + transfer_requested_at: Time.zone.now, + transferred_at: Time.zone.now, + transfer_to: params[:current_user].registrar, + transfer_from: registrar + ) + + generate_auth_info + + self.registrar = params[:current_user].registrar + save + end + end + + def approve_pending_transfer(current_user) + pt = pending_transfer + if current_user.registrar != pt.transfer_from + add_epp_error('2304', nil, nil, I18n.t('shared.transfer_can_be_approved_only_by_current_registrar')) + return false + end + + pt.update( + status: DomainTransfer::CLIENT_APPROVED, + transferred_at: Time.zone.now + ) + + generate_auth_info + + self.registrar = pt.transfer_to + save + end + + ### VALIDATIONS ### + + def validate_exp_dates(cur_exp_date) + return if cur_exp_date.to_date == valid_to + add_epp_error('2306', 'curExpDate', cur_exp_date, I18n.t('errors.messages.epp_exp_dates_do_not_match')) + end + + def epp_code_map # rubocop:disable Metrics/MethodLength + domain_validation_sg = SettingGroup.domain_validation + + { + '2302' => [ # Object exists + [:name_dirty, :taken], + [:name_dirty, :reserved] + ], + '2306' => [ # Parameter policy error + [:owner_contact, :blank], + [:admin_contacts, :out_of_range] + ], + '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] + ], + '2200' => [ + [:auth_info, :wrong_pw] + ] + } + end + + ## SHARED + + # For domain transfer + def authenticate(pw) + errors.add(:auth_info, { msg: errors.generate_message(:auth_info, :wrong_pw) }) if pw != auth_info + errors.empty? + end + + class << self + def parse_contacts_from_frame(parsed_frame) + res = {} + DomainContact::TYPES.each do |ct| + res[ct.to_sym] ||= [] + parsed_frame.css("contact[type='#{ct}']").each do |x| + res[ct.to_sym] << Hash.from_xml(x.to_s).with_indifferent_access + end + end + + res + end + + def parse_nameservers_from_frame(parsed_frame) + res = [] + parsed_frame.css('hostAttr').each do |x| + res << { + hostname: x.css('hostName').first.try(:text), + ipv4: x.css('hostAddr[ip="v4"]').first.try(:text), + ipv6: x.css('hostAddr[ip="v6"]').first.try(:text) + } + end + + parsed_frame.css('hostObj').each do |x| + res << { + hostname: x.text + } + end + + res + end + + def parse_period_unit_from_frame(parsed_frame) + p = parsed_frame.css('period').first + return nil unless p + 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) + + res = [] + domains.each do |x| + unless DomainNameValidator.validate_format(x) + res << { name: x, avail: 0, reason: 'invalid format' } + next + end + + unless DomainNameValidator.validate_reservation(x) + res << { name: x, avail: 0, reason: I18n.t('errors.messages.epp_domain_reserved') } + next + end + + if Domain.find_by(name: x) + res << { name: x, avail: 0, reason: 'in use' } + else + res << { name: x, avail: 1 } + end + end + + res + end + end +end diff --git a/config/locales/en.yml b/config/locales/en.yml index f288999d7..a4e0f2aae 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -67,7 +67,7 @@ en: blank: "Required parameter missing - ident" domains: exist: 'Object association prohibits operation' - domain: + epp_domain: attributes: name_dirty: invalid: 'Domain name is invalid' @@ -110,7 +110,7 @@ en: setting_id: taken: 'Status already exists on this domain' attributes: - domain: + epp_domain: name: 'Domain name' name_dirty: 'Domain name' name_puny: 'Domain name'