diff --git a/app/controllers/concerns/epp_requestable.rb b/app/controllers/concerns/epp_requestable.rb new file mode 100644 index 000000000..d290c8e38 --- /dev/null +++ b/app/controllers/concerns/epp_requestable.rb @@ -0,0 +1,63 @@ +module EppRequestable + extend ActiveSupport::Concern + + included do + # before_action :validate_epp_user, only: :create + end + + def create + result = server.request(request_params[:payload]) + render_success(data: { xml: result.force_encoding('UTF-8') }) + rescue StandardError + handle_non_epp_errors(nil, I18n.t('errors.messages.epp_conn_error')) + end + + private + + # def validate_epp_user + # return unless handle_hello_request + + # handle_login_request + # server.close_connection + # rescue OpenSSL::SSL::SSLError => e + # Rails.logger.error "INVALID CERT: #{e}" + # Rails.logger.error "INVALID CERT DEBUG INFO: epp_hostname: #{ENV['epp_hostname']}," \ + # "port: #{ENV['epp_port']}, cert_path: #{ENV['cert_path']}, key_path: #{ENV['key_path']}" + # handle_non_epp_errors(nil, I18n.t('errors.messages.invalid_cert')) + # end + + # def handle_hello_request + # res = server.open_connection + # unless Nokogiri::XML(res).css('greeting') + # server.close_connection # just in case + # handle_non_epp_errors(nil, I18n.t('errors.messages.failed_epp_conn')) and return false + # end + # true + # end + + # def handle_login_request + # tag = current_user.username + # ex = EppXml::Session.new(cl_trid_prefix: tag) + # xml = ex.login(clID: { value: tag }, pw: { value: current_user.plain_text_password }) + # res = server.send_request(xml) + + # return if Nokogiri::XML(res).css('result').first['code'] == '1000' + + # handle_non_epp_errors(nil, Nokogiri::XML(res).css('result').text) + # end + + def server + client_cert = File.read(ENV['cert_path']) + client_key = File.read(ENV['key_path']) + port = ENV['epp_port'] || 700 + @server ||= Epp::Server.new({ server: ENV['epp_hostname'], tag: current_user.username, + password: current_user.plain_text_password, + port: port, + cert: OpenSSL::X509::Certificate.new(client_cert), + key: OpenSSL::PKey::RSA.new(client_key) }) + end + + def request_params + params.require(:xml_console).permit(:payload) + end +end diff --git a/app/controllers/repp/v1/registrar/xml_console_controller.rb b/app/controllers/repp/v1/registrar/xml_console_controller.rb new file mode 100644 index 000000000..cedf23819 --- /dev/null +++ b/app/controllers/repp/v1/registrar/xml_console_controller.rb @@ -0,0 +1,55 @@ +module Repp + module V1 + module Registrar + class XmlConsoleController < BaseController + include EppRequestable + + THROTTLED_ACTIONS = %i[load_xml].freeze + include Shunter::Integration::Throttle + + PREFS = %w[domain-ee contact-ee eis epp-ee].freeze + + SCHEMA_VERSIONS = { + 'epp-ee' => '1.0', + 'eis' => '1.0', + 'contact-ee' => '1.1', + 'default' => '1.2', + }.freeze + + def load_xml + cl_trid = "#{current_user.username}-#{Time.zone.now.to_i}" + obj = ActionController::Base.helpers.sanitize(params[:obj]) + epp_action = ActionController::Base.helpers.sanitize(params[:epp_action]) + xml_dir_path = Rails.root.join('app/views/epp/sample_requests').to_s + xml = File.read("#{xml_dir_path}/#{obj}/#{epp_action}.xml") + xml = prepare_payload(xml, cl_trid) + + render_success(data: { xml: xml }) + end + + private + + def prepare_payload(xml, cl_trid) + PREFS.map do |pref| + xml = load_schema_by_prefix(pref, xml) + end + + xml.gsub!('ABC-12345', "#{cl_trid}") + xml + end + + def load_schema_by_prefix(pref, xml) + version = version_by_prefix(pref) + xml.gsub!("\"#{pref}\"", + "\"#{Xsd::Schema.filename(for_prefix: pref.to_s, for_version: version)}\"") + xml + end + + def version_by_prefix(pref) + key = SCHEMA_VERSIONS.key?(pref) ? pref : 'default' + SCHEMA_VERSIONS[key] + end + end + end + end +end diff --git a/app/controllers/repp/v1/stats_controller.rb b/app/controllers/repp/v1/stats_controller.rb index 480c81a6b..ae8489c34 100644 --- a/app/controllers/repp/v1/stats_controller.rb +++ b/app/controllers/repp/v1/stats_controller.rb @@ -90,6 +90,8 @@ module Repp def calculate_market_share(domains_by_rar) sum = domains_by_rar.values.sum + return domains_by_rar if sum.zero? + domains_by_rar.transform_values do |v| value = v.to_f / sum * 100.0 value < 0.1 ? value.round(3) : value.round(1) diff --git a/app/models/domain.rb b/app/models/domain.rb index 2c2b363fa..f770b623f 100644 --- a/app/models/domain.rb +++ b/app/models/domain.rb @@ -65,7 +65,6 @@ class Domain < ApplicationRecord statuses.include? DomainStatus::SERVER_REGISTRANT_CHANGE_PROHIBITED end - # NB! contacts, admin_contacts, tech_contacts are empty for a new record has_many :domain_contacts, dependent: :destroy has_many :contacts, through: :domain_contacts, source: :contact diff --git a/app/models/legal_document.rb b/app/models/legal_document.rb index cafd04af7..e70fa2d12 100644 --- a/app/models/legal_document.rb +++ b/app/models/legal_document.rb @@ -1,4 +1,4 @@ -class LegalDocument < ApplicationRecord +class LegalDocument < ApplicationRecord # rubocop:disable Metrics/ClassLength include EppErrors MIN_BODY_SIZE = (1.37 * 3.kilobytes).ceil MAX_BODY_SIZE = 8.megabytes @@ -14,7 +14,7 @@ class LegalDocument < ApplicationRecord belongs_to :documentable, polymorphic: true - validate :val_body_length, if: ->(file) { file.path.blank? } + validate :val_body_length, if: ->(file) { file.path.blank? && (Rails.env.production? || Rails.env.test?) } before_create :add_creator before_save :save_to_filesystem, if: :body @@ -24,7 +24,7 @@ class LegalDocument < ApplicationRecord '2308' => [ %i[body length_more_than], %i[body length_less_than], - ] + ], } end @@ -41,12 +41,13 @@ class LegalDocument < ApplicationRecord digest = Digest::SHA1.new.update(binary).to_s loop do - rand = SecureRandom.random_number.to_s.last(4) - next if rand.to_i == 0 || rand.length < 4 - dir = "#{ENV['legal_documents_dir']}/#{Time.zone.now.strftime('%Y/%m/%d')}" - FileUtils.mkdir_p(dir, mode: 0775) - self.path = "#{dir}/#{Time.zone.now.to_formatted_s(:number)}_#{rand}.#{document_type}" - break unless File.file?(path) + rand = SecureRandom.random_number.to_s.last(4) + next if rand.to_i.zero? || rand.length < 4 + + dir = "#{ENV['legal_documents_dir']}/#{Time.zone.now.strftime('%Y/%m/%d')}" + FileUtils.mkdir_p(dir, mode: 0775) + self.path = "#{dir}/#{Time.zone.now.to_formatted_s(:number)}_#{rand}.#{document_type}" + break unless File.file?(path) end File.open(path, 'wb') { |f| f.write(binary) } unless Rails.env.test? @@ -69,50 +70,57 @@ class LegalDocument < ApplicationRecord start = Time.zone.now.to_f Rails.logger.info '-----> Removing legal documents duplicates' count = 0 - modified = Array.new + modified = [] - LegalDocument.where(documentable_type: "Domain").where.not(checksum: [nil, ""]).find_each do |orig_legal| + LegalDocument.where(documentable_type: 'Domain') + .where.not(checksum: [nil, '']) + .find_each do |orig_legal| next if modified.include?(orig_legal.checksum) - next if !File.exist?(orig_legal.path) + next unless File.exist?(orig_legal.path) + modified.push(orig_legal.checksum) - LegalDocument.where(documentable_type: "Domain", documentable_id: orig_legal.documentable_id). - where(checksum: orig_legal.checksum). - where.not(id: orig_legal.id).where.not(path: orig_legal.path).each do |new_legal| - unless modified.include?(orig_legal.id) - File.delete(new_legal.path) if File.exist?(new_legal.path) - new_legal.update(path: orig_legal.path) - count += 1 - Rails.logger.info "File #{new_legal.path} has been removed by Domain "\ - "#{new_legal.documentable_id}. Document id: #{new_legal.id}" - end + LegalDocument.where(documentable_type: 'Domain', documentable_id: orig_legal.documentable_id) + .where(checksum: orig_legal.checksum) + .where.not(id: orig_legal.id) + .where.not(path: orig_legal.path).each do |new_legal| + next if modified.include?(orig_legal.id) + + File.delete(new_legal.path) if File.exist?(new_legal.path) + new_legal.update(path: orig_legal.path) + count += 1 + Rails.logger.info "File #{new_legal.path} has been removed by Domain "\ + "#{new_legal.documentable_id}. Document id: #{new_legal.id}" end - contact_ids = Version::DomainVersion.where(item_id: orig_legal.documentable_id).distinct. - pluck("object->>'registrant_id'", "object_changes->>'registrant_id'", - "children->>'tech_contacts'", "children->>'admin_contacts'").flatten.uniq - contact_ids = contact_ids.map{|id| + contact_ids = Version::DomainVersion.where(item_id: orig_legal.documentable_id).distinct + .pluck("object->>'registrant_id'", + "object_changes->>'registrant_id'", + "children->>'tech_contacts'", + "children->>'admin_contacts'") + .flatten.uniq + contact_ids = contact_ids.map do |id| case id - when Hash - id["id"] - when String - JSON.parse(id) rescue id.to_i - else - id - end - }.flatten.compact.uniq - LegalDocument.where(documentable_type: "Contact", documentable_id: contact_ids). - where(checksum: orig_legal.checksum).where.not(path: orig_legal.path).each do |new_legal| - unless modified.include?(orig_legal.id) - File.delete(new_legal.path) if File.exist?(new_legal.path) - new_legal.update(path: orig_legal.path) - count += 1 - Rails.logger.info "File #{new_legal.path} has been removed by Contact "\ - "#{new_legal.documentable_id}. Document id: #{new_legal.id}" + when Hash + id['id'] + when String + JSON.parse(id) rescue id.to_i + else + id end + end.flatten.compact.uniq + LegalDocument.where(documentable_type: 'Contact', documentable_id: contact_ids) + .where(checksum: orig_legal.checksum) + .where.not(path: orig_legal.path).each do |new_legal| + next if modified.include?(orig_legal.id) + + File.delete(new_legal.path) if File.exist?(new_legal.path) + new_legal.update(path: orig_legal.path) + count += 1 + Rails.logger.info "File #{new_legal.path} has been removed by Contact "\ + "#{new_legal.documentable_id}. Document id: #{new_legal.id}" end end Rails.logger.info "-----> Duplicates fixed for #{count} rows in #{(Time.zone.now.to_f - start).round(2)} seconds" - end end diff --git a/app/views/epp/sample_requests/contact/check.xml b/app/views/epp/sample_requests/contact/check.xml new file mode 100644 index 000000000..f4c10d8bd --- /dev/null +++ b/app/views/epp/sample_requests/contact/check.xml @@ -0,0 +1,12 @@ + + + + + + sh8013 + + + ABC-12345 + + diff --git a/app/views/epp/sample_requests/contact/check_multiple.xml b/app/views/epp/sample_requests/contact/check_multiple.xml new file mode 100644 index 000000000..bdcff03dd --- /dev/null +++ b/app/views/epp/sample_requests/contact/check_multiple.xml @@ -0,0 +1,14 @@ + + + + + + sh8013 + sh13 + vsdfvq + + + ABC-12345 + + diff --git a/app/views/epp/sample_requests/contact/create.xml b/app/views/epp/sample_requests/contact/create.xml new file mode 100644 index 000000000..fc60b8311 --- /dev/null +++ b/app/views/epp/sample_requests/contact/create.xml @@ -0,0 +1,32 @@ + + + + + + + Sillius Soddus + + 123 Example Dr. + Suite 100 + + Megaton + F3 + 201-33 + EE + + + +372.1234567 + example@test.example + + + + + 123 + + dGVzdCBmYWlsCg== + + + + ABC-12345 + + diff --git a/app/views/epp/sample_requests/contact/delete.xml b/app/views/epp/sample_requests/contact/delete.xml new file mode 100644 index 000000000..28b50af64 --- /dev/null +++ b/app/views/epp/sample_requests/contact/delete.xml @@ -0,0 +1,22 @@ + + + + + + sh8013 + + wrong-one + + + + + + + dGVzdCBmYWlsCg== + + + + ABC-12345 + + diff --git a/app/views/epp/sample_requests/contact/info.xml b/app/views/epp/sample_requests/contact/info.xml new file mode 100644 index 000000000..44e5d5a1e --- /dev/null +++ b/app/views/epp/sample_requests/contact/info.xml @@ -0,0 +1,14 @@ + + + + + + sh8013 + + Aas34fq + + + + ABC-12345 + + diff --git a/app/views/epp/sample_requests/contact/update_chg.xml b/app/views/epp/sample_requests/contact/update_chg.xml new file mode 100644 index 000000000..61ea43202 --- /dev/null +++ b/app/views/epp/sample_requests/contact/update_chg.xml @@ -0,0 +1,36 @@ + + + + + + sh8013 + + + John Doe + + 123 Example Dr. + Suite 100 + Dulles + VA + 20166-6503 + EE + + + +123.7035555555 + jdoe@example.com + + 2fooBAR + + + + + + + + dGVzdCBmYWlsCg== + + + + ABC-12345 + + diff --git a/app/views/epp/sample_requests/domain/check.xml b/app/views/epp/sample_requests/domain/check.xml new file mode 100644 index 000000000..06492bcfe --- /dev/null +++ b/app/views/epp/sample_requests/domain/check.xml @@ -0,0 +1,12 @@ + + + + + + example.ee + + + ABC-12345 + + diff --git a/app/views/epp/sample_requests/domain/client_hold.xml b/app/views/epp/sample_requests/domain/client_hold.xml new file mode 100644 index 000000000..68f0b778e --- /dev/null +++ b/app/views/epp/sample_requests/domain/client_hold.xml @@ -0,0 +1,15 @@ + + + + + + example.ee + + + + + + timo-1579351654 + + diff --git a/app/views/epp/sample_requests/domain/create.xml b/app/views/epp/sample_requests/domain/create.xml new file mode 100644 index 000000000..f57fcfe30 --- /dev/null +++ b/app/views/epp/sample_requests/domain/create.xml @@ -0,0 +1,42 @@ + + + + + + example.ee + 1 + + + ns1.example.net + + + ns2.example.net + 192.0.2.2 + 1080:0:0:0:8:800:200C:417A + + + jd1234 + sh8013 + sh8013 + sh801333 + + + + + + 257 + 3 + 8 + AwEAAddt2AkLfYGKgiEZB5SmIF8EvrjxNMH6HtxWEA4RJ9Ao6LCWheg8 + + + + + dGVzdCBmYWlsCg== + + + + ABC-12345 + + diff --git a/app/views/epp/sample_requests/domain/delete.xml b/app/views/epp/sample_requests/domain/delete.xml new file mode 100644 index 000000000..f47bb79eb --- /dev/null +++ b/app/views/epp/sample_requests/domain/delete.xml @@ -0,0 +1,19 @@ + + + + + + example.ee + + + + + + dGVzdCBmYWlsCg== + + + + ABC-12345 + + diff --git a/app/views/epp/sample_requests/domain/info.xml b/app/views/epp/sample_requests/domain/info.xml new file mode 100644 index 000000000..210396c3b --- /dev/null +++ b/app/views/epp/sample_requests/domain/info.xml @@ -0,0 +1,15 @@ + + + + + + example.ee + + 2fooBAR + + + + ABC-12345 + + diff --git a/app/views/epp/sample_requests/domain/renew.xml b/app/views/epp/sample_requests/domain/renew.xml new file mode 100644 index 000000000..6ef479468 --- /dev/null +++ b/app/views/epp/sample_requests/domain/renew.xml @@ -0,0 +1,14 @@ + + + + + + example.ee + 2014-08-07 + 1 + + + ABC-12345 + + diff --git a/app/views/epp/sample_requests/domain/transfer.xml b/app/views/epp/sample_requests/domain/transfer.xml new file mode 100644 index 000000000..6ab951eeb --- /dev/null +++ b/app/views/epp/sample_requests/domain/transfer.xml @@ -0,0 +1,15 @@ + + + + + + example.ee + + 2BARfoo + + + + ABC-12345 + + diff --git a/app/views/epp/sample_requests/domain/update.xml b/app/views/epp/sample_requests/domain/update.xml new file mode 100644 index 000000000..c087245e7 --- /dev/null +++ b/app/views/epp/sample_requests/domain/update.xml @@ -0,0 +1,54 @@ + + + + + + example.ee + + + + ns1.example.com + + + ns2.example.com + + + mak21 + + + + + ns1.example.net + + + mak21 + + + mak21 + + newpw + + + + + + + + + 257 + 3 + 8 + 700b97b591ed27ec2590d19f06f88bba700b97b591ed27ec2590d19f + + + + + + dGVzdCBmYWlsCg== + + + + ABC-12345 + + diff --git a/app/views/epp/sample_requests/poll/poll.xml b/app/views/epp/sample_requests/poll/poll.xml new file mode 100644 index 000000000..5ffed010e --- /dev/null +++ b/app/views/epp/sample_requests/poll/poll.xml @@ -0,0 +1,7 @@ + + + + + ABC-12345 + + diff --git a/config/application.yml.sample b/config/application.yml.sample index dffeea5be..61bf6f223 100644 --- a/config/application.yml.sample +++ b/config/application.yml.sample @@ -61,6 +61,13 @@ contact_org_enabled: 'false' # System default for legal document types is: pdf,asice,sce,asics,scs,adoc,edoc,bdoc,ddoc,zip,rar,gz,tar,7z,odt,doc,docx # legal_document_types: "pdf,asice,sce,asics,scs,adoc,edoc,bdoc,ddoc,zip,rar,gz,tar,7z,odt,doc,docx" +# +# REGISTRAR configuration +# +epp_port: '700' +cert_path: '/opt/ca/certs/webclient.crt.pem' +key_path: '/opt/ca/private/webclient.key.pem' +epp_hostname: 'epp_proxy' repp_url: 'http://epp:3000/repp/v1/' # Estonian Company Register diff --git a/config/locales/en.yml b/config/locales/en.yml index d42efd0a5..7a6fa2eb9 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -187,6 +187,9 @@ en: required_ident_attribute_missing: "Required ident attribute missing: %{key}" invalid_iso31661_alpha2: does not conform to ISO 3166-1 alpha-2 standard invalid_iso8601_date: has invalid date format YYYY-MM-DD (ISO 8601) + invalid_cert: 'Invalid certificate' + failed_epp_conn: 'Failed to open connection to EPP server!' + epp_conn_error: 'CONNECTION ERROR - Is the EPP server running?' code: 'Code' action: 'Action' diff --git a/config/routes.rb b/config/routes.rb index 21fc16224..d8d52f322 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -106,7 +106,7 @@ Rails.application.routes.draw do end end namespace :registrar do - resources :notifications, only: [:index, :show, :update] do + resources :notifications, only: %i[index show update] do collection do get '/all_notifications', to: 'notifications#all_notifications' end @@ -128,6 +128,11 @@ Rails.application.routes.draw do post '/tara_callback', to: 'auth#tara_callback' end end + resource :xml_console, controller: 'xml_console', only: %i[create] do + collection do + get 'load_xml' + end + end end resources :domains, constraints: { id: /.*/ } do resources :nameservers, only: %i[index create destroy], constraints: { id: /.*/ }, controller: 'domains/nameservers' @@ -136,8 +141,8 @@ Rails.application.routes.draw do resources :renew, only: %i[create], constraints: { id: /.*/ }, controller: 'domains/renews' resources :transfer, only: %i[create], constraints: { id: /.*/ }, controller: 'domains/transfers' resources :statuses, only: %i[update destroy], constraints: { id: /.*/ }, controller: 'domains/statuses' - match "dnssec", to: "domains/dnssec#destroy", via: "delete", defaults: { id: nil } - match "contacts", to: "domains/contacts#destroy", via: "delete", defaults: { id: nil } + match 'dnssec', to: 'domains/dnssec#destroy', via: 'delete', defaults: { id: nil } + match 'contacts', to: 'domains/contacts#destroy', via: 'delete', defaults: { id: nil } collection do get ':id/transfer_info', to: 'domains#transfer_info', constraints: { id: /.*/ } post 'transfer', to: 'domains#transfer' diff --git a/test/integration/repp/v1/registrar/xml_console_test.rb b/test/integration/repp/v1/registrar/xml_console_test.rb new file mode 100644 index 000000000..db37dbebc --- /dev/null +++ b/test/integration/repp/v1/registrar/xml_console_test.rb @@ -0,0 +1,108 @@ +require 'test_helper' + +class ReppV1RegistrarXmlConsoleTest < ActionDispatch::IntegrationTest + def setup + @user = users(:api_bestnames) + token = Base64.encode64("#{@user.username}:#{@user.plain_text_password}") + token = "Basic #{token}" + + @auth_headers = { 'Authorization' => token } + adapter = ENV['shunter_default_adapter'].constantize.new + adapter&.clear! + end + + def test_load_schema_path + get load_xml_repp_v1_registrar_xml_console_path, params: { obj: 'domain', epp_action: 'update' }, + headers: @auth_headers + + assert_response :ok + json = JSON.parse(response.body, symbolize_names: true) + assert_equal update_payload, json[:data][:xml] + end + + # TO BE REFACTORED + # def test_check_schema_path + # @auth_headers['Content-Type'] = 'application/json' + # params = { xml_console: { payload: payload } } + # post repp_v1_registrar_xml_console_path, params: params.to_json, + # headers: @auth_headers + + # assert_response :ok + # end + + private + + def payload + <<~XML + + + + + + auction.test + + + + + XML + end + + def update_payload + <<~XML + + + + + + example.ee + + + + ns1.example.com + + + ns2.example.com + + + mak21 + + + + + ns1.example.net + + + mak21 + + + mak21 + + newpw + + + + + + + + + 257 + 3 + 8 + 700b97b591ed27ec2590d19f06f88bba700b97b591ed27ec2590d19f + + + + + + dGVzdCBmYWlsCg== + + + + test_bestnames-#{Time.zone.now.to_i} + + + XML + end +end \ No newline at end of file