diff --git a/app/controllers/epp/contacts_controller.rb b/app/controllers/epp/contacts_controller.rb index b6a26a626..df9755af6 100644 --- a/app/controllers/epp/contacts_controller.rb +++ b/app/controllers/epp/contacts_controller.rb @@ -1,3 +1,5 @@ +require 'deserializers/xml/contact_update' + module Epp class ContactsController < BaseController before_action :find_contact, only: [:info, :update, :delete] @@ -43,9 +45,14 @@ module Epp def update authorize! :update, @contact, @password - frame = params[:parsed_frame] + collected_data = ::Deserializers::Xml::ContactUpdate.new(params[:parsed_frame]) + action = Actions::ContactUpdate.new(@contact, + collected_data.contact, + collected_data.legal_document, + collected_data.ident, + current_user) - if @contact.update_attributes(frame, current_user) + if action.call if !address_processing? && address_given? @response_code = 1100 @response_description = t('epp.contacts.completed_without_address') diff --git a/app/models/actions/contact_update.rb b/app/models/actions/contact_update.rb new file mode 100644 index 000000000..5b94cf918 --- /dev/null +++ b/app/models/actions/contact_update.rb @@ -0,0 +1,105 @@ +module Actions + class ContactUpdate + attr_reader :contact + attr_reader :new_attributes + attr_reader :legal_document + attr_reader :ident + attr_reader :user + + def initialize(contact, new_attributes, legal_document, ident, user) + @contact = contact + @new_attributes = new_attributes + @legal_document = legal_document + @ident = ident + @user = user + end + + def call + maybe_remove_address + maybe_update_statuses + maybe_update_ident + maybe_attach_legal_doc + commit + end + + def maybe_remove_address + return if Setting.address_processing? + + new_attributes.delete(:city) + new_attributes.delete(:zip) + new_attributes.delete(:street) + new_attributes.delete(:state) + new_attributes.delete(:country_code) + end + + def maybe_update_statuses + return unless Setting.client_status_editing_enabled + + new_statuses = + contact.statuses - new_attributes[:statuses_to_remove] + new_attributes[:statuses_to_add] + + new_attributes[:statuses] = new_statuses + end + + def maybe_attach_legal_doc + return unless legal_document + + document = contact.legal_documents.create( + document_type: legal_document[:type], + body: legal_document[:body] + ) + + contact.legal_document_id = document.id + end + + def maybe_update_ident + return unless ident[:ident] + + if contact.identifier.valid? + submitted_ident = ::Contact::Ident.new(code: ident[:ident], + type: ident[:ident_type], + country_code: ident[:ident_country_code]) + + if submitted_ident != contact.identifier + contact.add_epp_error('2308', nil, nil, I18n.t('epp.contacts.errors.valid_ident')) + @error = true + end + else + ident_update_attempt = ident[:ident] != contact.ident + + if ident_update_attempt + contact.add_epp_error('2308', nil, nil, I18n.t('epp.contacts.errors.ident_update')) + @error = true + end + + identifier = ::Contact::Ident.new(code: ident[:ident], + type: ident[:ident_type], + country_code: ident[:ident_country_code]) + + identifier.validate + + contact.identifier = identifier + contact.ident_updated_at ||= Time.zone.now + end + end + + def commit + return false if @error + + contact.upid = user.registrar&.id + contact.up_date = Time.zone.now + + contact.attributes = new_attributes + + email_changed = contact.will_save_change_to_email? + old_email = contact.email_was + updated = contact.save + + if updated && email_changed && contact.registrant? + ContactMailer.email_changed(contact: contact, old_email: old_email).deliver_now + end + + updated + end + end +end diff --git a/app/models/epp/contact.rb b/app/models/epp/contact.rb index d14bf1e1b..3fb87a1f0 100644 --- a/app/models/epp/contact.rb +++ b/app/models/epp/contact.rb @@ -1,5 +1,6 @@ require 'deserializers/xml/legal_document' require 'deserializers/xml/ident' +require 'deserializers/xml/contact' class Epp::Contact < Contact include EppErrors @@ -23,25 +24,8 @@ class Epp::Contact < Contact end def attrs_from(frame, new_record: false) - f = frame - at = {}.with_indifferent_access - at[:name] = f.css('postalInfo name').text if f.css('postalInfo name').present? - at[:org_name] = f.css('postalInfo org').text if f.css('postalInfo org').present? - at[:email] = f.css('email').text if f.css('email').present? - at[:fax] = f.css('fax').text if f.css('fax').present? - at[:phone] = f.css('voice').text if f.css('voice').present? - - if address_processing? - at[:city] = f.css('postalInfo addr city').text if f.css('postalInfo addr city').present? - at[:zip] = f.css('postalInfo addr pc').text if f.css('postalInfo addr pc').present? - at[:street] = f.css('postalInfo addr street').text if f.css('postalInfo addr street').present? - at[:state] = f.css('postalInfo addr sp').text if f.css('postalInfo addr sp').present? - at[:country_code] = f.css('postalInfo addr cc').text if f.css('postalInfo addr cc').present? - end - - at[:auth_info] = f.css('authInfo pw').text if f.css('authInfo pw').present? - - ident_attrs = ::Deserializers::Xml::Ident.new(f).call + at = ::Deserializers::Xml::Contact.new(frame).call + ident_attrs = ::Deserializers::Xml::Ident.new(frame).call at.merge!(ident_attrs) if new_record at end @@ -72,8 +56,8 @@ class Epp::Contact < Contact res end - end + delegate :ident_attr_valid?, to: :class def epp_code_map @@ -104,102 +88,6 @@ class Epp::Contact < Contact } end - def update_attributes(frame, current_user) - return super if frame.blank? - at = {}.with_indifferent_access - at.deep_merge!(self.class.attrs_from(frame.css('chg'), new_record: false)) - - if Setting.client_status_editing_enabled - at[:statuses] = statuses - statuses_attrs(frame.css('rem'), 'rem') + statuses_attrs(frame.css('add'), 'add') - end - - if doc = attach_legal_document(::Deserializers::Xml::LegalDocument.new(frame).call) - frame.css("legalDocument").first.content = doc.path if doc&.persisted? - self.legal_document_id = doc.id - end - - ident_frame = frame.css('ident').first - - # https://github.com/internetee/registry/issues/576 - if ident_frame - if identifier.valid? - submitted_ident = Ident.new(code: ident_frame.text, - type: ident_frame.attr('type'), - country_code: ident_frame.attr('cc')) - - if submitted_ident != identifier - add_epp_error('2308', nil, nil, I18n.t('epp.contacts.errors.valid_ident')) - return - end - else - ident_update_attempt = ident_frame.text.present? && (ident_frame.text != ident) - - if ident_update_attempt - add_epp_error('2308', nil, nil, I18n.t('epp.contacts.errors.ident_update')) - return - end - - identifier = Ident.new(code: ident, - type: ident_frame.attr('type'), - country_code: ident_frame.attr('cc')) - - identifier.validate - - self.identifier = identifier - self.ident_updated_at ||= Time.zone.now - end - end - - self.upid = current_user.registrar.id if current_user.registrar - self.up_date = Time.zone.now - - self.attributes = at - - email_changed = will_save_change_to_email? - old_email = email_was - updated = save - - if updated && email_changed && registrant? - ContactMailer.email_changed(contact: self, old_email: old_email).deliver_now - end - - updated - end - - def statuses_attrs(frame, action) - status_list = status_list_from(frame) - - if action == 'rem' - to_destroy = [] - status_list.each do |status| - if statuses.include?(status) - to_destroy << status - else - add_epp_error('2303', 'status', status, [:contact_statuses, :not_found]) - end - end - - return to_destroy - else - return status_list - end - end - - def status_list_from(frame) - status_list = [] - - frame.css('status').each do |status| - unless Contact::CLIENT_STATUSES.include?(status['s']) - add_epp_error('2303', 'status', status['s'], [:domain_statuses, :not_found]) - next - end - - status_list << status['s'] - end - - status_list - end - def attach_legal_document(legal_document_data) return unless legal_document_data diff --git a/lib/deserializers/xml/contact.rb b/lib/deserializers/xml/contact.rb new file mode 100644 index 000000000..4dd29c683 --- /dev/null +++ b/lib/deserializers/xml/contact.rb @@ -0,0 +1,61 @@ +module Deserializers + module Xml + class Contact + attr_reader :frame + + def initialize(frame) + @frame = frame + end + + def call + attributes = { + name: if_present('postalInfo name'), + org_name: if_present('postalInfo org'), + email: if_present('email'), + fax: if_present('fax'), + phone: if_present('voice'), + + # Address fields + city: if_present('postalInfo addr city'), + zip: if_present('postalInfo addr pc'), + street: if_present('postalInfo addr street'), + state: if_present('postalInfo addr sp'), + country_code: if_present('postalInfo addr cc'), + + # Auth info + auth_info: if_present('authInfo pw'), + + # statuses + statuses_to_add: statuses_to_add, + statuses_to_remove: statuses_to_remove, + } + + attributes.compact + end + + def if_present(css_path) + return if frame.css(css_path).blank? + + frame.css(css_path).text + end + + def statuses_to_add + statuses_frame = frame.css('add') + return if statuses_frame.blank? + + statuses_frame.css('status').map do |status| + status['s'] + end + end + + def statuses_to_remove + statuses_frame = frame.css('rem') + return if statuses_frame.blank? + + statuses_frame.css('status').map do |status| + status['s'] + end + end + end + end +end diff --git a/lib/deserializers/xml/contact_update.rb b/lib/deserializers/xml/contact_update.rb new file mode 100644 index 000000000..b3bc6fe4a --- /dev/null +++ b/lib/deserializers/xml/contact_update.rb @@ -0,0 +1,27 @@ +require 'deserializers/xml/legal_document' +require 'deserializers/xml/ident' +require 'deserializers/xml/contact' + +module Deserializers + module Xml + class ContactUpdate + attr_reader :frame + + def initialize(frame) + @frame = frame + end + + def contact + @contact ||= ::Deserializers::Xml::Contact.new(frame).call + end + + def ident + @ident ||= ::Deserializers::Xml::Ident.new(frame).call + end + + def legal_document + @legal_document ||= ::Deserializers::Xml::LegalDocument.new(frame).call + end + end + end +end diff --git a/test/lib/deserializers/xml/contact_test.rb b/test/lib/deserializers/xml/contact_test.rb new file mode 100644 index 000000000..4621efeb2 --- /dev/null +++ b/test/lib/deserializers/xml/contact_test.rb @@ -0,0 +1,114 @@ +require 'test_helper' +require 'deserializers/xml/contact' + +class DeserializersXmlContactTest < ActiveSupport::TestCase + def test_trims_empty_values + xml_string = <<-XML + XML + + nokogiri_frame = Nokogiri::XML(xml_string).remove_namespaces! + instance = ::Deserializers::Xml::Contact.new(nokogiri_frame) + assert_equal instance.call, {} + end + + def test_handles_update + xml_string = <<-XML + + + + + + john-001 + + + new name + Org + + +123.4 + new-email@inbox.test + + + + + + XML + + nokogiri_frame = Nokogiri::XML(xml_string).remove_namespaces! + instance = ::Deserializers::Xml::Contact.new(nokogiri_frame) + assert_equal instance.call, { name: 'new name', + org_name: 'Org', + email: 'new-email@inbox.test', + phone: '+123.4' } + end + + def test_handles_create + name = 'new' + email = 'new@registrar.test' + phone = '+1.2' + + xml_string = <<-XML + + + + + + + #{name} + + #{phone} + #{email} + + + + + any + + + + + XML + + nokogiri_frame = Nokogiri::XML(xml_string).remove_namespaces! + instance = ::Deserializers::Xml::Contact.new(nokogiri_frame) + assert_equal instance.call, { name: 'new', email: 'new@registrar.test', phone: '+1.2' } + end + + def test_handles_statuses + xml_string = <<-XML + + + + + + john-001 + + + new name + + +123.4 + new-email@inbox.test + + + Payment overdue. + + + + + + + + + + XML + + nokogiri_frame = Nokogiri::XML(xml_string).remove_namespaces! + instance = ::Deserializers::Xml::Contact.new(nokogiri_frame) + assert_equal instance.call, { name: 'new name', + email: 'new-email@inbox.test', + phone: '+123.4', + statuses_to_add: ['clientDeleteProhibited', + 'clientUpdateProhibited'], + statuses_to_remove: ['pendingDelete'] + } + end +end