diff --git a/.reek b/.reek index aaf662290..6cdd58c75 100644 --- a/.reek +++ b/.reek @@ -111,7 +111,6 @@ UncommunicativeVariableName: - Invoice#cancel_overdue_invoices - Legacy::Db - LegalDocument#save_to_filesystem - - Nameserver#replace_hostname_ends - RegistrantUser#find_or_create_by_idc_data - RegistrantUser#find_or_create_by_mid_data - Registrar @@ -318,7 +317,6 @@ DuplicateMethodCall: - Invoice#set_invoice_number - LegalDocument#save_to_filesystem - LegalDocument#self.remove_duplicates - - Nameserver#replace_hostname_ends - Setting#self.params_errors - Setting#self.reload_settings! - Soap::Arireg#associated_businesses @@ -806,7 +804,6 @@ TooManyStatements: - Invoice#set_invoice_number - LegalDocument#save_to_filesystem - LegalDocument#self.remove_duplicates - - Nameserver#replace_hostname_ends - RegistrantUser#find_or_create_by_idc_data - Registrar#generate_iso_11649_reference_no - Soap::Arireg#associated_businesses @@ -1003,7 +1000,6 @@ NestedIterators: - Domain#self.to_csv - Epp::Domain#nameservers_from - LegalDocument#self.remove_duplicates - - Nameserver#replace_hostname_ends - RegistrantPresenter#domain_names_with_roles - UniquenessMultiValidator#validate_each UnusedParameters: @@ -1140,7 +1136,5 @@ UncommunicativeParameterName: - Dnskey#int_to_hex UncommunicativeMethodName: exclude: - - Nameserver#val_ipv4 - - Nameserver#val_ipv6 - Soap::Arireg#country_code_3 - WhiteIp#validate_ipv4_and_ipv6 diff --git a/app/api/repp/api.rb b/app/api/repp/api.rb index aec46c68b..9c12470a0 100644 --- a/app/api/repp/api.rb +++ b/app/api/repp/api.rb @@ -58,5 +58,6 @@ module Repp mount Repp::ContactV1 mount Repp::AccountV1 mount Repp::DomainTransfersV1 + mount Repp::NameserversV1 end end diff --git a/app/api/repp/nameservers_v1.rb b/app/api/repp/nameservers_v1.rb new file mode 100644 index 000000000..d653adf7f --- /dev/null +++ b/app/api/repp/nameservers_v1.rb @@ -0,0 +1,44 @@ +module Repp + class NameserversV1 < Grape::API + version 'v1', using: :path + + resource 'registrar/nameservers' do + put '/' do + params do + requires :data, type: Hash, allow_blank: false do + requires :type, type: String, allow_blank: false + requires :id, type: String, allow_blank: false + requires :attributes, type: Hash, allow_blank: false do + requires :hostname, type: String, allow_blank: false + requires :ipv4, type: Array + requires :ipv6, type: Array + end + end + end + + hostname = params[:data][:id] + + unless current_user.registrar.nameservers.exists?(hostname: hostname) + error!({ errors: [{ title: "Hostname #{hostname} does not exist" }] }, 404) + end + + new_attributes = { + hostname: params[:data][:attributes][:hostname], + ipv4: params[:data][:attributes][:ipv4], + ipv6: params[:data][:attributes][:ipv6], + } + + begin + current_user.registrar.replace_nameservers(hostname, new_attributes) + rescue ActiveRecord::RecordInvalid => e + error!({ errors: e.record.errors.full_messages.map { |error| { title: error } } }, 400) + end + + status 200 + @response = { data: { type: 'nameserver', + id: params[:data][:attributes][:hostname], + attributes: params[:data][:attributes] } } + end + end + end +end diff --git a/app/controllers/registrar/registrar_nameservers_controller.rb b/app/controllers/registrar/registrar_nameservers_controller.rb new file mode 100644 index 000000000..1af3cde64 --- /dev/null +++ b/app/controllers/registrar/registrar_nameservers_controller.rb @@ -0,0 +1,59 @@ +class Registrar + class RegistrarNameserversController < DeppController + def edit + authorize! :manage, :repp + end + + def update + authorize! :manage, :repp + + ipv4 = params[:ipv4].split("\r\n") + ipv6 = params[:ipv6].split("\r\n") + + uri = URI.parse("#{ENV['repp_url']}registrar/nameservers") + request = Net::HTTP::Put.new(uri, 'Content-Type' => 'application/json') + request.body = { data: { type: 'nameserver', id: params[:old_hostname], + attributes: { hostname: params[:new_hostname], + ipv4: ipv4, + ipv6: ipv6 } } }.to_json + request.basic_auth(current_user.username, current_user.password) + + if Rails.env.test? + response = Net::HTTP.start(uri.hostname, uri.port, + use_ssl: (uri.scheme == 'https'), + verify_mode: OpenSSL::SSL::VERIFY_NONE) do |http| + http.request(request) + end + elsif Rails.env.development? + client_cert = File.read(ENV['cert_path']) + client_key = File.read(ENV['key_path']) + response = Net::HTTP.start(uri.hostname, uri.port, + use_ssl: (uri.scheme == 'https'), + verify_mode: OpenSSL::SSL::VERIFY_NONE, + cert: OpenSSL::X509::Certificate.new(client_cert), + key: OpenSSL::PKey::RSA.new(client_key)) do |http| + http.request(request) + end + else + client_cert = File.read(ENV['cert_path']) + client_key = File.read(ENV['key_path']) + response = Net::HTTP.start(uri.hostname, uri.port, + use_ssl: (uri.scheme == 'https'), + cert: OpenSSL::X509::Certificate.new(client_cert), + key: OpenSSL::PKey::RSA.new(client_key)) do |http| + http.request(request) + end + end + + parsed_response = JSON.parse(response.body, symbolize_names: true) + + if response.code == '200' + flash[:notice] = t '.replaced' + redirect_to registrar_domains_url + else + @api_errors = parsed_response[:errors] + render :edit + end + end + end +end diff --git a/app/models/domain.rb b/app/models/domain.rb index 54dfe608e..5cc81723f 100644 --- a/app/models/domain.rb +++ b/app/models/domain.rb @@ -40,7 +40,7 @@ class Domain < ActiveRecord::Base has_many :contacts, through: :domain_contacts, source: :contact has_many :admin_contacts, through: :admin_domain_contacts, source: :contact has_many :tech_contacts, through: :tech_domain_contacts, source: :contact - has_many :nameservers, dependent: :destroy + has_many :nameservers, dependent: :destroy, inverse_of: :domain accepts_nested_attributes_for :nameservers, allow_destroy: true, reject_if: proc { |attrs| attrs[:hostname].blank? } diff --git a/app/models/nameserver.rb b/app/models/nameserver.rb index fb56f1198..d9a5f2a75 100644 --- a/app/models/nameserver.rb +++ b/app/models/nameserver.rb @@ -2,18 +2,31 @@ class Nameserver < ActiveRecord::Base include Versions # version/nameserver_version.rb include EppErrors - # belongs_to :registrar - belongs_to :domain + HOSTNAME_REGEXP = /\A(([a-zA-Z0-9]|[a-zA-ZäöüõšžÄÖÜÕŠŽ0-9][a-zA-ZäöüõšžÄÖÜÕŠŽ0-9\-] + *[a-zA-ZäöüõšžÄÖÜÕŠŽ0-9])\.) + *([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-] + *[A-Za-z0-9])\z/x - # scope :owned_by_registrar, -> (registrar) { joins(:domain).where('domains.registrar_id = ?', registrar.id) } + IPV4_REGEXP = /\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/x - # rubocop: disable Metrics/LineLength - validates :hostname, format: { with: /\A(([a-zA-Z0-9]|[a-zA-ZäöüõšžÄÖÜÕŠŽ0-9][a-zA-ZäöüõšžÄÖÜÕŠŽ0-9\-]*[a-zA-ZäöüõšžÄÖÜÕŠŽ0-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_blank: 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_blank: true } - validate :val_ipv4 - validate :val_ipv6 - # rubocop: enable Metrics/LineLength + IPV6_REGEXP = /(([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]))/x + + belongs_to :domain, required: true + + validates :hostname, presence: true + validates :hostname, format: { with: HOSTNAME_REGEXP }, allow_blank: true + validate :validate_ipv4_format + validate :validate_ipv6_format + validate :require_ip, if: :glue_record_required? before_validation :normalize_attributes before_validation :check_puny_symbols @@ -38,6 +51,39 @@ class Nameserver < ActiveRecord::Base } end + def to_s + hostname + end + + def hostname=(hostname) + self[:hostname] = SimpleIDN.to_unicode(hostname) + self[:hostname_puny] = SimpleIDN.to_ascii(hostname) + end + + class << self + def find_by_hash_params params + params = params.with_indifferent_access + rel = all + rel = rel.where(hostname: params[:hostname]) + rel + end + + def hostnames + pluck(:hostname) + end + end + + private + + def require_ip + errors.add(:base, :ip_required) if ipv4.blank? && ipv6.blank? + end + + def glue_record_required? + return unless hostname? && domain + hostname.end_with?(domain.name) + end + def normalize_attributes self.hostname = hostname.try(:strip).try(:downcase) self.ipv4 = Array(ipv4).reject(&:blank?).map(&:strip) @@ -45,6 +91,8 @@ class Nameserver < ActiveRecord::Base end def check_label_length + return unless hostname + hostname_puny.split('.').each do |label| errors.add(:hostname, :puny_to_long) if label.length > 63 end @@ -55,71 +103,15 @@ class Nameserver < ActiveRecord::Base errors.add(:hostname, :invalid) if hostname =~ regexp end - def to_s - hostname - end - - def hostname=(hostname) - self[:hostname] = SimpleIDN.to_unicode(hostname) - self[:hostname_puny] = SimpleIDN.to_ascii(hostname) - end - - def val_ipv4 - regexp = /\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/ + def validate_ipv4_format ipv4.to_a.each do |ip| - errors.add(:ipv4, :invalid) unless ip =~ regexp + errors.add(:ipv4, :invalid) unless ip =~ IPV4_REGEXP end end - def val_ipv6 - regexp = /(([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]))/ + + def validate_ipv6_format ipv6.to_a.each do |ip| - errors.add(:ipv6, :invalid) unless ip =~ regexp - end - end - - class << self - def replace_hostname_ends(domains, old_end, new_end) - domains = domains.where('EXISTS( - select 1 from nameservers ns where ns.domain_id = domains.id AND ns.hostname LIKE ? - )', "%#{old_end}") - - count, success_count = 0.0, 0.0 - domains.each do |d| - ns_attrs = { nameservers_attributes: [] } - - d.nameservers.each do |ns| - next unless ns.hostname.end_with?(old_end) - - hn = ns.hostname.chomp(old_end) - ns_attrs[:nameservers_attributes] << { - id: ns.id, - hostname: "#{hn}#{new_end}" - } - end - - success_count += 1 if d.update(ns_attrs) - count += 1 - end - - return 'replaced_none' if count == 0.0 - - prc = success_count / count - - return 'replaced_all' if prc == 1.0 - 'replaced_some' - end - - def find_by_hash_params params - params = params.with_indifferent_access - rel = all - rel = rel.where(hostname: params[:hostname]) - # rel = rel.where(hostname: params[:hostname]) if params[:ipv4] - # ignoring ips - rel - end - - def hostnames - pluck(:hostname) + errors.add(:ipv6, :invalid) unless ip =~ IPV6_REGEXP end end end diff --git a/app/models/registrar.rb b/app/models/registrar.rb index 46022808f..6a10983d2 100644 --- a/app/models/registrar.rb +++ b/app/models/registrar.rb @@ -158,6 +158,20 @@ class Registrar < ActiveRecord::Base white_ips.api.pluck(:ipv4, :ipv6).flatten.include?(ip) end + # Audit log is needed, therefore no raw SQL + def replace_nameservers(hostname, new_attributes) + transaction do + nameservers.where(hostname: hostname).find_each do |original_nameserver| + new_nameserver = Nameserver.new + new_nameserver.domain = original_nameserver.domain + new_nameserver.attributes = new_attributes + new_nameserver.save! + + original_nameserver.destroy! + end + end + end + private def set_defaults diff --git a/app/views/registrar/domains/index.html.erb b/app/views/registrar/domains/index.html.erb index a62619ca4..319f0d04f 100644 --- a/app/views/registrar/domains/index.html.erb +++ b/app/views/registrar/domains/index.html.erb @@ -1,12 +1,14 @@